cli-claw-kit 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +245 -0
- package/config/default-groups.json +1 -0
- package/config/global-agents-md.template.md +37 -0
- package/config/mount-allowlist.json +11 -0
- package/container/Dockerfile +160 -0
- package/container/agent-runner/dist/.tsbuildinfo +1 -0
- package/container/agent-runner/dist/agent-definitions.js +22 -0
- package/container/agent-runner/dist/channel-prefixes.js +16 -0
- package/container/agent-runner/dist/codex-config.js +29 -0
- package/container/agent-runner/dist/image-detector.js +96 -0
- package/container/agent-runner/dist/index.js +2587 -0
- package/container/agent-runner/dist/mcp-tools.js +1076 -0
- package/container/agent-runner/dist/stream-event.types.js +5 -0
- package/container/agent-runner/dist/stream-processor.js +867 -0
- package/container/agent-runner/dist/types.js +6 -0
- package/container/agent-runner/dist/utils.js +115 -0
- package/container/agent-runner/package.json +36 -0
- package/container/agent-runner/prompts/security-rules.md +31 -0
- package/container/agent-runner/src/agent-definitions.ts +27 -0
- package/container/agent-runner/src/channel-prefixes.ts +16 -0
- package/container/agent-runner/src/codex-config.ts +40 -0
- package/container/agent-runner/src/image-detector.ts +116 -0
- package/container/agent-runner/src/index.ts +3107 -0
- package/container/agent-runner/src/mcp-tools.ts +1295 -0
- package/container/agent-runner/src/stream-event.types.ts +10 -0
- package/container/agent-runner/src/stream-processor.ts +932 -0
- package/container/agent-runner/src/types.ts +75 -0
- package/container/agent-runner/src/utils.ts +114 -0
- package/container/agent-runner/tsconfig.json +17 -0
- package/container/build.sh +28 -0
- package/container/entrypoint.sh +64 -0
- package/container/skills/agent-browser/SKILL.md +159 -0
- package/container/skills/install-skill/SKILL.md +64 -0
- package/container/skills/post-test-cleanup/SKILL.md +121 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/agent-output-parser.js +459 -0
- package/dist/app-root.js +52 -0
- package/dist/assistant-meta-footer.js +1 -0
- package/dist/auth.js +91 -0
- package/dist/billing.js +694 -0
- package/dist/channel-prefixes.js +16 -0
- package/dist/cli.js +86 -0
- package/dist/commands.js +79 -0
- package/dist/config.js +120 -0
- package/dist/container-runner.js +981 -0
- package/dist/daily-summary.js +210 -0
- package/dist/db.js +3683 -0
- package/dist/dingtalk.js +1347 -0
- package/dist/feishu-markdown-style.js +97 -0
- package/dist/feishu-streaming-card.js +1875 -0
- package/dist/feishu.js +1628 -0
- package/dist/file-manager.js +270 -0
- package/dist/group-queue.js +1070 -0
- package/dist/group-runtime.js +35 -0
- package/dist/host-workspace-cwd.js +85 -0
- package/dist/im-channel.js +384 -0
- package/dist/im-command-utils.js +142 -0
- package/dist/im-downloader.js +45 -0
- package/dist/im-manager.js +527 -0
- package/dist/im-utils.js +53 -0
- package/dist/image-detector.js +96 -0
- package/dist/index.js +5828 -0
- package/dist/logger.js +22 -0
- package/dist/mcp-utils.js +66 -0
- package/dist/message-attachments.js +69 -0
- package/dist/message-notifier.js +36 -0
- package/dist/middleware/auth.js +85 -0
- package/dist/mount-security.js +315 -0
- package/dist/permissions.js +67 -0
- package/dist/project-memory.js +6 -0
- package/dist/provider-pool.js +189 -0
- package/dist/qq.js +826 -0
- package/dist/reset-admin.js +42 -0
- package/dist/routes/admin.js +543 -0
- package/dist/routes/agent-definitions.js +241 -0
- package/dist/routes/agents.js +533 -0
- package/dist/routes/auth.js +675 -0
- package/dist/routes/billing.js +490 -0
- package/dist/routes/browse.js +210 -0
- package/dist/routes/bug-report.js +387 -0
- package/dist/routes/config.js +1868 -0
- package/dist/routes/files.js +671 -0
- package/dist/routes/groups.js +1367 -0
- package/dist/routes/mcp-servers.js +320 -0
- package/dist/routes/memory.js +523 -0
- package/dist/routes/monitor.js +307 -0
- package/dist/routes/skills.js +777 -0
- package/dist/routes/tasks.js +509 -0
- package/dist/routes/usage.js +64 -0
- package/dist/routes/workspace-config.js +458 -0
- package/dist/runtime-build.js +112 -0
- package/dist/runtime-command-handler.js +189 -0
- package/dist/runtime-command-registry.js +1 -0
- package/dist/runtime-config.js +1777 -0
- package/dist/runtime-identity.js +52 -0
- package/dist/schemas.js +590 -0
- package/dist/script-runner.js +64 -0
- package/dist/sdk-query.js +82 -0
- package/dist/skill-utils.js +145 -0
- package/dist/sqlite-compat.js +19 -0
- package/dist/stream-event.types.js +5 -0
- package/dist/streaming-runtime-meta.js +29 -0
- package/dist/task-scheduler.js +695 -0
- package/dist/task-utils.js +13 -0
- package/dist/telegram-pairing.js +59 -0
- package/dist/telegram.js +897 -0
- package/dist/terminal-manager.js +307 -0
- package/dist/tool-step-display.js +1 -0
- package/dist/types.js +1 -0
- package/dist/utils.js +85 -0
- package/dist/web-context.js +161 -0
- package/dist/web.js +1377 -0
- package/dist/wechat-crypto.js +182 -0
- package/dist/wechat.js +589 -0
- package/dist/workspace-runtime-reset.js +35 -0
- package/package.json +107 -0
- package/shared/assistant-meta-footer.ts +127 -0
- package/shared/channel-prefixes.ts +16 -0
- package/shared/dist/assistant-meta-footer.d.ts +29 -0
- package/shared/dist/assistant-meta-footer.js +85 -0
- package/shared/dist/channel-prefixes.d.ts +4 -0
- package/shared/dist/channel-prefixes.js +16 -0
- package/shared/dist/image-detector.d.ts +20 -0
- package/shared/dist/image-detector.js +96 -0
- package/shared/dist/runtime-command-registry.d.ts +38 -0
- package/shared/dist/runtime-command-registry.js +185 -0
- package/shared/dist/stream-event.d.ts +65 -0
- package/shared/dist/stream-event.js +8 -0
- package/shared/dist/tool-step-display.d.ts +4 -0
- package/shared/dist/tool-step-display.js +11 -0
- package/shared/image-detector.ts +116 -0
- package/shared/runtime-command-registry.ts +252 -0
- package/shared/stream-event.ts +67 -0
- package/shared/tool-step-display.ts +21 -0
- package/shared/tsconfig.json +24 -0
- package/web/dist/assets/BillingPage-B1wBR_o-.js +52 -0
- package/web/dist/assets/ChatPage-6GBZ9nXN.css +32 -0
- package/web/dist/assets/ChatPage-BOJcXtaj.js +161 -0
- package/web/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/web/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/web/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/web/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/web/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/web/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/web/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/web/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/web/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/web/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/web/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/web/dist/assets/SettingsPage-DoY7FoZ_.js +153 -0
- package/web/dist/assets/ShareImageDialog-C1ga8b7l.js +22 -0
- package/web/dist/assets/TasksPage-CRivnNsx.js +14 -0
- package/web/dist/assets/_basePickBy-Bf-bSoS9.js +1 -0
- package/web/dist/assets/_baseUniq-zAOaCuKw.js +1 -0
- package/web/dist/assets/arc-Dm9mVQ9U.js +1 -0
- package/web/dist/assets/architectureDiagram-2XIMDMQ5-BLmzX1wr.js +36 -0
- package/web/dist/assets/band-CquvqAHh.js +1 -0
- package/web/dist/assets/blockDiagram-WCTKOSBZ-B9pcqm3j.js +132 -0
- package/web/dist/assets/c4Diagram-IC4MRINW-Cytx1q3b.js +10 -0
- package/web/dist/assets/channel-BOVj73LR.js +1 -0
- package/web/dist/assets/channel-meta-CQD0Pei-.js +41 -0
- package/web/dist/assets/chunk-4BX2VUAB-0ToDr6RE.js +1 -0
- package/web/dist/assets/chunk-55IACEB6-DQDjnXfS.js +1 -0
- package/web/dist/assets/chunk-FMBD7UC4-Di8ABm6c.js +15 -0
- package/web/dist/assets/chunk-JSJVCQXG-BZQN6rnX.js +1 -0
- package/web/dist/assets/chunk-KX2RTZJC-zBbcpaN_.js +1 -0
- package/web/dist/assets/chunk-NQ4KR5QH-BCrLoU88.js +220 -0
- package/web/dist/assets/chunk-QZHKN3VN-Bqk8juan.js +1 -0
- package/web/dist/assets/chunk-WL4C6EOR-D2YX-MHY.js +189 -0
- package/web/dist/assets/classDiagram-VBA2DB6C-DUUoMyaK.js +1 -0
- package/web/dist/assets/classDiagram-v2-RAHNMMFH-DUUoMyaK.js +1 -0
- package/web/dist/assets/clone-BmaCesfa.js +1 -0
- package/web/dist/assets/cose-bilkent-S5V4N54A-CTsv6qQA.js +1 -0
- package/web/dist/assets/cytoscape.esm-BQaXIfA_.js +331 -0
- package/web/dist/assets/dagre-KLK3FWXG-Ci4Jh9nu.js +4 -0
- package/web/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/web/dist/assets/diagram-E7M64L7V-BFRnfTI2.js +24 -0
- package/web/dist/assets/diagram-IFDJBPK2-B7Zhnp0b.js +43 -0
- package/web/dist/assets/diagram-P4PSJMXO-BVyP7nwq.js +24 -0
- package/web/dist/assets/erDiagram-INFDFZHY-NorKdTOF.js +70 -0
- package/web/dist/assets/error-CGD5mp5f.js +1 -0
- package/web/dist/assets/flowDiagram-PKNHOUZH-Ch97nABF.js +162 -0
- package/web/dist/assets/ganttDiagram-A5KZAMGK-BQ2pLWsy.js +292 -0
- package/web/dist/assets/gitGraphDiagram-K3NZZRJ6-bcvnBsD2.js +65 -0
- package/web/dist/assets/graph-CeAEckur.js +1 -0
- package/web/dist/assets/index-CPnL1_qC.js +768 -0
- package/web/dist/assets/index-DVevCbcO.css +10 -0
- package/web/dist/assets/infoDiagram-LFFYTUFH-CcsrFdj-.js +2 -0
- package/web/dist/assets/init-Dmth1JHB.js +1 -0
- package/web/dist/assets/ishikawaDiagram-PHBUUO56-1upyMfHN.js +70 -0
- package/web/dist/assets/journeyDiagram-4ABVD52K-CKUi-V0c.js +139 -0
- package/web/dist/assets/kanban-definition-K7BYSVSG-DOnQwXfL.js +89 -0
- package/web/dist/assets/layout-BmMMqTnJ.js +1 -0
- package/web/dist/assets/linear-DiaJloY5.js +1 -0
- package/web/dist/assets/mermaid.core-BWLV1B2v.js +254 -0
- package/web/dist/assets/mindmap-definition-YRQLILUH-BeAKHVWP.js +68 -0
- package/web/dist/assets/ordinal-DILIJJjt.js +1 -0
- package/web/dist/assets/pieDiagram-SKSYHLDU-DfiMSfWo.js +30 -0
- package/web/dist/assets/quadrantDiagram-337W2JSQ-wZxZOJxd.js +7 -0
- package/web/dist/assets/requirementDiagram-Z7DCOOCP-BK4HHm17.js +73 -0
- package/web/dist/assets/sankeyDiagram-WA2Y5GQK-BX6t2avX.js +10 -0
- package/web/dist/assets/sequenceDiagram-2WXFIKYE-BPQlkbAa.js +145 -0
- package/web/dist/assets/sheet-rI0FfB1g.js +6 -0
- package/web/dist/assets/sliders-horizontal-CuijWFNK.js +6 -0
- package/web/dist/assets/sparkles-BsMYXJoT.js +11 -0
- package/web/dist/assets/square-0CqMX1Q3.js +11 -0
- package/web/dist/assets/stateDiagram-RAJIS63D-DxkV0Vwd.js +1 -0
- package/web/dist/assets/stateDiagram-v2-FVOUBMTO-qLYoiOPe.js +1 -0
- package/web/dist/assets/step-D51IIHGA.js +1 -0
- package/web/dist/assets/tasks-D8JjBTwx.js +1 -0
- package/web/dist/assets/time-O8zIGux3.js +1 -0
- package/web/dist/assets/timeline-definition-YZTLITO2-kNp1DyFc.js +61 -0
- package/web/dist/assets/treemap-KZPCXAKY-CkrClVhk.js +162 -0
- package/web/dist/assets/utils-KGAn0XTg.js +11 -0
- package/web/dist/assets/vennDiagram-LZ73GAT5-CgdzEZz4.js +34 -0
- package/web/dist/assets/xychartDiagram-JWTSCODW-DfYGPfNB.js +7 -0
- package/web/dist/assets/zap-_hKJYy7J.js +6 -0
- package/web/dist/favicon.svg +332 -0
- package/web/dist/fonts/AlibabaPuHuiTi-3-55-Regular.woff2 +0 -0
- package/web/dist/fonts/AlibabaPuHuiTi-3-65-Medium.woff2 +0 -0
- package/web/dist/fonts/AlibabaPuHuiTi-3-75-SemiBold.woff2 +0 -0
- package/web/dist/fonts/DMSans-latin-ext.woff2 +0 -0
- package/web/dist/fonts/DMSans-latin.woff2 +0 -0
- package/web/dist/icons/README.md +20 -0
- package/web/dist/icons/apple-touch-icon-180.png +0 -0
- package/web/dist/icons/icon-128.png +0 -0
- package/web/dist/icons/icon-144.png +0 -0
- package/web/dist/icons/icon-152.png +0 -0
- package/web/dist/icons/icon-192.png +0 -0
- package/web/dist/icons/icon-192.svg +332 -0
- package/web/dist/icons/icon-384.png +0 -0
- package/web/dist/icons/icon-48.png +0 -0
- package/web/dist/icons/icon-512-maskable.png +0 -0
- package/web/dist/icons/icon-512.png +0 -0
- package/web/dist/icons/icon-512.svg +332 -0
- package/web/dist/icons/icon-72.png +0 -0
- package/web/dist/icons/icon-96.png +0 -0
- package/web/dist/icons/loading-logo.svg +332 -0
- package/web/dist/icons/logo-1024.png +0 -0
- package/web/dist/icons/logo-icon.svg +332 -0
- package/web/dist/icons/logo-text.svg +332 -0
- package/web/dist/index.html +30 -0
- package/web/dist/manifest.webmanifest +1 -0
- package/web/dist/registerSW.js +1 -0
- package/web/dist/sw.js +1 -0
- package/web/dist/workbox-08d6266a.js +1 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
// MCP Servers management routes
|
|
2
|
+
import { Hono } from 'hono';
|
|
3
|
+
import fs from 'fs/promises';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import os from 'os';
|
|
6
|
+
import { authMiddleware } from '../middleware/auth.js';
|
|
7
|
+
import { DATA_DIR } from '../config.js';
|
|
8
|
+
import { checkMcpServerLimit } from '../billing.js';
|
|
9
|
+
// --- Utility Functions ---
|
|
10
|
+
function getUserMcpServersDir(userId) {
|
|
11
|
+
return path.join(DATA_DIR, 'mcp-servers', userId);
|
|
12
|
+
}
|
|
13
|
+
function getServersFilePath(userId) {
|
|
14
|
+
return path.join(getUserMcpServersDir(userId), 'servers.json');
|
|
15
|
+
}
|
|
16
|
+
function getHostSyncManifestPath(userId) {
|
|
17
|
+
return path.join(getUserMcpServersDir(userId), '.host-sync.json');
|
|
18
|
+
}
|
|
19
|
+
function validateServerId(id) {
|
|
20
|
+
return /^[\w\-]+$/.test(id) && id !== 'cli-claw';
|
|
21
|
+
}
|
|
22
|
+
async function readMcpServersFile(userId) {
|
|
23
|
+
try {
|
|
24
|
+
const data = await fs.readFile(getServersFilePath(userId), 'utf-8');
|
|
25
|
+
return JSON.parse(data);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return { servers: {} };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async function writeMcpServersFile(userId, data) {
|
|
32
|
+
const dir = getUserMcpServersDir(userId);
|
|
33
|
+
await fs.mkdir(dir, { recursive: true });
|
|
34
|
+
await fs.writeFile(getServersFilePath(userId), JSON.stringify(data, null, 2));
|
|
35
|
+
}
|
|
36
|
+
async function readHostSyncManifest(userId) {
|
|
37
|
+
try {
|
|
38
|
+
const data = await fs.readFile(getHostSyncManifestPath(userId), 'utf-8');
|
|
39
|
+
return JSON.parse(data);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return { syncedServers: [], lastSyncAt: '' };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async function writeHostSyncManifest(userId, manifest) {
|
|
46
|
+
const dir = getUserMcpServersDir(userId);
|
|
47
|
+
await fs.mkdir(dir, { recursive: true });
|
|
48
|
+
await fs.writeFile(getHostSyncManifestPath(userId), JSON.stringify(manifest, null, 2));
|
|
49
|
+
}
|
|
50
|
+
// --- Routes ---
|
|
51
|
+
const mcpServersRoutes = new Hono();
|
|
52
|
+
// GET / — list all MCP servers for the current user
|
|
53
|
+
mcpServersRoutes.get('/', authMiddleware, async (c) => {
|
|
54
|
+
const authUser = c.get('user');
|
|
55
|
+
const file = await readMcpServersFile(authUser.id);
|
|
56
|
+
const servers = Object.entries(file.servers).map(([id, entry]) => ({
|
|
57
|
+
id,
|
|
58
|
+
...entry,
|
|
59
|
+
}));
|
|
60
|
+
return c.json({ servers });
|
|
61
|
+
});
|
|
62
|
+
// POST / — add a new MCP server
|
|
63
|
+
mcpServersRoutes.post('/', authMiddleware, async (c) => {
|
|
64
|
+
const authUser = c.get('user');
|
|
65
|
+
const body = await c.req.json().catch(() => ({}));
|
|
66
|
+
const { id, command, args, env, description, type, url, headers } = body;
|
|
67
|
+
if (!id || typeof id !== 'string') {
|
|
68
|
+
return c.json({ error: 'id is required and must be a string' }, 400);
|
|
69
|
+
}
|
|
70
|
+
if (!validateServerId(id)) {
|
|
71
|
+
return c.json({
|
|
72
|
+
error: 'Invalid server ID: must match /^[\\w\\-]+$/ and cannot be "cli-claw"',
|
|
73
|
+
}, 400);
|
|
74
|
+
}
|
|
75
|
+
// Billing: check MCP server limit
|
|
76
|
+
const existingServers = await readMcpServersFile(authUser.id);
|
|
77
|
+
const currentCount = Object.keys(existingServers.servers).length;
|
|
78
|
+
if (!existingServers.servers[id]) {
|
|
79
|
+
// Only check limit for new servers, not updates
|
|
80
|
+
const limit = checkMcpServerLimit(authUser.id, authUser.role, currentCount);
|
|
81
|
+
if (!limit.allowed) {
|
|
82
|
+
return c.json({ error: limit.reason }, 403);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const isHttpType = type === 'http' || type === 'sse';
|
|
86
|
+
if (isHttpType) {
|
|
87
|
+
if (!url || typeof url !== 'string') {
|
|
88
|
+
return c.json({ error: 'url is required for http/sse type' }, 400);
|
|
89
|
+
}
|
|
90
|
+
if (headers !== undefined &&
|
|
91
|
+
(typeof headers !== 'object' ||
|
|
92
|
+
headers === null ||
|
|
93
|
+
Array.isArray(headers))) {
|
|
94
|
+
return c.json({ error: 'headers must be a plain object' }, 400);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
if (!command || typeof command !== 'string') {
|
|
99
|
+
return c.json({ error: 'command is required and must be a string' }, 400);
|
|
100
|
+
}
|
|
101
|
+
if (args !== undefined && !Array.isArray(args)) {
|
|
102
|
+
return c.json({ error: 'args must be an array of strings' }, 400);
|
|
103
|
+
}
|
|
104
|
+
if (env !== undefined &&
|
|
105
|
+
(typeof env !== 'object' || env === null || Array.isArray(env))) {
|
|
106
|
+
return c.json({ error: 'env must be a plain object' }, 400);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const file = await readMcpServersFile(authUser.id);
|
|
110
|
+
if (file.servers[id]) {
|
|
111
|
+
return c.json({ error: `Server "${id}" already exists` }, 409);
|
|
112
|
+
}
|
|
113
|
+
const entry = {
|
|
114
|
+
enabled: true,
|
|
115
|
+
...(description ? { description } : {}),
|
|
116
|
+
addedAt: new Date().toISOString(),
|
|
117
|
+
};
|
|
118
|
+
if (isHttpType) {
|
|
119
|
+
entry.type = type;
|
|
120
|
+
entry.url = url;
|
|
121
|
+
if (headers && Object.keys(headers).length > 0)
|
|
122
|
+
entry.headers = headers;
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
entry.command = command;
|
|
126
|
+
if (args && args.length > 0)
|
|
127
|
+
entry.args = args;
|
|
128
|
+
if (env && Object.keys(env).length > 0)
|
|
129
|
+
entry.env = env;
|
|
130
|
+
}
|
|
131
|
+
file.servers[id] = entry;
|
|
132
|
+
await writeMcpServersFile(authUser.id, file);
|
|
133
|
+
return c.json({ success: true, server: { id, ...file.servers[id] } });
|
|
134
|
+
});
|
|
135
|
+
// PATCH /:id — update config / enable / disable
|
|
136
|
+
mcpServersRoutes.patch('/:id', authMiddleware, async (c) => {
|
|
137
|
+
const authUser = c.get('user');
|
|
138
|
+
const id = c.req.param('id');
|
|
139
|
+
if (!validateServerId(id)) {
|
|
140
|
+
return c.json({ error: 'Invalid server ID' }, 400);
|
|
141
|
+
}
|
|
142
|
+
const body = await c.req.json().catch(() => ({}));
|
|
143
|
+
const { command, args, env, enabled, description, url, headers } = body;
|
|
144
|
+
const file = await readMcpServersFile(authUser.id);
|
|
145
|
+
const entry = file.servers[id];
|
|
146
|
+
if (!entry) {
|
|
147
|
+
return c.json({ error: 'Server not found' }, 404);
|
|
148
|
+
}
|
|
149
|
+
// stdio fields
|
|
150
|
+
if (command !== undefined) {
|
|
151
|
+
if (typeof command !== 'string' || !command) {
|
|
152
|
+
return c.json({ error: 'command must be a non-empty string' }, 400);
|
|
153
|
+
}
|
|
154
|
+
entry.command = command;
|
|
155
|
+
}
|
|
156
|
+
if (args !== undefined) {
|
|
157
|
+
if (!Array.isArray(args)) {
|
|
158
|
+
return c.json({ error: 'args must be an array of strings' }, 400);
|
|
159
|
+
}
|
|
160
|
+
entry.args = args;
|
|
161
|
+
}
|
|
162
|
+
if (env !== undefined) {
|
|
163
|
+
if (typeof env !== 'object' || env === null || Array.isArray(env)) {
|
|
164
|
+
return c.json({ error: 'env must be a plain object' }, 400);
|
|
165
|
+
}
|
|
166
|
+
entry.env = env;
|
|
167
|
+
}
|
|
168
|
+
// http/sse fields
|
|
169
|
+
if (url !== undefined) {
|
|
170
|
+
if (typeof url !== 'string' || !url) {
|
|
171
|
+
return c.json({ error: 'url must be a non-empty string' }, 400);
|
|
172
|
+
}
|
|
173
|
+
entry.url = url;
|
|
174
|
+
}
|
|
175
|
+
if (headers !== undefined) {
|
|
176
|
+
if (typeof headers !== 'object' ||
|
|
177
|
+
headers === null ||
|
|
178
|
+
Array.isArray(headers)) {
|
|
179
|
+
return c.json({ error: 'headers must be a plain object' }, 400);
|
|
180
|
+
}
|
|
181
|
+
entry.headers = headers;
|
|
182
|
+
}
|
|
183
|
+
// common fields
|
|
184
|
+
if (enabled !== undefined) {
|
|
185
|
+
if (typeof enabled !== 'boolean') {
|
|
186
|
+
return c.json({ error: 'enabled must be a boolean' }, 400);
|
|
187
|
+
}
|
|
188
|
+
entry.enabled = enabled;
|
|
189
|
+
}
|
|
190
|
+
if (description !== undefined) {
|
|
191
|
+
entry.description =
|
|
192
|
+
typeof description === 'string' ? description : undefined;
|
|
193
|
+
}
|
|
194
|
+
await writeMcpServersFile(authUser.id, file);
|
|
195
|
+
return c.json({ success: true, server: { id, ...entry } });
|
|
196
|
+
});
|
|
197
|
+
// DELETE /:id — delete a server
|
|
198
|
+
mcpServersRoutes.delete('/:id', authMiddleware, async (c) => {
|
|
199
|
+
const authUser = c.get('user');
|
|
200
|
+
const id = c.req.param('id');
|
|
201
|
+
if (!validateServerId(id)) {
|
|
202
|
+
return c.json({ error: 'Invalid server ID' }, 400);
|
|
203
|
+
}
|
|
204
|
+
const file = await readMcpServersFile(authUser.id);
|
|
205
|
+
if (!file.servers[id]) {
|
|
206
|
+
return c.json({ error: 'Server not found' }, 404);
|
|
207
|
+
}
|
|
208
|
+
delete file.servers[id];
|
|
209
|
+
await writeMcpServersFile(authUser.id, file);
|
|
210
|
+
return c.json({ success: true });
|
|
211
|
+
});
|
|
212
|
+
// POST /sync-host — sync from host MCP configs (admin only)
|
|
213
|
+
// Reads from both ~/.claude/settings.json and ~/.claude.json
|
|
214
|
+
mcpServersRoutes.post('/sync-host', authMiddleware, async (c) => {
|
|
215
|
+
const authUser = c.get('user');
|
|
216
|
+
if (authUser.role !== 'admin') {
|
|
217
|
+
return c.json({ error: 'Only admin can sync host MCP servers' }, 403);
|
|
218
|
+
}
|
|
219
|
+
// Read MCP servers from both config file locations
|
|
220
|
+
let hostServers = {};
|
|
221
|
+
// Source 1: ~/.claude/settings.json
|
|
222
|
+
const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
|
|
223
|
+
try {
|
|
224
|
+
const raw = await fs.readFile(settingsPath, 'utf-8');
|
|
225
|
+
const settings = JSON.parse(raw);
|
|
226
|
+
if (settings.mcpServers) {
|
|
227
|
+
hostServers = { ...hostServers, ...settings.mcpServers };
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
// File may not exist, that's OK
|
|
232
|
+
}
|
|
233
|
+
// Source 2: ~/.claude.json (global Claude Code config, stores per-user MCP settings)
|
|
234
|
+
// When both files define the same server ID, ~/.claude.json wins because it's
|
|
235
|
+
// the primary user-facing config file where Claude Code persists MCP settings.
|
|
236
|
+
const globalConfigPath = path.join(os.homedir(), '.claude.json');
|
|
237
|
+
try {
|
|
238
|
+
const raw = await fs.readFile(globalConfigPath, 'utf-8');
|
|
239
|
+
const config = JSON.parse(raw);
|
|
240
|
+
if (config.mcpServers) {
|
|
241
|
+
hostServers = { ...hostServers, ...config.mcpServers };
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
// File may not exist, that's OK
|
|
246
|
+
}
|
|
247
|
+
if (Object.keys(hostServers).length === 0) {
|
|
248
|
+
return c.json({
|
|
249
|
+
added: 0,
|
|
250
|
+
updated: 0,
|
|
251
|
+
deleted: 0,
|
|
252
|
+
skipped: 0,
|
|
253
|
+
message: 'No MCP servers found in host config files',
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
const file = await readMcpServersFile(authUser.id);
|
|
257
|
+
const manifest = await readHostSyncManifest(authUser.id);
|
|
258
|
+
const previouslySynced = new Set(manifest.syncedServers);
|
|
259
|
+
const hostServerIds = new Set(Object.keys(hostServers));
|
|
260
|
+
const stats = { added: 0, updated: 0, deleted: 0, skipped: 0 };
|
|
261
|
+
const newSyncedList = [];
|
|
262
|
+
// Add/update from host
|
|
263
|
+
for (const [id, hostEntry] of Object.entries(hostServers)) {
|
|
264
|
+
if (!validateServerId(id)) {
|
|
265
|
+
stats.skipped++;
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
const existsInUser = !!file.servers[id];
|
|
269
|
+
const wasSynced = previouslySynced.has(id);
|
|
270
|
+
// Skip manually added entries
|
|
271
|
+
if (existsInUser && !wasSynced) {
|
|
272
|
+
stats.skipped++;
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
const isHttpType = hostEntry.type === 'http' || hostEntry.type === 'sse';
|
|
276
|
+
const entry = {
|
|
277
|
+
enabled: true,
|
|
278
|
+
syncedFromHost: true,
|
|
279
|
+
addedAt: existsInUser
|
|
280
|
+
? file.servers[id].addedAt || new Date().toISOString()
|
|
281
|
+
: new Date().toISOString(),
|
|
282
|
+
};
|
|
283
|
+
if (isHttpType) {
|
|
284
|
+
entry.type = hostEntry.type;
|
|
285
|
+
entry.url = hostEntry.url || '';
|
|
286
|
+
if (hostEntry.headers)
|
|
287
|
+
entry.headers = hostEntry.headers;
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
entry.command = hostEntry.command || '';
|
|
291
|
+
if (hostEntry.args)
|
|
292
|
+
entry.args = hostEntry.args;
|
|
293
|
+
if (hostEntry.env)
|
|
294
|
+
entry.env = hostEntry.env;
|
|
295
|
+
}
|
|
296
|
+
if (existsInUser) {
|
|
297
|
+
stats.updated++;
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
stats.added++;
|
|
301
|
+
}
|
|
302
|
+
file.servers[id] = entry;
|
|
303
|
+
newSyncedList.push(id);
|
|
304
|
+
}
|
|
305
|
+
// Delete servers that were synced before but no longer on host
|
|
306
|
+
for (const id of previouslySynced) {
|
|
307
|
+
if (!hostServerIds.has(id) && file.servers[id]?.syncedFromHost) {
|
|
308
|
+
delete file.servers[id];
|
|
309
|
+
stats.deleted++;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
await writeMcpServersFile(authUser.id, file);
|
|
313
|
+
await writeHostSyncManifest(authUser.id, {
|
|
314
|
+
syncedServers: newSyncedList,
|
|
315
|
+
lastSyncAt: new Date().toISOString(),
|
|
316
|
+
});
|
|
317
|
+
return c.json(stats);
|
|
318
|
+
});
|
|
319
|
+
export { getUserMcpServersDir, readMcpServersFile };
|
|
320
|
+
export default mcpServersRoutes;
|