Replace FastMCP with toolregistry-server: multi-protocol, profiles, policy tags#63
Conversation
Swap the server engine from FastMCP to toolregistry + toolregistry-server, enabling multi-transport MCP (stdio/SSE/HTTP), optional OpenAPI/REST, namespace grouping, two-profile tool surface (core / deferred-full), policy tags, and BM25 discovery. No tool implementation code (tools/, domain/, remote/) is modified. Provenance, HPC dispatch, session/workflow state, and fallback semantics are all preserved. Closes #2
|
Hey Peng, thanks so much for putting this together. Really nice that this removes the fastmcp dependency and goes to the Anthropic MCP SDK directly — cleaner dependency chain overall. Ran it locally end-to-end: 304 tests pass, both packages install cleanly from PyPI, admin panel defaults to localhost, and One thing that needs a decision: Smaller things:
|
|
Is this targeted to work only with claude code? |
- Fix discover_tools ranking: enrich search hints for compute tools, reword prompt-tool docstrings to avoid keyword overlap with the actual computation tools they describe. - Bump version to 0.2.0 (pyproject.toml + __init__.py). - Clean remaining FastMCP references in docs/architecture.md and code comments in execution_control.py / remote_tools.py.
|
Thanks for running it end-to-end and the detailed feedback. Pushed a follow-up commit addressing everything below. Namespace prefixes on the wire: Keeping the prefixed names ( For now I'd leave the prefixes as-is and see if any client actually has trouble with them. LLMs handle Version bump: Bumped to BM25 ranking fix: Good catch. The issue was that
Before: Docs cleanup: Cleaned up remaining fastmcp references in toolregistry-server maintenance: I maintain the toolregistry ecosystem (toolregistry, toolregistry-server, toolregistry-hub) including PyPI releases and Docker images. For any issues or patches, open an issue or PR on the relevant repo — happy to discuss and review. No need to plan for a fork. |
Hey @rljacob Robert, good to see you here! Not Claude-only —
Users pick whichever protocol fits their stack. The tool implementations, provenance, and HPC dispatch are shared across all transports. |
|
Really appreciate the thoroughness here — the multi-protocol surface and policy tags are exactly what we need for non-Claude clients and access control, and the BM25 ranking fix landed well. One thing I want to think through with you before we merge: toolregistry is now a hard dependency for basic stdio MCP. For users who just want I'm also thinking out loud about longevity — Not asking you to roll back — more asking: is there a path where Happy to pair on what that would look like if it's useful. |
- Remove toolregistry from direct dependencies (transitive via toolregistry-server[mcp]). - Bump conda recipe version to 0.2.0. - Fix mypy python_version 3.11 → 3.12 to match requires-python. - Use registry._name_sep consistently in 3 test functions instead of hardcoded '-'.
|
Thanks for the call earlier, Rajeev — much easier to talk through this stuff live. For anyone following along, the short version of what we discussed: The features that are useful here — namespaces, policy tags, deferred/discovery, admin panel, multi-protocol — all live in On the "models will get better at large tool lists" point: progressive disclosure and runtime On the dependency concern: the alternative to depending on toolregistry isn't "no dependency" — it's reimplementing the same namespace/tag/discovery/profile machinery inside this repo. That's a real ongoing maintenance cost for features that already exist and are tested. That said, this is your project. The PR is here and working (CI green, 301 tests pass). Happy to answer questions from anyone on the team — feel free to loop me in. |
|
On the conda question — That means the current Two paths forward:
I'll get started on the feedstocks. In the meantime, if your conda users need to try this branch, |
There was a problem hiding this comment.
Pull request overview
Replaces the existing FastMCP-based MCP server entrypoint with a ToolRegistry/toolregistry-server based implementation, enabling namespaced tool grouping, profile-based tool visibility (core vs deferred-full), and policy tagging while keeping the underlying tool implementations intact.
Changes:
- Added
build_registry(profile=...)to define the tool surface (namespaces, profiles, prompt-as-tool, tags, deferred pool). - Rewrote server startup to construct/run an MCP server via toolregistry-server transports (stdio/SSE/HTTP) and updated CLI flags accordingly.
- Updated dependency metadata and rewrote/expanded server-related tests and docs to match the new architecture.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
src/uxarray_mcp/registry.py |
New registry builder defining namespaces, profiles, deferred tools, prompt-as-tool helpers, and policy tags. |
src/uxarray_mcp/server.py |
Replaced FastMCP wiring with ToolRegistry + toolregistry-server MCP server creation and multi-transport run(). |
src/uxarray_mcp/cli.py |
serve command now supports --profile, --transport, --host, --port and calls server.run(). |
src/uxarray_mcp/__init__.py |
Exports make_registry/make_mcp_server and bumps version to 0.2.0. |
src/uxarray_mcp/tools/remote_tools.py |
Updated comment to remove FastMCP reference. |
src/uxarray_mcp/tools/execution_control.py |
Updated docstring to remove FastMCP reference. |
tests/test_server.py |
Rewritten to validate registry profiles, bucket coverage, tags, prompt tools, live call, and MCP server construction. |
tests/test_vector_calc.py |
Adapted tests to use make_registry() and prompt tools as namespaced tools. |
pyproject.toml |
Dependency swap to toolregistry-server, adds openapi extra, bumps version and mypy python_version. |
docs/conf.py |
Removes fastmcp autodoc mock import. |
docs/architecture.md |
Updates architecture layer labeling to ToolRegistry server and removes FastMCP wording. |
CONTRIBUTING.md |
Updates guidance to reflect toolregistry-based server dependency boundaries. |
conda/recipe/meta.yaml |
Updates Python min/version and dependency from fastmcp to toolregistry-server. |
CHANGELOG.md |
Adds Unreleased notes describing the server engine rewrite and new capabilities. |
AGENTS.md |
Rewritten agent guidance reflecting the new registry/server architecture and profiles. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| "create_session": (set(), {"stateful"}), | ||
| "register_dataset": (set(), {"stateful"}), | ||
| "reset_session_state": (set(), {"stateful"}), |
| "get_session_state": ({ToolTag.READ_ONLY}, set()), | ||
| "get_result_handle": ({ToolTag.READ_ONLY}, set()), | ||
| "get_operation_status": ({ToolTag.READ_ONLY}, set()), | ||
| "list_operations": ({ToolTag.READ_ONLY}, set()), | ||
| "get_workflow_status": ({ToolTag.READ_ONLY}, set()), |
| "get_workflow_status": ({ToolTag.READ_ONLY}, set()), | ||
| # HPC control | ||
| "endpoint_status": ({ToolTag.READ_ONLY, ToolTag.NETWORK}, set()), | ||
| "get_execution_mode": ({ToolTag.READ_ONLY}, set()), |
| raise RuntimeError( | ||
| f"server.py registers {raw!r} via mcp.tool() but it is " | ||
| f"not exported from uxarray_mcp.tools." | ||
| ) |
| dependencies = [ | ||
| "fastmcp>=3.4.0", | ||
| "toolregistry-server[mcp]>=0.3.3", | ||
| "holoviews>=1.19.0", | ||
| "matplotlib>=3.9.0", | ||
| "pyyaml>=6.0", | ||
| "uxarray>=2025.12.0", | ||
| ] |
|
@Oaklight uv.lock stale, and if possible, try to address the co-pilot comments. If not, I can create a future PR. Thanks so much for this contribution. |
Sync uv.lock with pyproject.toml: drop fastmcp (and its transitive deps), add toolregistry + toolregistry-server, bump uxarray-mcp to 0.2.0. uv lock --check now passes.
Session create/read/reset tools persist and read records on disk via state._write_json/_read_json, so they now carry ToolTag.FILE_SYSTEM in addition to stateful / read-only. get_execution_mode reads config from disk and queries the Globus Compute endpoint when one is configured, so it gains FILE_SYSTEM and NETWORK. Also update a stale RuntimeError that referenced the removed FastMCP mcp.tool() registration. Adds tests locking in the corrected tags.
Summary
Replace the FastMCP-based
server.pywith atoolregistry+toolregistry-serverimplementation. Same tool functions, same Claude Desktop experience by default, but the server now supports:pip install uxarray-mcp[openapi])session/,hpc/,io/,prompt/,compute/, ...)coredefault and opt-indeferred-fullwith BM25 discoveryREAD_ONLY,FILE_SYSTEM,NETWORK,SLOW,experimental,stateful)fastmcpis no longer a dependency. No tool implementation code (tools/,domain/,remote/) was modified — provenance, HPC dispatch, session/workflow state, and fallback semantics are all unchanged.What changed
src/uxarray_mcp/registry.pybuild_registry(profile=...)with namespace plan, policy tags, BM25 search hints, prompt-as-tool wiringsrc/uxarray_mcp/server.pymake_registry(),make_mcp_server(),run(transport=...)replacing FastMCPsrc/uxarray_mcp/__init__.pymake_registry/make_mcp_serverinstead ofmcpsrc/uxarray_mcp/cli.pyservegains--profile,--transport,--host,--portpyproject.tomlfastmcp→toolregistry+toolregistry-server[mcp]; added[openapi]optional extratests/test_server.pytests/test_vector_calc.pyfrom server import mcptomake_registry()AGENTS.mdCHANGELOG.md[Unreleased]sectionCONTRIBUTING.mdfastmcp→toolregistryconda/recipe/meta.yamlfastmcp→toolregistrydepsdocs/conf.pyfastmcpautodoc mockTwo profiles
core(default, 27 tools visible)export_to_netcdf/export_to_csvare reachable in core viarun_analysis(operation="export", ...).deferred-full(58 loaded, 28 visible + 30 deferred)Core set stays visible. 30 raw implementation tools (
compute/,shape/,inspect/,plot/,io/,agent/) loaded withdefer=True. Agents find them viadiscover_tools(BM25 search). Operators promote from admin panel.Prompt → tool conversion
The 3
@mcp.prompt()decorators are now regular tools underprompt/namespace. They return the same instruction text as before — the LLM callsprompt-first_look(path="...")and gets a multi-step analysis plan to execute.Backward compatibility
uxarray-mcp servestill starts MCP stdio with the same default tool surfacemcpServerssnippets work unchangedpython -m uxarray_mcpstill defaults toserveuxarray_mcp.toolsbehave identicallyNew capabilities are opt-in:
Test results
The 3 failures (
test_plot_mesh_healpix,test_plot_mesh_healpix_custom_size,test_analyze_dataset_includes_plots_by_default) reproduce identically on upstreammain— they are a shapelyGEOSExceptionunrelated to this PR.Design decisions (discussed via Slack)
list_datasets+ prompts per maintainer specrun_scientific_agentstays deferred/experimental until explicitly promotedexport_to_*stays behindrun_analysisdispatcher in core; direct calls in deferreduse_remote→NETWORK) and explicit overridesdiscover_toolsdefault top_k controlled by the calling LLM; BM25 search hints added for domain synonyms (e.g. "vorticity" →calculate_curl)How to try