diff --git a/README.md b/README.md index 76c45ab9..6843fa7d 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,10 @@ npm install @modelcontextprotocol/sdk ```typescript import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; +import { + ListResourcesRequestSchema, + ReadResourceRequestSchema, +} from "@modelcontextprotocol/sdk/types.js"; const transport = new StdioClientTransport({ command: "path/to/server", @@ -61,6 +65,10 @@ const resourceContent = await client.request( ```typescript import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { + ListResourcesRequestSchema, + ReadResourceRequestSchema, +} from "@modelcontextprotocol/sdk/types.js"; const server = new Server({ name: "example-server", diff --git a/package.json b/package.json index 4cc75635..c46ea85d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/sdk", - "version": "1.0.4", + "version": "1.1.0", "description": "Model Context Protocol implementation for TypeScript", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", diff --git a/src/client/sse.ts b/src/client/sse.ts index e7f0dc6b..0e6e7eb9 100644 --- a/src/client/sse.ts +++ b/src/client/sse.ts @@ -12,13 +12,17 @@ export class SSEClientTransport implements Transport { private _endpoint?: URL; private _abortController?: AbortController; private _url: URL; + private _eventSourceInit?: EventSourceInit; + private _requestInit?: RequestInit; onclose?: () => void; onerror?: (error: Error) => void; onmessage?: (message: JSONRPCMessage) => void; - constructor(url: URL) { + constructor(url: URL, opts?: { eventSourceInit?: EventSourceInit, requestInit?: RequestInit }) { this._url = url; + this._eventSourceInit = opts?.eventSourceInit; + this._requestInit = opts?.requestInit; } start(): Promise { @@ -29,7 +33,7 @@ export class SSEClientTransport implements Transport { } return new Promise((resolve, reject) => { - this._eventSource = new EventSource(this._url.href); + this._eventSource = new EventSource(this._url.href, this._eventSourceInit); this._abortController = new AbortController(); this._eventSource.onerror = (event) => { @@ -90,14 +94,17 @@ export class SSEClientTransport implements Transport { } try { - const response = await fetch(this._endpoint, { + const headers = new Headers(this._requestInit?.headers); + headers.set("content-type", "application/json"); + const init = { + ...this._requestInit, method: "POST", - headers: { - "Content-Type": "application/json", - }, + headers, body: JSON.stringify(message), - signal: this._abortController?.signal, - }); + signal: this._abortController?.signal + }; + + const response = await fetch(this._endpoint, init); if (!response.ok) { const text = await response.text().catch(() => null); diff --git a/src/shared/protocol.test.ts b/src/shared/protocol.test.ts new file mode 100644 index 00000000..9423a2a7 --- /dev/null +++ b/src/shared/protocol.test.ts @@ -0,0 +1,63 @@ +import { Protocol } from "./protocol.js"; +import { Transport } from "./transport.js"; +import { + McpError, + ErrorCode, + Request, + Result, + Notification, +} from "../types.js"; +import { ZodType, z } from "zod"; + +// Mock Transport class +class MockTransport implements Transport { + onclose?: () => void; + onerror?: (error: Error) => void; + onmessage?: (message: unknown) => void; + + async start(): Promise {} + async close(): Promise { + this.onclose?.(); + } + async send(_message: unknown): Promise {} +} + +describe("protocol tests", () => { + let protocol: Protocol; + let transport: MockTransport; + + beforeEach(() => { + transport = new MockTransport(); + protocol = new (class extends Protocol { + protected assertCapabilityForMethod(): void {} + protected assertNotificationCapability(): void {} + protected assertRequestHandlerCapability(): void {} + })(); + }); + + test("should throw a timeout error if the request exceeds the timeout", async () => { + await protocol.connect(transport); + const request = { method: "example", params: {} }; + try { + const mockSchema: ZodType<{ result: string }> = z.object({ + result: z.string(), + }); + await protocol.request(request, mockSchema, { + timeout: 0, + }); + } catch (error) { + expect(error).toBeInstanceOf(McpError); + if (error instanceof McpError) { + expect(error.code).toBe(ErrorCode.RequestTimeout); + } + } + }); + + test("should invoke onclose when the connection is closed", async () => { + const oncloseMock = jest.fn(); + protocol.onclose = oncloseMock; + await protocol.connect(transport); + await transport.close(); + expect(oncloseMock).toHaveBeenCalled(); + }); +}); diff --git a/src/types.ts b/src/types.ts index 44a44d8f..59b055a8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -100,13 +100,13 @@ export const JSONRPCResponseSchema = z .strict(); /** - * An incomplete set of error codes that may appear in JSON-RPC responses. + * Error codes defined by the JSON-RPC specification. */ export enum ErrorCode { // SDK error codes - ConnectionClosed = -1, - RequestTimeout = -2, - + ConnectionClosed = -32000, + RequestTimeout = -32001, + // Standard JSON-RPC error codes ParseError = -32700, InvalidRequest = -32600, @@ -1237,4 +1237,4 @@ export type ClientResult = z.infer; /* Server messages */ export type ServerRequest = z.infer; export type ServerNotification = z.infer; -export type ServerResult = z.infer; +export type ServerResult = z.infer; \ No newline at end of file