feat(policy): add MCP-aware JSON-RPC L7 governance#1938
Conversation
Signed-off-by: ddurst <267424412+ddurst-nvidia@users.noreply.github.com>
Signed-off-by: ddurst <267424412+ddurst-nvidia@users.noreply.github.com>
Signed-off-by: ddurst <267424412+ddurst-nvidia@users.noreply.github.com>
Signed-off-by: ddurst <267424412+ddurst-nvidia@users.noreply.github.com>
Signed-off-by: ddurst <267424412+ddurst-nvidia@users.noreply.github.com>
Signed-off-by: ddurst <267424412+ddurst-nvidia@users.noreply.github.com>
|
@krishicks - Mostly for your review. |
Signed-off-by: ddurst <267424412+ddurst-nvidia@users.noreply.github.com>
a432508 to
8c9606b
Compare
|
I don't have permissions to add the |
| max_body_bytes: 131072 | ||
| rules: | ||
| deny: | ||
| - mcp_method: tools/call |
There was a problem hiding this comment.
protocol is already mcp, so why do we need it to be mcp_method here vs just method?
There was a problem hiding this comment.
Good catch. Easy enough to remove, leaving unresolved until I can.
There was a problem hiding this comment.
Resolved. You'll need to talk to @krishicks regarding rpc_method from #1865, I'm only building on top of that work.
| mcp: | ||
| max_body_bytes: 131072 | ||
| rules: | ||
| deny: |
There was a problem hiding this comment.
this would be a deviation from a separate deny_rules block that sits at the same level as rules, is there a particular reason for that?
There was a problem hiding this comment.
Particular reasons are:
- Support method-level governance for MCP tool calls (JSON-RPC) in sandbox policy #1793 's proposed design section.
- feat(l7): add JSON-RPC policy enforcement #1865 (comment)
But I'll address it more completely in the other comment.
Signed-off-by: ddurst <267424412+ddurst-nvidia@users.noreply.github.com>
Signed-off-by: ddurst <267424412+ddurst-nvidia@users.noreply.github.com>
Signed-off-by: ddurst <267424412+ddurst-nvidia@users.noreply.github.com>
|
Cleaned up the conformance fixture workarounds to align with modelcontextprotocol/conformance#346. |
Summary
Builds on #1865 by keeping its shared JSON-RPC-family L7 enforcement foundation and adding an MCP-specific policy surface on top of it.
PR #1865 introduces generic JSON-RPC method/params enforcement. This variant preserves generic JSON-RPC as a first-class supported protocol for non-MCP services, while adding MCP-specific policy syntax, MCP validation via
tower-mcp-types, and conformance-oriented e2e behavior.Related Issue
Addresses #1793.
Builds on #1865.
Changes
Relationship to #1865
PR #1865 adds the shared JSON-RPC-family L7 enforcement layer. This branch builds on that layer with an MCP-specific policy surface and MCP message validation via
tower-mcp-types, while preserving generic JSON-RPC as a first-class supported protocol for non-MCP services.How This Addresses #1793
Issue #1793 asks for method-level governance for MCP tool calls because MCP traffic is carried as JSON-RPC over a single allowed connection, making network-only policy all-or-nothing.
This branch addresses that by allowing policy to distinguish MCP requests inside the connection:
methodfilters MCP method names such asinitialize,tools/list, andtools/callwhenprotocol: mcp.toolfilters MCP tool calls byparams.name.paramsmatchers allow policy to constrain scalar MCP params such asarguments.scope.deny_rulescan block specific MCP calls even when broader allow rules match.Key Differences
protocol: mcpwhile preservingprotocol: json-rpc.mcp.max_body_bytesmethodtoolparamsmaps for MCP readability.rulesplus siblingdeny_rulespolicy shape for MCP, aligned with the other L7 protocols.tower-mcp-typesto validate known MCP request/notification shapes.Rule / OPA Tightening
methodnormalizes to the existing internal method-matching field, so OPA has one JSON-RPC-family method path.toolnormalizes to MCPparams.name, but only whenparams.namewas not explicitly set.protocol: json-rpcorprotocol: mcp.2025-11-25conformance compatibility path after host, path, binary, endpoint protocol, and parse-shape checks. MCP2026-07-28(draft) removes the GET stream endpoint and client-sent JSON-RPC responses, so this allowance should be version-gated or removed when OpenShell enforces that transport version.Type Enforcement Follow-up
This branch does not add typed MCP argument enforcement. The current L7 JSON-RPC-family policy input flattens scalar params into string values before OPA evaluation, so
1and"1"ortrueand"true"are not distinguishable by policy today.Typed argument enforcement would be better handled as a wider L7 policy input change, for example by preserving scalar JSON type metadata alongside the existing flattened matcher keys. That would let a future MCP argument policy express
type: integerortype: booleanwithout weakening the existing JSON-RPC compatibility path.Conformance Notes
The MCP conformance policy template now uses:
Expected-failure carve-outs are empty:
Note
The wrapper still carries a temporary workaround for modelcontextprotocol/conformance#345 while pinned to an upstream ref where the bundled TypeScript client fixtures drift from the runner scenarios. The wrapper patches the local checkout before building the client image so
elicitation-sep1034-client-defaultsandsse-retryexercise the intended MCP paths through OpenShell. Remove this workaround whenOPENSHELL_MCP_CONFORMANCE_REFpoints at an upstream release containing that fix.Testing
mise run pre-commitpassesLocal validation run for this branch includes:
mise run pre-commitcargo test -p openshell-policycargo test -p openshell-supervisor-network mcp_tool_deny_rule_blocks_tools_callcargo test -p openshell-supervisor-network l7_mcpOPENSHELL_DOCKER_SUPERVISOR_BIN=deploy/docker/.build/prebuilt-binaries/amd64/openshell-sandbox mise run e2e:mcpinitialize,tools_call,elicitation-sep1034-client-defaults,sse-retryChecklist