Skip to content

fix(mcp): check_chain variant inputs no longer silently discarded#191

Merged
avrabe merged 1 commit intomainfrom
cleanup/mcp-check-chain-variant
Apr 30, 2026
Merged

fix(mcp): check_chain variant inputs no longer silently discarded#191
avrabe merged 1 commit intomainfrom
cleanup/mcp-check-chain-variant

Conversation

@avrabe
Copy link
Copy Markdown
Contributor

@avrabe avrabe commented Apr 29, 2026

Tier A #7 + Tier C #50 from the Wave 3 MCP review. check_chain accepted variant / variant_context but discarded them silently — agents got unfiltered chain results with no warning. Picked Option B per the brief: hard-reject with BAD_INPUT pointing at verify_move/enumerate_moves (Option A requires threading VariantScope through LatencyAnalysis, a v0.10-scale effort). Also added JSON Schema 'not' mutex on all three tools and an up-front mutex guard in check_chain. Tests: 5 new (3 in-process, 3 stdio) covering the BAD_INPUT path, the conflict path, and the schema advertising. Quality gates (cargo test -p spar-mcp, clippy, fmt, rivet validate) all pass. Out of scope per brief: protocolVersion (#21), notifications/initialized (#22), outputSchema (#23).

Previously `crates/spar-mcp/src/tools/check_chain.rs` accepted `variant`
and `variant_context` in its input schema but bound both to `_variant`
/ `_unused`, dropping them on the floor. An LLM agent supplying a
variant name received unfiltered chain results with no warning — a
silent correctness bug that broke the rivet<->spar v1 contract for
variant-scoped queries.

Wiring full variant scoping through `LatencyAnalysis` is non-trivial
(the pass walks the unfiltered `SystemInstance` and chains can cross
dropped components in subtle ways), so this PR takes Option B from the
task brief: refuse the call with a clear `BAD_INPUT` error pointing
the agent at `spar.verify_move` / `spar.enumerate_moves` for variant-
scoped use, and document the deferral as a v0.10 enhancement.

Also addresses Tier C #50: the variant <-> variant_context mutual-
exclusion was previously enforced only at the `verify_pipeline` /
`enumerate_pipeline` layer (via `MovesError::VariantArgsConflict`).
This PR adds a JSON Schema `not` constraint to all three tools'
`inputSchema` so a strict client rejects the conflicting payload
before sending it, and applies the same guard up-front in
`check_chain::call` so the more specific "mutually exclusive" message
wins over the not-yet-supported one.

Tests:

- `check_chain_rejects_variant_input_with_bad_input` (in-process)
- `check_chain_rejects_both_variant_and_variant_context` (in-process)
- `verify_rejects_both_variant_and_variant_context` (in-process)
- `check_chain_with_variant_returns_bad_input_via_stdio` (stdio)
- `check_chain_rejects_both_variant_args_via_stdio` (stdio)
- `tools_list_advertises_variant_mutex_in_schema` (stdio)

Out of scope: protocolVersion (#21), notifications/initialized (#22),
outputSchema (#23) — separate PRs per the task brief.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@avrabe avrabe merged commit 7702ee3 into main Apr 30, 2026
16 checks passed
@avrabe avrabe deleted the cleanup/mcp-check-chain-variant branch April 30, 2026 04:02
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 30, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

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