Repository navigation
v0.4.0 - fastmcp 3.x migration
First of two planned releases. This one is the fastmcp 3.x migration and
the features and fixes built on it. The awslabs_openapi_mcp_server removal
follows as 0.5.0.
Upgrading
Two default behaviour changes:
--tool-searchnow defaults toauto. A server above the
--tool-search-threshold(15 tools) listssearch_toolsandcall_tool
instead of every tool; every tool stays callable viacall_tool. Pass
--tool-search offfor the previous behaviour.biothings_coregained MyTaxon.info (6 APIs;biothings_testis now 7).
Requires fastmcp>=3.3.1,<4 and awslabs_openapi_mcp_server>=1.1.5,<2.
Added
-
CORE_BIOTHINGS_API_IDSinsmartapi_mcp.smartapi: the canonical,
broad-coverage BioThings annotation services, as distinct from the ~50
single-source satellite APIs.biothings_coreandbiothings_testare now
derived from it, so "which APIs are core" is stated once instead of being
duplicated per preset. -
--tool-search autois now the default (envSMARTAPI_TOOL_SEARCH). Search
turns on once the merged server reaches--tool-search-thresholdtools
(default 15, envTOOL_SEARCH_THRESHOLD); smaller catalogs keep their direct
listing. Combined with the BioThings facade this gives a hybrid server: the
facade answers BioThings queries directly (lexical search is weakest there,
because the generated per-API descriptions are near-identical boilerplate) and
search covers the non-BioThings tail (where it measures 79-86% recall@10).
This changes default behaviour for sets above the threshold: e.g. a
biothings_allper-API server previously listed ~314 tools and now lists 2.
Pass--tool-search offto restore the old behaviour. -
--tool-search {auto,off,bm25,regex}(envSMARTAPI_TOOL_SEARCH): collapses the
tool listing behind a search interface instead of listing every tool, using
fastmcp 3's search transforms. Clients seesearch_toolsandcall_tooland
discover tools on demand; every tool stays callable throughcall_tool. This
addresses the per-API tool explosion where the BioThings facade does not apply
(biothings_allis ~50 APIs at ~6 tools each). Facade tools are pinned so they
stay listed and only the per-API long tail is collapsed.
--tool-search-max-results(envTOOL_SEARCH_MAX_RESULTS) caps the hits per
search; the default is 10 (measured recall@10 vs recall@5: BioThings
65%->80%, non-BioThings 79%->86%, for roughly 1k extra tokens per search).
Results are serialized as Markdown, roughly half the size of fastmcp's default
JSON serialization. -
apply_tool_search()insmartapi_mcp.serverfor programmatic use, plus
tool_search/tool_search_max_results/tool_search_thresholdarguments on
build_server_for_set().
Fixed
-
TRAPI services are no longer served through the BioThings facade.
BioThings Explorer and Service Provider carry thebiothingstag but speak
the Translator Reasoner query-graph protocol, not the BioThings annotation
interface, so none of the generic facade tools apply. They are now excluded by
the newis_biothings_family()predicate (taggedbiothingsand not
trapi) and served with faithful per-API tools instead.
This was returning wrong answers, not merely hiding endpoints: the facade
infers an entity type from the first/{type}/{id}-shaped path, and BTE's
GET /asyncquery_status/{id}matches that shape, sobiothings_getwould
request/asyncquery_status/<id>and hand back a job status as though it were
an annotation record — with no error.biothings_allavoided this via its
NOT tags.name=trapifilter, but any set assembled by id or by a
tags.name:biothingsquery did not. -
The facade's
list_biothings_apisranking was scoring by verbosity.
rank_apis()summedstr.count()of every query token over the raw API text,
so (a) stopwords dominated — "get a gene annotation by its Entrez gene id" was
driven by "get"/"a"/"by"/"its"/"id" — (b) substrings matched, with "id" hitting
inside "identifier", "candidate" and "provide", and (c) repetition was
rewarded, ranking MyGeneSet above MyGene. It now uses word-boundary tokens,
a stopword list, binary term frequency, Robertson/Sparck-Jones IDF, and 3x
weighting for name/title/tag hits over description hits. Measured on 20
BioThings intents against the live registry: recall@10 75% -> 85%,
recall@5 70% -> 80%, which brings facade discovery level with the BM25
tool-search path. -
A single unloadable API no longer aborts the whole server. Per-API servers
were built with a bare list comprehension, so one bad spec took down every
other API in the set. Measured against the registry's uptime-passing set, 15
of 107 APIs (about one in seven) fail to load -- external$refs (refused by
awslabs 1.x as an SSRF guard), invalid OpenAPI schemas, missingservers
blocks -- which made--smartapi_q '_status.uptime_status:pass'impossible to
start at all. Those APIs are now skipped with a warning and a summary count,
and the rest are served. Newbuild_api_servers()returns
(servers, failures)for callers that want the detail.
This also catchesSystemExit, not justException: awslabs reports every
spec error by callingsys.exit(1)from inside the library, so a spec that
fastmcp itself rejected (e.g. an OpenAPI 3.0 document using"type": "null")
still aborted the whole build. It killed a 27-API run at API 17 against the
uptime-passing set. -
A spec that parses but yields no tools is now a warning, not an error.
_merge_servers_into()raisedAttributeErrorin that case, which likewise
discarded every other API in the set. -
Environment variables for
--facade,--facade-threshold,--tool-searchand
--tool-search-max-resultswere ignored when running via the CLI.load_config
assigns fromargswhenever the attribute is truthy, so a non-Noneargparse
default ("auto",10,"off",5) silently overwrote the environment on every
invocation —SMARTAPI_FACADE=offhad no effect despite being documented in
--help. Those four defaults are nowNone, leaving the effective defaults on
Config, so precedence is CLI > environment > default. (FACADE_STRICTwas
unaffected:store_truedefaults toFalse, which is falsy.)
Changed
-
biothings_corenow includes MyTaxon.info, so the preset is 6 APIs, not 5
(andbiothings_testis 7). MyTaxon is a coreMy*annotation service by
every other measure and was the odd one out. -
The facade's
list_biothings_apisnow prefers the core APIs when several
APIs match a query, via aCORE_API_BOOSTmultiplier of 1.2 inrank_apis().
Broad-coverage services are the worst served by pure lexical scoring --
being general means their descriptions carry the least distinctive vocabulary,
while single-source satellites read as highly specific -- so a lexical ranker
systematically under-ranks exactly the APIs a user most often wants.
The boost is multiplicative, so a zero score stays zero and a core API is
never promoted into a query it does not match.
Measured on 20 BioThings intents: with the registry descriptions as they are
today, recall@5 goes 16/20 -> 19/20 (MRR 0.71 -> 0.82) and the core-API
intents go 3/6 -> 6/6. 1.2 is deliberately the smallest value that captures
the benefit: larger values add no recall and cost ranking quality once the
registry metadata improves (at 3.0, MRR against enriched descriptions falls
from 0.90 to 0.81).
A metadata PR enriching those descriptions is under review upstream
(NCATS-Tangerine/translator-api-registry#168); with it merged the same
benchmark reaches 20/20 at MRR 0.90, and the boost becomes a small safety
margin rather than the mechanism. -
--tool-search-thresholddefaults to 15 (envTOOL_SEARCH_THRESHOLD).
This flag is new in this release, so 15 is its first shipped default; it was
tuned down from an initial 50 during development.
Measured over the registry's uptime-passing set
(592 tools, 92 APIs), one entry intools/list— name plus enriched
description plus JSON input schema — averages ~3,900 characters (~975 tokens),
median ~1,270 (~320), p90 ~7,500, with one TRAPI tool at 84,000 (~21,000
tokens). The old default therefore let a listing reach roughly 16k tokens of
median-sized tools or 51k of mean-sized ones before search engaged;
biothings_core --facade offis 30 tools and measures ~31k tokens. The new
default caps that at roughly 5-15k while still leaving a single API (~6 tools)
and the facade (~5 tools) directly listed.
Note the 65x spread in per-tool size: tool count is a crude proxy for
payload size, and a token budget would be the better instrument — the constant
is documented as a floor pending that change. -
Migrated to
fastmcp3.x andawslabs_openapi_mcp_server1.x. Both pins
moved together (fastmcp>=3.3.1,<4,awslabs_openapi_mcp_server>=1.1.5,<2)
because the two projects migrated in lockstep. The 2.x/0.2.x lines are no
longer maintained upstream (last releases 2026-04-13 and 2026-03-27). -
_merge_servers_into()now usesFastMCP.list_tools()/list_prompts(),
which replaced the dict-returningget_tools()/get_prompts()in fastmcp
3.x. Merged tool/prompt names, the 64-character cap, and collision handling
are unchanged. -
Merge tests now build real
Tool/Promptobjects instead ofMagicMocks:
fastmcp 3'sadd_tool()coerces non-Toolinputs viaTool.from_function(),
which rejects mocks. This closes a gap where the old tests could not have
caught a malformed component being registered.