MCP endpoint (/api/mcp) rejects spec-compliant clients — 400 on GET, DELETE, and notifications without params. 405
@jordan-simonovski is already working on this.
Since Jul 21, 2026.
- Dominant language
- TypeScript
- Stars
- 9.9k
- Forks
- 471
- Avg merge
- 2d 4h
- Merged PRs (30d)
- 117
Description
MCP endpoint (/api/mcp) rejects spec-compliant clients — 400 on GET, DELETE, and notifications without params
Environment
- HyperDX
2.31.0, self-hosted (imagedocker.hyperdx.io/hyperdx/hyperdx:2, digestsha256:e9db8e393e30117ccef3b1e094dbfc1d911d4325535e5af95b95199c8594474a, container envservice.version=2.31.0) - The MCP server self-reports as
{"name":"clickstack","version":"2.31.0-beta"}in theinitializeresponse - Client: Claude Code
2.1.215(uses the official@modelcontextprotocol/sdkStreamable HTTP transport) - Auth:
Authorization: Bearer <api key>— auth itself works correctly (401 on bad key)
Summary
The MCP Streamable HTTP endpoint returns HTTP 400 for three request shapes that the MCP spec
requires to be handled differently. Any client built on the official MCP TypeScript SDK treats these
400s as a failed connection, so the server is unusable with such clients even though POST requests
(initialize, tools/list, tools/call) all work fine. The client-visible symptom is:
Failed to reconnect to <server>: HTTP 400 at https://<your-clickstack-host>/api/mcp
Issue 1: GET (standalone SSE stream) → 400 or hang, should be 405
Per the Streamable HTTP spec, a server that does not offer a server-initiated SSE stream MUST return 405 Method Not Allowed. SDK clients treat 405 as "no stream offered, continue"; any other status fails the connection.
curl -i "https://<your-clickstack-host>/api/mcp" \
-H "Authorization: Bearer <API_KEY>" \
-H "Accept: text/event-stream"
Actual: 400 Bad Request (observed intermittently: sometimes the connection is held open with zero bytes sent instead).
Expected: 405 Method Not Allowed (or an actual SSE stream).
Issue 2: DELETE (session termination) → 400, should be 405 or 200
Per the spec, clients MAY send DELETE to terminate a session; servers that don't allow client-initiated termination MUST respond 405. SDK clients call this during reconnect and fail on any other error status.
curl -i -X DELETE "https://<your-clickstack-host>/api/mcp" \
-H "Authorization: Bearer <API_KEY>"
Actual: 400 Bad Request.
Expected: 405 (session termination not supported) or 200. Note the server appears stateless anyway — POSTs succeed even with an arbitrary Mcp-Session-Id header.
Issue 3: Notifications without params → 400, should be 202
In JSON-RPC 2.0 the params member is optional ("MAY be omitted"). The official MCP TS SDK sends notifications/initialized without params immediately after initialize, so the handshake dies here even if issues 1–2 are worked around.
# As sent by the official SDK — rejected:
curl -i -X POST "https://<your-clickstack-host>/api/mcp" \
-H "Authorization: Bearer <API_KEY>" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","method":"notifications/initialized"}'
# → 400 Bad Request, empty body
# Identical message with an empty params object — accepted:
curl -i -X POST "https://<your-clickstack-host>/api/mcp" \
-H "Authorization: Bearer <API_KEY>" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","method":"notifications/initialized","params":{}}'
# → 202 Accepted
Actual: 400 with empty body when params is absent.
Expected: 202 in both cases — the request-body validator should treat params as optional.
What works (for contrast)
curl -i -X POST "https://<your-clickstack-host>/api/mcp" \
-H "Authorization: Bearer <API_KEY>" \
-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":"repro","version":"1.0"}}}'
# → 200, valid initialize result
tools/list and tools/call also work normally, including with stale/unknown Mcp-Session-Id values.
Impact / workaround
Because the SDK aborts the connection on the first non-405 error, MCP clients (e.g. Claude Code) cannot connect at all. We currently work around it in a reverse proxy: answer GET/DELETE on /api/mcp with a synthetic 405 and rewrite upstream 400s on that path to 202 — with those shims in place the client connects and all tools work, confirming the core functionality is fine and only the transport edge cases are broken.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.