docs(19.7-02): close plan without a release

This commit is contained in:
Jakub Zych
2026-10-23 00:23:08 +02:00
parent a26ee2b72d
commit eadd58414d
3 changed files with 283 additions and 10 deletions

View File

@@ -1454,14 +1454,14 @@ Plans:
3. `tools/list` lists only the tools the session's principals may call (frontend permissions through the merged group/user permissions, backend permissions and token scope through cabana), and a call runs through the route's own middleware.
4. An editor signs in once through `wristband` OAuth as a frontend user; with a verified link to a backend user, backend-guarded tools such as Journal's run as that admin, and removing the link or the grant removes them. The phase gets a security review; the MCP SDK dependency has a decision note before code lands; unit tests are the last plan.
**Plans:** 1/5 plans executed
**Plans:** 2/5 plans executed
Plans:
**Wave 1**
- [x] 19.7-01-PLAN.md — Framework seams: bouncer dispatch marker, surf handler lookup and streaming routes, tagged pier mount hook, SDK-free pact MCP contracts, My account contribution seam, prompted record action, wristband scope set
**Wave 2** *(blocked on Wave 1 completion)*
- [ ] 19.7-02-PLAN.md — The `pier` module behind `summercms_mcp` (go-sdk, hybrid stateful/stateless, Redis sessions, fan-out, PRM) and the framework release carrying 19.6.1 + 19.7-01/02
- [x] 19.7-02-PLAN.md — The `pier` module behind `summercms_mcp` (go-sdk, hybrid stateful/stateless, Redis sessions, fan-out, PRM) and the framework release carrying 19.6.1 + 19.7-01/02
**Wave 3** *(blocked on Wave 2 completion)*
- [ ] 19.7-03-PLAN.md — sm-user-plugin: verified account link, Administrators-screen link actions, plugin frontend permission codes, the `user` MCP guard and `user_me` tool, plugin release and app pointer bumps
@@ -1607,7 +1607,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 →
| 19.5. Social login in sm-user-plugin | 5/5 | Complete | 2026-10-21 |
| 19.6. Two-factor auth and device flow in sm-user-plugin | 0/TBD | Not started | - |
| 19.6.1. Backend Administrators screen (INSERTED) | 5/5 | Complete | 2026-10-22 |
| 19.7. Generic MCP module | 1/5 | In Progress| |
| 19.7. Generic MCP module | 2/5 | In Progress| |
| 19.8. Console commands overhaul (INSERTED) | 6/6 | In Progress| |
| 20. Demo instance with every shared plugin | 0/TBD | Not started | - |
| 30. Płytarium cutover | 0/TBD | Not started | - |

View File

@@ -4,16 +4,16 @@ milestone: v1.0
current_phase: "19.7"
current_phase_name: Generic MCP module (INSERTED)
status: executing
stopped_at: Completed 19.7-01-PLAN.md
last_updated: "2026-10-22T22:15:45.219Z"
stopped_at: Completed 19.7-02-PLAN.md
last_updated: "2026-10-22T22:22:58.311Z"
last_activity: 2026-10-23
last_activity_desc: "Completed quick task 261022-x7a: user-plugin golem15.user.password.public_routes switch (login/forgot/reset 404 when false)"
state_head: e5dd4579a8a1503b9711fa27bc140e5b594baa88
state_head: a26ee2b72d5b99db7596217ce1c3da7ec0c6bf89
progress:
total_phases: 45
completed_phases: 37
total_plans: 238
completed_plans: 230
completed_plans: 231
gsd_state_version: "1.0"
milestone_name: milestone
---
@@ -30,7 +30,7 @@ See: .planning/PROJECT.md (updated 2026-10-22)
## Current Position
Phase: 19.7 (Generic MCP module (INSERTED)) — EXECUTING
Plan: 2 of 5
Plan: 3 of 5
Status: Ready to execute
Last activity: 2026-10-23 — Completed quick task 261022-x7a: user-plugin golem15.user.password.public_routes switch (login/forgot/reset 404 when false)
@@ -277,6 +277,7 @@ Progress: [████████░░] 82%
| Phase 19.6.1 P04 | 18 min | 3 tasks | 10 files |
| Phase 19.6.1 P05 | 4 min | 2 tasks | 7 files |
| Phase 19.7 P01 | 52 min | 5 tasks | 64 files |
| Phase 19.7 P02 | 38 min | 4 tasks | 36 files |
## Accumulated Context
@@ -948,6 +949,7 @@ Recent decisions affecting current work:
- [Phase 19.7]: Framework release is v0.5.0 (19.7-02 Task 5), carrying 19.6.1, 19.7 plans 01-02 and a set of 7 msd-quick patches; tag only after all 7 are committed — User decision 2026-10-22: minor bump for the new pier module and pact capabilities; hold the tag until the quick patches land
- [Phase 19.7]: v0.5.0 is tagged only when phase 19.7 is fully complete (plans 01-05 and phase verification); supersedes the earlier 19.7-02 Task 5 entry. Plan 02 closes without a tag; no intermediate framework tag for 19.7 work — User decision 2026-10-23: a minor version marks the finished MCP phase, not a partial one
- [Phase 19.7]: Once the user confirms all in-flight msd-quick tasks are finished: release the framework as the next v0.4.x patch (carrying the quick patches and 19.7 work so far) and tag the held user-plugin fixes; plugins may be tagged mid-phase. Framework v0.5.0 stays reserved for full MCP usability at phase 19.7 completion — User decision 2026-10-23: plugins can be tagged now, framework 0.5 means the MCP phase is finished; wait for the user's word that the last quick agent is done
- [Phase 19.7]: 19.7-02: pier serves 2025-11-25 clients through its own session router over StreamableServerTransport (Redis-shared sessions, D-25) and 2026-07-28 clients through the SDK stateless handler; plan 02 closed without a tag, pier ships opt-in in v0.4.4 (summercms_mcp tag plus mcp.enabled, default off)
### Pending Todos
@@ -1063,6 +1065,6 @@ Items acknowledged and carried forward from previous milestone close:
## Session Continuity
Last session: 2026-10-22T14:44:54.330Z
Stopped at: Completed 19.7-01-PLAN.md
Last session: 2026-10-22T22:22:57.128Z
Stopped at: Completed 19.7-02-PLAN.md
Resume file: None

View File

@@ -0,0 +1,271 @@
---
phase: 19.7-generic-mcp-module
plan: 02
subsystem: mcp
tags: [pier, mcp, go-sdk, jsonschema-go, surf, bouncer, redis, sessions, stateless, prm, build-tag]
requires:
- phase: 19.7-01
provides: bouncer.WithDispatch marker, surf.Router.Handler + MountStream + mountPier hook, cabana.ResolveBackendToken / ScopeLimitFor / BackendPrincipal, SDK-free pact MCP contracts, pact.MCPSubjectChanged events, cooler.KeyPrefix, wristband ScopesSupported
provides:
- modules/pier, the only importer of go-sdk v1.8.0 and jsonschema-go v0.4.3, linked only into binaries built with -tags summercms_mcp
- modules/surf/pier_on.go (//go:build summercms_mcp) adapting the router to pier.Host
- catalog compiled at boot from pact.HasMCP (<shortcode>_<name> prefixing, route binding, guard resolution, schema reflection, annotations, boot failures by name)
- in-process tool dispatch through surf's compiled chain with the bouncer marker and no Authorization or Cookie header
- per-request identity (OAuth verifier, personal tokens behind mcp.allow_personal_tokens, backend scope and link), read/write ceiling, built-in backend guard provider
- stateful 2025-11-25 sessions over a memory or Redis store with rehydration, binding checks, DELETE and pub/sub fan-out; stateless 2026-07-28 path on the same mcp.path
- RFC 9728 protected resource metadata and WWW-Authenticate challenge; periodic re-verification of long GET streams
- prompts/get, audit lines (pier: tool call, pier: session)
- docs/services/mcp.md, modules/pier/README.md, root README row, decision note .planning/notes/2026-10-22-mcp-go-sdk.md and STACK.md rows
affects: [19.7-03 sm-user-plugin link and frontend permissions, 19.7-04 oauthserver verifier and Journal, 19.7-05 unit tests and security review, framework v0.4.4 (pier opt-in), framework v0.5.0 at phase completion]
actuals:
tokens: 51272
tasks: 4
commits: 29
plan_head_before: 3746675c726909b9280e78ea7f73985a1dcb48a5
plan_head_after: a26ee2b72d5b99db7596217ce1c3da7ec0c6bf89
tech-stack:
added:
- "github.com/modelcontextprotocol/go-sdk v1.8.0 (tagged builds only)"
- "github.com/google/jsonschema-go v0.4.3 (tagged builds only)"
patterns:
- "Build-tag containment: the SDK is reachable only through modules/pier and modules/surf/pier_on.go; an untagged binary lists no modelcontextprotocol module"
- "One SDK server per identity; a receiving middleware re-filters tools/list and tools/call for the current request's identity"
- "pier's own session router over StreamableServerTransport so sessions can be rehydrated from a shared Redis store"
- "Identity re-read from the bearer on every request; the stored session carries only the binding, never the token"
- "Store opened lazily from redis.url under cooler's prefix; nothing dials at mount"
key-files:
created:
- .planning/notes/2026-10-22-mcp-go-sdk.md
- modules/pier/README.md
- modules/pier/pier.go
- modules/pier/config.go
- modules/pier/host.go
- modules/pier/catalog.go
- modules/pier/dispatch.go
- modules/pier/filter.go
- modules/pier/identity.go
- modules/pier/guards.go
- modules/pier/backend_guard.go
- modules/pier/session.go
- modules/pier/stateless.go
- modules/pier/store.go
- modules/pier/store_memory.go
- modules/pier/store_redis.go
- modules/pier/events.go
- modules/pier/prm.go
- modules/pier/audit.go
- modules/pier/export_test.go
- modules/pier/example_mcp_test.go
- modules/pier/tracer_smoke_test.go
- modules/pier/identity_smoke_test.go
- modules/pier/sessions_smoke_test.go
- modules/pier/stateless_smoke_test.go
- modules/surf/pier_on.go
- docs/services/mcp.md
modified:
- go.mod
- go.sum
- .planning/research/STACK.md
- README.md
- modules/surf/README.md
- docs/site.yaml
- docs/services/authentication.md
- docs/plugins/registration.md
- .planning/phases/19.7-generic-mcp-module/deferred-items.md
key-decisions:
- "D-24: go-sdk v1.8.0 and jsonschema-go v0.4.3 chosen and approved by the user (Task 1 legitimacy check) before any go get; decision note and STACK rows committed first (3ea82a1)"
- "Stateful 2025-11-25 sessions are served by pier's own router over StreamableServerTransport (needed for Redis-shared sessions, D-25); 2026-07-28 clients go through the SDK's stateless handler; both share identity, dispatch and filtering (D-32)"
- "The mounted *pier.Pier is published on the application so tests reach it through export_test.go seams"
- "Release deferred: no tag in this plan. pier ships opt-in in framework v0.4.4 (build tag summercms_mcp plus mcp.enabled, default off), released by the orchestrator outside this plan; v0.5.0 is reserved for phase 19.7 completion (user decision 2026-10-23)"
patterns-established:
- "Tracer first: one fake-plugin tool listed and called end to end by an SDK client through the stateful path before Redis, stateless and identity expansion"
- "Docs example as a test: TestExampleBlogPlugin builds the acme.blog declaration that docs/services/mcp.md copies through src="
requirements-completed: [SC-1, SC-2, SC-3, SC-4, D-08, D-10, D-11, D-12, D-13, D-14, D-15, D-16, D-17, D-19, D-20, D-21, D-22, D-23, D-24, D-25, D-26, D-27, D-28, D-29, D-30, D-32, D-33, D-35, D-36]
coverage:
- id: D1
description: "Tracer: an SDK client lists and calls acme_echo end to end through surf's compiled guard chain with the dispatch marker; the inner request carries no Authorization or Cookie header; mcp.enabled off mounts nothing; untagged binaries carry no SDK"
requirement: "SC-1"
verification:
- kind: unit
ref: "modules/pier/tracer_smoke_test.go#TestPierTracerSmoke"
status: pass
- kind: other
ref: "go build -tags summercms_mcp ./..."
status: pass
human_judgment: false
- id: D2
description: "Catalog: <shortcode>_<name> prefixing, route binding, guard resolution, schema reflection, boot failures by name"
requirement: "SC-2"
verification:
- kind: unit
ref: "modules/pier/tracer_smoke_test.go#TestPierTracerSmoke"
status: pass
- kind: unit
ref: "modules/pier/example_mcp_test.go#TestExampleBlogPlugin"
status: pass
human_judgment: false
- id: D3
description: "Per-identity tools/list and tools/call filtering, read/write ceiling, personal tokens behind the flag, prompts/get, audit line without arguments or token"
requirement: "SC-3"
verification:
- kind: unit
ref: "modules/pier/identity_smoke_test.go (subtests of TestPierTracerSmoke: read-write ceiling and prompts, audit line, personal tokens behind the flag)"
status: pass
human_judgment: false
- id: D4
description: "Redis sessions: binding check (403), rehydration on another instance, DELETE ends the session everywhere"
requirement: "D-25"
verification:
- kind: unit
ref: "modules/pier/sessions_smoke_test.go#TestPierSessionsSmoke"
status: pass
human_judgment: false
- id: D5
description: "Subject-changed fan-out: list_changed on permission and link changes, session close on revocation and deletion, across instances"
requirement: "D-26"
verification:
- kind: unit
ref: "modules/pier/sessions_smoke_test.go#TestPierFanOutSmoke"
status: pass
human_judgment: false
- id: D6
description: "2026-07-28 clients served statelessly from the same path"
requirement: "D-32"
verification:
- kind: unit
ref: "modules/pier/stateless_smoke_test.go#TestPierStatelessSmoke"
status: pass
human_judgment: false
- id: D7
description: "Framework tag, push and changelog post (D-31)"
requirement: "D-31"
verification:
- kind: other
ref: "deferred by user decision 2026-10-23: v0.4.4 released by the orchestrator outside this plan; v0.5.0 at phase 19.7 completion"
status: deferred
human_judgment: true
rationale: "The user reserved v0.5.0 for full MCP usability; the release is not part of this plan's close"
duration: 38 min (tasks 1-4, 15:58Z-16:36Z; the release checkpoint wait is excluded)
completed: 2026-10-23
status: complete
---
# Phase 19.7 Plan 02: pier MCP module Summary
**The `pier` module serves an opt-in MCP endpoint on go-sdk v1.8.0 behind the `summercms_mcp` build tag. Plugin tools declared through `pact.HasMCP` are listed per identity and called in-process through surf's compiled route chains with the bouncer dispatch marker. Clients on protocol 2025-11-25 get stateful sessions over a memory or Redis store, with fan-out across instances; 2026-07-28 clients are served statelessly from the same path. The module also serves RFC 9728 resource metadata. No release was cut in this plan.**
## Performance
- **Duration:** 38 min of task work (2026-10-22T15:58:41Z to the Task 4 commit at 16:36Z), followed by the release checkpoint
- **Started:** 2026-10-22T15:58:41Z
- **Completed:** 2026-10-23 (closed without a release)
- **Tasks:** 4 executed, plus the release decision (Task 5, answered: no tag in this plan). Task 6 (the release) was deferred.
- **Files:** 36 touched by the plan's commits (3,285 + 1,133 + 589 insertions in the three code commits)
## Accomplishments
- **Task 1 (D-24):** the decision note `.planning/notes/2026-10-22-mcp-go-sdk.md` records the transitive graph, size, govulncheck result and build-tag containment. STACK.md gained a row for each module. The user approved both modules before `go get`.
- **Task 2 (tracer):** `modules/pier` contains the Host contract, the `mcp.*` config, the catalog compiled from `pact.HasMCP` and per-request identity through the published verifier and resolvers. It also has the built-in backend guard provider, in-process dispatch with `bouncer.WithDispatch`, a per-identity SDK server with a per-request re-filter, the stateful session router over a memory store and audit lines. `modules/surf/pier_on.go` sets the `mountPier` hook. `TestPierTracerSmoke` lists and calls `acme_echo` end to end.
- **Task 3:** `store_redis.go` stores records with PX TTLs, keeps per-binding sets and carries pub/sub events, under cooler's prefix and opened lazily. `events.go` republishes `pact.MCPSubjectChanged` through the store, so every instance sends list_changed or ends the subject's sessions. `stateless.go` serves 2026-07-28 clients. `prm.go` serves the metadata at both well-known paths, and long GET streams re-verify their bearer every minute. These are covered by `TestPierSessionsSmoke`, `TestPierFanOutSmoke` and `TestPierStatelessSmoke` on miniredis.
- **Task 4:** personal tokens are accepted only when `mcp.allow_personal_tokens` is on. An admin token gives a backend-only identity with its scope limit, and a user token gives a frontend-only one. The read/write ceiling, prompts and the audit line are asserted in the tracer's subtests. This task also added `docs/services/mcp.md` with the tested `acme.blog` example, the site.yaml entry and cross-links from the authentication, registration and surf pages.
- Verified again at close: `go test -count=1 -short ./modules/pier/` passes all five tests (`TestExampleBlogPlugin`, `TestPierSessionsSmoke`, `TestPierFanOutSmoke`, `TestPierStatelessSmoke`, `TestPierTracerSmoke`), and `go build -tags summercms_mcp ./...` succeeds.
## Task Commits
1. **Task 1: Decide the MCP Go SDK** - `3ea82a1` (docs)
2. **Task 2: pier tracer** - `cfdbaf0` (feat)
3. **Task 3: Redis sessions, events, stateless path, PRM** - `806e87c` (feat)
4. **Task 4: Identity kinds, ceilings, prompts, MCP services page, READMEs** - `8c6c0a3` (feat), plus `1aff96f` (docs: deferred items)
5. **Task 5: Release decision** - answered by the user, no commit (see below)
6. **Task 6: Release** - deferred, no commit
**Commit count note:** `commits: 29` is measured as `git rev-list --count 3746675..a26ee2b`. That range also contains 24 commits from concurrent sessions (msd-quick tasks 261022-k9k, -tb4, -tto, -usi, -vfz, -wah, -x7a and their planning docs). This plan's own commits are the five listed above.
## Release (Tasks 5 and 6): deferred
The user's final answer to Task 5 replaces the earlier v0.5.0 answer: **no tag in this plan.**
- The pier code ships **opt-in in framework v0.4.4**. To use it, an application is built with `-tags summercms_mcp` and sets `mcp.enabled: true` (the default is off). Without the tag the binary carries no go-sdk. The orchestrator handles v0.4.4 outside this plan, after the plan closes.
- **v0.5.0 is reserved for phase 19.7 completion** (plans 01-05 and phase verification, i.e. full MCP usability).
- This plan did not tag, push, post a changelog or edit the decision note's "Released in" line. The plan's D-31 truth and the Task 6 acceptance criteria (tag on origin, changelog URL) are therefore not met here. The release is tracked by the 19.7 decisions in STATE.md.
- The Docker-host gate from the plan's verification (`go test -count=1 ./...` and `bash scripts/check-phase19.6.1.sh --all` before the tag) has to run before the v0.4.4 tag. It was not run in this close.
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 3 - Blocking] The tracer client is pinned to protocol 2025-11-25**
- **Found during:** Task 2
- **Issue:** an SDK client left at its default negotiates the newest protocol, which pier sends to the stateless path. The tracer has to exercise the stateful path with sessions.
- **Fix:** `client.Connect(..., &mcp.ClientSessionOptions{ProtocolVersion: "2025-11-25"})` in `TestPierTracerSmoke`, and `connectPinned` for the identity subtests
- **Files modified:** modules/pier/tracer_smoke_test.go, modules/pier/identity_smoke_test.go
- **Committed in:** cfdbaf0, 8c6c0a3
**2. [Rule 1 - Bug] SDK client sessions are closed by defer before the test server closes**
- **Found during:** Task 2
- **Issue:** if `httptest.Server.Close` runs while an SDK session's GET stream is still open, it blocks waiting on the hanging connection
- **Fix:** `defer cs.Close()` is registered after `defer srv.Close()`, so the session closes first (LIFO)
- **Files modified:** modules/pier/tracer_smoke_test.go
- **Committed in:** cfdbaf0
**3. [Rule 3 - Blocking] `TestExampleBlogPlugin` calls `blogTools()`**
- **Found during:** Task 4
- **Issue:** the docs example has to be a compiled, tested declaration that `docs/services/mcp.md` can copy through `src=`
- **Fix:** the declaration lives in `blogTools()`. The example plugin's `MCP()` returns it, and the test calls it directly to assert 3 tools and 1 prompt before mounting the plugin.
- **Files modified:** modules/pier/example_mcp_test.go
- **Committed in:** 8c6c0a3
**4. [Rule 3 - Blocking] `*pier.Pier` is published on the app as a test seam**
- **Found during:** Task 4
- **Issue:** the identity subtests need to inject a personal-token resolver and reach the mounted instance. The cabana resolver needs a database, which is not available under `-short`.
- **Fix:** `Mount` publishes the `*pier.Pier` on the application, and `export_test.go` exposes the unexported seams to tests only
- **Files modified:** modules/pier/pier.go, modules/pier/export_test.go
- **Committed in:** 8c6c0a3
---
**Total deviations:** 4 auto-fixed (1 bug, 3 blocking). The release (Task 6) was deferred by the user's decision.
**Impact on plan:** the code scope is unchanged. The release moved out of the plan.
## Issues Encountered
None beyond the deviations and the deferred items below.
## Follow-ups (from deferred-items.md)
- **open:** stateful pier sessions cannot carry the SDK's protocol-version context value. go-sdk v1.8.0 keeps it under an unexported key, so stateful streams run with the 2025-03-26 defaults for close/reconnect hints and JSON-RPC batching. Revisit if the SDK exports the key. Plan 05 may pin the behaviour in a test.
- **open:** `notifications/initialized` can land on a different instance than `initialize`. The first instance's local copy then refuses calls until it is dropped. Sticky sessions or a per-request re-read of the stored `initialized` flag would fix this. It does not affect a single-instance deployment.
- **open (from 19.7-01):** pre-existing `gofmt` drift in six cabana files. The fix is a separate `style(cabana)` commit.
- Before any framework tag: run `bash scripts/check-phase19.6.1.sh --all` and the full Docker `go test -count=1 ./...` on the Docker host.
## Known Stubs
None. The release is deferred by decision; it is not a stub.
## User Setup Required
None for this plan. Applications that want MCP build with `-tags summercms_mcp` and set `mcp.enabled: true` (see `docs/services/mcp.md`).
## Next Phase Readiness
- **Plan 03 (sm-user-plugin):** publish `pact.MCPLinkedBackendResolver` and `pact.MCPPersonalTokenResolver`, frontend permissions, and the link and unlink record actions. pier picks them up through `app.Lookup`.
- **Plan 04 (oauthserver):** publish `pact.MCPTokenVerifier` and add `backend` to `ScopesSupported`. The PRM `resource` must match the verifier's resource.
- **Plan 05:** unit tests and security review, including the two open pier items above and the subject-event listener assertions carried over from 01.
- **Framework version for plans 03 and 04:** v0.4.4 (pier opt-in), once the orchestrator tags it. v0.5.0 follows at phase completion.
---
*Phase: 19.7-generic-mcp-module*
*Completed: 2026-10-23*
## Self-Check: PASSED
All key created files exist on disk (pier sources, surf/pier_on.go, docs/services/mcp.md, the pier README, the decision note) and the five plan commits (3ea82a1, cfdbaf0, 806e87c, 8c6c0a3, 1aff96f) are in history. `go test -count=1 -short ./modules/pier/` and `go build -tags summercms_mcp ./...` pass at a26ee2b.