diff --git a/src/hooks/tool-output-truncator.ts b/src/hooks/tool-output-truncator.ts index 7af9df2..7fb2eb8 100644 --- a/src/hooks/tool-output-truncator.ts +++ b/src/hooks/tool-output-truncator.ts @@ -16,6 +16,9 @@ const TRUNCATABLE_TOOLS = [ "ast_grep_search", "interactive_bash", "Interactive_bash", + "skill_mcp", + "webfetch", + "WebFetch", ] interface ToolOutputTruncatorOptions { diff --git a/src/tools/skill-mcp/tools.test.ts b/src/tools/skill-mcp/tools.test.ts index 1868c99..a8184fe 100644 --- a/src/tools/skill-mcp/tools.test.ts +++ b/src/tools/skill-mcp/tools.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach, mock } from "bun:test" -import { createSkillMcpTool } from "./tools" +import { createSkillMcpTool, applyGrepFilter } from "./tools" import { SkillMcpManager } from "../../features/skill-mcp-manager" import type { LoadedSkill } from "../../features/opencode-skill-loader/types" @@ -147,5 +147,69 @@ describe("skill_mcp tool", () => { expect(tool.description.length).toBeLessThan(200) expect(tool.description).toContain("mcp_name") }) + + it("includes grep parameter in schema", () => { + // #given / #when + const tool = createSkillMcpTool({ + manager, + getLoadedSkills: () => [], + getSessionID: () => "session", + }) + + // #then + expect(tool.description).toBeDefined() + }) + }) +}) + +describe("applyGrepFilter", () => { + it("filters lines matching pattern", () => { + // #given + const output = `line1: hello world +line2: foo bar +line3: hello again +line4: baz qux` + + // #when + const result = applyGrepFilter(output, "hello") + + // #then + expect(result).toContain("line1: hello world") + expect(result).toContain("line3: hello again") + expect(result).not.toContain("foo bar") + expect(result).not.toContain("baz qux") + }) + + it("returns original output when pattern is undefined", () => { + // #given + const output = "some output" + + // #when + const result = applyGrepFilter(output, undefined) + + // #then + expect(result).toBe(output) + }) + + it("returns message when no lines match", () => { + // #given + const output = "line1\nline2\nline3" + + // #when + const result = applyGrepFilter(output, "xyz") + + // #then + expect(result).toContain("[grep] No lines matched pattern") + }) + + it("handles invalid regex gracefully", () => { + // #given + const output = "some output" + + // #when + const result = applyGrepFilter(output, "[invalid") + + // #then + expect(result).toBe(output) }) }) diff --git a/src/tools/skill-mcp/tools.ts b/src/tools/skill-mcp/tools.ts index 7a56f11..b678c99 100644 --- a/src/tools/skill-mcp/tools.ts +++ b/src/tools/skill-mcp/tools.ts @@ -87,6 +87,20 @@ function parseArguments(argsJson: string | undefined): Record { } } +export function applyGrepFilter(output: string, pattern: string | undefined): string { + if (!pattern) return output + 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}` + } catch { + return output + } +} + export function createSkillMcpTool(options: SkillMcpToolOptions): ToolDefinition { const { manager, getLoadedSkills, getSessionID } = options @@ -98,6 +112,7 @@ export function createSkillMcpTool(options: SkillMcpToolOptions): ToolDefinition 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)"), }, async execute(args: SkillMcpArgs) { const operation = validateOperationParams(args) @@ -126,14 +141,17 @@ export function createSkillMcpTool(options: SkillMcpToolOptions): ToolDefinition const parsedArgs = parseArguments(args.arguments) + let output: string switch (operation.type) { case "tool": { const result = await manager.callTool(info, context, operation.name, parsedArgs) - return JSON.stringify(result, null, 2) + output = JSON.stringify(result, null, 2) + break } case "resource": { const result = await manager.readResource(info, context, operation.name) - return JSON.stringify(result, null, 2) + output = JSON.stringify(result, null, 2) + break } case "prompt": { const stringArgs: Record = {} @@ -141,9 +159,11 @@ export function createSkillMcpTool(options: SkillMcpToolOptions): ToolDefinition stringArgs[key] = String(value) } const result = await manager.getPrompt(info, context, operation.name, stringArgs) - return JSON.stringify(result, null, 2) + output = JSON.stringify(result, null, 2) + break } } + return applyGrepFilter(output, args.grep) }, }) } diff --git a/src/tools/skill-mcp/types.ts b/src/tools/skill-mcp/types.ts index 3b56026..7402817 100644 --- a/src/tools/skill-mcp/types.ts +++ b/src/tools/skill-mcp/types.ts @@ -4,4 +4,5 @@ export interface SkillMcpArgs { resource_name?: string prompt_name?: string arguments?: string + grep?: string }