Skip to content

MCP: add Streamable HTTP transport endpoint#4122

Merged
whysosaket merged 2 commits intomem0ai:mainfrom
lan17:lan17/add-streamable-http-transport
Mar 25, 2026
Merged

MCP: add Streamable HTTP transport endpoint#4122
whysosaket merged 2 commits intomem0ai:mainfrom
lan17:lan17/add-streamable-http-transport

Conversation

@lan17
Copy link
Copy Markdown
Contributor

@lan17 lan17 commented Feb 24, 2026

Fixes #2992
Fixes #2812

Description

Add a stateless Streamable HTTP transport endpoint at /mcp/{client_name}/http/{user_id} alongside the existing SSE transport. This enables MCP clients that only support Streamable HTTP (e.g. openclaw-mcp-adapter) to connect to the OpenMemory MCP server.

The existing SSE endpoint is untouched — this is purely additive.

What changed

  • New import: StreamableHTTPServerTransport from mcp.server.streamable_http and anyio
  • New route: /{client_name}/http/{user_id} accepting POST, GET, and DELETE
    • Extracts user_id and client_name from path params and sets context vars (same pattern as the SSE handler)
    • Creates a per-request stateless StreamableHTTPServerTransport with JSON responses enabled
    • Runs the MCP server in a scoped anyio task group, delegates to transport.handle_request(), then terminates
  • Bug fix: The transport writes its response directly via the ASGI send callable. Without interception, FastAPI would also try to send its own response — a "double-response" bug that only worked because uvicorn silently dropped the second one. Fixed by capturing the transport's ASGI messages via capture_send and returning a proper starlette.responses.Response to FastAPI. A response_started guard ensures a transport crash returns 500 instead of an empty 200.

Why

The MCP spec defines two transports: SSE and Streamable HTTP. The OpenMemory MCP server currently only supports SSE. Some MCP client adapters (like openclaw-mcp-adapter) only speak Streamable HTTP. Adding this transport makes OpenMemory accessible to a wider set of MCP clients without changing existing behavior.

Type of change

  • New feature (non-breaking change which adds functionality)
  • Bug fix (double-response in Streamable HTTP handler)

Testing

Automated tests (21 tests)

New test suite at openmemory/api/tests/test_mcp_server.py using pytest-asyncio and httpx ASGI transport:

Category Tests What's verified
Basic routing 5 POST initialize returns valid JSON-RPC, DELETE returns 405 (stateless), missing Accept → 406, invalid JSON → 400, wrong path → 404
Protocol flow 6 tools/list returns all 5 tools, tools have descriptions and inputSchemas, unknown tool returns error, unknown JSON-RPC method returns error, Content-Type is application/json
Context variables 3 user_id and client_name set correctly from path params, isolation across different user_ids, isolation across different client_names
Response correctness 4 Error status codes (406, 405) forwarded correctly, sequential requests get independent responses, wrong Content-Type returns error
Route registration 3 SSE, SSE messages, and Streamable HTTP routes are registered

Run with:

cd openmemory/api && python -m pytest tests/test_mcp_server.py -v

Manual verification

Tested against a live uvicorn server (OPENAI_API_KEY=test-key uvicorn main:app):

  1. Initialize:

    curl -X POST http://localhost:8765/mcp/testclient/http/user1 \
      -H 'Content-Type: application/json' \
      -H 'Accept: application/json, text/event-stream' \
      -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"0.1.0"}}}'
    # → 200 OK with serverInfo, capabilities, protocolVersion
  2. tools/list:

    curl -X POST http://localhost:8765/mcp/testclient/http/user1 \
      -H 'Content-Type: application/json' \
      -H 'Accept: application/json, text/event-stream' \
      -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
    # → 200 OK with all 5 tools
  3. tools/call:

    curl -X POST http://localhost:8765/mcp/testclient/http/user1 \
      -H 'Content-Type: application/json' \
      -H 'Accept: application/json, text/event-stream' \
      -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"add_memories","arguments":{"text":"I like pizza"}}}'
    # → 200 OK with tool response (graceful error without vector store)
  4. Error handling: DELETE → 405, missing Accept → 406, invalid JSON → 400

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • My changes generate no new warnings
  • New and existing unit tests pass locally with my changes
  • I have checked my code and corrected any misspellings

Add a stateless Streamable HTTP transport at /mcp/{client_name}/http/{user_id}
alongside the existing SSE transport. This enables MCP clients that only support
Streamable HTTP (like openclaw-mcp-adapter) to connect to OpenMemory.
@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Feb 24, 2026

CLA assistant check
All committers have signed the CLA.

…ests

The original handler wrote the MCP transport response directly to the raw
ASGI send callable (request._send), then FastAPI also tried to send its
own response — causing a double-response that only worked because uvicorn
silently dropped the second one.

Fix by intercepting the transport's ASGI messages via capture_send and
returning a proper starlette Response to FastAPI.  Also adds a
response_started guard so a transport crash returns 500 instead of an
empty 200.

Adds 21 tests covering the full JSON-RPC protocol flow, error handling,
context-variable isolation, and route registration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@utkarsh240799 utkarsh240799 self-requested a review March 24, 2026 14:58
@whysosaket whysosaket merged commit 13c7f84 into mem0ai:main Mar 25, 2026
1 of 2 checks passed
@lan17 lan17 deleted the lan17/add-streamable-http-transport branch March 27, 2026 19:02
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.

[OpenMemory] Streamable HTTP Support Streamable HTTP MCP server

4 participants