feat(ai): 新增 MCP 工具参数探针

This commit is contained in:
Syngnat
2026-06-10 15:19:23 +08:00
parent c4153202ba
commit 1d1d8d21cd
14 changed files with 511 additions and 1 deletions

View File

@@ -42,6 +42,9 @@ describe('AIBuiltinToolsCatalog', () => {
expect(markup).toContain('inspect_mcp_setup');
expect(markup).toContain('新增 MCP 填写指引');
expect(markup).toContain('inspect_mcp_authoring_guide');
expect(markup).toContain('查看 MCP 工具参数');
expect(markup).toContain('inspect_mcp_tool_schema');
expect(markup).toContain('inputSchema');
expect(markup).toContain('查看当前提示与 Skills');
expect(markup).toContain('inspect_ai_guidance');
expect(markup).toContain('查看当前 AI 上下文');

View File

@@ -80,6 +80,11 @@ const BUILTIN_TOOL_FLOWS = [
steps: 'inspect_mcp_authoring_guide → inspect_mcp_setup',
description: '适合先读真实字段说明、模板样例和整行命令拆分规则,再结合当前 MCP 配置现状判断应该新增哪种启动方式。',
},
{
title: '查看 MCP 工具参数',
steps: 'inspect_mcp_setup → inspect_mcp_tool_schema',
description: '适合先找到当前真实发现到的 MCP 工具 alias再读取对应 inputSchema、必填字段、枚举和嵌套参数路径避免调用外部 MCP 工具时乱填 arguments。',
},
{
title: '查看当前提示与 Skills',
steps: 'inspect_ai_guidance → inspect_ai_runtime',

View File

@@ -323,6 +323,45 @@ describe('aiLocalToolExecutor AI config inspection tools', () => {
expect(result.content).toContain('"exampleLaunchPreview":"uvx some-mcp-server"');
});
it('returns mcp tool input schemas so the model can build arguments from discovered tool metadata', async () => {
const result = await executeLocalAIToolCall({
toolCall: buildToolCall('inspect_mcp_tool_schema', {
alias: 'github_create_issue',
}),
connections: [buildConnection()],
mcpTools: [{
alias: 'github_create_issue',
originalName: 'create_issue',
serverId: 'github-server',
serverName: 'GitHub',
title: '创建 Issue',
description: 'Create a GitHub issue',
inputSchema: {
type: 'object',
required: ['owner', 'repo', 'title'],
properties: {
owner: { type: 'string', description: '仓库 owner' },
repo: { type: 'string', description: '仓库名' },
title: { type: 'string', description: 'Issue 标题' },
state: { type: 'string', enum: ['open', 'closed'] },
},
},
}],
toolContextMap: new Map(),
runtime: {
getDatabases: vi.fn(),
getTables: vi.fn(),
},
});
expect(result.success).toBe(true);
expect(result.content).toContain('"alias":"github_create_issue"');
expect(result.content).toContain('"requiredParameters":["owner","repo","title"]');
expect(result.content).toContain('"path":"state"');
expect(result.content).toContain('"enumValues":["open","closed"]');
expect(result.content).toContain('调用 github_create_issue 前必须提供owner, repo, title');
});
it('returns the current ai guidance snapshot so the model can inspect active prompts and enabled skills', async () => {
const result = await executeLocalAIToolCall({
toolCall: buildToolCall('inspect_ai_guidance', {}),

View File

@@ -0,0 +1,99 @@
import { describe, expect, it } from 'vitest';
import { buildMCPToolSchemaSnapshot } from './aiMCPToolSchemaInsights';
describe('aiMCPToolSchemaInsights', () => {
it('summarizes discovered mcp tool input schemas with required, enum, and nested parameter hints', () => {
const snapshot = buildMCPToolSchemaSnapshot({
alias: 'github_create_issue',
mcpTools: [
{
alias: 'github_create_issue',
originalName: 'create_issue',
serverId: 'github-server',
serverName: 'GitHub',
title: '创建 Issue',
description: 'Create a GitHub issue',
inputSchema: {
type: 'object',
required: ['owner', 'repo', 'title'],
properties: {
owner: { type: 'string', description: '仓库 owner' },
repo: { type: 'string', description: '仓库名' },
title: { type: 'string', description: 'Issue 标题' },
priority: { type: 'string', enum: ['low', 'medium', 'high'], default: 'medium' },
labels: {
type: 'array',
items: { type: 'string' },
},
metadata: {
type: 'object',
properties: {
milestone: { type: 'string', description: '里程碑' },
},
},
},
},
},
],
});
expect(snapshot.matchedToolCount).toBe(1);
expect(snapshot.tools[0].alias).toBe('github_create_issue');
expect(snapshot.tools[0].requiredParameters).toEqual(['owner', 'repo', 'title']);
expect(snapshot.tools[0].parameters.map((item) => item.path)).toContain('metadata.milestone');
expect(snapshot.tools[0].parameters.find((item) => item.path === 'priority')?.enumValues).toEqual(['low', 'medium', 'high']);
expect(snapshot.tools[0].parameters.find((item) => item.path === 'labels')?.arrayItemType).toBe('string');
expect(snapshot.tools[0].usageHints).toContain('调用 github_create_issue 前必须提供owner, repo, title');
expect(snapshot.tools[0].usageHints).toContain('priority 只能从枚举值中选择low / medium / high');
expect(snapshot.tools[0].inputSchema).toBeUndefined();
});
it('can include the raw schema when deep debugging a mcp tool argument mismatch', () => {
const snapshot = buildMCPToolSchemaSnapshot({
alias: 'browser_open',
includeSchema: true,
mcpTools: [
{
alias: 'browser_open',
originalName: 'open',
serverId: 'browser-server',
serverName: 'Browser',
inputSchema: {
type: 'object',
required: ['url'],
properties: {
url: { type: 'string', description: '要打开的 URL' },
},
},
},
],
});
expect(snapshot.tools[0].inputSchema).toEqual({
type: 'object',
required: ['url'],
properties: {
url: { type: 'string', description: '要打开的 URL' },
},
});
});
it('returns actionable warnings when no discovered mcp tool matches the query', () => {
const snapshot = buildMCPToolSchemaSnapshot({
keyword: 'github',
mcpTools: [
{
alias: 'browser_open',
originalName: 'open',
serverId: 'browser-server',
serverName: 'Browser',
},
],
});
expect(snapshot.matchedToolCount).toBe(0);
expect(snapshot.warnings).toContain('没有找到匹配的 MCP 工具。');
expect(snapshot.nextActions[0]).toContain('inspect_mcp_setup');
});
});

View File

@@ -0,0 +1,296 @@
import type { AIMCPToolDescriptor } from '../../types';
const DEFAULT_TOOL_LIMIT = 8;
const MAX_TOOL_LIMIT = 30;
const MAX_PARAMETER_HINTS = 40;
const MAX_ENUM_VALUES = 12;
const MAX_SCHEMA_DEPTH = 2;
interface JSONSchemaRecord {
[key: string]: any;
}
const isRecord = (value: unknown): value is JSONSchemaRecord =>
Boolean(value) && typeof value === 'object' && !Array.isArray(value);
const normalizeSearchText = (value: unknown): string =>
String(value || '').trim().toLowerCase();
const readSchemaType = (schema: JSONSchemaRecord): string => {
const rawType = schema.type;
if (Array.isArray(rawType)) {
return rawType.map((item) => String(item)).filter(Boolean).join('|') || 'unknown';
}
if (typeof rawType === 'string' && rawType.trim()) {
return rawType.trim();
}
if (Array.isArray(schema.enum)) {
return 'enum';
}
if (Array.isArray(schema.anyOf)) {
return 'anyOf';
}
if (Array.isArray(schema.oneOf)) {
return 'oneOf';
}
if (isRecord(schema.properties)) {
return 'object';
}
if (isRecord(schema.items)) {
return 'array';
}
return 'unknown';
};
const readRequiredSet = (schema: JSONSchemaRecord): Set<string> =>
new Set(
Array.isArray(schema.required)
? schema.required.map((item) => String(item)).filter(Boolean)
: [],
);
const readDescription = (schema: JSONSchemaRecord): string => {
const description = String(schema.description || '').trim();
if (description) {
return description;
}
return String(schema.title || '').trim();
};
const readEnumValues = (schema: JSONSchemaRecord): string[] =>
Array.isArray(schema.enum)
? schema.enum.slice(0, MAX_ENUM_VALUES).map((item) => String(item))
: [];
const readDefaultValue = (schema: JSONSchemaRecord): string => {
if (!Object.prototype.hasOwnProperty.call(schema, 'default')) {
return '';
}
const value = schema.default;
if (value === null) {
return 'null';
}
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
return String(value);
}
try {
return JSON.stringify(value);
} catch {
return String(value);
}
};
export interface MCPToolSchemaParameterHint {
path: string;
name: string;
required: boolean;
type: string;
description: string;
enumValues: string[];
enumValuesTruncated: boolean;
defaultValue: string;
nestedPropertyCount: number;
arrayItemType: string;
}
const buildParameterHints = (
schema: JSONSchemaRecord,
pathPrefix = '',
depth = 0,
): MCPToolSchemaParameterHint[] => {
if (!isRecord(schema.properties)) {
return [];
}
const requiredSet = readRequiredSet(schema);
const hints: MCPToolSchemaParameterHint[] = [];
Object.entries(schema.properties).forEach(([name, rawChildSchema]) => {
if (hints.length >= MAX_PARAMETER_HINTS) {
return;
}
const childSchema = isRecord(rawChildSchema) ? rawChildSchema : {};
const path = pathPrefix ? `${pathPrefix}.${name}` : name;
const childProperties = isRecord(childSchema.properties) ? childSchema.properties : {};
const itemSchema = isRecord(childSchema.items) ? childSchema.items : {};
const enumValues = readEnumValues(childSchema);
hints.push({
path,
name,
required: requiredSet.has(name),
type: readSchemaType(childSchema),
description: readDescription(childSchema),
enumValues,
enumValuesTruncated: Array.isArray(childSchema.enum) && childSchema.enum.length > MAX_ENUM_VALUES,
defaultValue: readDefaultValue(childSchema),
nestedPropertyCount: Object.keys(childProperties).length,
arrayItemType: isRecord(childSchema.items) ? readSchemaType(itemSchema) : '',
});
if (depth >= MAX_SCHEMA_DEPTH || hints.length >= MAX_PARAMETER_HINTS) {
return;
}
if (Object.keys(childProperties).length > 0) {
hints.push(...buildParameterHints(childSchema, path, depth + 1).slice(0, MAX_PARAMETER_HINTS - hints.length));
return;
}
if (isRecord(itemSchema.properties)) {
hints.push(...buildParameterHints(itemSchema, `${path}[]`, depth + 1).slice(0, MAX_PARAMETER_HINTS - hints.length));
}
});
return hints.slice(0, MAX_PARAMETER_HINTS);
};
const matchesKeyword = (tool: AIMCPToolDescriptor, keyword: string): boolean => {
if (!keyword) {
return true;
}
return [
tool.alias,
tool.originalName,
tool.title,
tool.description,
tool.serverId,
tool.serverName,
].some((item) => normalizeSearchText(item).includes(keyword));
};
const buildUsageHints = (params: {
tool: AIMCPToolDescriptor;
hasInputSchema: boolean;
parameterHints: MCPToolSchemaParameterHint[];
}) => {
const { tool, hasInputSchema, parameterHints } = params;
const hints: string[] = [];
const requiredTopLevel = parameterHints
.filter((item) => item.required && !item.path.includes('.'))
.map((item) => item.path);
const enumHint = parameterHints.find((item) => item.enumValues.length > 0);
const nestedHint = parameterHints.find((item) => item.nestedPropertyCount > 0 || item.path.includes('.'));
if (!hasInputSchema) {
hints.push('这个 MCP 工具没有声明 inputSchema调用前优先查看服务 README 或先用空对象试探。');
}
if (requiredTopLevel.length > 0) {
hints.push(`调用 ${tool.alias} 前必须提供:${requiredTopLevel.join(', ')}`);
}
if (enumHint) {
hints.push(`${enumHint.path} 只能从枚举值中选择:${enumHint.enumValues.join(' / ')}`);
}
if (nestedHint) {
hints.push('嵌套对象和数组参数必须按 JSON 结构传入,不要把对象整体写成字符串。');
}
if (parameterHints.length > 0) {
hints.push('调用前只传 schema 中声明的字段;不确定字段含义时先向用户确认,而不是猜测。');
}
return hints;
};
export const buildMCPToolSchemaSnapshot = (params: {
mcpTools?: AIMCPToolDescriptor[];
alias?: string;
serverId?: string;
keyword?: string;
includeSchema?: boolean;
limit?: number;
}) => {
const {
mcpTools = [],
alias = '',
serverId = '',
keyword = '',
includeSchema = false,
} = params;
const normalizedAlias = normalizeSearchText(alias);
const normalizedServerId = normalizeSearchText(serverId);
const normalizedKeyword = normalizeSearchText(keyword);
const limit = Math.max(1, Math.min(MAX_TOOL_LIMIT, Number(params.limit) || DEFAULT_TOOL_LIMIT));
const allTools = Array.isArray(mcpTools) ? mcpTools : [];
const matchedTools = allTools
.filter((tool) => {
if (normalizedAlias) {
const aliasText = normalizeSearchText(tool.alias);
const originalText = normalizeSearchText(tool.originalName);
if (aliasText !== normalizedAlias && originalText !== normalizedAlias) {
return false;
}
}
if (normalizedServerId && normalizeSearchText(tool.serverId) !== normalizedServerId) {
return false;
}
return matchesKeyword(tool, normalizedKeyword);
})
.sort((left, right) => {
if (normalizedAlias) {
const leftExact = normalizeSearchText(left.alias) === normalizedAlias ? 0 : 1;
const rightExact = normalizeSearchText(right.alias) === normalizedAlias ? 0 : 1;
if (leftExact !== rightExact) {
return leftExact - rightExact;
}
}
return String(left.alias || '').localeCompare(String(right.alias || ''));
});
const tools = matchedTools.slice(0, limit).map((tool) => {
const inputSchema = isRecord(tool.inputSchema) ? tool.inputSchema : {};
const parameterHints = buildParameterHints(inputSchema);
const topLevelParameters = parameterHints.filter((item) => !item.path.includes('.'));
const requiredParameters = topLevelParameters
.filter((item) => item.required)
.map((item) => item.path);
const hasInputSchema = Object.keys(inputSchema).length > 0;
return {
alias: tool.alias,
originalName: tool.originalName,
title: tool.title || tool.originalName || tool.alias,
description: tool.description || '',
serverId: tool.serverId,
serverName: tool.serverName,
hasInputSchema,
parameterCount: topLevelParameters.length,
parameterHintCount: parameterHints.length,
parameterHintsTruncated: parameterHints.length >= MAX_PARAMETER_HINTS,
requiredParameterCount: requiredParameters.length,
requiredParameters,
parameters: parameterHints,
usageHints: buildUsageHints({ tool, hasInputSchema, parameterHints }),
inputSchema: includeSchema ? inputSchema : undefined,
};
});
const warnings: string[] = [];
const nextActions: string[] = [];
if (allTools.length === 0) {
warnings.push('当前没有发现任何 MCP 工具,可能还没有配置 MCP 服务,或服务测试/发现失败。');
nextActions.push('先调用 inspect_mcp_setup 查看 MCP 服务是否启用并已发现工具。');
} else if (matchedTools.length === 0) {
warnings.push('没有找到匹配的 MCP 工具。');
nextActions.push('先调用 inspect_mcp_setup 查看当前实际发现到的 MCP 工具 alias再用 alias 精确查询。');
} else if (tools.some((tool) => !tool.hasInputSchema)) {
warnings.push('部分 MCP 工具没有声明 inputSchema参数说明可能不完整。');
nextActions.push('没有 schema 的工具需要回到 MCP 服务 README 或工具返回错误继续确认参数。');
}
return {
query: {
alias: alias || '',
serverId: serverId || '',
keyword: keyword || '',
includeSchema: includeSchema === true,
limit,
},
totalMCPToolCount: allTools.length,
matchedToolCount: matchedTools.length,
returnedToolCount: tools.length,
toolsTruncated: matchedTools.length > tools.length,
tools,
warnings,
nextActions,
message: matchedTools.length > 0
? `已找到 ${matchedTools.length} 个 MCP 工具,返回 ${tools.length} 个参数 schema 摘要`
: allTools.length > 0
? '没有找到匹配的 MCP 工具'
: '当前还没有可用 MCP 工具 schema',
};
};

View File

@@ -14,6 +14,7 @@ describe('aiSlashCommands', () => {
expect(commands.some((command) => command.cmd === '/health')).toBe(true);
expect(commands.some((command) => command.cmd === '/mcp')).toBe(true);
expect(commands.some((command) => command.cmd === '/mcpadd')).toBe(true);
expect(commands.some((command) => command.cmd === '/mcptool')).toBe(true);
expect(commands.some((command) => command.cmd === '/connfail')).toBe(true);
expect(commands.some((command) => command.cmd === '/shortcuts')).toBe(true);
expect(commands.some((command) => command.cmd === '/applog')).toBe(true);
@@ -47,6 +48,12 @@ describe('aiSlashCommands', () => {
expect(filterAISlashCommands('/air').map((command) => command.cmd)).toContain('/airender');
});
it('supports filtering mcp tool schema diagnostics by keyword and command prefix', () => {
expect(filterAISlashCommands('arguments').map((command) => command.cmd)).toContain('/mcptool');
expect(filterAISlashCommands('MCP工具参数').map((command) => command.cmd)).toContain('/mcptool');
expect(filterAISlashCommands('/mcpt').map((command) => command.cmd)).toContain('/mcptool');
});
it('groups commands by configured category order', () => {
const groups = groupAISlashCommands(filterAISlashCommands('/'));

View File

@@ -50,6 +50,7 @@ export const DEFAULT_AI_SLASH_COMMANDS: AISlashCommandDefinition[] = [
{ cmd: '/health', label: '🩺 AI 配置体检', desc: '调用体检探针总览当前 AI 配置', prompt: '请先调用 inspect_ai_setup_health对当前 GoNavi AI 配置做一次完整体检,然后总结 blockers、warnings 和 nextActions。', category: 'diagnose', featured: true, keywords: ['health', '体检', 'ai配置', '探针'] },
{ cmd: '/mcp', label: '🪛 排查 MCP 接入', desc: '检查 MCP 服务和外部客户端状态', prompt: '请先调用 inspect_mcp_setup帮我盘点当前 MCP 服务、工具发现结果,以及 Claude Code / Codex 的接入状态。', category: 'diagnose', featured: true, keywords: ['mcp', 'codex', 'claude', '外部客户端'] },
{ cmd: '/mcpadd', label: '🧭 新增 MCP 指引', desc: '查看 command、args、env 和模板怎么填', prompt: '请先调用 inspect_mcp_authoring_guide再结合 inspect_mcp_setup告诉我新增 GoNavi MCP 服务时 command、args、env、timeout 应该怎么填,以及最接近的模板应该选哪个。', category: 'diagnose', featured: true, keywords: ['mcp新增', 'command', 'args', 'env', '模板'] },
{ cmd: '/mcptool', label: '🧩 MCP 工具参数', desc: '查看 MCP 工具 schema 和 arguments 写法', prompt: '请先调用 inspect_mcp_setup 找到当前已发现的 MCP 工具 alias如果我已经给了工具名或关键词再调用 inspect_mcp_tool_schema 读取对应 inputSchema告诉我必填参数、字段类型、枚举值、嵌套路径以及 arguments JSON 应该怎么写。', category: 'diagnose', keywords: ['mcp工具', 'mcp工具参数', 'schema', 'arguments', '参数', '工具调用', 'inputschema'] },
{ cmd: '/connfail', label: '🧯 连接失败探针', desc: '总结最近连接失败、冷却和验证异常', prompt: '请先调用 inspect_recent_connection_failures帮我总结最近数据库连接失败、连接冷却、验证失败和 SSH 隧道异常的真实日志结论;如果已经有明确地址或类型,再结合 inspect_current_connection 或 inspect_saved_connections 继续缩小范围。', category: 'diagnose', featured: true, keywords: ['连接失败', '冷却', '验证失败', 'ssh', 'mysql'] },
{ cmd: '/shortcuts', label: '⌨️ 快捷键探针', desc: '读取当前 Win/Mac 快捷键配置', prompt: '请先调用 inspect_shortcuts告诉我当前 GoNavi 的快捷键配置,尤其是执行 SQL、切换结果区、打开 AI 面板和 AI 发送消息这些动作在当前平台和另一平台分别怎么按,是否改过默认值。', category: 'diagnose', keywords: ['快捷键', 'shortcuts', '结果区', 'mac', 'windows'] },
{ cmd: '/applog', label: '🪵 应用日志', desc: '回看最近 GoNavi 应用日志', prompt: '请先调用 inspect_app_logs帮我看最近 GoNavi 应用日志里的错误和警告如果我提到连接失败、MCP 拉起失败、启动异常或 gonavi.log就优先结合关键词继续筛。', category: 'diagnose', keywords: ['日志', 'gonavi.log', 'mcp报错', '连接失败', '启动异常'] },

View File

@@ -15,6 +15,7 @@ import { buildAISafetySnapshot } from './aiSafetyInsights';
import { buildMCPAuthoringGuideSnapshot } from './aiMCPAuthoringGuideInsights';
import { buildAISetupHealthSnapshot } from './aiSetupHealthInsights';
import { buildMCPSetupSnapshot } from './aiMCPInsights';
import { buildMCPToolSchemaSnapshot } from './aiMCPToolSchemaInsights';
import type {
AISnapshotInspectionRuntime,
AISnapshotInspectionRuntimeState,
@@ -25,6 +26,7 @@ const BUILTIN_AI_TOOL_NAMES = BUILTIN_AI_TOOL_INFO.map((item) => item.name);
interface ExecuteAIConfigSnapshotToolCallOptions {
toolName: string;
args?: Record<string, any>;
activeContext?: { connectionId: string; dbName: string } | null;
aiContexts?: Record<string, AIContextItem[]>;
connections: SavedConnection[];
@@ -57,6 +59,7 @@ export async function executeAIConfigSnapshotToolCall(
): Promise<SnapshotInspectionResult | null> {
const {
toolName,
args = {},
activeContext = null,
aiContexts = {},
connections,
@@ -168,6 +171,18 @@ export async function executeAIConfigSnapshotToolCall(
content: JSON.stringify(buildMCPAuthoringGuideSnapshot()),
success: true,
};
case 'inspect_mcp_tool_schema':
return {
content: JSON.stringify(buildMCPToolSchemaSnapshot({
mcpTools,
alias: args.alias,
serverId: args.serverId,
keyword: args.keyword,
includeSchema: args.includeSchema === true,
limit: args.limit,
})),
success: true,
};
case 'inspect_ai_guidance':
return {
content: JSON.stringify(buildAIGuidanceSnapshot({
@@ -188,6 +203,7 @@ export async function executeAIConfigSnapshotToolCall(
inspect_ai_chat_readiness: '读取 AI 聊天发送前置状态失败',
inspect_mcp_setup: '读取 MCP 配置状态失败',
inspect_mcp_authoring_guide: '读取 MCP 新增填写指引失败',
inspect_mcp_tool_schema: '读取 MCP 工具参数 schema 失败',
inspect_ai_guidance: '读取当前 AI 提示与技能配置失败',
}[toolName] || '读取 AI 配置探针失败';
return {

View File

@@ -114,6 +114,7 @@ export async function executeSnapshotInspectionToolCall(
const aiConfigResult = await executeAIConfigSnapshotToolCall({
toolName,
args,
activeContext,
aiContexts,
connections,

View File

@@ -68,7 +68,7 @@ describe('buildAISystemContextMessages', () => {
connections: [connections[0]],
tabs: [],
activeTabId: null,
availableToolNames: ['inspect_workspace_tabs', 'inspect_app_health', 'inspect_ai_setup_health', 'inspect_ai_runtime', 'inspect_ai_safety', 'inspect_ai_providers', 'inspect_ai_chat_readiness', 'inspect_mcp_setup', 'inspect_mcp_authoring_guide', 'inspect_ai_guidance', 'inspect_ai_context', 'inspect_current_connection', 'inspect_connection_capabilities', 'inspect_saved_connections', 'inspect_external_sql_directories', 'inspect_external_sql_file', 'inspect_recent_sql_activity', 'inspect_sql_risk', 'inspect_recent_connection_failures', 'inspect_app_logs', 'inspect_ai_last_render_error', 'inspect_ai_message_flow', 'inspect_saved_queries', 'inspect_ai_sessions', 'inspect_sql_snippets', 'inspect_shortcuts', 'get_columns'],
availableToolNames: ['inspect_workspace_tabs', 'inspect_app_health', 'inspect_ai_setup_health', 'inspect_ai_runtime', 'inspect_ai_safety', 'inspect_ai_providers', 'inspect_ai_chat_readiness', 'inspect_mcp_setup', 'inspect_mcp_authoring_guide', 'inspect_mcp_tool_schema', 'inspect_ai_guidance', 'inspect_ai_context', 'inspect_current_connection', 'inspect_connection_capabilities', 'inspect_saved_connections', 'inspect_external_sql_directories', 'inspect_external_sql_file', 'inspect_recent_sql_activity', 'inspect_sql_risk', 'inspect_recent_connection_failures', 'inspect_app_logs', 'inspect_ai_last_render_error', 'inspect_ai_message_flow', 'inspect_saved_queries', 'inspect_ai_sessions', 'inspect_sql_snippets', 'inspect_shortcuts', 'get_columns'],
skills,
userPromptSettings,
});
@@ -83,6 +83,7 @@ describe('buildAISystemContextMessages', () => {
expect(joined).toContain('inspect_ai_chat_readiness 读取真实发送前置状态');
expect(joined).toContain('inspect_mcp_setup 读取真实 MCP 配置');
expect(joined).toContain('inspect_mcp_authoring_guide 读取真实新增指引和模板');
expect(joined).toContain('inspect_mcp_tool_schema 读取真实 inputSchema');
expect(joined).toContain('inspect_ai_guidance 读取真实提示与技能配置');
expect(joined).toContain('inspect_ai_context 读取当前挂载的表结构上下文');
expect(joined).toContain('inspect_current_connection');

View File

@@ -92,6 +92,12 @@ export const appendDatabaseInspectionGuidanceMessages = (
'inspect_mcp_authoring_guide',
'如果用户提到“新增 MCP 不知道 command/args/env/timeout 怎么填”“给我一个 node / uvx / python 模板”“为什么启动命令不能直接填整行”,优先调用 inspect_mcp_authoring_guide 读取真实新增指引和模板,再结合 inspect_mcp_setup 判断当前配置现状,不要凭记忆口述。',
);
appendGuidanceIfToolAvailable(
messages,
availableToolNames,
'inspect_mcp_tool_schema',
'如果用户提到“某个 MCP 工具参数怎么填”“MCP 工具调用报参数错误”“这个 MCP tool 的 arguments JSON 怎么写”,优先调用 inspect_mcp_tool_schema 读取真实 inputSchema如果不知道 alias先调用 inspect_mcp_setup 找到当前发现的工具 alias。',
);
appendGuidanceIfToolAvailable(
messages,
availableToolNames,

View File

@@ -29,6 +29,7 @@ const TOOL_ACTION_LABELS: Record<string, string> = {
inspect_ai_chat_readiness: '读取当前 AI 聊天发送前置状态',
inspect_mcp_setup: '读取当前 MCP 配置状态',
inspect_mcp_authoring_guide: '读取 MCP 新增填写指引',
inspect_mcp_tool_schema: '读取 MCP 工具参数 schema',
inspect_ai_guidance: '读取当前 AI 提示与技能配置',
get_connections: '获取可用连接信息',
get_databases: '扫描数据库列表',

View File

@@ -145,6 +145,32 @@ export const BUILTIN_AI_INSPECTION_TOOL_INFO: AIBuiltinToolInfo[] = [
},
},
},
{
name: "inspect_mcp_tool_schema",
icon: "🧩",
desc: "查看 MCP 工具参数怎么传",
detail:
"按 alias、serverId 或关键词查看当前已发现 MCP 工具的 inputSchema返回必填参数、字段类型、枚举值、嵌套对象路径和调用前提示。适合新增 MCP 成功后,用户或 AI 不知道某个 MCP 工具到底该传哪些参数时先读真实 schema。",
params: "alias?, serverId?, keyword?, includeSchema?(默认 false), limit?(默认 8)",
tool: {
type: "function",
function: {
name: "inspect_mcp_tool_schema",
description:
"读取当前已发现 MCP 工具的参数 schema 摘要,可按 alias、serverId 或关键词过滤,并返回必填字段、类型、枚举值、嵌套参数路径和调用前提示。适用于用户问某个 MCP 工具参数怎么填、AI 准备调用外部 MCP 工具但不确定 arguments JSON 怎么写、或工具调用报参数错误时,先读取真实 inputSchema 再继续。",
parameters: {
type: "object",
properties: {
alias: { type: "string", description: "可选,按 MCP 工具 alias 精确查询,例如 github_create_issue优先通过 inspect_mcp_setup 获取真实 alias" },
serverId: { type: "string", description: "可选,只看某个 MCP serverId 下发现的工具" },
keyword: { type: "string", description: "可选,按工具 alias、原始名称、标题、描述或服务名做关键词筛选" },
includeSchema: { type: "boolean", description: "可选,是否附带完整原始 inputSchema默认 false需要深查复杂嵌套 schema 时再开启" },
limit: { type: "number", description: "可选,最多返回多少个匹配工具,默认 8最大 30" },
},
},
},
},
},
{
name: "inspect_ai_guidance",
icon: "🧠",

View File

@@ -38,6 +38,14 @@ describe('aiToolRegistry', () => {
expect(info?.tool.function.description).toContain('command、args、env、timeout');
});
it('registers the mcp-tool-schema inspector as a builtin tool', () => {
const info = BUILTIN_AI_TOOL_INFO.find((item) => item.name === 'inspect_mcp_tool_schema');
expect(info).toBeTruthy();
expect(info?.desc).toContain('MCP 工具参数');
expect(info?.tool.function.description).toContain('inputSchema');
expect(info?.tool.function.parameters?.properties?.alias?.description).toContain('真实 alias');
});
it('registers the ai-provider inspector as a builtin tool', () => {
const info = BUILTIN_AI_TOOL_INFO.find((item) => item.name === 'inspect_ai_providers');
expect(info).toBeTruthy();
@@ -183,6 +191,7 @@ describe('aiToolRegistry', () => {
expect(tools.some((item) => item.function.name === 'inspect_ai_chat_readiness')).toBe(true);
expect(tools.some((item) => item.function.name === 'inspect_mcp_setup')).toBe(true);
expect(tools.some((item) => item.function.name === 'inspect_mcp_authoring_guide')).toBe(true);
expect(tools.some((item) => item.function.name === 'inspect_mcp_tool_schema')).toBe(true);
expect(tools.some((item) => item.function.name === 'inspect_ai_guidance')).toBe(true);
expect(tools.some((item) => item.function.name === 'inspect_current_connection')).toBe(true);
expect(tools.some((item) => item.function.name === 'inspect_connection_capabilities')).toBe(true);