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)
This commit is contained in:
@@ -16,6 +16,9 @@ const TRUNCATABLE_TOOLS = [
|
||||
"ast_grep_search",
|
||||
"interactive_bash",
|
||||
"Interactive_bash",
|
||||
"skill_mcp",
|
||||
"webfetch",
|
||||
"WebFetch",
|
||||
]
|
||||
|
||||
interface ToolOutputTruncatorOptions {
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -87,6 +87,20 @@ function parseArguments(argsJson: string | undefined): Record<string, unknown> {
|
||||
}
|
||||
}
|
||||
|
||||
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<string, string> = {}
|
||||
@@ -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)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,4 +4,5 @@ export interface SkillMcpArgs {
|
||||
resource_name?: string
|
||||
prompt_name?: string
|
||||
arguments?: string
|
||||
grep?: string
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user