Repository navigation
Keep the signed file-action subset through MCP read-only authorization (#1103) - #1128
Merged
Merged
Conversation
#1103) Read-only MCP OAuth (scope mdbase:read) signs only the file actions paired with its requested operations (list, read). Connect compared that proof with the application's full declared file set, which includes the optional write actions, so it required durable_mutation and refused the proof before an authorization request existed. MCP surfaced that as HTTP 500. Connect now accepts a signed request that selects declared optional file actions (every required action, declared order, declared scope) and keeps exactly that signed subset as the ceiling for the consent preview, every approval path and the provider grant. Approval can no longer add file actions the application did not sign. Periodic application reconciliation now accepts an approved optional subset instead of retiring every grant whose file capability is not the full declaration. Adds a real-server regression test that runs the MCP gateway's read-only OAuth flow against the real Connect control plane through consent, approval, token exchange and reconciliation.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1103.
Root cause
With
scope=mdbase:read, MCP signs only the file actions that go with its requested operations:listandread, and nodurable_mutation. In/oauth/authorization_request, Connect compared that proof with the application's full declared file set (fileRequestForRequirements), which includes the optionaladd,replace,moveanddelete. That full set requiresdurable_mutation, so Connect refused the proof withdurable_mutation_unsupportedbefore any authorization request existed. MCP reported the refusal as HTTP 500 (mcp_upstream_redirect_failed).Two more problems sat behind that one in the same path:
file_actions(the portal default, and how staging extended acceptance approves) would therefore select all six actions. The hosted provider admits a replica only when its file actions are within the signedrequested_files(verified_setup_evidence), so activation would have failed even after the proof was accepted.reconcileApplicationGrants, which runs on every application about every 6 hours, required a grant'sfile_capabilityto deep-equal the full declaration. Any grant with an approved optional subset, including every read-only MCP grant, was retired asapplication_manifest_change.Change
signedFileRequestForRequirements(application-requirements.ts): both authorization request routes now expect the declared-order selection that the application signed. It must include every required action, use only declared actions and keep the declared scope. Semantic v1 still requires the exact declaration. The existing exact proof comparison does the enforcement, and nothing is widened.planCollectionGrant/previewCollectionGranttakeapplicationFileCeiling, which is the signedrequested_files.actions, the file counterpart ofapplicationOperationCeiling. It is passed from the consent preview and from the hosted, native, local portal/connector and activation re-plan paths. The default selection is the signed subset, and an approval that asks for an action outside it is refused.fileCapabilityPermittedByRequirements: same scope, every required action, only declared actions. It replaces the exact-match check.Tests
services/mcp/src/readonly-authorization.real-server.test.tsruns the real MCP gateway against the real Connect control plane (buildApp, memory DB). Only the Rust hosted provider is a double, and it enforces the provider's file-subset admission rule. The test covers MCP/oauth/authorizewithmdbase:read, the claim, the consent preview (offers onlylist/read), an acceptance-style approval with nofile_actions, the read-only replica enrolment, the status and callback, the MCP token exchange, and application reconciliation keeping the grant.mainwith exactly the staging symptom (MCP 500 at/oauth/authorize).grant-planner.test.ts: signed-selection canonicalisation, the consent/approval ceiling and the reconciliation predicate.rpnpm) runs: server and MCP typecheck pass. Server unit suite: 171 files pass, 76 Postgres files skipped. MCP suite: 56/56. Architecture and changelog checks pass.mainon the same machine, mostly from 15 s timeouts, or passes when rerun.grant-narrowing"revocation holding the grant lock cannot be undone by PATCH" fails on both.architecture.d/mcp-readonly-oauth-1103.jsonjustifies +1 export.fileCapabilityForRequirementsbecame internal, and an unused import was removed.Compatibility
This PR does not change the protocol or MCP. Clients that sign the full declaration, which covers every currently working client, are unaffected. It merges cleanly with #1118, and its preview hunk carries over to #1118's renamed native collections block.