fix(pier): end only the revoked token's or client's MCP sessions and log the client address (B-WR-04, B-IN-04)
- pact.MCPSubjectChanged and pier.Event gain TokenID and ClientID; a narrowed revoked event closes only matching sessions and stateless servers - cabana fires the admin token revoke with the token row id - towel.WithClientIP/ClientIP; pier puts the address (surf applies http.trusted_proxies) on the resolve context and ResolveBackendToken logs it
This commit is contained in:
@@ -132,7 +132,7 @@ The `read` and `write` scopes are a ceiling: a grant with only `read` sees tools
|
||||
|
||||
With `mcp.allow_personal_tokens` true, personal API tokens also open sessions, each capped by the token's own scope exactly as over HTTP: an administrator's `g15_` token yields backend tools only, with the token's scope limit computed per dispatched route method; a frontend user's token, resolved by the user plugin's published `pact.MCPPersonalTokenResolver`, yields frontend tools only. An application has one such resolver; a subject it answers must carry the token's `TokenID`, which keys the session binding, and a subject without one is refused. With the flag off (the default), any bearer that is not an OAuth token is refused without a lookup.
|
||||
|
||||
Sessions follow the streamable HTTP transport: `initialize` opens one, `Mcp-Session-Id` names it, DELETE ends it. A session is bound to the client and user that opened it (or to the personal token), so it survives access-token rotation, and another identity presenting its id is refused with 403. Sessions carry a sliding idle TTL and a cap per binding; a session whose client went away without DELETE is closed on every instance once its idle TTL passes. When the same client and user come back with a wider grant (more scopes, the backend scope with a link), the session gains the newly allowed tools on that request. With `mcp.sessions.store: redis` several instances share them: a session opened on one instance is served by another, which rehydrates it from the stored session state, and a permission change, a link change or a revocation fired on any instance reaches every session it concerns, as `notifications/tools/list_changed` or as the end of the session. Clients of protocol version 2026-07-28 and later, which have no sessions, are served statelessly from the same path with the same identity, dispatch and filtering.
|
||||
Sessions follow the streamable HTTP transport: `initialize` opens one, `Mcp-Session-Id` names it, DELETE ends it. A session is bound to the client and user that opened it (or to the personal token), so it survives access-token rotation, and another identity presenting its id is refused with 403. Sessions carry a sliding idle TTL and a cap per binding; a session whose client went away without DELETE is closed on every instance once its idle TTL passes. When the same client and user come back with a wider grant (more scopes, the backend scope with a link), the session gains the newly allowed tools on that request. With `mcp.sessions.store: redis` several instances share them: a session opened on one instance is served by another, which rehydrates it from the stored session state, and a permission change, a link change or a revocation fired on any instance reaches every session it concerns, as `notifications/tools/list_changed` or as the end of the session. A revocation can be narrowed: a `pact.MCPSubjectChanged` with `TokenID` ends only the sessions that personal token opened (revoking one admin token on My account does this), one with `ClientID` only the sessions of that OAuth client, and one with neither every session of the user. Clients of protocol version 2026-07-28 and later, which have no sessions, are served statelessly from the same path with the same identity, dispatch and filtering.
|
||||
|
||||
## Protected resource metadata
|
||||
|
||||
|
||||
Reference in New Issue
Block a user