Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 30 additions & 20 deletions src/tools/skill-mcp/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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": "..."}')`,
)
}

Expand All @@ -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.`,
)
}

Expand All @@ -47,7 +49,7 @@ function validateOperationParams(args: SkillMcpArgs): OperationType {

function findMcpServer(
mcpName: string,
skills: LoadedSkill[]
skills: LoadedSkill[],
): { skill: LoadedSkill; config: NonNullable<LoadedSkill["mcpConfig"]>[string] } | null {
for (const skill of skills) {
if (skill.mcpConfig && mcpName in skill.mcpConfig) {
Expand Down Expand Up @@ -75,7 +77,10 @@ function parseArguments(argsJson: string | Record<string, unknown> | 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")
}
Expand All @@ -84,8 +89,8 @@ function parseArguments(argsJson: string | Record<string, unknown> | 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}`,
)
}
}
Expand All @@ -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
}
Expand All @@ -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)
Expand All @@ -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.`,
)
}

Expand Down