# MCP Store

> Browse and install remote MCP servers into Claude Code, Codex, Hermes or ai-chat; publish new MCP entries to the registry.

# MCP Store (sub-tab MCP trong tab Store)

## Mục đích

Settings → Store → sub-tab **MCP** (desktop và Web Dashboard; trên web thao tác chạy trên thiết bị đang xem qua relay, xem mục "Web Dashboard" bên dưới). Duyệt danh sách **MCP server từ xa** (remote, Streamable HTTP) trên registry, deploy MCP mới bằng cách nhập URL + kiểu xác thực (giống deploy plugin/skill), và cài MCP vào nhiều engine AI khác nhau:

- **Cài tự động** (app tự làm trên máy này): Claude Code, Codex, Hermes, ai-chat (Kazuo). Chọn **global** (mọi dự án) hoặc **local** (một folder: mặc định workspace đang mở trong ai-chat, có nút "Chọn folder khác"). Hermes chỉ có global.
- **Nhờ AI cài**: chép lời nhắn (có URL, kiểu xác thực, lệnh tham khảo, KHÔNG có token) dán vào AI agent.
- **Tự gõ lệnh**: các bước + lệnh/đoạn cấu hình có nút Chép. Claude.ai, ChatGPT, Hermes server (Telegram) chỉ có hướng dẫn (không cài tự động được).
- Hiện trạng thái **Đã cài** theo từng engine (global/local) và nút **Gỡ**.
- **Icon**: lúc Deploy/Sửa chọn file ảnh (png/svg/jpg/webp, ≤ 256 KB) qua hộp thoại hệ thống, có xem trước. Icon hiện trên thẻ MCP và đầu modal cài; không có icon → 🔌.
- **Sửa (Edit)**: devMode + đúng tác giả → nút ✏️ trên thẻ mở lại dialog Deploy với dữ liệu cũ. **Không đổi được `name`** (định danh trong config của máy đã cài). Không chọn icon mới → giữ icon cũ. Nút ✏️/🗑️ chỉ hiện trên MCP của chính username đang đăng nhập (`mcp:me`).

## File liên quan

- `electron/mcp-config-edit.ts` — logic thuần (test được bằng `node --test`): validate tên (`^[A-Za-z0-9_-]{1,64}$`), URL (https, http chỉ cho localhost, cấm user:pass), token (1 dòng), header; sửa TOML của Codex bằng text (`upsertTomlMcp/removeTomlMcp/hasTomlMcp` — chỉ đụng khối `[mcp_servers.<name>]` + sub-table, giữ nguyên mọi dòng khác kể cả comment, giữ CRLF); `claudeHasMcp` đọc `~/.claude.json` (user = `mcpServers`, local = `projects[<path dạng C:/x>].mcpServers`, so khớp không phân biệt hoa thường).
- `electron/mcp-config-edit.test.ts` — unit test cho file trên.
- `electron/mcp-store.ts` — registry + installer:
  - Registry: `plugin-store/mcp-index.json` (chung folder với `skills-index.json`), entry `McpIndexEntry { id: <author>/<name>, name, title, description, url, auth: none|oauth|bearer, headerName?, iconFileId?, iconMime?, author, uploadedAt }`. **Chỉ metadata + icon, không có zip, không bao giờ có token.** `fetchEntries()` lọc theo `authors.json` (`fetchApprovedAuthors`) + `sanitizeEntry` validate lại mọi field (registry dùng key ghi công khai → không tin; icon chỉ giữ khi `iconFileId` đúng charset và `iconMime` thuộc whitelist ảnh). `listMcps()` = `fetchEntries()` + tải icon thành `iconDataUri` (qua `fetchStoreMediaDataUri`, cache theo fileId, lỗi → không icon).
  - `deployMcp(input)`: gate devMode ở IPC + `requireDeployAuthor`; id = `<username>/<name>` nên chỉ sửa được entry của chính mình. `edit: false` mà id đã tồn tại → lỗi (chống ghi đè nhầm); `edit: true` mà chưa tồn tại → lỗi. Icon mới upload vào `plugin-store/mcp/<md5(id)[0..8]>/icon-<md5(nội dung)[0..12]>.<ext>` (tên theo hash nội dung → ảnh mới = fileId mới, cache không bị cũ); không có icon mới → giữ `iconFileId/iconMime` cũ. `undeployMcp` chỉ cho xoá entry của chính username đang đăng nhập. `currentAuthor()` cho UI biết username.
  - Install/uninstall luôn tra lại entry từ registry theo `id` qua `fetchEntries()` (không tải icon; renderer không truyền URL).
  - **Claude Code**: CLI `claude mcp add --transport http --scope user|local <name> <url> [--header "H: v"]` (`--header` là variadic → đặt SAU positional). Local = chạy với `cwd = folder`. Cài lại = `remove` trước rồi `add`. Tìm CLI: `~/.local/bin/claude(.exe)` (chỗ ai-chat cài) rồi PATH. Không có CLI → báo lỗi, gợi ý tab Tự gõ lệnh.
  - **Codex**: CLI `codex mcp add <name> --url <url>` khi: global + không có token + có binary native trên PATH (`codex.exe` trên Windows — shim `.cmd` bị bỏ qua để không cần shell). Còn lại (có token / local / không có CLI) → sửa `config.toml` trực tiếp, token ghi vào `http_headers`. Global: `$CODEX_HOME/config.toml` (mặc định `~/.codex`); local: `<folder>/.codex/config.toml` (Codex chỉ nạp khi folder được đánh dấu **trusted**).
  - **Hermes**: CLI `hermes mcp add` hỏi tương tác (đã thử: stdin null → thoát không ghi gì) nên luôn sửa `$HERMES_HOME/config.yaml` (mặc định `~/.hermes`) bằng thư viện `yaml` (giữ comment). OAuth → `auth: oauth`; token → `headers`.
  - **ai-chat**: ghi vào cả cấu hình Claude Code lẫn Codex (engine Claude của ai-chat dùng `settingSources` mặc định `user/project/local` nên đọc `~/.claude.json`; engine Codex chỉ thêm `--config` nên vẫn đọc `~/.codex/config.toml` của user). Đã cài = cài ở cả hai.
  - Ghi file: sao lưu `<file>.kazuo-bak` rồi ghi tạm + rename (atomic).
  - `mcpStatus(names, folder?)` đọc file cấu hình (chỉ đọc, nhanh, không chạy CLI). Tôn trọng `CLAUDE_CONFIG_DIR`, `CODEX_HOME`, `HERMES_HOME`.
- `electron/mcp-ipc.ts` — `mcp:list/me/status/cli/install/uninstall/pick-folder/pick-icon/deploy/undeploy` (`deploy/undeploy` gate `devMode`). `mcp:pick-icon` mở hộp thoại chọn ảnh, validate (đuôi + ≤ 256 KB) rồi trả `{ path, dataUri }` để xem trước; main nhớ path vừa chọn và `mcp:deploy` **chỉ nhận `iconPath` trùng path đó** → renderer không thể bắt main upload file tuỳ ý lên registry công khai. Đăng ký trong `electron/main.ts` cạnh `registerSkillIpcHandlers()`.
- `electron/preload.ts` (`electronAPI.mcp`), `src/lib/types.ts` (`McpIndexEntry`, `McpInstallRequest`, `McpDeployInput`, `McpEngineStatus`, `ElectronAPI.mcp`), `src/lib/ipc.ts` (`mcpAPI`), `src/lib/electron-api-web.ts` (nối sang route `/mcp/*`; chỉ `pickFolder`/`pickIcon` vẫn `notOnWeb`).
- `src/lib/mcp-guides.ts` — danh sách 7 engine (`GUIDE_ENGINES`: auto/ai/globalOnly/noBearer) + `manualCommand()` sinh lệnh/đoạn config với placeholder `<YOUR_TOKEN>`, `fillTemplate()`.
- `src/config/McpStore.svelte` — grid thẻ MCP (badge kiểu xác thực, tác giả, "✓ engine đã cài"), search, dialog Deploy (devMode), nút gỡ khỏi Store (devMode).
- `src/config/McpInstallModal.svelte` — modal cài: lưới "Bạn dùng AI nào?" → panel "Kết nối {engine}" với tab Cài tự động / Nhờ AI cài / Tự gõ lệnh.
- `src/config/ConfigApp.svelte` — `storeSub` thêm `'mcp'`, nút sub-tab hiện ở cả desktop lẫn web.
- `src/locales/{en,vi}.json5` — `store.mcpTab`, `store.mcp.*` (kể cả `store.mcp.engines.<id>.{name,sub,desc,step1..3,after}`).
- `package.json` — thêm dependency `yaml`.

## Giới hạn / rủi ro tồn dư

- Chỉ hỗ trợ MCP **remote qua URL** (không có stdio/`npx`).
- Token được ghi **dạng chữ thường** vào file cấu hình của engine (giống cách các engine tự làm). UI cảnh báo không commit file local có token lên git. Với Claude, local scope nằm trong `~/.claude.json` (không nằm trong folder dự án). Còn Codex local thì nằm trong `<folder>/.codex/config.toml`.
- Token đi qua tham số dòng lệnh của `claude mcp add` (có thể thấy trong danh sách tiến trình trong tích tắc).
- TOML editor chỉ nhận dạng dạng table `[mcp_servers.<name>]`. Nếu user tự khai báo kiểu inline/dotted-key (`mcp_servers.x.url = …`) thì trạng thái có thể sai, và cài thêm có thể tạo khoá trùng.
- Claude.ai / ChatGPT custom connector không hỗ trợ token riêng → UI báo không dùng được với MCP kiểu bearer.
- Không có nút "Xoá icon" (chỉ thay được bằng ảnh khác). File icon cũ trên registry không bị dọn khi thay (giống plugin store).
- SVG được hiển thị qua `<img src="data:...">` nên script trong SVG không chạy.
- Chưa smoke test end-to-end trong app chạy thật (cần registry + Dev Mode). Installer đã được kiểm chứng riêng lẻ với CLI thật trong thư mục tạm: Claude (`CLAUDE_CONFIG_DIR`), Codex (`CODEX_HOME`, `codex mcp list --json` đọc đúng `http_headers`), Hermes (`HERMES_HOME`, `hermes mcp list` thấy cả oauth lẫn header).

### Web Dashboard

`electron/dashboard-server.ts` có các route cùng nghĩa với IPC `mcp:*`: `GET /mcp/list`, `GET /mcp/me` (`{author}`), `POST /mcp/status`, `GET /mcp/cli`, `POST /mcp/install`, `POST /mcp/uninstall`, `POST /mcp/deploy`, `POST /mcp/undeploy`. Mọi `/api/*` đã bắt token như các route khác. Install/uninstall ghi file cấu hình của engine trên **thiết bị đang xem** (không phải máy chạy trình duyệt). Token bearer của MCP đi qua relay (TLS, nhưng qua edge Cloudflare) rồi mới tới thiết bị.

- Không có hộp thoại hệ thống trong trình duyệt: phạm vi local nhập đường dẫn folder trên thiết bị bằng ô text (mặc định là workspace ai-chat, qua `skill/active-workspace`); `requireFolder` vẫn bắt đường dẫn tuyệt đối và phải tồn tại. Icon khi Deploy/Sửa chọn bằng `<input type=file>` rồi gửi dạng data URI (`iconDataUri`, ≤ 256 KB); thiết bị ghi ra file tạm bằng `iconFileFromDataUri`, `deployMcp` kiểm tra lại như file chọn, rồi xoá file tạm. `iconPath` do trình duyệt gửi bị bỏ.
- Deploy/Undeploy chỉ gate bằng `devMode` — cùng điểm yếu đã biết với `POST /store/deploy` (xem `dashboard-server-security.md`: `devMode` ghi được từ web nên ai có write-token đều bật được). Cần phát hành lại app desktop thì thiết bị mới có các route này; thiết bị cũ trả lỗi 404 ở tab MCP.

### Tốc độ tải danh sách

`listMcps()` dùng `StoreListCache` (TTL 5 phút, stale-while-revalidate, xoá ngay khi Deploy/Undeploy). `fetchEntries()` đọc `mcp-index.json` và `authors.json` song song (đường cài/gỡ vẫn đọc registry trực tiếp, không qua cache). `ensureStoreFolderId` (`store-conn.ts`) nhớ id thư mục gốc 10 phút để mỗi lần đọc index bớt một request tới registry. Registry chậm vì mỗi request mất 1–2 giây, xem `files-api.md`.

### Built-in Kazuo AI MCP

Store always includes the trusted `kazuo-ai/kazuo-ai` entry, including when the remote registry is unavailable. It supports automatic installation into Claude Code, Codex, AI Chat (both configs), and Hermes (global only). It is not a remote URL and cannot be edited/published through the registry form.

The installer derives paths from the running app and writes stdio configuration using `process.execPath` with `ELECTRON_RUN_AS_NODE=1`, plus explicit user-data and workspace paths. No shell or system Node installation is required. Existing MCP entries are preserved and config backups follow the normal Store write path. Restart the client/MCP connection after installation. Live application tools require Kazuo AI to be running.

`npm run build:mcp` installs locked production MCP dependencies and compiles the server; desktop builds include the server and dependencies under `resources/mcp-server`. Changes to this Store integration require rebuilding the desktop app, not just updating the AI Chat plugin.
