實施級 PRD · v1.0
ai-mcp-server:多 API Key 多模型能力的本地 MCP 橋樑
把分散在多個 (api_key, base_url) 中的模型能力(chat / vision / reasoning / embedding / image_gen / tts / stt / rerank)收編成單一本地 MCP 橋樑,讓只能配一個模型的 Agent 也能按需路由到任意能力的模型。三進程拓撲:MCP server (stdio) + CLI + Web UI,透過 in-process 共享 application/* 與 WAL 模式 SQLite 構成單一邏輯後端;探活異步化、樂觀鎖避雙跑。
術語解釋
| 術語 | 解釋 |
| MCP | Model Context Protocol,Anthropic 主導的客戶端與工具服務的協議;本項目以 stdio 模式對接 MCP client(如 Claude Desktop、Cursor、Cline)。 |
| MCP server | 本倉庫主交付物,以 stdio 啟動,被 Agent 客戶端按需拉起。 |
| MCP tool | MCP server 暴露給 Agent 的可調用功能,本期共 6 個:usage_guide / list_models / invoke_model / model_performance / refresh_endpoint / add_models。 |
| endpoint | 用戶登記的一組 (name, base_url, api_key, provider_type),代表一個上游 API 提供商。 |
| model | endpoint 下發現的單一模型 id,附帶 capabilities 標籤集。 |
| capability | 模型能力標籤:text_chat、vision、tool_call、reasoning、json_mode、embedding、image_gen、audio_tts、audio_stt、rerank。 |
| probe | 能力探活,發最小 payload(內容統一為「1」)驗證模型在某能力上是否真實可用。 |
| resolver | 能力來源融合器,按「用戶覆寫 > 探活 > LiteLLM metadata > 內建靜態表」優先級合併。 |
| provider adapter | 協議防腐層;本期只實作 OpenAICompatAdapter,Anthropic 預留接口。 |
| LiteLLM | 第三方項目,維護一份 model_prices_and_context_window.json,包含主流模型的 context_length、modality、定價等 metadata。 |
| Fernet | cryptography 庫提供的對稱加密方案,用於加密儲存 api_key。 |
| keyring | 系統級密鑰儲存(macOS Keychain / Windows Credential Vault),用於儲存 master key。 |
| 透傳 | 對上游 API 的請求與響應原樣轉發,不做業務語義改寫(除協議格式轉換外)。 |
| stdio | 標準輸入輸出流;MCP server 透過 stdio 與 client 進程通信,無網路埠。 |
| DAO | Data Access Object,封裝對 SQLite 表的讀寫的薄函數層。 |
| ER | 實體關係圖,展示資料表及其關係。 |
| ABC | Python abc.ABC 抽象基類,定義 Provider Adapter 接口契約。 |
| 解讀 X | 「邏輯單一後端」架構:UI / CLI / MCP server 三個進程各自 import ai_mcp_server.application.* 共享同一份業務邏輯與同一個 SQLite,但不共享進程。 |
| WAL | SQLite Write-Ahead Logging journal mode;支援多進程並發讀寫,本期所有進程啟動時設定 PRAGMA journal_mode=WAL。 |
| Worker | 探活背景執行單元;可由 MCP server 進程內背景任務承擔,也可由 CLI endpoint probe 同步消化。 |
| probe_jobs | 探活任務佇列表;UI / CLI / MCP 任一入口都可入隊;任務狀態 pending / claiming / running / done / failed。 |
| instance_id | 每個 worker 實例啟動時生成的 UUID,寫入 probe_jobs.claimed_by 作為樂觀鎖標記。 |
| 樂觀鎖 | 並發控制策略:先寫 claimed_by → 等 0.5 秒 → 二次讀;若仍是自己才執行,否則放棄。 |
| lease | worker 認領任務後的時效租約;超時未完成則被視為孤兒任務,被定期掃描回退為 pending。 |
| Jinja2 | Python 後端模板引擎,本期 Web UI 使用其純後端渲染,不引入 SPA。 |
| FastAPI | Python 異步 Web 框架;本期 ai-mcp ui 子命令以 uvicorn 託管 FastAPI 應用。 |
| SSE | Server-Sent Events;本期 UI 監聽 probe_jobs 狀態變化用 SSE 推送進度。 |
0. 審查結論與交付門禁
結論可進入實施。需求、技術棧、6 個 MCP tool 形態、能力來源優先級均已與用戶確認。
硬門禁真實 MCP client(如 Claude Desktop)拉起 stdio server 後,6 個 tool 的 schema、響應結構、透傳行為必須可見、可診斷。CLI 單元測試不可替代此驗收。
MVP 不變量橋樑不攔截、靜態優先探活兜底、四來源優先級不動、stdio 部署不引入帳號層。
本輪契約變更:MCP tool 從 4 個升級為 6 個
2026-06-28 決策:將現有實作中的 model_performance 與 add_models 正式納入 v1 MCP 公共契約,避免設計文檔、協作指引、README、驗收腳本與 server.py 分離。6 個 tool 是本期刻意上限;後續新增 tool 必須先回到本 PRD 更新設計與驗收。
| Tool | 契約定位 | 邊界 |
usage_guide | 連線後第一步,返回動態 inventory、能力分佈與推薦路由模式。 | 只提供說明與統計,不調上游 API。 |
list_models | 按 capability、context length、endpoint、probe 狀態篩選可用模型。 | 只查本地 SQLite 與 resolver,不做自動 fallback。 |
invoke_model | 對選定 endpoint/model 透傳上游調用。 | 只做協議格式轉換與 model 注入,不改寫 messages 或業務語義。 |
model_performance | 查詢近 3 天每模型調用次數、成功率、首字延遲與 token 均值,輔助 Agent 在同能力模型間做選擇。 | 讀本地聚合指標,不做計費、不做成本估算、不影響路由決策。 |
refresh_endpoint | 刷新 model list 並入隊 capability probes,由 worker 異步消化。 | 不同步阻塞等待所有 probe 完成;不做重試或自動 fallback。 |
add_models | 為不暴露 /v1/models 的 endpoint 手動登記 model_id、context length 與能力覆寫。 | 只寫本地模型/覆寫資料;應由 Agent 先向用戶確認 model_id 與能力來源。 |
本輪發布阻斷修復
2026-06-28 發布審查後修復以下阻斷項;本表是設計與實施同步的發布準入記錄。
| 問題 | 修復策略 | 驗收 |
sdist 夾帶 .keys、.trae/ 等本地敏感或工具文件。 | .gitignore 改為忽略 .keys 並保留 .keys.example;pyproject.toml 使用 sdist 白名單 include 與 build exclude。 | uv build 後檢查 tarball / wheel,不得包含 .keys、.trae/。 |
| wheel 中 probe assets 被重複打包。 | 移除 wheel shared-data 規則,只保留 force-include assets/probe → ai_mcp_server/_assets/probe。 | wheel 中每個 probe asset 只出現一次,且可被 probe_assets_dir() 找到。 |
默認 DB 寫入源碼/安裝目錄附近的 .data。 | 默認改為 ~/.ai-mcp-server;相對 AI_MCP_CONFIG_DIR/AI_MCP_DB_PATH 按當前工作目錄解析。 | tests/unit/test_path_util.py 覆蓋 home-relative default 與 cwd-relative env。 |
| timeout / rate limit / skipped probe 會把已知 capability 覆蓋為 false。 | worker 僅在 ok 或明確 not_supported 時寫回 capability;其他狀態只記 probe_runs 與 job error。 | timeout 測試保持 gpt-4o 的 static vision=true,不被 probe false 污染。 |
assets/probe/digit_1.wav 缺失,STT probe 永遠 skipped。 | 新增 digit_1.wav,並擴展 scripts/generate_probe_assets.py 同時生成 PNG/WAV。 | file assets/probe/digit_1.wav 顯示 16kHz mono PCM WAV;wheel/sdist 均包含該資產。 |
| TTS 透傳成功時只返回音訊長度,沒有返回可用音訊內容。 | OpenAICompatAdapter.tts() 將二進位音訊以 audio_base64 放入 JSON body,同時保留 byte length 與 content-type。 | Agent 可從 invoke_model(operation="tts") 的 body.audio_base64 取得音訊產物。 |
AI_MCP_UI_TOKEN 開啟時 /jobs/stream 被放行。 | token middleware 僅放行 static assets;SSE job stream 必須帶 token。 | tests/unit/test_web_security.py 覆蓋無 token 訪問 /jobs/stream 返回 401。 |
| MCP stdio smoke 未納入 CI,且舊腳本仍驗 4 tools。 | smoke 腳本改驗 6 tools;CI 新增 uv run python scripts/verify_mcp_server.py。 | CI 與本地 smoke 都必須看到 6 個 MCP tool 並能調用 usage_guide。 |
已確認決策
| 決策項 | 選擇 | 備註 |
| 部署形態 | 本地單用戶 stdio MCP | 不做多用戶 SaaS、不做 HTTP 服務 |
| 協議支援 | OpenAI 兼容(首期),Anthropic 預留 | Provider ABC 留接口,不寫 Anthropic 殼 |
| MCP tool 形態 | 方案 C 擴展:guide + list + invoke + performance + refresh + manual add | 正式契約共 6 個 tool;model_performance 與 add_models 納入 v1 |
| 使用說明承載 | MCP instructions + usage_guide tool 雙保險 | instructions 強提示、tool 動態組裝 |
| 能力來源 | 四來源融合:用戶覆寫 > 探活 > LiteLLM > 靜態表 | resolver 統一收口 |
| 探活覆蓋 | 10 種能力全做 | 內容統一「1」,vision/stt 用內建資產 |
| 探活觸發 | UI / CLI / MCP 任一入隊;worker 異步消化 | 狀態綁定 DB,非進程記憶體 |
| Worker 模型 | (δ) MCP server 內背景任務 + (γ) CLI endpoint probe 同步消化 | 雙來源驅動,UI 不開也能跑 |
| 並發控制 | 樂觀鎖:寫 claimed_by=instance_id → 等 0.5s → 二次讀 → 仍是自己才執行 | 含 lease 超時回退機制 |
| Web UI | 納入 MVP:ai-mcp ui 子命令、FastAPI + Jinja2、127.0.0.1 本機無認證;設置 AI_MCP_UI_TOKEN 時非 static route 均需 token | 9 個頁面,純後端渲染 + SSE 進度 |
| 後端架構 | 解讀 X:UI / CLI / MCP 三進程共享 application/*,各自 in-process 調用,共享 WAL 模式 SQLite | 不引入 subprocess 反代 |
| 透傳邊界 | 強制非流式、支援多輪 tool_calls、不做重試/容錯 | 錯誤原樣冒泡 |
| 儲存 | SQLite (WAL) + Fernet 加密 api_key | 路徑 ~/.ai-mcp-server/db.sqlite3 |
| 技術棧 | Python 3.11+ / mcp SDK / httpx / typer / sqlite3 / cryptography / keyring / FastAPI / Jinja2 / uvicorn | 不上 ORM |
會構成驗收假通過的情況
- 只跑 pytest 通過但 MCP client 拉不起來 server,或拉起來後 tool list 為空。
- 探活返回「全部 true」但其實 vision / tool_call 走的是 fallback(如 4xx 被當成 ok)。
- capability resolver 返回的能力與實際 invoke 結果不一致。
- api_key 在 SQLite 中以明文落盤、或在日誌 / 錯誤訊息中出現。
- 「不做」清單(streaming、自動重試、自動 fallback)被悄悄做了。
1. 產品價值
目標用戶與場景
- 目標用戶:手上同時持有多個 LLM API key(OpenAI、DeepSeek、Moonshot、SiliconFlow、OpenRouter、Together、vLLM、Ollama…)的開發者,並使用至少一個 MCP client(Claude Desktop / Cursor / Cline)。
- 核心 Job:在不切換 Agent 配置的前提下,按任務性質讓 Agent 自動挑選最合適的模型(長上下文走 Gemini、視覺走 GPT-4o、推理走 DeepSeek-R1、便宜對話走國產、Embedding 走 BGE 等)。
當前痛點
| 痛點 | 當前做法 | 代價 |
| Agent 只配一個模型 | 大多數 MCP client 的 model 設定是全域單選 | 其他 API key 與能力閒置 |
| 模型 metadata 散落 | 每家 /v1/models 只返回 id,能力靠用戶記憶 | 無法按能力路由 |
| key 管理混亂 | vendor 多了之後 .env 變成一張表 | 容易誤用、容易洩漏 |
| 能力驗證靠玄學 | 不知道某 endpoint 上某 model 真的支援 vision / tool_call 嗎 | 到 Agent 對話中才報錯 |
產品價值(用戶可見)
- 登記一次,所有 key 與模型即可被 Agent 統一發現與調用。
- Agent 透過
list_models 按能力篩選,透過 invoke_model 透傳調用,不被服務語義污染。
- 能力標籤可被「探活」驗證,避免「以為能用」。
- 所有 key 本地加密儲存,無雲端、無遙測。
成功信號
- 同一個 Claude Desktop 會話中,Agent 能在 vision 問題上自動選 GPT-4o、在長文檔問題上自動選 Gemini,不需要用戶手動切換 client model 設定。
endpoint probe --all 跑完後,至少 90% 的模型能拿到 ≥1 個探活確認的 capability。
- 本地 SQLite 中
api_key 欄位無明文,strings db.sqlite3 | grep sk- 無命中。
2. 範圍
| 本期範圍 (MVP) | 非目標 | 後續階段 |
- CLI 登記 / 查詢 / 刪除 endpoint
- OpenAI 兼容協議 list_models / chat / embedding / image_gen / tts / stt / rerank 透傳
- 10 種能力探活
- 四來源能力 resolver
- 6 個 MCP tool + stdio 啟動
- SQLite (WAL) + Fernet 加密儲存
- LiteLLM metadata 本地快取
- 探活異步化:probe_jobs 佇列 + 樂觀鎖 + lease 超時回退
- Worker:MCP server 進程內背景任務 + CLI 同步消化
- Web UI:
ai-mcp ui + FastAPI + Jinja2,9 個管理頁面
|
- streaming 響應
- 自動重試、自動 fallback、rate limit
- token 計費與用量統計
- 多用戶、帳號、權限、Web 認證
- Anthropic / Gemini 原生協議實作
- 定時探活、跨機同步、雲端備份
- SPA / 前端構建工具鏈(UI 純後端模板)
|
- Anthropic / Gemini adapter
- 定時探活與健康監控
- cost-aware 路由建議
- HTTP 模式(給遠端 MCP client)
- 多用戶分租
- Web 管理 UI
|
假設與待用戶確認項
- 假設:用戶能接受首次啟動時自動建立
~/.ai-mcp-server/ 目錄並向系統 keyring 寫入 master key。
- 已落地:
assets/probe/digit_1.wav 已納入倉庫,並可由 scripts/generate_probe_assets.py 重建;stt 探活不再依賴用戶手工補檔。
- 待確認:LiteLLM metadata 是內建一份 snapshot 進 wheel,還是首次運行時從 GitHub 拉取?默認傾向「內建 snapshot + 可選
ai-mcp meta refresh 拉取最新版」。
3. 現況與差距
當前倉庫狀態
- 已具備
pyproject.toml、src/ai_mcp_server/、tests/、assets/probe/、docs/、CI 與發布 workflow。
- 三入口已落地:
ai-mcp CLI、ai-mcp-server stdio MCP server、ai-mcp ui Web UI。
- 本 PRD 是設計與實施同步的源頭文件;發布審查發現的阻斷項需先回寫本文件,再落到 source/test。
外部生態現況
| 項目 | 觀察 | 對本期的影響 |
| MCP Python SDK | 官方 mcp 包穩定,支援 stdio + sse + streamable_http;提供 Server / FastMCP 兩種寫法 | 用 FastMCP,schema 與 docstring 自動推導 |
OpenAI /v1/models | 只返回 id 和 owned_by,無能力欄位 | 必須四來源融合 |
OpenRouter /api/v1/models | 返回完整 metadata(context_length、modality、定價、tool_call 支援) | 對於配置為 OpenRouter 的 endpoint,可優先採用其原生 metadata |
LiteLLM model_prices_and_context_window.json | 覆蓋 200+ 主流模型;定期更新 | 作為靜態 metadata 主要來源 |
| MCP client streaming | tool response 為一次性 JSON;progress notification 可選但實作彆扭 | 本期強制非流式 |
差距
- 無任何代碼基線,從零開始。
assets/probe/digit_1.wav 已提供;若安裝包或本地資產缺失,stt 探活仍需自動降級為 skip,不影響其他能力探活。
4. 目標旅程
4.1 首次安裝與登記
- 用戶
uv tool install ai-mcp-server 安裝。
- 首次運行任意
ai-mcp 命令時自動建立 ~/.ai-mcp-server/ 與 SQLite。
- 首次需要加密時自動生成 master key 並寫入系統 keyring。
- 用戶執行
ai-mcp endpoint add 完成登記,緊接著 ai-mcp endpoint probe 探活。
4.2 Agent 透過 MCP 調用
- MCP client 啟動時拉起
ai-mcp-server 進程(stdio)。
- client 收到
instructions:「請先調用 usage_guide 取得能力說明」。
- Agent 視任務需要調用
list_models 篩選能力。
- 如 endpoint 不提供
/v1/models,Agent 先向用戶確認後可調用 add_models 手動登記。
- 如多個模型能力相近,Agent 可調用
model_performance 查看近期成功率與延遲作為選擇依據。
- Agent 帶上選定的
endpoint + model 調用 invoke_model。
- MCP server 找對應 adapter,透傳請求;響應原樣返回。
4.3 異常與兜底
| 場景 | 系統行為 | Agent 可見 |
| 上游 4xx(如 key 失效) | 原樣冒泡 status + body | 完整錯誤 JSON,Agent 可決策換 endpoint |
| 上游 5xx / 超時 | 原樣冒泡,不重試 | 完整錯誤,Agent 自行決定是否重試 |
| 用戶指定 capability 但無模型匹配 | list_models 返回空陣列 + hint | Agent 知道要放寬條件 |
| endpoint 從未探活 | list_models 標註 probe_status: never | Agent 可建議用戶先 refresh |
| 探活時上游限流 | 該 capability 標記 probe_status: rate_limited,不污染既有 true | refresh tool 響應中報告 |
| vision 探活素材缺失 | 啟動時警告 + 探活時 skip | 不影響其他 capability 探測 |
5. 產品方案
5.1 CLI 命令
# endpoint 管理
ai-mcp endpoint add --name <n> --base-url <url> --key <k> [--provider openai-compat]
ai-mcp endpoint list [--json]
ai-mcp endpoint show <name>
ai-mcp endpoint remove <name>
# 模型與能力
ai-mcp endpoint probe <name> [--capability text_chat,vision,...]
ai-mcp endpoint probe --all
ai-mcp model list [--endpoint <name>] [--capability vision] [--min-context 128000]
ai-mcp model show <endpoint> <model_id>
ai-mcp model override <endpoint> <model_id> --capability vision=true
ai-mcp model override <endpoint> <model_id> --context-length 200000
# metadata
ai-mcp meta refresh # 拉取最新 LiteLLM metadata
# MCP server
ai-mcp-server # stdio 啟動(給 MCP client 用)
5.2 MCP Tool 規格
usage_guide
| 項 | 內容 |
| 入參 | 無 |
| 出參 | 結構化 Markdown 文本 + JSON 統計 |
| 動態內容 | 當前已登記 endpoint 數、模型總數、按 capability 的模型分佈、最近探活時間、推薦使用順序 |
| 靜態 hint | MCP server instructions 中強制提示:「You MUST call usage_guide first before using any other tool.」 |
list_models
| 入參 | 類型 | 說明 |
capability | string[] (可選) | 過濾必須具備的能力標籤 |
min_context_length | int (可選) | 過濾最小上下文長度 |
endpoint | string (可選) | 限定 endpoint name |
include_unprobed | bool, 默認 true | 是否包含未探活的模型 |
出參:模型陣列,每個元素含 endpoint, model_id, capabilities, context_length, probe_status, capability_source(標明每個 capability 來自哪一來源)。
invoke_model
| 入參 | 類型 | 說明 |
endpoint | string | endpoint name |
model | string | model_id |
operation | enum | chat / embedding / image_gen / tts / stt / rerank |
payload | object | 原樣透傳給上游 API 的 body(OpenAI 兼容格式) |
出參:透傳上游響應 + 附加 _meta: {endpoint, model, latency_ms, upstream_status}。錯誤時返回 {error: {status, body}},不拋出。TTS 成功時二進位音訊以 body.audio_base64 返回,並附 audio_bytes_len 與 content_type。
model_performance
| 入參 | 類型 | 說明 |
endpoint | string (可選) | 限定 endpoint name;留空則查全部。 |
sort_by | enum, 默認 call_count | call_count / success_count / avg_first_byte_ms / avg_prompt_tokens / avg_output_tokens。 |
limit | int, 默認 50 | 最多返回行數。 |
出參:模型陣列,每個元素含 endpoint, model_id, full_id, call_count, success_count, success_rate, avg_first_byte_ms, avg_prompt_tokens, avg_output_tokens, window_days。此 tool 只讀本地聚合性能資料,不做成本估算、不替 Agent 自動路由。
refresh_endpoint
| 入參 | 類型 | 說明 |
endpoint | string (可選) | 留空則全部 |
capabilities | string[] (可選) | 留空則按 model metadata 與名稱 hint 生成智能 probe plan;顯式傳入時按指定能力入隊。 |
refresh_model_list | bool, 默認 true | 是否先重新拉 /v1/models |
出參:{enqueued, plan, note}。此 tool 只入隊,不等待所有 probe 同步完成;MCP server 進程內 worker 或 CLI worker 會異步消化。
add_models
| 入參 | 類型 | 說明 |
endpoint | string | endpoint name。 |
model_ids | string[] | 一個或多個待手動登記的 model_id。 |
context_length | int (可選) | 可選上下文窗口。 |
capabilities | string[] (可選) | 可選能力標籤;寫入 override source,視為用戶確認的能力覆寫。 |
出參:{endpoint, added, count} 或 {error}。Agent 使用此 tool 前應先向用戶確認模型 id、context length 與能力來源,避免憑空登記不存在模型。
5.3 能力路由建議(給 Agent 的引導)
不在服務側做路由;服務只提供能力標籤與篩選 API。usage_guide 中向 Agent 提示常見的選擇模式,例如「長文檔 → 按 context_length 排序」「視覺問題 → 篩 capability=vision」。
5.4 Web 管理 UI(ai-mcp ui)
本地單用戶管理頁面,純後端 Jinja2 模板渲染;預設綁定 127.0.0.1、本機無認證;設置 AI_MCP_UI_TOKEN 後,除 static assets 外所有 route(含 /jobs/stream SSE)均需 ?token=...。UI 進程 in-process 調用 application/*,與 CLI / MCP 共享同一份業務邏輯。
# 啟動
ai-mcp ui [--host 127.0.0.1] [--port 8765]
# 控制台輸出:
# ai-mcp ui ready at http://127.0.0.1:8765/
# 按 Ctrl+C 退出
5.5 UI 頁面清單(9 個,全為 MVP)
| 路由 | 頁面 | 功能 |
GET / | Dashboard | endpoint 總數、模型總數、按 capability 分佈直方圖、最近探活時間、MCP server 狀態提示 |
GET /endpoints | Endpoints 列表 | 表格顯示所有 endpoint;操作:新增、編輯、刪除 |
GET /endpoints/<name> | Endpoint 詳情 | 該 endpoint 下所有 model、探活狀態、按鈕「探活此 endpoint」 |
GET /models | Models 全表 | 跨 endpoint 模型表;可按 capability / min_context / endpoint 篩選 |
GET /models/<endpoint>/<model_id> | Model 詳情 | 顯示四來源每個 capability 判定值與 source;連結到能力覆寫 |
GET /overrides | 能力覆寫 | 列出所有 override;新增 / 編輯 / 刪除 |
GET /probes | 探活歷史 | 讀 probe_runs;可按 endpoint / model / capability / 時間範圍篩選 |
GET /jobs | 探活佇列 | 顯示 probe_jobs 當前狀態;SSE 推送進度 |
GET /meta | Metadata 管理 | 顯示 LiteLLM metadata 版本;按鈕「拉取最新」 |
寫操作以 form POST 提交,UI route 直接 in-process 調 application/*;不走 subprocess、不走 HTTP 反代到 MCP server。SSE 用於 /jobs 與 /endpoints/<name> 中的探活進度刷新。
5.6 探活異步化流程
探活不再阻塞觸發者;改為「入隊 → worker 認領 → 寫回結果」三段式。
- 入隊:UI / CLI / MCP 任一入口都可
INSERT INTO probe_jobs,狀態 pending,無 claimed_by。
- 認領(樂觀鎖):
- worker 啟動時生成
instance_id = uuid4()。
UPDATE probe_jobs SET claimed_by=instance_id, claimed_at=now, status='claiming' WHERE id=? AND claimed_by IS NULL。
- 等 0.5 秒;
SELECT claimed_by FROM probe_jobs WHERE id=?;如仍是自己 → status='running' 進入執行,否則 noop。
- 執行:跑 provider 探活 → 寫
probe_runs raw 響應 → 更新 models.capabilities_json(透過 resolver)→ UPDATE probe_jobs SET status='done', finished_at=now。
- lease 超時回退:背景定期掃描
status IN ('claiming','running') AND lease_until < now,重置為 pending, claimed_by=NULL(孤兒任務恢復)。
- 失敗:探活內部錯誤 →
status='failed',error 欄位記原因;不重試(用戶自行重新入隊)。
Worker 承擔:(δ) ai-mcp-server MCP 進程內以 asyncio task 常駐輪詢 + (γ) ai-mcp endpoint probe CLI 也跑 worker loop 直到佇列清空。兩者用同一份 worker 代碼,靠樂觀鎖避免並發雙跑。
6. 架構擴展
6.1 整體架構
- 入口層:三個獨立進程入口共享
application/*。server.py(stdio MCP);cli.py(typer CLI);web/app.py(FastAPI + Jinja2,由 ai-mcp ui 子命令拉起 uvicorn)。
- 編排層 application/:endpoint 管理、模型發現、capability 解析、probe_jobs 入隊與消化;無 IO 細節。CLI / UI / MCP route 都直接 import。
- Worker 層:
application/worker.py 提供 claim_one() / run_loop();MCP server 進程啟動時 asyncio task 常駐輪詢,CLI endpoint probe 也跑直到 queue 清空。
- Provider 層 providers/:ABC + OpenAICompat 實作;負責協議格式、HTTP 細節、錯誤碼透傳。
- Capability 層:static_map(內建表)、litellm_meta(拉取/快取)、resolver(四來源融合)。
- Probe 層:每能力一個模組,輸入 (adapter, model_id) → ProbeResult。
- Storage 層:
sqlite3(連線時設 PRAGMA journal_mode=WAL; PRAGMA busy_timeout=5000)+ Fernet + keyring;DAO 函數薄。
- 架構原則「解讀 X」:三進程不共享記憶體、各自 in-process 調 application;共享真相在 SQLite(WAL 多進程併發安全)。
6.2 倉庫結構
ai-mcp-server/
├── pyproject.toml
├── AGENTS.md
├── docs/prd/ai-mcp-server-v1/index.html
├── assets/probe/
│ ├── digit_1.png # vision 探活素材
│ └── digit_1.wav # stt 探活素材
├── src/ai_mcp_server/
│ ├── __init__.py
│ ├── server.py # MCP server 入口 (ai-mcp-server)
│ ├── cli.py # CLI 入口 (ai-mcp; 含 ui / endpoint / model / meta / worker 子命令)
│ ├── application/
│ │ ├── endpoint_service.py
│ │ ├── model_discovery.py
│ │ ├── probe_jobs.py # enqueue / claim / lease / list
│ │ ├── worker.py # claim_one / run_loop / lease_sweeper
│ │ ├── capability_query.py
│ │ └── meta_service.py
│ ├── providers/
│ ├── capability/
│ ├── probes/
│ ├── storage/
│ │ ├── db.py # WAL + busy_timeout + migration
│ │ ├── crypto.py
│ │ └── dao.py
│ ├── web/
│ │ ├── app.py # FastAPI app factory
│ │ ├── routes/
│ │ │ ├── dashboard.py
│ │ │ ├── endpoints.py
│ │ │ ├── models.py
│ │ │ ├── overrides.py
│ │ │ ├── probes.py
│ │ │ ├── jobs.py # 含 SSE endpoint
│ │ │ └── meta.py
│ │ ├── templates/ # Jinja2
│ │ │ ├── base.html
│ │ │ ├── dashboard.html
│ │ │ └── ...
│ │ └── static/ # 樸素 CSS,不引入前端構建
│ ├── models/
│ └── utils/
└── tests/{unit,integration,conftest.py}
6.3 代碼變更影響矩陣
| 層級 | 文件或模組 | 當前職責 | 計劃修改 | 影響 | 驗證 |
tooling | pyproject.toml | 不存在 | 新建:依賴(mcp / httpx / typer / cryptography / keyring / pydantic)、entry points(ai-mcp / ai-mcp-server)、ruff / mypy 配置 | uv sync 可解依賴,CLI 與 server 可被 uv run 拉起 | uv run ai-mcp --help |
storage | storage/db.py | 不存在 | 新建:SQLite 連線、初始化三張表、migration 編號 | 首次運行自動建表 | pytest 覆蓋連線、建表、二次運行不重建 |
storage | storage/crypto.py | 不存在 | 新建:Fernet 包裝、master key 取得(env > keyring > 生成) | api_key 加密寫入 | pytest + strings db.sqlite3 無命中 |
storage | storage/dao.py | 不存在 | 新建:endpoints / models / overrides 三套 CRUD | 編排層僅依賴 DAO | pytest 隔離 DB 覆蓋 CRUD |
providers | providers/base.py | 不存在 | 新建:ProviderAdapter ABC,方法 list_models / chat / embed / image_gen / tts / stt / rerank | 所有 provider 必須實作;為 Anthropic 預留 | mypy + ABC 實例化測試 |
providers | providers/openai_compat.py | 不存在 | 新建:用 httpx 對接 /v1/models 與 /v1/* 路徑;錯誤原樣冒泡 | 覆蓋首期所有上游 | 對 mock httpx 跑單測 + 真實 endpoint smoke |
providers | providers/factory.py | 不存在 | 新建:按 provider_type 路由到 adapter 實例 | 編排層不知道具體 adapter | pytest 覆蓋路由 |
capability | capability/static_map.py | 不存在 | 新建:內建 model_id 模式 → capability set 的字典(覆蓋 OpenAI / Claude / Gemini / DeepSeek / Qwen / Moonshot 等常見前綴) | resolver 第四級兜底 | pytest 覆蓋若干典型 model_id 命中 |
capability | capability/litellm_meta.py | 不存在 | 新建:內建 snapshot + 可選遠端拉取;本地 JSON 快取 | resolver 第三級 | pytest mock 網路 + 離線降級測試 |
capability | capability/resolver.py | 不存在 | 新建:合併四來源;返回時保留 capability_source 標註 | tool 層、CLI 層統一入口 | pytest 覆蓋四來源衝突案例 |
probes | probes/*.py | 不存在 | 新建:10 個 probe 模組 + ProbeResult dataclass + 統一「1」素材 | 探活輸出統一可被 resolver 寫回 | pytest mock adapter + 真實 endpoint 抽測 |
application | application/*.py | 不存在 | 新建:endpoint CRUD / model discovery / probe batch / capability query | CLI 與 MCP tool handler 共用 | pytest 覆蓋編排語義 |
cli | cli.py | 不存在 | 新建:typer app;endpoint / model / meta 子命令 | 用戶可登記、查詢、覆寫 | typer CliRunner 單測 + 真實命令 smoke |
mcp | server.py | 不存在 | 新建:FastMCP 註冊 6 個 tool、設定 instructions | MCP client 可拉起並調用 | mcp inspector + Claude Desktop 真實串通 |
assets | assets/probe/digit_1.png | 不存在 | 新建:白底黑色「1」的 64x64 PNG(生成腳本一次性) | vision 探活素材 | 檔案存在 + 可被 PIL 讀取 |
assets | assets/probe/digit_1.wav | 已納入 | 由 scripts/generate_probe_assets.py 生成;缺失時 stt 探活降級為 skip | stt 探活素材可隨包發布 | file assets/probe/digit_1.wav 顯示 16kHz mono PCM WAV |
storage | storage/db.py(補強) | 新建 | 連線時設 PRAGMA journal_mode=WAL; PRAGMA busy_timeout=5000;新增 probe_jobs 表 schema 與 migration | 支援三進程並發讀寫 | pytest 多進程同時寫測試 |
application | application/probe_jobs.py | 不存在 | 新建:enqueue、list、樂觀鎖 claim、lease 回退 | UI / CLI / MCP 共用入隊與查詢 | pytest 模擬兩個 instance 搶 claim |
application | application/worker.py | 不存在 | 新建:instance_id = uuid4()、claim_one()(含 0.5s 二次確認)、run_loop()、lease_sweeper() | MCP server 與 CLI 共用 worker 代碼 | pytest + 真實 sqlite 並發測試 |
mcp | server.py(補強) | 新建 | 啟動時 asyncio.create_task(worker.run_loop()) 常駐;shutdown 時優雅停止 | Claude Desktop 拉起 MCP 後自動消化探活佇列 | integration 啟動 server 後入隊一個 job,預期被消化 |
cli | cli.py(補強) | 新建 | 新增 ai-mcp ui、ai-mcp worker、ai-mcp endpoint probe 改為入隊 + 同步跑 worker loop 直到清空 | CLI 也能驅動消化,無 MCP server 也能跑 | typer CliRunner + 真實命令 smoke |
web | web/app.py | 不存在 | 新建:FastAPI app factory、Jinja2 模板註冊、靜態目錄掛載、127.0.0.1 綁定預設 | ai-mcp ui 可拉起 uvicorn | httpx async client 對 ASGI app 整合測試 |
web | web/routes/*.py | 不存在 | 新建 9 個頁面 route(Dashboard / Endpoints / Endpoint detail / Models / Model detail / Overrides / Probes / Jobs / Meta);jobs 含 SSE | UI 全部頁面就緒 | 各 route 對 ASGI 跑 smoke + Jinja 渲染 snapshot |
web | web/templates/*.html | 不存在 | 新建:base.html 含側邊導航;每頁一個模板;樸素 CSS,無前端構建 | UI 視覺一致 | 真實瀏覽器打開所有頁面截圖 |
models | models/enums.py / models/schemas.py | 不存在 | 新建:Capability、ProbeStatus、JobStatus(pending/claiming/running/done/failed)、EndpointDTO、ProbeJobDTO 等 | 共用型別契約 | mypy |
6.4 對外契約
- CLI 契約:見 §5.1,命令名與參數穩定;新增子命令不破壞既有。
- MCP tool 契約:6 個 tool 的 name、入參 schema、出參結構為穩定契約;演進時新增可選欄位、不刪除既有欄位。
- Provider 契約:上游 API 響應原樣透傳;僅附加
_meta,不修改 body。
7. 數據與資料庫規劃
7.1 ER 視圖
endpoints 是根表,主鍵 id;存 (name 唯一, base_url, api_key_enc, provider_type, created_at)。
models 多對一引用 endpoints;(endpoint_id, model_id) 聯合主鍵;存 capability 探活結果與 metadata 緩存。
overrides 多對一引用 models;(endpoint_id, model_id, capability_key) 聯合主鍵;存用戶手動覆寫。
probe_runs 記錄歷次探活的 raw 響應,便於追溯;無 FK,按 (endpoint_id, model_id) 字串關聯。
probe_jobs 探活任務佇列;含 claimed_by(worker instance_id)、claimed_at、lease_until、status;UI / CLI / MCP 任一入隊。
7.2 Schema
-- endpoints
CREATE TABLE endpoints (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
base_url TEXT NOT NULL,
api_key_enc BLOB NOT NULL, -- Fernet ciphertext
provider_type TEXT NOT NULL DEFAULT 'openai-compat',
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);
-- models
CREATE TABLE models (
endpoint_id INTEGER NOT NULL REFERENCES endpoints(id) ON DELETE CASCADE,
model_id TEXT NOT NULL,
capabilities_json TEXT NOT NULL DEFAULT '{}', -- {cap: {value, source, probed_at}}
context_length INTEGER,
raw_meta_json TEXT, -- 上游 /v1/models 原始 item
last_discovered_at TEXT NOT NULL DEFAULT (datetime('now')),
PRIMARY KEY (endpoint_id, model_id)
);
-- overrides
CREATE TABLE overrides (
endpoint_id INTEGER NOT NULL,
model_id TEXT NOT NULL,
capability_key TEXT NOT NULL, -- e.g. 'vision', 'context_length'
value_json TEXT NOT NULL,
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
PRIMARY KEY (endpoint_id, model_id, capability_key)
);
-- probe_runs
CREATE TABLE probe_runs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
endpoint_id INTEGER NOT NULL,
model_id TEXT NOT NULL,
capability TEXT NOT NULL,
ok INTEGER NOT NULL,
latency_ms INTEGER,
raw_json TEXT,
error TEXT,
ran_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX idx_probe_runs_lookup ON probe_runs(endpoint_id, model_id, capability, ran_at);
-- probe_jobs (探活任務佇列)
CREATE TABLE probe_jobs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
endpoint_id INTEGER NOT NULL REFERENCES endpoints(id) ON DELETE CASCADE,
model_id TEXT NOT NULL,
capability TEXT NOT NULL, -- 待探活的 capability key
status TEXT NOT NULL DEFAULT 'pending',
-- pending | claiming | running | done | failed
priority INTEGER NOT NULL DEFAULT 0,
claimed_by TEXT, -- worker instance_id (UUID)
claimed_at TEXT,
lease_until TEXT, -- claim 後預估完成時限,超時可被回退
finished_at TEXT,
error TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX idx_probe_jobs_pending ON probe_jobs(status, priority DESC, id ASC)
WHERE status='pending';
CREATE INDEX idx_probe_jobs_lease ON probe_jobs(status, lease_until)
WHERE status IN ('claiming','running');
-- 連線初始化
PRAGMA journal_mode=WAL;
PRAGMA busy_timeout=5000;
-- migrations
CREATE TABLE _schema_version (version INTEGER PRIMARY KEY);
7.3 變更與兼容性
| 對象 | 變更 | 原因 | 兼容性 | 測試 |
endpoints | 新建表 | 持久化用戶登記的上游 | 初版,無歷史 | 建表 + CRUD + name UNIQUE 衝突測試 |
models | 新建表 | 緩存模型發現結果與能力 | 初版 | 聯合主鍵 + 級聯刪除測試 |
overrides | 新建表 | 用戶覆寫優先級最高 | 初版 | resolver 衝突合併測試 |
probe_runs | 新建表 | 探活歷史追溯 | 初版,可選表 | 插入 + 按時間查詢 |
probe_jobs | 新建表 | 探活異步化的任務佇列;UI / CLI / MCP 共用 | 初版 | 樂觀鎖並發測試 + lease 超時回退測試 |
| WAL journal mode | 所有連線開啟 | 三進程並發讀寫共享 DB | 單機 SQLite 標配;多寫者透過 busy_timeout 排隊 | 多進程同時寫測試 |
_schema_version | 新建表 | 後續 migration 編號 | 初版設為 1 | migration 函數冪等測試 |
7.4 加密、回滾、隱私
- 加密:
api_key_enc 使用 Fernet(AES-128-CBC + HMAC);master key 來自 env AI_MCP_MASTER_KEY > 系統 keyring > 首次運行自動生成並寫入 keyring。
- 回滾:MVP schema 不需 down migration;直接刪除
~/.ai-mcp-server/db.sqlite3 重建即可(用戶會被警示)。
- 遺留可讀性:master key 一旦遺失,所有
api_key_enc 不可解密 → CLI 在加密失敗時提示用戶 re-add endpoint。
- 隱私:無遙測;所有 IO 限於本機 SQLite、本機 keyring、向用戶登記的 base_url 發 HTTPS。
- 日誌:所有日誌路徑禁止輸出
api_key 明文與 Authorization header;錯誤響應原樣冒泡時需 mask 上游 echo back 的 key(如有)。
8. 實施計劃
分 9 個里程碑,每個獨立可測。
| # | 里程碑 | 交付物 | 主要文件 | 驗證 |
| M1 | 腳手架 | pyproject.toml + 目錄骨架 + typer CLI 入口 | pyproject.toml, src/ai_mcp_server/{cli,__init__}.py | uv sync, uv run ai-mcp --help |
| M2 | 儲存層 | SQLite schema (含 WAL + busy_timeout)、Fernet 加密、DAO | storage/*.py | pytest 隔離 DB + strings 驗證無明文 + 多進程同時寫 |
| M3 | Provider ABC + OpenAI 兼容 | ProviderAdapter + OpenAICompatAdapter + factory | providers/*.py | pytest mock httpx + 對真實 endpoint smoke |
| M4 | CLI endpoint 子命令 | add / list / show / remove | cli.py, application/endpoint_service.py | typer CliRunner + 真實命令跑通 |
| M5 | Capability 來源層 | static_map + litellm_meta + resolver(四來源融合) | capability/*.py | pytest 覆蓋四來源衝突 |
| M6 | 探活 + probe_jobs 入隊 | 10 個 probe + probe_jobs 表 + enqueue API + 探活素材 | probes/*.py, application/probe_jobs.py | pytest 覆蓋 enqueue + 真實 endpoint 探活 |
| M7 | Worker(樂觀鎖) | worker.py 含 instance_id、claim_one 含 0.5s 二次確認、run_loop、lease_sweeper;CLI ai-mcp endpoint probe 改為入隊 + 同步消化 | application/worker.py | 並發測試:兩 instance 同時搶 100 個 job,無雙跑、無遺漏 |
| M8 | MCP Server + 背景 worker | FastMCP 註冊 6 個 tool + instructions + stdio 入口 + 啟動時 spawn worker task | server.py | mcp inspector + Claude Desktop 真實串通 + 入隊一個 job 預期被消化 |
| M9 | Web UI | ai-mcp ui 子命令 + FastAPI app + 9 個頁面 + Jinja2 模板 + SSE 進度 | web/** | 瀏覽器跑通 9 個頁面截圖 + SSE 進度刷新 |
8.1 依賴與順序
- M1 → M2 → M3 / M5(可並行)→ M4 → M6 → M7 → M8 / M9(可並行)。
- M7 依賴 M6 的 probe_jobs 表與 enqueue API。
- M8 / M9 都依賴 M7 的 worker;M8 用內建 task,M9 不啟動 worker(只入隊)。
8.2 回滾
- 任何里程碑失敗,回退至上一里程碑 tag;MVP 階段 SQLite 直接刪庫重建。
9. 開發風險(Developer Reality)
| 風險 | 觸發場景 | 緩解 |
| 探活 false negative | 上游 429 / 排隊 / 暫時超時被當成「不支援」 | 區分 ok / not_supported / rate_limited / timeout / error 五態;rate_limited / timeout 不污染既有 true |
| 探活 false positive | 上游對未知 tool_call schema 返回空字串而非報錯 | 探活時校驗響應結構(tool_call 必須有 tool_calls 欄位、vision 必須命中「1」) |
| 不同 endpoint 的 model_id 撞名 | OpenAI 和 OpenRouter 都有 gpt-4o | 所有對外 ID 統一為 {endpoint_name}/{model_id};list_models / invoke_model 均接受此複合 id |
| OpenAI 兼容協議方言差異 | Anthropic-via-OpenAI、Gemini-via-OpenAI、本地 vLLM 在 tool_calls / finish_reason 欄位上略有不同 | 透傳不改寫;在 README 標明已知差異;後續按需在 adapter 內加 quirk 開關 |
| master key 遺失 | 用戶清空 keychain 或換機 | CLI 解密失敗時清晰報錯:「請設置 AI_MCP_MASTER_KEY 或重新登記 endpoint」 |
| MCP stdio 啟動失敗難診斷 | 用戶在 Claude Desktop 配置後黑屏 | 提供 ai-mcp-server --debug,輸出到 stderr;README 給 Claude Desktop config 範例 |
| LiteLLM metadata 結構演進 | 上游欄位更名 | internal mapping 函數隔離;解析失敗降級為「無 metadata」,不影響其他來源 |
| image_gen / tts 探活費用 | 批量探活時可能花幾分錢~幾毛錢 | CLI endpoint probe 顯示「以下能力探活會產生上游費用 (image_gen, tts),是否繼續? [y/N]」 |
| 非流式對長響應的影響 | 長文生成可能讓 MCP tool call 看起來「卡住」 | 記錄到 README;後續用 MCP progress notification 緩解 |
| Anthropic 預留接口走樣 | 未來實作時發現 ABC 不夠用 | ABC 方法簽名按「OpenAI 兼容超集」設計;message 結構用 list[dict],不過度封裝 |
| 樂觀鎖未生效導致雙跑 | 兩個 worker 同時跑 UPDATE ... WHERE claimed_by IS NULL | 條件 UPDATE 在 SQLite 是原子的;再加 0.5s 二次讀;最終 status 流轉只允許 pending → claiming → running → done/failed 一向單調 |
| Worker 孤兒任務 | worker 崩潰時 status='running' 永不結束 | lease_sweeper 定期掃描 lease_until < now 的 claiming/running 重置為 pending;探活開始時設 lease_until = now + 5min,過程中可續租 |
| 三進程共寫 SQLite | UI / CLI / MCP server 同時寫 | WAL 模式 + busy_timeout=5000;長交易禁用;寫操作短小 |
| SSE 連線斷開後 UI 看不到進度 | 瀏覽器或網路抖動 | SSE 帶 Last-Event-Id;前端重連時用最後一個 job id 補拉 |
| UI 進程意外被外網訪問 | 用戶誤改 --host 0.0.0.0 | 預設 127.0.0.1;改為非 loopback 時 CLI 加大紅警告 |
| UI 與 MCP server 共寫 capabilities | 探活 worker 與用戶 override 同時寫 capabilities_json | 所有寫入 capabilities 都走 resolver;override 是覆蓋層不寫主表的同一欄位 |
10. 測試與驗收
10.1 測試矩陣
| 層級 | 類型 | 內容 | 命令 |
| 儲存 | unit | schema 建表冪等、Fernet 加密解密、DAO CRUD | uv run pytest tests/unit/storage |
| provider | unit | OpenAI 兼容 list_models / chat / embed 各端點請求拼裝與錯誤透傳 | uv run pytest tests/unit/providers |
| capability | unit | resolver 四來源衝突優先級、static_map 命中、litellm 解析、離線降級 | uv run pytest tests/unit/capability |
| probe | unit | 每個 probe 對 mock adapter 的 ok / not_supported / rate_limited 路徑 | uv run pytest tests/unit/probes |
| cli | integration | typer CliRunner 跑 endpoint add / list / probe / model override | uv run pytest tests/integration/cli |
| MCP server | integration | mcp SDK in-memory transport 跑 6 個 tool 的 schema 與響應;server 啟動時 worker task 消化已入隊 job | uv run pytest tests/integration/mcp |
| worker | integration | 兩個 worker 實例同時啟動,搶 100 個 pending job:無雙跑、無遺漏;lease_sweeper 能把孤兒任務回退 | uv run pytest tests/integration/worker |
| web | integration | httpx 對 ASGI app 跑 9 個頁面 200 OK;form POST 寫操作落 DB;SSE 接收一個 job 完成事件 | uv run pytest tests/integration/web |
| lint / type | static | ruff / mypy | uv run ruff check src tests && uv run mypy src |
10.2 真實串通驗收(硬門禁)
- 登記至少 2 個真實 endpoint(如 OpenRouter + 本地 Ollama):
uv run ai-mcp endpoint add --name openrouter --base-url https://openrouter.ai/api/v1 --key sk-...
- 跑探活:
uv run ai-mcp endpoint probe --all(CLI 模式:入隊 + 同步消化 worker loop),預期 ≥90% model 拿到至少 1 個 ok 的 capability。
- 用 mcp inspector 拉起:
npx @modelcontextprotocol/inspector uv run ai-mcp-server,校驗 6 個 tool 出現、schema 正確。
- 啟動 web UI:
uv run ai-mcp ui,瀏覽器打開 http://127.0.0.1:8765/,依序驗證 9 個頁面渲染正常。
- 在 UI 的「Endpoint 詳情」點「探活」→
/jobs 頁面看 SSE 進度條推進 → 跑完後 Models 表 capability 標籤更新。
- 並發驗證:UI 點探活的同時,CLI 也跑
ai-mcp endpoint probe --all,預期無雙跑(樂觀鎖生效),所有 job 最終都 done 或 failed。
- 在 Claude Desktop 配置:
{
"mcpServers": {
"ai-mcp": {
"command": "uv",
"args": ["run", "--directory", "/path/to/ai-mcp-server", "ai-mcp-server"]
}
}
}
- 在 Claude Desktop 對話中依次驗證:
- chat 透傳:要求 Claude 透過
invoke_model 走 OpenRouter 上某 chat model,得到回答。
- vision 透傳:傳一張圖,要求 Claude 路由到具備 vision 能力的 model 進行識別。
- embedding 透傳:要求 Claude 透過
invoke_model 取一段文字的 embedding,返回向量長度正確。
- model_performance:要求 Claude 查詢近期模型成功率與首字延遲,確認返回本地聚合指標。
- refresh_endpoint:透過 MCP 調用此 tool 入隊;確認 MCP server 進程內的背景 worker 自動消化並寫回。
- add_models:對不暴露
/v1/models 的測試 endpoint 手動登記一個模型,確認 list_models 可見。
- 驗證
~/.ai-mcp-server/db.sqlite3 中 api_key_enc 為二進位密文:strings db.sqlite3 | grep -E '^sk-' 無命中。
10.3 不能宣告完成的情況
- 10.2 任一步驟未跑通,僅靠 pytest 通過不算完成。
- 探活對至少一個真實 endpoint 全部失敗。
- api_key 在任何路徑(DB / 日誌 / stderr / MCP 響應)以明文出現。
11. 安全、隱私與權限
- 敏感資料:api_key 加密儲存;master key 在 system keyring 或 env;任何路徑禁止明文輸出。
- 留存:所有資料只在本機;無雲端、無遙測。
- 日誌脫敏:日誌中
Authorization header 與 api_key 字段強制 mask;錯誤透傳前掃描上游 echo 並 mask。
- 權限:MCP server 本身為當前用戶進程;無多用戶 / 無權限分層。
- 濫用風險:用戶可能用 invoke_model 跑昂貴模型(如 image_gen)→ usage_guide 中明示「該操作可能產生上游費用」,但服務側不做攔截。
- 探活費用透明:CLI
endpoint probe 在執行涉及付費能力(image_gen / tts)前提示用戶確認。
12. 風險、開放問題與決策
12.1 主要風險與緩解
| 風險 | 嚴重度 | 緩解 |
| 能力探活的 false positive / false negative | 高 | 五態 ProbeStatus + 結構校驗 + 不污染既有 true |
| master key 遺失導致 api_key 不可解密 | 中 | 清晰報錯 + re-add 流程 |
| OpenAI 兼容方言差異造成 invoke 失敗 | 中 | 透傳不改寫 + adapter 內 quirk 開關空間 |
| image_gen / tts 探活造成意外費用 | 中 | CLI 二次確認 + usage_guide 明示 |
| MCP stdio 配置失敗無法診斷 | 低 | --debug stderr 輸出 + 文檔 config 範例 |
| LiteLLM metadata 結構演進 | 低 | 解析失敗降級,不影響其他來源 |
| 樂觀鎖實際失效 | 中 | 並發 pytest 覆蓋;status 流轉單調保證最差只是少跑、不會雙跑 |
| UI 進程被外網訪問 | 低 | 預設 127.0.0.1;改 host 時 CLI 警告 |
| SQLite 寫衝突 | 低 | WAL + busy_timeout,已驗證在 macOS 本地用戶下穩定 |
12.2 開放問題
- LiteLLM metadata 是內建 snapshot 還是首次遠端拉取?(默認 snapshot + 可選 refresh)
- 是否在 list_models 出參中暴露上游
raw_meta_json?(默認否,按需展開)
- 是否提供
ai-mcp-server --http 模式給遠端 MCP client?(默認 P2+ 階段)
12.3 已確認決策
見 §0「已確認決策」表格。
13. 完成定義(Definition of Done)
產品級
- 用戶可登記 ≥2 endpoint 並完成 10 種能力探活。
- Claude Desktop 中可看到 6 個 tool,並能透過
invoke_model 完成 chat / vision / embedding 三條路徑。
usage_guide 動態反映當前能力分佈。
- Web UI 9 個頁面可用,SSE 進度條能反映探活進展。
- UI / CLI / MCP 三入口同時操作不衝突(樂觀鎖生效)。
架構級
- 四層分明:MCP / CLI / Web 入口 → application → providers / capability / probes → storage。
- 「解讀 X」原則:三進程不共享記憶體、共享 SQLite (WAL)。
- ProviderAdapter ABC 可塞入 Anthropic 實作而不改上層。
- resolver 四來源優先級可診斷(每個 capability 帶 source 標籤)。
- Worker 樂觀鎖 + lease 回退驗證通過。
測試級
- unit + integration pytest 全綠;ruff / mypy 無 error。
- 10.2 真實串通驗收全部走通並留下截圖或 transcript。
strings db.sqlite3 | grep -E '^sk-' 無命中。
文檔與發布
- README 含安裝、CLI 範例、Claude Desktop 配置範例、故障排查。
- 本 PRD 與 AGENTS.md 隨倉庫一起發布。
- CHANGELOG 由
update-changelog skill 自動維護。
回滾開關
- 用戶可隨時刪除
~/.ai-mcp-server/ 與 system keyring 中的 ai-mcp-server 條目,徹底重置。
- MCP client 端只需從配置中移除
ai-mcp 條目即可解除接入。