Skip to content

v0.6.0

Latest

Choose a tag to compare

@newgene newgene released this 03 Oct 01:06
2cd3f4d

Breaking

  • Requires fastmcp 4.x and httpx2. If you construct HTTP clients
    yourself and hand them to this package, they must now be httpx2 clients.
    For fastmcp 3.x, stay on 0.5.0.
  • smartapi_mcp.log.logger is gone. Each module now uses
    logging.getLogger(__name__); use configure_logging() (exported from the
    package) to get the previous stderr output.

Added

  • --api_set all (env SMARTAPI_API_SET=all): every API the registry
    reports as reachable, BioThings or not, via
    _status.uptime_status:pass. This was already expressible as
    --smartapi_q '_status.uptime_status:pass' but undiscoverable, and it only
    became practical with the defaults introduced in 0.4.0. Measured on the 106
    matching APIs: 7 listed tools, ~1.9k tokens of tools/list, ~19s startup,
    119 MB RSS
    — the BioThings family goes through the facade without
    downloading a single spec, and the remaining ~294 per-API tools sit behind
    tool search. Served flat as per-API tools the same set would be ~592 tools
    and roughly 340k tokens, so the facade and search defaults are what make
    it viable at all.
    Also exported as WORKING_APIS_QUERY from smartapi_mcp.smartapi.
  • --spec_url (env SMARTAPI_SPEC_URLS): serve an API straight from its
    spec -- an http(s) URL, a file:// URL or a local path, JSON or YAML --
    instead of a registry id. For testing a spec before it is registered, or an
    updated version of a registered one. Repeatable or comma-separated, and
    usable alone or alongside --api_set / --smartapi_id(s) / --smartapi_q.
    These APIs always get per-API tools (never the facade) and stay listed when
    tool search collapses everything else. A spec that cannot be served stops the
    server with the reason, rather than being skipped as a registry API would be.
    Local files are never cached, so edits are picked up on restart. External
    $refs are still refused. Python API: build_server_for_set(spec_urls=...),
    build_spec_url_servers(), get_mcp_server_from_url(), and fetch_spec()
    now accepts local paths.
  • pyyaml is now a declared dependency. YAML specs already parsed because
    fastmcp installs PyYAML indirectly, but --spec_url drafts are usually YAML,
    so it is no longer left to chance.
  • configure_logging(level, stream=None, *, color=None) installs the coloured
    stderr handler on the smartapi_mcp logger, and
    configure_third_party_logging() applies the same level to fastmcp, mcp and
    httpx2. The CLI calls both; importing the package calls neither.

Changed

  • Migrated to fastmcp 4.x and httpx2. fastmcp 4 moves to the MCP 2.x SDK
    and replaces httpx with httpx2, a
    continuation of httpx under a new package name; FastMCP.from_openapi() types
    its client parameter as httpx2.AsyncClient. Pins are now
    fastmcp>=4.0.1,<5 and httpx2>=2.5,<3, and smartapi_mcp.openapi,
    .smartapi and .biothings import httpx2 directly.
    Everything we use from httpx exists in httpx2 under the same names, with the
    same exception hierarchy (HTTPStatusError, ConnectError,
    TimeoutException and TransportError all still subclass HTTPError, which
    the spec-fetch retry logic depends on), so the migration was a module rename
    rather than a rewrite. Every fastmcp API this package uses survives 4.x at an
    unchanged import path: FastMCP.from_openapi, fastmcp.client.Client,
    fastmcp.tools.Tool, fastmcp.prompts.Prompt,
    fastmcp.utilities.openapi.format_description_with_responses and the
    fastmcp.server.transforms.search transforms.
    Otherwise the migration is not behavioural. Verified against a recording of
    the fastmcp 3 output over the same 107 uptime-passing registry APIs
    (scripts/check_spec_parity.py): the same 92 APIs build, no tool names
    change, no descriptions change, and there are no structural regressions.
    401 of 592 input schemas are byte-identical; the other 191 differ only by
    additions, dominated by 244 example values that fastmcp 3 omitted from
    input schemas. Two tools on one API (Aragorn TRAPI) additionally have their
    request body flattened: where fastmcp 3 exposed a single nested request
    object parameter, 4.x names each body field individually (message,
    log_level, workflow, ...), which is easier for a model to fill in. The
    wire format is unchanged -- the JSON body is still sent nested as the API
    expects -- and a test now pins both halves of that, since it is fastmcp
    behaviour we depend on rather than something this package controls.
  • Logging moved from loguru to the standard library, and importing this
    package no longer reconfigures the host application's logging.
    loguru has a
    single global logger, so installing our own sink meant calling
    logger.remove() at import time -- which removed the application's
    handlers too. Importing smartapi_mcp would silently redirect an embedding
    app's logs into our stderr sink and drop whatever it had configured, verified
    by reproducing it. Now each module holds its own logging.getLogger(__name__),
    the only import-time action is a NullHandler on the smartapi_mcp logger,
    and records propagate to the application until it decides otherwise. CLI log
    output is unchanged: same format, same colours, same call-site detail.
  • The log level can now be set from the environment, like every other
    option: SMARTAPI_LOG_LEVEL, or the generic LOG_LEVEL, with --log-level
    still taking precedence. The README's configuration examples already used
    both names, but neither was read. An unrecognised value falls back to INFO
    with a warning rather than failing, since LOG_LEVEL may be set for another
    program.
  • --log-level now also reaches fastmcp, mcp and httpx2. DEBUG turns on
    their diagnostics, and WARNING and above quieten them. At the default
    INFO their own defaults are kept, since mcp and httpx2 log every request at
    INFO.
  • get_format() now returns a logging format string (%(levelname)s,
    %(name)s, ...) rather than loguru's brace-and-markup syntax. It is still
    exported, but configure_logging() is the supported entry point.

Fixed

  • search_tools now says what its index covers. fastmcp describes its
    search tool as "Search for tools using natural language", which gives a model
    no way to know that the pinned BioThings facade tools are deliberately not
    in the index. On the full working set,
    search_tools("gene annotation by entrez id") returned QuickGO and BTE
    instead of directing the caller at biothings_query. The description now
    states how many tools are searchable, names the facade tools that are not,
    and lists the domains they own. Applied through the transform's public
    transform_tools hook, because the synthetic tools are rebuilt on every
    list_tools call, so mutating the returned object does not stick.
  • get_base_server_url() now honours the x-maturity extension. It only
    inspected the free-text description for "Production" (or matched
    ci.transltr.io), so Translator specs that plainly declare a production
    server were refused. Checked after the existing rules, so no API that
    already resolved changes URL. On the registry's uptime-passing set this took
    coverage from 92 to 97 of 106 APIs served when measured; of the rest, the
    APIs with no production server are now served by the first-server fallback
    below.
  • An API with several servers but no identifiable production server is now
    served from its first server, with a warning, instead of being skipped.

    This is the default OpenAPI tooling (Swagger UI, generated clients) uses. It
    runs only after every existing rule, so no API that already resolved changes
    URL -- checked against all 102 loadable specs in the registry's
    uptime-passing set: 101 unchanged, and Identifiers.org (three mirrors, none
    labelled production) goes from skipped to served. Relative URLs are skipped,
    and so are servers whose x-maturity says development, testing or
    staging: a spec declaring only those is stating it has no production
    deployment, so it is still refused.
  • "Production" in a server description is matched case-insensitively, and
    "non-production" no longer counts.
    "production server" was not recognised
    and "Non-Production test server" was taken as production.
  • A servers entry without a url raised KeyError: 'url' from
    get_base_server_url() instead of the clear ValueError that follows, and a
    spec with no servers at all raised KeyError: 'servers'.
  • The CLI's signal handler logged a literal %s instead of the signal
    number.
    logger.debug("Received signal %s, ...", sig) used %-style
    arguments, but loguru formats with braces, so the argument was silently
    dropped. The standard library interpolates it correctly.

Removed

  • Dropped the loguru dependency in favour of the standard library's
    logging. This package used only debug/info/warning/error -- none of
    loguru's distinguishing features (no .bind(), .catch(), .opt(),
    serialization or rotation) -- and nothing else in the dependency tree required
    it, so it is one fewer package (426 KB) for no loss of capability.
  • smartapi_mcp.log.logger, the loguru logger the other modules used to import
    (see Breaking).
  • The API_SPEC_URL and API_BASE_URL environment variables, and the
    Config.api_spec_url / Config.api_base_url fields they set. They were
    still accepted after awslabs was dropped in 0.5.0, but nothing read them, so
    setting either silently did nothing. To serve a spec from a URL, use
    --spec_url / SMARTAPI_SPEC_URLS.