Skip to content

Commit 470f882

Browse files
feat(policy): add JSON-RPC and MCP L7 policies
Add policy schema, proto, provider profile, OPA, and L7 proxy support for `protocol: json-rpc` and `protocol: mcp`. Generic JSON-RPC endpoints match exact method names only, with `method: "*"` as the all-method sentinel; wildcard/glob methods and params matchers are rejected. Parse JSON-RPC request bodies and batches in the forward proxy, deny response-shaped client frames, limit receive-stream GET allowance to MCP endpoints, and redact params in decision logs. Preserve L7 rule params on the proto load path so MCP `tools/call` tool filters behave like YAML-loaded policies. Add MCP conformance coverage, JSON-RPC L7 e2e coverage, and docs for the new protocols and current matcher limitations. Co-authored-by: ddurst <267424412+ddurst-nvidia@users.noreply.github.com> Signed-off-by: Kris Hicks <khicks@nvidia.com>
1 parent 7e0cce4 commit 470f882

40 files changed

Lines changed: 7842 additions & 579 deletions

.github/workflows/e2e-test.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ jobs:
4444
cmd: "mise run --no-deps --skip-deps e2e:podman:rootless"
4545
apt_packages: "openssh-client podman uidmap"
4646
rootless: true
47+
- suite: mcp
48+
cmd: "mise run --no-deps --skip-deps e2e:mcp"
49+
apt_packages: ""
4750
container:
4851
image: ghcr.io/nvidia/openshell/ci:latest
4952
credentials:
@@ -65,6 +68,17 @@ jobs:
6568
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
6669
with:
6770
ref: ${{ inputs['checkout-ref'] || github.sha }}
71+
persist-credentials: false
72+
73+
- name: Check out MCP conformance tests
74+
if: matrix.suite == 'mcp'
75+
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
76+
with:
77+
repository: modelcontextprotocol/conformance
78+
# Pin after v0.1.16 to include the tools_call client scenario fix.
79+
ref: b9041ea41b0188581803459dbae71bc7e02fd995
80+
path: .cache/mcp-conformance
81+
persist-credentials: false
6882

6983
- name: Install OS test dependencies
7084
if: matrix.apt_packages != ''
@@ -104,6 +118,7 @@ jobs:
104118
- name: Run tests
105119
env:
106120
OPENSHELL_SUPERVISOR_IMAGE: ${{ format('ghcr.io/nvidia/openshell/supervisor:{0}', inputs.image-tag) }}
121+
OPENSHELL_MCP_CONFORMANCE_CLIENT_IMAGE: ${{ format('openshell-mcp-conformance-client:{0}', inputs.image-tag) }}
107122
E2E_CMD: ${{ matrix.cmd }}
108123
run: |
109124
if [ "${{ matrix.rootless }}" = "true" ]; then

Cargo.lock

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ members = ["crates/*"]
88
[workspace.package]
99
version = "0.0.0"
1010
edition = "2024"
11-
rust-version = "1.88"
11+
rust-version = "1.90"
1212
license = "Apache-2.0"
1313
repository = "https://github.com/NVIDIA/OpenShell"
1414

@@ -73,6 +73,7 @@ serde_json = "1"
7373
serde_yml = "0.0.12"
7474
toml = "0.8"
7575
apollo-parser = "0.8.5"
76+
tower-mcp-types = "0.12.0"
7677

7778
# HTTP client
7879
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls-native-roots"] }

architecture/sandbox.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,17 @@ paths, such as proxy support files or GPU device paths when a GPU is present.
4949
All ordinary agent egress is routed through the sandbox proxy. The proxy
5050
identifies the calling binary, checks trust-on-first-use binary identity, rejects
5151
unsafe internal destinations, and evaluates the active policy.
52+
For inspected HTTP traffic, the proxy can enforce REST method/path rules,
53+
WebSocket upgrade and text-message rules, GraphQL operation rules, and
54+
MCP method, tool, and supported params rules or generic JSON-RPC method rules
55+
on sandbox-to-server request bodies. MCP and JSON-RPC inspection buffers up to
56+
the endpoint `mcp.max_body_bytes` or `json_rpc.max_body_bytes` limit. MCP
57+
`tools/call` tool names are checked against the spec-recommended syntax by
58+
default before policy evaluation, with a per-endpoint `mcp.strict_tool_names`
59+
compatibility opt-out. Generic JSON-RPC policies do not support `params`
60+
matchers; generic JSON-RPC rules match only the method.
61+
JSON-RPC responses and server-to-client MCP messages on response or SSE streams
62+
are relayed but are not currently parsed for policy enforcement.
5263

5364
`https://inference.local` is special. It bypasses OPA network policy and is
5465
handled by the inference interception path:

crates/openshell-cli/src/policy_update.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ fn group_allow_rules(specs: &[String]) -> Result<BTreeMap<(String, u32), Vec<L7R
205205
operation_type: String::new(),
206206
operation_name: String::new(),
207207
fields: Vec::new(),
208+
params: HashMap::default(),
208209
}),
209210
});
210211
}
@@ -226,6 +227,7 @@ fn group_deny_rules(specs: &[String]) -> Result<BTreeMap<(String, u32), Vec<L7De
226227
operation_type: String::new(),
227228
operation_name: String::new(),
228229
fields: Vec::new(),
230+
params: HashMap::default(),
229231
});
230232
}
231233
Ok(grouped)

0 commit comments

Comments
 (0)