@mcpkit-dev/testing 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/index.d.ts +3 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ npm install -D @mcpkit-dev/testing
|
|
|
19
19
|
```typescript
|
|
20
20
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
21
21
|
import { MockMcpClient } from '@mcpkit-dev/testing';
|
|
22
|
-
import {
|
|
22
|
+
import { bootstrapServer, MetadataStorage } from '@mcpkit-dev/core';
|
|
23
23
|
import { MyServer } from './my-server';
|
|
24
24
|
|
|
25
25
|
describe('MyServer', () => {
|
|
@@ -30,7 +30,9 @@ describe('MyServer', () => {
|
|
|
30
30
|
const { client: mockClient, serverTransport } = MockMcpClient.create();
|
|
31
31
|
client = mockClient;
|
|
32
32
|
|
|
33
|
-
const
|
|
33
|
+
const instance = new MyServer();
|
|
34
|
+
const options = MetadataStorage.getServerOptions(MyServer);
|
|
35
|
+
const server = await bootstrapServer(instance, options!);
|
|
34
36
|
await server.server.connect(serverTransport);
|
|
35
37
|
await client.connect();
|
|
36
38
|
|
package/dist/index.d.ts
CHANGED
|
@@ -31,7 +31,9 @@ interface MockClientResult {
|
|
|
31
31
|
* const { client, serverTransport } = MockMcpClient.create();
|
|
32
32
|
*
|
|
33
33
|
* // Connect your server to serverTransport
|
|
34
|
-
* const
|
|
34
|
+
* const instance = new MyServer();
|
|
35
|
+
* const options = MetadataStorage.getServerOptions(MyServer);
|
|
36
|
+
* const server = await bootstrapServer(instance, options!);
|
|
35
37
|
* await server.server.connect(serverTransport);
|
|
36
38
|
*
|
|
37
39
|
* // Use the client to test your server
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/mock-client.ts","../src/transport.ts","../src/helpers.ts"],"sourcesContent":["import { Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';\nimport { InMemoryTransport } from './transport.js';\n\n/**\n * Options for creating a mock MCP client\n */\nexport interface MockClientOptions {\n /** Client name */\n name?: string;\n /** Client version */\n version?: string;\n}\n\n/**\n * Result from creating a mock client\n */\nexport interface MockClientResult {\n /** The MCP client instance */\n client: MockMcpClient;\n /** Transport for connecting to a server */\n serverTransport: Transport;\n}\n\n/**\n * Mock MCP client for testing servers\n *\n * Provides a simple interface for testing MCP server implementations\n * without needing actual network connections.\n *\n * @example\n * ```typescript\n * const { client, serverTransport } = MockMcpClient.create();\n *\n * // Connect your server to serverTransport\n * const server = await listen(MyServer);\n * await server.server.connect(serverTransport);\n *\n * // Use the client to test your server\n * const tools = await client.listTools();\n * const result = await client.callTool('myTool', { param: 'value' });\n * ```\n */\nexport class MockMcpClient {\n private client: Client;\n private clientTransport: InMemoryTransport;\n private connected = false;\n\n private constructor(options: MockClientOptions = {}) {\n this.client = new Client(\n {\n name: options.name ?? 'test-client',\n version: options.version ?? '1.0.0',\n },\n {\n capabilities: {},\n },\n );\n\n this.clientTransport = new InMemoryTransport();\n }\n\n /**\n * Create a mock client with linked transports\n */\n static create(options?: MockClientOptions): MockClientResult {\n const mockClient = new MockMcpClient(options);\n const { clientTransport, serverTransport } = InMemoryTransport.createPair();\n\n mockClient.clientTransport = clientTransport;\n\n return {\n client: mockClient,\n serverTransport,\n };\n }\n\n /**\n * Connect to the server\n */\n async connect(): Promise<void> {\n if (this.connected) {\n return;\n }\n\n await this.client.connect(this.clientTransport);\n this.connected = true;\n }\n\n /**\n * Close the connection\n */\n async close(): Promise<void> {\n if (!this.connected) {\n return;\n }\n\n await this.client.close();\n this.connected = false;\n }\n\n /**\n * List all available tools\n */\n async listTools() {\n await this.ensureConnected();\n return this.client.listTools();\n }\n\n /**\n * Call a tool by name\n */\n async callTool(name: string, args?: Record<string, unknown>) {\n await this.ensureConnected();\n return this.client.callTool({ name, arguments: args });\n }\n\n /**\n * List all available resources\n */\n async listResources() {\n await this.ensureConnected();\n return this.client.listResources();\n }\n\n /**\n * Read a resource by URI\n */\n async readResource(uri: string) {\n await this.ensureConnected();\n return this.client.readResource({ uri });\n }\n\n /**\n * List all available prompts\n */\n async listPrompts() {\n await this.ensureConnected();\n return this.client.listPrompts();\n }\n\n /**\n * Get a prompt by name\n */\n async getPrompt(name: string, args?: Record<string, string>) {\n await this.ensureConnected();\n return this.client.getPrompt({ name, arguments: args });\n }\n\n /**\n * Ensure the client is connected\n */\n private async ensureConnected(): Promise<void> {\n if (!this.connected) {\n await this.connect();\n }\n }\n\n /**\n * Get the underlying MCP client\n */\n get rawClient(): Client {\n return this.client;\n }\n}\n","import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';\nimport type { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';\n\n/**\n * In-memory transport for testing MCP servers\n *\n * This transport allows direct communication between a client and server\n * without any network overhead, perfect for unit and integration tests.\n *\n * @example\n * ```typescript\n * const { clientTransport, serverTransport } = InMemoryTransport.createPair();\n *\n * // Connect server to serverTransport\n * await server.connect(serverTransport);\n *\n * // Use clientTransport to send messages\n * await clientTransport.send({ jsonrpc: '2.0', method: 'ping', id: 1 });\n * ```\n */\nexport class InMemoryTransport implements Transport {\n private peer: InMemoryTransport | null = null;\n private messageQueue: JSONRPCMessage[] = [];\n private started = false;\n private closed = false;\n\n onclose?: () => void;\n onerror?: (error: Error) => void;\n onmessage?: (message: JSONRPCMessage) => void;\n\n /**\n * Create a linked pair of transports for testing\n */\n static createPair(): { clientTransport: InMemoryTransport; serverTransport: InMemoryTransport } {\n const clientTransport = new InMemoryTransport();\n const serverTransport = new InMemoryTransport();\n\n clientTransport.peer = serverTransport;\n serverTransport.peer = clientTransport;\n\n return { clientTransport, serverTransport };\n }\n\n /**\n * Start the transport\n */\n async start(): Promise<void> {\n if (this.started) {\n throw new Error('Transport already started');\n }\n if (this.closed) {\n throw new Error('Transport is closed');\n }\n\n this.started = true;\n\n // Process any queued messages\n while (this.messageQueue.length > 0) {\n const message = this.messageQueue.shift();\n if (message) {\n this.onmessage?.(message);\n }\n }\n }\n\n /**\n * Send a message to the peer transport\n */\n async send(message: JSONRPCMessage): Promise<void> {\n if (this.closed) {\n throw new Error('Transport is closed');\n }\n\n if (!this.peer) {\n throw new Error('No peer transport connected');\n }\n\n // Simulate async behavior\n await Promise.resolve();\n\n if (this.peer.started) {\n this.peer.onmessage?.(message);\n } else {\n this.peer.messageQueue.push(message);\n }\n }\n\n /**\n * Close the transport\n */\n async close(): Promise<void> {\n if (this.closed) {\n return;\n }\n\n this.closed = true;\n this.onclose?.();\n\n // Also close peer\n if (this.peer && !this.peer.closed) {\n await this.peer.close();\n }\n }\n\n /**\n * Deliver a message directly (for testing purposes)\n */\n deliverMessage(message: JSONRPCMessage): void {\n if (this.started) {\n this.onmessage?.(message);\n } else {\n this.messageQueue.push(message);\n }\n }\n}\n","import { type MockClientOptions, type MockClientResult, MockMcpClient } from './mock-client.js';\n\n/**\n * Result from creating a test server\n */\nexport interface TestServerResult<T> {\n /** The server instance */\n instance: T;\n /** The bootstrapped server */\n server: {\n server: unknown;\n transport: unknown;\n connect: () => Promise<void>;\n close: () => Promise<void>;\n };\n /** The test client */\n client: MockMcpClient;\n /** Cleanup function */\n cleanup: () => Promise<void>;\n}\n\n/**\n * Create a test client for MCP server testing\n *\n * @example\n * ```typescript\n * const { client, serverTransport } = createTestClient();\n * ```\n */\nexport function createTestClient(options?: MockClientOptions): MockClientResult {\n return MockMcpClient.create(options);\n}\n\n/**\n * Wait for a condition to be true, with timeout\n *\n * @example\n * ```typescript\n * await waitForCondition(() => server.isReady, { timeout: 5000 });\n * ```\n */\nexport async function waitForCondition(\n condition: () => boolean | Promise<boolean>,\n options: { timeout?: number; interval?: number } = {},\n): Promise<void> {\n const { timeout = 5000, interval = 50 } = options;\n const startTime = Date.now();\n\n while (Date.now() - startTime < timeout) {\n if (await condition()) {\n return;\n }\n await new Promise((resolve) => setTimeout(resolve, interval));\n }\n\n throw new Error(`Condition not met within ${timeout}ms`);\n}\n\n/**\n * Create a test server with a connected client\n *\n * This is a convenience function that sets up a complete test environment\n * with a server instance, bootstrapped server, and connected client.\n *\n * @example\n * ```typescript\n * import { createTestServer } from '@mcpkit-dev/testing';\n * import { listen } from '@mcpkit-dev/core';\n *\n * // In your test\n * const { client, cleanup } = await createTestServer(MyServer, listen);\n *\n * // Test your server\n * const tools = await client.listTools();\n * expect(tools.tools).toHaveLength(1);\n *\n * // Clean up\n * await cleanup();\n * ```\n */\nexport async function createTestServer<T extends new () => object>(\n ServerClass: T,\n listenFn: (ServerClass: T) => Promise<{\n server: unknown;\n transport: unknown;\n connect: () => Promise<void>;\n close: () => Promise<void>;\n }>,\n): Promise<TestServerResult<InstanceType<T>>> {\n const { client, serverTransport } = createTestClient();\n const instance = new ServerClass() as InstanceType<T>;\n const bootstrapped = await listenFn(ServerClass);\n\n // Connect server to transport\n // We need to access the internal server and connect it\n const server = bootstrapped.server as {\n connect: (transport: unknown) => Promise<void>;\n };\n await server.connect(serverTransport);\n\n // Connect client\n await client.connect();\n\n return {\n instance,\n server: bootstrapped,\n client,\n cleanup: async () => {\n await client.close();\n await bootstrapped.close();\n },\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/mock-client.ts","../src/transport.ts","../src/helpers.ts"],"sourcesContent":["import { Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';\nimport { InMemoryTransport } from './transport.js';\n\n/**\n * Options for creating a mock MCP client\n */\nexport interface MockClientOptions {\n /** Client name */\n name?: string;\n /** Client version */\n version?: string;\n}\n\n/**\n * Result from creating a mock client\n */\nexport interface MockClientResult {\n /** The MCP client instance */\n client: MockMcpClient;\n /** Transport for connecting to a server */\n serverTransport: Transport;\n}\n\n/**\n * Mock MCP client for testing servers\n *\n * Provides a simple interface for testing MCP server implementations\n * without needing actual network connections.\n *\n * @example\n * ```typescript\n * const { client, serverTransport } = MockMcpClient.create();\n *\n * // Connect your server to serverTransport\n * const instance = new MyServer();\n * const options = MetadataStorage.getServerOptions(MyServer);\n * const server = await bootstrapServer(instance, options!);\n * await server.server.connect(serverTransport);\n *\n * // Use the client to test your server\n * const tools = await client.listTools();\n * const result = await client.callTool('myTool', { param: 'value' });\n * ```\n */\nexport class MockMcpClient {\n private client: Client;\n private clientTransport: InMemoryTransport;\n private connected = false;\n\n private constructor(options: MockClientOptions = {}) {\n this.client = new Client(\n {\n name: options.name ?? 'test-client',\n version: options.version ?? '1.0.0',\n },\n {\n capabilities: {},\n },\n );\n\n this.clientTransport = new InMemoryTransport();\n }\n\n /**\n * Create a mock client with linked transports\n */\n static create(options?: MockClientOptions): MockClientResult {\n const mockClient = new MockMcpClient(options);\n const { clientTransport, serverTransport } = InMemoryTransport.createPair();\n\n mockClient.clientTransport = clientTransport;\n\n return {\n client: mockClient,\n serverTransport,\n };\n }\n\n /**\n * Connect to the server\n */\n async connect(): Promise<void> {\n if (this.connected) {\n return;\n }\n\n await this.client.connect(this.clientTransport);\n this.connected = true;\n }\n\n /**\n * Close the connection\n */\n async close(): Promise<void> {\n if (!this.connected) {\n return;\n }\n\n await this.client.close();\n this.connected = false;\n }\n\n /**\n * List all available tools\n */\n async listTools() {\n await this.ensureConnected();\n return this.client.listTools();\n }\n\n /**\n * Call a tool by name\n */\n async callTool(name: string, args?: Record<string, unknown>) {\n await this.ensureConnected();\n return this.client.callTool({ name, arguments: args });\n }\n\n /**\n * List all available resources\n */\n async listResources() {\n await this.ensureConnected();\n return this.client.listResources();\n }\n\n /**\n * Read a resource by URI\n */\n async readResource(uri: string) {\n await this.ensureConnected();\n return this.client.readResource({ uri });\n }\n\n /**\n * List all available prompts\n */\n async listPrompts() {\n await this.ensureConnected();\n return this.client.listPrompts();\n }\n\n /**\n * Get a prompt by name\n */\n async getPrompt(name: string, args?: Record<string, string>) {\n await this.ensureConnected();\n return this.client.getPrompt({ name, arguments: args });\n }\n\n /**\n * Ensure the client is connected\n */\n private async ensureConnected(): Promise<void> {\n if (!this.connected) {\n await this.connect();\n }\n }\n\n /**\n * Get the underlying MCP client\n */\n get rawClient(): Client {\n return this.client;\n }\n}\n","import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';\nimport type { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';\n\n/**\n * In-memory transport for testing MCP servers\n *\n * This transport allows direct communication between a client and server\n * without any network overhead, perfect for unit and integration tests.\n *\n * @example\n * ```typescript\n * const { clientTransport, serverTransport } = InMemoryTransport.createPair();\n *\n * // Connect server to serverTransport\n * await server.connect(serverTransport);\n *\n * // Use clientTransport to send messages\n * await clientTransport.send({ jsonrpc: '2.0', method: 'ping', id: 1 });\n * ```\n */\nexport class InMemoryTransport implements Transport {\n private peer: InMemoryTransport | null = null;\n private messageQueue: JSONRPCMessage[] = [];\n private started = false;\n private closed = false;\n\n onclose?: () => void;\n onerror?: (error: Error) => void;\n onmessage?: (message: JSONRPCMessage) => void;\n\n /**\n * Create a linked pair of transports for testing\n */\n static createPair(): { clientTransport: InMemoryTransport; serverTransport: InMemoryTransport } {\n const clientTransport = new InMemoryTransport();\n const serverTransport = new InMemoryTransport();\n\n clientTransport.peer = serverTransport;\n serverTransport.peer = clientTransport;\n\n return { clientTransport, serverTransport };\n }\n\n /**\n * Start the transport\n */\n async start(): Promise<void> {\n if (this.started) {\n throw new Error('Transport already started');\n }\n if (this.closed) {\n throw new Error('Transport is closed');\n }\n\n this.started = true;\n\n // Process any queued messages\n while (this.messageQueue.length > 0) {\n const message = this.messageQueue.shift();\n if (message) {\n this.onmessage?.(message);\n }\n }\n }\n\n /**\n * Send a message to the peer transport\n */\n async send(message: JSONRPCMessage): Promise<void> {\n if (this.closed) {\n throw new Error('Transport is closed');\n }\n\n if (!this.peer) {\n throw new Error('No peer transport connected');\n }\n\n // Simulate async behavior\n await Promise.resolve();\n\n if (this.peer.started) {\n this.peer.onmessage?.(message);\n } else {\n this.peer.messageQueue.push(message);\n }\n }\n\n /**\n * Close the transport\n */\n async close(): Promise<void> {\n if (this.closed) {\n return;\n }\n\n this.closed = true;\n this.onclose?.();\n\n // Also close peer\n if (this.peer && !this.peer.closed) {\n await this.peer.close();\n }\n }\n\n /**\n * Deliver a message directly (for testing purposes)\n */\n deliverMessage(message: JSONRPCMessage): void {\n if (this.started) {\n this.onmessage?.(message);\n } else {\n this.messageQueue.push(message);\n }\n }\n}\n","import { type MockClientOptions, type MockClientResult, MockMcpClient } from './mock-client.js';\n\n/**\n * Result from creating a test server\n */\nexport interface TestServerResult<T> {\n /** The server instance */\n instance: T;\n /** The bootstrapped server */\n server: {\n server: unknown;\n transport: unknown;\n connect: () => Promise<void>;\n close: () => Promise<void>;\n };\n /** The test client */\n client: MockMcpClient;\n /** Cleanup function */\n cleanup: () => Promise<void>;\n}\n\n/**\n * Create a test client for MCP server testing\n *\n * @example\n * ```typescript\n * const { client, serverTransport } = createTestClient();\n * ```\n */\nexport function createTestClient(options?: MockClientOptions): MockClientResult {\n return MockMcpClient.create(options);\n}\n\n/**\n * Wait for a condition to be true, with timeout\n *\n * @example\n * ```typescript\n * await waitForCondition(() => server.isReady, { timeout: 5000 });\n * ```\n */\nexport async function waitForCondition(\n condition: () => boolean | Promise<boolean>,\n options: { timeout?: number; interval?: number } = {},\n): Promise<void> {\n const { timeout = 5000, interval = 50 } = options;\n const startTime = Date.now();\n\n while (Date.now() - startTime < timeout) {\n if (await condition()) {\n return;\n }\n await new Promise((resolve) => setTimeout(resolve, interval));\n }\n\n throw new Error(`Condition not met within ${timeout}ms`);\n}\n\n/**\n * Create a test server with a connected client\n *\n * This is a convenience function that sets up a complete test environment\n * with a server instance, bootstrapped server, and connected client.\n *\n * @example\n * ```typescript\n * import { createTestServer } from '@mcpkit-dev/testing';\n * import { listen } from '@mcpkit-dev/core';\n *\n * // In your test\n * const { client, cleanup } = await createTestServer(MyServer, listen);\n *\n * // Test your server\n * const tools = await client.listTools();\n * expect(tools.tools).toHaveLength(1);\n *\n * // Clean up\n * await cleanup();\n * ```\n */\nexport async function createTestServer<T extends new () => object>(\n ServerClass: T,\n listenFn: (ServerClass: T) => Promise<{\n server: unknown;\n transport: unknown;\n connect: () => Promise<void>;\n close: () => Promise<void>;\n }>,\n): Promise<TestServerResult<InstanceType<T>>> {\n const { client, serverTransport } = createTestClient();\n const instance = new ServerClass() as InstanceType<T>;\n const bootstrapped = await listenFn(ServerClass);\n\n // Connect server to transport\n // We need to access the internal server and connect it\n const server = bootstrapped.server as {\n connect: (transport: unknown) => Promise<void>;\n };\n await server.connect(serverTransport);\n\n // Connect client\n await client.connect();\n\n return {\n instance,\n server: bootstrapped,\n client,\n cleanup: async () => {\n await client.close();\n await bootstrapped.close();\n },\n };\n}\n"],"mappings":"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","names":[]}
|