Goal
Apply the exact member-name check experimental_test_graphs already uses to the other MCP tools that decode an arguments object.
Why
Every tool advertises additionalProperties: false, which means the exact spelling. encoding/json matches struct field names case-insensitively, so DisallowUnknownFields alone is not enough: a differently-cased member binds to the real field and the call succeeds.
internal/mcp/tools.go has an exactMembers helper for this, added with experimental_test_graphs, but it is used by that tool only. The others decode with DisallowUnknownFields alone.
Demonstrated against a built server, same argument shape, two different answers:
get_pack {"PACK_ID":"intake"} -> isError=false, returns the pack
experimental_test_graphs {"GRAPH_ID":"x"} -> isError=true, "unknown member"
The advertised schema is identical in kind for both. One of them enforces it.
Scope
- Change
internal/mcp/tools.go and internal/mcp/server_test.go only.
- Call
exactMembers before decoding in each tool that takes an arguments object and does not already: get_pack, experimental_evaluate, and experimental_test_packs.
- Keep each tool's existing error text shape;
exactMembers already produces a message naming the offending member and the accepted ones.
- Do not change any tool's advertised schema, argument semantics, or success behaviour.
Acceptance criteria
Contributor learning
MCP argument contracts, why a schema keyword needs an enforcing check behind it, and one concrete way Go's JSON decoding is more permissive than a schema.
Goal
Apply the exact member-name check
experimental_test_graphsalready uses to the other MCP tools that decode an arguments object.Why
Every tool advertises
additionalProperties: false, which means the exact spelling.encoding/jsonmatches struct field names case-insensitively, soDisallowUnknownFieldsalone is not enough: a differently-cased member binds to the real field and the call succeeds.internal/mcp/tools.gohas anexactMembershelper for this, added withexperimental_test_graphs, but it is used by that tool only. The others decode withDisallowUnknownFieldsalone.Demonstrated against a built server, same argument shape, two different answers:
The advertised schema is identical in kind for both. One of them enforces it.
Scope
internal/mcp/tools.goandinternal/mcp/server_test.goonly.exactMembersbefore decoding in each tool that takes an arguments object and does not already:get_pack,experimental_evaluate, andexperimental_test_packs.exactMembersalready produces a message naming the offending member and the accepted ones.Acceptance criteria
PACK_ID,Pack_Id,packIdwhere the schema sayspack_id) is refused.t.Runper case.env GO111MODULE=on go test ./internal/mcpandgo test ./...pass.git commit -s.Contributor learning
MCP argument contracts, why a schema keyword needs an enforcing check behind it, and one concrete way Go's JSON decoding is more permissive than a schema.