頂層 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>
9.1 KiB
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 全通。根因:MCPmiddleware/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://<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 transport(richblack §2:不需要)。
- ❌ 不把 init / config 搬進 MCP(init 是本機 CF 部署動作,MCP Worker 做不到)。
- ❌ 不在 MCP 重實作 credential 加密 / workflow 執行(server 職責,rule 02 §3.4)。
7. 驗收(客觀證據,mindset §7)
cli/+mcp/各自tsc --noEmitexit 0。acr mcp-setup在含.arcrun.yaml(mcp_url=X)的資料夾 → 產出.mcp.json指向 X;無 mcp_url → 指向 DEFAULT_MCP_URL。acr config --where顯示 mcp_url 來源層。mcp/被 deploy 掃描掃到(find wrangler.toml命中)。- self-hosted 用戶在客戶資料夾
acr mcp-setup→ Claude Code 連客戶 MCP(端到端待 richblack 實測)。