@epiphytic/claudecodeui 1.0.0

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.
Files changed (142) hide show
  1. package/LICENSE +675 -0
  2. package/README.md +414 -0
  3. package/dist/api-docs.html +879 -0
  4. package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  5. package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  6. package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  7. package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  8. package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  9. package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  10. package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  11. package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  12. package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  13. package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  14. package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  15. package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  16. package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  17. package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  18. package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  19. package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  20. package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  21. package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  22. package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  23. package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  24. package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  25. package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  26. package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  27. package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  28. package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  29. package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  30. package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  31. package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  32. package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  33. package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  34. package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  35. package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  36. package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  37. package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  38. package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  39. package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  40. package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  41. package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  42. package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  43. package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  44. package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  45. package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  46. package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  47. package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  48. package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  49. package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  50. package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  51. package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  52. package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  53. package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  54. package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  55. package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  56. package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  57. package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  58. package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  59. package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  60. package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  61. package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  62. package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  63. package/dist/assets/index-DfR9xEkp.css +32 -0
  64. package/dist/assets/index-DvlVn6Eb.js +1231 -0
  65. package/dist/assets/vendor-codemirror-CJLzwpLB.js +39 -0
  66. package/dist/assets/vendor-react-DcyRfQm3.js +59 -0
  67. package/dist/assets/vendor-xterm-DfaPXD3y.js +66 -0
  68. package/dist/clear-cache.html +85 -0
  69. package/dist/convert-icons.md +53 -0
  70. package/dist/favicon.png +0 -0
  71. package/dist/favicon.svg +9 -0
  72. package/dist/generate-icons.js +49 -0
  73. package/dist/icons/claude-ai-icon.svg +1 -0
  74. package/dist/icons/codex-white.svg +3 -0
  75. package/dist/icons/codex.svg +3 -0
  76. package/dist/icons/cursor-white.svg +12 -0
  77. package/dist/icons/cursor.svg +1 -0
  78. package/dist/icons/generate-icons.md +19 -0
  79. package/dist/icons/icon-128x128.png +0 -0
  80. package/dist/icons/icon-128x128.svg +12 -0
  81. package/dist/icons/icon-144x144.png +0 -0
  82. package/dist/icons/icon-144x144.svg +12 -0
  83. package/dist/icons/icon-152x152.png +0 -0
  84. package/dist/icons/icon-152x152.svg +12 -0
  85. package/dist/icons/icon-192x192.png +0 -0
  86. package/dist/icons/icon-192x192.svg +12 -0
  87. package/dist/icons/icon-384x384.png +0 -0
  88. package/dist/icons/icon-384x384.svg +12 -0
  89. package/dist/icons/icon-512x512.png +0 -0
  90. package/dist/icons/icon-512x512.svg +12 -0
  91. package/dist/icons/icon-72x72.png +0 -0
  92. package/dist/icons/icon-72x72.svg +12 -0
  93. package/dist/icons/icon-96x96.png +0 -0
  94. package/dist/icons/icon-96x96.svg +12 -0
  95. package/dist/icons/icon-template.svg +12 -0
  96. package/dist/index.html +52 -0
  97. package/dist/logo-128.png +0 -0
  98. package/dist/logo-256.png +0 -0
  99. package/dist/logo-32.png +0 -0
  100. package/dist/logo-512.png +0 -0
  101. package/dist/logo-64.png +0 -0
  102. package/dist/logo.svg +17 -0
  103. package/dist/manifest.json +61 -0
  104. package/dist/screenshots/cli-selection.png +0 -0
  105. package/dist/screenshots/desktop-main.png +0 -0
  106. package/dist/screenshots/mobile-chat.png +0 -0
  107. package/dist/screenshots/tools-modal.png +0 -0
  108. package/dist/sw.js +107 -0
  109. package/package.json +120 -0
  110. package/server/claude-sdk.js +721 -0
  111. package/server/cli.js +469 -0
  112. package/server/cursor-cli.js +267 -0
  113. package/server/database/db.js +554 -0
  114. package/server/database/init.sql +54 -0
  115. package/server/index.js +2120 -0
  116. package/server/middleware/auth.js +161 -0
  117. package/server/openai-codex.js +389 -0
  118. package/server/orchestrator/client.js +989 -0
  119. package/server/orchestrator/github-auth.js +308 -0
  120. package/server/orchestrator/index.js +216 -0
  121. package/server/orchestrator/protocol.js +299 -0
  122. package/server/orchestrator/proxy.js +364 -0
  123. package/server/orchestrator/status-tracker.js +226 -0
  124. package/server/projects.js +1604 -0
  125. package/server/routes/agent.js +1230 -0
  126. package/server/routes/auth.js +135 -0
  127. package/server/routes/cli-auth.js +341 -0
  128. package/server/routes/codex.js +345 -0
  129. package/server/routes/commands.js +521 -0
  130. package/server/routes/cursor.js +795 -0
  131. package/server/routes/git.js +1128 -0
  132. package/server/routes/mcp-utils.js +48 -0
  133. package/server/routes/mcp.js +650 -0
  134. package/server/routes/projects.js +378 -0
  135. package/server/routes/settings.js +178 -0
  136. package/server/routes/taskmaster.js +1963 -0
  137. package/server/routes/user.js +106 -0
  138. package/server/utils/commandParser.js +303 -0
  139. package/server/utils/gitConfig.js +24 -0
  140. package/server/utils/mcp-detector.js +198 -0
  141. package/server/utils/taskmaster-websocket.js +129 -0
  142. package/shared/modelConstants.js +65 -0
@@ -0,0 +1,48 @@
1
+ /**
2
+ * MCP UTILITIES API ROUTES
3
+ * ========================
4
+ *
5
+ * API endpoints for MCP server detection and configuration utilities.
6
+ * These endpoints expose centralized MCP detection functionality.
7
+ */
8
+
9
+ import express from 'express';
10
+ import { detectTaskMasterMCPServer, getAllMCPServers } from '../utils/mcp-detector.js';
11
+
12
+ const router = express.Router();
13
+
14
+ /**
15
+ * GET /api/mcp-utils/taskmaster-server
16
+ * Check if TaskMaster MCP server is configured
17
+ */
18
+ router.get('/taskmaster-server', async (req, res) => {
19
+ try {
20
+ const result = await detectTaskMasterMCPServer();
21
+ res.json(result);
22
+ } catch (error) {
23
+ console.error('TaskMaster MCP detection error:', error);
24
+ res.status(500).json({
25
+ error: 'Failed to detect TaskMaster MCP server',
26
+ message: error.message
27
+ });
28
+ }
29
+ });
30
+
31
+ /**
32
+ * GET /api/mcp-utils/all-servers
33
+ * Get all configured MCP servers
34
+ */
35
+ router.get('/all-servers', async (req, res) => {
36
+ try {
37
+ const result = await getAllMCPServers();
38
+ res.json(result);
39
+ } catch (error) {
40
+ console.error('MCP servers detection error:', error);
41
+ res.status(500).json({
42
+ error: 'Failed to get MCP servers',
43
+ message: error.message
44
+ });
45
+ }
46
+ });
47
+
48
+ export default router;
@@ -0,0 +1,650 @@
1
+ import express from "express";
2
+ import { promises as fs } from "fs";
3
+ import path from "path";
4
+ import os from "os";
5
+ import { fileURLToPath } from "url";
6
+ import { dirname } from "path";
7
+ import { spawn } from "child_process";
8
+
9
+ const router = express.Router();
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = dirname(__filename);
12
+
13
+ // Claude CLI command routes
14
+
15
+ // GET /api/mcp/cli/list - List MCP servers using Claude CLI
16
+ router.get("/cli/list", async (req, res) => {
17
+ try {
18
+ console.log("📋 Listing MCP servers using Claude CLI");
19
+
20
+ const { spawn } = await import("child_process");
21
+ const { promisify } = await import("util");
22
+ const exec = promisify(spawn);
23
+
24
+ const process = spawn("claude", ["mcp", "list"], {
25
+ stdio: ["pipe", "pipe", "pipe"],
26
+ });
27
+
28
+ let stdout = "";
29
+ let stderr = "";
30
+
31
+ process.stdout.on("data", (data) => {
32
+ stdout += data.toString();
33
+ });
34
+
35
+ process.stderr.on("data", (data) => {
36
+ stderr += data.toString();
37
+ });
38
+
39
+ process.on("close", (code) => {
40
+ if (code === 0) {
41
+ res.json({
42
+ success: true,
43
+ output: stdout,
44
+ servers: parseClaudeListOutput(stdout),
45
+ });
46
+ } else {
47
+ console.error("Claude CLI error:", stderr);
48
+ res
49
+ .status(500)
50
+ .json({ error: "Claude CLI command failed", details: stderr });
51
+ }
52
+ });
53
+
54
+ process.on("error", (error) => {
55
+ console.error("Error running Claude CLI:", error);
56
+ res
57
+ .status(500)
58
+ .json({ error: "Failed to run Claude CLI", details: error.message });
59
+ });
60
+ } catch (error) {
61
+ console.error("Error listing MCP servers via CLI:", error);
62
+ res
63
+ .status(500)
64
+ .json({ error: "Failed to list MCP servers", details: error.message });
65
+ }
66
+ });
67
+
68
+ // POST /api/mcp/cli/add - Add MCP server using Claude CLI
69
+ router.post("/cli/add", async (req, res) => {
70
+ try {
71
+ const {
72
+ name,
73
+ type = "stdio",
74
+ command,
75
+ args = [],
76
+ url,
77
+ headers = {},
78
+ env = {},
79
+ scope = "user",
80
+ projectPath,
81
+ } = req.body;
82
+
83
+ console.log(
84
+ `➕ Adding MCP server using Claude CLI (${scope} scope):`,
85
+ name,
86
+ );
87
+
88
+ const { spawn } = await import("child_process");
89
+
90
+ let cliArgs = ["mcp", "add"];
91
+
92
+ // Add scope flag
93
+ cliArgs.push("--scope", scope);
94
+
95
+ if (type === "http") {
96
+ cliArgs.push("--transport", "http", name, url);
97
+ // Add headers if provided
98
+ Object.entries(headers).forEach(([key, value]) => {
99
+ cliArgs.push("--header", `${key}: ${value}`);
100
+ });
101
+ } else if (type === "sse") {
102
+ cliArgs.push("--transport", "sse", name, url);
103
+ // Add headers if provided
104
+ Object.entries(headers).forEach(([key, value]) => {
105
+ cliArgs.push("--header", `${key}: ${value}`);
106
+ });
107
+ } else {
108
+ // stdio (default): claude mcp add --scope user <name> <command> [args...]
109
+ cliArgs.push(name);
110
+ // Add environment variables
111
+ Object.entries(env).forEach(([key, value]) => {
112
+ cliArgs.push("-e", `${key}=${value}`);
113
+ });
114
+ cliArgs.push(command);
115
+ if (args && args.length > 0) {
116
+ cliArgs.push(...args);
117
+ }
118
+ }
119
+
120
+ console.log("🔧 Running Claude CLI command:", "claude", cliArgs.join(" "));
121
+
122
+ // For local scope, we need to run the command in the project directory
123
+ const spawnOptions = {
124
+ stdio: ["pipe", "pipe", "pipe"],
125
+ };
126
+
127
+ if (scope === "local" && projectPath) {
128
+ spawnOptions.cwd = projectPath;
129
+ console.log("📁 Running in project directory:", projectPath);
130
+ }
131
+
132
+ const process = spawn("claude", cliArgs, spawnOptions);
133
+
134
+ let stdout = "";
135
+ let stderr = "";
136
+
137
+ process.stdout.on("data", (data) => {
138
+ stdout += data.toString();
139
+ });
140
+
141
+ process.stderr.on("data", (data) => {
142
+ stderr += data.toString();
143
+ });
144
+
145
+ process.on("close", (code) => {
146
+ if (code === 0) {
147
+ res.json({
148
+ success: true,
149
+ output: stdout,
150
+ message: `MCP server "${name}" added successfully`,
151
+ });
152
+ } else {
153
+ console.error("Claude CLI error:", stderr);
154
+ res
155
+ .status(400)
156
+ .json({ error: "Claude CLI command failed", details: stderr });
157
+ }
158
+ });
159
+
160
+ process.on("error", (error) => {
161
+ console.error("Error running Claude CLI:", error);
162
+ res
163
+ .status(500)
164
+ .json({ error: "Failed to run Claude CLI", details: error.message });
165
+ });
166
+ } catch (error) {
167
+ console.error("Error adding MCP server via CLI:", error);
168
+ res
169
+ .status(500)
170
+ .json({ error: "Failed to add MCP server", details: error.message });
171
+ }
172
+ });
173
+
174
+ // POST /api/mcp/cli/add-json - Add MCP server using JSON format
175
+ router.post("/cli/add-json", async (req, res) => {
176
+ try {
177
+ const { name, jsonConfig, scope = "user", projectPath } = req.body;
178
+
179
+ console.log("➕ Adding MCP server using JSON format:", name);
180
+
181
+ // Validate and parse JSON config
182
+ let parsedConfig;
183
+ try {
184
+ parsedConfig =
185
+ typeof jsonConfig === "string" ? JSON.parse(jsonConfig) : jsonConfig;
186
+ } catch (parseError) {
187
+ return res.status(400).json({
188
+ error: "Invalid JSON configuration",
189
+ details: parseError.message,
190
+ });
191
+ }
192
+
193
+ // Validate required fields
194
+ if (!parsedConfig.type) {
195
+ return res.status(400).json({
196
+ error: "Invalid configuration",
197
+ details: "Missing required field: type",
198
+ });
199
+ }
200
+
201
+ if (parsedConfig.type === "stdio" && !parsedConfig.command) {
202
+ return res.status(400).json({
203
+ error: "Invalid configuration",
204
+ details: "stdio type requires a command field",
205
+ });
206
+ }
207
+
208
+ if (
209
+ (parsedConfig.type === "http" || parsedConfig.type === "sse") &&
210
+ !parsedConfig.url
211
+ ) {
212
+ return res.status(400).json({
213
+ error: "Invalid configuration",
214
+ details: `${parsedConfig.type} type requires a url field`,
215
+ });
216
+ }
217
+
218
+ const { spawn } = await import("child_process");
219
+
220
+ // Build the command: claude mcp add-json --scope <scope> <name> '<json>'
221
+ const cliArgs = ["mcp", "add-json", "--scope", scope, name];
222
+
223
+ // Add the JSON config as a properly formatted string
224
+ const jsonString = JSON.stringify(parsedConfig);
225
+ cliArgs.push(jsonString);
226
+
227
+ console.log(
228
+ "🔧 Running Claude CLI command:",
229
+ "claude",
230
+ cliArgs[0],
231
+ cliArgs[1],
232
+ cliArgs[2],
233
+ cliArgs[3],
234
+ cliArgs[4],
235
+ jsonString,
236
+ );
237
+
238
+ // For local scope, we need to run the command in the project directory
239
+ const spawnOptions = {
240
+ stdio: ["pipe", "pipe", "pipe"],
241
+ };
242
+
243
+ if (scope === "local" && projectPath) {
244
+ spawnOptions.cwd = projectPath;
245
+ console.log("📁 Running in project directory:", projectPath);
246
+ }
247
+
248
+ const process = spawn("claude", cliArgs, spawnOptions);
249
+
250
+ let stdout = "";
251
+ let stderr = "";
252
+
253
+ process.stdout.on("data", (data) => {
254
+ stdout += data.toString();
255
+ });
256
+
257
+ process.stderr.on("data", (data) => {
258
+ stderr += data.toString();
259
+ });
260
+
261
+ process.on("close", (code) => {
262
+ if (code === 0) {
263
+ res.json({
264
+ success: true,
265
+ output: stdout,
266
+ message: `MCP server "${name}" added successfully via JSON`,
267
+ });
268
+ } else {
269
+ console.error("Claude CLI error:", stderr);
270
+ res
271
+ .status(400)
272
+ .json({ error: "Claude CLI command failed", details: stderr });
273
+ }
274
+ });
275
+
276
+ process.on("error", (error) => {
277
+ console.error("Error running Claude CLI:", error);
278
+ res
279
+ .status(500)
280
+ .json({ error: "Failed to run Claude CLI", details: error.message });
281
+ });
282
+ } catch (error) {
283
+ console.error("Error adding MCP server via JSON:", error);
284
+ res
285
+ .status(500)
286
+ .json({ error: "Failed to add MCP server", details: error.message });
287
+ }
288
+ });
289
+
290
+ // DELETE /api/mcp/cli/remove/:name - Remove MCP server using Claude CLI
291
+ router.delete("/cli/remove/:name", async (req, res) => {
292
+ try {
293
+ const { name } = req.params;
294
+ const { scope } = req.query; // Get scope from query params
295
+
296
+ // Handle the ID format (remove scope prefix if present)
297
+ let actualName = name;
298
+ let actualScope = scope;
299
+
300
+ // If the name includes a scope prefix like "local:test", extract it
301
+ if (name.includes(":")) {
302
+ const [prefix, serverName] = name.split(":");
303
+ actualName = serverName;
304
+ actualScope = actualScope || prefix; // Use prefix as scope if not provided in query
305
+ }
306
+
307
+ console.log(
308
+ "🗑️ Removing MCP server using Claude CLI:",
309
+ actualName,
310
+ "scope:",
311
+ actualScope,
312
+ );
313
+
314
+ const { spawn } = await import("child_process");
315
+
316
+ // Build command args based on scope
317
+ let cliArgs = ["mcp", "remove"];
318
+
319
+ // Add scope flag if it's local scope
320
+ if (actualScope === "local") {
321
+ cliArgs.push("--scope", "local");
322
+ } else if (actualScope === "user" || !actualScope) {
323
+ // User scope is default, but we can be explicit
324
+ cliArgs.push("--scope", "user");
325
+ }
326
+
327
+ cliArgs.push(actualName);
328
+
329
+ console.log("🔧 Running Claude CLI command:", "claude", cliArgs.join(" "));
330
+
331
+ const process = spawn("claude", cliArgs, {
332
+ stdio: ["pipe", "pipe", "pipe"],
333
+ });
334
+
335
+ let stdout = "";
336
+ let stderr = "";
337
+
338
+ process.stdout.on("data", (data) => {
339
+ stdout += data.toString();
340
+ });
341
+
342
+ process.stderr.on("data", (data) => {
343
+ stderr += data.toString();
344
+ });
345
+
346
+ process.on("close", (code) => {
347
+ if (code === 0) {
348
+ res.json({
349
+ success: true,
350
+ output: stdout,
351
+ message: `MCP server "${name}" removed successfully`,
352
+ });
353
+ } else {
354
+ console.error("Claude CLI error:", stderr);
355
+ res
356
+ .status(400)
357
+ .json({ error: "Claude CLI command failed", details: stderr });
358
+ }
359
+ });
360
+
361
+ process.on("error", (error) => {
362
+ console.error("Error running Claude CLI:", error);
363
+ res
364
+ .status(500)
365
+ .json({ error: "Failed to run Claude CLI", details: error.message });
366
+ });
367
+ } catch (error) {
368
+ console.error("Error removing MCP server via CLI:", error);
369
+ res
370
+ .status(500)
371
+ .json({ error: "Failed to remove MCP server", details: error.message });
372
+ }
373
+ });
374
+
375
+ // GET /api/mcp/cli/get/:name - Get MCP server details using Claude CLI
376
+ router.get("/cli/get/:name", async (req, res) => {
377
+ try {
378
+ const { name } = req.params;
379
+
380
+ console.log("📄 Getting MCP server details using Claude CLI:", name);
381
+
382
+ const { spawn } = await import("child_process");
383
+
384
+ const process = spawn("claude", ["mcp", "get", name], {
385
+ stdio: ["pipe", "pipe", "pipe"],
386
+ });
387
+
388
+ let stdout = "";
389
+ let stderr = "";
390
+
391
+ process.stdout.on("data", (data) => {
392
+ stdout += data.toString();
393
+ });
394
+
395
+ process.stderr.on("data", (data) => {
396
+ stderr += data.toString();
397
+ });
398
+
399
+ process.on("close", (code) => {
400
+ if (code === 0) {
401
+ res.json({
402
+ success: true,
403
+ output: stdout,
404
+ server: parseClaudeGetOutput(stdout),
405
+ });
406
+ } else {
407
+ console.error("Claude CLI error:", stderr);
408
+ res
409
+ .status(404)
410
+ .json({ error: "Claude CLI command failed", details: stderr });
411
+ }
412
+ });
413
+
414
+ process.on("error", (error) => {
415
+ console.error("Error running Claude CLI:", error);
416
+ res
417
+ .status(500)
418
+ .json({ error: "Failed to run Claude CLI", details: error.message });
419
+ });
420
+ } catch (error) {
421
+ console.error("Error getting MCP server details via CLI:", error);
422
+ res
423
+ .status(500)
424
+ .json({
425
+ error: "Failed to get MCP server details",
426
+ details: error.message,
427
+ });
428
+ }
429
+ });
430
+
431
+ // GET /api/mcp/config/read - Read MCP servers directly from Claude config files
432
+ router.get("/config/read", async (req, res) => {
433
+ try {
434
+ console.log("📖 Reading MCP servers from Claude config files");
435
+
436
+ const homeDir = os.homedir();
437
+ const configPaths = [
438
+ path.join(homeDir, ".claude.json"),
439
+ path.join(homeDir, ".claude", "settings.json"),
440
+ ];
441
+
442
+ let configData = null;
443
+ let configPath = null;
444
+
445
+ // Try to read from either config file
446
+ for (const filepath of configPaths) {
447
+ try {
448
+ const fileContent = await fs.readFile(filepath, "utf8");
449
+ configData = JSON.parse(fileContent);
450
+ configPath = filepath;
451
+ console.log(`✅ Found Claude config at: ${filepath}`);
452
+ break;
453
+ } catch (error) {
454
+ // File doesn't exist or is not valid JSON, try next
455
+ console.log(`ℹ️ Config not found or invalid at: ${filepath}`);
456
+ }
457
+ }
458
+
459
+ if (!configData) {
460
+ return res.json({
461
+ success: false,
462
+ message: "No Claude configuration file found",
463
+ servers: [],
464
+ });
465
+ }
466
+
467
+ // Extract MCP servers from the config
468
+ const servers = [];
469
+
470
+ // Check for user-scoped MCP servers (at root level)
471
+ if (
472
+ configData.mcpServers &&
473
+ typeof configData.mcpServers === "object" &&
474
+ Object.keys(configData.mcpServers).length > 0
475
+ ) {
476
+ console.log(
477
+ "🔍 Found user-scoped MCP servers:",
478
+ Object.keys(configData.mcpServers),
479
+ );
480
+ for (const [name, config] of Object.entries(configData.mcpServers)) {
481
+ const server = {
482
+ id: name,
483
+ name: name,
484
+ type: "stdio", // Default type
485
+ scope: "user", // User scope - available across all projects
486
+ config: {},
487
+ raw: config, // Include raw config for full details
488
+ };
489
+
490
+ // Determine transport type and extract config
491
+ if (config.command) {
492
+ server.type = "stdio";
493
+ server.config.command = config.command;
494
+ server.config.args = config.args || [];
495
+ server.config.env = config.env || {};
496
+ } else if (config.url) {
497
+ server.type = config.transport || "http";
498
+ server.config.url = config.url;
499
+ server.config.headers = config.headers || {};
500
+ }
501
+
502
+ servers.push(server);
503
+ }
504
+ }
505
+
506
+ // Check for local-scoped MCP servers (project-specific)
507
+ const currentProjectPath = process.cwd();
508
+
509
+ // Check under 'projects' key
510
+ if (configData.projects && configData.projects[currentProjectPath]) {
511
+ const projectConfig = configData.projects[currentProjectPath];
512
+ if (
513
+ projectConfig.mcpServers &&
514
+ typeof projectConfig.mcpServers === "object" &&
515
+ Object.keys(projectConfig.mcpServers).length > 0
516
+ ) {
517
+ console.log(
518
+ `🔍 Found local-scoped MCP servers for ${currentProjectPath}:`,
519
+ Object.keys(projectConfig.mcpServers),
520
+ );
521
+ for (const [name, config] of Object.entries(projectConfig.mcpServers)) {
522
+ const server = {
523
+ id: `local:${name}`, // Prefix with scope for uniqueness
524
+ name: name, // Keep original name
525
+ type: "stdio", // Default type
526
+ scope: "local", // Local scope - only for this project
527
+ projectPath: currentProjectPath,
528
+ config: {},
529
+ raw: config, // Include raw config for full details
530
+ };
531
+
532
+ // Determine transport type and extract config
533
+ if (config.command) {
534
+ server.type = "stdio";
535
+ server.config.command = config.command;
536
+ server.config.args = config.args || [];
537
+ server.config.env = config.env || {};
538
+ } else if (config.url) {
539
+ server.type = config.transport || "http";
540
+ server.config.url = config.url;
541
+ server.config.headers = config.headers || {};
542
+ }
543
+
544
+ servers.push(server);
545
+ }
546
+ }
547
+ }
548
+
549
+ console.log(`📋 Found ${servers.length} MCP servers in config`);
550
+
551
+ res.json({
552
+ success: true,
553
+ configPath: configPath,
554
+ servers: servers,
555
+ });
556
+ } catch (error) {
557
+ console.error("Error reading Claude config:", error);
558
+ res.status(500).json({
559
+ error: "Failed to read Claude configuration",
560
+ details: error.message,
561
+ });
562
+ }
563
+ });
564
+
565
+ // Helper functions to parse Claude CLI output
566
+ function parseClaudeListOutput(output) {
567
+ const servers = [];
568
+ const lines = output.split("\n").filter((line) => line.trim());
569
+
570
+ for (const line of lines) {
571
+ // Skip the header line and empty lines
572
+ if (line.includes("Checking MCP server health")) continue;
573
+ if (!line.includes(":")) continue;
574
+
575
+ // Parse lines like:
576
+ // "plugin:context7:context7: npx -y @upstash/context7-mcp - ✓ Connected"
577
+ // "server-name: command args - ✗ Failed to connect"
578
+ //
579
+ // The format is: <name>: <command> - <status>
580
+ // Names can contain colons (e.g., plugin:context7:context7)
581
+ // We need to find the ": " that separates name from command
582
+
583
+ // Look for the pattern ": " followed by something that looks like a command
584
+ // The command section ends with " - " followed by status
585
+ const statusMatch = line.match(/^(.+?):\s+(.+?)\s+-\s+([✓✗].*?)$/);
586
+
587
+ if (statusMatch) {
588
+ const name = statusMatch[1].trim();
589
+ const command = statusMatch[2].trim();
590
+ const statusText = statusMatch[3].trim();
591
+
592
+ // Skip empty names
593
+ if (!name) continue;
594
+
595
+ const status = statusText.includes("✓") ? "connected" : "failed";
596
+
597
+ // Try to determine type from command
598
+ let type = "stdio";
599
+ if (command.startsWith("http://") || command.startsWith("https://")) {
600
+ type = "http";
601
+ }
602
+
603
+ servers.push({
604
+ id: name,
605
+ name,
606
+ type,
607
+ status,
608
+ command,
609
+ description: command,
610
+ });
611
+ }
612
+ }
613
+
614
+ console.log("🔍 Parsed Claude CLI servers:", servers);
615
+ return servers;
616
+ }
617
+
618
+ function parseClaudeGetOutput(output) {
619
+ // Parse the output from 'claude mcp get <name>' command
620
+ // This is a simple parser - might need adjustment based on actual output format
621
+ try {
622
+ // Try to extract JSON if present
623
+ const jsonMatch = output.match(/\{[\s\S]*\}/);
624
+ if (jsonMatch) {
625
+ return JSON.parse(jsonMatch[0]);
626
+ }
627
+
628
+ // Otherwise, parse as text
629
+ const server = { raw_output: output };
630
+ const lines = output.split("\n");
631
+
632
+ for (const line of lines) {
633
+ if (line.includes("Name:")) {
634
+ server.name = line.split(":")[1]?.trim();
635
+ } else if (line.includes("Type:")) {
636
+ server.type = line.split(":")[1]?.trim();
637
+ } else if (line.includes("Command:")) {
638
+ server.command = line.split(":")[1]?.trim();
639
+ } else if (line.includes("URL:")) {
640
+ server.url = line.split(":")[1]?.trim();
641
+ }
642
+ }
643
+
644
+ return server;
645
+ } catch (error) {
646
+ return { raw_output: output, parse_error: error.message };
647
+ }
648
+ }
649
+
650
+ export default router;