MCP hardening: nested-schema passthrough + hung-server timeout#124
Conversation
Module of the runtime-core split (off main). internal/mcp had no drift on main, so this is a clean extract of the zenline MCP improvements. Deps (config, tools) are on main; uses tools.PropertySchema.Properties/Required from #119. No new deps. - schema.go: serialize nested object Properties/Required (and object-typed Items) when converting tools.PropertySchema <-> MCP JSON schema, so tools with nested args are advertised faithfully to/from MCP hosts instead of being flattened (audit H6). - client.go/server.go: bound MCP stdio handshake/calls so a hung or non-responsive MCP server can't block the caller forever; surface a timeout instead (audit H9). hang_test.go covers it. - registry.go: recognize the hardened client/schema paths; registry_test/schema_test added. build/vet/-race/full-suite + GOOS=windows build green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Zero automated PR reviewVerdict: No blockers found Blockers
Validation
ScopeHead: This deterministic review checks validation status and basic diff hygiene. A human reviewer still owns product judgment and design quality. |
WalkthroughThis PR refactors the MCP client and server to handle concurrent requests and graceful cancellation, makes tool registration atomic with rollback semantics, and extends schema conversion to support nested JSON-schema structures. ChangesMCP Concurrency, Registration Atomicity, and Schema Support
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
🚥 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)
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
internal/mcp/hang_test.go (2)
86-130: 💤 Low valueSilence errcheck warning on deferred Close.
The linter flags the unchecked
reader.Close()return value. In test cleanup this is harmless, but can be silenced:- defer reader.Close() + defer func() { _ = reader.Close() }()🤖 Prompt for 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. In `@internal/mcp/hang_test.go` around lines 86 - 130, Replace the bare deferred call to reader.Close() in TestClientRequestUnblocksOnContextCancel with a suppressed-error wrapper so the linter stops complaining; e.g. change defer reader.Close() to defer func() { _ = reader.Close() }() (locate the call near newBlockingReader()/reader variable at the top of the test).
134-156: 💤 Low valueSame errcheck suppression.
- defer reader.Close() + defer func() { _ = reader.Close() }()🤖 Prompt for 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. In `@internal/mcp/hang_test.go` around lines 134 - 156, The test still has the same unchecked-error issue in its cleanup path. Update TestServeReturnsOnContextCancelWithBlockingReader to handle or explicitly suppress the error returned by reader.Close() in the deferred cleanup, using the same approach already requested elsewhere, so the blockingReader shutdown is intentional and errcheck-clean.
🤖 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.
Nitpick comments:
In `@internal/mcp/hang_test.go`:
- Around line 86-130: Replace the bare deferred call to reader.Close() in
TestClientRequestUnblocksOnContextCancel with a suppressed-error wrapper so the
linter stops complaining; e.g. change defer reader.Close() to defer func() { _ =
reader.Close() }() (locate the call near newBlockingReader()/reader variable at
the top of the test).
- Around line 134-156: The test still has the same unchecked-error issue in its
cleanup path. Update TestServeReturnsOnContextCancelWithBlockingReader to handle
or explicitly suppress the error returned by reader.Close() in the deferred
cleanup, using the same approach already requested elsewhere, so the
blockingReader shutdown is intentional and errcheck-clean.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: afb30637-52b6-4c6b-b579-4f3b77d76341
📒 Files selected for processing (7)
internal/mcp/client.gointernal/mcp/hang_test.gointernal/mcp/registry.gointernal/mcp/registry_test.gointernal/mcp/schema.gointernal/mcp/schema_test.gointernal/mcp/server.go
Vasanthdev2004
left a comment
There was a problem hiding this comment.
Verdict: approved.
The MCP timeout/dispatch changes look good and the CI smoke suite is green. I left one small hardening note around fractional JSON-RPC ids, but I do not think it should block this PR.
| return typed, true | ||
| case int64: | ||
| return int(typed), true | ||
| case float64: |
There was a problem hiding this comment.
Small hardening follow-up: this accepts any float64 id by truncating it, so a malformed response id like 1.9 would match pending request 1. JSON-RPC numeric ids should be integral for our dispatcher; consider rejecting non-integral or out-of-range float values here, similar to the integer parsing used elsewhere.
Module — MCP hardening
Independent slice of the runtime-core split (off
main).internal/mcphad zero drift on main, so this is a clean extract of the zenline MCP improvements. Deps (config,tools) are on main; usestools.PropertySchema.Properties/Requiredfrom #119. No new deps.What's in it
schema.go) — when convertingtools.PropertySchema⇄ MCP JSON schema, serialize nested objectProperties/Required(and object-typedItems) instead of flattening them, so tools with nested args are advertised faithfully to/from MCP hosts. (audit H6.)client.go/server.go) — bound the MCP stdio handshake/calls so a hung or non-responsive MCP server can't block the caller forever; surface a timeout instead. (audit H9;hang_test.gocovers it.)registry.gorecognizes the hardened paths;registry_test.go/schema_test.goadded.Testing
go build ./...,go vet ./...,go test ./...,go test -race ./internal/mcp/,GOOS=windows go build ./...all green.Part of decomposing #101 (draft).
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes