mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-13 02:41:50 +08:00
feat(mcp): add cross-platform MCP config support with Windows cmd /c auto-fix
- Add buildCrossPlatformMcpConfig() helper for automatic Windows cmd /c wrapping - Add checkWindowsMcpCompatibility() to detect configs needing Windows fixes - Add autoFixWindowsMcpConfig() to automatically fix incompatible configs - Add showWindowsMcpCompatibilityWarning() dialog for user confirmation - Simplify recommended MCP configs (ace-tool, chrome-devtools, exa) using helper - Auto-detect and prompt when adding MCP servers with npx/npm/node/python commands - Add i18n translations for Windows compatibility warnings (en/zh) Supported commands for auto-detection: npx, npm, node, python, python3, pip, pip3, pnpm, yarn, bun Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -84,6 +84,147 @@ function getCliMode() {
|
|||||||
return currentCliMode;
|
return currentCliMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========== Cross-Platform MCP Helpers ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build cross-platform MCP server configuration
|
||||||
|
* On Windows, wraps npx/node/python commands with cmd /c for proper execution
|
||||||
|
* @param {string} command - The command to run (e.g., 'npx', 'node', 'python')
|
||||||
|
* @param {string[]} args - Command arguments
|
||||||
|
* @param {object} [options] - Additional options (env, type, etc.)
|
||||||
|
* @returns {object} MCP server configuration
|
||||||
|
*/
|
||||||
|
function buildCrossPlatformMcpConfig(command, args = [], options = {}) {
|
||||||
|
const { env, type, ...rest } = options;
|
||||||
|
|
||||||
|
// Commands that need cmd /c wrapper on Windows
|
||||||
|
const windowsWrappedCommands = ['npx', 'npm', 'node', 'python', 'python3', 'pip', 'pip3', 'pnpm', 'yarn', 'bun'];
|
||||||
|
const needsWindowsWrapper = isWindowsPlatform && windowsWrappedCommands.includes(command.toLowerCase());
|
||||||
|
|
||||||
|
const config = needsWindowsWrapper
|
||||||
|
? { command: 'cmd', args: ['/c', command, ...args] }
|
||||||
|
: { command, args };
|
||||||
|
|
||||||
|
// Add optional fields
|
||||||
|
if (type) config.type = type;
|
||||||
|
if (env && Object.keys(env).length > 0) config.env = env;
|
||||||
|
Object.assign(config, rest);
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if MCP config needs Windows cmd /c wrapper
|
||||||
|
* @param {object} serverConfig - MCP server configuration
|
||||||
|
* @returns {object} { needsWrapper: boolean, command: string }
|
||||||
|
*/
|
||||||
|
function checkWindowsMcpCompatibility(serverConfig) {
|
||||||
|
if (!isWindowsPlatform) return { needsWrapper: false };
|
||||||
|
|
||||||
|
const command = serverConfig.command?.toLowerCase() || '';
|
||||||
|
const windowsWrappedCommands = ['npx', 'npm', 'node', 'python', 'python3', 'pip', 'pip3', 'pnpm', 'yarn', 'bun'];
|
||||||
|
|
||||||
|
// Already wrapped with cmd
|
||||||
|
if (command === 'cmd') return { needsWrapper: false };
|
||||||
|
|
||||||
|
const needsWrapper = windowsWrappedCommands.includes(command);
|
||||||
|
return { needsWrapper, command: serverConfig.command };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-fix MCP config for Windows platform
|
||||||
|
* @param {object} serverConfig - Original MCP server configuration
|
||||||
|
* @returns {object} Fixed configuration (or original if no fix needed)
|
||||||
|
*/
|
||||||
|
function autoFixWindowsMcpConfig(serverConfig) {
|
||||||
|
const { needsWrapper, command } = checkWindowsMcpCompatibility(serverConfig);
|
||||||
|
|
||||||
|
if (!needsWrapper) return serverConfig;
|
||||||
|
|
||||||
|
// Create new config with cmd /c wrapper
|
||||||
|
const fixedConfig = {
|
||||||
|
...serverConfig,
|
||||||
|
command: 'cmd',
|
||||||
|
args: ['/c', command, ...(serverConfig.args || [])]
|
||||||
|
};
|
||||||
|
|
||||||
|
return fixedConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show Windows compatibility warning for MCP config
|
||||||
|
* @param {string} serverName - Name of the MCP server
|
||||||
|
* @param {object} serverConfig - MCP server configuration
|
||||||
|
* @returns {Promise<boolean>} True if user confirms auto-fix, false to keep original
|
||||||
|
*/
|
||||||
|
async function showWindowsMcpCompatibilityWarning(serverName, serverConfig) {
|
||||||
|
const { needsWrapper, command } = checkWindowsMcpCompatibility(serverConfig);
|
||||||
|
|
||||||
|
if (!needsWrapper) return false;
|
||||||
|
|
||||||
|
// Show warning toast with auto-fix option
|
||||||
|
const message = t('mcp.windows.compatibilityWarning', {
|
||||||
|
name: serverName,
|
||||||
|
command: command
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
// Create custom toast with action buttons
|
||||||
|
const toastContainer = document.getElementById('refreshToast') || createToastContainer();
|
||||||
|
const toastId = `windows-mcp-warning-${Date.now()}`;
|
||||||
|
|
||||||
|
const toastHtml = `
|
||||||
|
<div id="${toastId}" class="fixed bottom-4 right-4 bg-warning text-warning-foreground p-4 rounded-lg shadow-lg max-w-md z-50 animate-slide-up">
|
||||||
|
<div class="flex items-start gap-3">
|
||||||
|
<i data-lucide="alert-triangle" class="w-5 h-5 flex-shrink-0 mt-0.5"></i>
|
||||||
|
<div class="flex-1">
|
||||||
|
<p class="font-medium mb-2">${t('mcp.windows.title')}</p>
|
||||||
|
<p class="text-sm opacity-90 mb-3">${message}</p>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<button class="px-3 py-1.5 text-sm bg-background text-foreground rounded hover:opacity-90"
|
||||||
|
onclick="document.getElementById('${toastId}').remove(); window._mcpWindowsResolve && window._mcpWindowsResolve(true)">
|
||||||
|
${t('mcp.windows.autoFix')}
|
||||||
|
</button>
|
||||||
|
<button class="px-3 py-1.5 text-sm border border-current rounded hover:opacity-90"
|
||||||
|
onclick="document.getElementById('${toastId}').remove(); window._mcpWindowsResolve && window._mcpWindowsResolve(false)">
|
||||||
|
${t('mcp.windows.keepOriginal')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button onclick="document.getElementById('${toastId}').remove(); window._mcpWindowsResolve && window._mcpWindowsResolve(false)"
|
||||||
|
class="text-current opacity-70 hover:opacity-100">
|
||||||
|
<i data-lucide="x" class="w-4 h-4"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Store resolve function globally for button clicks
|
||||||
|
window._mcpWindowsResolve = (result) => {
|
||||||
|
delete window._mcpWindowsResolve;
|
||||||
|
resolve(result);
|
||||||
|
};
|
||||||
|
|
||||||
|
document.body.insertAdjacentHTML('beforeend', toastHtml);
|
||||||
|
|
||||||
|
// Initialize icons
|
||||||
|
if (typeof lucide !== 'undefined') {
|
||||||
|
lucide.createIcons();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-dismiss after 15 seconds (keep original)
|
||||||
|
setTimeout(() => {
|
||||||
|
const toast = document.getElementById(toastId);
|
||||||
|
if (toast) {
|
||||||
|
toast.remove();
|
||||||
|
if (window._mcpWindowsResolve) {
|
||||||
|
window._mcpWindowsResolve(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 15000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// ========== Codex MCP Functions ==========
|
// ========== Codex MCP Functions ==========
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -847,6 +988,19 @@ async function submitMcpCreateFromJson() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function createMcpServerWithConfig(name, serverConfig, scope = 'project') {
|
async function createMcpServerWithConfig(name, serverConfig, scope = 'project') {
|
||||||
|
// Check Windows compatibility and offer auto-fix if needed
|
||||||
|
const { needsWrapper } = checkWindowsMcpCompatibility(serverConfig);
|
||||||
|
let finalConfig = serverConfig;
|
||||||
|
|
||||||
|
if (needsWrapper) {
|
||||||
|
// Show warning and ask user whether to auto-fix
|
||||||
|
const shouldAutoFix = await showWindowsMcpCompatibilityWarning(name, serverConfig);
|
||||||
|
if (shouldAutoFix) {
|
||||||
|
finalConfig = autoFixWindowsMcpConfig(serverConfig);
|
||||||
|
console.log('[MCP] Auto-fixed config for Windows:', finalConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Submit to API
|
// Submit to API
|
||||||
try {
|
try {
|
||||||
let response;
|
let response;
|
||||||
@@ -859,7 +1013,7 @@ async function createMcpServerWithConfig(name, serverConfig, scope = 'project')
|
|||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
serverName: name,
|
serverName: name,
|
||||||
serverConfig: serverConfig
|
serverConfig: finalConfig
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
scopeLabel = 'Codex';
|
scopeLabel = 'Codex';
|
||||||
@@ -869,7 +1023,7 @@ async function createMcpServerWithConfig(name, serverConfig, scope = 'project')
|
|||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
serverName: name,
|
serverName: name,
|
||||||
serverConfig: serverConfig
|
serverConfig: finalConfig
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
scopeLabel = 'global';
|
scopeLabel = 'global';
|
||||||
@@ -880,7 +1034,7 @@ async function createMcpServerWithConfig(name, serverConfig, scope = 'project')
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
projectPath: projectPath,
|
projectPath: projectPath,
|
||||||
serverName: name,
|
serverName: name,
|
||||||
serverConfig: serverConfig
|
serverConfig: finalConfig
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
scopeLabel = 'project';
|
scopeLabel = 'project';
|
||||||
@@ -1231,16 +1385,14 @@ const RECOMMENDED_MCP_SERVERS = [
|
|||||||
descKey: 'mcp.ace-tool.field.token.desc'
|
descKey: 'mcp.ace-tool.field.token.desc'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
buildConfig: (values) => ({
|
// Uses buildCrossPlatformMcpConfig for automatic Windows cmd /c wrapping
|
||||||
command: 'npx',
|
buildConfig: (values) => buildCrossPlatformMcpConfig('npx', [
|
||||||
args: [
|
'ace-tool',
|
||||||
'ace-tool',
|
'--base-url',
|
||||||
'--base-url',
|
values.baseUrl || 'https://acemcp.heroman.wtf/relay/',
|
||||||
values.baseUrl || 'https://acemcp.heroman.wtf/relay/',
|
'--token',
|
||||||
'--token',
|
values.token
|
||||||
values.token
|
])
|
||||||
]
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'chrome-devtools',
|
id: 'chrome-devtools',
|
||||||
@@ -1249,12 +1401,8 @@ const RECOMMENDED_MCP_SERVERS = [
|
|||||||
icon: 'chrome',
|
icon: 'chrome',
|
||||||
category: 'browser',
|
category: 'browser',
|
||||||
fields: [],
|
fields: [],
|
||||||
buildConfig: () => ({
|
// Uses buildCrossPlatformMcpConfig for automatic Windows cmd /c wrapping
|
||||||
type: 'stdio',
|
buildConfig: () => buildCrossPlatformMcpConfig('npx', ['chrome-devtools-mcp@latest'], { type: 'stdio' })
|
||||||
command: 'npx',
|
|
||||||
args: ['chrome-devtools-mcp@latest'],
|
|
||||||
env: {}
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'exa',
|
id: 'exa',
|
||||||
@@ -1273,16 +1421,10 @@ const RECOMMENDED_MCP_SERVERS = [
|
|||||||
descKey: 'mcp.exa.field.apiKey.desc'
|
descKey: 'mcp.exa.field.apiKey.desc'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
// Uses buildCrossPlatformMcpConfig for automatic Windows cmd /c wrapping
|
||||||
buildConfig: (values) => {
|
buildConfig: (values) => {
|
||||||
const config = {
|
const env = values.apiKey ? { EXA_API_KEY: values.apiKey } : undefined;
|
||||||
command: 'npx',
|
return buildCrossPlatformMcpConfig('npx', ['-y', 'exa-mcp-server'], { env });
|
||||||
args: ['-y', 'exa-mcp-server']
|
|
||||||
};
|
|
||||||
// Only add env if API key is provided
|
|
||||||
if (values.apiKey) {
|
|
||||||
config.env = { EXA_API_KEY: values.apiKey };
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -995,6 +995,12 @@ const i18n = {
|
|||||||
'mcp.clickToEdit': 'Click to edit',
|
'mcp.clickToEdit': 'Click to edit',
|
||||||
'mcp.clickToViewDetails': 'Click to view details',
|
'mcp.clickToViewDetails': 'Click to view details',
|
||||||
|
|
||||||
|
// Windows MCP Compatibility
|
||||||
|
'mcp.windows.title': 'Windows Compatibility Warning',
|
||||||
|
'mcp.windows.compatibilityWarning': 'The MCP server "{name}" uses "{command}" which requires "cmd /c" wrapper on Windows to work properly with Claude Code.',
|
||||||
|
'mcp.windows.autoFix': 'Auto-fix (Recommended)',
|
||||||
|
'mcp.windows.keepOriginal': 'Keep Original',
|
||||||
|
|
||||||
// Hook Manager
|
// Hook Manager
|
||||||
'hook.projectHooks': 'Project Hooks',
|
'hook.projectHooks': 'Project Hooks',
|
||||||
'hook.projectFile': '.claude/settings.json',
|
'hook.projectFile': '.claude/settings.json',
|
||||||
@@ -3128,6 +3134,12 @@ const i18n = {
|
|||||||
'mcp.clickToEdit': '点击编辑',
|
'mcp.clickToEdit': '点击编辑',
|
||||||
'mcp.clickToViewDetails': '点击查看详情',
|
'mcp.clickToViewDetails': '点击查看详情',
|
||||||
|
|
||||||
|
// Windows MCP 兼容性
|
||||||
|
'mcp.windows.title': 'Windows 兼容性警告',
|
||||||
|
'mcp.windows.compatibilityWarning': 'MCP 服务器 "{name}" 使用的 "{command}" 命令需要在 Windows 上添加 "cmd /c" 包装才能与 Claude Code 正常工作。',
|
||||||
|
'mcp.windows.autoFix': '自动修复(推荐)',
|
||||||
|
'mcp.windows.keepOriginal': '保持原样',
|
||||||
|
|
||||||
// Hook Manager
|
// Hook Manager
|
||||||
'hook.projectHooks': '项目钩子',
|
'hook.projectHooks': '项目钩子',
|
||||||
'hook.projectFile': '.claude/settings.json',
|
'hook.projectFile': '.claude/settings.json',
|
||||||
|
|||||||
Reference in New Issue
Block a user