Keep tool argument headers and bodies in sync - #15
Merged
Merged
Conversation
quinnj
added a commit
that referenced
this pull request
Oct 2, 2026
Local MCP servers using stdin/stdout cannot currently use this package's client: a manually constructed stdio descriptor fails during initialization with `transport_unsupported`. Add the qualified `ModelContextProtocol.prepare_stdio_client(command)` constructor, including a do-block form, and reuse the existing initialization, list/call, handler, and termination APIs. The client owns one child process and its protocol pipes. It matches concurrent responses by exact request ID, separates stderr, bounds frames and pending work, and applies one deadline to queued writes and response waits. EOF or invalid output fails pending calls and starts process cleanup. Write failures preserve the original failure; complete responses received before EOF remain available. Notifications retain arrival order. Legacy server requests run in bounded, owned tasks so a handler's nested call can receive another server request before completing. Request handlers may overlap notifications and other request handlers. External `close` waits for all owned tasks under one deadline. A callback calling `close` waits for process and IO shutdown without waiting on other callbacks, avoiding cycles between callbacks that close concurrently. Unfinished callbacks remain owned for a later external close; arbitrary blocked user code still needs cooperative release. Protocol selection is explicit: `2025-11-25` by default, or `2026-07-28` discovery and request metadata. Automatic version probing/restart/replay, modern subscriptions, and Agentif catalog import are outside this change. Subprocess stdio requires the Julia runtime; the existing static native server remains separate. There are no new dependencies or exports. Existing HTTP behavior and positional client constructors remain covered. `MCPClient` gains one optional field, changing its exact field layout. Validation on macOS arm64: - Full Julia 1.10.12 / HTTP 1.11 / OAuth 2 suite: 1,048 assertions passed. - Full Julia 1.13.1 / HTTP 2.8 / OAuth 4 suite: 1,053 assertions passed, including seven strict native compilation/execution checks with zero verifier errors or warnings. - Both full suites pass all 446 stdio checks and the 125 argument-serialization checks merged in #15. The new nested-request regression failed before the correction and passes afterward. - Lifecycle controls cover blocked notifications, active-handler overflow, EOF/malformed output during nested calls, simultaneous handler closes, notification/handler close, retained callbacks, cleanup retry, exact IDs, cancellation, and blocked pipes. - An independent review found no actionable issues. Its separate three-level nested-request and blocked-notification check passed all nine assertions, including shutdown and recovery. - Strict documentation, doctests, and the runnable local-child example pass. Local documentation deployment was disabled. AI-driven research and implementation with Codex, followed by independent review. Co-authored by Codex
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Modern
call_toolencoded known tools' arguments twice: once forMcp-Param-*headers and again for the request body. Custom JSON lowering could therefore send a routing header such aswestwith body valueeast, causing HTTP 400 / HeaderMismatch (-32020) before the tool ran.Prepare one validated argument snapshot and reuse it for both representations through the existing
JSON.JSONTextAPI. Full object validation, duplicate-key/null semantics, schema discovery, omitted arguments, legacy calls and generic JSON-RPC behavior stay covered. This repairs the existing 2026-07-28 header/body contract; no exports or dependencies are added.Validation:
--trim=safecompile/run and Documenter build/doctests pass. The native gate does not cover the dynamic modern client.9b31da6f65ba2510ce90a576c20f66748f7c8604, including Linux/macOS/Windows on minimum/current/nightly Julia.Alternating same-process loopback calls, cached schema, HTTP 2.8 / JSON 1.9, median of 31 pairs:
These measurements include client/server JSON handling and actual TCP, excluding discovery, TLS and application work. Small warmed calls were unchanged. The first routed call added approximately 9 ms on Julia 1.13 and 22 ms on 1.10 in three fresh-process pairs; current-runtime numeric calls allocate about 7 KB more. Unrelated host work remained active, so these are bounded measurements, not a general throughput claim.
This is an AI-driven, researched change. An independent AI review traced the client, JSON-RPC serialization, schema validation and server header checks at the exact published head. Fresh complete suites pass on Julia 1.13.1 (607 checks, including the 7 static-server native checks) and Julia 1.10.12 (601 checks). All 11 hosted checks are green.
Co-authored by Codex