diff --git a/src/tools/skill-mcp/tools.ts b/src/tools/skill-mcp/tools.ts index c06fa322bf..3d38790d5f 100644 --- a/src/tools/skill-mcp/tools.ts +++ b/src/tools/skill-mcp/tools.ts @@ -21,10 +21,10 @@ function validateOperationParams(args: SkillMcpArgs): OperationType { if (operations.length === 0) { throw new Error( `Missing operation. Exactly one of tool_name, resource_name, or prompt_name must be specified.\n\n` + - `Examples:\n` + - ` skill_mcp(mcp_name="sqlite", tool_name="query", arguments='{"sql": "SELECT * FROM users"}')\n` + - ` skill_mcp(mcp_name="memory", resource_name="memory://notes")\n` + - ` skill_mcp(mcp_name="helper", prompt_name="summarize", arguments='{"text": "..."}')` + `Examples:\n` + + ` skill_mcp(mcp_name="sqlite", tool_name="query", arguments='{"sql": "SELECT * FROM users"}')\n` + + ` skill_mcp(mcp_name="memory", resource_name="memory://notes")\n` + + ` skill_mcp(mcp_name="helper", prompt_name="summarize", arguments='{"text": "..."}')`, ) } @@ -33,12 +33,14 @@ function validateOperationParams(args: SkillMcpArgs): OperationType { args.tool_name && `tool_name="${args.tool_name}"`, args.resource_name && `resource_name="${args.resource_name}"`, args.prompt_name && `prompt_name="${args.prompt_name}"`, - ].filter(Boolean).join(", ") + ] + .filter(Boolean) + .join(", ") throw new Error( `Multiple operations specified. Exactly one of tool_name, resource_name, or prompt_name must be provided.\n\n` + - `Received: ${provided}\n\n` + - `Use separate calls for each operation.` + `Received: ${provided}\n\n` + + `Use separate calls for each operation.`, ) } @@ -47,7 +49,7 @@ function validateOperationParams(args: SkillMcpArgs): OperationType { function findMcpServer( mcpName: string, - skills: LoadedSkill[] + skills: LoadedSkill[], ): { skill: LoadedSkill; config: NonNullable[string] } | null { for (const skill of skills) { if (skill.mcpConfig && mcpName in skill.mcpConfig) { @@ -75,7 +77,10 @@ function parseArguments(argsJson: string | Record | undefined): return argsJson } try { - const parsed = JSON.parse(argsJson) + // Strip outer single quotes if present (common in LLM output) + const jsonStr = argsJson.startsWith("'") && argsJson.endsWith("'") ? argsJson.slice(1, -1) : argsJson + + const parsed = JSON.parse(jsonStr) if (typeof parsed !== "object" || parsed === null) { throw new Error("Arguments must be a JSON object") } @@ -84,8 +89,8 @@ function parseArguments(argsJson: string | Record | undefined): const errorMessage = error instanceof Error ? error.message : String(error) throw new Error( `Invalid arguments JSON: ${errorMessage}\n\n` + - `Expected a valid JSON object, e.g.: '{"key": "value"}'\n` + - `Received: ${argsJson}` + `Expected a valid JSON object, e.g.: '{"key": "value"}'\n` + + `Received: ${argsJson}`, ) } } @@ -95,10 +100,8 @@ export function applyGrepFilter(output: string, pattern: string | undefined): st try { const regex = new RegExp(pattern, "i") const lines = output.split("\n") - const filtered = lines.filter(line => regex.test(line)) - return filtered.length > 0 - ? filtered.join("\n") - : `[grep] No lines matched pattern: ${pattern}` + const filtered = lines.filter((line) => regex.test(line)) + return filtered.length > 0 ? filtered.join("\n") : `[grep] No lines matched pattern: ${pattern}` } catch { return output } @@ -114,8 +117,14 @@ export function createSkillMcpTool(options: SkillMcpToolOptions): ToolDefinition tool_name: tool.schema.string().optional().describe("MCP tool to call"), resource_name: tool.schema.string().optional().describe("MCP resource URI to read"), prompt_name: tool.schema.string().optional().describe("MCP prompt to get"), - arguments: tool.schema.string().optional().describe("JSON string of arguments"), - grep: tool.schema.string().optional().describe("Regex pattern to filter output lines (only matching lines returned)"), + arguments: tool.schema + .union([tool.schema.string(), tool.schema.record(tool.schema.string(), tool.schema.unknown())]) + .optional() + .describe("JSON string or object of arguments"), + grep: tool.schema + .string() + .optional() + .describe("Regex pattern to filter output lines (only matching lines returned)"), }, async execute(args: SkillMcpArgs) { const operation = validateOperationParams(args) @@ -125,9 +134,10 @@ export function createSkillMcpTool(options: SkillMcpToolOptions): ToolDefinition if (!found) { throw new Error( `MCP server "${args.mcp_name}" not found.\n\n` + - `Available MCP servers in loaded skills:\n` + - formatAvailableMcps(skills) + `\n\n` + - `Hint: Load the skill first using the 'skill' tool, then call skill_mcp.` + `Available MCP servers in loaded skills:\n` + + formatAvailableMcps(skills) + + `\n\n` + + `Hint: Load the skill first using the 'skill' tool, then call skill_mcp.`, ) }