-
Notifications
You must be signed in to change notification settings - Fork 0
test: add fixture-based tests and snapshots for MCP tools #12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d30f7eb
a218266
203f869
c2b64da
af95df7
4c260c2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| import { fileURLToPath } from "node:url"; | ||
|
|
||
| export const saveFixtures = [ | ||
| [ | ||
| "Pro cycling manager 2018", | ||
| fileURLToPath( | ||
| new URL("../fixtures/OfficialRelease-2018.cdb", import.meta.url), | ||
| ), | ||
| ], | ||
| [ | ||
| "Pro cycling manager 2019", | ||
| fileURLToPath( | ||
| new URL("../fixtures/OfficialRelease-2019.cdb", import.meta.url), | ||
| ), | ||
| ], | ||
| [ | ||
| "Pro cycling manager 2021", | ||
| fileURLToPath( | ||
| new URL("../fixtures/OfficialRelease-2021.cdb", import.meta.url), | ||
| ), | ||
| ], | ||
| [ | ||
| "Pro cycling manager 2025", | ||
| fileURLToPath( | ||
| new URL("../fixtures/OfficialRelease-2025.cdb", import.meta.url), | ||
| ), | ||
| ], | ||
| ]; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| import type { MockInstance } from "vitest"; | ||
| import { vi } from "vitest"; | ||
| import { | ||
| McpServer, | ||
| type ToolCallback, | ||
| } from "@modelcontextprotocol/sdk/server/mcp.js"; | ||
|
|
||
| interface RegisteredTool { | ||
| name: string; | ||
| config: any; | ||
|
Check warning on line 10 in test/mocks/mock-mcp-server.ts
|
||
| callback: ToolCallback<any>; | ||
|
Check warning on line 11 in test/mocks/mock-mcp-server.ts
|
||
| } | ||
|
|
||
| export interface MockMcpServer { | ||
| /** The object to pass where an `McpServer` is expected. */ | ||
| server: McpServer; | ||
| /** Vitest spy behind `server.registerTool`, for call assertions. */ | ||
| registerTool: MockInstance<McpServer["registerTool"]>; | ||
| /** Every tool registered so far, in registration order. */ | ||
| tools: RegisteredTool[]; | ||
| /** Look up a registered tool by its name. */ | ||
| getTool(name: string): RegisteredTool | undefined; | ||
| /** Invoke a registered tool's callback by name. */ | ||
| callTool( | ||
| name: string, | ||
| args: Record<string, unknown>, | ||
| ): ReturnType<ToolCallback<any>>; | ||
|
Check warning on line 27 in test/mocks/mock-mcp-server.ts
|
||
| } | ||
|
mpicciolli marked this conversation as resolved.
|
||
|
|
||
| /** | ||
| * Build a fake `McpServer` for unit tests. Only `registerTool` is implemented; | ||
| * it records each registration so tests can inspect the config or invoke the | ||
| * tool callback directly. | ||
| * | ||
| * @example | ||
| * const mcp = createMockMcpServer(); | ||
| * registerGetTableSchema(mcp.server); | ||
| * const result = await mcp.callTool("pcm_get_table_schema", { savePath, tableName }); | ||
| */ | ||
| export function createMockMcpServer(): MockMcpServer { | ||
| const tools: RegisteredTool[] = []; | ||
|
|
||
| // A real `McpServer`, so no cast is needed to satisfy the tool | ||
| // registration functions. Only `registerTool` is exercised; spying on it | ||
| // records each registration and suppresses the real side effects. | ||
| const server = new McpServer({ name: "mock", version: "0.0.0" }); | ||
|
|
||
| const registerTool = vi | ||
| .spyOn(server, "registerTool") | ||
| .mockImplementation((name, config, callback) => { | ||
| tools.push({ | ||
| name, | ||
| config, | ||
| callback: callback as ToolCallback<any>, | ||
|
Check warning on line 54 in test/mocks/mock-mcp-server.ts
|
||
| }); | ||
| return {} as ReturnType<McpServer["registerTool"]>; | ||
| }); | ||
|
mpicciolli marked this conversation as resolved.
|
||
|
|
||
| const getTool = (name: string) => tools.find((t) => t.name === name); | ||
|
|
||
| const callTool = (name: string, args: Record<string, unknown>) => { | ||
| const tool = getTool(name); | ||
| if (!tool) { | ||
| throw new Error( | ||
| `Tool "${name}" was not registered. Registered: ${ | ||
| tools.map((t) => t.name).join(", ") || "(none)" | ||
| }`, | ||
| ); | ||
| } | ||
| // The second arg is the RequestHandlerExtra, unused by these tools. | ||
| return tool.callback(args as never, {} as any); | ||
|
Check warning on line 71 in test/mocks/mock-mcp-server.ts
|
||
| }; | ||
|
|
||
| return { server, registerTool, tools, getTool, callTool }; | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.