Repository navigation
feat: move to the mcp 2.x SDK (MCPServer), carries #44 - #48
Merged
Merged
Conversation
The 1.x line is in maintenance mode (security fixes only) and the `<2` pin from #32 was always meant to be temporary. Dependabot's range bump (#39) could not do it alone: 2.0 is a breaking release for this code base, so the port lands in the dedicated commits that follow. - `mcp[cli]>=1.0,<2` -> `mcp>=2.3,<3`. The `[cli]` extra is dropped: it only pulled typer for the `mcp` command-line tool the server never uses. `httpx` stays for bmc/redfish.py; the SDK's `httpx2` arrives as a transitive dependency and the two packages coexist.
`mcp.server.fastmcp.FastMCP` is `mcp.server.mcpserver.MCPServer` in 2.x,
and three things around it changed shape. The transport parameters left
the constructor: without an explicit `transport_security` the 2.x app
factory auto-enables a loopback-only allowlist, so every public Host
(claude.ai, ChatGPT, Le Chat) would have been answered 421. Only
`ToolError` keeps its message for the model now; any other exception is
reported as a generic "Error executing tool X" with the text kept
server-side, which would have hidden "node not found" and friends. And
the server version defaults to "" instead of the SDK's.
- The 10 modules import `MCPServer`; `Icon(mime_type=...)`.
- `server.py` exposes a module-level `transport_security`
(DNS-rebinding protection, `allowed_hosts`, `allowed_origins`) and
`_run_http` passes it to `mcp.streamable_http_app(...)`. The parent
Starlette lifespan uses the public `mcp.session_manager.run()` instead
of reaching into the child app's router.
- `MCPServer("beaconmcp", ..., version=<package version>)`.
- `_metric_tool` keeps its async wrapper (the SDK only offloads sync
functions, so there is no double offload) and now re-raises
`ToolError`/`MCPError` as is, converts anything else to
`ToolError(str(e))`, and counts a cancelled call as `cancelled`
instead of `ok`. Comment rewritten: the "FastMCP runs sync tools
inline" justification no longer holds.
`streamablehttp_client` is gone in 2.x: headers and timeouts now live on an `httpx2.AsyncClient` handed to `streamable_http_client`, which yields a 2-tuple. `AppsClientSession` had to go too: it tagged the ui extension onto `InitializeRequest` by testing `request.root`, and 2.x request types are no longer RootModels, so the check would have been silently False and the MCP Apps panels would have stopped rendering without any error. Result and tool fields are snake_case in 2.x; the `getattr(..., "isError")` reads would have defaulted to False/None and counted errors as ok. - `open_session` keeps its signature, builds the httpx2 client with the bearer and `Timeout(connect=30, read=300)`, and declares the ui extension through `ClientSession(extensions=CLIENT_EXTENSIONS)`. - `chat.py` reuses `open_session` instead of its own copy of the handshake, and reads `is_error`, `input_schema`, `mime_type`, `structured_content`. Wire keys sent to the browser and to Gemini (`isError`, `mimeType`, `structuredContent`) are unchanged.
- `test_mcp_apps_panel.py`: `MCPServer`, `mime_type`, `call_tool` returns
a `CallToolResult`; every panel tool's `input_schema` must be
`type: object` (2.x rejects `{}` at serialization).
- `test_dashboard_mcp_apps.py`: the two `AppsClientSession` tests are
replaced by one that captures the real `initialize` request and checks
the ui extension is advertised, and one that checks `open_session`
wires the bearer, the timeouts and the extensions.
- New `test_mcp_http_stack.py`: captures the app `_run_http` hands to
uvicorn and drives it in-process through the parent lifespan: 401
with `resource_metadata` without a bearer, initialize 200 with
`Mcp-Session-Id` (negotiates 2025-06-18 when offered), tools/list with
object schemas, tools/call, the `/mcp/c/<slug>` rewrite, and 421 on a
foreign Host. First test to cover auth, slug, lifespan and the
DNS-rebinding guard together.
- Fakes in `test_dashboard_unit.py` use the snake_case fields.
Tests: 517 passed under mcp 2.3.0, ruff clean.
`beaconmcp --help` and `validate-config` never import the MCP SDK, so a break like the 2.0 rename could pass the build and docker jobs. Both now run `import beaconmcp.server` (and the dashboard bridge from the wheel) with the dummy secrets of the config job; the image gets the example yaml mounted at the path its `BEACONMCP_CONFIG` points to.
- `docs/updates.md`: how to move an existing install to this release (pip reinstall + restart, Docker pull, rollback). - `docs/dashboard.md`, `CLAUDE.md`: `MCPServer`, `streamable_http_client`, no more `<2` pin wording.
# Conflicts: # src/beaconmcp/__main__.py
constraints.txt (added on main by the self-update work) still pinned mcp==1.30.0, so `pip install -e . -c constraints.txt` hit ResolutionImpossible against mcp>=2.3: CI, the Dockerfile, install.sh and the self-update would all have failed to install this tree. Regenerated with the documented recipe; only the SDK set moves (mcp 2.3.0, mcp-types, httpx2, httpcore2, opentelemetry-api, truststore; typer, shellingham, pydantic-settings, httpx-sse and annotated-doc drop out). The upgrade note now uses the constraints file and check-startup. Co-authored-by: Ailcope <54411234+Ailcope@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lxm6BaCA2kUHH1f6xvh8kS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Carries @Ailcope's #44 onto the current
main. The six SDK migration commits are theirs and unchanged; see #44 for the full description of the port.Before: #44 conflicts with
main, and once merged it could not be installed at all.constraints.txt(added by #47) pinsmcp==1.30.0, sopip install -e . -c constraints.txtfails withResolutionImpossibleagainstmcp>=2.3. CI, the Dockerfile,install.shand the self-update all install that way.After: the server runs on mcp 2.3.0 with the same behaviour as
main, and every install path resolves.How:
maininto feat: move to the mcp 2.x SDK (MCPServer) #44. One conflict, theratelimit/serverimports in_run_http(fix(auth): key the TOTP lockout on client_id + source IP #45 addedTotpLockout, feat: move to the mcp 2.x SDK (MCPServer) #44 addedtransport_security); both kept.constraints.txtregenerated with its documented recipe. Only the SDK set moves:mcp2.3.0,mcp-types,httpx2,httpcore2,opentelemetry-api,truststorein;typer,shellingham,pydantic-settings,httpx-sse,annotated-docout. Every other pin is unchanged.docs/updates.md: the 2.x upgrade note now reinstalls with-c constraints.txtand points tobeaconmcp check-startup.Exercised on a real server
beaconmcp servefrommain(mcp 1.30) and from this branch (mcp 2.3), fake Proxmox API behind it, real client and TOTP, driven by an mcp 1.x client (so an older connector against the new server):main/mcpwithout bearerresource_metadatatype: objectproxmox_list_nodes,proxmox_read_fileisError, "Node 'nope' is not configured..."Host: mcp.example.com(inallowed_hosts)2025-06-18Host: evil.example.net/localhost:8000Origin: https://claude.ai/ foreign originopen_session,list_tools,call_tool)Upgrade path: a venv holding
main(mcp 1.30) reinstalled from this tree with-c constraints.txtmoves to mcp 2.3.0,pip checkis clean andbeaconmcp check-startup --config beaconmcp.yaml.examplepasses. That is what the self-update runs before restarting.Tests: 640 passed under mcp 2.3.0,
ruff checkclean.Closes #44.
Generated by Claude Code