Skip to content

feat(server): transport="both" — host MCP and A2A on a single binary (#354)#370

Merged
bokelley merged 1 commit intomainfrom
bokelley/v3-tier2-mcp-a2a-unified
May 2, 2026
Merged

feat(server): transport="both" — host MCP and A2A on a single binary (#354)#370
bokelley merged 1 commit intomainfrom
bokelley/v3-tier2-mcp-a2a-unified

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

@bokelley bokelley commented May 2, 2026

Summary

JS sellers host both transports on one Express/Hono app; this PR brings Python parity. `serve(transport="both")` routes by URL path on a single Starlette parent — `/mcp` and `/mcp/...` to FastMCP, everything else (`/`, `/.well-known/agent.json`, A2A push) to a2a-sdk. Both apps share one handler instance, so context_factory / middleware / AccountStore / BuyerAgentRegistry wire once and reach both transports.

  • New `"both"` value on `serve(transport=)`.
  • Internal split: `_build_mcp_and_a2a_app` (testable) + `_serve_mcp_and_a2a` (uvicorn driver).
  • Lifespan composition: FastMCP's session manager and a2a-sdk's stores both initialize via a composed asynccontextmanager. Without it, MCP requests fail with `Task group is not initialized`.

Closes #354.

Test plan

  • 8 tests via Starlette TestClient covering both routing paths and the public surface validation
  • Full suite: 3126 passed
  • ruff/mypy/black clean

🤖 Generated with Claude Code

…354)

JS sellers host both transports on one Express/Hono app; this is
the Python parity. ``serve(transport="both")`` builds both apps from
the same handler and routes by URL path on a single Starlette parent:
``/mcp`` and ``/mcp/...`` go to FastMCP's streamable-http app,
everything else (``/``, ``/.well-known/agent.json``, A2A push
endpoints) goes to the a2a-sdk app. Adopters writing context_factory,
middleware, AccountStore, BuyerAgentRegistry, etc., wire one place
and reach both transports automatically.

* New ``"both"`` value on ``serve(transport=)``.
* Internal ``_build_mcp_and_a2a_app`` constructs the dispatcher;
  ``_serve_mcp_and_a2a`` drives uvicorn against it.
* Lifespan composition: FastMCP's session manager and a2a-sdk's
  stores both initialize via a composed asynccontextmanager on the
  parent Starlette. Without this, requests to the MCP path fail
  with ``Task group is not initialized``.
* 8 tests via Starlette TestClient.

Closes #354.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bokelley bokelley merged commit 1d5d907 into main May 2, 2026
12 checks passed
@bokelley bokelley deleted the bokelley/v3-tier2-mcp-a2a-unified branch May 2, 2026 20:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(serve): host MCP and A2A on a single Starlette binary (parity with JS)

1 participant