Skip to content

CL-8905: lock MCP promotion schema fidelity for optional and required arguments - #1181

Merged
TheGreatAxios merged 4 commits into
mainfrom
cl-8905-mcp-optional-schema-regression
Sep 27, 2026
Merged

TheGreatAxios merged 4 commits into
mainfrom
cl-8905-mcp-optional-schema-regression

Conversation

@TheGreatAxios

@TheGreatAxios TheGreatAxios commented Sep 26, 2026 •

Copy link
Copy Markdown
Collaborator

Test-only regression guard for CL-8905 (corbits-code MCP side).

Scope: corbits-code MCP plumbing only. Canceled children CL-8906/CL-8907/CL-8908 untouched; tools-linear untouched; isError/structuredContent surfacing left to in-flight CL-8992.

What this lands:

  • Prior commit d97cf3a proved optional MCP properties survive tool_search promotion with no 'required' key synthesized on the wire.
  • Commit a38e29c added a second promotion test with a mixed schema (required: [limit] plus optional team/customView) asserting the published wire input_schema is deep-equal to the server schema and dispatched args arrive exactly as the caller specified.
  • New commit ae133d3 hardens the guards per review: two loud-failure tests (unadvertised tool dispatch and out-of-scope server rejection, both asserting isError), optional args transmitted on both promotion dispatches ({limit: 1, team: "eng"} and {limit: 1, team: "eng", customView: "mine"} with full receipt asserted), and schema expectations snapshot-cloned before addTools so in-place mutation cannot move both sides.

Per-box evidence (corbits-code-owned paths):

  • Box 1 (direct queries express only caller-requested filters): both promotion tests assert receivedArgs deep-equals the caller's full args; wire schema is byte-faithful, so the model sees exactly the server's filters.
  • Box 2 (invalid/unintended scope cannot silently alter results): 'unadvertised MCP tool dispatch fails loudly' asserts unknown-tool content with isError true on the live-dispatch path (frozen-path test asserts the same isError); 'out-of-scope MCP arguments fail loudly' asserts a server scope-denied rejection surfaces as isError with the actionable message. Wire schema can neither gain nor lose required-ness silently — locked by the deep-equal/snapshot assertions.
  • Box 3 (effective scope visible): promoted tool schemas are published in full on the next infer, asserted via captured Anthropic request bodies.

Red evidence (targeted mutation check in src/mcp/plugin.ts mcpClientTools passthrough, restored after):

  • Injecting required: [] on the all-optional schema turned the optional-promotion test red (hasOwn required true, expected false); restoring returned it green.
  • Deleting required on the mixed schema turned the mixed-promotion test red (snapshot deep-equal diff on required: [limit]); the all-optional test stayed green under the same mutation, proving targeting; restoring returned all green.
    No src changes needed — plumbing passes definitions/args by reference end to end.

Verification:

  • bun run typecheck: exit 0
  • bun test tests/integration/mcp-late-dispatch.test.ts: 6 pass, 0 fail
  • bun run lint: exit 0
  • bun test ./src ./tests ./evals ./scripts --randomize --seed 424242: 8031 pass, 0 fail

@linear-code

linear-code Bot commented Sep 26, 2026

Copy link
Copy Markdown

CL-8905

@TheGreatAxios
TheGreatAxios merged commit ccce53b into main Sep 27, 2026
13 checks passed
@TheGreatAxios
TheGreatAxios deleted the cl-8905-mcp-optional-schema-regression branch September 27, 2026 22:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant