Hardening follow-ups split out of the vMCP authz-gate PR (#5841, fixes #5827) so they don't hold up the 403 fix. All three are reviewer notes that don't affect current correctness.
1. Unify the gate's parse source with the enforced parse (argument-conditional policies)
pkg/vmcp/server/call_gate.go authorizes tools/call on parsed.Arguments (decoded by pkg/mcp's parser), while dispatch re-authorizes on the SDK's decode of the same request bytes. Both are encoding/json over identical bytes, so they agree for plain JSON today. If the decoders ever diverge (json.Number vs float64, a future typed-params path, duplicate-key handling), an argument-conditional Cedar policy could have the gate ALLOW while the call path DENIES — re-introducing the exact 200 + IsError this PR removed (or a spurious 403). Make the gated decision and the enforced decision derive from a single parse (e.g. carry the gate's decision forward, or share one decoded argument map). Raised by @rdimitrov.
2. Fail-loud collision check for the execute_tool_script reserved name
pkg/vmcp/codemode/decorator.go's CheckToolCall admits execute_tool_script unconditionally by name. If a backend ever advertised a real tool literally named execute_tool_script, it would be shadowed at ListTools/LookupTool and its Cedar policy would never run. The name is effectively reserved for the codemode virtual tool, but nothing enforces that. Add a fail-loud collision check at aggregation/ListTools time so a real backend tool with that name is a loud error, not a silent shadow. Raised by @jhrozek.
3. Verb parity between the vMCP gate and thv run's authz middleware
thv run's authz middleware denies elicitation/create, sampling/createMessage, and tasks/* by default (pkg/authz/middleware.go). The vMCP pre-dispatch gate admits them, because the core admission seam has no Cedar entity for these method kinds yet (its default is deliberately fail-open — see the gate's default case). When vMCP grows an admission decision for these verbs, add the matching gate cases so the two paths share one posture. Raised by @jhrozek.
Context: split out of #5841 (fixes #5827).
Hardening follow-ups split out of the vMCP authz-gate PR (#5841, fixes #5827) so they don't hold up the 403 fix. All three are reviewer notes that don't affect current correctness.
1. Unify the gate's parse source with the enforced parse (argument-conditional policies)
pkg/vmcp/server/call_gate.goauthorizestools/callonparsed.Arguments(decoded bypkg/mcp's parser), while dispatch re-authorizes on the SDK's decode of the same request bytes. Both areencoding/jsonover identical bytes, so they agree for plain JSON today. If the decoders ever diverge (json.Numbervsfloat64, a future typed-params path, duplicate-key handling), an argument-conditional Cedar policy could have the gate ALLOW while the call path DENIES — re-introducing the exact200 + IsErrorthis PR removed (or a spurious 403). Make the gated decision and the enforced decision derive from a single parse (e.g. carry the gate's decision forward, or share one decoded argument map). Raised by @rdimitrov.2. Fail-loud collision check for the
execute_tool_scriptreserved namepkg/vmcp/codemode/decorator.go'sCheckToolCalladmitsexecute_tool_scriptunconditionally by name. If a backend ever advertised a real tool literally namedexecute_tool_script, it would be shadowed at ListTools/LookupTool and its Cedar policy would never run. The name is effectively reserved for the codemode virtual tool, but nothing enforces that. Add a fail-loud collision check at aggregation/ListTools time so a real backend tool with that name is a loud error, not a silent shadow. Raised by @jhrozek.3. Verb parity between the vMCP gate and
thv run's authz middlewarethv run's authz middleware denieselicitation/create,sampling/createMessage, andtasks/*by default (pkg/authz/middleware.go). The vMCP pre-dispatch gate admits them, because the core admission seam has no Cedar entity for these method kinds yet (its default is deliberately fail-open — see the gate'sdefaultcase). When vMCP grows an admission decision for these verbs, add the matching gate cases so the two paths share one posture. Raised by @jhrozek.Context: split out of #5841 (fixes #5827).