refactor(opencode): activate MCP search by catalog size
This commit is contained in:
@@ -44,6 +44,22 @@ async function catalog() {
|
||||
}
|
||||
|
||||
describe("MCP tool search", () => {
|
||||
test("uses direct tools below the token threshold", async () => {
|
||||
const tools = {
|
||||
github_create_issue: target("github_create_issue", "Create an issue"),
|
||||
}
|
||||
const schemas = { github_create_issue: { type: "object", properties: {} } } satisfies Record<string, JSONSchema7>
|
||||
expect(McpToolSearch.shouldUse(tools, schemas)).toBe(false)
|
||||
})
|
||||
|
||||
test("uses search above the token threshold", async () => {
|
||||
const tools = {
|
||||
large_catalog: target("large_catalog", "x".repeat(60_000)),
|
||||
}
|
||||
const schemas = { large_catalog: { type: "object", properties: {} } } satisfies Record<string, JSONSchema7>
|
||||
expect(McpToolSearch.shouldUse(tools, schemas)).toBe(true)
|
||||
})
|
||||
|
||||
test("exposes only the three stable control tools", async () => {
|
||||
expect(Object.keys(await catalog())).toEqual(["mcp_search", "mcp_describe", "mcp_call"])
|
||||
})
|
||||
|
||||
@@ -28,7 +28,7 @@ import { Session as SessionNs } from "@/session/session"
|
||||
import { LLMRequestPrep } from "@/session/llm/request"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import { ModelV2 } from "@opencode-ai/core/model"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import { McpToolSearch } from "@/mcp/tool-search"
|
||||
|
||||
type ConfigModel = NonNullable<NonNullable<ConfigV1.Info["provider"]>[string]["models"]>[string]
|
||||
|
||||
@@ -177,73 +177,88 @@ describe("session.llm.hasToolCalls", () => {
|
||||
|
||||
describe("session.llm.resolveTools", () => {
|
||||
test("keeps MCP search controls under a generic deny rule", () => {
|
||||
const previous = Flag.OPENCODE_EXPERIMENTAL_MCP_TOOL_SEARCH
|
||||
Flag.OPENCODE_EXPERIMENTAL_MCP_TOOL_SEARCH = true
|
||||
try {
|
||||
const agent = {
|
||||
name: "test",
|
||||
mode: "primary",
|
||||
options: {},
|
||||
permission: [
|
||||
{ permission: "*", pattern: "*", action: "deny" },
|
||||
{ permission: "github_create_issue", pattern: "*", action: "allow" },
|
||||
],
|
||||
} satisfies Agent.Info
|
||||
const user = {
|
||||
id: MessageID.make("msg_mcp_tool_search"),
|
||||
sessionID: SessionID.make("ses_mcp_tool_search"),
|
||||
role: "user",
|
||||
time: { created: Date.now() },
|
||||
agent: agent.name,
|
||||
model: { providerID: ProviderV2.ID.make("test"), modelID: ModelV2.ID.make("test") },
|
||||
} satisfies SessionV1.User
|
||||
const control = tool({ inputSchema: z.object({}), execute: async () => ({ output: "" }) })
|
||||
expect(
|
||||
Object.keys(
|
||||
LLMRequestPrep.resolveTools({
|
||||
tools: { mcp_search: control, mcp_describe: control, mcp_call: control },
|
||||
agent,
|
||||
user,
|
||||
}),
|
||||
),
|
||||
).toEqual(["mcp_search", "mcp_describe", "mcp_call"])
|
||||
} finally {
|
||||
Flag.OPENCODE_EXPERIMENTAL_MCP_TOOL_SEARCH = previous
|
||||
}
|
||||
})
|
||||
|
||||
test("honors explicit MCP search control denies", () => {
|
||||
const previous = Flag.OPENCODE_EXPERIMENTAL_MCP_TOOL_SEARCH
|
||||
Flag.OPENCODE_EXPERIMENTAL_MCP_TOOL_SEARCH = true
|
||||
try {
|
||||
const agent = {
|
||||
name: "test",
|
||||
mode: "primary",
|
||||
options: {},
|
||||
permission: [
|
||||
{ permission: "*", pattern: "*", action: "deny" },
|
||||
{ permission: "mcp_*", pattern: "*", action: "deny" },
|
||||
],
|
||||
} satisfies Agent.Info
|
||||
const user = {
|
||||
id: MessageID.make("msg_mcp_tool_search_deny"),
|
||||
sessionID: SessionID.make("ses_mcp_tool_search_deny"),
|
||||
role: "user",
|
||||
time: { created: Date.now() },
|
||||
agent: agent.name,
|
||||
model: { providerID: ProviderV2.ID.make("test"), modelID: ModelV2.ID.make("test") },
|
||||
} satisfies SessionV1.User
|
||||
const control = tool({ inputSchema: z.object({}), execute: async () => ({ output: "" }) })
|
||||
expect(
|
||||
const controls = McpToolSearch.create({
|
||||
tools: { target: tool({ inputSchema: z.object({}), execute: async () => ({ output: "" }) }) },
|
||||
schemas: { target: { type: "object", properties: {} } },
|
||||
transformSchema: (schema) => schema,
|
||||
})
|
||||
const agent = {
|
||||
name: "test",
|
||||
mode: "primary",
|
||||
options: {},
|
||||
permission: [
|
||||
{ permission: "*", pattern: "*", action: "deny" },
|
||||
{ permission: "github_create_issue", pattern: "*", action: "allow" },
|
||||
],
|
||||
} satisfies Agent.Info
|
||||
const user = {
|
||||
id: MessageID.make("msg_mcp_tool_search"),
|
||||
sessionID: SessionID.make("ses_mcp_tool_search"),
|
||||
role: "user",
|
||||
time: { created: Date.now() },
|
||||
agent: agent.name,
|
||||
model: { providerID: ProviderV2.ID.make("test"), modelID: ModelV2.ID.make("test") },
|
||||
} satisfies SessionV1.User
|
||||
expect(
|
||||
Object.keys(
|
||||
LLMRequestPrep.resolveTools({
|
||||
tools: { mcp_search: control, mcp_describe: control, mcp_call: control },
|
||||
tools: controls,
|
||||
agent,
|
||||
user,
|
||||
}),
|
||||
).toEqual({})
|
||||
} finally {
|
||||
Flag.OPENCODE_EXPERIMENTAL_MCP_TOOL_SEARCH = previous
|
||||
}
|
||||
),
|
||||
).toEqual(["mcp_search", "mcp_describe", "mcp_call"])
|
||||
})
|
||||
|
||||
test("honors explicit MCP search control denies", () => {
|
||||
const controls = McpToolSearch.create({
|
||||
tools: { target: tool({ inputSchema: z.object({}), execute: async () => ({ output: "" }) }) },
|
||||
schemas: { target: { type: "object", properties: {} } },
|
||||
transformSchema: (schema) => schema,
|
||||
})
|
||||
const agent = {
|
||||
name: "test",
|
||||
mode: "primary",
|
||||
options: {},
|
||||
permission: [
|
||||
{ permission: "*", pattern: "*", action: "deny" },
|
||||
{ permission: "mcp_*", pattern: "*", action: "deny" },
|
||||
],
|
||||
} satisfies Agent.Info
|
||||
const user = {
|
||||
id: MessageID.make("msg_mcp_tool_search_deny"),
|
||||
sessionID: SessionID.make("ses_mcp_tool_search_deny"),
|
||||
role: "user",
|
||||
time: { created: Date.now() },
|
||||
agent: agent.name,
|
||||
model: { providerID: ProviderV2.ID.make("test"), modelID: ModelV2.ID.make("test") },
|
||||
} satisfies SessionV1.User
|
||||
expect(
|
||||
LLMRequestPrep.resolveTools({
|
||||
tools: controls,
|
||||
agent,
|
||||
user,
|
||||
}),
|
||||
).toEqual({})
|
||||
})
|
||||
|
||||
test("does not exempt plugin tools that use MCP control names", () => {
|
||||
const agent = {
|
||||
name: "test",
|
||||
mode: "primary",
|
||||
options: {},
|
||||
permission: [{ permission: "*", pattern: "*", action: "deny" }],
|
||||
} satisfies Agent.Info
|
||||
const user = {
|
||||
id: MessageID.make("msg_mcp_plugin_tool"),
|
||||
sessionID: SessionID.make("ses_mcp_plugin_tool"),
|
||||
role: "user",
|
||||
time: { created: Date.now() },
|
||||
agent: agent.name,
|
||||
model: { providerID: ProviderV2.ID.make("test"), modelID: ModelV2.ID.make("test") },
|
||||
} satisfies SessionV1.User
|
||||
const pluginTool = tool({ inputSchema: z.object({}), execute: async () => ({ output: "" }) })
|
||||
expect(LLMRequestPrep.resolveTools({ tools: { mcp_call: pluginTool }, agent, user })).toEqual({})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user