Skip to content

174: MCP stateless - #194

Open
JaeYeonLee0621 wants to merge 2 commits into
mainfrom
174-mcp-stateless
Open

JaeYeonLee0621 wants to merge 2 commits into
mainfrom
174-mcp-stateless

Conversation

@JaeYeonLee0621

@JaeYeonLee0621 JaeYeonLee0621 commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

MCP: stateful → stateless (2026-07-28 protocol)

  • Upgrade mcp 1.25.0 → 2.2.0 (stateless protocol core).
  • Remove session management: no session init, no mcp-session-id, no GET listening stream, no DELETE endpoint.
  • Each POST is now a self-contained exchange: the gateway requires the Mcp-Method / Mcp-Name headers and relays the request to the configured upstream, returning its response (JSON or passthrough SSE). No state held between requests.

+) https://blog.modelcontextprotocol.io/posts/2026-07-28/

+) But how.. could i test it before deploying in production environment? -> staging chat ai

@JaeYeonLee0621 JaeYeonLee0621 self-assigned this Sep 24, 2026
@JaeYeonLee0621 JaeYeonLee0621 linked an issue Sep 24, 2026 that may be closed by this pull request
@JaeYeonLee0621
JaeYeonLee0621 marked this pull request as ready for review September 24, 2026 16:16
@JaeYeonLee0621
JaeYeonLee0621 requested review from meffmadd and natkam and removed request for natkam September 24, 2026 16:16
@meffmadd

Copy link
Copy Markdown
Member

I will write my review after the next release but for testing you can use the https://github.com/modelcontextprotocol/inspector. You can then start Aqueduct locally and route to a locally hosted MCP server (e.g. everything). Then connect the inspector to Aqueduct and now you can test any functionality (also in debugging to set breakpoints etc.)

@natkam natkam left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yay, so much code removed! Nice 😁

Just a few small things (mostly regarding inaccurate comments and documentation), but nothing terribly serious.

Comment thread docs/api/mcp.md
MCP-compatible tools.
implements the **2026-07-28** version of the streamable HTTP transport specification, which has a **stateless protocol
core**. There is no handshake, no session id, and no server-initiated stream — every request is self-contained and can
land on any gateway instance behind a plain round-robin load balancer.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Do we have a round-robin load balancer? Even if we do, I'm not sure if this deployment detail is relevant here in the API documentation...

Comment thread pyproject.toml
Comment on lines +28 to +31
# NOTE: the ``vllm`` extra was removed because ``vllm==0.8.4`` requires
# ``opentelemetry-api<1.27`` while ``mcp`` (v2, 2026-07-28 protocol) requires ``>=1.28`` --
# they cannot share one environment. vllm is unused by the codebase (never imported),
# and the ``INTEGRATION_TEST_BACKEND=vllm`` mode is incomplete, so it was dropped entirely.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment makes sense as a PR comment, but not in the actual project file.

Each POST is a self-contained JSON-RPC exchange: the gateway requires the
2026-07-28 ``Mcp-Method`` header, then relays the request to the configured
upstream server and returns its response. There is no session, no GET stream,
and no DELETE endpoint.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The last sentence sounds like a description of what changed in this PR, but I don't think it's relevant here in the docstring of an endpoint that explicitly requires POST.

Comment on lines +68 to +71
The gateway always advertises the latest protocol version and the JSON-RPC
method, mirroring the client's ``Mcp-Method``/``Mcp-Name`` headers when sent and
otherwise deriving them from the message body. This makes every relayed request
self-describing so it can land on any instance behind a round-robin load balancer.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of this paragraph doesn't really describe what this function is doing, does it? It's also almost as long as the function's body. It's probably faster to read the function's body than this text, so I would just delete that paragraph from the docstring.

*args: Any,
**kwargs: Any,
) -> JsonResponse | StreamingHttpResponse:
request: ASGIRequest, name: str | None, json_rpc_message: Any = None, *args: Any, **kwargs: Any

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to confirm that it's not an oversight: does json_rpc_message have to be Any? The annotation has changed - until now it's been JSONRPCMessage | None - but I see we still make some assumptions about the object itself (e.g. that it won't explode when we call model_dump_json on it in _relay). The docstring of _relay also explicitly mentions relaying a JSON-RPC message. In principle, a type adapter's validate_python should return the same type of the object that it received, so maybe the type annotation here could be improved somehow?

Comment thread docs/api/mcp.md
The MCP gateway acts as a bridge between your client application and MCP servers. You can request tools, call functions,
and receive responses through standard HTTP requests while the gateway manages the underlying session.
Aqueduct acts as a stateless bridge between your client application and MCP servers. Each `POST` carries the protocol
version, client identity, and capabilities either in HTTP headers or in the JSON-RPC `_meta` field. The gateway

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This description sounds like the requests to Aqueduct could carry the "client identity" and all the other details in the _meta field. But do we actually support it? I think right now we require the user's token to be sent in the header. But maybe I misunderstood or missed something here?

Here's a suggestion of an update to this paragraph (feel free to come up with something else, but please ask your agent to make it sound like human language):

Each call to an MCP server through Aqueduct is an independent, self-contained exchange. There is no handshake and no session to manage, and nothing is held in memory between requests. The gateway checks the request headers, forwards it to the configured upstream server, and returns its response (a JSON message or an SSE stream) unchanged.

Also, since we actually require token authentication for this endpoint, I would say the Required headers section should mention it, even though it's not a part of the MCP protocol.

self.assertEqual(r1.json(), r2.json())

def test_relay_streams_sse_response(self):
from asgiref.sync import async_to_sync

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please put the imports at the top of the file :)

Comment thread pyproject.toml
[dependency-groups]
dev = [
"celery-types>=0.26.0",
"channels[daphne]>=4.3.1",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if we still need channels now that we don't have to run the mock MCP server :)

This branch has not been deployed

No deployments
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.

Switch to MCP 2026-07-28 Specification

3 participants