Reuse OpenCode 2 HTTP/stdio clients with per-session read-only grants, native approval and sanitized owner-scoped MCP status. Narrow the environment inherited by trusted local MCP processes. Validated in an isolated checkout with bun run check and 20 focused tests.
28 lines
1.6 KiB
TypeScript
28 lines
1.6 KiB
TypeScript
import { Plugin, Rpc } from "../../.opencode/node_modules/@opencode/plugin/dist/promise/index.js";
|
|
import { z } from "../../.opencode/node_modules/zod/index.js";
|
|
|
|
// Loaded only by the opt-in isolated smoke script, never by the Agent service.
|
|
export default Plugin.define({
|
|
id: "tjwater.mcp-fixture",
|
|
async setup(ctx) {
|
|
// Materialize the native session toolbox, then stop before any model call.
|
|
await ctx.session.hook("context", () => { throw new Error("MCP fixture stops before model request"); });
|
|
const evaluated: string[] = [];
|
|
await ctx.permission.hook("evaluate", (event) => { evaluated.push(event.action); });
|
|
await ctx.rpc.register(Rpc.define({
|
|
id: "tjwater.mcp-fixture", events: {}, methods: {
|
|
call: { input: z.object({ session_id: z.string(), tool: z.enum(["mcp_stdio_echo", "mcp_http_write"]), value: z.string() }), output: z.object({ content: z.unknown(), evaluated: z.array(z.string()), error: z.string().optional() }) },
|
|
},
|
|
}), {
|
|
call: async (input, context) => {
|
|
const tool = (await ctx.tool.list()).find((tool) => tool.id === input.tool);
|
|
if (!tool) throw new Error("fixture tool missing");
|
|
try {
|
|
const result = await tool.execute({ value: input.value }, { sessionID: input.session_id as never, id: "call-mcp-fixture" as never, messageID: "msg-mcp-fixture" as never, agent: "instruction" as never, signal: context.signal, progress: async () => {} });
|
|
return { content: result.content ?? [], evaluated };
|
|
} catch (error) { return { content: [], evaluated, error: String(error) }; }
|
|
},
|
|
});
|
|
},
|
|
});
|