# Design:MCP 統一帳號來源 — 單一 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 已能讀三層 config(env > 專案 `.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 平台預設 MCP,AI 幫他帶 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-14,HANDOFF §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-15,HANDOFF §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://.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 transport(richblack §2:不需要)。 - ❌ 不把 init / config 搬進 MCP(init 是本機 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 實測)。