Migrate to MCP Python SDK v2 (dual-era protocol support) - #89
Merged
Conversation
Port the low-level Server handlers to v2's on_* constructor API, preserve model-visible tool errors, keep handshake metrics alive for 2026-07-28 clients, and fix integration tests for required variable_id. Co-authored-by: Cursor <cursoragent@cursor.com>
Greptile SummaryMigrates the server to MCP Python SDK v2 while preserving legacy and modern protocol support.
Confidence Score: 5/5The PR appears safe to merge, with no concrete changed-code failures identified. The MCP v2 handler, model, HTTP identity, telemetry, and integration-test changes remain internally consistent, and the investigated dependency advisories did not originate from changed vulnerable versions or expose a reachable changed-code security path. Important Files Changed
Reviews (1): Last reviewed commit: "Migrate to MCP Python SDK v2 with dual-e..." | Re-trigger Greptile |
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.
Summary
Ports
mcp-server-appwritefrom MCP Python SDK v1.28 to v2.0.0, which brings free support for the 2026-07-28 (stateless / modern) protocol while keeping the existing 2025-era handshake path working for every client shipping today.The HTTP/OAuth surface (
StreamableHTTPSessionManager(stateless=True), bearer auth, discovery routes) is unchanged. The cost is in the low-levelServerrewrite: decorators →on_*constructor handlers, full result types, snake_casemcp.types, and explicitCallToolResult(is_error=True)so tool failures stay model-visible.Also keeps
mcp.handshakemetrics alive for modern clients (noinitialize), and fixes integration smokes for the Appwrite SDK’s requiredvariable_idoncreate_variable.Why this migration
v2’s
StreamableHTTPSessionManagerroutes onMCP-Protocol-Version:2024-11-05…2025-11-25) → existing legacy/stateless path2026-07-28) → modern per-request handlerSo one hosted app serves both eras with no new ASGI wiring.
flowchart LR req["POST / or /mcp"] --> bearer["RequireBearer + AuthContext"] bearer --> mgr["StreamableHTTPSessionManager"] mgr --> check{"Handshake protocol version?"} check -->|yes| legacy["legacy / initialize path"] check -->|no| modern["handle_modern_request"] legacy --> srv["low-level Server on_* handlers"] modern --> srvDependencies
pyproject.toml/uv.lock:mcp[cli]>=1.12.0,<2→mcp[cli]>=2,<3opentelemetry-{api,sdk,exporter-otlp-proto-http}floor →>=1.28(v2 hard-depends on this)httpx(v2 swaps its internal client tohttpx2; we still usehttpxfor uploads / OAuth proxy / discovery)Core: rewrite
build_mcp_serverDecorators are gone. Handlers take
ServerRequestContext+ typed params and return full result objects:Keep tool errors visible to the model
v1 wrapped raised exceptions into
CallToolResult(isError=true). v2 turns uncaught exceptions into a sanitized-32603. We return an error result explicitly soconfirm_writerefusals and Appwrite API errors still reach the model:Unknown resource URIs raise
MCPError(INVALID_PARAMS, …)so the message survives.Dual-era client identity
_emit_initialize/_mcp_request_contextnow takeServerRequestContext. Prefer 2025-eractx.session.client_params, fall back to 2026-eractx.meta[CLIENT_INFO_META_KEY], and always take protocol version fromctx.protocol_version.Snake_case type sweep
v2’s
mcp.typesattributes are snake_case. Constructor kwargs still accept camelCase, so reads are the dangerous half:tool.inputSchematool.input_schemamimeType=/.mimeTypemime_type=/.mime_typeuriTemplate=uri_template=Resource(uri=AnyUrl(...))strURIitem.model_dump(mode="json")…(mode="json", by_alias=True)so stored resources keep camelCase wire keysTouched:
server.py,operator.py,service.py,docs_search.py.HTTP / CORS
constants.py— allow the SEP-2243 routing headers modern clients send:Telemetry: backwards-compatible handshake for modern clients
Modern clients never send
initialize, so body-sniffing alone would silencemcp.handshake{status="success"}.set_request_identitynow returnsTruewhen it opens a new(client, subject)activity windowrecord_stateless_connectionemits one handshake success per new window (same instrument / attrs)MCPIdentityMiddlewarekeeps the legacyinitializepath, and for modern requests reads_metaclientInfo / protocolVersion (User-Agent / header fallback)Legacy initialize counting is unchanged, so the timeseries stays comparable through the client migration.
Integration test fix (Appwrite SDK, not MCP)
Python SDK ≥18.1 requires client-supplied
variable_idonfunctions.create_variable/sites.create_variable(same pattern asfunction_id/document_id). Our smokes still omitted it.Same for sites. Unrelated to MCP v2; surfaced while E2Eing against Cloud on
appwrite==22.2.0.Files changed
pyproject.toml,uv.lockserver.py,operator.py,service.py,docs_search.pyhttp_app.py,constants.pytelemetry.pytest_server.py,test_http_app.py,test_operator.py,test_service.py,test_telemetry.pytest_functions.py,test_sites.pyVerification
ruff check src testsblack --check src testspyright193passed, includingis_error=Truetool failures and modern handshake dedupe)mode="legacy"→2025-11-25, default/auto →2026-07-28) against a live Cloud-backed operator: tools/list, search, users_list, confirm_write refusal, get_context, catalog resources/read, docs search, tables_db_listvariable_idfix/healthz, protected-resource metadata, CORS allowsMcp-Method/Mcp-Name, unauth POST →401+WWW-Authenticate(legacy + modern headers)docker build(Docker daemon unavailable in the agent environment — please run locally / CI)Breaking changes for existing users?
No intentional user-facing breaks for current MCP clients.
by_alias=TrueWhat does change under the hood: modern (2026-07-28) clients work; handshake metrics for those clients are keyed off activity sessions instead of
initialize; operators on Appwrite SDK ≥18.1 must passvariable_idwhen calling create-variable tools (API change, not MCP).Test plan
mcp_handshake/ active-session panels still move for both legacy and modern trafficClient(mode="legacy")andClient()against a staging MCP URL once the image is publishedMade with Cursor