fix(stdio): treat "id": null as a request, not a notification#169
fix(stdio): treat "id": null as a request, not a notification#169dpage wants to merge 3 commits into
Conversation
Per JSON-RPC 2.0 §4.1, a Notification is a Request object without an
"id" member, while a Request whose id is explicitly null is still a
Request and MUST receive a reply. Go's interface{} unmarshaling
collapses "id absent" and "id": null to the same nil value, so the
previous default-branch guard (`if req.ID != nil`) silently dropped
unknown-method requests with explicit null id and the hardcoded
notifications/initialized case suppressed null-id requests for that
method too.
Mirror the HTTP transport fix from #142/#143: probe the raw bytes
with hasIDField to distinguish absent vs null id, short-circuit
notifications before dispatch, and remove the now-redundant nil-id
guards in handleRequest's default branch and handlePing. The
notifications/initialized case is no longer reachable through
dispatch and is dropped from the switch.
Tests exercise the full Run() read loop with stdin piped from a pipe:
- notifications/initialized produces no output
- unknown notification methods produce no output
- {"id": null, "method": "unknown"} receives -32601
- {"id": null, "method": "ping"} receives an empty-object result
Closes #152
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
WalkthroughThis PR fixes a JSON-RPC 2.0 compliance bug in the stdio transport where requests with explicit ChangesJSON-RPC stdio dispatch compliance
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 23 |
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
Address CodeRabbit's docstring coverage pre-merge warning. Move the explanatory comments above the new TestRunStdio_* and handlePing function declarations and prefix them with the function name, per Go doc convention. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@internal/mcp/server.go`:
- Around line 223-228: The JSON-RPC ping handler (handlePing -> sendResponse)
promises to reply even when req.ID is a nil interface (incoming "id": null), but
JSONRPCResponse.ID in internal/mcp/types.go is tagged `json:"id,omitempty"` so
marshaling will drop the id field; remove `omitempty` from the JSON tag on
JSONRPCResponse.ID (or implement custom MarshalJSON for JSONRPCResponse to
explicitly emit "id": null when ID == nil) so responses to null-id pings include
`"id": null`, and update the stdio tests to assert the raw JSON contains `"id":
null`.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 18ceb9d8-0db0-4776-a611-444d0a9a85e8
📒 Files selected for processing (2)
internal/mcp/server.gointernal/mcp/server_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
- internal/mcp/server_test.go
Per JSON-RPC 2.0 §5.1, the response object MUST include the id
member; the value is the originating request's id, or null when the
id cannot be determined (parse error / invalid request) or when the
request itself used "id": null.
JSONRPCResponse.ID was tagged json:"id,omitempty", so Go's encoder
dropped the field whenever the interface{} value was nil — producing
a response without an id field, which is itself a malformed JSON-RPC
body. This contradicted the new handlePing doc comment promise that
null-id pings receive a response, and affected both the HTTP transport
(parse errors and the id:null branch added in #142/#143) and the new
stdio behavior in this PR.
Drop omitempty from ID so a nil interface marshals as JSON null. Add
raw-byte assertions to the stdio null-id tests and to the HTTP
TestHandleNotification_NullIDIsRequest regression guard so the spec
compliance is verified, not just the parsed Error code.
Addresses CodeRabbit review on PR #169 (discussion_r3310822965).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
hasIDFieldon the raw line bytes, matching the HTTP transport fixfrom HTTP transport: server replies to JSON-RPC notifications with malformed (no-
id) Response objects #142/fix(http): return 202 Accepted with empty body for JSON-RPC notifications #143. Notifications now never reach dispatch.req.ID == nilguards inhandleRequest's default branch andhandlePing; a request withexplicit
"id": nullnow reaches the dispatcher and is answered.notifications/initializedcase from thedispatch switch (filtered upstream, and a request with that method
and
id: nullwas being silently swallowed).Test plan
go test ./internal/mcp/— added Run()-level tests that pipestdin and assert the resulting stdout
notifications/initializedproduces no response/cancelled,/roots/list_changed,some/unknown) produce no response{"jsonrpc":"2.0","id":null,"method":"unknown/method"}receives-32601 Method not found{"jsonrpc":"2.0","id":null,"method":"ping"}receives anempty-object result
make build,make test,make fmtall clean.make lintreports 3 pre-existing staticcheck findings in
internal/embedding/{ollama,provider}.go(untouched by this PR;present on
origin/main).Closes #152
Summary by CodeRabbit
Bug Fixes
Documentation
Tests