Skip to content

feat(transport): add modern Streamable HTTP endpoint POST /mcp - #2

Open
TumGovic wants to merge 1 commit into
KilDoomWise:mainfrom
TumGovic:feat/streamable-http-mcp
Open

feat(transport): add modern Streamable HTTP endpoint POST /mcp#2
TumGovic wants to merge 1 commit into
KilDoomWise:mainfrom
TumGovic:feat/streamable-http-mcp

Conversation

@TumGovic

Copy link
Copy Markdown
Contributor

Что добавлено

Современный транспорт MCP Streamable HTTP — эндпоинт POST /mcp, в дополнение к текущему legacy SSE.

Зачем

Legacy SSE держит долгоживущий стрим и серверный sessionId. За реверс-прокси (Cloudflare-туннель, mcpproxy и т.п.) этот стрим рвётся:

SSE connection lost: unexpected EOF
MCP Ping failed: 404 {"error":"Session not found"}

После обрыва сервер выглядит здоровым и tools/list работает, но любой tools/call падает с 404 Session not found. Современные клиенты уже ждут /mcp, а не /sse.

Как сделано

  • POST /mcp — stateless Streamable HTTP: на каждый запрос поднимается свой MCP-сервер, поэтому нет сессии, которая может протухнуть, и нет стрима, который может оборваться.
  • Поддержаны одиночные сообщения и батчи; payload только из нотификаций отвечает 202 без тела, по спеке.
  • GET /mcp405 с заголовком Allow, DELETE /mcp204.
  • src/streamable.ts — реализация Transport внутри процесса, без моков IncomingMessage/ServerResponse.
  • Legacy GET /sse + POST /messages не тронуты, старые клиенты продолжают работать.
  • Баннер запуска и README описывают оба транспорта, /mcp помечен как рекомендуемый.

Проверка

bunx tsc --noEmit — чисто. Живой прогон против POST /mcp с Bearer-токеном:

Запрос Результат
initialize 200, protocolVersion 2025-03-26
notifications/initialized 202
tools/list 200, 31 тул
tools/callnotcode_status 200, корректный JSON

Legacy SSE keeps a long-lived stream and a server-side sessionId. Behind a
reverse proxy (Cloudflare tunnel, mcpproxy) that stream gets dropped, and every
following tools/call fails with 404 Session not found even though the server is
healthy and tools/list still works.

Add a stateless Streamable HTTP transport:

- POST /mcp handles a JSON-RPC message or batch, spinning up a fresh MCP server
  per request, so there is no session to expire and no stream to drop.
- Notification-only payloads answer 202 with no body, per spec.
- GET /mcp answers 405 with Allow, DELETE /mcp answers 204.
- src/streamable.ts adds an in-process Transport implementation, so no node
  IncomingMessage/ServerResponse mocks are needed.
- Legacy GET /sse + POST /messages are untouched for older clients.
- Startup banner and README document both transports, /mcp recommended.
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.

1 participant