mirror of
https://github.com/siteboon/claudecodeui.git
synced 2025-12-10 23:09:46 +00:00
feat: enhance MCP server management with config file support and improved CLI interactions
This commit is contained in:
232
package-lock.json
generated
232
package-lock.json
generated
@@ -9,7 +9,6 @@
|
|||||||
"version": "1.5.0",
|
"version": "1.5.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@anthropic-ai/claude-code": "^1.0.57",
|
|
||||||
"@codemirror/lang-css": "^6.3.1",
|
"@codemirror/lang-css": "^6.3.1",
|
||||||
"@codemirror/lang-html": "^6.4.9",
|
"@codemirror/lang-html": "^6.4.9",
|
||||||
"@codemirror/lang-javascript": "^6.2.4",
|
"@codemirror/lang-javascript": "^6.2.4",
|
||||||
@@ -83,26 +82,6 @@
|
|||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@anthropic-ai/claude-code": {
|
|
||||||
"version": "1.0.58",
|
|
||||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code/-/claude-code-1.0.58.tgz",
|
|
||||||
"integrity": "sha512-XcfqklHSCuBRpVV9vZaAGvdJFAyVKb/UHz2VG9osvn1pRqY7e+HhIOU9X7LeI+c116QhmjglGwe+qz4jOC83CQ==",
|
|
||||||
"license": "SEE LICENSE IN README.md",
|
|
||||||
"bin": {
|
|
||||||
"claude": "cli.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18.0.0"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"@img/sharp-darwin-arm64": "^0.33.5",
|
|
||||||
"@img/sharp-darwin-x64": "^0.33.5",
|
|
||||||
"@img/sharp-linux-arm": "^0.33.5",
|
|
||||||
"@img/sharp-linux-arm64": "^0.33.5",
|
|
||||||
"@img/sharp-linux-x64": "^0.33.5",
|
|
||||||
"@img/sharp-win32-x64": "^0.33.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@babel/code-frame": {
|
"node_modules/@babel/code-frame": {
|
||||||
"version": "7.27.1",
|
"version": "7.27.1",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
|
||||||
@@ -1023,114 +1002,6 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@img/sharp-darwin-arm64": {
|
|
||||||
"version": "0.33.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
|
|
||||||
"integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"darwin"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://opencollective.com/libvips"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"@img/sharp-libvips-darwin-arm64": "1.0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@img/sharp-darwin-x64": {
|
|
||||||
"version": "0.33.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz",
|
|
||||||
"integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"darwin"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://opencollective.com/libvips"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"@img/sharp-libvips-darwin-x64": "1.0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@img/sharp-libvips-darwin-arm64": {
|
|
||||||
"version": "1.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz",
|
|
||||||
"integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"license": "LGPL-3.0-or-later",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"darwin"
|
|
||||||
],
|
|
||||||
"funding": {
|
|
||||||
"url": "https://opencollective.com/libvips"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@img/sharp-libvips-darwin-x64": {
|
|
||||||
"version": "1.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz",
|
|
||||||
"integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"license": "LGPL-3.0-or-later",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"darwin"
|
|
||||||
],
|
|
||||||
"funding": {
|
|
||||||
"url": "https://opencollective.com/libvips"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@img/sharp-libvips-linux-arm": {
|
|
||||||
"version": "1.0.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz",
|
|
||||||
"integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==",
|
|
||||||
"cpu": [
|
|
||||||
"arm"
|
|
||||||
],
|
|
||||||
"license": "LGPL-3.0-or-later",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"funding": {
|
|
||||||
"url": "https://opencollective.com/libvips"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@img/sharp-libvips-linux-arm64": {
|
|
||||||
"version": "1.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz",
|
|
||||||
"integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"license": "LGPL-3.0-or-later",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"funding": {
|
|
||||||
"url": "https://opencollective.com/libvips"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@img/sharp-libvips-linux-ppc64": {
|
"node_modules/@img/sharp-libvips-linux-ppc64": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.0.tgz",
|
||||||
@@ -1165,22 +1036,6 @@
|
|||||||
"url": "https://opencollective.com/libvips"
|
"url": "https://opencollective.com/libvips"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@img/sharp-libvips-linux-x64": {
|
|
||||||
"version": "1.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz",
|
|
||||||
"integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"license": "LGPL-3.0-or-later",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"funding": {
|
|
||||||
"url": "https://opencollective.com/libvips"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
|
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.0.tgz",
|
||||||
@@ -1215,50 +1070,6 @@
|
|||||||
"url": "https://opencollective.com/libvips"
|
"url": "https://opencollective.com/libvips"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@img/sharp-linux-arm": {
|
|
||||||
"version": "0.33.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz",
|
|
||||||
"integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==",
|
|
||||||
"cpu": [
|
|
||||||
"arm"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://opencollective.com/libvips"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"@img/sharp-libvips-linux-arm": "1.0.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@img/sharp-linux-arm64": {
|
|
||||||
"version": "0.33.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz",
|
|
||||||
"integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==",
|
|
||||||
"cpu": [
|
|
||||||
"arm64"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://opencollective.com/libvips"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"@img/sharp-libvips-linux-arm64": "1.0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@img/sharp-linux-ppc64": {
|
"node_modules/@img/sharp-linux-ppc64": {
|
||||||
"version": "0.34.3",
|
"version": "0.34.3",
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.3.tgz",
|
"resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.3.tgz",
|
||||||
@@ -1305,28 +1116,6 @@
|
|||||||
"@img/sharp-libvips-linux-s390x": "1.2.0"
|
"@img/sharp-libvips-linux-s390x": "1.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@img/sharp-linux-x64": {
|
|
||||||
"version": "0.33.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz",
|
|
||||||
"integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://opencollective.com/libvips"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"@img/sharp-libvips-linux-x64": "1.0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@img/sharp-linuxmusl-arm64": {
|
"node_modules/@img/sharp-linuxmusl-arm64": {
|
||||||
"version": "0.34.3",
|
"version": "0.34.3",
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.3.tgz",
|
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.3.tgz",
|
||||||
@@ -1433,25 +1222,6 @@
|
|||||||
"url": "https://opencollective.com/libvips"
|
"url": "https://opencollective.com/libvips"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@img/sharp-win32-x64": {
|
|
||||||
"version": "0.33.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz",
|
|
||||||
"integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==",
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
],
|
|
||||||
"license": "Apache-2.0 AND LGPL-3.0-or-later",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"win32"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://opencollective.com/libvips"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@isaacs/cliui": {
|
"node_modules/@isaacs/cliui": {
|
||||||
"version": "8.0.2",
|
"version": "8.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||||
@@ -1933,7 +1703,9 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ router.get('/cli/list', async (req, res) => {
|
|||||||
const { promisify } = await import('util');
|
const { promisify } = await import('util');
|
||||||
const exec = promisify(spawn);
|
const exec = promisify(spawn);
|
||||||
|
|
||||||
const process = spawn('claude', ['mcp', 'list', '-s', 'user'], {
|
const process = spawn('claude', ['mcp', 'list'], {
|
||||||
stdio: ['pipe', 'pipe', 'pipe']
|
stdio: ['pipe', 'pipe', 'pipe']
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -60,27 +60,30 @@ router.post('/cli/add', async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
const { name, type = 'stdio', command, args = [], url, headers = {}, env = {} } = req.body;
|
const { name, type = 'stdio', command, args = [], url, headers = {}, env = {} } = req.body;
|
||||||
|
|
||||||
console.log('➕ Adding MCP server using Claude CLI:', name);
|
console.log('➕ Adding MCP server using Claude CLI (user scope):', name);
|
||||||
|
|
||||||
const { spawn } = await import('child_process');
|
const { spawn } = await import('child_process');
|
||||||
|
|
||||||
let cliArgs = ['mcp', 'add'];
|
let cliArgs = ['mcp', 'add'];
|
||||||
|
|
||||||
|
// Always add with user scope (global availability)
|
||||||
|
cliArgs.push('--scope', 'user');
|
||||||
|
|
||||||
if (type === 'http') {
|
if (type === 'http') {
|
||||||
cliArgs.push('--transport', 'http', name, '-s', 'user', url);
|
cliArgs.push('--transport', 'http', name, url);
|
||||||
// Add headers if provided
|
// Add headers if provided
|
||||||
Object.entries(headers).forEach(([key, value]) => {
|
Object.entries(headers).forEach(([key, value]) => {
|
||||||
cliArgs.push('--header', `${key}: ${value}`);
|
cliArgs.push('--header', `${key}: ${value}`);
|
||||||
});
|
});
|
||||||
} else if (type === 'sse') {
|
} else if (type === 'sse') {
|
||||||
cliArgs.push('--transport', 'sse', name, '-s', 'user', url);
|
cliArgs.push('--transport', 'sse', name, url);
|
||||||
// Add headers if provided
|
// Add headers if provided
|
||||||
Object.entries(headers).forEach(([key, value]) => {
|
Object.entries(headers).forEach(([key, value]) => {
|
||||||
cliArgs.push('--header', `${key}: ${value}`);
|
cliArgs.push('--header', `${key}: ${value}`);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// stdio (default): claude mcp add <name> -s user <command> [args...]
|
// stdio (default): claude mcp add --scope user <name> <command> [args...]
|
||||||
cliArgs.push(name, '-s', 'user');
|
cliArgs.push(name);
|
||||||
// Add environment variables
|
// Add environment variables
|
||||||
Object.entries(env).forEach(([key, value]) => {
|
Object.entries(env).forEach(([key, value]) => {
|
||||||
cliArgs.push('-e', `${key}=${value}`);
|
cliArgs.push('-e', `${key}=${value}`);
|
||||||
@@ -131,12 +134,39 @@ router.post('/cli/add', async (req, res) => {
|
|||||||
router.delete('/cli/remove/:name', async (req, res) => {
|
router.delete('/cli/remove/:name', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { name } = req.params;
|
const { name } = req.params;
|
||||||
|
const { scope } = req.query; // Get scope from query params
|
||||||
|
|
||||||
console.log('🗑️ Removing MCP server using Claude CLI:', name);
|
// Handle the ID format (remove scope prefix if present)
|
||||||
|
let actualName = name;
|
||||||
|
let actualScope = scope;
|
||||||
|
|
||||||
|
// If the name includes a scope prefix like "local:test", extract it
|
||||||
|
if (name.includes(':')) {
|
||||||
|
const [prefix, serverName] = name.split(':');
|
||||||
|
actualName = serverName;
|
||||||
|
actualScope = actualScope || prefix; // Use prefix as scope if not provided in query
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('🗑️ Removing MCP server using Claude CLI:', actualName, 'scope:', actualScope);
|
||||||
|
|
||||||
const { spawn } = await import('child_process');
|
const { spawn } = await import('child_process');
|
||||||
|
|
||||||
const process = spawn('claude', ['mcp', 'remove', '-s', 'user', name], {
|
// Build command args based on scope
|
||||||
|
let cliArgs = ['mcp', 'remove'];
|
||||||
|
|
||||||
|
// Add scope flag if it's local scope
|
||||||
|
if (actualScope === 'local') {
|
||||||
|
cliArgs.push('--scope', 'local');
|
||||||
|
} else if (actualScope === 'user' || !actualScope) {
|
||||||
|
// User scope is default, but we can be explicit
|
||||||
|
cliArgs.push('--scope', 'user');
|
||||||
|
}
|
||||||
|
|
||||||
|
cliArgs.push(actualName);
|
||||||
|
|
||||||
|
console.log('🔧 Running Claude CLI command:', 'claude', cliArgs.join(' '));
|
||||||
|
|
||||||
|
const process = spawn('claude', cliArgs, {
|
||||||
stdio: ['pipe', 'pipe', 'pipe']
|
stdio: ['pipe', 'pipe', 'pipe']
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -179,7 +209,7 @@ router.get('/cli/get/:name', async (req, res) => {
|
|||||||
|
|
||||||
const { spawn } = await import('child_process');
|
const { spawn } = await import('child_process');
|
||||||
|
|
||||||
const process = spawn('claude', ['mcp', 'get', '-s', 'user', name], {
|
const process = spawn('claude', ['mcp', 'get', name], {
|
||||||
stdio: ['pipe', 'pipe', 'pipe']
|
stdio: ['pipe', 'pipe', 'pipe']
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -213,37 +243,172 @@ router.get('/cli/get/:name', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// GET /api/mcp/config/read - Read MCP servers directly from Claude config files
|
||||||
|
router.get('/config/read', async (req, res) => {
|
||||||
|
try {
|
||||||
|
console.log('📖 Reading MCP servers from Claude config files');
|
||||||
|
|
||||||
|
const homeDir = os.homedir();
|
||||||
|
const configPaths = [
|
||||||
|
path.join(homeDir, '.claude.json'),
|
||||||
|
path.join(homeDir, '.claude', 'settings.json')
|
||||||
|
];
|
||||||
|
|
||||||
|
let configData = null;
|
||||||
|
let configPath = null;
|
||||||
|
|
||||||
|
// Try to read from either config file
|
||||||
|
for (const filepath of configPaths) {
|
||||||
|
try {
|
||||||
|
const fileContent = await fs.readFile(filepath, 'utf8');
|
||||||
|
configData = JSON.parse(fileContent);
|
||||||
|
configPath = filepath;
|
||||||
|
console.log(`✅ Found Claude config at: ${filepath}`);
|
||||||
|
break;
|
||||||
|
} catch (error) {
|
||||||
|
// File doesn't exist or is not valid JSON, try next
|
||||||
|
console.log(`ℹ️ Config not found or invalid at: ${filepath}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!configData) {
|
||||||
|
return res.json({
|
||||||
|
success: false,
|
||||||
|
message: 'No Claude configuration file found',
|
||||||
|
servers: []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract MCP servers from the config
|
||||||
|
const servers = [];
|
||||||
|
|
||||||
|
// Check for user-scoped MCP servers (at root level)
|
||||||
|
if (configData.mcpServers && typeof configData.mcpServers === 'object' && Object.keys(configData.mcpServers).length > 0) {
|
||||||
|
console.log('🔍 Found user-scoped MCP servers:', Object.keys(configData.mcpServers));
|
||||||
|
for (const [name, config] of Object.entries(configData.mcpServers)) {
|
||||||
|
const server = {
|
||||||
|
id: name,
|
||||||
|
name: name,
|
||||||
|
type: 'stdio', // Default type
|
||||||
|
scope: 'user', // User scope - available across all projects
|
||||||
|
config: {},
|
||||||
|
raw: config // Include raw config for full details
|
||||||
|
};
|
||||||
|
|
||||||
|
// Determine transport type and extract config
|
||||||
|
if (config.command) {
|
||||||
|
server.type = 'stdio';
|
||||||
|
server.config.command = config.command;
|
||||||
|
server.config.args = config.args || [];
|
||||||
|
server.config.env = config.env || {};
|
||||||
|
} else if (config.url) {
|
||||||
|
server.type = config.transport || 'http';
|
||||||
|
server.config.url = config.url;
|
||||||
|
server.config.headers = config.headers || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
servers.push(server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for local-scoped MCP servers (project-specific)
|
||||||
|
const currentProjectPath = process.cwd();
|
||||||
|
|
||||||
|
// Check under 'projects' key
|
||||||
|
if (configData.projects && configData.projects[currentProjectPath]) {
|
||||||
|
const projectConfig = configData.projects[currentProjectPath];
|
||||||
|
if (projectConfig.mcpServers && typeof projectConfig.mcpServers === 'object' && Object.keys(projectConfig.mcpServers).length > 0) {
|
||||||
|
console.log(`🔍 Found local-scoped MCP servers for ${currentProjectPath}:`, Object.keys(projectConfig.mcpServers));
|
||||||
|
for (const [name, config] of Object.entries(projectConfig.mcpServers)) {
|
||||||
|
const server = {
|
||||||
|
id: `local:${name}`, // Prefix with scope for uniqueness
|
||||||
|
name: name, // Keep original name
|
||||||
|
type: 'stdio', // Default type
|
||||||
|
scope: 'local', // Local scope - only for this project
|
||||||
|
projectPath: currentProjectPath,
|
||||||
|
config: {},
|
||||||
|
raw: config // Include raw config for full details
|
||||||
|
};
|
||||||
|
|
||||||
|
// Determine transport type and extract config
|
||||||
|
if (config.command) {
|
||||||
|
server.type = 'stdio';
|
||||||
|
server.config.command = config.command;
|
||||||
|
server.config.args = config.args || [];
|
||||||
|
server.config.env = config.env || {};
|
||||||
|
} else if (config.url) {
|
||||||
|
server.type = config.transport || 'http';
|
||||||
|
server.config.url = config.url;
|
||||||
|
server.config.headers = config.headers || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
servers.push(server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`📋 Found ${servers.length} MCP servers in config`);
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
configPath: configPath,
|
||||||
|
servers: servers
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error reading Claude config:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
error: 'Failed to read Claude configuration',
|
||||||
|
details: error.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Helper functions to parse Claude CLI output
|
// Helper functions to parse Claude CLI output
|
||||||
function parseClaudeListOutput(output) {
|
function parseClaudeListOutput(output) {
|
||||||
// Parse the output from 'claude mcp list' command
|
|
||||||
// Format: "name: command/url" or "name: url (TYPE)"
|
|
||||||
const servers = [];
|
const servers = [];
|
||||||
const lines = output.split('\n').filter(line => line.trim());
|
const lines = output.split('\n').filter(line => line.trim());
|
||||||
|
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
|
// Skip the header line
|
||||||
|
if (line.includes('Checking MCP server health')) continue;
|
||||||
|
|
||||||
|
// Parse lines like "test: test test - ✗ Failed to connect"
|
||||||
|
// or "server-name: command or description - ✓ Connected"
|
||||||
if (line.includes(':')) {
|
if (line.includes(':')) {
|
||||||
const colonIndex = line.indexOf(':');
|
const colonIndex = line.indexOf(':');
|
||||||
const name = line.substring(0, colonIndex).trim();
|
const name = line.substring(0, colonIndex).trim();
|
||||||
|
|
||||||
|
// Skip empty names
|
||||||
|
if (!name) continue;
|
||||||
|
|
||||||
|
// Extract the rest after the name
|
||||||
const rest = line.substring(colonIndex + 1).trim();
|
const rest = line.substring(colonIndex + 1).trim();
|
||||||
|
|
||||||
|
// Try to extract description and status
|
||||||
|
let description = rest;
|
||||||
|
let status = 'unknown';
|
||||||
let type = 'stdio'; // default type
|
let type = 'stdio'; // default type
|
||||||
|
|
||||||
// Check if it has transport type in parentheses like "(SSE)" or "(HTTP)"
|
// Check for status indicators
|
||||||
const typeMatch = rest.match(/\((\w+)\)\s*$/);
|
if (rest.includes('✓') || rest.includes('✗')) {
|
||||||
if (typeMatch) {
|
const statusMatch = rest.match(/(.*?)\s*-\s*([✓✗].*)$/);
|
||||||
type = typeMatch[1].toLowerCase();
|
if (statusMatch) {
|
||||||
} else if (rest.startsWith('http://') || rest.startsWith('https://')) {
|
description = statusMatch[1].trim();
|
||||||
// If it's a URL but no explicit type, assume HTTP
|
status = statusMatch[2].includes('✓') ? 'connected' : 'failed';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to determine type from description
|
||||||
|
if (description.startsWith('http://') || description.startsWith('https://')) {
|
||||||
type = 'http';
|
type = 'http';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name) {
|
servers.push({
|
||||||
servers.push({
|
name,
|
||||||
name,
|
type,
|
||||||
type,
|
status: status || 'active',
|
||||||
status: 'active'
|
description
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,23 @@ function ToolsSettings({ isOpen, onClose }) {
|
|||||||
try {
|
try {
|
||||||
const token = localStorage.getItem('auth-token');
|
const token = localStorage.getItem('auth-token');
|
||||||
|
|
||||||
// First try to get servers using Claude CLI
|
// Try to read directly from config files for complete details
|
||||||
|
const configResponse = await fetch('/api/mcp/config/read', {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (configResponse.ok) {
|
||||||
|
const configData = await configResponse.json();
|
||||||
|
if (configData.success && configData.servers) {
|
||||||
|
setMcpServers(configData.servers);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to Claude CLI
|
||||||
const cliResponse = await fetch('/api/mcp/cli/list', {
|
const cliResponse = await fetch('/api/mcp/cli/list', {
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`,
|
'Authorization': `Bearer ${token}`,
|
||||||
@@ -99,7 +115,7 @@ function ToolsSettings({ isOpen, onClose }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to direct config reading
|
// Final fallback to direct config reading
|
||||||
const response = await fetch('/api/mcp/servers?scope=user', {
|
const response = await fetch('/api/mcp/servers?scope=user', {
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`,
|
'Authorization': `Bearer ${token}`,
|
||||||
@@ -167,8 +183,8 @@ function ToolsSettings({ isOpen, onClose }) {
|
|||||||
try {
|
try {
|
||||||
const token = localStorage.getItem('auth-token');
|
const token = localStorage.getItem('auth-token');
|
||||||
|
|
||||||
// Use Claude CLI to remove the server
|
// Use Claude CLI to remove the server with proper scope
|
||||||
const response = await fetch(`/api/mcp/cli/remove/${serverId}`, {
|
const response = await fetch(`/api/mcp/cli/remove/${serverId}?scope=${scope}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`,
|
'Authorization': `Bearer ${token}`,
|
||||||
@@ -362,7 +378,7 @@ function ToolsSettings({ isOpen, onClose }) {
|
|||||||
setMcpFormData({
|
setMcpFormData({
|
||||||
name: '',
|
name: '',
|
||||||
type: 'stdio',
|
type: 'stdio',
|
||||||
scope: 'user', // Always use user scope
|
scope: 'user', // Always use user scope for global availability
|
||||||
config: {
|
config: {
|
||||||
command: '',
|
command: '',
|
||||||
args: [],
|
args: [],
|
||||||
@@ -386,7 +402,8 @@ function ToolsSettings({ isOpen, onClose }) {
|
|||||||
name: server.name,
|
name: server.name,
|
||||||
type: server.type,
|
type: server.type,
|
||||||
scope: server.scope,
|
scope: server.scope,
|
||||||
config: { ...server.config }
|
config: { ...server.config },
|
||||||
|
raw: server.raw // Store raw config for display
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
resetMcpForm();
|
resetMcpForm();
|
||||||
@@ -846,8 +863,13 @@ function ToolsSettings({ isOpen, onClose }) {
|
|||||||
{server.type}
|
{server.type}
|
||||||
</Badge>
|
</Badge>
|
||||||
<Badge variant="outline" className="text-xs">
|
<Badge variant="outline" className="text-xs">
|
||||||
{server.scope}
|
{server.scope === 'local' ? '📁 local' : server.scope === 'user' ? '👤 user' : server.scope}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
{server.projectPath && (
|
||||||
|
<Badge variant="outline" className="text-xs bg-purple-50 dark:bg-purple-900/20" title={server.projectPath}>
|
||||||
|
{server.projectPath.split('/').pop()}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="text-sm text-muted-foreground space-y-1">
|
<div className="text-sm text-muted-foreground space-y-1">
|
||||||
@@ -860,6 +882,17 @@ function ToolsSettings({ isOpen, onClose }) {
|
|||||||
{server.config.args && server.config.args.length > 0 && (
|
{server.config.args && server.config.args.length > 0 && (
|
||||||
<div>Args: <code className="bg-gray-100 dark:bg-gray-800 px-1 rounded text-xs">{server.config.args.join(' ')}</code></div>
|
<div>Args: <code className="bg-gray-100 dark:bg-gray-800 px-1 rounded text-xs">{server.config.args.join(' ')}</code></div>
|
||||||
)}
|
)}
|
||||||
|
{server.config.env && Object.keys(server.config.env).length > 0 && (
|
||||||
|
<div>Environment: <code className="bg-gray-100 dark:bg-gray-800 px-1 rounded text-xs">{Object.entries(server.config.env).map(([k, v]) => `${k}=${v}`).join(', ')}</code></div>
|
||||||
|
)}
|
||||||
|
{server.raw && (
|
||||||
|
<details className="mt-2">
|
||||||
|
<summary className="cursor-pointer text-xs text-muted-foreground hover:text-foreground">View full config</summary>
|
||||||
|
<pre className="mt-1 text-xs bg-gray-100 dark:bg-gray-800 p-2 rounded overflow-x-auto">
|
||||||
|
{JSON.stringify(server.raw, null, 2)}
|
||||||
|
</pre>
|
||||||
|
</details>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Test Results */}
|
{/* Test Results */}
|
||||||
@@ -1062,6 +1095,18 @@ function ToolsSettings({ isOpen, onClose }) {
|
|||||||
|
|
||||||
{/* Scope is fixed to user - no selection needed */}
|
{/* Scope is fixed to user - no selection needed */}
|
||||||
|
|
||||||
|
{/* Show raw configuration details when editing */}
|
||||||
|
{editingMcpServer && mcpFormData.raw && (
|
||||||
|
<div className="bg-gray-50 dark:bg-gray-900/50 border border-gray-200 dark:border-gray-700 rounded-lg p-4">
|
||||||
|
<h4 className="text-sm font-medium text-foreground mb-2">
|
||||||
|
Configuration Details (from {editingMcpServer.scope === 'global' ? '~/.claude.json' : 'project config'})
|
||||||
|
</h4>
|
||||||
|
<pre className="text-xs bg-gray-100 dark:bg-gray-800 p-3 rounded overflow-x-auto">
|
||||||
|
{JSON.stringify(mcpFormData.raw, null, 2)}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Transport-specific Config */}
|
{/* Transport-specific Config */}
|
||||||
{mcpFormData.type === 'stdio' && (
|
{mcpFormData.type === 'stdio' && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
|||||||
Reference in New Issue
Block a user