diff --git a/docker/witan.Dockerfile b/docker/witan.Dockerfile index bebf1a0b..c53358d3 100644 --- a/docker/witan.Dockerfile +++ b/docker/witan.Dockerfile @@ -149,5 +149,5 @@ ENTRYPOINT ["witan"] # deployed invocation and keeps the image runnable standalone. The transport # serves both protocol eras: a 2026-07-28 client is answered statelessly (no # handshake, no Mcp-Session-Id, so replicas need no session affinity), an older -# one still gets the handshake. See mcp/servers/witan/docs/adr/0006. +# one still gets the handshake. See mcp/servers/witan/docs/adr/0009. CMD ["serve", "--transport", "streamable-http", "--host", "0.0.0.0", "--port", "8000"] diff --git a/mcp/servers/witan/CHANGELOG.md b/mcp/servers/witan/CHANGELOG.md index 8bab006e..400335e7 100644 --- a/mcp/servers/witan/CHANGELOG.md +++ b/mcp/servers/witan/CHANGELOG.md @@ -1221,7 +1221,8 @@ unchanged; this is what has to be true before it can move. ### Added -- **ADR-0006** (`docs/adr/0006-stateless-mcp-protocol-era.md`) records the move +- **ADR-0009** (`docs/adr/0009-stateless-mcp-protocol-era.md`, numbered 0006 at + the time) records the move to the stateless 2026-07-28 era: what it unlocks (multi-replica behind a plain round-robin LB, no session affinity), the two pieces of state that are still per-replica, and why the fastmcp 3.4.x/4.x straddle stays until 4.0 GA. diff --git a/mcp/servers/witan/docs/adr/0004-keycloak-jwt-per-user-actor-mapping.md b/mcp/servers/witan/docs/adr/0004-keycloak-jwt-per-user-actor-mapping.md index 7fb31378..36cdd325 100644 --- a/mcp/servers/witan/docs/adr/0004-keycloak-jwt-per-user-actor-mapping.md +++ b/mcp/servers/witan/docs/adr/0004-keycloak-jwt-per-user-actor-mapping.md @@ -9,7 +9,7 @@ `witan-users` group); ol-infrastructure `docs/adr/0009-deploy-witan-as-shared-multi-tenant-mcp-service.md`; `tk-ol-infrastructure-toolhive-witan-pulumi-stack-e843b3`; - `docs/adr/0006-stateless-mcp-protocol-era.md` (the JWT→actor mapping below is + `docs/adr/0009-stateless-mcp-protocol-era.md` (the JWT→actor mapping below is unchanged by the stateless era — it reads the token on each request and never depended on session state — but the `streamable-http` connection it describes no longer carries a handshake or a session id) diff --git a/mcp/servers/witan/docs/adr/0005-secure-cli-path-into-deployed-witan.md b/mcp/servers/witan/docs/adr/0005-secure-cli-path-into-deployed-witan.md index 9018cdd1..6e4c50e9 100644 --- a/mcp/servers/witan/docs/adr/0005-secure-cli-path-into-deployed-witan.md +++ b/mcp/servers/witan/docs/adr/0005-secure-cli-path-into-deployed-witan.md @@ -9,7 +9,7 @@ server-side JWT→actor→token mapping this reuses); ol-infrastructure `docs/adr/0009-deploy-witan-as-shared-multi-tenant-mcp-service.md` (ClusterIP-only omnigraph-server, the `svc-witan-admin` sketch); - `docs/adr/0006-stateless-mcp-protocol-era.md` (the 2026-07-28 era this path + `docs/adr/0009-stateless-mcp-protocol-era.md` (the 2026-07-28 era this path now runs on — read it alongside every `streamable-http` reference below, which describes the handshake-era shape) @@ -247,6 +247,6 @@ already actor-resolving. - **Amendment (2026-07-30):** largely moot against a 2026-07-28 deployment. That era has no `initialize` handshake and no session id, so a fresh connection per call costs a connection, not a negotiation — see - `docs/adr/0006-stateless-mcp-protocol-era.md`. The proxy also gained an + `docs/adr/0009-stateless-mcp-protocol-era.md`. The proxy also gained an elicitation handler, so a prompt the deployment raises now reaches the human at the terminal instead of degrading to the tool's default. diff --git a/mcp/servers/witan/docs/adr/0004-optional-task-phase-tag.md b/mcp/servers/witan/docs/adr/0008-optional-task-phase-tag.md similarity index 99% rename from mcp/servers/witan/docs/adr/0004-optional-task-phase-tag.md rename to mcp/servers/witan/docs/adr/0008-optional-task-phase-tag.md index 55dd3f6e..77dc0718 100644 --- a/mcp/servers/witan/docs/adr/0004-optional-task-phase-tag.md +++ b/mcp/servers/witan/docs/adr/0008-optional-task-phase-tag.md @@ -1,4 +1,4 @@ -# 4. Optional task `phase` field + per-phase ready-work rollup +# 8. Optional task `phase` field + per-phase ready-work rollup > "Phase tag" is used loosely in the tracking task title; the decision below is a > typed, optional `phase` **enum field** on `Task` (not a free-form tag) — see diff --git a/mcp/servers/witan/docs/adr/0006-stateless-mcp-protocol-era.md b/mcp/servers/witan/docs/adr/0009-stateless-mcp-protocol-era.md similarity index 99% rename from mcp/servers/witan/docs/adr/0006-stateless-mcp-protocol-era.md rename to mcp/servers/witan/docs/adr/0009-stateless-mcp-protocol-era.md index e7165e44..7efb0152 100644 --- a/mcp/servers/witan/docs/adr/0006-stateless-mcp-protocol-era.md +++ b/mcp/servers/witan/docs/adr/0009-stateless-mcp-protocol-era.md @@ -1,4 +1,4 @@ -# 6. Serving the stateless MCP protocol era (2026-07-28) +# 9. Serving the stateless MCP protocol era (2026-07-28) - Status: Accepted - Date: 2026-07-30 diff --git a/mcp/servers/witan/witan/config.py b/mcp/servers/witan/witan/config.py index c2be7f46..00c17f9a 100644 --- a/mcp/servers/witan/witan/config.py +++ b/mcp/servers/witan/witan/config.py @@ -143,7 +143,7 @@ class IdentityConfig(BaseModel): Sourced entirely from ``WITAN_OIDC_*``/``WITAN_ACTOR_TOKENS_FILE`` env vars — this is deployment/ops config for the shared ``streamable-http`` service, not something an individual local user sets in config.toml. - Unaffected by the stateless 2026-07-28 era (ADR-0006): the mapping reads + Unaffected by the stateless 2026-07-28 era (ADR-0009): the mapping reads the JWT on every request and never depended on session state. ``oidc_issuer`` unset means the deployed-auth path is disabled entirely (local ``stdio`` usage never sets it). diff --git a/mcp/servers/witan/witan/remote/__init__.py b/mcp/servers/witan/witan/remote/__init__.py index acd908aa..f19c30bb 100644 --- a/mcp/servers/witan/witan/remote/__init__.py +++ b/mcp/servers/witan/witan/remote/__init__.py @@ -6,7 +6,7 @@ instead of the in-process static-token fallback that only works locally. Against a 2026-07-28 deployment each call is a self-contained request: no handshake, no session id, and any session context the server needs travels as a -tool argument (ADR-0006). +tool argument (ADR-0009). - :mod:`witan.remote.oidc` — OIDC device-authorization-grant login + token cache. - :mod:`witan.remote.proxy` — :class:`RemoteServerProxy`, a drop-in stand-in diff --git a/packages/witan-core/witan_core/remote/__init__.py b/packages/witan-core/witan_core/remote/__init__.py index 67860b8d..bd00c44b 100644 --- a/packages/witan-core/witan_core/remote/__init__.py +++ b/packages/witan-core/witan_core/remote/__init__.py @@ -4,7 +4,7 @@ self over ``streamable-http`` with a per-user OIDC identity (ADR-0005, path a), factored out of witan-council so a second server (e.g. a deployed witan-code) can reuse it instead of copy-pasting. On MCP 2026-07-28 that transport carries -no handshake and no session id, so every call stands alone (witan ADR-0006): +no handshake and no session id, so every call stands alone (witan ADR-0009): - :mod:`witan_core.remote.config` — :class:`~witan_core.remote.config.RemoteConfig`, which deployment to talk to and how to authenticate to it, resolved from the diff --git a/packages/witan-core/witan_core/remote/config.py b/packages/witan-core/witan_core/remote/config.py index 3ff70101..ca5626ac 100644 --- a/packages/witan-core/witan_core/remote/config.py +++ b/packages/witan-core/witan_core/remote/config.py @@ -44,7 +44,7 @@ class RemoteConfig: through the deployment over ``streamable-http``, authenticated with a per-user Keycloak JWT (device-code flow, :mod:`witan_core.remote.oidc`). Against a 2026-07-28 deployment that connection is stateless — no - handshake, no session id (witan ADR-0006). + handshake, no session id (witan ADR-0009). These name the *client's* view of the deployment and are deliberately separate from the server-side identity config (``WITAN_ACTOR_TOKENS_FILE``