docs(32.1-01): complete MCP scope list and call context plan
- Record the plan summary - Mark 32.1-01 executed on the roadmap
This commit is contained in:
@@ -1621,7 +1621,7 @@ Plans:
|
||||
**Goal:** As an application whose plugins declare their own member token scopes, I want an agent connected to `/mcp` over OAuth to get the same tools, attribution and revocation as one using a personal token, so that OAuth sign-in can be switched on in production. A consuming app's agent-access work found three gaps (its D-36, D-37 and D-38); all three are additive and ship as patch releases of the framework (0.5.x) and `sm-oauthserver-plugin`.
|
||||
**Requirements**: TBD
|
||||
**Depends on:** Phase 19.4 (OIDC provider on wristband), Phase 19.7 (generic MCP module)
|
||||
**Plans:** 5 plans
|
||||
**Plans:** 1/5 plans executed
|
||||
|
||||
**Success Criteria** (what must be TRUE):
|
||||
1. Scopes a plugin declares through `pact.HasFrontendTokenScopes` can be requested, consented to and granted over MCP OAuth: the wristband metadata and authorize allow-list and pier's protected-resource scopes include them, so a tool route behind `user.scope:<declared>` lists and runs for an OAuth agent granted that scope. Today the list is fixed to `read`, `write`, `ai`, `offline_access` (and `backend`).
|
||||
@@ -1631,7 +1631,7 @@ Plans:
|
||||
|
||||
Plans:
|
||||
**Wave 1**
|
||||
- [ ] 32.1-01-PLAN.md — Framework seam: catalog, MCPCall, one advertised scope list
|
||||
- [x] 32.1-01-PLAN.md — Framework seam: catalog, MCPCall, one advertised scope list
|
||||
|
||||
**Wave 2** *(blocked on Wave 1 completion)*
|
||||
- [ ] 32.1-02-PLAN.md — sm-user-plugin from v0.1.14: publish the catalog and Implies for OAuth subjects
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
---
|
||||
phase: 32.1-mcp-oauth-for-plugin-apps-declared-scopes-mcp-subject-on-con
|
||||
plan: 01
|
||||
subsystem: auth
|
||||
tags: [mcp, oauth, pact, pier, wristband, scopes]
|
||||
|
||||
requires:
|
||||
- phase: 19.7-generic-mcp-module
|
||||
provides: pier in-process dispatch, PRMScopes and the read/write ceiling
|
||||
- phase: 19.4-oidc-provider-on-wristband
|
||||
provides: OIDC discovery scope list left unchanged
|
||||
provides:
|
||||
- "pact.MemberTokenScopeCatalog, pact.MCPAdvertisedScopes, pact.MCPCall, pact.WithMCPCall, pact.MCPCallFrom"
|
||||
- "pact.FrontendTokenScope.Implies"
|
||||
- "wristband resolvedScopes shared by MCP metadata and authorize"
|
||||
- "wristband.PendingRequestView.OfflineAccess"
|
||||
- "pier PRM copies MCPScopesSupported when non-empty, else PRMScopes, and sets MCPCall beside WithDispatch"
|
||||
affects: [32.1-02, 32.1-03, 32.1-04, 32.1-05]
|
||||
|
||||
actuals:
|
||||
tokens: 17695
|
||||
tasks: 3
|
||||
commits: 3
|
||||
plan_head_before: 2cea3127cc2a2da5f8e0ea6ec47356bfaecc5c67
|
||||
plan_head_after: 876f0f09f2c4a42864941118d12d636123af1594
|
||||
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns:
|
||||
- "MCPAdvertisedScopes is a second interface; MCPTokenVerifier stays MCPVerifyToken and MCPResource"
|
||||
- "One resolvedScopes slice drives MCP metadata and the authorize allow-list"
|
||||
- "WithMCPCall is set on the dispatch context immediately before WithDispatch; a nil context is not replaced"
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- modules/pact/mcp_call_test.go
|
||||
- modules/pier/mcp_seam_test.go
|
||||
modified:
|
||||
- modules/pact/capabilities.go
|
||||
- modules/pact/mcp.go
|
||||
- modules/pact/README.md
|
||||
- modules/pier/dispatch.go
|
||||
- modules/pier/prm.go
|
||||
- modules/pier/session.go
|
||||
- modules/pier/stateless.go
|
||||
- modules/pier/README.md
|
||||
- modules/wristband/authorize.go
|
||||
- modules/wristband/server.go
|
||||
- modules/wristband/consent.go
|
||||
- modules/wristband/mcp_scopes_smoke_test.go
|
||||
- modules/wristband/README.md
|
||||
- docs/services/mcp.md
|
||||
- docs/services/oauth-server.md
|
||||
- docs/services/authentication.md
|
||||
|
||||
key-decisions:
|
||||
- "Empty MCP Options.ScopesSupported resolves to read, write, ai, offline_access, backend; a non-empty list, including DefaultOptions, is used as given"
|
||||
- "Advertised scopes are pact.MCPAdvertisedScopes, not a new method on pact.MCPTokenVerifier"
|
||||
- "Stateless tool calls set callInfo.stateless so MCPCall.SessionID stays empty"
|
||||
|
||||
patterns-established:
|
||||
- "Pattern: Metadata and allowedAuthorizeScopes both call Server.resolvedScopes"
|
||||
- "Pattern: pier mountPRM type-asserts MCPAdvertisedScopes and clones a non-empty MCPScopesSupported, else PRMScopes"
|
||||
|
||||
requirements-completed: []
|
||||
|
||||
coverage:
|
||||
- id: D1
|
||||
description: "An empty MCP Options.ScopesSupported advertises and accepts the five fixed names including backend; DefaultOptions still refuses backend; OIDC discovery stays openid, profile, email"
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: modules/wristband/mcp_scopes_smoke_test.go#TestMCPScopesSmoke
|
||||
status: pass
|
||||
- kind: unit
|
||||
ref: modules/wristband/oidc_discovery_test.go#TestDiscoveryFields
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D2
|
||||
description: "pier copies MCPScopesSupported onto protected-resource metadata when non-empty and otherwise keeps PRMScopes, and one frontend tools/call observes MCPCall beside the bouncer dispatch marker"
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: modules/pier/mcp_seam_test.go#TestMCPSeam
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D3
|
||||
description: "WithMCPCall then MCPCallFrom round-trips Subject.Guard, SessionID and Tool; a context without a call answers ok false"
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: modules/pact/mcp_call_test.go#TestMCPCallRoundTrip
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D4
|
||||
description: "PendingRequestView.OfflineAccess is set from the pending row; IssueCode's signature is unchanged"
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: go test -short ./modules/wristband
|
||||
status: pass
|
||||
human_judgment: true
|
||||
rationale: "No test asserts that PendingRequest copies OfflineAccess; the package suite only proves the field compiles and existing consent tests still pass"
|
||||
- id: D5
|
||||
description: "pact, pier and wristband READMEs and the three service docs name the new identifiers and the mcp_scopes rule without naming a consuming application"
|
||||
verification:
|
||||
- kind: other
|
||||
ref: go test ./cmd/summer -run TestDocsTree && go run ./cmd/summer docs:build --check
|
||||
status: pass
|
||||
human_judgment: false
|
||||
|
||||
duration: 12min
|
||||
completed: 2026-10-28
|
||||
status: complete
|
||||
---
|
||||
|
||||
# Phase 32.1 Plan 01: MCP scope list and call context Summary
|
||||
|
||||
**One resolved MCP scope list drives wristband metadata and authorize, pier copies it from the verifier, and every in-process tool call carries pact.MCPCall**
|
||||
|
||||
## Performance
|
||||
|
||||
- **Duration:** 12 min
|
||||
- **Started:** 2026-10-28T13:25:44Z
|
||||
- **Completed:** 2026-10-28T13:37:30Z
|
||||
- **Tasks:** 3
|
||||
- **Files modified:** 18
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- `pact` publishes `MemberTokenScopeCatalog`, `MCPAdvertisedScopes`, `MCPCall` (`WithMCPCall` / `MCPCallFrom`) and `FrontendTokenScope.Implies`. `MCPTokenVerifier` is still only `MCPVerifyToken` and `MCPResource`.
|
||||
- An empty or nil MCP `Options.ScopesSupported` resolves to `read`, `write`, `ai`, `offline_access`, `backend` for both metadata and the authorize allow-list. `DefaultOptions` stays the four-name list and still refuses `backend`. OIDC discovery stays `openid`, `profile`, `email`.
|
||||
- pier copies `MCPScopesSupported` into protected-resource metadata when that slice is non-empty, otherwise `PRMScopes`. `dispatcher.run` stores `MCPCall` immediately before `bouncer.WithDispatch`. Stateless calls leave `SessionID` empty.
|
||||
- `PendingRequestView.OfflineAccess` is the pending row's flag. `IssueCode`'s signature is unchanged.
|
||||
|
||||
## Task Commits
|
||||
|
||||
Each task was committed atomically:
|
||||
|
||||
1. **Task 1: Tracer — one advertised scope list and one MCP call on the dispatch context** - `63d05e8` (feat)
|
||||
2. **Task 2: Expose offline_access on the pending consent view** - `9e29d6f` (feat)
|
||||
3. **Task 3: Document the seam in READMEs and docs** - `876f0f0` (docs)
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
- `modules/pact/mcp.go` - catalog and advertised-scope interfaces, `MCPCall`, `WithMCPCall`, `MCPCallFrom`
|
||||
- `modules/pact/capabilities.go` - `FrontendTokenScope.Implies`
|
||||
- `modules/pact/mcp_call_test.go` - round-trip and absent-context cases
|
||||
- `modules/wristband/server.go` - unexported `resolvedScopes` used by metadata
|
||||
- `modules/wristband/authorize.go` - allow-list uses that same slice
|
||||
- `modules/wristband/consent.go` - `PendingRequestView.OfflineAccess`
|
||||
- `modules/wristband/mcp_scopes_smoke_test.go` - empty list expects the five names and accepts backend
|
||||
- `modules/pier/prm.go` - copy `MCPScopesSupported` or keep `PRMScopes`
|
||||
- `modules/pier/dispatch.go` - `WithMCPCall` immediately before `WithDispatch`; blank the ephemeral audit word
|
||||
- `modules/pier/session.go` - copy the SDK session id only when the call is not stateless
|
||||
- `modules/pier/stateless.go` - mark `callInfo.stateless` before the handler runs
|
||||
- `modules/pier/mcp_seam_test.go` - metadata, PRM and one tool call that observes `MCPCall`
|
||||
- `modules/pact/README.md`, `modules/pier/README.md`, `modules/wristband/README.md` - the new identifiers and the advertised-set rule
|
||||
- `docs/services/mcp.md`, `docs/services/oauth-server.md`, `docs/services/authentication.md` - the same rule, `MCPCallFrom`, and the unchanged ceiling and OIDC list
|
||||
|
||||
## Decisions Made
|
||||
|
||||
None beyond the plan's recorded discretion. `MemberTokenScopeCatalog.MemberTokenScopes` is not named `FrontendTokenScopes`. `MCPAdvertisedScopes` is a separate interface. Empty MCP options resolve to the five fixed names; a non-empty list is cloned as given. `filter.go` and `oidc_options.go` were not edited.
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
None - plan executed exactly as written.
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
None
|
||||
|
||||
## User Setup Required
|
||||
|
||||
None - no external service configuration required.
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
The framework seam for SC-1 and SC-2 is in place. Later plans in this phase can publish the member-token catalog, build the advertised list from `golem15.oauthserver.mcp_scopes`, and read `MCPCallFrom` on a dispatched route. The read/write ceiling and the OIDC discovery list are unchanged.
|
||||
|
||||
## Self-Check: PASSED
|
||||
|
||||
- FOUND: modules/pact/mcp.go
|
||||
- FOUND: modules/pact/mcp_call_test.go
|
||||
- FOUND: modules/pier/mcp_seam_test.go
|
||||
- FOUND: modules/wristband/consent.go
|
||||
- FOUND: modules/pact/README.md
|
||||
- FOUND: docs/services/mcp.md
|
||||
- FOUND: 63d05e8
|
||||
- FOUND: 9e29d6f
|
||||
- FOUND: 876f0f0
|
||||
|
||||
---
|
||||
*Phase: 32.1-mcp-oauth-for-plugin-apps-declared-scopes-mcp-subject-on-con*
|
||||
*Completed: 2026-10-28*
|
||||
Reference in New Issue
Block a user