Skip to content

Commit 0ae6542

Browse files
committed
chore(changeset): patch @objectstack/mcp for the Account-app Connect-an-Agent entry
Claude-Session: https://claude.ai/code/session_01TSf4DV7ziu4V5j73e46b7c Co-authored-by: Claude <noreply@anthropic.com>
1 parent 5bfe2e6 commit 0ae6542

1 file changed

Lines changed: 50 additions & 0 deletions

File tree

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
---
2+
'@objectstack/mcp': patch
3+
---
4+
5+
Connect an Agent is reachable from the Account app, so a non-admin can mint their own key
6+
7+
`POST /api/v1/keys` mints a `sys_api_key` bound to the **caller**, and the
8+
Connect-an-Agent page says the key "acts as you". But the page's only navigation
9+
entry sat in the Setup app, which declares `requiredPermissions:
10+
['setup.access']` — so every non-admin following the shipped two-step guide, and
11+
every reader of the runtime's own error text (`packages/mcp/src/plugin.ts`:
12+
*"mint an API key (Setup → Connect an Agent, or POST /api/v1/keys)"*, and
13+
`README.md`), stopped at step 1 while the endpoint behind the button had accepted
14+
them all along. Measured before: a principal with no system permissions gets
15+
`403 PERMISSION_DENIED` on `GET /api/v1/meta/apps/setup` and `nav_connect_agent`
16+
is absent from the wire.
17+
18+
`CONNECT_AGENT_UI_BUNDLE` now carries a **second** `navigationContributions`
19+
entry, targeting the `account` app's `grp_account_developer` group beside the
20+
`nav_account_api_keys` entry already shipping there. Measured after, over the
21+
real composition (real `SETUP_APP` / `ACCOUNT_APP` / `SETUP_NAV_CONTRIBUTIONS`,
22+
the real fold and the real RBAC-by-route filter): the same permissionless
23+
principal gets `200` on `GET /api/v1/meta/apps/account` with
24+
`grp_account_developer` carrying `['nav_account_api_keys',
25+
'nav_account_oauth_apps', 'nav_connect_agent']`, while `apps/setup` still
26+
answers `403 PERMISSION_DENIED` with `connect_agent` absent from that body.
27+
28+
**Nothing else moves.** No backend change, no authorization change, no change to
29+
which permissions exist, and the published "acts as you" promise is unchanged —
30+
it simply becomes keepable for the users it was written for. The Setup entry
31+
stays exactly as it was, so admins keep the page where the guide points, and no
32+
gate is added or removed anywhere: a navigation contribution registers exactly
33+
when the page registers, so an opted-out deployment
34+
(`OS_MCP_SERVER_ENABLED=false`) still gets no page and neither entry.
35+
36+
⛔ Ungating Setup was **not** the fix, and was measured rather than assumed: the
37+
app-level `setup.access` gate fires before the group gate, so dropping the group
38+
gate alone changes nothing, and dropping both serves 14+ unrelated Setup
39+
surfaces (Users, Organization, Business Units, Branding, Feature Flags, …) to
40+
every signed-in user. ⛔ Nor was a `requiresService: 'mcp'` gate on an
41+
`account.app.ts` entry: the `mcp` service registers unconditionally in `init()`
42+
while this bundle registers behind `isMcpServerEnabled()`, so such an entry
43+
would outlive its page and 404 for every signed-in user on an opted-out
44+
deployment.
45+
46+
Both entries deliberately share the item id `nav_connect_agent` — one
47+
destination, one identity. That is scoped, not a collision: `SchemaRegistry`
48+
keys contributions by target app and `applyNavContributions(app)` consults only
49+
that app's bucket, so a nav item id is unique within one app's navigation tree,
50+
and the translation bundles are keyed `apps.<app>.navigation.<id>`.

0 commit comments

Comments
 (0)