test(core): cover mcp instruction producer (#37303)

This commit is contained in:
Kit Langton
2026-07-16 10:40:38 -04:00
committed by GitHub
parent fc37ae4452
commit eec6cd5234
+150
View File
@@ -0,0 +1,150 @@
import { describe, expect } from "bun:test"
import { Effect, Layer } from "effect"
import { AgentV2 } from "@opencode-ai/core/agent"
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
import { MCP } from "@opencode-ai/core/mcp/index"
import { McpInstructions } from "@opencode-ai/core/mcp/instructions"
import { PermissionV2 } from "@opencode-ai/core/permission"
import { McpTool } from "@opencode-ai/core/tool/mcp"
import { it } from "./lib/effect"
import { readInitial, readUpdate } from "./lib/instructions"
const build = AgentV2.ID.make("build")
const selection = (permissions: PermissionV2.Ruleset = []) => {
const info = AgentV2.Info.make({ ...AgentV2.Info.empty(build), permissions })
return { id: info.id, info }
}
const instructions = (server: string, text: string) =>
new MCP.ServerInstructions({ server: MCP.ServerName.make(server), instructions: text })
const tool = (server: string, name = "search") => new MCP.Tool({ server: MCP.ServerName.make(server), name })
const layer = (catalog: () => MCP.ServerInstructions[], tools: () => MCP.Tool[]) =>
AppNodeBuilder.build(McpInstructions.node, [
[
MCP.node,
Layer.mock(MCP.Service, {
instructions: () => Effect.succeed(catalog()),
tools: () => Effect.succeed(tools()),
}),
],
])
describe("McpInstructions", () => {
it.effect("renders instructions for servers with at least one permitted tool", () =>
Effect.gen(function* () {
const service = yield* McpInstructions.Service
const generation = yield* service
.load(
selection([
{ action: McpTool.name("alpha", "restricted"), resource: "*", effect: "deny" },
{ action: McpTool.name("hidden", "search"), resource: "*", effect: "deny" },
]),
)
.pipe(Effect.flatMap(readInitial))
expect(generation.text).toBe(
[
"<mcp_instructions>",
' <server name="alpha">',
' Use tools from this server through `execute` under `tools["alpha"]`.',
" Alpha line one",
" Alpha line two",
" </server>",
' <server name="beta">',
' Use tools from this server through `execute` under `tools["beta"]`.',
" Beta instructions",
" </server>",
"</mcp_instructions>",
].join("\n"),
)
}).pipe(
Effect.provide(
layer(
() => [
instructions("beta", "Beta instructions"),
instructions("unused", "No tools"),
instructions("hidden", "Denied tool"),
instructions("alpha", "Alpha line one\nAlpha line two"),
],
() => [tool("alpha"), tool("alpha", "restricted"), tool("beta"), tool("hidden")],
),
),
),
)
it.effect("omits instructions when the agent cannot use execute", () =>
Effect.gen(function* () {
const service = yield* McpInstructions.Service
const generation = yield* service
.load(selection([{ action: "execute", resource: "*", effect: "deny" }]))
.pipe(Effect.flatMap(readInitial))
expect(generation.text).toBe("")
}).pipe(
Effect.provide(
layer(
() => [instructions("alpha", "Alpha instructions")],
() => [tool("alpha")],
),
),
),
)
it.effect("renders additions, changes, and removal", () => {
let catalog = [instructions("alpha", "Alpha instructions")]
const tools = [tool("alpha"), tool("beta")]
return Effect.gen(function* () {
const service = yield* McpInstructions.Service
const initialized = yield* service.load(selection()).pipe(Effect.flatMap(readInitial))
catalog = [instructions("alpha", "Alpha instructions"), instructions("beta", "Beta instructions")]
const added = yield* readUpdate(yield* service.load(selection()), initialized)
expect(added.text).toBe(
[
"New MCP server instructions are available in addition to those previously listed:",
' <server name="beta">',
' Use tools from this server through `execute` under `tools["beta"]`.',
" Beta instructions",
" </server>",
].join("\n"),
)
catalog = [instructions("alpha", "Updated alpha"), instructions("beta", "Beta instructions")]
const changed = yield* readUpdate(yield* service.load(selection()), added)
expect(changed.text).toBe(
[
"The available MCP server instructions have changed. This list supersedes the previous one.",
"<mcp_instructions>",
' <server name="alpha">',
' Use tools from this server through `execute` under `tools["alpha"]`.',
" Updated alpha",
" </server>",
' <server name="beta">',
' Use tools from this server through `execute` under `tools["beta"]`.',
" Beta instructions",
" </server>",
"</mcp_instructions>",
].join("\n"),
)
catalog = [instructions("beta", "Beta instructions")]
const removed = yield* readUpdate(yield* service.load(selection()), changed)
expect(removed.text).toBe("Instructions for the following MCP servers are no longer available: alpha.")
catalog = []
expect((yield* readUpdate(yield* service.load(selection()), removed)).text).toBe(
"MCP server instructions are no longer available.",
)
}).pipe(
Effect.provide(
layer(
() => catalog,
() => tools,
),
),
)
})
})