diff --git a/.planning/phases/32.1-mcp-oauth-for-plugin-apps-declared-scopes-mcp-subject-on-con/32.1-CONTEXT.md b/.planning/phases/32.1-mcp-oauth-for-plugin-apps-declared-scopes-mcp-subject-on-con/32.1-CONTEXT.md index e6648091..feaba77e 100644 --- a/.planning/phases/32.1-mcp-oauth-for-plugin-apps-declared-scopes-mcp-subject-on-con/32.1-CONTEXT.md +++ b/.planning/phases/32.1-mcp-oauth-for-plugin-apps-declared-scopes-mcp-subject-on-con/32.1-CONTEXT.md @@ -12,9 +12,9 @@ An agent connected to `/mcp` over OAuth gets the same tools, attribution and rev 2. **MCP call context.** A route run by an MCP tool call can read who called it (guard, user, client ID, token id, scopes) and the MCP session through a `pact` accessor. 3. **MCP consent row.** `sm-oauthserver-plugin` records a consent for MCP grants, so the existing per-client revoke ends one agent's grant and its MCP sessions. -Everything ships as patch releases: framework 0.5.x, `sm-oauthserver-plugin`, and `sm-user-plugin` (it publishes the scope catalog). Module READMEs and `docs/` pages describe the changes. +Everything ships as patch releases: framework 0.5.x, `sm-oauthserver-plugin`, and `sm-user-plugin` (it publishes the scope catalog and applies the implied scopes). Module READMEs and `docs/` pages describe the changes. The phase also starts a public Nuxt layer, `vue-oauth-layer`, with the `/connect` consent page (D-17). -The source of the three gaps is a consuming app's agent-access work (its D-36, D-37, D-38). Those notes were not found in the local sibling repos; the ROADMAP entry is the authoritative statement. +The three gaps come from sm-servers-app phase 05 (`/media/nvme/dev/pxpx/sm-servers-app/.planning/phases/05-agent-access-over-mcp/05-CONTEXT.md`, D-36 to D-38), escalated as upstream Requests 3, 4 and 5 on 2026-10-28. That app keeps `golem15.oauthserver.mcp.enabled` off in production until they ship. Its expected-gap tests (`TestMCPOAuthToolsMatchPersonal`, `TestMCPOAuthWriteAttribution`) must turn into passing assertions against the released versions without app-side scope middleware. @@ -23,6 +23,7 @@ The source of the three gaps is a consuming app's agent-access work (its D-36, D ### Declared scopes and the read/write ceiling - **D-01:** **The 19.7 D-20 ceiling stays.** OAuth `read`/`write` remain the ceiling: a non-read-only tool still needs `write`, and a route's `user.scope:` still needs the declared scope in the grant. A write tool behind `user.scope:bm:write` needs both `write` and `bm:write`, as a personal token with read/write plus declared scopes does today. No pier ceiling change. +- **D-01a:** **Declared scopes can be implied by stock `read`/`write` (Request 3).** `pact.FrontendTokenScope` gets an additive field (proposed `Implies`, value `"read"` or `"write"`, empty = not implied). An OAuth MCP grant holding the stock scope also satisfies every declared scope that names it as implied. For example, `servers:read` with `Implies: "read"` is satisfied by a `read` grant, so an agent granted `read write` lists the same servers tools as a personal token with `servers:read servers:write` (sm-servers-app's "OAuth list equals personal list"). The expansion applies only to OAuth MCP subjects, in sm-user-plugin's MCP guard (`MCPAllows`/`MCPDispatch`) or where the subject's scopes are built. A personal token's scopes keep their meaning, and the user still never gets a scope the plugin did not mark as implied. Declared scopes are still advertised (D-03) for clients that want a narrower grant. — **Reversibility:** costly — once apps rely on read/write implying their scopes, removing it shrinks live agents' tool lists. - **D-02:** **Declared scopes are offered on the MCP OAuth server only**, like `backend` (19.7 D-21). The OIDC server keeps its PHP-parity scope list. - **D-03:** **Advertised scopes = fixed scopes + declared scopes, filtered by an optional opt-in list.** When the config list is set, only the declared scopes it names are advertised and accepted. When it is unset, every declared scope is. The fixed scopes (`read`, `write`, `ai`, `offline_access`, `backend`) are always present. The same set drives the wristband MCP server's metadata `scopes_supported`, its authorize allow-list (`allowedAuthorizeScopes`) and pier's PRM `scopes_supported`. This rule must be documented clearly in the oauthserver README, the pier README and `docs/services/oauth-server.md` / `docs/services/mcp.md`. A name in the list that no plugin declares should be a clear boot error or warning; the planner decides which. — **Reversibility:** costly — scope names published in metadata and stored in grants. - **D-04:** **The config key lives in sm-oauthserver-plugin** (proposed `golem15.oauthserver.mcp_scopes`), next to where `NewServers` builds `mcpOpts.ScopesSupported`. **pier reads the list from the published verifier** (`pact.MCPTokenVerifier`, extended additively) instead of its own `PRMScopes` constant, so the PRM and the authorization server cannot drift. `pier.PRMScopes` stays as the fallback when no verifier publishes a list. @@ -44,8 +45,11 @@ The source of the three gaps is a consuming app's agent-access work (its D-36, D - **D-14:** **A data migration backfills consents** for grants that predate this release: one row per (user, client) with live MCP access or refresh tokens, scopes = union of the tokens' scopes, `created_at` = earliest token. It must be idempotent and skip pairs that already have a consent. — **Reversibility:** one-way — writes rows into a shared table on every consuming install. - **D-15:** **One revoke ends everything for that client:** frontend and backend (`backend_user_id`) access and every MCP session of the client. No separate backend revoke. +### Consent page as a Nuxt layer +- **D-17:** **An initial `vue-oauth-layer` ships in this phase.** It is a new public Nuxt layer repo next to `vue-journal-layer`, `vue-faq-layer`, `vue-menu-layer` and `vue-paymentgateway-layer`, with the same structure and the same pnpm-git-dependency-pinned-by-tag consumption. It provides the `/connect` page of redirect consent mode, rendered from the MCP consent JSON (D-07): client name and logo, per-scope checkboxes with labels and descriptions, `read` locked, the "also act as admin " line for `backend`, allow/deny, and the `backend_link_changed` 409 re-show. Apps adopt it later as their own work, so 32.1 does not migrate any app's existing `/connect` page. The layer gets a tag. It is one plan in the phase. + ### Release -- **D-16:** Patch releases only: framework 0.5.x (never hardcode the version), `sm-user-plugin` (catalog publication), `sm-oauthserver-plugin` (scopes, consent JSON/page, consent row, backfill, listing marker). Commit and push each plugin before bumping pointers in apps. Each pushed framework tag gets a changelog post. +- **D-16:** Patch releases only: framework 0.5.x (never hardcode the version), `sm-user-plugin` (catalog publication, implied scopes), `sm-oauthserver-plugin` (scopes, consent JSON/page, consent row, backfill, listing marker), plus the first tag of `vue-oauth-layer`. Commit and push each plugin before bumping pointers in apps. Each pushed framework tag gets a changelog post. The release notes tell sm-servers-app which versions close Requests 3–5. ### Claude's Discretion - The pact interface name and shape for the published scope catalog (D-05), within "names + label + description, additive". @@ -66,6 +70,10 @@ The source of the three gaps is a consuming app's agent-access work (its D-36, D ### Prior decisions this phase builds on - `.planning/phases/19.7-generic-mcp-module/19.7-CONTEXT.md` — pier design; D-10 (tool binds to route, in-process dispatch), D-16 (permissions from route middleware), D-20 (read/write ceiling, kept by D-01), D-21 (`backend` scope on the MCP server only). - `.planning/phases/19.4-oidc-provider-on-wristband/19.4-CONTEXT.md` — sm-oauthserver-plugin's two-Server design, client kinds, consent model, additive wristband interfaces. +- `/media/nvme/dev/pxpx/sm-servers-app/.planning/phases/05-agent-access-over-mcp/05-CONTEXT.md` § "Decisions after gap verification (2026-10-28)" — D-36/D-37/D-38, the source of this phase (Requests 3, 4, 5). +- `/media/nvme/dev/pxpx/sm-servers-app/.planning/phases/05-agent-access-over-mcp/05-09-PLAN.md` — the expected-gap tests the releases must satisfy (`TestMCPOAuthToolsMatchPersonal`, `TestMCPOAuthWriteAttribution`, `TestMCPOAuthEmptyGrant`) and the rejected app-side scope middleware. +- `/media/nvme/dev/pxpx/sm-servers-app/OFFBOARDING.md` § 8 — the request texts (what, why, interim). +- `../vue-journal-layer/` (and the faq/menu/paymentgateway layers) — the layer structure and release pattern to copy for `vue-oauth-layer`. - `../sm-bm-app/.planning/phases/03.3-mcp-access-for-bm/03.3-CONTEXT.md` — a consuming app's MCP access decisions (D-07 consent page all-or-nothing in that app; personal tokens alongside OAuth). ### Docs to update @@ -113,6 +121,8 @@ The source of the three gaps is a consuming app's agent-access work (its D-36, D - Offering declared scopes on the OIDC server (non-MCP OAuth clients). Rejected for this phase (D-02). - Release/rollout order across framework, sm-user-plugin, sm-oauthserver-plugin and the consuming app was not discussed in detail; the planner follows the standard "plugin first, pointer bump second" order. +- Moving apps' existing `/connect` pages (sm-servers-app, sm-bm-app, sm-demo-app) onto `vue-oauth-layer`: each app's own work. +- Switching OAuth on in sm-servers-app production: that app's decision once the releases land. ### Reviewed Todos (not folded) - 27 pending todos matched on keywords only (e.g. "Record PHP OIDC parity against WavePath", user-plugin logout cookie, activate-by-code second factor). None touch MCP scopes, call context or MCP consent; none were folded. diff --git a/.planning/phases/32.1-mcp-oauth-for-plugin-apps-declared-scopes-mcp-subject-on-con/32.1-DISCUSSION-LOG.md b/.planning/phases/32.1-mcp-oauth-for-plugin-apps-declared-scopes-mcp-subject-on-con/32.1-DISCUSSION-LOG.md index 1d63f5b2..a6122f41 100644 --- a/.planning/phases/32.1-mcp-oauth-for-plugin-apps-declared-scopes-mcp-subject-on-con/32.1-DISCUSSION-LOG.md +++ b/.planning/phases/32.1-mcp-oauth-for-plugin-apps-declared-scopes-mcp-subject-on-con/32.1-DISCUSSION-LOG.md @@ -124,3 +124,20 @@ - Declared scopes on the OIDC server. - Detailed rollout order across repos (planner uses the standard plugin-first order). + +--- + +## Follow-up (same day): source notes and Vue layer + +The source notes were found in `/media/nvme/dev/pxpx/sm-servers-app` phase 05 (D-36 to D-38, Requests 3–5). Request 3 asked that OAuth `read`/`write` map onto declared scopes, which differs from the locked "advertise declared scopes" approach. + +| Option | Description | Selected | +|--------|-------------|----------| +| Both: declared implies + advertise | Additive `Implies` on FrontendTokenScope; declared scopes still advertised | ✓ | +| Mapping only | Exactly Request 3; drop advertising | | +| Advertise only (as locked) | Clients must request servers:* explicitly | | + +| Option | Description | Selected | +|--------|-------------|----------| +| Yes, initial layer in 32.1 | New public vue-oauth-layer with the /connect page | ✓ | +| Defer to its own phase | JSON contract only | |