Files
Arcrun/system-dev/docs/3-specs/arcrun/sdk-and-website/mcp-account-source.md
T
uncle6me-web 5d00e71275 chore: D22 落地——docs/SDD/wiki/CLAUDE.md 進 repo(Gitea private 預設全 push)
頂層 D22 決策(leo 2026-07-03 拍板):推什麼由開發環境歸屬決定,
Gitea private=除機敏值/build 產物/.github 外全 push。
解 T1.5 卡點:雲端工人 clone 拿得到 credential-store-migration.md,可就地改寫 SDD。
機敏掃描兩輪通過(新增 189 檔約 2.1MB,node_modules/dist/wasm 照舊排除)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 07:13:33 +08:00

133 lines
9.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# DesignMCP 統一帳號來源 — 單一 remote MCP + .env 切 MCP URL
> 2026-06-06 richblack 拍板(推翻本檔初版的「①工具帶參數 / ②自架 worker / ③平台 routing」三方案)。
> `sdk-and-website/design.md` 的單檔補充(規則 02 §4.3 允許)。
> 來源:壓測報告 §5.1/§5.2/§5.4(薄殼原則)+ richblack 對話釐清。
> 對應鐵律:`.claude/rules/07-thin-shell.md` §4(統一帳號來源)。
---
## 1. 問題(壓測 §5.2
CLI 已能讀三層 configenv > 專案 `.arcrun.yaml` > 全域)切帳號;**MCP 不能**——MCP 用 Cloudflare
service binding 焊死平台 `arcrun-cypher-executor`self-hosted 用戶用 MCP 連不到自己的 cypher。
**這違反薄殼鐵律**:「切換帳號」這能力做在了 CLI(介面層),MCP 沒跟上 → 證明它不在 API/共用層。
## 2. 關鍵洞見(richblack):不需要兩套 transport
> 「self-hosted 用戶也有 CF,把 MCP 放上網對他也沒困難。接案時進客戶專案讀 `.env` 連的是
> 客戶專案的雲端 MCP — 這比保留 stdio + 雲端兩套更單純。」
推論:**所有人都用 remote Worker MCP,差別只在「連哪台 MCP」**。
- **我自己**:全域 config 的 `mcp_url` 指向我自己的 MCP Worker。
- **接案幫客戶**:客戶資料夾 `.arcrun.yaml` / `.env` 放客戶的 `mcp_url`(那台 MCP 綁客戶 cypher)。
進客戶資料夾 → 自動連客戶那套。
- **SaaS 用戶**:不設 `mcp_url` → fallback 平台預設 MCPAI 幫他帶 api_key。
**不需要 stdio 本機 MCP、不需要 transport 抽象層、不需要把 Worker 改成讀本機檔。**
現有 remote HTTP Worker MCP 形態完全保留。
## 3. 薄殼原則怎麼落地
「身份解析(讀哪個帳號/哪台 MCP)」本質在 client(帳號設定是本機檔,cypher/MCP Worker 讀不到)。
正解不是「API 去讀 .env」(做不到),而是:
> **MCP URL 與 cypher URL 一樣,由同一份 config 解析模組(env > 專案 > 全域)決定。**
> CLI 讀 `cypher_url`Claude Code 的 MCP 連線讀 `mcp_url`**同一份 `.arcrun.yaml` / env、同一個解析邏輯**。
> 「切換帳號」這能力只實作一次(config 解析),不再綁死在 CLI。
### 接線:mcp_url → Claude Code 的 MCP 設定
Claude Code 看 `.mcp.json`(專案層)決定連哪台 MCP。所以需要一個東西把
「arcrun config 解析出的 `mcp_url`」寫進專案 `.mcp.json`
- **`acr mcp-setup`**(新指令):依三層 config 解析出 `mcp_url`,在 cwd 寫 / 更新 `.mcp.json`
- `acr init` 順帶呼叫(裝好就有)。
- 「切帳號」= 在客戶資料夾跑 `acr mcp-setup`(讀該資料夾的 `.arcrun.yaml`)→ 產對的 `.mcp.json`
## 4. 動到的檔案
| 檔案 | 動作 | 狀態 |
|---|---|---|
| `cli/src/lib/config.ts` | `ArcrunConfig``mcp_url``ENV_MAP``ARCRUN_MCP_URL``resolveConfigSources` 含 mcp_url;新增 `DEFAULT_MCP_URL` + `getMcpUrl()` | ✅ 完成 |
| `cli/src/commands/mcp-setup.ts`(新增) | `acr mcp-setup`:依 `getMcpUrl()` 寫專案 `.mcp.json` | 進行中 |
| `cli/src/index.ts` | 註冊 `acr mcp-setup` | 進行中 |
| `cli/src/commands/init.ts` | init 尾端順帶 `acr mcp-setup`(裝好即有) | 進行中 |
| **`arcrun/mcp/`(新目錄)** | MCP 從 sibling repo `matrix/arcrun-mcp` **搬進主庫**(與 cli/ 並列)。形態不變(remote Worker),只是進主庫 → 同 repo、同 deploy 掃描、與 cypher API 對齊 | 進行中 |
| `mcp/wrangler.toml` | name/route 對齊 arcrun 部署慣例(`workers_dev=true`deploy.yml/local-deploy.sh 自動掃到) | 進行中 |
## 5. MCP 搬進 arcrun/mcp/(不改形態,只進主庫)
- **搬 verbatim**`src/tools/*``src/lib/*``src/types.ts``src/mcp-handler.ts``src/index.ts``tests/`
- **形態不變**:仍是 Hono + `WebStandardStreamableHTTPServerTransport` 的 remote Worker(不加 stdio)。
- **進主庫的理由**(a) 與 cypher-executor 同 repo → 改 API 時 MCP 薄殼同步可見、一起 review;
(b) 被 deploy 掃描(wrangler.toml)自動部署,納入 release.feature「推送=全部到位」;
(c) self-host 用戶 codeload 主庫即得 MCP,能部署自己的 MCP Worker。
- **sibling repo `matrix/arcrun-mcp` 去留**:搬進來後,原 sibling 為歷史/過渡;新開發在 `arcrun/mcp/`
## 5.5 self-hosted MCP 認證對齊(2026-06-14HANDOFF §3b
> 症狀(mira CC 實測 `arcrun-mcp.leo21c.workers.dev`):self-hosted 用 namespace 明碼連 MCP 一律 401
> CLL 全通。根因:MCP `middleware/partner-auth.ts` 把 Bearer 拿去 KBDB `/partners/:token/info`
> **驗證**namespace 明碼非註冊 partner → 401。而 cypher-executor 的 `X-Arcrun-API-Key`
> **不驗證、直接當分區 key**webhooks-named.ts triggerNamed)→ 這就是「CLI 通、MCP 401」的分歧。
**決策:① MCP self-hosted 繞 partner 驗證 + ② mcp-setup 寫 headers(兩者缺一不可)。**
| | 修法 | 為何必須 |
|---|---|---|
| ① | `partner-auth.ts``MULTI_TENANT === 'false'` 時 Bearer = namespace 明碼直接當 `org_namespace`,不打 KBDB partner 驗證(對齊 cypher 的 opaque-key 模型)。官方 SaaS(不設/`"true"`)行為不變 → 官方與 self-host 共用同一份程式碼 | 只做②也沒用:partner 驗證仍擋明碼 |
| ② | `mcp-setup.ts`:把 `config.api_key`self-hosted 存 namespace 明碼)寫進 `.mcp.json``headers.Authorization: Bearer …`(與 CLI 同一份身份,rule 07 §4) | 只做①也沒用:裸 `.mcp.json` 不送任何 header |
判定旗標:worker `[vars] MULTI_TENANT`(與 cypher 同名)。
### 5.5.1 部署注入修補(2026-06-15HANDOFF §3b-2
> 症狀:①②code 正確、官方帳號測綠,但 mira 推 leo21c 端到端**仍 401**。
> **根因(非 code bug,是部署注入缺口)**partner-auth.ts `if (c.env.MULTI_TENANT === 'false')`
> 邏輯對,但 worker env 裡 `MULTI_TENANT === undefined`——因為:
> - mcp/wrangler.toml 的 `MULTI_TENANT` 原本是**註解掉的**
> - `cli/src/lib/deploy.ts` 的 `injectWranglerConfig` 部署時注入了 KV id / WORKER_SUBDOMAIN / D1 id
> **但沒注入 MULTI_TENANT** → 部署後 `c.env.MULTI_TENANT===undefined ≠ 'false'` → 走 partner-key → 401。
> - config 源頭早有(init.ts `multi_tenant:false` + `mode:'self-hosted'`),只是沒被注進 worker。
>
> **只取消註解 mcp/wrangler.toml 不夠**——那只修「手動 fork」,沒修「acr update 自動部署」(mira 走後者)。
> 根因要修在 deploy.ts 注入邏輯。
**修法(方案①:注 vars 非 secret,符合 self-hosted 零填寫契約)**
| 檔案 | 動作 | 狀態 |
|---|---|---|
| `cli/src/lib/deploy.ts` | `DeployContext``selfHosted?`;新增 export `injectMultiTenant(toml)`(處理 active/註解/無行三態,加進 `[vars]`);`injectWranglerConfig``selfHosted` 時呼叫——與 WORKER_SUBDOMAIN/KV 注入同層級 | ✅ 完成 |
| `cli/src/commands/init.ts` | deployCtx 帶 `selfHosted: true`init 本就是 --self-hosted 分支) | ✅ 完成 |
| `cli/src/commands/update.ts` | ctx 帶 `selfHosted: config.mode==='self-hosted' \|\| config.multi_tenant===false`(mira 走這條) | ✅ 完成 |
| `mcp/wrangler.toml` | `# [vars]`/`# MULTI_TENANT` 改 active `[vars]`(官方不含 MULTI_TENANT=多租戶;注入走 case-3 加行,結構正確在 [vars] 下) | ✅ 完成 |
**本地驗注入(dry-run,真實 export 函式)**mcp / cypher-executor 注入後各恰 1 行 active `MULTI_TENANT = "false"` 且在 active `[vars]` 之下 → ✓ PASS。cli tsc exit 0。
**端到端(交棒回 mira**mira 在 leo21c 重跑 `acr update` 重部 MCP worker(這次帶 MULTI_TENANT=false)→ `curl -H "Authorization: Bearer leo" https://<mcp>.leo21c.workers.dev/mcp`**200 非 401**。官方帳號測不到(不設 MULTI_TENANT)。
### 5.5.0 原始 code 修法(2026-06-14,①②)
| 檔案 | 動作 | 狀態 |
|---|---|---|
| `mcp/src/types.ts` | `Env``MULTI_TENANT?` | ✅ 完成 |
| `mcp/src/middleware/partner-auth.ts` | self-hosted 分支:Bearer 明碼直接當 org_namespace | ✅ 完成 |
| `cli/src/commands/mcp-setup.ts` | `.mcp.json``headers.Authorization` | ✅ 完成 |
驗收:mira 用 namespace 明碼連 self-hosted MCP(待 leo21c 部署後實測,HANDOFF §3);官方 SaaS MCP partner-key 路徑回歸不變。
## 6. 不在範圍(明確排除)
- ❌ 不加 stdio transportrichblack §2:不需要)。
- ❌ 不把 init / config 搬進 MCPinit 是本機 CF 部署動作,MCP Worker 做不到)。
- ❌ 不在 MCP 重實作 credential 加密 / workflow 執行(server 職責,rule 02 §3.4)。
## 7. 驗收(客觀證據,mindset §7)
1. `cli/` + `mcp/` 各自 `tsc --noEmit` exit 0。
2. `acr mcp-setup` 在含 `.arcrun.yaml`(mcp_url=X)的資料夾 → 產出 `.mcp.json` 指向 X;無 mcp_url → 指向 DEFAULT_MCP_URL。
3. `acr config --where` 顯示 mcp_url 來源層。
4. `mcp/` 被 deploy 掃描掃到(`find wrangler.toml` 命中)。
5. self-hosted 用戶在客戶資料夾 `acr mcp-setup` → Claude Code 連客戶 MCP(端到端待 richblack 實測)。