diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index f5f31e4a..2d1d889f 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -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:** 4/5 plans executed +**Plans:** 5/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:` lists and runs for an OAuth agent granted that scope. Today the list is fixed to `read`, `write`, `ai`, `offline_access` (and `backend`). @@ -1643,7 +1643,7 @@ Plans: - [x] 32.1-04-PLAN.md — vue-oauth-layer /connect checkboxes **Wave 5** *(blocked on Wave 4 completion)* -- [ ] 32.1-05-PLAN.md — Unit tests, then the release checkpoint +- [x] 32.1-05-PLAN.md — Unit tests, then the release checkpoint ### Phase 33: SEO output, audit and content diff --git a/.planning/phases/32.1-mcp-oauth-for-plugin-apps-declared-scopes-mcp-subject-on-con/32.1-05-SUMMARY.md b/.planning/phases/32.1-mcp-oauth-for-plugin-apps-declared-scopes-mcp-subject-on-con/32.1-05-SUMMARY.md new file mode 100644 index 00000000..500f0412 --- /dev/null +++ b/.planning/phases/32.1-mcp-oauth-for-plugin-apps-declared-scopes-mcp-subject-on-con/32.1-05-SUMMARY.md @@ -0,0 +1,206 @@ +--- +phase: 32.1-mcp-oauth-for-plugin-apps-declared-scopes-mcp-subject-on-con +plan: 05 +subsystem: auth +tags: [mcp, oauth, release, changelog, scopes] + +requires: + - phase: 32.1-01 + provides: MCP scope list and MCPCall on the tool-call context + - phase: 32.1-02 + provides: OAuth implied scopes in sm-user-plugin + - phase: 32.1-03 + provides: MCP consent replace, kind, and backfill + - phase: 32.1-04 + provides: vue-oauth-layer /connect +provides: + - "Unit tests for a bare MCP call, both guards, PRM scopes, implied scopes, consent replace, kind, and backfill" + - "SummerCMS v0.5.2, sm-user-plugin v0.1.16, sm-oauthserver-plugin v0.1.4, and local vue-oauth-layer v0.1.0" + - "Changelog post https://summercms.io/changelog/summercms-0-5-2" +affects: [33] + +actuals: + tokens: 6474 + tasks: 4 + commits: 1 +plan_head_before: 06a144e19f1bca9b918af7adfc44e7b7a1e3f011 +plan_head_after: 9094bf9ad99c92b4343d8d3f1062ef4863f58e00 + +tech-stack: + added: [] + patterns: + - "Patch tags are computed from git tag --list at release time, recorded in 32.1-05-release-decision.txt before any ref changes" + - "sm-oauthserver-plugin bumps require versions after those tags are pushed and keeps the summercms replace" + +key-files: + created: + - .planning/phases/32.1-mcp-oauth-for-plugin-apps-declared-scopes-mcp-subject-on-con/32.1-05-release-decision.txt + - .planning/phases/32.1-mcp-oauth-for-plugin-apps-declared-scopes-mcp-subject-on-con/32.1-05-changelog-notes.md + - modules/pact/mcp_call_negative_test.go + - modules/pier/dispatch_mcpcall_test.go + - modules/pier/prm_scopes_test.go + - ../sm-user-plugin/mcp_implies_test.go + - ../sm-oauthserver-plugin/mcp_consent_test.go + - ../sm-oauthserver-plugin/authorizations_kind_test.go + - ../sm-oauthserver-plugin/mcp_backfill_test.go + modified: + - ../sm-oauthserver-plugin/go.mod + - ../sm-oauthserver-plugin/go.sum + - .planning/ROADMAP.md + +key-decisions: + - "tag-and-publish. Framework v0.5.2 on master, sm-user-plugin v0.1.16 on phase-32.1-mcp-implied-scopes, sm-oauthserver-plugin v0.1.4 on master after the require bump, vue-oauth-layer v0.1.0 local only." + - "sm-user-plugin v0.1.16 stays on phase-32.1-mcp-implied-scopes and is not retargeted onto v0.1.15. That tag is not an ancestor." + - "vue-oauth-layer has no origin. The tag was not pushed and no remote was created." + +patterns-established: + - "Release decision file is written from for-each-ref before the first tag or push and is not rewritten." + +requirements-completed: [] + +coverage: + - id: D1 + description: "A bare context has no MCP call, both guards record MCPCall, and PRM uses advertised scopes or falls back." + verification: + - kind: unit + ref: modules/pact/mcp_call_negative_test.go#TestMCPCallFromBareContext + status: pass + - kind: unit + ref: modules/pier/dispatch_mcpcall_test.go#TestDispatchMCPCallBothGuards + status: pass + - kind: unit + ref: modules/pier/prm_scopes_test.go#TestPRMUsesAdvertisedScopes + status: pass + human_judgment: false + - id: D2 + description: "OAuth implied scopes do not expand a personal token, write does not grant read, and the subject slice is unchanged." + verification: + - kind: unit + ref: sm-user-plugin/mcp_implies_test.go#TestMCPImpliesPersonalUnchanged + status: pass + human_judgment: false + - id: D3 + description: "MCP consent replace, offline_access, revoke, authorization kind, and idempotent backfill." + verification: + - kind: integration + ref: sm-oauthserver-plugin/mcp_consent_test.go#TestMCPConsentReplace + status: pass + - kind: integration + ref: sm-oauthserver-plugin/mcp_backfill_test.go#TestMCPConsentBackfillIdempotent + status: pass + human_judgment: false + - id: D4 + description: "Patch tags pushed where origin existed, and the framework changelog post is live." + verification: + - kind: other + ref: python3 plan task-4 verify (exit 0) against 32.1-05-release-decision.txt and https://summercms.io/changelog/summercms-0-5-2 + status: pass + human_judgment: false + +duration: 34min +completed: 2026-10-28 +status: complete +--- + +# Phase 32.1 Plan 05: Unit tests and patch release Summary + +**SummerCMS v0.5.2, sm-user-plugin v0.1.16, sm-oauthserver-plugin v0.1.4, and local vue-oauth-layer v0.1.0 ship the MCP OAuth scope, caller, and consent gaps, with the framework changelog posted** + +## Performance + +- **Duration:** 34 min +- **Started:** 2026-10-28T15:09:30Z +- **Completed:** 2026-10-28T15:42:50Z +- **Tasks:** 4 +- **Files modified:** 12 + +`actuals.tokens` is chars/4 of `git show` for `9094bf9`, `756797c`, `4f8618e`, and `a7743e7`, plus the decision file and changelog notes (25896 / 4). This summary is not in that count. `commits: 1` is `git rev-list --count 06a144e19f1bca9b918af7adfc44e7b7a1e3f011..9094bf9` at summary time. The metadata commit is after that range. + +## Accomplishments + +- Framework tests prove a bare context has no MCP call, both guards set one, and PRM uses an advertised list or falls back. +- sm-user-plugin tests keep implied scopes off the stored subject. sm-oauthserver-plugin tests prove consent replace, offline access, revoke, authorization kind, and an idempotent backfill. +- Tags: SummerCMS `v0.5.2` on `9094bf9` (pushed `master`, then the tag), sm-user-plugin `v0.1.16` on `756797c` (pushed `phase-32.1-mcp-implied-scopes`, then the tag), sm-oauthserver-plugin `v0.1.4` on `a7743e7` (pushed `master`, then the tag), vue-oauth-layer `v0.1.0` on `7f805b3` (local only). +- Changelog post id 46: https://summercms.io/changelog/summercms-0-5-2. The plan python verify exited 0. +- No app submodule pointer moved. `go.work` in sm-oauthserver-plugin stayed unstaged. + +## Task Commits + +The test commits were already on the branches before this continuation. They were not redone. + +1. **Task 1: Tracer: bare context has no MCP call, and both guards' tool calls do** - `9094bf9` (test, summercms.go) +2. **Task 2: Plugin, layer, and docs proofs** - `756797c` (test, sm-user-plugin), `4f8618e` (test, sm-oauthserver-plugin). vue-oauth-layer had no new commit in this plan; its tests were already on `7f805b3`. +3. **Task 3: Release checkpoint** - no commit. The accepted option is `tag-and-publish`, recorded in `32.1-05-release-decision.txt` before any tag or push. That file was not rewritten. +4. **Task 4: Tag and publish** - `a7743e7` (chore, sm-oauthserver-plugin) sets `summercms v0.5.2` and `sm-user-plugin v0.1.16` and keeps the summercms replace. Tags and pushes are listed above. No framework commit in this task before the metadata commit. + +**Plan metadata:** this summary, the decision file, the changelog notes, and the roadmap check, committed in summercms.go after this file was written. + +## Files Created/Modified + +- `modules/pact/mcp_call_negative_test.go` - bare context and a FrontendTokenScope literal without Implies +- `modules/pier/dispatch_mcpcall_test.go` - frontend and backend tool calls record MCPCall +- `modules/pier/prm_scopes_test.go` - advertised scopes and the PRMScopes fallback +- `../sm-user-plugin/mcp_implies_test.go` - personal token, write-does-not-grant-read, subject slice unchanged +- `../sm-oauthserver-plugin/mcp_consent_test.go` - replace, offline access, revoke +- `../sm-oauthserver-plugin/authorizations_kind_test.go` - kind mcp or oidc +- `../sm-oauthserver-plugin/mcp_backfill_test.go` - idempotent backfill +- `../sm-oauthserver-plugin/go.mod` - require SummerCMS v0.5.2 and sm-user-plugin v0.1.16 +- `../sm-oauthserver-plugin/go.sum` - sum for sm-user-plugin v0.1.16 +- `.planning/phases/32.1-mcp-oauth-for-plugin-apps-declared-scopes-mcp-subject-on-con/32.1-05-release-decision.txt` - checkpoint record and the tag lists from before the release +- `.planning/phases/32.1-mcp-oauth-for-plugin-apps-declared-scopes-mcp-subject-on-con/32.1-05-changelog-notes.md` - notes published for v0.5.2 +- `.planning/ROADMAP.md` - 32.1-05 checked, 5/5 plans executed + +## Decisions Made + +- The checkpoint option `tag-and-publish` was applied. Versions came from the tag lists in the decision file: next v0.5 patch is v0.5.2, next user v0.1 patch is v0.1.16, next oauth v0.1 patch is v0.1.4, and vue-oauth-layer is v0.1.0 because that tag did not exist. +- sm-user-plugin was tagged on `phase-32.1-mcp-implied-scopes` at `756797c`. `v0.1.15` is not an ancestor. The tag was not moved onto `v0.1.15`. The tag message and the changelog say the missing-scope fields from v0.1.15 are absent. The plugin `go.mod` still requires SummerCMS v0.5.0; the new seams are in v0.5.2, which sm-oauthserver-plugin v0.1.4 requires. +- vue-oauth-layer has no `origin`. `v0.1.0` is local. `git@git.golem15.com:golem15/vue-oauth-layer.git` was not created. +- STATE.md and state.json were not edited and are not in the metadata commit. Current Position stays phase 33. + +## Deviations from Plan + +### Auto-fixed Issues + +None during the release continuation. The test tasks were already committed. + +The user-plugin test file is `mcp_implies_test.go` at the plugin root, not `classes/mcp_guard_test.go`. That path was chosen by the earlier test commit and was left as committed. + +--- + +**Total deviations:** 0 auto-fixed during the release continuation +**Impact on plan:** The release followed the accepted checkpoint. The user-plugin tag is intentionally not based on v0.1.15. + +## Issues Encountered + +- Pushing summercms.go `master` published 44 commits since v0.5.1, including the phase 32 review fixes and planning commits that were already on local master. The push was a fast-forward. No force-push. +- Phase 33 plan 01 reserved the name v0.5.2 for a later tag that would also carry phase 33 seams, and plan 33-14 was going to create that tag. This release took v0.5.2 because it was the next v0.5 patch. Phase 33 must not reuse v0.5.2. The next framework patch is v0.5.3 unless a newer v0.5 tag appears first. +- vue-oauth-layer `README.md` inside `v0.1.0` still says the tag does not exist yet. The release order tagged the existing commit and did not add a README commit. The tag was not moved. "No tag has been pushed" is still true, because there is no remote. +- `state.advance-plan` was not run. It would move Current Position, which is phase 33. Roadmap 32.1-05 was checked by editing ROADMAP.md directly. + +## User Setup Required + +Create `git@git.golem15.com:golem15/vue-oauth-layer.git`, then push `main` and the annotated tag `v0.1.0` from `/media/nvme/dev/golem15/summercms.io/summercms/vue-oauth-layer`. Do not force-push. Until that remote exists, apps cannot pin the layer. The tagged README still describes the pre-tag install shape. + +No journal token was printed, copied, or minted. + +## Next Phase Readiness + +- The three MCP OAuth gaps are on the tags above. No application go.mod or submodule pointer was bumped. +- Phase 33's release ledger must treat v0.5.2 as already published. Its framework seams belong on a later patch. +- Current Position was left on phase 33. + +## Known Stubs + +- `vue-oauth-layer/README.md` line 9, inside tag `v0.1.0`: "That tag does not exist yet." The local tag exists; the remote does not. Not edited, because editing would require moving the tag. + +## Self-Check: PASSED + +- FOUND: `32.1-05-release-decision.txt` (sha256 `3e33d20e4c626c7c43d5da66930ac4b51a0cc02d2995c1fc7e6d5e59f638fa8f`, first line `tag-and-publish`, not rewritten after the tags) +- FOUND: `32.1-05-changelog-notes.md` +- FOUND: summercms.go `9094bf9`, sm-user-plugin `756797c`, sm-oauthserver-plugin `4f8618e` and `a7743e7` +- FOUND remote tags: `v0.5.2`, `v0.1.16`, `v0.1.4`. Local only: vue-oauth-layer `v0.1.0` +- Plan task-4 python verify exit 0. Changelog URL https://summercms.io/changelog/summercms-0-5-2 contains the posted title. + +--- +*Phase: 32.1-mcp-oauth-for-plugin-apps-declared-scopes-mcp-subject-on-con* +*Completed: 2026-10-28* diff --git a/.planning/phases/32.1-mcp-oauth-for-plugin-apps-declared-scopes-mcp-subject-on-con/32.1-05-changelog-notes.md b/.planning/phases/32.1-mcp-oauth-for-plugin-apps-declared-scopes-mcp-subject-on-con/32.1-05-changelog-notes.md new file mode 100644 index 00000000..b57421d2 --- /dev/null +++ b/.planning/phases/32.1-mcp-oauth-for-plugin-apps-declared-scopes-mcp-subject-on-con/32.1-05-changelog-notes.md @@ -0,0 +1,29 @@ +## Highlights + +SummerCMS 0.5.2 lets an MCP OAuth agent use the scopes a plugin declares, and lets a tool call see which subject invoked it. sm-user-plugin v0.1.16 applies read and write as implied scopes for OAuth subjects only. sm-oauthserver-plugin v0.1.4 stores one consent per MCP client, replaces it on a later allow, and backfills live grants. vue-oauth-layer v0.1.0 is the `/connect` page for that consent. Together these versions close the three gaps sm-servers-app needs before MCP OAuth can be switched on: the same tool list as a personal token, attribution of the caller, and per-client revoke. + +## Added + +- One MCP scope list. Plugins publish member-token scopes, the OAuth server advertises that list (optionally filtered), and the protected-resource metadata uses the same names. An empty list still means the five fixed scopes, including backend. OIDC discovery is unchanged. +- An MCP call on the request context: guard, user, client, token, scopes, session and tool. A request that is not an MCP tool call has none. +- A pending consent view reports whether offline access was requested. +- sm-user-plugin v0.1.16 expands a declared scope when an OAuth grant holds the read or write that the scope marks as implied. A personal token is not expanded, write does not imply read, and the stored subject scopes stay as issued. The dispatch credential still carries the personal token id. +- sm-oauthserver-plugin v0.1.4 records MCP consent per client, shows scope details on the consent JSON, marks authorizations as mcp or oidc, and backfills a consent for each live grant. A second migrate inserts nothing. +- vue-oauth-layer v0.1.0 renders `/connect` from that JSON: checkboxes, read locked, the administrator named as text, and a refetch when the backend link changed. The layer's git remote is not published yet, so an app cannot pin the tag until that repository exists. + +## Changed + +- A later MCP allow replaces the stored consent scopes. It does not merge them with the previous grant. +- Unticking offline access clears that flag on the issued code. Omitting the scopes field leaves the pending flag as it was. +- Revoking an authorized MCP client deletes its unused code and leaves the user's other clients in place. +- sm-oauthserver-plugin v0.1.4 requires SummerCMS v0.5.2 and sm-user-plugin v0.1.16. +- sm-user-plugin v0.1.16 is not a descendant of v0.1.15. It does not include the missing-scope and missing-permission fields from that tag. Build it with SummerCMS v0.5.2; its go.mod still states v0.5.0 and selects the newer framework only when the application does. + +## Fixed + +- A hidden relation field or partial field no longer keeps its routes after it is removed from the form. +- Record events fire when a hasMany child is linked, unlinked, or attached from a deferred commit, and the event locale is the request locale's primary tag. +- A widget action dispatched while the widget connects is delivered. +- Uploading through a form extension follows that extension's visibility. +- A required field that belongs to a form extension is checked on the extension's own value, so saving the host form no longer fails on a rule the owner model cannot satisfy. +- The additive API check compares the new release with the previous one after the tree is tagged. diff --git a/.planning/phases/32.1-mcp-oauth-for-plugin-apps-declared-scopes-mcp-subject-on-con/32.1-05-release-decision.txt b/.planning/phases/32.1-mcp-oauth-for-plugin-apps-declared-scopes-mcp-subject-on-con/32.1-05-release-decision.txt new file mode 100644 index 00000000..569911c1 --- /dev/null +++ b/.planning/phases/32.1-mcp-oauth-for-plugin-apps-declared-scopes-mcp-subject-on-con/32.1-05-release-decision.txt @@ -0,0 +1,70 @@ +tag-and-publish +framework yes +v0.1.1 +v0.1.10 +v0.1.11 +v0.1.12 +v0.1.13 +v0.1.14 +v0.1.15 +v0.1.16 +v0.1.17 +v0.1.18 +v0.1.19 +v0.1.2 +v0.1.20 +v0.1.21 +v0.1.22 +v0.1.23 +v0.1.24 +v0.1.25 +v0.1.26 +v0.1.27 +v0.1.3 +v0.1.4 +v0.1.5 +v0.1.6 +v0.1.7 +v0.1.8 +v0.1.9 +v0.2.0 +v0.2.1 +v0.2.2 +v0.2.3 +v0.2.4 +v0.3.0 +v0.3.1 +v0.4.0 +v0.4.1 +v0.4.2 +v0.4.3 +v0.4.4 +v0.5.0 +v0.5.1 +. +user yes +v0.1.0 +v0.1.1 +v0.1.10 +v0.1.11 +v0.1.12 +v0.1.13 +v0.1.14 +v0.1.15 +v0.1.2 +v0.1.3 +v0.1.4 +v0.1.5 +v0.1.6 +v0.1.7 +v0.1.8 +v0.1.9 +. +oauth yes +v0.1.0 +v0.1.1 +v0.1.2 +v0.1.3 +. +layer no +.