Spaces:
Sleeping
Sleeping
| /** | |
| * MCP test harness. | |
| * | |
| * Creates an McpServer + MCP Client connected via InMemoryTransport for unit testing | |
| * tools and resources without HTTP overhead. | |
| * | |
| * Usage: | |
| * const harness = await createMcpHarness({ userId, registerTools: true }); | |
| * const result = await harness.client.callTool({ name: 'create_trip', arguments: { title: 'Test' } }); | |
| * await harness.cleanup(); | |
| */ | |
| import { McpServer } from '@modelcontextprotocol/sdk/server/mcp'; | |
| import { Client } from '@modelcontextprotocol/sdk/client/index'; | |
| import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory'; | |
| import { registerResources } from '../../src/mcp/resources'; | |
| import { registerTools } from '../../src/mcp/tools'; | |
| export interface McpHarness { | |
| client: Client; | |
| server: McpServer; | |
| cleanup: () => Promise<void>; | |
| } | |
| export interface McpHarnessOptions { | |
| userId: number; | |
| /** Register read-only resources (default: true) */ | |
| withResources?: boolean; | |
| /** Register read-write tools (default: true) */ | |
| withTools?: boolean; | |
| /** OAuth scopes to restrict tools; null = full access (default: null) */ | |
| scopes?: string[] | null; | |
| /** Whether the session is authenticated via a static API token (default: false) */ | |
| isStaticToken?: boolean; | |
| } | |
| export async function createMcpHarness(options: McpHarnessOptions): Promise<McpHarness> { | |
| const { userId, withResources = true, withTools = true, scopes = null, isStaticToken = false } = options; | |
| const server = new McpServer({ name: 'trek-test', version: '1.0.0' }); | |
| if (withResources) registerResources(server, userId); | |
| if (withTools) registerTools(server, userId, scopes ?? null, isStaticToken); | |
| const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); | |
| const client = new Client({ name: 'test-client', version: '1.0.0' }); | |
| await server.connect(serverTransport); | |
| await client.connect(clientTransport); | |
| const cleanup = async () => { | |
| try { await client.close(); } catch { /* ignore */ } | |
| try { await server.close(); } catch { /* ignore */ } | |
| }; | |
| return { client, server, cleanup }; | |
| } | |
| /** Parse JSON from a callTool result (first text content item). */ | |
| export function parseToolResult(result: Awaited<ReturnType<Client['callTool']>>): unknown { | |
| const text = result.content.find((c: { type: string }) => c.type === 'text') as { type: 'text'; text: string } | undefined; | |
| if (!text) throw new Error('No text content in tool result'); | |
| return JSON.parse(text.text); | |
| } | |
| /** Parse JSON from a readResource result (first content item). */ | |
| export function parseResourceResult(result: Awaited<ReturnType<Client['readResource']>>): unknown { | |
| const item = result.contents[0] as { text?: string } | undefined; | |
| if (!item?.text) throw new Error('No text content in resource result'); | |
| return JSON.parse(item.text); | |
| } | |