From 81266de77d84180e1158f477d9d6f3467f231303 Mon Sep 17 00:00:00 2001 From: Jakub Zych Date: Fri, 23 Oct 2026 13:17:36 +0200 Subject: [PATCH] docs(19.7-04): plan 04 summary; oauthserver v0.1.2 and Journal v0.2.3 released, pinned in the demo - SUMMARY with every deviation from the checkpoint report - deferred items: sm-demo-app push and deploy wait for the user, release builds on summercms v0.4.4 lack the cabana subject-event fix, Redis for a second instance, the real-client check, the retracted mimetype --- .../19.7-04-SUMMARY.md | 245 ++++++++++++++++++ .../19.7-generic-mcp-module/deferred-items.md | 43 +++ 2 files changed, 288 insertions(+) create mode 100644 .planning/phases/19.7-generic-mcp-module/19.7-04-SUMMARY.md diff --git a/.planning/phases/19.7-generic-mcp-module/19.7-04-SUMMARY.md b/.planning/phases/19.7-generic-mcp-module/19.7-04-SUMMARY.md new file mode 100644 index 00000000..28817339 --- /dev/null +++ b/.planning/phases/19.7-generic-mcp-module/19.7-04-SUMMARY.md @@ -0,0 +1,245 @@ +--- +phase: 19.7-generic-mcp-module +plan: 04 +subsystem: oauthserver-journal-demo +status: complete +tags: [sm-oauthserver-plugin, sm-journal-plugin, sm-demo-app, vue-demo-app, mcp, oauth, consent, backend-scope, release, nginx] + +requires: + - phase: 19.7-02 + provides: modules/pier (MCP endpoint, guards, dispatch marker, subject-event fan-out) in framework v0.4.4 + - phase: 19.7-03 + provides: sm-user-plugin v0.1.10 (verified backend link, pact.MCPLinkedBackendResolver, user guard, user_me) +provides: + - sm-oauthserver-plugin v0.1.2 (tagged on 8bbec72, pushed): backend scope on the MCP server, classes.MCPTokenVerifier published as pact.MCPTokenVerifier, GET|POST /oauth/mcp/requests/{request} consent JSON with backend_admin, classes.FireSubjectChanged / FireSubjectChangedAfterCommit (revoked, deleted) + - sm-journal-plugin v0.2.3 (tagged on 4ab94da, pushed): seven journal_ tools and the journal_draft_post prompt (guard backend, golem15.journal.access_posts); optionalBackendPrincipal honours the dispatch marker + - vue-demo-app 21a22b6 (pushed): /connect consent page, /link-admin link-proof page, connect.* and linkAdmin.* (en, pl), dev proxy for the MCP paths + - sm-demo-app (local, not pushed): MCP endpoint at /mcp behind the summercms_mcp tag, TestMCPEndToEnd as frontend user and as linked admin, pins oauthserver v0.1.2 / journal v0.2.3 / vue-demo-app 21a22b6, nginx MCP locations, check-deploy MCP rules, DEPLOY.md "MCP endpoint" +affects: [19.7-05 unit tests, revoke matrix and security review, the demo deploy (needs the user's go-ahead), framework v0.5.0 at phase completion] + +actuals: + tokens: 28539 # chars/4 over the added lines of all five repos' diffs + tasks: 5 + commits: 11 # across repos, see commit_ledger; the SUMMARY commit is extra +commit_ledger: + sm-oauthserver-plugin: {before: d31f8e7, after: 8bbec72, count: 2} + sm-journal-plugin: {before: ffa2c8f, after: 4ab94da, count: 1} + vue-demo-app: {before: f397df2, after: 21a22b6, count: 1} + sm-demo-app: {before: 985af539b9c15019bbbe0fcc3b5dc74c76057774, after: c21ae9dc317f73863494f412a81889bf346d553a, count: 6} + summercms.go: {before: 29ba9a28cba13e802e13635ba7d3cae3c3a3d9e0, after: 9550fe0a9f566d943d515bc47928725feb986317, count: 1} +plan_head_before: 29ba9a28cba13e802e13635ba7d3cae3c3a3d9e0 +plan_head_after: 9550fe0a9f566d943d515bc47928725feb986317 + +tech-stack: + added: [] + patterns: + - "MCP access tokens (oas_at_) are verified by the plugin that minted them and published as pact.MCPTokenVerifier; the verifier refuses revoked, expired, non-mcp, inactive or revoked clients" + - "The backend scope is granted at consent only when the frontend user has a verified admin link; granted = submitted ∩ requested" + - "Subject events from grant revocation and the user cascade fire as *pact.MCPSubjectChanged after commit" + - "A WinterCMS-style optional principal reads the MCP dispatch marker instead of short-circuiting on a missing bearer" + - "Streamable HTTP behind nginx: exact location, proxy_buffering off, 1h read/send timeouts" + +key-files: + created: + - ../sm-oauthserver-plugin/classes/mcp_verifier.go + - ../sm-oauthserver-plugin/classes/subject_events.go + - ../sm-oauthserver-plugin/controllers/mcp_consent_api.go + - ../sm-oauthserver-plugin/mcp_verifier_smoke_test.go + - ../sm-oauthserver-plugin/mcp_consent_smoke_test.go + - ../sm-journal-plugin/mcp.go + - ../sm-journal-plugin/mcp_smoke_test.go + - ../sm-journal-plugin/controllers/api/optional_principal_test.go + - ../sm-demo-app/vue-demo-app/app/pages/connect.vue + - ../sm-demo-app/vue-demo-app/app/pages/link-admin.vue + - ../sm-demo-app/config/mcp.yaml + - ../sm-demo-app/config/env/production/golem15.oauthserver.yaml + - ../sm-demo-app/mcp_e2e_test.go + - ../sm-demo-app/internal/demotest/mcp_tag_on.go + - ../sm-demo-app/internal/demotest/mcp_tag_off.go + modified: + - ../sm-oauthserver-plugin/servers.go + - ../sm-oauthserver-plugin/plugin.go + - ../sm-oauthserver-plugin/routes.go + - ../sm-oauthserver-plugin/classes/cascade.go + - ../sm-oauthserver-plugin/controllers/authorizations.go + - ../sm-oauthserver-plugin/README.md + - ../sm-journal-plugin/controllers/api/auth.go + - ../sm-journal-plugin/README.md + - ../sm-demo-app/vue-demo-app/i18n/locales/en.json + - ../sm-demo-app/vue-demo-app/i18n/locales/pl.json + - ../sm-demo-app/vue-demo-app/nuxt.config.ts + - ../sm-demo-app/summer.yaml + - ../sm-demo-app/config/golem15.oauthserver.yaml + - ../sm-demo-app/scripts/build.sh + - ../sm-demo-app/scripts/dev.sh + - ../sm-demo-app/scripts/check-all.sh + - ../sm-demo-app/scripts/check-deploy.sh + - ../sm-demo-app/boot_test.go + - ../sm-demo-app/realtime_test.go + - ../sm-demo-app/sociallogin_test.go + - ../sm-demo-app/go.mod + - ../sm-demo-app/go.sum + - ../sm-demo-app/deploy/nginx/demo.summercms.io.conf + - ../sm-demo-app/DEPLOY.md + - .planning/phases/19.7-generic-mcp-module/deferred-items.md + +key-decisions: + - "Released oauthserver v0.1.2 and Journal v0.2.3 (tag-both); pushed vue-demo-app master; sm-demo-app stays local until the user's go-ahead" + - "The demo's resource indicator comes from app.url + mcp.path and the production overlay, not from .env; .env keys are documented as overrides only" + - "nginx opens exactly /mcp, the two MCP metadata paths and /oauth/mcp/; /oauth/authorize and OIDC discovery stay closed, enforced by check-deploy.sh" + +requirements-completed: [SC-1, SC-3, SC-4, D-02, D-05, D-15, D-16, D-17, D-18, D-21, D-22, D-23, D-26, D-30, D-31, D-33, D-35, D-36] + +duration: ~50min +completed: 2026-10-23 +--- + +# Phase 19.7 Plan 04: oauthserver and Journal on the MCP endpoint, demo wiring Summary + +**The demo's own OAuth server now issues `oas_at_` tokens that pier verifies, with a `backend` scope granted only through a verified admin link. Journal exposes seven post tools and a draft prompt to linked admins. The demo site has `/connect` and `/link-admin`. Plugin releases oauthserver v0.1.2 and Journal v0.2.3 are pushed; the demo pins them and documents nginx and the deploy for `/mcp`.** + +## Performance + +- **Duration:** about 50 minutes (12:27 to 13:20, 2026-10-23, including the Task 4 checkpoint wait) +- **Tasks:** 5 of 5 (Task 4 was the go-ahead checkpoint, answered tag-both) +- **Repositories touched:** 5 (sm-oauthserver-plugin, sm-journal-plugin, vue-demo-app, sm-demo-app, summercms.go planning) + +## Accomplishments + +- An MCP client signs in on the demo's own authorization server (DCR, PKCE, `/connect` consent) and calls `user_me` through `/mcp` with an `oas_at_` token (Task 1, `TestMCPEndToEnd`) +- The consent is JSON for the site's `/connect` page. It shows the linked admin and grants `backend` only with a link. Revoking a grant or deleting a user fires `revoked` or `deleted` subject events after commit (Task 2) +- Journal's `journal_list_posts`, `journal_show_post`, `journal_list_categories`, `journal_list_tags`, `journal_create_post`, `journal_update_post`, `journal_delete_post` and the `journal_draft_post` prompt are listed only for a linked admin with `golem15.journal.access_posts` and a token carrying `backend`. A created draft is authored by the admin, and unlinking removes the tools (Task 3) +- Releases pushed: sm-oauthserver-plugin v0.1.2 (annotated, on 8bbec72), sm-journal-plugin v0.2.3 (annotated, on 4ab94da), vue-demo-app master at 21a22b6 (Task 5) +- The demo pins all three. It has nginx locations for `/mcp` (unbuffered, 1h timeouts), `/.well-known/oauth-protected-resource*`, `/.well-known/oauth-authorization-server` and `/oauth/mcp/`, `check-deploy.sh` enforces exactly that allow-list, and DEPLOY.md has a new "MCP endpoint" section (Task 5) + +## Task Commits + +1. **Task 1: Tracer, an oas_at_ token runs user_me through /mcp in the tagged demo:** `116df7f` (oauthserver feat), `44cac13` (sm-demo-app feat) +2. **Task 2: MCP consent JSON, revoked/deleted events, /connect and /link-admin:** `8bbec72` (oauthserver feat), `21a22b6` (vue-demo-app feat) +3. **Task 3: Journal MCP tools and prompt, the optionalBackendPrincipal fix, e2e as the linked admin:** `4ab94da` (journal feat), `8668918` (sm-demo-app test) +4. **Task 4: Go-ahead checkpoint:** answered tag-both, no commit +5. **Task 5: Release, pin, nginx and deploy docs:** + - tags `v0.1.2` (sm-oauthserver-plugin, pushed with master) and `v0.2.3` (sm-journal-plugin, pushed with main); vue-demo-app master pushed + - `aec16c4` chore: bump oauthserver to v0.1.2 (sm-demo-app) + - `996586c` chore: bump journal to v0.2.3 (sm-demo-app) + - `7e76957` chore: bump vue-demo-app (MCP consent and link pages) (sm-demo-app) + - `c21ae9d` feat: nginx locations and DEPLOY section for the MCP endpoint (sm-demo-app) + +Planning: `9550fe0` deferred items (framework), plus this SUMMARY's docs commit. + +## Verification + +- Plugins before tagging: `go vet ./...` and `go test -count=1 -short ./...` green in both; the Docker suites, the vue test/build and the additive checks against d31f8e7/ffa2c8f had passed at the checkpoint +- After each pin: `go build -tags summercms_mcp ./... && go test -count=1 -short ./...` green in sm-demo-app; `scripts/check-pins.sh` reports `ok (15 modules)` after the last pin +- Task 5's automated verify passes: both plugin HEADs `describe --exact-match` to v0.1.2 / v0.2.3, `check-deploy.sh` ok, tagged build and short tests green, and no uncommitted change to go.mod, go.sum or the three submodule paths +- `git ls-remote` on origin lists `refs/tags/v0.1.2`, `refs/tags/v0.2.3` and vue-demo-app `master` at 21a22b6 +- A release-mode build (`GOWORK=off go build -tags summercms_mcp`) compiles, and `TestMCPEndToEnd` (tagged, Docker) passes on the pinned tree +- `check-deploy.sh` rejects a `/oauth/` location, a `/.well-known/` location and a `/mcp` without `proxy_buffering off` (each tried and reverted) + +## Deviations from Plan + +### Auto-fixed and adjusted + +**1. [Rule 3 - Blocking] The tag is passed to go build in the build and dev scripts** +- **Found during:** Task 1 +- **Issue:** `summer.yaml` `build.tags` covers `summer build` only. `scripts/build.sh` and `scripts/dev.sh` call `go build` directly and produced binaries that refuse `mcp.enabled: true`. +- **Fix:** `GO_TAGS=summercms_mcp` in build.sh, `-tags summercms_mcp` in dev.sh, and `check-all.sh` also runs the tagged tests +- **Commit:** 44cac13 + +**2. [Rule 3 - Blocking] Untagged test binaries boot with mcp.enabled off** +- **Found during:** Task 1 +- **Issue:** a plain `go test ./...` would boot the app without the pier build and fail on `mcp.enabled: true` +- **Fix:** `internal/demotest/mcp_tag_off.go` / `mcp_tag_on.go` set `mcp.enabled` from the build tag, so the untagged suite stays green and the tagged suite exercises the endpoint +- **Commit:** 44cac13 + +**3. [Rule 1 - Bug, worked around] pier publishes `*pier.Pier` once per app, but some tests build the router twice** +- **Found during:** Task 1 +- **Issue:** a second `surf.BuildRouter` on the same app fails with `backpack: duplicate provider for *pier.Pier` +- **Fix:** `TestRealtimeRoutes` and `socialServer` set `mcp.enabled: false`. Serving is unaffected because a binary builds its router once. The framework-side fix is recorded in deferred-items.md. +- **Commit:** 44cac13 + +**4. [Rule 3] The resource indicator comes from the base app.url plus the production overlay, not .env** +- **Found during:** Task 1 / Task 5 +- **Issue:** the plan put `SUMMER_MCP__RESOURCE` and `SUMMER_GOLEM15__OAUTHSERVER__MCP__RESOURCE` in the production `.env` +- **Fix:** `mcp.resource` defaults to `app.url` + `mcp.path`. `config/golem15.oauthserver.yaml` uses `http://127.0.0.1:8097/mcp` and `config/env/production/golem15.oauthserver.yaml` uses `https://demo.summercms.io/mcp`, so rome needs no `.env` key. DEPLOY.md documents both keys as optional overrides that must stay equal. +- **Commits:** 44cac13, c21ae9d + +**5. [Rule 2 - Missing validation] Consent client-name cap and body validation** +- **Found during:** Task 2 +- **Fix:** the consent JSON caps the client name at 120 runes (DCR clients choose their own names), and a decision POST whose body is not JSON answers 400 `{"error":"invalid_request"}` +- **Commit:** 8bbec72 + +**6. [Rule 2 - Security] The verifier also refuses inactive clients** +- **Found during:** Task 1 +- **Fix:** besides revoked, `MCPTokenVerifier` refuses a token whose client has `is_active` false +- **Commit:** 116df7f + +**7. [Rule 1] The user cascade fires through AfterCommit, and its `deleted` event duplicates the user plugin's** +- **Found during:** Task 2 +- **Issue:** sm-user-plugin v0.1.10 already fires `deleted` for the user guard. The oauthserver cascade fires it again (through `FireSubjectChangedAfterCommit`, so nothing fires on rollback). +- **Fix:** both are kept. The cascade's event follows its own purge of tokens, codes and consents (every purge path, not only the user plugin's deletion), and a second `deleted` for the same subject only repeats the session close in pier. Plan 05's revoke matrix may drop one. +- **Commit:** 8bbec72 + +**8. [Rule 2] /link-admin stale-session handling also covers 401** +- **Found during:** Task 2 +- **Fix:** on `409 stale_session` or a `401`, the page calls `logout()`, shows `linkAdmin.signInAgain` and offers the sign-in that returns to the same `/link-admin?code=...`, so the fresh sign-in D-05 requires happens before confirming +- **Commit:** 21a22b6 + +**9. [Rule 3] Nuxt dev proxy paths** +- **Found during:** Task 2 +- **Fix:** `nuxt.config.ts` adds `/mcp`, `/.well-known/oauth-protected-resource`, `/.well-known/oauth-authorization-server` and `/oauth/mcp/` to the dev proxy targets, so a local client can use the Nuxt origin and the consent redirect lands on the Nuxt `/connect` page. DEPLOY.md, "MCP endpoint", "Locally", gives the hand-off: `SUMMER_APP__URL=http://127.0.0.1:3020`, with both resources at `http://127.0.0.1:3020/mcp`. +- **Commits:** 21a22b6, c21ae9d + +**10. [Rule 3 - Blocking] check-deploy.sh refused any /oauth location** +- **Found during:** Task 5 +- **Issue:** the deploy check rejected every `/oauth` location. Its `/.well-known` rule sat after a `sys.exit` inside an `if` and never ran. +- **Fix:** the check now allows exactly `/oauth/mcp/`, the ACME webroot and the two MCP metadata paths, runs the `/.well-known` rule in the location loop, and requires `= /mcp` to proxy to Go with `proxy_buffering off` and `proxy_read_timeout 1h` +- **Commit:** c21ae9d + +**11. [Rule 1 - Docs accuracy] DEPLOY.md does not promise self-service revocation** +- **Found during:** Task 5 +- **Issue:** MCP grants write no consent row, so `GET /oauth/authorizations` does not list them and `DELETE /oauth/authorizations/{client_id}` answers 404 for an MCP client +- **Fix:** DEPLOY.md says unlinking removes the Journal tools at once, MCP grants are not yet in the authorized-apps list, and deleting the user ends every grant. The gap is in deferred-items.md for plan 05's revoke matrix. +- **Commit:** c21ae9d + +**12. Pre-existing main.go diff in sm-demo-app (left alone)** +- sm-demo-app had an uncommitted `main.go` change from another session before this plan. Running `summer build` with the v0.4.4 CLI reproduces HEAD's `main.go`, so this plan has no regeneration to commit. The working-tree diff is not ours: it was never staged, and it is still uncommitted. + +**13. sm-journal-plugin has an untracked `go.work.sum`** +- It is generated by the plugin's own `go.work` (which is gitignored) and is not part of any commit or tag. The tag sits on the clean commit 4ab94da. + +### Not performed in this run + +- **Task 5's human check** with a real MCP client against the local demo (sign-in on `/connect`, `user_me` only, linking through `/link-admin`, reconnecting with `backend`, the `journal_` tools, a draft post, unlinking) was not run, so no local servers were left running. `TestMCPEndToEnd` covers the same path automatically. The steps are in DEPLOY.md, "MCP endpoint", "Locally". It is recorded in deferred-items.md. + +## Auth Gates + +None. + +## Deferred Issues + +Recorded in `.planning/phases/19.7-generic-mcp-module/deferred-items.md`: +- sm-demo-app is eight commits ahead of origin. It is not pushed or deployed, and both wait for the user's go-ahead (deploy also installs the vhost and runs the MCP smoke) +- release builds pinning summercms v0.4.4 lack the cabana subject-event fix d96c726 until the demo requires the next framework tag +- Redis for MCP sessions if rome ever runs a second instance +- MCP grants not in the authorized-apps list (DELETE answers 404), carried from Task 2 +- pier's once-per-app publish, carried from Task 1 +- the real-client check above +- a retracted indirect `github.com/gabriel-vasile/mimetype` v1.4.15 (through lagoon and validator), which predates this plan + +## Threat Flags + +| Flag | File | Description | +|------|------|-------------| +| threat_flag: public-surface | ../sm-demo-app/deploy/nginx/demo.summercms.io.conf | `/mcp`, `/.well-known/oauth-*` and `/oauth/mcp/` become public on the next deploy (T-19.7-37: throttled in pier and oauthserver; nginx timeouts bounded at 1h; check-deploy pins the allow-list) | + +## Next Phase Readiness + +- Plan 05 (unit tests, revoke matrix, security review) can run against tagged plugins and the pinned demo +- Before the demo deploy: the user's go-ahead for pushing sm-demo-app and deploying, and ideally a framework tag carrying d96c726 + +## Self-Check: PASSED + +- Files present: connect.vue, link-admin.vue, mcp.go (journal), mcp_verifier.go, mcp_consent_api.go, demo mcp_e2e_test.go, nginx conf, DEPLOY.md "### MCP endpoint" +- Commits present: 116df7f, 8bbec72 (oauthserver); 4ab94da (journal); 21a22b6 (vue-demo-app); 44cac13, 8668918, aec16c4, 996586c, 7e76957, c21ae9d (sm-demo-app); 9550fe0 (framework) +- Tags on origin: sm-oauthserver-plugin v0.1.2, sm-journal-plugin v0.2.3 diff --git a/.planning/phases/19.7-generic-mcp-module/deferred-items.md b/.planning/phases/19.7-generic-mcp-module/deferred-items.md index dad73633..3f3b664f 100644 --- a/.planning/phases/19.7-generic-mcp-module/deferred-items.md +++ b/.planning/phases/19.7-generic-mcp-module/deferred-items.md @@ -89,3 +89,46 @@ was changed by the plan that recorded it. status: open **What:** `gofmt -l` lists `classes/blog_editor_test.go` before this plan touched the repository. Left as is. +- sm-demo-app push and the demo deploy wait for the user (found during 19.7-04 Task 5) + status: open + **What:** sm-demo-app master is eight commits ahead of origin (a25444e, + 985af53 and this plan's 44cac13, 8668918, the pins aec16c4 oauthserver + v0.1.2, 996586c journal v0.2.3, 7e76957 vue-demo-app 21a22b6, and + c21ae9d with the nginx MCP locations and DEPLOY.md "MCP endpoint"). None + is pushed and nothing is deployed: both need the user's go-ahead. The + deploy also installs the changed vhost (DEPLOY.md, "When + deploy/nginx/demo.summercms.io.conf changed") and runs the MCP smoke. + The sm-oauthserver-plugin v0.1.2 and sm-journal-plugin v0.2.3 tags and + the vue-demo-app commit are pushed. +- Release builds pinning summercms v0.4.4 lack the cabana subject-event fix (found during 19.7-04 Task 5) + status: open + **What:** the demo (GOWORK=off release build), oauthserver v0.1.2 and + Journal v0.2.3 require summercms v0.4.4, which predates d96c726 + (cabana fires `*pact.MCPSubjectChanged` after commit). Development + builds use the go.work replace and carry the fix; a release binary does + not: admin deletion, permission or role changes and token revocation in + the admin do not end MCP sessions or send list_changed until the next + framework tag is required by the demo. The verifier and the backend + resolver still refuse at the next request, so access does not outlive + the change; only the live session notification is missing. Deploy the + demo after the next framework tag, or accept the gap for the demo. +- Redis for MCP sessions on rome (found during 19.7-04 Task 5) + status: open + **What:** the demo runs one instance with `mcp.sessions.store: memory`. + A second instance needs `SUMMER_MCP__SESSIONS__STORE=redis` and + `SUMMER_REDIS__URL` on every instance (DEPLOY.md, "MCP endpoint") and a + Redis on rome; also see the `notifications/initialized` item above. +- Real MCP client check of the demo not run (found during 19.7-04 Task 5) + status: open + **What:** Task 5's human check (a real client against the local demo: + OAuth sign-in on /connect, user_me only, link an admin through + /link-admin, reconnect with backend, the journal_ tools, a draft post, + unlink) was not performed in the executor run; the automated + TestMCPEndToEnd covers the same path. The local setup is DEPLOY.md, + "MCP endpoint", "Locally". +- Retracted `github.com/gabriel-vasile/mimetype` v1.4.15 in sm-demo-app (found during 19.7-04 Task 5) + status: open + **What:** `go get` in sm-demo-app warns that mimetype v1.4.15 is + retracted (quadratic time on crafted CDF files). It arrives indirectly + and predates this plan; a `go get github.com/gabriel-vasile/mimetype@latest` + in the framework (and a tag) would lift it for every consumer.