Skip to content

Commit bd05f5b

Browse files
committed
feat(skill_mcp): add dynamic truncation and grep filtering
- Add skill_mcp and webfetch to TRUNCATABLE_TOOLS list - Add grep parameter for regex filtering of output lines - Prevents token overflow from large MCP responses 🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
1 parent a82575b commit bd05f5b

File tree

4 files changed

+92
-4
lines changed

4 files changed

+92
-4
lines changed

src/hooks/tool-output-truncator.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ const TRUNCATABLE_TOOLS = [
1616
"ast_grep_search",
1717
"interactive_bash",
1818
"Interactive_bash",
19+
"skill_mcp",
20+
"webfetch",
21+
"WebFetch",
1922
]
2023

2124
interface ToolOutputTruncatorOptions {

src/tools/skill-mcp/tools.test.ts

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, it, expect, beforeEach, mock } from "bun:test"
2-
import { createSkillMcpTool } from "./tools"
2+
import { createSkillMcpTool, applyGrepFilter } from "./tools"
33
import { SkillMcpManager } from "../../features/skill-mcp-manager"
44
import type { LoadedSkill } from "../../features/opencode-skill-loader/types"
55

@@ -147,5 +147,69 @@ describe("skill_mcp tool", () => {
147147
expect(tool.description.length).toBeLessThan(200)
148148
expect(tool.description).toContain("mcp_name")
149149
})
150+
151+
it("includes grep parameter in schema", () => {
152+
// #given / #when
153+
const tool = createSkillMcpTool({
154+
manager,
155+
getLoadedSkills: () => [],
156+
getSessionID: () => "session",
157+
})
158+
159+
// #then
160+
expect(tool.description).toBeDefined()
161+
})
162+
})
163+
})
164+
165+
describe("applyGrepFilter", () => {
166+
it("filters lines matching pattern", () => {
167+
// #given
168+
const output = `line1: hello world
169+
line2: foo bar
170+
line3: hello again
171+
line4: baz qux`
172+
173+
// #when
174+
const result = applyGrepFilter(output, "hello")
175+
176+
// #then
177+
expect(result).toContain("line1: hello world")
178+
expect(result).toContain("line3: hello again")
179+
expect(result).not.toContain("foo bar")
180+
expect(result).not.toContain("baz qux")
181+
})
182+
183+
it("returns original output when pattern is undefined", () => {
184+
// #given
185+
const output = "some output"
186+
187+
// #when
188+
const result = applyGrepFilter(output, undefined)
189+
190+
// #then
191+
expect(result).toBe(output)
192+
})
193+
194+
it("returns message when no lines match", () => {
195+
// #given
196+
const output = "line1\nline2\nline3"
197+
198+
// #when
199+
const result = applyGrepFilter(output, "xyz")
200+
201+
// #then
202+
expect(result).toContain("[grep] No lines matched pattern")
203+
})
204+
205+
it("handles invalid regex gracefully", () => {
206+
// #given
207+
const output = "some output"
208+
209+
// #when
210+
const result = applyGrepFilter(output, "[invalid")
211+
212+
// #then
213+
expect(result).toBe(output)
150214
})
151215
})

src/tools/skill-mcp/tools.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,20 @@ function parseArguments(argsJson: string | undefined): Record<string, unknown> {
8787
}
8888
}
8989

90+
export function applyGrepFilter(output: string, pattern: string | undefined): string {
91+
if (!pattern) return output
92+
try {
93+
const regex = new RegExp(pattern, "i")
94+
const lines = output.split("\n")
95+
const filtered = lines.filter(line => regex.test(line))
96+
return filtered.length > 0
97+
? filtered.join("\n")
98+
: `[grep] No lines matched pattern: ${pattern}`
99+
} catch {
100+
return output
101+
}
102+
}
103+
90104
export function createSkillMcpTool(options: SkillMcpToolOptions): ToolDefinition {
91105
const { manager, getLoadedSkills, getSessionID } = options
92106

@@ -98,6 +112,7 @@ export function createSkillMcpTool(options: SkillMcpToolOptions): ToolDefinition
98112
resource_name: tool.schema.string().optional().describe("MCP resource URI to read"),
99113
prompt_name: tool.schema.string().optional().describe("MCP prompt to get"),
100114
arguments: tool.schema.string().optional().describe("JSON string of arguments"),
115+
grep: tool.schema.string().optional().describe("Regex pattern to filter output lines (only matching lines returned)"),
101116
},
102117
async execute(args: SkillMcpArgs) {
103118
const operation = validateOperationParams(args)
@@ -126,24 +141,29 @@ export function createSkillMcpTool(options: SkillMcpToolOptions): ToolDefinition
126141

127142
const parsedArgs = parseArguments(args.arguments)
128143

144+
let output: string
129145
switch (operation.type) {
130146
case "tool": {
131147
const result = await manager.callTool(info, context, operation.name, parsedArgs)
132-
return JSON.stringify(result, null, 2)
148+
output = JSON.stringify(result, null, 2)
149+
break
133150
}
134151
case "resource": {
135152
const result = await manager.readResource(info, context, operation.name)
136-
return JSON.stringify(result, null, 2)
153+
output = JSON.stringify(result, null, 2)
154+
break
137155
}
138156
case "prompt": {
139157
const stringArgs: Record<string, string> = {}
140158
for (const [key, value] of Object.entries(parsedArgs)) {
141159
stringArgs[key] = String(value)
142160
}
143161
const result = await manager.getPrompt(info, context, operation.name, stringArgs)
144-
return JSON.stringify(result, null, 2)
162+
output = JSON.stringify(result, null, 2)
163+
break
145164
}
146165
}
166+
return applyGrepFilter(output, args.grep)
147167
},
148168
})
149169
}

src/tools/skill-mcp/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ export interface SkillMcpArgs {
44
resource_name?: string
55
prompt_name?: string
66
arguments?: string
7+
grep?: string
78
}

0 commit comments

Comments
 (0)