feat(ai): 增强 MCP 配置诊断能力

- 复用新增 MCP 表单校验逻辑,向 inspect_mcp_setup 输出服务配置问题

- 将 MCP 配置问题汇总到 AI 设置体检,补充后续处理建议

- 补充 MCP 快照、AI 体检和本地工具执行器测试
This commit is contained in:
Syngnat
2026-06-10 08:33:06 +08:00
parent 7df524e9ef
commit d0b9a7f50c
5 changed files with 162 additions and 2 deletions

View File

@@ -404,6 +404,16 @@ describe('aiLocalToolExecutor', () => {
enabled: true,
timeoutSeconds: 20,
},
{
id: 'server-2',
name: 'Broken',
transport: 'stdio',
command: '',
args: [],
env: {},
enabled: true,
timeoutSeconds: 1,
},
]),
getMCPClientInstallStatuses: vi.fn().mockResolvedValue([
{
@@ -424,9 +434,12 @@ describe('aiLocalToolExecutor', () => {
});
expect(result.success).toBe(true);
expect(result.content).toContain('"serverCount":1');
expect(result.content).toContain('"serverCount":2');
expect(result.content).toContain('"name":"Browser"');
expect(result.content).toContain('"launchCommandPreview":"uvx mcp-server-browser"');
expect(result.content).toContain('"serverConfigurationIssueCount":2');
expect(result.content).toContain('"serversWithConfigurationErrors":1');
expect(result.content).toContain('"key":"command-missing"');
expect(result.content).toContain('"displayName":"Codex"');
expect(result.content).toContain('"launchCommandPreview":"gonavi-mcp-server stdio"');
});

View File

@@ -49,11 +49,46 @@ describe('aiMCPInsights', () => {
expect(snapshot.serverCount).toBe(1);
expect(snapshot.enabledServerCount).toBe(1);
expect(snapshot.discoveredMCPToolCount).toBe(1);
expect(snapshot.serverConfigurationIssueCount).toBe(0);
expect(snapshot.servers[0].launchCommandPreview).toBe('uvx mcp-server-browser');
expect(snapshot.servers[0].configurationIssueCount).toBe(0);
expect(snapshot.servers[0].envVarCount).toBe(2);
expect(snapshot.servers[0].discoveredToolCount).toBe(1);
expect(snapshot.clients[0].displayName).toBe('Claude Code');
expect(snapshot.clients[0].launchCommandPreview).toBe('gonavi-mcp-server stdio');
expect(snapshot.currentClientCount).toBe(1);
});
it('surfaces saved mcp server launch validation issues for ai diagnostics', () => {
const snapshot = buildMCPSetupSnapshot({
mcpServers: [
{
id: 'server-1',
name: 'Broken',
transport: 'stdio',
command: '',
args: [],
env: {},
enabled: true,
timeoutSeconds: 1,
},
],
mcpClientStatuses: [],
mcpTools: [],
});
expect(snapshot.serverConfigurationIssueCount).toBe(2);
expect(snapshot.serversWithConfigurationErrors).toBe(1);
expect(snapshot.enabledServersWithConfigurationIssues).toBe(1);
expect(snapshot.servers[0].configurationErrorCount).toBe(1);
expect(snapshot.servers[0].configurationWarningCount).toBe(1);
expect(snapshot.servers[0].configurationCanTest).toBe(false);
expect(snapshot.servers[0].configurationIssues.map((issue) => issue.key)).toEqual([
'command-missing',
'timeout-out-of-range',
]);
expect(snapshot.warnings).toContain('有 1 个 MCP 服务存在启动配置错误,测试和工具发现可能失败');
expect(snapshot.nextActions).toContain('先修复 MCP 服务配置检查里的错误项,再重新测试服务');
expect(snapshot.message).toContain('2 个配置检查项需要确认');
});
});

View File

@@ -1,4 +1,5 @@
import type { AIMCPClientInstallStatus, AIMCPServerConfig, AIMCPToolDescriptor } from '../../types';
import { validateMCPServerDraft } from '../../utils/mcpServerValidation';
const SERVER_TOOL_PREVIEW_LIMIT = 20;
@@ -38,6 +39,7 @@ export const buildMCPSetupSnapshot = (params: {
alias: tool.alias,
title: tool.title || tool.originalName || tool.alias,
}));
const validation = validateMCPServerDraft(server);
return {
id: server.id,
name: server.name,
@@ -52,6 +54,11 @@ export const buildMCPSetupSnapshot = (params: {
discoveredToolCount: serverTools.length,
discoveredTools: serverTools.slice(0, SERVER_TOOL_PREVIEW_LIMIT),
discoveredToolsTruncated: serverTools.length > SERVER_TOOL_PREVIEW_LIMIT,
configurationIssueCount: validation.issues.length,
configurationErrorCount: validation.errorCount,
configurationWarningCount: validation.warningCount,
configurationCanTest: validation.canTest,
configurationIssues: validation.issues,
};
}),
);
@@ -74,20 +81,45 @@ export const buildMCPSetupSnapshot = (params: {
const enabledServerCount = normalizedServers.filter((server) => server.enabled).length;
const installedClientCount = normalizedClientStatuses.filter((item) => item.installed).length;
const currentClientCount = normalizedClientStatuses.filter((item) => item.matchesCurrent).length;
const serversWithConfigurationErrors = normalizedServers.filter((server) => server.configurationErrorCount > 0).length;
const serversWithConfigurationWarnings = normalizedServers.filter((server) => server.configurationWarningCount > 0).length;
const serverConfigurationIssueCount = normalizedServers
.reduce((total, server) => total + server.configurationIssueCount, 0);
const enabledServersWithConfigurationIssues = normalizedServers
.filter((server) => server.enabled && server.configurationIssueCount > 0)
.length;
const warnings: string[] = [];
const nextActions: string[] = [];
if (serversWithConfigurationErrors > 0) {
warnings.push(`${serversWithConfigurationErrors} 个 MCP 服务存在启动配置错误,测试和工具发现可能失败`);
nextActions.push('先修复 MCP 服务配置检查里的错误项,再重新测试服务');
} else if (serversWithConfigurationWarnings > 0) {
warnings.push(`${serversWithConfigurationWarnings} 个 MCP 服务存在启动配置告警,建议在排查工具发现失败前先确认`);
nextActions.push('打开对应 MCP 服务,按配置检查提示拆分启动命令、参数和超时时间');
}
return {
serverCount: normalizedServers.length,
enabledServerCount,
disabledServerCount: normalizedServers.length - enabledServerCount,
discoveredMCPToolCount: Array.isArray(mcpTools) ? mcpTools.length : 0,
serverConfigurationIssueCount,
serversWithConfigurationErrors,
serversWithConfigurationWarnings,
enabledServersWithConfigurationIssues,
servers: normalizedServers,
clientInstallCount: normalizedClientStatuses.length,
installedClientCount,
currentClientCount,
detectedClientCount: normalizedClientStatuses.filter((item) => item.clientDetected).length,
clients: normalizedClientStatuses,
warnings,
nextActions,
message: normalizedServers.length > 0
? `当前共配置 ${normalizedServers.length} 个 MCP 服务,其中 ${enabledServerCount} 个已启用`
? serverConfigurationIssueCount > 0
? `当前共配置 ${normalizedServers.length} 个 MCP 服务,其中 ${enabledServerCount} 个已启用,${serverConfigurationIssueCount} 个配置检查项需要确认`
: `当前共配置 ${normalizedServers.length} 个 MCP 服务,其中 ${enabledServerCount} 个已启用`
: '当前还没有配置任何 MCP 服务',
};
};

View File

@@ -131,4 +131,80 @@ describe('buildAISetupHealthSnapshot', () => {
expect(snapshot.mcp.message).toContain('当前共配置 1 个 MCP 服务');
expect(snapshot.guidance.enabledSkillPreview).toContain('结构审查');
});
it('includes mcp server configuration validation issues in setup health warnings', () => {
const snapshot = buildAISetupHealthSnapshot({
providers: [{
id: 'provider-1',
type: 'openai',
name: 'OpenAI 主账号',
apiKey: '',
hasSecret: true,
baseUrl: 'https://api.openai.com/v1',
model: 'gpt-5.4',
models: ['gpt-5.4'],
maxTokens: 32000,
temperature: 0.2,
}],
activeProviderId: 'provider-1',
safetyLevel: 'readonly',
contextLevel: 'schema_only',
builtinToolNames: ['inspect_ai_setup_health', 'inspect_mcp_setup'],
mcpServers: [{
id: 'server-1',
name: 'Broken',
transport: 'stdio',
command: '',
args: [],
env: {},
enabled: true,
timeoutSeconds: 1,
}],
mcpClientStatuses: [{
client: 'codex',
displayName: 'Codex',
installed: true,
matchesCurrent: true,
clientDetected: true,
clientCommand: 'codex',
clientPath: 'C:/Tools/codex.exe',
configPath: 'C:/Users/demo/.codex/config.toml',
command: 'gonavi-mcp-server',
args: ['stdio'],
message: '已接入当前 GoNavi MCP',
}],
mcpTools: [],
skills: [{
id: 'skill-1',
name: '结构审查',
description: '优先核对字段',
systemPrompt: '先看字段和索引,再给结论。',
enabled: true,
scopes: ['database'],
requiredTools: ['get_columns'],
}],
userPromptSettings: {
global: '回答前先核对上下文。',
database: '',
jvm: '',
jvmDiagnostic: '',
},
activeContext: {
connectionId: 'conn-1',
dbName: 'crm',
},
activeContextItems: [{
dbName: 'crm',
tableName: 'orders',
ddl: 'CREATE TABLE orders (...)',
}],
});
expect(snapshot.status).toBe('needs_attention');
expect(snapshot.summary.mcpServerConfigurationIssueCount).toBe(2);
expect(snapshot.summary.mcpServersWithConfigurationErrors).toBe(1);
expect(snapshot.warnings).toContain('有 1 个 MCP 服务存在启动配置错误,测试和工具发现可能失败');
expect(snapshot.nextActions).toContain('先修复 MCP 服务配置检查里的错误项,再重新测试服务');
expect(snapshot.mcp.servers[0].configurationIssues.map((issue) => issue.key)).toContain('command-missing');
});
});

View File

@@ -109,6 +109,8 @@ export const buildAISetupHealthSnapshot = (params: {
appendUnique(warnings, '当前还没有配置任何 MCP 服务');
appendUnique(nextActions, '如需扩展 AI 工具能力,可新增并测试至少 1 个 MCP 服务');
}
mcpSnapshot.warnings.forEach((warning) => appendUnique(warnings, warning));
mcpSnapshot.nextActions.forEach((action) => appendUnique(nextActions, action));
if (mcpSnapshot.currentClientCount === 0) {
appendUnique(warnings, 'Claude Code / Codex 还没有任何客户端接入当前 GoNavi MCP');
appendUnique(nextActions, '如需让外部 CLI 使用 GoNavi MCP先把当前 GoNavi 接入 Claude Code 或 Codex');
@@ -160,6 +162,8 @@ export const buildAISetupHealthSnapshot = (params: {
customPromptCount: guidanceSnapshot.customPromptCount,
mcpServerCount: mcpSnapshot.serverCount,
enabledMCPServerCount: mcpSnapshot.enabledServerCount,
mcpServerConfigurationIssueCount: mcpSnapshot.serverConfigurationIssueCount,
mcpServersWithConfigurationErrors: mcpSnapshot.serversWithConfigurationErrors,
installedExternalClientCount: mcpSnapshot.installedClientCount,
currentExternalClientCount: mcpSnapshot.currentClientCount,
discoveredMCPToolCount: mcpSnapshot.discoveredMCPToolCount,