Skip to content

feat(serve): add Streamable HTTP transport to MCP server (#1143)#1155

Closed
TPAteeq wants to merge 1 commit into
Graphify-Labs:v8from
TPAteeq:serve-http-transport
Closed

feat(serve): add Streamable HTTP transport to MCP server (#1143)#1155
TPAteeq wants to merge 1 commit into
Graphify-Labs:v8from
TPAteeq:serve-http-transport

Conversation

@TPAteeq

@TPAteeq TPAteeq commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Adds a Streamable HTTP transport to the graphify MCP server (graphify/serve.py)
alongside the existing stdio path, so a single shared process (in CI or on a host)
can serve the graph for a whole team — clients point their IDE MCP config at one URL
instead of running graphify locally. Closes #1143.

stdio remains the default; existing setups are unchanged.

What's included

  • HTTP transport--transport http serves the same tools/resources over the MCP
    Streamable HTTP transport (spec 2025-03-26) via StreamableHTTPSessionManager. The
    existing low-level Server and all handlers are reused (refactored into a shared
    _build_server()), so there is no duplication and stdio behavior is byte-for-byte
    unchanged.
  • Auth — optional --api-key (env GRAPHIFY_API_KEY) accepting Authorization: Bearer
    or X-API-Key. Implemented as pure-ASGI middleware (not BaseHTTPMiddleware, which
    would break SSE streaming), constant-time compare, RFC-6750 case-insensitive scheme.
  • Docker — a Dockerfile (+.dockerignore, non-root user) builds the server as a
    container image. Builds from source so the image includes the transport before it lands
    on PyPI; the graph is mounted at runtime, never baked in.

Flags

Flag Default Purpose
--transport {stdio,http} stdio Transport to serve on
--host 127.0.0.1 HTTP bind host (0.0.0.0 to expose)
--port 8080 HTTP bind port
--api-key env GRAPHIFY_API_KEY Require Authorization: Bearer / X-API-Key
--path /mcp HTTP mount path
--json-response off Plain JSON instead of SSE
--stateless off No per-session state (load-balanced / CI)
--session-timeout 3600 Reap idle stateful sessions after N seconds (0 disables)

Interface

# stdio (current, unchanged)
python -m graphify.serve graphify-out/graph.json

# streamable HTTP
python -m graphify.serve graphify-out/graph.json --transport http --port 8080 --api-key $SECRET

# Docker (entrypoint is `python -m graphify.serve`, so no `serve` subcommand word)
docker build -t graphify .
docker run -p 8080:8080 -v "$(pwd)/graphify-out:/data" graphify /data/graph.json --transport http

Security / robustness

- Default bind is loopback (127.0.0.1); binding a wildcard host with no --api-key
prints an explicit exposure warning.
- DNS-rebinding (Host-header) protection is on for specific binds and relaxed only when
the operator intentionally binds a wildcard address.
- A blank --api-key/GRAPHIFY_API_KEY is normalized to "no auth" so it can't be mistaken
for auth being on.
- --session-timeout (default 3600s) reaps idle stateful sessions so a long-running shared
server doesn't leak memory when IDE clients disconnect without a DELETE.

Notes

- No new dependencies and no version bump — starlette and uvicorn already ride along
transitively with the existing [mcp] extra.
- OAuth is intentionally left as a follow-up (issue lists it as such).

Testing

- New tests/test_serve_http.py (16 tests, importorskip-guarded): full initialize → tools/list
round trip over HTTP, auth 401/200 for both header forms, blank-key normalization,
case-insensitive Bearer, custom mount path, stateless mode, session-timeout handling, CLI parsing.
- Adversarial checks against a live server: missing/corrupt/non-json graph and port-in-use all
exit cleanly before binding; malformed/hostile requests degrade to 406/400/404 (no 500/crash);
session reaping verified (200 → 404 after idle).
- Full suite: 1865 passed, 1 skipped; stdio path untouched.

…bs#1143)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@safishamsi

Copy link
Copy Markdown
Collaborator

Landed in 2a683aa. Independent verification confirmed:

  • All 52 existing stdio tests pass unchanged — refactor is byte-for-byte equivalent
  • Query logging (added in v8 post-claude-cli backend ignores GRAPHIFY_API_TIMEOUT and --api-timeout (hardcoded 600s) #1112) is fully preserved in _build_server
  • _ApiKeyMiddleware is correct (raw ASGI, constant-time compare, RFC 6750, blank-key normalization)
  • session_idle_timeout matches SDK API exactly; stateless+timeout guard prevents SDK RuntimeError
  • DNS-rebinding protection logic is correct
  • Full suite: 1865 passed, 1 skipped, 0 failures (+16 new HTTP transport tests)

Two optional hardening notes for a follow-up: add mcp>=1.8 minimum version pin so transport_security import is guaranteed, and bracket IPv6 specific-bind hosts in allowed_hosts. Neither is a blocker.

@safishamsi safishamsi closed this Jun 6, 2026
safishamsi added a commit that referenced this pull request Jun 6, 2026
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@TPAteeq TPAteeq deleted the serve-http-transport branch June 23, 2026 15:01
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): add Streamable HTTP transport to MCP server

2 participants