From 82731d67f8cd8ef174ec7f8bf360b2759e78865f Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Tue, 9 Dec 2025 16:29:55 +0000 Subject: [PATCH 1/7] feat(mocks): add Hono-based MCP mock server for testing Create createMcpApp() function that generates a Hono app implementing the MCP protocol using @hono/mcp's StreamableHTTPTransport. This replaces the Bun.serve() approach with a runtime-agnostic solution that works with MSW via app.fetch(). The mock server supports: - Account-based tool filtering via x-account-id header - Basic authentication using Hono's basicAuth middleware - Pre-defined tool sets for common test scenarios This implementation allows MCP tests to run without starting a real HTTP server, using Hono's request/response testing pattern instead. Related to #170 --- mocks/mcp-server.ts | 230 ++++++++++++++++++++++++++++++++++++++++++++ pnpm-workspace.yaml | 2 + 2 files changed, 232 insertions(+) create mode 100644 mocks/mcp-server.ts diff --git a/mocks/mcp-server.ts b/mocks/mcp-server.ts new file mode 100644 index 00000000..b92d2a73 --- /dev/null +++ b/mocks/mcp-server.ts @@ -0,0 +1,230 @@ +/** + * Mock MCP server for testing using Hono's app.request() method. + * This creates an MCP-compatible handler that can be used with MSW + * without starting a real HTTP server. + */ +import type { Hono as HonoApp } from 'hono'; +import { http, HttpResponse } from 'msw'; +import { StreamableHTTPTransport } from '@hono/mcp'; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { Hono } from 'hono'; +import { basicAuth } from 'hono/basic-auth'; + +export interface McpToolDefinition { + name: string; + description?: string; + inputSchema: { + type: 'object'; + properties?: Record; + required?: string[]; + additionalProperties?: boolean; + }; +} + +export interface MockMcpServerOptions { + /** Tools available per account ID. Use 'default' for tools when no account header is provided. */ + accountTools: Record; +} + +/** + * Creates an MSW handler for mocking MCP protocol requests. + * Uses Hono's app.request() to handle requests without starting a server. + * + * @example + * ```ts + * import { server } from './mocks/node'; + * import { createMcpHandler, defaultMcpTools, accountMcpTools } from './mocks/mcp-server'; + * + * // In your test setup + * server.use( + * createMcpHandler({ + * accountTools: { + * default: defaultMcpTools, + * 'account-1': accountMcpTools.acc1, + * }, + * }) + * ); + * ``` + */ +export function createMcpApp(options: MockMcpServerOptions): HonoApp { + const { accountTools } = options; + + // Create a Hono app that handles MCP protocol + const app = new Hono(); + + // Apply Basic Auth middleware with hardcoded test credentials + app.use( + '/mcp', + basicAuth({ + username: 'test-key', + password: '', + }) + ); + + app.all('/mcp', async (c) => { + // Get account ID from header + const accountId = c.req.header('x-account-id') ?? 'default'; + const tools = accountTools[accountId] ?? accountTools.default ?? []; + + // Create a new MCP server instance per request + const mcp = new McpServer({ name: 'test-mcp-server', version: '1.0.0' }); + const transport = new StreamableHTTPTransport(); + + for (const tool of tools) { + mcp.registerTool( + tool.name, + { + description: tool.description, + // MCP SDK expects Zod-like schema but accepts JSON Schema objects + // biome-ignore lint/suspicious/noExplicitAny: MCP SDK type mismatch + inputSchema: tool.inputSchema as any, + }, + async ({ params }: { params: { arguments?: Record } }) => ({ + content: [], + structuredContent: params.arguments ?? {}, + _meta: undefined, + }) + ); + } + + await mcp.connect(transport); + return transport.handleRequest(c); + }); + + return app; +} + +// Pre-defined tool sets for common test scenarios + +export const defaultMcpTools: McpToolDefinition[] = [ + { + name: 'default_tool_1', + description: 'Default Tool 1', + inputSchema: { + type: 'object', + properties: { fields: { type: 'string' } }, + }, + }, + { + name: 'default_tool_2', + description: 'Default Tool 2', + inputSchema: { + type: 'object', + properties: { id: { type: 'string' } }, + required: ['id'], + }, + }, +]; + +export const accountMcpTools: Record = { + acc1: [ + { + name: 'acc1_tool_1', + description: 'Account 1 Tool 1', + inputSchema: { + type: 'object', + properties: { fields: { type: 'string' } }, + }, + }, + { + name: 'acc1_tool_2', + description: 'Account 1 Tool 2', + inputSchema: { + type: 'object', + properties: { id: { type: 'string' } }, + required: ['id'], + }, + }, + ], + acc2: [ + { + name: 'acc2_tool_1', + description: 'Account 2 Tool 1', + inputSchema: { + type: 'object', + properties: { fields: { type: 'string' } }, + }, + }, + { + name: 'acc2_tool_2', + description: 'Account 2 Tool 2', + inputSchema: { + type: 'object', + properties: { id: { type: 'string' } }, + required: ['id'], + }, + }, + ], + acc3: [ + { + name: 'acc3_tool_1', + description: 'Account 3 Tool 1', + inputSchema: { + type: 'object', + properties: { fields: { type: 'string' } }, + }, + }, + ], + 'test-account': [ + { + name: 'dummy_action', + description: 'Dummy tool', + inputSchema: { + type: 'object', + properties: { + foo: { + type: 'string', + description: 'A string parameter', + }, + }, + required: ['foo'], + additionalProperties: false, + }, + }, + ], +}; + +export const mixedProviderTools = [ + { + name: 'hibob_list_employees', + description: 'HiBob List Employees', + inputSchema: { + type: 'object', + properties: { fields: { type: 'string' } }, + }, + }, + { + name: 'hibob_create_employees', + description: 'HiBob Create Employees', + inputSchema: { + type: 'object', + properties: { name: { type: 'string' } }, + required: ['name'], + }, + }, + { + name: 'bamboohr_list_employees', + description: 'BambooHR List Employees', + inputSchema: { + type: 'object', + properties: { fields: { type: 'string' } }, + }, + }, + { + name: 'bamboohr_get_employee', + description: 'BambooHR Get Employee', + inputSchema: { + type: 'object', + properties: { id: { type: 'string' } }, + required: ['id'], + }, + }, + { + name: 'workday_list_employees', + description: 'Workday List Employees', + inputSchema: { + type: 'object', + properties: { fields: { type: 'string' } }, + }, + }, +] as const satisfies McpToolDefinition[]; diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index ed46dd35..ee7e3ed4 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -2,6 +2,8 @@ packages: - . - examples +catalog: + catalogMode: strict catalogs: From 760465214e770aa01cbcefb2c535dfb143c0eccc Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Tue, 9 Dec 2025 16:30:19 +0000 Subject: [PATCH 2/7] feat(mocks): integrate MCP mock server into MSW handlers Add MCP protocol endpoints to handlers.ts that delegate to the Hono MCP app. This enables MCP tests to use MSW's standard setup without requiring manual server.use() calls in each test. The handlers support both production (api.stackone.com) and test (api.stackone-dev.com) endpoints, with full account filtering and authentication via the Hono app. Related to #170 --- mocks/handlers.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/mocks/handlers.ts b/mocks/handlers.ts index e6a12d0d..ffd7c1f7 100644 --- a/mocks/handlers.ts +++ b/mocks/handlers.ts @@ -1,4 +1,17 @@ import { http, HttpResponse } from 'msw'; +import { accountMcpTools, createMcpApp, defaultMcpTools, mixedProviderTools } from './mcp-server'; + +// Create MCP apps for testing +const defaultMcpApp = createMcpApp({ + accountTools: { + default: defaultMcpTools, + acc1: accountMcpTools.acc1, + acc2: accountMcpTools.acc2, + acc3: accountMcpTools.acc3, + 'test-account': accountMcpTools['test-account'], + mixed: mixedProviderTools, + }, +}); // Helper to extract text content from OpenAI responses API input const extractTextFromInput = (input: unknown): string => { @@ -514,4 +527,13 @@ export const handlers = [ }); }), + // ============================================================ + // MCP Protocol endpoints (delegated to Hono app) + // ============================================================ + http.all('https://api.stackone.com/mcp', async ({ request }) => { + return defaultMcpApp.fetch(request); + }), + http.all('https://api.stackone-dev.com/mcp', async ({ request }) => { + return defaultMcpApp.fetch(request); + }), ]; From dd07c3880ffae4b0878a02aeb9817d0270b8afac Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Tue, 9 Dec 2025 16:30:22 +0000 Subject: [PATCH 3/7] refactor(test): replace Bun.serve with MSW + Hono for MCP tests Refactor MCP integration tests to use MSW with Hono app.fetch() instead of Bun.serve(). This change: - Removes Bun runtime dependency and createMockMcpServer() helper - Removes all describe.skip() - tests now run with standard MSW setup - Eliminates vi.fn() mocks and type assertions - Uses real MCP protocol implementation via @hono/mcp - Simplifies test setup by using shared MSW handlers Tests now verify account filtering, provider filtering, and action filtering using the actual MCP client code paths. Closes #170 --- src/tests/stackone.mcp-fetch.spec.ts | 353 +++++++-------------------- 1 file changed, 89 insertions(+), 264 deletions(-) diff --git a/src/tests/stackone.mcp-fetch.spec.ts b/src/tests/stackone.mcp-fetch.spec.ts index 034acc08..5a31d074 100644 --- a/src/tests/stackone.mcp-fetch.spec.ts +++ b/src/tests/stackone.mcp-fetch.spec.ts @@ -1,116 +1,23 @@ -// TODO: Rewrite these tests to use a runtime-agnostic HTTP server (e.g., node:http or MSW) -// instead of Bun.serve(). Currently skipped because Bun runtime is not available. -// See: https://github.com/StackOneHQ/stackone-ai-node/issues/XXX - -import { StreamableHTTPTransport } from '@hono/mcp'; -import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; -import { Hono } from 'hono'; -import { z } from 'zod'; -import { server as mswServer } from '../../mocks/node'; +/** + * MCP fetch integration tests using MSW + Hono. + * These tests verify the StackOneToolSet's ability to fetch tools via MCP protocol. + */ +import { http } from 'msw'; +import { type McpToolDefinition, createMcpApp } from '../../mocks/mcp-server'; +import { server } from '../../mocks/node'; import { ToolSet } from '../toolsets/base'; import { StackOneToolSet } from '../toolsets/stackone'; -// Bun runtime types for test environment -declare const Bun: { - serve(options: { port: number; fetch: (req: Request) => Response | Promise }): { - url: URL; - stop(): void; - }; -}; - -type MockTool = { - name: string; - description?: string; - shape: Record; // JSON Schema object -}; - -async function createMockMcpServer(accountTools: Record) { - const app = new Hono(); - - app.all('/mcp', async (c) => { - // Get account ID from header - const accountId = c.req.header('x-account-id') || 'default'; - const tools = accountTools[accountId] || []; - - // Create a new MCP server instance per account - const mcp = new McpServer({ name: 'test-mcp', version: '1.0.0' }); - const transport = new StreamableHTTPTransport(); - - for (const tool of tools) { - mcp.registerTool( - tool.name, - { - description: tool.description, - // TODO: Remove type assertion - MCP SDK expects Zod schema but we're using JSON Schema objects in tests - // biome-ignore lint/suspicious/noExplicitAny: MCP SDK type mismatch - using JSON Schema instead of Zod - inputSchema: tool.shape as any, - }, - async ({ params }: { params: { arguments?: Record } }) => ({ - content: [], - structuredContent: params.arguments ?? {}, - _meta: undefined, - }) - ); - } - - await mcp.connect(transport); - return transport.handleRequest(c); - }); - - const server = Bun.serve({ port: 0, fetch: app.fetch }); - const origin = server.url.toString().replace(/\/$/, ''); - - return { - origin, - close: () => server.stop(), - } as const; -} - -describe.skip('ToolSet.fetchTools (MCP + RPC integration)', () => { - const mockTools = [ - { - name: 'dummy_action', - description: 'Dummy tool', - shape: { - type: 'object', - properties: { - foo: { - type: 'string', - description: 'A string parameter', - }, - }, - required: ['foo'], - additionalProperties: false, - }, - }, - ] as const; - - let origin: string; - let closeServer: () => void; - let restoreMsw: (() => void) | undefined; - - beforeAll(async () => { - mswServer.close(); - restoreMsw = () => mswServer.listen({ onUnhandledRequest: 'warn' }); - - const server = await createMockMcpServer({ - default: mockTools, - 'test-account': mockTools, - }); - origin = server.origin; - closeServer = server.close; - }); - - afterAll(() => { - closeServer(); - restoreMsw?.(); - }); - +describe('ToolSet.fetchTools (MCP + RPC integration)', () => { it('creates tools from MCP catalog and wires RPC execution', async () => { class TestToolSet extends ToolSet {} const toolset = new TestToolSet({ - baseUrl: origin, + baseUrl: 'https://api.stackone-dev.com', + authentication: { + type: 'basic', + credentials: { username: 'test-key', password: '' }, + }, headers: { 'x-account-id': 'test-account' }, }); @@ -124,92 +31,19 @@ describe.skip('ToolSet.fetchTools (MCP + RPC integration)', () => { const aiToolDefinition = aiTools.dummy_action; expect(aiToolDefinition).toBeDefined(); expect(aiToolDefinition.description).toBe('Dummy tool'); - // TODO: Remove ts-ignore once AISDKToolDefinition properly types inputSchema.jsonSchema - // @ts-ignore - jsonSchema is available on Schema wrapper from ai sdk + // @ts-expect-error - jsonSchema is available on Schema wrapper from ai sdk expect(aiToolDefinition.inputSchema.jsonSchema.properties).toBeDefined(); expect(aiToolDefinition.execution).toBeUndefined(); const executableTool = (await tool.toAISDK()).dummy_action; - assert(executableTool.execute, 'execute should be defined'); - // TODO: Re-enable execution test when RPC mocking is properly set up - // The execute function now uses internal RpcClient, not an injected stackOneClient + expect(executableTool.execute).toBeDefined(); }); }); -describe.skip('StackOneToolSet account filtering', () => { - const acc1Tools = [ - { - name: 'acc1_tool_1', - description: 'Account 1 Tool 1', - shape: { fields: z.string().optional() }, - }, - { - name: 'acc1_tool_2', - description: 'Account 1 Tool 2', - shape: { id: z.string() }, - }, - ] as const satisfies MockTool[]; - - const acc2Tools = [ - { - name: 'acc2_tool_1', - description: 'Account 2 Tool 1', - shape: { fields: z.string().optional() }, - }, - { - name: 'acc2_tool_2', - description: 'Account 2 Tool 2', - shape: { id: z.string() }, - }, - ] as const satisfies MockTool[]; - - const acc3Tools = [ - { - name: 'acc3_tool_1', - description: 'Account 3 Tool 1', - shape: { fields: z.string().optional() }, - }, - ] as const satisfies MockTool[]; - - const defaultTools = [ - { - name: 'default_tool_1', - description: 'Default Tool 1', - shape: { fields: z.string().optional() }, - }, - { - name: 'default_tool_2', - description: 'Default Tool 2', - shape: { id: z.string() }, - }, - ] as const satisfies MockTool[]; - - let origin: string; - let closeServer: () => void; - let restoreMsw: (() => void) | undefined; - - beforeAll(async () => { - mswServer.close(); - restoreMsw = () => mswServer.listen({ onUnhandledRequest: 'warn' }); - - const server = await createMockMcpServer({ - default: defaultTools, - acc1: acc1Tools, - acc2: acc2Tools, - acc3: acc3Tools, - }); - origin = server.origin; - closeServer = server.close; - }); - - afterAll(() => { - closeServer(); - restoreMsw?.(); - }); - - it('supports setAccounts() for chaining', async () => { +describe('StackOneToolSet account filtering', () => { + it('supports setAccounts() for chaining', () => { const toolset = new StackOneToolSet({ - baseUrl: origin, + baseUrl: 'https://api.stackone-dev.com', apiKey: 'test-key', }); @@ -220,7 +54,7 @@ describe.skip('StackOneToolSet account filtering', () => { it('fetches tools without account filtering when no accountIds provided', async () => { const toolset = new StackOneToolSet({ - baseUrl: origin, + baseUrl: 'https://api.stackone-dev.com', apiKey: 'test-key', }); @@ -235,7 +69,7 @@ describe.skip('StackOneToolSet account filtering', () => { it('uses x-account-id header when fetching tools with accountIds', async () => { const toolset = new StackOneToolSet({ - baseUrl: origin, + baseUrl: 'https://api.stackone-dev.com', apiKey: 'test-key', }); @@ -251,7 +85,7 @@ describe.skip('StackOneToolSet account filtering', () => { it('uses setAccounts when no accountIds provided in fetchTools', async () => { const toolset = new StackOneToolSet({ - baseUrl: origin, + baseUrl: 'https://api.stackone-dev.com', apiKey: 'test-key', }); @@ -274,7 +108,7 @@ describe.skip('StackOneToolSet account filtering', () => { it('overrides setAccounts when accountIds provided in fetchTools', async () => { const toolset = new StackOneToolSet({ - baseUrl: origin, + baseUrl: 'https://api.stackone-dev.com', apiKey: 'test-key', }); @@ -292,59 +126,12 @@ describe.skip('StackOneToolSet account filtering', () => { }); }); -describe.skip('StackOneToolSet provider and action filtering', () => { - const mixedTools = [ - { - name: 'hibob_list_employees', - description: 'HiBob List Employees', - shape: { fields: z.string().optional() }, - }, - { - name: 'hibob_create_employees', - description: 'HiBob Create Employees', - shape: { name: z.string() }, - }, - { - name: 'bamboohr_list_employees', - description: 'BambooHR List Employees', - shape: { fields: z.string().optional() }, - }, - { - name: 'bamboohr_get_employee', - description: 'BambooHR Get Employee', - shape: { id: z.string() }, - }, - { - name: 'workday_list_employees', - description: 'Workday List Employees', - shape: { fields: z.string().optional() }, - }, - ] as const satisfies MockTool[]; - - let origin: string; - let closeServer: () => void; - let restoreMsw: (() => void) | undefined; - - beforeAll(async () => { - mswServer.close(); - restoreMsw = () => mswServer.listen({ onUnhandledRequest: 'warn' }); - - const server = await createMockMcpServer({ - default: mixedTools, - }); - origin = server.origin; - closeServer = server.close; - }); - - afterAll(() => { - closeServer(); - restoreMsw?.(); - }); - +describe('StackOneToolSet provider and action filtering', () => { it('filters tools by providers', async () => { const toolset = new StackOneToolSet({ - baseUrl: origin, + baseUrl: 'https://api.stackone-dev.com', apiKey: 'test-key', + accountId: 'mixed', }); // Filter by providers @@ -363,8 +150,9 @@ describe.skip('StackOneToolSet provider and action filtering', () => { it('filters tools by actions with exact match', async () => { const toolset = new StackOneToolSet({ - baseUrl: origin, + baseUrl: 'https://api.stackone-dev.com', apiKey: 'test-key', + accountId: 'mixed', }); // Filter by exact action names @@ -382,8 +170,9 @@ describe.skip('StackOneToolSet provider and action filtering', () => { it('filters tools by actions with glob pattern', async () => { const toolset = new StackOneToolSet({ - baseUrl: origin, + baseUrl: 'https://api.stackone-dev.com', apiKey: 'test-key', + accountId: 'mixed', }); // Filter by glob pattern @@ -401,39 +190,61 @@ describe.skip('StackOneToolSet provider and action filtering', () => { }); it('combines accountIds and actions filters', async () => { - const acc1Tools = [ + const acc1Tools: McpToolDefinition[] = [ { name: 'hibob_list_employees', description: 'HiBob List Employees', - shape: { fields: z.string().optional() }, + inputSchema: { + type: 'object', + properties: { fields: { type: 'string' } }, + }, }, { name: 'hibob_create_employees', description: 'HiBob Create Employees', - shape: { name: z.string() }, + inputSchema: { + type: 'object', + properties: { name: { type: 'string' } }, + required: ['name'], + }, }, - ] as const satisfies MockTool[]; + ]; - const acc2Tools = [ + const acc2Tools: McpToolDefinition[] = [ { name: 'bamboohr_list_employees', description: 'BambooHR List Employees', - shape: { fields: z.string().optional() }, + inputSchema: { + type: 'object', + properties: { fields: { type: 'string' } }, + }, }, { name: 'bamboohr_get_employee', description: 'BambooHR Get Employee', - shape: { id: z.string() }, + inputSchema: { + type: 'object', + properties: { id: { type: 'string' } }, + required: ['id'], + }, }, - ] as const satisfies MockTool[]; + ]; - const server = await createMockMcpServer({ - acc1: acc1Tools, - acc2: acc2Tools, + // Override the handler for this specific test + const testMcpApp = createMcpApp({ + accountTools: { + acc1: acc1Tools, + acc2: acc2Tools, + }, }); + server.use( + http.all('https://api.stackone-dev.com/mcp', async ({ request }) => { + return testMcpApp.fetch(request); + }) + ); const toolset = new StackOneToolSet({ - baseUrl: server.origin, + baseUrl: 'https://api.stackone-dev.com', apiKey: 'test-key', }); @@ -451,35 +262,51 @@ describe.skip('StackOneToolSet provider and action filtering', () => { expect(toolNames).not.toContain('hibob_create_employees'); expect(toolNames).not.toContain('bamboohr_get_employee'); expect(toolNames).toContain('meta_collect_tool_feedback'); - - server.close(); }); it('combines all filters: accountIds, providers, and actions', async () => { - const acc1Tools = [ + const acc1Tools: McpToolDefinition[] = [ { name: 'hibob_list_employees', description: 'HiBob List Employees', - shape: { fields: z.string().optional() }, + inputSchema: { + type: 'object', + properties: { fields: { type: 'string' } }, + }, }, { name: 'hibob_create_employees', description: 'HiBob Create Employees', - shape: { name: z.string() }, + inputSchema: { + type: 'object', + properties: { name: { type: 'string' } }, + required: ['name'], + }, }, { name: 'workday_list_employees', description: 'Workday List Employees', - shape: { fields: z.string().optional() }, + inputSchema: { + type: 'object', + properties: { fields: { type: 'string' } }, + }, }, - ] as const satisfies MockTool[]; + ]; - const server = await createMockMcpServer({ - acc1: acc1Tools, + // Override the handler for this specific test + const testMcpApp = createMcpApp({ + accountTools: { + acc1: acc1Tools, + }, }); + server.use( + http.all('https://api.stackone-dev.com/mcp', async ({ request }) => { + return testMcpApp.fetch(request); + }) + ); const toolset = new StackOneToolSet({ - baseUrl: server.origin, + baseUrl: 'https://api.stackone-dev.com', apiKey: 'test-key', }); @@ -495,7 +322,5 @@ describe.skip('StackOneToolSet provider and action filtering', () => { const toolNames = tools.toArray().map((t) => t.name); expect(toolNames).toContain('hibob_list_employees'); expect(toolNames).toContain('meta_collect_tool_feedback'); - - server.close(); }); }); From 8715f30575d582dd846947908b7eb0d10d1b3e44 Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Tue, 9 Dec 2025 16:30:33 +0000 Subject: [PATCH 4/7] test(vitest): enable MCP fetch tests Remove src/toolsets/tests/stackone.mcp-fetch.spec.ts from the exclude array now that tests use MSW instead of Bun.serve(). Related to #170 --- vitest.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vitest.config.ts b/vitest.config.ts index 6ec78a24..13599329 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -18,7 +18,7 @@ export default defineConfig({ name: 'root', root: '.', include: ['src/**/*.spec.ts', 'scripts/**/*.spec.ts'], - exclude: ['node_modules', 'dist', 'examples', 'src/toolsets/tests/stackone.mcp-fetch.spec.ts'], + exclude: ['node_modules', 'dist', 'examples'], setupFiles: ['./vitest.setup.ts'], typecheck: { enabled: true, From 59ea22322cbdfc9223b83dfbaee6f9566f45c193 Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Tue, 9 Dec 2025 16:30:44 +0000 Subject: [PATCH 5/7] chore(deps): remove unused @hono/node-server Remove @hono/node-server dev dependency as it's no longer needed. The MCP mock server uses Hono's app.fetch() directly without requiring a Node.js HTTP server adapter. Related to #170 --- pnpm-lock.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b55d6293..96dafb88 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,9 +6,6 @@ settings: catalogs: dev: - '@ai-sdk/openai': - specifier: ^2.0.80 - version: 2.0.80 '@ai-sdk/provider-utils': specifier: ^3.0.18 version: 3.0.18 From d8348fdf9a823d096c57b893bab869db99e3f264 Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Tue, 9 Dec 2025 16:31:45 +0000 Subject: [PATCH 6/7] chore: workspace --- pnpm-workspace.yaml | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index ee7e3ed4..2ee55110 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -2,20 +2,18 @@ packages: - . - examples -catalog: - catalogMode: strict catalogs: dev: - '@ai-sdk/openai': ^2.0.80 - '@ai-sdk/provider-utils': ^3.0.18 - '@biomejs/biome': ^1.5.3 - '@hono/mcp': ^0.1.4 - '@types/json-schema': ^7.0.15 - '@types/node': ^22.13.5 - '@typescript/native-preview': ^7.0.0-dev.20251202.1 - '@vitest/coverage-v8': ^4.0.15 + "@ai-sdk/openai": ^2.0.80 + "@ai-sdk/provider-utils": ^3.0.18 + "@biomejs/biome": ^1.5.3 + "@hono/mcp": ^0.1.4 + "@types/json-schema": ^7.0.15 + "@types/node": ^22.13.5 + "@typescript/native-preview": ^7.0.0-dev.20251202.1 + "@vitest/coverage-v8": ^4.0.15 hono: ^4.9.10 knip: ^5.72.0 lefthook: ^2.0.8 @@ -31,8 +29,8 @@ catalogs: ai: ^5.0.108 openai: ^6.2.0 prod: - '@modelcontextprotocol/sdk': ^1.24.3 - '@orama/orama': ^3.1.11 + "@modelcontextprotocol/sdk": ^1.24.3 + "@orama/orama": ^3.1.11 json-schema: ^0.4.0 enablePrePostScripts: true @@ -40,7 +38,7 @@ enablePrePostScripts: true minimumReleaseAge: 1440 onlyBuiltDependencies: - - '@biomejs/biome' + - "@biomejs/biome" - esbuild - lefthook - msw From efb9e1dec8e5082ec856593742c39863d53444f7 Mon Sep 17 00:00:00 2001 From: ryoppippi <1560508+ryoppippi@users.noreply.github.com> Date: Tue, 9 Dec 2025 16:47:36 +0000 Subject: [PATCH 7/7] refactor(mocks): add as const satisfies to all mock tool definitions Applied 'as const satisfies McpToolDefinition[]' pattern consistently across all mock tool definitions (defaultMcpTools, accountMcpTools, mixedProviderTools) for improved type safety and immutability. This ensures TypeScript can infer the most specific literal types whilst maintaining type compatibility, following the project's typescript-patterns skill guidelines. --- mocks/mcp-server.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/mocks/mcp-server.ts b/mocks/mcp-server.ts index b92d2a73..a048f401 100644 --- a/mocks/mcp-server.ts +++ b/mocks/mcp-server.ts @@ -96,7 +96,7 @@ export function createMcpApp(options: MockMcpServerOptions): HonoApp { // Pre-defined tool sets for common test scenarios -export const defaultMcpTools: McpToolDefinition[] = [ +export const defaultMcpTools = [ { name: 'default_tool_1', description: 'Default Tool 1', @@ -114,9 +114,9 @@ export const defaultMcpTools: McpToolDefinition[] = [ required: ['id'], }, }, -]; +] as const satisfies McpToolDefinition[]; -export const accountMcpTools: Record = { +export const accountMcpTools = { acc1: [ { name: 'acc1_tool_1', @@ -135,7 +135,7 @@ export const accountMcpTools: Record = { required: ['id'], }, }, - ], + ] as const satisfies McpToolDefinition[], acc2: [ { name: 'acc2_tool_1', @@ -154,7 +154,7 @@ export const accountMcpTools: Record = { required: ['id'], }, }, - ], + ] as const satisfies McpToolDefinition[], acc3: [ { name: 'acc3_tool_1', @@ -164,7 +164,7 @@ export const accountMcpTools: Record = { properties: { fields: { type: 'string' } }, }, }, - ], + ] as const satisfies McpToolDefinition[], 'test-account': [ { name: 'dummy_action', @@ -181,8 +181,8 @@ export const accountMcpTools: Record = { additionalProperties: false, }, }, - ], -}; + ] as const satisfies McpToolDefinition[], +} as const; export const mixedProviderTools = [ {