Stateless - #21
Conversation
Bring in PR 17 (Duffel API v2) and PR 18 (agent skills on init/upgrade).
feat: implement MCP 2026-07-28 stateless protocol, task management, CIMD/OAuth 2.1 hardening, and MRTR support
VerdictDo not merge this PR as-is. It is the same change as #20 ( This comment is an implementation handoff. Dual-era as a product ( GoalOne
NitroStack HTTP code must not:
Non-goals
Assumptions (do not silently change)
Delete first (WP0)Delete or stop calling these. Do not “fix” them in place.
After WP0, Verify: WP1 — One HTTP engine (
|
| Case | Era | Expect |
|---|---|---|
POST /mcp discover + required headers |
modern, auto | 200, _meta.serverInfo, no session header |
POST /mcp ping |
modern, auto | -32601 |
POST /mcp initialize |
modern | -32601 |
POST /mcp initialize |
auto | mcp 2.x 2025 handshake or documented auto behavior; no NitroStack sidecar |
POST /mcp tools/call without Mcp-Method |
modern | -32020 |
POST /mcp tools/call unknown tool |
any | tool isError or -32601 — pick one and test it |
GET /mcp/health |
auto | protocolEra present |
| CIMD blocked IP / redirect / oversize / client_id mismatch | — | existing tests |
Cross-tenant tasks/get |
only if WP3 Finish | -32602 / not found |
Do not test StatelessIngressPipeline as a unit if it no longer answers RPCs.
Concurrency: if InFlightRegistry is deleted, delete test_mcp20_concurrency.py cases that poke app._in_flight. If kept, cancel must be wired to a real notification.
WP6 — Public API cleanup
nitrostack/__init__.py/protocol/__init__.py: stop exportingbuild_ping_response,wrap_stateless_transport,StatelessIngressPipelineif deleted.protocol/method_contract.py: shrink or delete once handlers are the source of truth.runtime/layers.py(RuntimeLayerenum): unused — delete only if this PR introduced it (it did). Same for unusedDispatchStageif the pipeline dies./json/versioninhttp.pystill reports"Protocol-Version": "2025-06-18"— set fromprotocol_version_for_era.pyproject.tomlversion0.3.2— leave unless release.
Order of work (agent)
WP0 delete sidecar from get_combined_app → app starts, mcp handles POST /mcp
WP1 subscribe flag + stdio public API → discover vs capabilities agree
WP2 ping gone, discover _meta, headers → TestClient table in WP5 starts green
WP3 Hide tasks on 2026 → no tasks/* on modern/auto
WP4 CIMD deny on host mismatch + dedupe → oauth_cimd tests
WP5 rewrite tests, delete 3k-line file → pytest tests/test_mcp20_*.py focused
WP6 exports → rg ping / wrap_stateless in nitrostack/ empty
Do not start WP3 Finish. Do not add era tokens. Do not keep a “thin” handle_post that still returns ping “for Inspector.”
Known bugs to fix if the code they live in survives
These are from the line-by-line review of this PR. If WP0 deletes the file, skip.
auth/cimd.py: duplicatelooks_like_cimd_url/resolve_cimd_synctasks/authorization.py: usesAnywithout importing it (ok withfrom __future__ import annotations, still fix if you touch the file)middleware.py_replay_receive: hang on keepalive (must not remain)SessionlessHttpGuard:discover_handler()vs(request)ttl_ms_to_seconds:max(1, int(ttl_ms/1000))turns 0ms/500ms into 1scheck_task_access(context=None)bypasses isolation — HTTP must always pass a context object (empty is OK)cachedecorator skipsExecutionContextin the key — do not expand cache work in this PR (pre-existing); do not use it to implement SEP-2549
Success criteria (PR is mergeable when)
get_combined_app()does not wrap withwrap_stateless_transport.- No production function named
build_ping_response;ping→-32601. - Discover is handled only by
NitroStackMcpServer/handle_server_discover; result_metacontainsserverInfo. resources.subscribeis the same boolean on discover andget_capabilitiesfor a given era.- Modern POST without
MCP-Protocol-Version/Mcp-Methodfails closed. - Auto/modern responses never emit
Mcp-Session-Id. - Tasks extension absent on modern/auto unless WP3 Finish is complete (it should not be).
- CIMD host mismatch is an error.
pyteston the remaining mcp20 tests + existingtests/test_transport_http.py/tests/test_lifecycle_http.py/tests/test_tasks.pypass.- No new private imports from
mcp.server.runner._*.
Out of scope leftovers (mention, don’t fix here)
@cacheskipping auth in the cache key- Widget preview hardcoded pizza/flight defaults in
http.py - OAuth fail-open (
OAUTH_REQUIRED) behavior - Package version bump / changelog / README feature list for 2026
No description provided.