|
2 | 2 | title: Streamable HTTP |
3 | 3 | weight: 15 |
4 | 4 | description: Connect to MCP servers via streamable HTTP with automatic session management |
| 5 | +test: |
| 6 | + mcp-stateless: |
| 7 | + - file: ${versionRoot}/mcp/connect/http.md |
| 8 | + path: mcp-stateless |
5 | 9 | --- |
6 | 10 |
|
7 | 11 | Connect to an MCP server via streamable HTTP. |
@@ -36,6 +40,100 @@ sequenceDiagram |
36 | 40 | 3. **State encoding**: The session state is encoded into the session ID using AES-256-GCM encryption |
37 | 41 | 4. **Session resumption**: Subsequent requests with the same session ID are automatically routed to the same backend |
38 | 42 |
|
| 43 | +## Stateless sessions {#stateless-sessions} |
| 44 | + |
| 45 | +By default, agentgateway proxies streamable HTTP in **stateful** mode, as described in the previous section. You can instead run in **stateless** mode with the `statefulMode` field, so that agentgateway does not create a session or return an `Mcp-Session-Id` header. Each request is treated independently, and the client must send the full context that the request needs. This mode suits stateless agents, or MCP servers where the client handles state directly. |
| 46 | + |
| 47 | +{{< callout type="info" >}} |
| 48 | +The `statefulMode` field controls how agentgateway proxies session-based servers. It is separate from the newer, inherently sessionless `2026-07-28` MCP protocol, which agentgateway supports automatically through version negotiation. For more information, see [MCP spec compatibility]({{< link-hextra path="/mcp/spec-compatibility" >}}). |
| 49 | +{{< /callout >}} |
| 50 | + |
| 51 | +To use stateless mode, set `statefulMode` to `stateless` on the MCP configuration. |
| 52 | + |
| 53 | +```yaml |
| 54 | +# yaml-language-server: $schema=https://agentgateway.dev/schema/config |
| 55 | +mcp: |
| 56 | + port: 3000 |
| 57 | + statefulMode: stateless |
| 58 | + targets: |
| 59 | + - name: mcp |
| 60 | + mcp: |
| 61 | + host: http://localhost:3005/mcp/ |
| 62 | +``` |
| 63 | +
|
| 64 | +When you send an `initialize` request through agentgateway in stateless mode, the response returns `HTTP 200` with no `Mcp-Session-Id` header. In the default stateful mode, the same request returns an `Mcp-Session-Id` header that pins the session to a backend. |
| 65 | + |
| 66 | +{{< doc-test paths="mcp-stateless" >}} |
| 67 | +# WHAT THIS TEST VALIDATES: |
| 68 | +# * statefulMode: stateless is accepted and the MCP endpoint serves an initialize request (HTTP 200) |
| 69 | +# * In stateless mode, the initialize response does NOT include an Mcp-Session-Id header |
| 70 | +# WHAT THIS TEST DOES NOT VALIDATE (and why): |
| 71 | +# * The visible example uses a streamable HTTP upstream (host: http://localhost:3005/mcp/); |
| 72 | +# the hidden test config uses a self-contained stdio target so no external MCP server is |
| 73 | +# needed. statefulMode governs the downstream (client-facing) session, independent of the |
| 74 | +# upstream transport, so the stateless behavior under test is the same. |
| 75 | +# * The default stateful contrast (Mcp-Session-Id present) is covered by other pages that |
| 76 | +# use the default configuration. |
| 77 | +{{< /doc-test >}} |
| 78 | + |
| 79 | +{{< doc-test paths="mcp-stateless" >}} |
| 80 | +# Install agentgateway binary |
| 81 | +{{< reuse "agw-docs/snippets/install-agentgateway-binary.md" >}} |
| 82 | +{{< /doc-test >}} |
| 83 | + |
| 84 | +{{< doc-test paths="mcp-stateless" >}} |
| 85 | +cat <<'EOF' > config.yaml |
| 86 | +# yaml-language-server: $schema=https://agentgateway.dev/schema/config |
| 87 | +mcp: |
| 88 | + port: 3000 |
| 89 | + statefulMode: stateless |
| 90 | + targets: |
| 91 | + - name: everything |
| 92 | + stdio: |
| 93 | + cmd: npx |
| 94 | + args: ["@modelcontextprotocol/server-everything"] |
| 95 | +EOF |
| 96 | +{{< /doc-test >}} |
| 97 | + |
| 98 | +{{< doc-test paths="mcp-stateless" >}} |
| 99 | +agentgateway -f config.yaml & |
| 100 | +AGW_PID=$! |
| 101 | +trap 'kill $AGW_PID 2>/dev/null' EXIT |
| 102 | +sleep 3 |
| 103 | +{{< /doc-test >}} |
| 104 | + |
| 105 | +{{< doc-test paths="mcp-stateless" >}} |
| 106 | +YAMLTest -f - <<'EOF' |
| 107 | +- name: MCP endpoint accepts initialize request in stateless mode |
| 108 | + http: |
| 109 | + url: "http://localhost:3000" |
| 110 | + path: /mcp |
| 111 | + method: POST |
| 112 | + headers: |
| 113 | + content-type: application/json |
| 114 | + accept: "application/json, text/event-stream" |
| 115 | + body: | |
| 116 | + {"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}},"id":1} |
| 117 | + source: |
| 118 | + type: local |
| 119 | + expect: |
| 120 | + statusCode: 200 |
| 121 | +EOF |
| 122 | +{{< /doc-test >}} |
| 123 | + |
| 124 | +{{< doc-test paths="mcp-stateless" >}} |
| 125 | +# Assert that stateless mode does not return an Mcp-Session-Id header |
| 126 | +if curl -sS -D - -o /dev/null -X POST http://localhost:3000/mcp/ \ |
| 127 | + -H 'Content-Type: application/json' \ |
| 128 | + -H 'Accept: application/json, text/event-stream' \ |
| 129 | + -d '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}},"id":1}' \ |
| 130 | + | grep -qi 'mcp-session-id'; then |
| 131 | + echo "FAIL: stateless mode returned an Mcp-Session-Id header" |
| 132 | + exit 1 |
| 133 | +fi |
| 134 | +echo "PASS: no Mcp-Session-Id header in stateless mode" |
| 135 | +{{< /doc-test >}} |
| 136 | + |
39 | 137 | ## Before you begin |
40 | 138 |
|
41 | 139 | {{< reuse "agw-docs/snippets/prereq-agentgateway.md" >}} |
|
0 commit comments