Skip to content

[MCP] Streamable HTTP transport: session, lifecycle, Origin, contentTypes reuse #614

@kylebernhardy

Description

@kylebernhardy

Scope. Implement the MCP Streamable HTTP transport (POST/GET/DELETE /mcp), session management (Mcp-Session-Id), required headers (MCP-Protocol-Version, Origin), and the full initialize / notifications/initialized lifecycle. Reuse Harper's existing Request/Headers abstraction and the text/event-stream serializer in contentTypes.ts.

Design reference. Sections "Transports → Streamable HTTP", "Security headers (MCP §transports → Security Warning)", "Server-side integration with existing Harper machinery", "Lifecycle Notes", and "HTTP Headers" in #465.

Acceptance criteria

  • POST /mcp accepts JSON-RPC, returns either application/json or upgrades to text/event-stream via the existing serializer at server/serverHelpers/contentTypes.ts:127-161. No new Fastify API surface added.
  • GET /mcp returns 405 when no SSE channel is offered, or opens a text/event-stream channel for server-initiated messages.
  • DELETE /mcp terminates the session when session.allowClientDelete: true; returns 405 otherwise.
  • Mcp-Session-Id issued on initialize (UUIDv4); enforced on subsequent requests; 400 if missing, 404 if terminated.
  • MCP-Protocol-Version required after initialize; 400 on unsupported. v1 supports 2025-06-18 (preferred) and 2025-03-26 (backcompat).
  • Origin header validated against existing http_corsAccessList / operationsApi_network_corsAccessList; 403 on mismatch. No new mcp.allowedOrigins key.
  • Client-sent JSON-RPC notifications/responses return HTTP 202 with empty body (e.g., notifications/initialized).
  • initialize returns server capabilities (tools.listChanged: true, resources.listChanged: true, logging: {}); subsequent requests reach a stub handler.
  • Unit tests cover each MUST behavior; integration test exercises full initializenotifications/initialized over HTTP.

Out of scope. No tools, no resources, no rate limiting, no audit. Resumability via Last-Event-ID is deferred to v2 — the SSE serializer's id field is reserved but not used.

Stacks on. #613 (component scaffold).

Branch & PR conventions

Smoke test

curl -sS -X POST http://localhost:9925/mcp \
  -H 'Origin: http://localhost' \
  -H 'Accept: application/json, text/event-stream' \
  -H 'Authorization: Bearer ...' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"smoke","version":"0"}}}' \
  -i
# Expected: 200, `Mcp-Session-Id` response header, body with serverInfo + capabilities.

Tracking. Part of #465. Sub-issue #2 of 11.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:componentsComponents / applications subsystemarea:mcpModel Context Protocol (MCP) server: protocol, profiles, stdio CLIenhancementNew feature or requestfeature:mcp-v1Rollout of native MCP server v1 (HarperFast/harper#465). Removed when v1 closes.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions