diff --git a/src/examples/client/multipleClientsParallel.ts b/src/examples/client/multipleClientsParallel.ts index 90b9d4ea..cc01fc06 100644 --- a/src/examples/client/multipleClientsParallel.ts +++ b/src/examples/client/multipleClientsParallel.ts @@ -4,6 +4,7 @@ import { CallToolRequest, CallToolResultSchema, LoggingMessageNotificationSchema, + CallToolResult, } from '../../types.js'; /** @@ -23,12 +24,12 @@ interface ClientConfig { id: string; name: string; toolName: string; - toolArguments: Record; + toolArguments: Record; } -async function createAndRunClient(config: ClientConfig): Promise<{ id: string; result: any }> { +async function createAndRunClient(config: ClientConfig): Promise<{ id: string; result: CallToolResult }> { console.log(`[${config.id}] Creating client: ${config.name}`); - + const client = new Client({ name: config.name, version: '1.0.0' @@ -101,7 +102,7 @@ async function main(): Promise { } }, { - id: 'client2', + id: 'client2', name: 'basic-client-2', toolName: 'start-notification-stream', toolArguments: { diff --git a/src/examples/client/parallelToolCallsClient.ts b/src/examples/client/parallelToolCallsClient.ts index 8a53ade9..3783992d 100644 --- a/src/examples/client/parallelToolCallsClient.ts +++ b/src/examples/client/parallelToolCallsClient.ts @@ -3,9 +3,9 @@ import { StreamableHTTPClientTransport } from '../../client/streamableHttp.js'; import { ListToolsRequest, ListToolsResultSchema, - CallToolRequest, CallToolResultSchema, LoggingMessageNotificationSchema, + CallToolResult, } from '../../types.js'; /** @@ -57,11 +57,11 @@ async function main(): Promise { // 2. Start multiple notification tools in parallel console.log('\n=== Starting Multiple Notification Streams in Parallel ==='); const toolResults = await startParallelNotificationTools(client); - + // Log the results from each tool call for (const [caller, result] of Object.entries(toolResults)) { console.log(`\n=== Tool result for ${caller} ===`); - result.content.forEach((item: { type: string; text: any; }) => { + result.content.forEach((item: { type: string; text?: string; }) => { if (item.type === 'text') { console.log(` ${item.text}`); } else { @@ -113,7 +113,7 @@ async function listTools(client: Client): Promise { * Start multiple notification tools in parallel with different configurations * Each tool call includes a caller parameter to identify its notifications */ -async function startParallelNotificationTools(client: Client): Promise> { +async function startParallelNotificationTools(client: Client): Promise> { try { // Define multiple tool calls with different configurations const toolCalls = [ @@ -162,7 +162,7 @@ async function startParallelNotificationTools(client: Client): Promise { console.log(`Starting tool call for ${caller}...`); @@ -176,9 +176,9 @@ async function startParallelNotificationTools(client: Client): Promise = {}; + const resultsByTool: Record = {}; results.forEach(({ caller, result }) => { resultsByTool[caller] = result; }); diff --git a/src/integration-tests/streamableHttpBatchMessages.test.ts b/src/integration-tests/streamableHttpBatchMessages.test.ts deleted file mode 100644 index dc79e3de..00000000 --- a/src/integration-tests/streamableHttpBatchMessages.test.ts +++ /dev/null @@ -1,252 +0,0 @@ -/** - * Integration tests for batch messaging in StreamableHttp transport - * - * This test suite focuses specifically on the requirement from the spec: - * "The body of the POST request MUST be one of the following: - * - A single JSON-RPC request, notification, or response - * - An array batching one or more requests and/or notifications - * - An array batching one or more responses" - */ - -import { createServer, type Server } from "node:http"; -import { AddressInfo } from "node:net"; -import { randomUUID } from "node:crypto"; -import { StreamableHTTPServerTransport } from "../server/streamableHttp.js"; -import { McpServer } from "../server/mcp.js"; -import { Client } from "../client/index.js"; -import { CallToolResult, CallToolResultSchema, JSONRPCMessage } from "../types.js"; -import { z } from "zod"; -import { StreamableHTTPClientTransport } from '../client/streamableHttp.js'; -describe("StreamableHttp Batch Messaging - SSE", () => { - let server: Server; - let client: Client; - - // Just a basic server and client for testing - beforeEach(async () => { - // Create MCP server with test tools - const mcpServer = new McpServer( - { name: "batch-test-server", version: "1.0.0" }, - { capabilities: { logging: {} } } - ); - - // Simple greeting tool - mcpServer.tool( - "greet", - "A simple greeting tool", - { name: z.string().describe("Name to greet") }, - async ({ name }): Promise => { - return { content: [{ type: "text", text: `Hello, ${name}!` }] }; - } - ); - - // Server transport with session management - const serverTransport = new StreamableHTTPServerTransport({ - sessionIdGenerator: () => randomUUID(), - }); - - // Connect transport to server - await mcpServer.connect(serverTransport); - - // Create HTTP server - server = createServer(async (req, res) => { - await serverTransport.handleRequest(req, res); - }); - - // Start server on random port - const baseUrl = await new Promise((resolve) => { - server.listen(0, '127.0.0.1', () => { - const addr = server.address() as AddressInfo; - resolve(new URL(`http://127.0.0.1:${addr.port}`)); - }); - }); - - // Create client that connects to the server - const transport = new StreamableHTTPClientTransport(baseUrl); - client = new Client({ name: "test-client", version: "1.0.0" }); - await client.connect(transport); - - }); - - afterEach(async () => { - await client.close(); - await new Promise((resolve) => server.close(() => resolve())); - }); - - /** - * 1. Test sending a single JSON-RPC request - */ - it("handles a single request", async () => { - const result = await client.callTool({ - name: "greet", - arguments: { - name: "user" - } - }); - - expect(result).toHaveProperty("content"); - expect(Array.isArray(result.content)).toBe(true); - expect(result.content).toHaveLength(1); - - // Type assertion to handle the unknown type - const content = result.content as Array<{ type: string, text: string }>; - expect(content[0]).toHaveProperty("type", "text"); - expect(content[0]).toHaveProperty("text", "Hello, user!"); - - }); - - /** - * 1. Test sending a single JSON-RPC request - */ - it("handles a single request with request", async () => { - const result = await client.request( - { - method: "tools/call", - params: { - name: "greet", - arguments: { - name: "user" - } - }, - }, - CallToolResultSchema, - ); - - - expect(result).toHaveProperty("content"); - expect(Array.isArray(result.content)).toBe(true); - expect(result.content).toHaveLength(1); - - // Type assertion to handle the unknown type - const content = result.content as Array<{ type: string, text: string }>; - expect(content[0]).toHaveProperty("type", "text"); - expect(content[0]).toHaveProperty("text", "Hello, user!"); - - }); - - // it("handles an array batching one or more requests and/or notifications", async () => { - // const result = await client.request({ - - // }); - - // }); - - -}); - -describe("StreamableHttp Batch Messaging - JSON response", () => { - let server: Server; - let client: Client; - - // Just a basic server and client for testing - beforeEach(async () => { - // Create MCP server with test tools - const mcpServer = new McpServer( - { name: "batch-test-server", version: "1.0.0" }, - { capabilities: { logging: {} } } - ); - - // Simple greeting tool - mcpServer.tool( - "greet", - "A simple greeting tool", - { name: z.string().describe("Name to greet") }, - async ({ name }): Promise => { - return { content: [{ type: "text", text: `Hello, ${name}!` }] }; - } - ); - - // Server transport with session management - const serverTransport = new StreamableHTTPServerTransport({ - sessionIdGenerator: () => randomUUID(), - enableJsonResponse: true, - - }); - - // Connect transport to server - await mcpServer.connect(serverTransport); - - // Create HTTP server - server = createServer(async (req, res) => { - await serverTransport.handleRequest(req, res); - }); - - // Start server on random port - const baseUrl = await new Promise((resolve) => { - server.listen(0, '127.0.0.1', () => { - const addr = server.address() as AddressInfo; - resolve(new URL(`http://127.0.0.1:${addr.port}`)); - }); - }); - - // Create client that connects to the server - const transport = new StreamableHTTPClientTransport(baseUrl); - client = new Client({ name: "test-client", version: "1.0.0" }); - await client.connect(transport); - - }); - - afterEach(async () => { - await client.close(); - await new Promise((resolve) => server.close(() => resolve())); - }); - - /** - * 1. Test sending a single JSON-RPC request - */ - it("handles a single request", async () => { - const result = await client.callTool({ - name: "greet", - arguments: { - name: "user" - } - }); - - expect(result).toHaveProperty("content"); - expect(Array.isArray(result.content)).toBe(true); - expect(result.content).toHaveLength(1); - - // Type assertion to handle the unknown type - const content = result.content as Array<{ type: string, text: string }>; - expect(content[0]).toHaveProperty("type", "text"); - expect(content[0]).toHaveProperty("text", "Hello, user!"); - - }); - - /** - * 1. Test sending a single JSON-RPC request - */ - it("handles a single request with request", async () => { - const result = await client.request( - { - method: "tools/call", - params: { - name: "greet", - arguments: { - name: "user" - } - }, - }, - CallToolResultSchema, - ); - - - expect(result).toHaveProperty("content"); - expect(Array.isArray(result.content)).toBe(true); - expect(result.content).toHaveLength(1); - - // Type assertion to handle the unknown type - const content = result.content as Array<{ type: string, text: string }>; - expect(content[0]).toHaveProperty("type", "text"); - expect(content[0]).toHaveProperty("text", "Hello, user!"); - - }); - - // it("handles an array batching one or more requests and/or notifications", async () => { - // const result = await client.request({ - - // }); - - // }); - - -}); \ No newline at end of file diff --git a/src/server/streamableHttp.test.ts b/src/server/streamableHttp.test.ts index 7f9e85d8..a0f2e0bb 100644 --- a/src/server/streamableHttp.test.ts +++ b/src/server/streamableHttp.test.ts @@ -288,7 +288,7 @@ describe("StreamableHTTPServerTransport", () => { }); }); - it.only("should reject requests without a valid session ID", async () => { + it("should reject requests without a valid session ID", async () => { const response = await sendPostRequest(baseUrl, TEST_MESSAGES.toolsList); expect(response.status).toBe(400);