Skip to content

Answer MCP notifications with 202 and no body - #560

Merged
simonhamp merged 1 commit into
NativePHP:mainfrom
mattgrul:mcp-notification-202
Oct 16, 2026
Merged

simonhamp merged 1 commit into
NativePHP:mainfrom
mattgrul:mcp-notification-202

Conversation

@mattgrul

Copy link
Copy Markdown
Contributor

What this changes

McpController::message now answers a JSON-RPC notification (a message
with a string method and no id) with HTTP 202 and an empty body.
Requests are unchanged. A message with no method still gets the existing
JSON-RPC error.

Why

When an MCP client connects, it sends notifications/initialized. The
endpoint answered it with HTTP 200 and this body:

{"jsonrpc":"2.0","id":null,"result":{}}

JSON-RPC 2.0 gives a notification no response, and the MCP Streamable HTTP
transport says the server answers an accepted notification with 202 and no
body (spec).

Claude Code and Grok tolerate the reply, but Codex does not. Its MCP client
logs Deserialize error: data did not match any variant of untagged enum JsonRpcMessage, when send initialized notification, drops the server,
and shows none of the NativePHP docs tools. This was on Codex 0.162.1.

To reproduce:

curl -s -i -X POST https://nativephp.com/api/mcp/message \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","method":"notifications/initialized"}'

Today this prints HTTP/2 200 and the body above. With this change it
prints 202 and no body.

Tests

tests/Feature/McpNotificationTest.php covers notifications/initialized,
another notification, and a message with no method.

I could not run the suite locally, because composer install needs the
Flux Pro credentials. Both changed files pass php -l on PHP 8.4. The
tests workflow runs on push, so it needs a maintainer to run it.

The MCP message endpoint answered a notification, such as
notifications/initialized, with a JSON-RPC response whose id was null. A
notification gets no response in JSON-RPC, and MCP's Streamable HTTP
transport answers it with HTTP 202 and an empty body. Strict clients
reject the response: Codex drops the server at startup and shows none of
its tools.

A message without an id now returns 202 with no body. Requests are
unchanged.
@simonhamp
simonhamp merged commit 680c71f into NativePHP:main Oct 16, 2026
1 check passed
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.

2 participants