mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-13 02:41:50 +08:00
fix(mcp): support same-name MCP servers with different configs
- Add getMcpConfigHash() to generate unique config fingerprints - Modify getAllAvailableMcpServers() to differentiate same-name servers with different configurations using name@project-folder suffix - Update renderAvailableServerCard() to show source project info and use originalName for correct installation - Fix event handlers to use btn.dataset for event bubbling safety - Add CCW Tools MCP installation with npx for cross-platform compatibility 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -159,30 +159,83 @@ function updateMcpBadge() {
|
||||
}
|
||||
|
||||
// ========== Helpers ==========
|
||||
|
||||
/**
|
||||
* Generate a unique key for MCP server config comparison
|
||||
* Used to distinguish servers with same name but different configurations
|
||||
*/
|
||||
function getMcpConfigHash(config) {
|
||||
const cmd = config.command || '';
|
||||
const args = (config.args || []).join('|');
|
||||
const envKeys = Object.keys(config.env || {}).sort().join(',');
|
||||
return `${cmd}::${args}::${envKeys}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all available MCP servers from all sources
|
||||
* Supports servers with same name but different configurations from different projects
|
||||
*/
|
||||
function getAllAvailableMcpServers() {
|
||||
const allServers = {};
|
||||
const configHashes = {}; // Track unique configs per server name
|
||||
|
||||
// Collect global servers first
|
||||
for (const [name, serverConfig] of Object.entries(mcpGlobalServers)) {
|
||||
const hash = getMcpConfigHash(serverConfig);
|
||||
allServers[name] = {
|
||||
config: serverConfig,
|
||||
usedIn: [],
|
||||
isGlobal: true
|
||||
isGlobal: true,
|
||||
configHash: hash
|
||||
};
|
||||
configHashes[name] = { [hash]: name };
|
||||
}
|
||||
|
||||
// Collect servers from all projects
|
||||
// Collect servers from all projects - handle same name with different configs
|
||||
for (const [path, config] of Object.entries(mcpAllProjects)) {
|
||||
const servers = config.mcpServers || {};
|
||||
for (const [name, serverConfig] of Object.entries(servers)) {
|
||||
if (!allServers[name]) {
|
||||
allServers[name] = {
|
||||
config: serverConfig,
|
||||
usedIn: [],
|
||||
isGlobal: false
|
||||
};
|
||||
const hash = getMcpConfigHash(serverConfig);
|
||||
|
||||
if (!configHashes[name]) {
|
||||
// First occurrence of this server name
|
||||
configHashes[name] = {};
|
||||
}
|
||||
|
||||
if (!configHashes[name][hash]) {
|
||||
// New unique configuration for this server name
|
||||
// Use suffixed key if name already exists with different config
|
||||
let serverKey = name;
|
||||
if (allServers[name] && allServers[name].configHash !== hash) {
|
||||
// Generate unique key: name@project-folder
|
||||
const projectFolder = path.split('\\').pop() || path.split('/').pop() || 'unknown';
|
||||
serverKey = `${name}@${projectFolder}`;
|
||||
// Avoid collisions
|
||||
let suffix = 1;
|
||||
while (allServers[serverKey]) {
|
||||
serverKey = `${name}@${projectFolder}-${suffix++}`;
|
||||
}
|
||||
}
|
||||
|
||||
configHashes[name][hash] = serverKey;
|
||||
|
||||
if (!allServers[serverKey]) {
|
||||
allServers[serverKey] = {
|
||||
config: serverConfig,
|
||||
usedIn: [],
|
||||
isGlobal: false,
|
||||
configHash: hash,
|
||||
originalName: name, // Store original name for installation
|
||||
sourceProject: path // Store source project for reference
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Track which projects use this config
|
||||
const serverKey = configHashes[name][hash];
|
||||
if (allServers[serverKey]) {
|
||||
allServers[serverKey].usedIn.push(path);
|
||||
}
|
||||
allServers[name].usedIn.push(path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -526,3 +579,42 @@ async function createMcpServerWithConfig(name, serverConfig) {
|
||||
showRefreshToast(`Failed to create MCP server: ${err.message}`, 'error');
|
||||
}
|
||||
}
|
||||
// ========== CCW Tools MCP Installation ==========
|
||||
async function installCcwToolsMcp() {
|
||||
// Define CCW Tools MCP server configuration
|
||||
// Use npx for better cross-platform compatibility (handles PATH issues)
|
||||
const ccwToolsConfig = {
|
||||
command: "npx",
|
||||
args: ["-y", "ccw-mcp"]
|
||||
};
|
||||
|
||||
try {
|
||||
// Show loading toast
|
||||
showRefreshToast('Installing CCW Tools MCP...', 'info');
|
||||
|
||||
// Use the existing copyMcpServerToProject function
|
||||
const response = await fetch('/api/mcp-copy-server', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
projectPath: projectPath,
|
||||
serverName: 'ccw-tools',
|
||||
serverConfig: ccwToolsConfig
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Failed to install CCW Tools MCP');
|
||||
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
await loadMcpConfig();
|
||||
renderMcpManager();
|
||||
showRefreshToast('CCW Tools MCP installed successfully', 'success');
|
||||
} else {
|
||||
showRefreshToast(result.error || 'Failed to install CCW Tools MCP', 'error');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to install CCW Tools MCP:', err);
|
||||
showRefreshToast(`Failed to install CCW Tools MCP: ${err.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user