Skip to content

Commit 78eb352

Browse files
feat(console): put Connect an AI Assistant where each person looks, and call it that (#30935)
<!-- claude-projects-attribution: {"github_login":"os-tesla"} --> _Requested by **Tesla** · [project thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6Jao?thread=cmsg_01AXsQkLFKRdQCqQ3RTr6JaoGdsEvvwWcprHpSwjPsjxFN)_ Part of #30895: Expected 1 (avatar menu item), 4 (Account group of its own, Setup top group) and 5 (rename). Expected 2 (Home card) and 3 (first-run checklist row) need "has this person connected anything", which #30894 provides; they follow in a second PR. Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:「连接智能体」改名为「连接 AI 助手」(英/中/日/西四种语言的页面标题、页头和两个菜单入口);头像菜单在「个人资料」下新增「连接 AI 助手」,每个登录用户都能看到;账户应用里它不再藏在默认折叠的「开发者」组,而是独立成「AI 助手」组并默认展开;系统设置里从倒数第二组「集成」移到最上面的「总览」组、系统概览之下。 - 为什么改:这是每个人自己把 Claude、豆包、Cursor 等 AI 助手接入系统的入口,原来成员找不到、管理员要翻到数据源和连接器之间;「智能体」又和 Studio 里自建的智能体撞名。 - 风险与回滚:只改菜单位置、名称和头像菜单一项,页面地址 `page/connect_agent` 与菜单项 id 不变,书签照用;可见范围不变(系统设置两组都要 `manage_platform_settings`);关闭 MCP 时三个入口都不出现。回滚即 revert 本 PR。 - 先看:`packages/mcp/src/connect-ui.ts`(两个菜单贡献的目标组),`packages/platform-objects/src/apps/account.app.ts`(新的「AI 助手」空槽组),`ui/packages/app-shell/src/layout/AppHeader.tsx`(头像菜单项及其按页面是否下发的门控)。 - 两分钟验证:以普通成员登录控制台,点右上角头像,应看到「连接 AI 助手」,点开即连接页;进入账户应用,侧栏「收件箱」下方是展开的「AI 助手」组。 - 需要你做的:无,席位落地。 - **席位意见**:ui 席 2 对照 head `34e84800` 的 diff 复核:38 个文件、+299 / −104,无受治理路径。改名只动标签与页头,页面地址 `page/connect_agent` 与菜单项 id 不变;账户应用新增空槽组 `grp_account_ai_assistant`(默认展开),系统设置入口移入 `group_overview`(同一 `manage_platform_settings` 门槛,可见范围不变);头像菜单项仅在下发的账户应用导航里有该页时出现,关闭 MCP 时三个入口都不出现。席位核对时发现新增声明把 `isTypingTarget` 的文档注释隔开,已在 `34e84800` 改回,此外无改动。席位授权的文案扩面(mcp、runtime、五个文档页)只改名称与路径字样。CI 65 项全绿,Clause-② no,未改 `packages/spec/src`,不欠契约复审。同意落地。 Surface extended by the seat (wording only, the path and name words this move made false): `packages/mcp/src/plugin.ts`, `packages/runtime/src/domains/keys.ts`, `packages/mcp/server.json`, `content/docs/ai/connect-mcp.mdx`, `content/docs/api/index.mdx`, `content/docs/getting-started/build-with-claude-code.mdx`, `content/docs/ai/agents.mdx`, `content/docs/deployment/environment-variables.mdx`, `packages/mcp/README.md`. Also comment-only: `packages/cloud-connection/src/cloud-connection-ui.ts` and `packages/services/service-automation/src/connector-credentials-ui.ts` list the Integrations group's other priorities in their headers and no longer name Connect an AI Assistant there. ## What changes - **Name.** The `connect_agent` page, its `page:header` title and both nav entries are called Connect an AI Assistant. The platform-objects bundles carry it in all four locales (`连接 AI 助手`, `AI アシスタントを接続`, `Conectar un asistente de IA`): `apps.account.navigation.nav_connect_agent`, `apps.setup.navigation.nav_connect_agent` and `pages.connect_agent.label`. The three re-translated leaves record their new source digests in each locale's `source-hashes.ts`, so none is served as stale. - **Account.** `ACCOUNT_APP` declares `grp_account_ai_assistant` (AI Assistant, `expanded: true`, `children: []`) between Inbox and Security, and the MCP bundle's account contribution targets it instead of `grp_account_developer`. The group label is translated in all four locales. The slot is empty in the app definition, so with MCP off the nav filter drops it, as it drops Setup's empty slots. - **Setup.** The MCP bundle's setup contribution targets `group_overview` (priority 110, behind System Overview at 100) instead of `group_integrations`. Both groups carry `manage_platform_settings`, so who sees the entry is unchanged. - **Avatar menu.** `AppHeader` draws a Connect an AI Assistant item under Profile. It opens `/apps/ACCOUNT_ADDRESS/page/connect_agent`, where the segment is the Account app's one address (`appAddressSegment`). The item is drawn only while the served Account app's navigation carries a `page` entry for `connect_agent`, so it lives and dies with the MCP bundle like the two menu entries. The label is `user.connectAiAssistant`, added to all ten console locales. - **Path text.** The stdio refusal (`plugin.ts`), the API-key session refusal (`keys.ts`), `server.json`, the mcp README and five docs pages name the new page and paths. Nothing else on those pages changes. - Nothing in the top bar. The page address and the nav item id are unchanged. ## Why Connecting an AI client is something every person does for themselves (each call runs as the caller), and the maintainer called it the platform's most central feature. On `main` a member had to open the collapsed Developer group in Account to find it, and an administrator found it near the bottom of Setup among datasource and connector screens. "Agent" also reads as one of the agents built in Studio. ## Tests - `packages/mcp/src/connect-agent-account-nav.test.ts`: the account contribution targets `grp_account_ai_assistant`; the setup contribution targets `group_overview` at priority 110; new case: the page label, the `page:header` title and both entries are Connect an AI Assistant. - `packages/cli/test/connect-agent-both-halves-wire.pin.test.ts` (the composed wire pin): a permissionless caller gets `grp_account_ai_assistant` = `['nav_connect_agent']` and Developer back to its two authored entries, 13 account nav ids; the admin's Setup has `group_overview` = `['nav_system_overview', 'nav_connect_agent']`. The Setup count moves 32 to 31 because `group_integrations` held only this entry in that composition and is now dropped as empty; the anti-widening control still compares the admin tree with and without the account half. - `packages/cli/test/setup-integrations-order.pin.test.ts`: the Integrations tiers drop `nav_connect_agent`. The MCP bundle is still read, so the "nothing contributes into the group that this pin does not order" check reds if the entry moves back. - `AppHeader.hiddenAppAddress-29024.test.tsx` (the avatar-menu harness with a settable app list), new top-level describe, 4 cases: opens `/apps/com.objectstack.account/page/connect_agent`; is labelled Connect an AI Assistant; draws nothing when the served Account app lacks the page; draws nothing when no Account app is served. <details><summary>Ablation (head 0264ecd)</summary> `node scripts/ablation-replace.mjs` on `AppHeader.tsx`, anchor `accountApp && navCarriesPage((accountApp as any).navigation, CONNECT_ASSISTANT_PAGE)` replaced by `accountApp && /* ABLATED */ true` (the page gate removed), against `AppHeader.hiddenAppAddress-29024.test.tsx`: anchor 1 to 0, `1 failed | 7 passed` (the MCP-off case red), then `ok restored: blob == HEAD`. </details> ## Acceptance notes - Measured live on `main` `7d9682ff` and on this branch (showcase, `objectstack dev --fresh`, Console from `ui/` src via Vite), as `admin@objectos.ai` and as a member created by the admin (`chen@example.test`, no Setup access): | Who | Path | Before | After | |:--|:--|:--|:--| | Admin | Setup | Integrations (8th of 9 groups), entry 42 of 45 | Overview (1st group), entry 2 of 45, under System Overview | | Admin, member | Account | inside Developer, collapsed by default | AI Assistant group, expanded, right after Inbox | | Admin, member | Avatar menu | Profile, My Organizations, Log out | Profile, Connect an AI Assistant, My Organizations, Log out; it opens the page, header "Connect an AI Assistant" | | Member | Setup | "You don't have access to this app" | unchanged | In zh the avatar item, the Account group and entry, the Setup entry and the page header all read 连接 AI 助手 / AI 助手. The showcase item count (45) differs from the card's 40 because the showcase composes more Setup entries. - The Console's widget registry label in `ConnectAgentWidget.tsx` (`label: 'Connect an Agent'`, a palette label for the `mcp:connect-agent` type) is left as it is: the order puts that file off limits while #30894 works there. The Integrations page's own "Connect an AI agent (MCP)" card (`AgentConnectSection`, console i18n `connectSection` and friends) is a different page and keeps its copy. - Resolver and extractor test fixtures that use "Connect an Agent" / 连接智能体 as sample data (`packages/spec` i18n-resolver tests, `packages/rest` tests, `packages/cli` i18n-extract test, the `check-app-nav-i18n` self-test fixture) are left unchanged: they test the mechanism, not the shipped label. - Released `CHANGELOG.md` entries and `content/docs/releases/` keep the old name, as release-owned history. ## Verification <details><summary>Local gates and tests (head 0264ecd)</summary> Derived with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` against this diff, each run with stdin closed; reconciled with `--ran`: 100 derived, 100 run, 0 NOT-MEASURED, 0 UNRUN. `check-plugin-teardown-shape.mjs --self-test` first exited 3 because the shallow clone lacked its pinned commit; after `git fetch origin 621a487 --depth=1` it exits 0 (48 cases). Outside the derived list: `pnpm check:app-nav-i18n` (its script's comments changed) exit 0, `setup: 56 merged nav id(s), account: 13`; `pnpm check:changeset-fixed` exit 0; `ui` `check:new-line-citations` 0 new. ```text node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0 node scripts/check-adr-0087-registration.mjs --self-test :: exit 0 node scripts/check-adr-links.mjs :: exit 0 node scripts/check-adr-links.mjs --self-test :: exit 0 node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0 node scripts/check-closing-keyword-parity.mjs :: exit 0 node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0 node scripts/check-comment-mask-adoption.mjs :: exit 0 node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0 node scripts/check-comment-mask-corpus.mjs :: exit 0 node scripts/check-doc-route-spelling.mjs --advisory :: exit 0 node scripts/check-doc-route-spelling.mjs --self-test :: exit 0 node scripts/check-docs-section-name.mjs :: exit 0 node scripts/check-docs-section-name.mjs --self-test :: exit 0 node scripts/check-dts-emitted.mjs --self-test :: exit 0 node scripts/check-empty-changeset.mjs --base origin/main :: exit 0 node scripts/check-empty-changeset.mjs --self-test :: exit 0 node scripts/check-issue-citations.mjs :: exit 0 node scripts/check-plugin-teardown-shape.mjs :: exit 0 node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0 node scripts/check-sdui-manifest.mjs :: exit 0 node scripts/check-sdui-manifest.mjs --self-test :: exit 0 node scripts/check-section-landing-index.mjs :: exit 0 node scripts/check-section-landing-index.mjs --self-test :: exit 0 node scripts/check-tenant-audit-census.mjs :: exit 0 node scripts/check-tenant-audit-census.mjs --self-test :: exit 0 node scripts/ci/assert-turbo-remote-cache.mjs --self-test :: exit 0 node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0 node scripts/ci/prune-turbo-cache.mjs --self-test :: exit 0 node scripts/ci/snapshot-next.mjs --self-test :: exit 0 node scripts/docs-audit/check-affected-docs.mjs :: exit 0 node scripts/docs-audit/check-drift-comment.mjs :: exit 0 node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0 node scripts/release-pending-publish.mjs --self-test :: exit 0 pnpm --dir ui exec vitest run scripts/ :: exit 0 pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0 pnpm --filter @objectstack/lint run check:doc-security-posture :: exit 0 pnpm --filter @objectstack/spec run check:docs :: exit 0 pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0 pnpm --filter @objectstack/spec run check:empty-state :: exit 0 pnpm --filter @objectstack/spec run check:liveness :: exit 0 pnpm --filter @objectstack/spec run check:skill-examples :: exit 0 pnpm --filter @objectstack/spec run check:strictness-ledger :: exit 0 pnpm --filter @objectstack/spec run check:variant-docs :: exit 0 pnpm --filter @objectstack/spec run check:yaml-examples :: exit 0 pnpm check:changeset-gate-self-tests :: exit 0 pnpm check:ci-filter-parity :: exit 0 pnpm check:cli-test-child-env :: exit 0 pnpm check:closed-ceremony-text :: exit 0 pnpm check:corpus-claim-drift :: exit 0 pnpm check:cross-package-test-inputs :: exit 0 pnpm check:dispatcher-error-vocabulary :: exit 0 pnpm check:doc-anchors :: exit 0 pnpm check:doc-authoring :: exit 0 pnpm check:doc-frontmatter :: exit 0 pnpm check:docs-audit-scope :: exit 0 pnpm check:docs-redirects :: exit 0 pnpm check:docs-single-h1 :: exit 0 pnpm check:docs-spec-enumerations :: exit 0 pnpm check:docs-transcript-drift :: exit 0 pnpm check:driver-memory-census :: exit 0 pnpm check:dts-closure :: exit 0 pnpm check:dual-build-cjs-loads :: exit 0 pnpm check:engine-double-contract :: exit 0 pnpm check:error-status-conformance :: exit 0 pnpm check:gitlink-declared :: exit 0 pnpm check:i18n :: exit 0 pnpm check:i18n-stale-fill :: exit 0 pnpm check:issue-citations :: exit 0 pnpm check:keyed-text-bounds :: exit 0 pnpm check:lean-entry-closure :: exit 0 pnpm check:logger-receiver-detach :: exit 0 pnpm check:nul-bytes :: exit 0 pnpm check:objectql-double-limit :: exit 0 pnpm check:org-identifier :: exit 0 pnpm check:package-naming :: exit 0 pnpm check:page-declaration-shape :: exit 0 pnpm check:platform-object-tenancy-census :: exit 0 pnpm check:pnpm-flag-spellings :: exit 0 pnpm check:published-files :: exit 0 pnpm check:published-readme-links :: exit 0 pnpm check:query-options-erasure :: exit 0 pnpm check:react-page-adapter-contract :: exit 0 pnpm check:refd-timer-probe :: exit 0 pnpm check:registry-log-declared :: exit 0 pnpm check:rest-log-spy-declared :: exit 0 pnpm check:role-word :: exit 0 pnpm check:route-envelope :: exit 0 pnpm check:skill-identifier-liveness :: exit 0 pnpm check:slot-lookup :: exit 0 pnpm check:sourcemap-no-sources-content :: exit 0 pnpm check:system-context-census :: exit 0 pnpm check:test-source-alias :: exit 0 pnpm check:tier-file-adoption :: exit 0 pnpm check:type-check-coverage :: exit 0 pnpm check:type-check-debt :: exit 0 pnpm check:undeclared-dep-imports :: exit 0 pnpm check:vendor-version-stamps :: exit 0 pnpm check:watch-hint-literal :: exit 0 pnpm check:where-matcher :: exit 0 ``` Tests (on 0264ecd): - `pnpm --filter @objectstack/mcp --filter @objectstack/platform-objects test`: mcp 56 files / 681 tests passed; platform-objects 117 files / 1591 tests passed. - `pnpm --dir ui exec vitest run packages/i18n/ packages/app-shell/src/layout/`: 237 files passed, 2431 tests passed, 13 skipped. - `packages/cli`: `connect-agent-both-halves-wire.pin.test.ts`, `setup-integrations-order.pin.test.ts`, `platform-page-i18n-parity.test.ts`, `i18n-extract.test.ts`: 4 files passed. - `packages/runtime`: `keys-mint-interactive-session.pin.test.ts`, `keys-actions-method-not-allowed-allow.test.ts`: 21 tests passed. - Type checks: `typecheck` for `@objectstack/mcp`, `platform-objects`, `runtime`, `cloud-connection`, `service-automation`, `cli`, and `type-check` for `@object-ui/i18n`, `@object-ui/app-shell`: all exit 0. </details> <!-- signed: domain:ui#2 · os-tesla · session_01L9qibw7DekEe3wx5f3zphf --> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 616311c commit 78eb352

38 files changed

Lines changed: 299 additions & 104 deletions
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
'@objectstack/mcp': patch
3+
'@objectstack/platform-objects': patch
4+
'@objectstack/runtime': patch
5+
'@object-ui/app-shell': patch
6+
'@object-ui/i18n': patch
7+
---
8+
9+
feat: put Connect an AI Assistant where each person looks, and call it that
10+
11+
Clause-②: no
12+
Metadata-protocol: none
13+
14+
The Connect an Agent page is now Connect an AI Assistant (连接 AI 助手, AI アシスタントを接続, Conectar un asistente de IA): the page title, its header and both of its navigation entries, in every shipped language. "Agent" already names the agents an author builds in Studio, and this page connects the AI assistant a person uses, such as Claude, Doubao or Cursor.
15+
16+
Where to find it:
17+
18+
- **Every signed-in person**: the avatar menu has a Connect an AI Assistant item, under Profile. In the Account app the entry has a group of its own, AI Assistant, open by default, instead of sitting inside the collapsed Developer group.
19+
- **Platform administrators**: in Setup the entry is in the top group, Overview, under System Overview, instead of near the bottom of Integrations. Who sees it does not change: both groups need `manage_platform_settings`.
20+
21+
All three doors appear only where the MCP server is enabled; with `OS_MCP_SERVER_ENABLED=false` the page, both menu entries and the avatar item are absent. The page address (`page/connect_agent`) and the nav item id (`nav_connect_agent`) are unchanged, so bookmarks and links keep working. The stdio start-up refusal and the API-key session refusal name the new page and paths.

‎content/docs/ai/agents.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ architecture.
5353
Every deployment serves MCP at `/api/v1/mcp` by default (a core platform
5454
capability — set `OS_MCP_SERVER_ENABLED=false` to opt out), with two
5555
authentication tracks: The in-product entry point is the
56-
**Connect an Agent** page: per-client connect snippets, the portable
56+
**Connect an AI Assistant** page: per-client connect snippets, the portable
5757
SKILL.md download (`GET /api/v1/mcp/skill`), and API-key minting.
5858

5959
- **OAuth 2.1 (interactive clients — recommended).** Each deployment is its

‎content/docs/ai/connect-mcp.mdx‎

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ about the only thing left to do: **connecting a client and proving it works**.
1313
<Callout type="info">
1414
To turn the surface off, set `OS_MCP_SERVER_ENABLED=false` — the endpoint then
1515
returns 404 to a caller with an API key or token (an anonymous caller still gets
16-
401 first, with no OAuth metadata advertised), and the **Connect an Agent** page
16+
401 first, with no OAuth metadata advertised), and the **Connect an AI Assistant** page
1717
disappears with it, along with both its Setup and Account navigation entries. See
1818
[environment variables](/docs/deployment/environment-variables#mcp-server).
1919
</Callout>
@@ -95,7 +95,7 @@ client version offers none, add the request header
9595
| **Doubao Work** (豆包工作) | Skills · Connectors → New → New custom connector | Name `objectstack`, transport `HTTP`, server URL `<MCP URL>` |
9696
| **QwenWork** (千问办公) | Extensions → Connectors → + Add → Paste JSON config | `{ "mcpServers": { "objectstack": { "type": "streamable-http", "url": "<MCP URL>" } } }` |
9797

98-
The **Connect an Agent** page renders each of these with your deployment's URL
98+
The **Connect an AI Assistant** page renders each of these with your deployment's URL
9999
filled in. A client task that runs in the vendor's cloud, rather than on your
100100
machine, reaches only a deployment on public HTTPS — the same constraint as
101101
[claude.ai](#claude-desktop-and-claudeai).
@@ -118,13 +118,14 @@ Clients that read an `mcpServers` map connect the same way. With an API key:
118118

119119
## Headless: API keys
120120

121-
Mint a key from the **Connect an Agent** page in the Console. One page, two
121+
Mint a key from the **Connect an AI Assistant** page in the Console. One page, two
122122
doors — which one you have depends on your permissions:
123123

124-
- **Any signed-in user** — open the avatar menu (top right) → **Profile** to
125-
enter the Account app, then **Developer → Connect an Agent** in its sidebar.
124+
- **Any signed-in user** — open the avatar menu (top right) → **Connect an AI
125+
Assistant**. It opens the page in the Account app, whose sidebar lists it
126+
under **AI Assistant**.
126127
Direct link: `/_console/apps/com.objectstack.account/page/connect_agent`.
127-
- **Platform admins** — **Setup → Connect an Agent**, at
128+
- **Platform admins** — **Setup → Overview → Connect an AI Assistant**, at
128129
`/_console/apps/com.objectstack.setup/page/connect_agent`, reached from the
129130
Setup sidebar. The Setup app requires the `setup.access` permission, so this
130131
door alone is not enough for an ordinary member; the Account one needs none.

‎content/docs/api/index.mdx‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,9 @@ permission model grants anonymous users. Two ways to authenticate:
6666
`curl -c cookies.txt` / `-b cookies.txt`. On a fresh dev database the seeded
6767
admin is `admin@objectos.ai` / `admin123`.
6868
- **API key** (scripts, CI, headless agents): mint one with `POST /api/v1/keys`
69-
(the key is shown once), or from the **Connect an Agent** page in the Console
70-
— **Account → Developer** for any signed-in user, **Setup → Connect an Agent**
71-
for platform admins. Minting needs a signed-in session: a request
69+
(the key is shown once), or from the **Connect an AI Assistant** page in the Console
70+
— the avatar menu or **Account → AI Assistant** for any signed-in user,
71+
**Setup → Overview** for platform admins. Minting needs a signed-in session: a request
7272
authenticated with an API key is refused `403 PERMISSION_DENIED`, so a key
7373
cannot mint another key. Send the key as `x-api-key: osk_…` or
7474
`Authorization: Bearer osk_…`. Keys expire (90 days by default, at most a

‎content/docs/deployment/environment-variables.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ OS_MCP_STDIO_ENABLED=true OS_MCP_STDIO_API_KEY=osk_... os start # start the lo
292292
|:---|:---|:---|:---|
293293
| `OS_MCP_SERVER_ENABLED` | boolean | `true` | The MCP **HTTP** surface (`/api/v1/mcp`) is a core capability and defaults **on**. Set `false` to disable it (the endpoint answers 404 to a caller with an API key or token and 401 to an anonymous one, and the Connect-an-Agent page disappears). |
294294
| `OS_MCP_STDIO_ENABLED` | boolean | `false` | Auto-start the long-lived **stdio** transport at boot. Opt-in and **stricter** than the HTTP surface. **Requires `OS_MCP_STDIO_API_KEY`** — stdio runs as that key's identity with RLS/FLS/tenant applied; if the key is missing or invalid, boot **fails closed** (stdio refuses to start). See ADR-0101. |
295-
| `OS_MCP_STDIO_API_KEY` | string | — | The `osk_...` API key the **stdio** transport runs as. Resolved through the same verify chain as the HTTP/REST surfaces, so reads are scoped to that identity's permissions. Mint one from the **Connect an Agent** page — **Account → Developer** for any signed-in user, **Setup → Connect an Agent** for platform admins — or `POST /api/v1/keys`. For full authority, mint a key on a platform-admin or dedicated **service** identity — there is deliberately no `system`/unscoped bypass. |
295+
| `OS_MCP_STDIO_API_KEY` | string | — | The `osk_...` API key the **stdio** transport runs as. Resolved through the same verify chain as the HTTP/REST surfaces, so reads are scoped to that identity's permissions. Mint one from the **Connect an AI Assistant** page — the avatar menu or **Account → AI Assistant** for any signed-in user, **Setup → Overview** for platform admins — or `POST /api/v1/keys`. For full authority, mint a key on a platform-admin or dedicated **service** identity — there is deliberately no `system`/unscoped bypass. |
296296
| `OS_MCP_SERVER_NAME` | string | `objectstack` | Server name advertised to MCP clients. |
297297
| `OS_MCP_SERVER_TRANSPORT` | enum | `stdio` | `stdio` \| `http`. Use `http` (Streamable HTTP) for a remote client; `stdio` for a local one. |
298298

‎content/docs/getting-started/build-with-claude-code.mdx‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -487,8 +487,9 @@ claude plugin marketplace add objectstack-ai/claude-plugin
487487
```
488488

489489
Every client's copy-paste-ready connect snippet — plus the SKILL.md download and
490-
API-key minting — lives on the **Connect an Agent** page: **Account → Developer**
491-
for any signed-in user, **Setup → Connect an Agent** for platform admins.
490+
API-key minting — lives on the **Connect an AI Assistant** page: the avatar menu
491+
or **Account → AI Assistant** for any signed-in user, **Setup → Overview** for
492+
platform admins.
492493

493494
For headless callers (CI, scripts), mint an API key instead
494495
(`POST /api/v1/keys`, shown once) and pass it as a header:

‎packages/cli/scripts/check-app-nav-i18n.mjs‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ const CLI_ROOT = join(HERE, '..');
145145
//
146146
// `account` satisfied both halves all along and was invisible anyway:
147147
// `@objectstack/mcp` contributes `nav_connect_agent` into
148-
// `grp_account_developer` (`connect-ui.ts`, #16746) and commit c744c0af3 supplied its
148+
// `grp_account_ai_assistant` (`connect-ui.ts`, #16746) and commit c744c0af3 supplied its
149149
// label in all four locales — while this gate printed a BYTE-IDENTICAL
150150
// `OK (…)` line before and after that landing, because every site below
151151
// narrowed to one app name. Widening the three obvious filters is NOT the fix:
@@ -407,8 +407,8 @@ const CONTRIBUTORS = [
407407
source: '@objectstack/mcp (CONNECT_AGENT_UI_BUNDLE)',
408408
// The one contributor that lands in BOTH apps: `CONNECT_AGENT_UI_BUNDLE`
409409
// carries two `navigationContributions` entries pointing at one page —
410-
// `setup`/`group_integrations` for admins and `account`/
411-
// `grp_account_developer` for the per-user half (#16746). The Account half
410+
// `setup`/`group_overview` for admins and `account`/
411+
// `grp_account_ai_assistant` for the per-user half (#16746). The Account half
412412
// is the contribution this gate was blind to.
413413
apps: ['setup', 'account'],
414414
async load() {

‎packages/cli/test/connect-agent-both-halves-wire.pin.test.ts‎

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
// promise TOGETHER:
1010
//
1111
// 1. a PERMISSIONLESS principal gets `200` on `GET /api/v1/meta/apps/account`
12-
// with `nav_connect_agent` present in `grp_account_developer`; and
12+
// with `nav_connect_agent` present in `grp_account_ai_assistant`; and
1313
// 2. that SAME principal still gets `403` / `PERMISSION_DENIED` on
1414
// `GET /api/v1/meta/apps/setup`, with `connect_agent` absent from the body.
1515
//
@@ -215,12 +215,16 @@ describe('#17647 — Connect-an-Agent visibility: BOTH halves, over the wire', (
215215

216216
// `ACCOUNT_APP` declares no `requiredPermissions` — deliberately, so
217217
// every authenticated user reaches their own security surface — and the
218-
// fold puts the contributed entry inside the group that already carries
219-
// `nav_account_api_keys`.
218+
// fold puts the contributed entry into the AI Assistant slot the app
219+
// declares for it (#30895), leaving the collapsed Developer group as
220+
// authored.
221+
expect(groupChildren(account.body?.item, 'grp_account_ai_assistant'))
222+
.toEqual(['nav_connect_agent']);
220223
expect(groupChildren(account.body?.item, 'grp_account_developer'))
221-
.toEqual(['nav_account_api_keys', 'nav_account_oauth_apps', 'nav_connect_agent']);
222-
// The whole served tree, counted: 11 authored ids + the one contributed.
223-
expect(navIds(account.body?.item)).toHaveLength(12);
224+
.toEqual(['nav_account_api_keys', 'nav_account_oauth_apps']);
225+
// The whole served tree, counted: 12 authored ids (the AI Assistant
226+
// slot among them) + the one contributed.
227+
expect(navIds(account.body?.item)).toHaveLength(13);
224228
// …and the entry's TARGET rode along, so the menu item resolves.
225229
expect(JSON.stringify(account.body)).toContain('connect_agent');
226230

@@ -250,20 +254,23 @@ describe('#17647 — Connect-an-Agent visibility: BOTH halves, over the wire', (
250254
expect(setupAllowed.body?.error).toBeUndefined();
251255
const allowedIds = navIds(setupAllowed.body?.item);
252256
expect(allowedIds).toContain('nav_connect_agent');
253-
expect(groupChildren(setupAllowed.body?.item, 'group_integrations'))
254-
.toContain('nav_connect_agent');
257+
// The top group, behind System Overview (#30895).
258+
expect(groupChildren(setupAllowed.body?.item, 'group_overview'))
259+
.toEqual(['nav_system_overview', 'nav_connect_agent']);
255260
// The Setup tree the platform admin is served BY THIS COMPOSITION: 9
256-
// authored groups less `group_approvals`, which nothing contributes
257-
// into and the filter collapses, plus the 23 entries
258-
// `SETUP_NAV_CONTRIBUTIONS` carries, plus the MCP one. (25 until
259-
// #25098 moved the unfiltered Packages entry to Studio; 24 until
260-
// #30204 removed the Feature Flags entry with its namespace.)
261+
// authored groups less `group_approvals` and `group_integrations`,
262+
// which nothing in it contributes into and the filter collapses, plus
263+
// the 23 entries `SETUP_NAV_CONTRIBUTIONS` carries, plus the MCP one.
264+
// (25 until #25098 moved the unfiltered Packages entry to Studio; 24
265+
// until #30204 removed the Feature Flags entry with its namespace; 32
266+
// until #30895 moved the MCP entry out of `group_integrations`, the
267+
// one entry this composition put there.)
261268
// `nav_settings_branding` is not among them, and not because of a
262269
// tenancy wall (this harness boots none): since #24766 it is
263270
// contributed by `@objectstack/service-settings` together with the
264271
// `branding` namespace, offered only where no organization wall stands,
265272
// and this harness composes platform-objects and the MCP bundle alone.
266-
expect(allowedIds).toHaveLength(32);
273+
expect(allowedIds).toHaveLength(31);
267274
expect(allowedIds).not.toContain('nav_settings_branding');
268275
// The two per-user stores are reached from the User record page, not
269276
// from Setup's menu (#23803).
@@ -286,8 +293,8 @@ describe('#17647 — Connect-an-Agent visibility: BOTH halves, over the wire', (
286293
expect(withAccountHalf.statusCode).toBe(200);
287294
expect(withoutAccountHalf.statusCode).toBe(200);
288295
expect(navIds(withAccountHalf.body?.item)).toEqual(navIds(withoutAccountHalf.body?.item));
289-
// 32: the composition the count in the test above states.
290-
expect(navIds(withAccountHalf.body?.item)).toHaveLength(32);
296+
// 31: the composition the count in the test above states.
297+
expect(navIds(withAccountHalf.body?.item)).toHaveLength(31);
291298

292299
// The mechanism behind the invariance, asserted rather than assumed:
293300
// the bundle aims exactly one contribution at `setup` and one at

‎packages/cli/test/setup-integrations-order.pin.test.ts‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,13 @@
1212
//
1313
// Webhooks / HTTP Deliveries `@objectstack/plugin-webhooks`
1414
// Datasources `@objectstack/service-datasource`
15-
// Connect an Agent `@objectstack/mcp`
1615
// Connector Credentials `@objectstack/service-automation`
1716
// Connector Connections `@objectstack/service-automation` (same contribution)
1817
// Cloud Connection `@objectstack/cloud-connection`
1918
//
2019
// Each contributor states the group's other priorities in its own header so
2120
// that its number sorts where it means to — and nothing held those headers to
22-
// the code. A later contributor could renumber one of the five and silently
21+
// the code. A later contributor could renumber one of the four and silently
2322
// reorder the menu an administrator reads. This file is that hold.
2423
//
2524
// ⛔ The priorities are READ from the real contributions, never copied here as
@@ -66,7 +65,6 @@ const GROUP = 'group_integrations';
6665
*/
6766
const EXPECTED_TIERS: readonly (readonly string[])[] = [
6867
['nav_webhooks', 'nav_http_deliveries', 'nav_datasources'],
69-
['nav_connect_agent'],
7068
// One contribution carries both, in this order (ADR-0143 D5 added the second).
7169
['nav_connector_credentials', 'nav_connector_connections'],
7270
['nav_cloud_connection'],
@@ -101,6 +99,10 @@ async function setupContributions(): Promise<Array<{ packageId: string; contribu
10199
const sources: Array<[string, AnyRec[]]> = [
102100
['@objectstack/plugin-webhooks', await manifestsRegisteredBy(new WebhookOutboxPlugin({ autoEnqueue: false }))],
103101
['@objectstack/service-datasource', await manifestsRegisteredBy(new DatasourceAdminServicePlugin({}) as any)],
102+
// Still read although it no longer contributes here: Connect an AI
103+
// Assistant sits in `group_overview` (#30895), and the "nothing
104+
// contributes into the group that this pin does not order" check below
105+
// reds if it moves back.
104106
['@objectstack/mcp', [CONNECT_AGENT_UI_BUNDLE as AnyRec]],
105107
['@objectstack/service-automation', [CONNECTOR_CREDENTIALS_UI_BUNDLE as AnyRec]],
106108
['@objectstack/cloud-connection', [CLOUD_CONNECTION_UI_BUNDLE as AnyRec]],
@@ -126,7 +128,7 @@ function groupChildren(app: AnyRec | undefined, groupId: string): string[] {
126128
}
127129

128130
describe('Setup → Integrations: the merged order follows each contributor\'s priority', () => {
129-
it('serves the five contributors in priority order, read from the contributions themselves', async () => {
131+
it('serves the four contributors in priority order, read from the contributions themselves', async () => {
130132
const contributions = await setupContributions();
131133

132134
// `structuredClone`: `registerApp` writes the `_lock` envelope onto

‎packages/cloud-connection/src/cloud-connection-ui.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,9 @@ export const CloudConnectionSettingsPage: Page = {
8383
* `priority` orders contributions within a group, lowest first. The group's
8484
* other contributors, read where they are declared: Webhooks / HTTP Deliveries
8585
* (`@objectstack/plugin-webhooks`) and Datasources
86-
* (`@objectstack/service-datasource`) at 100, Connect an Agent
87-
* (`@objectstack/mcp`) at 110 — so 200 sorts this entry last.
86+
* (`@objectstack/service-datasource`) at 100, Connector Credentials and
87+
* Connector Connections (`@objectstack/service-automation`) at 120 — so 200
88+
* sorts this entry last.
8889
*
8990
* The group's own `requiredPermissions` (`manage_platform_settings`, on
9091
* `SETUP_APP` in `@objectstack/platform-objects`) applies to the entry, and

0 commit comments

Comments
 (0)