fix(mcp): never respond to JSON-RPC notifications (#129)#131
Conversation
Codex CLI v0.120.0 fails to start the standalone MCP server with "MCP startup failed: Transport closed". Other MCP clients (Claude Desktop, Cursor, OpenClaw) work fine with the same binary. Reported in #129 by @GuillaumeOuint. Root cause: src/mcp/transport.ts unconditionally writes a JSON-RPC response for every incoming message, including notifications. Per JSON-RPC 2.0 §4.1 a notification is "a Request object without an id member" and "the Server MUST NOT reply to a Notification". Strict clients close the transport when they receive an unsolicited response — exactly the symptom Guillaume reported. The trigger sequence on Codex CLI: 1. Codex sends initialize (request, has id) → server responds ✓ 2. Codex sends notifications/initialized (notification, no id) 3. Our handler in standalone.ts returns {} 4. Transport writes {"jsonrpc":"2.0","result":{}} to stdout (no id field because request.id was undefined) 5. Codex parses an unsolicited response → closes transport 6. Guillaume sees "Transport closed" This worked accidentally for lenient clients (Claude Desktop, Cursor, etc.) which silently ignored the spurious responses. Fix: - transport.ts: detect notifications via missing/null id field, run the handler for side effects, then drop the response. Errors inside notification handlers are logged to stderr (not silently swallowed) so production debugging still works - transport.ts: malformed messages that have no id are also dropped silently (consistent with notification semantics) instead of being responded to with id: null - transport.ts: extracted processLine() so the line-handling logic is testable independently of process.stdin/stdout - new test/mcp-transport.test.ts with 9 tests covering: request responses (success + error), notification suppression (no id + null id + handler-throws), malformed input (parse error, invalid request shape with id, invalid request shape without id, empty lines) Tests: 693/693 (684 baseline + 9 new). Build clean.
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughExtracted per-line JSON-RPC handling into Changes
Sequence Diagram(s)mermaid Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/mcp/transport.ts`:
- Around line 54-95: The code currently treats any non-null id as valid and may
call handler or echo invalid ids; before invoking handler (around where parsed
is cast to JsonRpcRequest and before calling isNotification/handler), explicitly
validate request.id: if request.id is null/undefined treat as a notification; if
request.id is present but not a string or number, immediately writeOut a
JSON-RPC error with code -32600 and id: null and return; only call handler when
id is absent (notification) or id is a string/number, and when writing responses
use the validated id (casted to string | number) in writeOut; ensure writeErr
path for notifications remains unchanged.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: c17ff76a-e1a3-4b1e-a25e-c8862e3df140
📒 Files selected for processing (2)
src/mcp/transport.tstest/mcp-transport.test.ts
CodeRabbit flagged that processLine only checked `id != null` in the
invalid-shape path, which meant (a) malformed messages with non-primitive
ids could be echoed back with that id, and (b) valid-shape requests with
id: {} / [] / true would fall through to the handler and be responded to
with the bogus id as the response id — both spec violations.
Add isValidId() (string | number | null | undefined), extract rawId once
as unknown, and:
- In the invalid-shape path, only echo if rawId is string or number.
- After shape validation, reject bad-id requests with -32600 / id: null
before invoking the handler.
Adds 5 test cases covering id: object, array, boolean, plus the malformed
+ non-primitive-id drop path and a positive string-id case.
|
Addressed the CodeRabbit id-validation finding in 461ef82. Before: Now:
5 new tests in |
Fixes #129 reported by @GuillaumeOuint.
Symptom
```
MCP client for agentmemory failed to start: MCP startup failed: Transport closed
```
Codex CLI v0.120.0 fails to start the standalone MCP server. Other MCP clients (Claude Desktop, Cursor, OpenClaw) work fine with the same binary. Running `npx agentmemory-mcp` directly in a terminal also "works" — it prints the v0.8.1 banner and waits for input. Only Codex's stdio handshake breaks.
Root cause
`src/mcp/transport.ts` unconditionally writes a JSON-RPC response for every incoming message, including notifications. Per JSON-RPC 2.0 §4.1:
Lenient clients (Claude Desktop, Cursor) silently ignore unsolicited responses. Strict clients (Codex CLI) treat them as protocol violations and close the transport. That's exactly what Guillaume sees.
The exact sequence on Codex CLI:
initialize(request, hasid) → server responds ✓notifications/initialized(notification, noid)`memory_save` / `memory_recall` etc never had a chance to fire because the handshake itself was broken.
Fix
The existing `test/mcp-standalone.test.ts` mocks the transport entirely so it never exercised the notification path. The new file imports the real `processLine` and verifies the actual behavior.
Why the bug went undetected
Test plan
Related
Summary by CodeRabbit
Bug Fixes
Tests