Conversation
Adopter confirmation (filed #617)Read the diff. This kills our core/middleware/transport_detect.py cleanly (85 LOC of URL-path classification + ContextVar plumbing). Two specific things I like:
One verification requestThe PR reads from `tool_ctx.metadata["transport"]`. This depends on the SDK's MCP and A2A dispatchers always populating that key on the `ToolContext` they hand to `_build_request_context`. The diff covers `_build_request_context` cleanly but I don't see the dispatcher-side population in the diff scope. Can you confirm both paths set `metadata["transport"]` before this lands? An adopter who hits a `transport=None` in production handler code would have a confusing time. If the dispatcher wiring is in another PR, please link it; if it's already on `main`, the test count (15 passing) suggests it's fine. Adoption planOnce this lands and we bump:
Small, mechanical change on our side. Ready when you are. |
|
@bokelley — confirmed, no separate PR needed. The dispatcher-side wiring was already on
Both receive The only Generated by Claude Code |
…ontextVar Closes #617 Adopters building webhook services need to know whether the current request arrived via MCP or A2A so they can select the correct payload shape (Task/TaskStatusUpdateEvent vs McpWebhookPayload). The transport was already known to the dispatcher (via RequestMetadata.transport) but was buried in the opaque ToolContext.metadata dict with no typed surface. - Add `transport: Literal["mcp", "a2a"] | None` field to RequestContext; populated in _build_request_context from tool_ctx.metadata["transport"] (always present in production paths; None only in bare test fixtures) - Add `current_transport: ContextVar[Literal["mcp", "a2a"] | None]` to adcp.server.auth; set in _build_request_context so webhook services called from handlers can read it without a RequestContext in scope - Export current_transport from adcp.server (alongside current_tenant) - Also export current_principal and current_principal_metadata from adcp.server — pre-existing gap where these were accessible only via the private adcp.server.auth module path https://claude.ai/code/session_01UifYgpi26gbfXmhrNRFDvK
- Validate that metadata["transport"] is "mcp", "a2a", or None before
assigning to RequestContext.transport; raises ValueError on invalid
values so misconfigured context_factories fail visibly
- Strip SDK-internal keys ("transport", "tool_name") from the
handler-visible RequestContext.metadata so ctx.transport is the sole
typed surface and adopters can't accidentally rely on the dict path
- Add Literal import to dispatch.py for the explicit transport annotation
- Add current_transport ContextVar assertion and "tool_name" not in
ctx.metadata assertions to the parametrized transport extraction test
- Extend RequestContext.transport docstring to cross-link current_transport
and note that custom context_factories that omit metadata["transport"]
also produce None
https://claude.ai/code/session_01UifYgpi26gbfXmhrNRFDvK
2a9654a to
78ed926
Compare
Verified dispatcher write sitesConfirmed
One caveat to call out — already documented in the field docstringAdopters who do not wire a The PR's docstring on
The "production dispatch always populates" claim is true for adopters using the SDK-provided Net: the read site ( |
|
Thanks for the write-site audit, @bokelley. The Triaged by Claude Code. Session: https://claude.ai/code/session_01QMJzJHYW6SzcPUZTAE9WTU Generated by Claude Code |
Closes #617
Summary
Adds
RequestContext.transport: Literal["mcp", "a2a"] | Noneso handler methods can branch on the inbound wire protocol without 85 lines of ASGI URL-inference middleware. Also exportscurrent_transport: ContextVar[Literal["mcp", "a2a"] | None]fromadcp.serverfor webhook services and other code running outside a handler call stack. Fixes a pre-existing gap wherecurrent_principalandcurrent_principal_metadatawere only accessible via the privateadcp.server.authpath rather than the publicadcp.serversurface.What was tested
Pre-PR review
ctx.metadata, (c) missing ContextVar assertion in test. All three fixed before PR open.current_transport.set()without reset token; resolved via explanatory comment noting asyncio task-context isolation (each request task copies context at creation, soset()is task-scoped and cannot bleed across requests; resetting immediately after_build_request_contextreturns would undo the value before any handler code runs). Also fixed: cross-link tocurrent_transportin field docstring, note that customcontext_factoryomittingmetadata["transport"]also producesNone.Nits surfaced, not fixed:
current_tenant(fromtenant_router.py) andcurrent_principal(fromauth.py) are heterogeneous in origin and type in__init__.py— pre-existing, noted for a future cleanup PR.@pytest.mark.parametrizeis now used for the transport extraction test (clearer failure messages vs. the originalforloop).Session: https://claude.ai/code/session_01UifYgpi26gbfXmhrNRFDvK
Generated by Claude Code