Enforce permitted MCP request profiles at the forwarding boundary
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 8.7k
- Forks
- 1.3k
- Avg merge
- 2d 11h
- Merged PRs (30d)
- 253
Description
User Story
As a sandbox operator, I want MCP traffic checked against an explicitly permitted request revision and my method/tool policy, so that valid supported clients work and the request reaching the upstream matches the request OpenShell authorized.
Problem Statement
MCP revisions differ in request grammar, batching, method availability and HTTP metadata. An endpoint allowlist alone does not establish which revision governs a request, whether every operation is authorized, or whether middleware preserves that decision. A version-independent parser can reject valid older traffic or accept a request shape unavailable in the selected revision.
The acceptance scope is correctness around the request OpenShell authorizes and forwards. This replaces the earlier proposal's OpenShell-owned wire registry and negotiated-session/response guarantees with the request contract below. It does not describe those broader guarantees as implemented.
Impact / Why This Matters
Unconditional MCP batch rejection breaks the March 2025 request contract; unconditional acceptance admits forms later revisions prohibit. Operators also need protection against a request being inspected under one interpretation and forwarded under another after a rewrite. Requiring a proxy session registry or extra handshake round trips would add operational burden without establishing a demonstrated requirement for the method/tool authorization boundary.
Proposed Design
An operator configures a nonempty allowlist of supported exact revisions with mcp.versions. The supported vocabulary is 2025-03-26, 2025-06-18, 2025-11-25 and opt-in 2026-07-28. Omission keeps the pinned ["2025-11-25"] default; adding support in a dependency cannot widen an existing policy.
OpenShell selects one endpoint-permitted profile for each request outside valid standalone legacy initialization. That initialization is an explicit bootstrap exception: structural inspection and endpoint method policy still apply, but the client's proposed params.protocolVersion is not treated as an effective or negotiated revision. For subsequent requests, the allowlist never unions profile grammar. The missing-header fallback for legacy requests is March 2025, which must itself be allowed; it is distinct from the policy default. July 2026 requires its sessionless request metadata and matching HTTP mirrors.
Tower owns revision-specific JSON-RPC/MCP structure, method availability, direction, typed parameters and batch grammar. OpenShell owns its supported policy vocabulary, endpoint authorization, request limits, HTTP/body consistency, audit behavior and forwarding. Typed inspection is not a claim of complete JSON-schema validation.
Every policy-relevant operation is checked, including every batch member. In enforce mode a denied member prevents forwarding the complete batch. Malformed or ambiguous protocol input remains rejected in audit mode. Unknown extensions require exact-method permission; deny rules retain precedence. Generic JSON-RPC keeps its separate policy contract.
The final forwarded representation must preserve the authorization decision. Replaced bodies are reinspected and reauthorized; final header-selected grammar and required metadata mirrors are checked before forwarding. Middleware cannot change method, path or query. Future changes that alter policy inputs must reauthorize those inputs. This does not claim arbitrary tool-argument authorization or unconditional OPA reevaluation for every header change.
Client and server retain negotiation and session lifecycle ownership. This scope does not certify initialization results, validate server response contents, correlate callbacks/resumed responses, or prove equality with an earlier negotiated revision. It also does not claim complete MIME/Accept or lifecycle conformance: legacy DELETE cleanup is unsupported. Existing response forwarding, framing and streaming behavior is preserved within the tested boundaries. Any demonstrated interpretation or forwarding bypass of the retained request contract remains a correctness defect that must be fixed before closure.
Acceptance Criteria
- Canonical revision allowlists survive all policy, provider, protobuf/storage, update, projection and activation paths without silent loss or widening.
- Outside valid standalone legacy initialization, each inspected request uses one supported permitted profile; conflicting, duplicate, unknown or disallowed selection signals fail closed. Bootstrap initialization remains structurally inspected and subject to method policy without treating the proposal as the effective or negotiated revision.
- Supported valid requests and explicitly permitted extensions work; unavailable methods, invalid shapes, ambiguous JSON and bounded-request violations are rejected.
- Enforce-mode batch denial is atomic, with valid, malformed and audit controls proving the documented behavior.
- Middleware rewrites preserve the configured method/tool policy behavior. In enforce mode, denied rewrites forward no request bytes; audit may forward otherwise valid policy-denied rewrites. Malformed or profile-invalid rewrites are rejected in both modes, and forwarded rewrites have the expected representation.
- Authenticated proxy evidence covers every supported revision and a multi-version endpoint; relay evidence covers both entry paths, bootstrap, final-request checks and relevant transport rejection cases.
- Clients require no proxy-managed session registration, added handshake round trip, manual session bookkeeping or extra version-selection knob.
- User/architecture documentation states the default, ownership boundary, audit behavior and compatibility limits consistently.
- Required implementation and proof commits are merged, required checks/reviews pass, and the final upstream source matches the acceptance evidence with no unresolved in-scope correctness defect.
Alternatives Considered
- Reject or accept batches for every MCP endpoint: either choice violates one of the supported request profiles.
- Use a
disable_batchswitch or dependency-selected defaults: these create contradictory policy settings or silently changing policy semantics. - Permit only one revision per endpoint: this excludes legitimate clients using different supported revisions at the same endpoint.
- Maintain a second MCP schema registry in OpenShell: this duplicates protocol ownership already provided by Tower.
- Enforce initialization responses and session equality now: this can detect additional server/session inconsistencies, but requires an explicit policy guarantee, correlation/state ownership and resource limits. It is separately scoped unless a concrete failure shows it is necessary for the retained authorization boundary.
Agent Investigation
The implementation sequence includes #3199, #3241, #3251 and the current #3335. The latter delegates inspection to pinned tower-mcp-types 0.22.2 and includes the canonical policy-schema integration and deterministic boundary-digest correction.
Published proof commits 5e9a833ba and d34c38946 cover atomic March batch denial, actual middleware tool rewrites and per-profile sandbox execution. The final sandbox harness executed both tests successfully. The supported November conformance scenarios passed separately; neither result establishes complete conformance across all revisions. Final-head hosted Branch Checks passed; local workspace/server verification and required human review remain in progress. Issue closure requires merged acceptance evidence, not merely this implementation status.
Checklist
- I've reviewed existing issues and the architecture docs
- This is a design proposal, not a "please build this" request
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading the acceptance criteria and the implementation sequence in PRs #3199, #3241, #3251, and #3335, then review the referenced proof commits. Done means the permitted MCP profiles, batch and rewrite checks, policy/storage paths, documentation, merged proof, required checks, and human review all satisfy the listed evidence requirements without unresolved correctness defects.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- api, backend-api-design, security
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100