import { afterEach, describe, expect, it, vi } from "vitest" ;
import type { AnyAgentTool } from "../agents/tools/common.js" ;
import {
initializeGlobalHookRunner,
resetGlobalHookRunner,
} from "../plugins/hook-runner-global.js" ;
import { createMockPluginRegistry } from "../plugins/hooks.test-helpers.js" ;
import { createPluginToolsMcpHandlers } from "./plugin-tools-handlers.js" ;
const callGatewayTool = vi.hoisted(() => vi.fn());
vi.mock("../agents/tools/gateway.js" , () => ({
callGatewayTool,
}));
afterEach(() => {
vi.restoreAllMocks();
callGatewayTool.mockReset();
resetGlobalHookRunner();
});
describe("plugin tools MCP server" , () => {
it("lists registered plugin tools and serializes non-array tool content" , async () => {
const execute = vi.fn().mockResolvedValue({
content: "Stored." ,
});
const tool = {
name: "memory_recall" ,
description: "Recall stored memory" ,
parameters: {
type: "object" ,
properties: {
query: { type: "string" },
},
required: ["query" ],
},
execute,
} as unknown as AnyAgentTool;
const handlers = createPluginToolsMcpHandlers([tool]);
const listed = await handlers.listTools();
expect(listed.tools).toEqual([
expect.objectContaining({
name: "memory_recall" ,
description: "Recall stored memory" ,
inputSchema: expect.objectContaining({
type: "object" ,
required: ["query" ],
}),
}),
]);
const result = await handlers.callTool({
name: "memory_recall" ,
arguments: { query: "remember this" },
});
expect(execute).toHaveBeenCalledWith(
expect.stringMatching(/^mcp-\d+$/),
{
query: "remember this" ,
},
undefined,
undefined,
);
expect(result.content).toEqual([{ type: "text" , text: "Stored." }]);
});
it("returns MCP errors for unknown tools and thrown tool errors" , async () => {
const failingTool = {
name: "memory_forget" ,
description: "Forget memory" ,
parameters: { type: "object" , properties: {} },
execute: vi.fn().mockRejectedValue(new Error("boom" )),
} as unknown as AnyAgentTool;
const handlers = createPluginToolsMcpHandlers([failingTool]);
const unknown = await handlers.callTool({
name: "missing_tool" ,
arguments: {},
});
expect(unknown.isError).toBe(true );
expect(unknown.content).toEqual([{ type: "text" , text: "Unknown tool: missing_tool" }]);
const failed = await handlers.callTool({
name: "memory_forget" ,
arguments: {},
});
expect(failed.isError).toBe(true );
expect(failed.content).toEqual([{ type: "text" , text: "Tool error: boom" }]);
});
it("blocks tool execution when before_tool_call requires approval on the MCP bridge" , async () => {
let hookCalls = 0 ;
const execute = vi.fn().mockResolvedValue({
content: "Stored." ,
});
initializeGlobalHookRunner(
createMockPluginRegistry([
{
hookName: "before_tool_call" ,
handler: async () => {
hookCalls += 1 ;
return {
requireApproval: {
pluginId: "test-plugin" ,
title: "Approval required" ,
description: "Approval required" ,
},
};
},
},
]),
);
callGatewayTool.mockRejectedValueOnce(new Error("gateway unavailable" ));
const tool = {
name: "memory_store" ,
description: "Store memory" ,
parameters: { type: "object" , properties: {} },
execute,
} as unknown as AnyAgentTool;
const handlers = createPluginToolsMcpHandlers([tool]);
const result = await handlers.callTool({
name: "memory_store" ,
arguments: { text: "remember this" },
});
expect(hookCalls).toBe(1 );
expect(execute).not.toHaveBeenCalled();
expect(result.isError).toBe(true );
expect(result.content).toEqual([
{ type: "text" , text: "Tool error: Plugin approval required (gateway unavailable)" },
]);
});
});
Messung V0.5 in Prozent C=100 H=100 G=100
¤ Dauer der Verarbeitung: 0.10 Sekunden
(vorverarbeitet am 2026-06-09)
¤
*© Formatika GbR, Deutschland