Repository navigation
[mcp-2026] Remove JSON-RPC sidecar; one /mcp owner - #34
Conversation
Shreyavemana
left a comment
There was a problem hiding this comment.
I tested PR #34 locally.
- The new test file passes all the 5 tests.
- However, running the full test suite fails during test collection because existing tests still import modules and functions removed by this PR:
test_mcp20_blueprint.py— missingnitrostack.transports.dispatchtest_mcp20_deprecated.py— missingnitrostack.transports.dispatchtest_mcp20_jsonrpc_wire.py— missingnitrostack.transports.dispatchtest_mcp20_stateless_http.py— cannot importbuild_ping_responsefromnitrostack.protocol.jsonrpc
Please update or remove the affected tests as appropriate and rerun the full test suite.
Resolve the modify/delete conflicts by keeping the sidecar modules deleted, as this PR intends. develop's session-id rejection lived only in those modules.
…TP rules as xfail - remove tests that import or exercise StatelessIngressPipeline, IngressContext, StatelessTransportMiddleware and build_ping_response (the code is deleted) - the 21 HTTP tests that checked rules only the sidecar enforced are marked xfail(run=False) with a reason instead of being deleted, because the official /mcp app behaves differently (session-id rejection, Mcp-Method/Mcp-Name rules, modern initialize rejection). They need a keep-or-drop decision.
|
Thanks. Removing the sidecar itself looks right, but merged onto current First, CORS falls back to Please move the existing Also, please don't park the 21 tests as Finally, the request-context items from the #22 comment are still open. Please include them here or link a follow-up issue. |
…t context - Restore the transports/cors.py policy on the official /mcp app (MCP_CORS_ALLOWED_ORIGINS honoured, Mcp-* expose headers, Mcp-Param-* preflight echo, Mcp-Session-Id exposed only on the legacy wire). - Add one server middleware that sets request_ctx for every handler and rejects the 2025 initialize on the modern era. - Add runtime.caller.caller_identity; transforms and task access use it. TransformContext now carries session_id, subject and execution_context. - Triage the 21 parked tests: port 7, delete 5 (header-only ping), move 9 to nitrocloudofficial#27. No xfail(run=False) marks remain. - Add HTTP-level transform, CORS, initialize and identity tests.
Closes #22
Problem
The SDK had two possible owners for the MCP HTTP endpoint:
Both paths could process requests such as ping, initialize, tools/list, and tools/call. This could produce different protocol responses, session behaviour, headers, and error handling depending on which path handled the request.
The duplicate sidecar also contained its own ping helper and transport exports, which were no longer needed once the official MCP SDK became the owner of the HTTP lifecycle.
Proposed solution
Make the official MCP SDK the only owner of
/mcp.The application should:
Implementation details
Modified
nitrostack/core/app.pyRemoved the
wrap_stateless_transport(...)wrapper fromget_combined_app().The method now returns the HTTP app created by
build_http_app()directly. A comment explains that the official MCP SDK owns the complete Streamable HTTP request lifecycle and that a second dispatcher must not be introduced.Deleted
nitrostack/transports/middleware.pyThis file implemented the duplicate stateless transport wrapper around the official MCP app. It was deleted because the official SDK now owns request validation, dispatch, protocol handling, and response generation.
Deleted
nitrostack/transports/dispatch.pyThis file implemented the duplicate JSON-RPC sidecar dispatcher and its request interception logic. It was deleted because maintaining a second dispatcher could cause the sidecar and official SDK to disagree.
Modified
nitrostack/protocol/jsonrpc.pyRemoved the standalone
build_ping_response()helper. Ping requests are now handled by the official MCP SDK.Modified
nitrostack/__init__.pyRemoved public exports for:
wrap_stateless_transportStatelessIngressPipelineThese symbols belonged to the deleted sidecar implementation.
Modified
nitrostack/protocol/__init__.pyRemoved the obsolete
build_ping_responseexport.Modified
nitrostack/transports/__init__.pyRemoved exports for the deleted sidecar classes and helpers, including:
DispatchStageIngressContextStatelessIngressPipelineStatelessTransportMiddlewarewrap_stateless_transportis_task_wire_interceptionThe module now exposes only the remaining official transport helpers.
Modified
nitrostack/transports/http.pyUpdated the middleware documentation to state that it only normalises transport headers and does not perform JSON-RPC dispatch.
Added
tests/test_mcp22_official_http_owner.pyAdded focused regression tests proving that:
/mcp/healthremains available.Mcp-Session-Id.-32601method-not-found error.Review follow-up (this push)
CORS policy restored on the official app
McpCorsMiddleware(intransports/cors.py) applies the existing policy to/mcpand/subscriptions/listen:MCP_CORS_ALLOWED_ORIGINSis honoured (no*fallback when an allowlist is set), preflight returns 204 and echoes requestedMcp-Param-*headers, andMcp-Method,Mcp-NameandMCP-Protocol-Versionare exposed.Mcp-Session-Idis exposed only on the legacy (sessionful) wire. Other routes keep the previous permissive CORS.Modern era rejects the 2025
initializeThe official
ServerRunnerhandlesinitializeitself, so the oldinitialize_handlerhook never ran. AServer.middlewareentry now raisesMethod not found: initialize(-32601) whenrejects_legacy_initialize(era)is true.Request context from one place
The same middleware sets
request_ctxonce for every handler. The per-handler copies in tools/call, resources/read, prompts/get, the four task handlers andnotifications/initializedare removed, sotools/listand the other list handlers now see it too.One
caller_identityhelpernitrostack/runtime/caller.py::caller_identity(rc, era)returns(session_id, subject). Subject is thesubof a verified bearer token only; unsigneduserIdand_metaare never identity. A session id is reported only on the sessionful wire. Transforms andextract_task_access_context(rc, era)both use it.TransformContextpopulatedsession_id, newsubject, andexecution_contextare filled fortools/listandtools/call.The 21 parked tests (no
xfail(run=False)remains)NITRO_MCP_PROTOCOL_VERSIONenv test, era session coexistence, CORS expose headers, auto-era initialize and discover on one/mcp(expectations updated to the official handler), moderninitializerejection (only the initialize assertions; the rest moved to [mcp-2026] Replace sidecar tests with a TestClient matrix; drop ping exports #27).TestPingFastPathx3,TestHeaderTriggeredPingx2). [mcp-2026] Remove JSON-RPC sidecar; one /mcp owner (official mcp 2.x) #22 drops that behaviour.Mcp-Session-Idrejection on modern/auto (x2), forged session id association,GET /mcprejecting a session id, requiredMcp-Name, requiredMcp-Method, protocol-version header cross-check, unsupported-version rejection, response echo headers. These were sidecar-only rules; the tests are removed here and [mcp-2026] Replace sidecar tests with a TestClient matrix; drop ping exports #27 should re-add them against the official path.notifications/initializedrejection on modern is also part of [mcp-2026] Replace sidecar tests with a TestClient matrix; drop ping exports #27.New tests (
tests/test_mcp22_official_http_owner.py)HTTP-level transform context on
tools/list,RegexSearchTransformend to end (search_toolsthencall_tool), legacy session id inTransformContext, moderninitializerejection, CORS allowlist and preflight, session-id exposure per era,caller_identityand task-access parity.Branch
developmerged in (merge commit, no rebase, no force push).Verification
pytest testspasses with no xfail/skip marks added by this PR;tests/test_mcp22_official_http_owner.pynow has 13 tests.git diff --checkpassed.Result
The
/mcpendpoint now has one clear owner: the official MCP SDK Streamable HTTP implementation. This removes conflicting request paths, keeps protocol behaviour consistent, and makes future MCP 2026 changes safer to maintain.