Skip to content

Commit 2a1ad99

Browse files
committed
docs(ai): an OAuth agent acts in the organization its consent bound
agents.mdx states the one organization a connected agent acts in, that membership is re-checked per request under an organization wall, and that a former member's agent gets an empty 200 read under isolated tenancy. Plus the runtime changeset. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QyoWKK9RwUNxBxmevy9Neb
1 parent 7702d27 commit 2a1ad99

2 files changed

Lines changed: 26 additions & 1 deletion

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@objectstack/runtime': patch
3+
---
4+
5+
fix(runtime): an OAuth MCP agent acts in the organization the user's consent bound, so the user's organization-scoped grants count for it
6+
7+
Clause-②: no
8+
9+
- **What was wrong.** On the `/mcp` door, `resolveExecutionContext` built a verified OAuth access token's principal with no organization. The agent's delegator (the user it acts for, ADR-0090 D10) was then resolved with no active organization and held only its global grants. Every grant an administrator assigns is scoped to an organization, so an ordinary user's agent was refused every object operation: `on-behalf-of principal may not 'find' … the delegator lacks that grant (ADR-0090 D10 intersection)`.
10+
- **What changed.** The principal is resolved in the organization the consent bound: the `organizationId` that `IAuthService.verifyMcpAccessToken` returns from the token's `organization_id` claim. It is handed to the shared resolver where a session carries its active organization, so it is vetted on every request by the rule a browser session's organization gets (`vetOrganizationClaim`, `@objectstack/core`). The user's grants scoped to that organization count for the agent; grants scoped to the user's other organizations do not. It is never the union of the user's organizations, never the organization the user's browser session has selected now, and never a widened global grant.
11+
- **A membership that is gone.** Under a walled tenancy posture (`isolated`, `group`), a token whose organization the user no longer belongs to resolves with no organization, so only global grants apply. Under `isolated`, a former member's agent gets an empty `200` read and a refused tenant write. The same holds for an organization the user never belonged to, whatever put it on the token. Under `single`, or with no `tenancy` service, the organization stands as claimed, exactly as a browser session's does there.
12+
- **Unchanged.** A token with no `organization_id` (minted before consents bound one, or by a user with no active organization at consent) resolves with no organization, as before: global grants only, until the user re-confirms the consent once. A platform admin's global grant applies as before. The API-key and session tracks are untouched.

‎content/docs/ai/agents.mdx‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,20 @@ SKILL.md download (`GET /api/v1/mcp/skill`), and API-key minting.
6565
private deployments work out of the box**. You authorize the client in the
6666
browser; it then acts as an **agent on your behalf** (`principalKind: 'agent'`),
6767
its **data access** bounded by the **intersection** of the consent scopes and
68-
your own permissions/RLS — it can never exceed either. For object CRUD the
68+
your own permissions/RLS — it can never exceed either. The agent acts in
69+
**one organization**: the one you had selected when you consented, carried
70+
in the token as its `organization_id` claim. Your grants scoped to that
71+
organization count for it; grants scoped to your other organizations do not
72+
(select the other organization and authorize the client again to work
73+
there). Your membership is re-checked on every request by the rule a
74+
browser session's organization gets: under an organization wall (`isolated`
75+
or `group` tenancy) a token whose organization you no longer belong to falls
76+
back to **no organization**, so only your global grants apply — under
77+
`isolated`, a former member's agent gets an **empty `200`** on reads and a
78+
refusal on writes to organization data. Under `single` (no wall) the
79+
organization stands as consented, as a session's does. A token issued
80+
before consents carried an organization holds your global grants only until
81+
you re-confirm the consent once. For object CRUD the
6982
consent scopes (`data:read`, `data:write`) are a real ceiling, not just a
7083
tool-family filter: they narrow the exposed tools **and** cap what the agent
7184
can do at the data layer — a `data:read` token can never write a record even

0 commit comments

Comments
 (0)