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>
This commit is contained in:
uncle6me-web
2026-07-03 07:13:15 +08:00
parent c830150da1
commit 5d00e71275
190 changed files with 39486 additions and 14 deletions
@@ -0,0 +1,132 @@
# 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 實測)。