Answer Orbit MCP notifications with 202 Accepted
What does this MR do and why?
The Orbit MCP endpoint, POST /api/v4/orbit/mcp, answers JSON-RPC notifications in two ways that break MCP clients:
notifications/initializedgets204 No Content, becauseformat_jsonrpc_responsecallsbody false, which Grape turns into a 204.- A notification the endpoint has no handler for, such as
notifications/cancelled, gets404 Not Foundwith a JSON-RPC-32601 Method not founderror, becauseinvoke_basic_handlerrefuses the method before the response for a notification is chosen.
Every MCP revision the endpoint negotiates (2025-03-26, 2025-06-18 and 2025-11-25) says that a server that accepts a notification MUST answer 202 Accepted with no body, and so does 2026-07-28, which the endpoint accepts in initialize and answers with 2025-11-25. The main MCP endpoint already answers notifications/initialized that way. Both answers break clients today. Bifrost, an MCP gateway built on mcp-go, cannot add Orbit, because mcp-go accepts only 200 or 202 for a notification and fails the handshake on the 204 (reported on #628350 (closed)). GitLab Duo CLI stops using Orbit after the 404 it gets for the notifications/cancelled it sends whenever a turn pauses for tool approval (gitlab-org/editor-extensions/gitlab-lsp#3176). The MCP cancellation utility lets a receiver ignore a cancellation for a request it does not know or cannot cancel, which is every request this stateless endpoint is asked to cancel, so refusing it serves no purpose.
This MR changes ee/lib/api/orbit/mcp.rb in two places:
format_jsonrpc_responseanswers a notification with202 AcceptedandGitlab::Json::Precompiled.new(''), the same body the main endpoint uses, with the same comment on why:body falseforces a 204, and a plain''would be JSON-dumped to"".invoke_basic_handlerlooks the handler up first and, for a notification with no handler, returns before it builds a gRPC client, so the response is the 202 above. A request that carries anidand names a method the endpoint does not handle still gets404with-32601, which is also what 2026-07-28 prescribes for an unknown request method.
ping is one of those requests: it still answers 404 with -32601 here, as it does on the main endpoint until !259873 adds a handler there; I left it out of this MR.
I maintain gitlab-mcp-server, an MCP server for GitLab that also calls the Orbit API, and picked this up during the Hackathon.
References
Closes #628350 (closed)
Related to #627825: #628350 (closed) is part of it, the work toward the stateless MCP revision 2026-07-28 on both endpoints.
Related to gitlab-org/editor-extensions/gitlab-lsp#3176: GitLab Duo CLI drops Orbit after the 404 for notifications/cancelled. It was moved there from #631580 (moved), and its server-side cause is the second fix here.
Related to #632259: the same fall-through for unhandled notifications on the main endpoint, POST /api/v4/mcp, which !259873 addresses there. The two MRs touch different files.
- #628350 (closed) planned to fold this into !254461 (closed), which shared the JSON-RPC handling of both endpoints; that MR was closed without merging, so this MR fixes the Orbit endpoint on its own.
- MCP Streamable HTTP transport, "Sending Messages to the Server", item 4 (2025-11-25): https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#sending-messages-to-the-server. The 2026-07-28 text is item 5 of "Sending Messages": https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http#sending-messages.
- MCP cancellation, "Behavior Requirements", item 5 (a receiver MAY ignore a cancellation notification whose request is unknown, already completed or cannot be cancelled): https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/cancellation#behavior-requirements.
Screenshots or screen recordings
Measured with a throwaway request spec, not part of this MR, posting each message as JSON with an OAuth token that carries mcp_orbit, on this branch and with master's ee/lib/api/orbit/mcp.rb (at 6eca5a2053f8):
Message posted to /api/v4/orbit/mcp |
master |
This branch |
|---|---|---|
notifications/initialized, no id |
204, empty body |
202, empty body |
notifications/cancelled, no id |
404, {"jsonrpc":"2.0","error":{"code":-32601,"message":"Method not found","data":{"method":"notifications/cancelled"}},"id":null} |
202, empty body |
notifications/progress, no id |
404, the same -32601 error |
202, empty body |
ping, "id": 7 |
404, -32601 with "id":7 |
unchanged |
unknown/method, "id": 8 |
404, -32601 with "id":8 |
unchanged |
notifications/cancelled, "id": null |
400, -32600 with id is empty |
unchanged |
How to set up and validate locally
-
On a GDK with a Premium or Ultimate license, set
orbit: enabled: trueinconfig/gitlab.yml(knowledge_graphis its legacy alias) and restart. Neither notification reaches the Knowledge Graph service, so it does not need to be running. -
Create a personal access token with the
read_apiscope, which the endpoint accepts alongsidemcp_orbitandmcp. -
Post both notifications:
H=(-H "Authorization: Bearer <token>" -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream") curl -s -w " -> HTTP %{http_code}\n" "${H[@]}" http://gdk.test:3000/api/v4/orbit/mcp \ -d '{"jsonrpc":"2.0","method":"notifications/initialized"}' curl -s -w " -> HTTP %{http_code}\n" "${H[@]}" http://gdk.test:3000/api/v4/orbit/mcp \ -d '{"jsonrpc":"2.0","method":"notifications/cancelled","params":{"requestId":1,"reason":"test"}}' -
On this branch both print
-> HTTP 202with nothing before it. Onmasterthe first prints-> HTTP 204and the second prints the-32601error body followed by-> HTTP 404.
bundle exec rspec ee/spec/requests/api/orbit/mcp_spec.rb passes on the branch: 87 examples, 0 failures, and the same 4 pending as on master. With master's ee/lib/api/orbit/mcp.rb and this branch's spec, exactly the changed example and the new one fail: notifications/initialized with 204, and the new notifications/cancelled example with 404 and the -32601 body. RuboCop and both commit message linters report nothing.
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.