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
This commit is contained in:
Jakub Zych
2026-10-23 13:17:36 +02:00
parent 9550fe0a9f
commit 81266de77d
2 changed files with 288 additions and 0 deletions

View File

@@ -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

View File

@@ -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.