From f383a9cb8cbcebd9057e0eb2ec1dc22b250c8970 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Tue, 1 Jul 2025 14:00:56 -0400 Subject: [PATCH] * In sse.test.ts - More specific expectations in test "uses custom fetch implementation from options" * In sse.ts - Import FetchLike from transport.ts * In steramableHttp.ts - Import FetchLike from transport.ts - Export StartSSEOptions interface for testing * In streamableHttp.test.ts - import StartSSEOptions from streamableHttp.ts - use StartSSEOptions instead of any in test "uses custom fetch implementation" * In transport.ts - Add FetchLike function type --- src/client/sse.test.ts | 4 +++- src/client/sse.ts | 4 +--- src/client/streamableHttp.test.ts | 6 +++--- src/client/streamableHttp.ts | 12 +++++------- src/shared/transport.ts | 10 ++++++---- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/client/sse.test.ts b/src/client/sse.test.ts index c446b9e9..2d116344 100644 --- a/src/client/sse.test.ts +++ b/src/client/sse.test.ts @@ -289,7 +289,9 @@ describe("SSEClientTransport", () => { await transport.send(message); - expect(fetchWithAuth).toHaveBeenCalled(); + expect(fetchWithAuth).toHaveBeenCalledTimes(2); + expect(lastServerRequest.method).toBe("POST"); + expect(lastServerRequest.headers.authorization).toBe(authToken); }); it("passes custom headers to fetch requests", async () => { diff --git a/src/client/sse.ts b/src/client/sse.ts index af04f274..faffecc4 100644 --- a/src/client/sse.ts +++ b/src/client/sse.ts @@ -1,10 +1,8 @@ import { EventSource, type ErrorEvent, type EventSourceInit } from "eventsource"; -import { Transport } from "../shared/transport.js"; +import { Transport, FetchLike } from "../shared/transport.js"; import { JSONRPCMessage, JSONRPCMessageSchema } from "../types.js"; import { auth, AuthResult, extractResourceMetadataUrl, OAuthClientProvider, UnauthorizedError } from "./auth.js"; -export type FetchLike = (url: string | URL, init?: RequestInit) => Promise; - export class SseError extends Error { constructor( public readonly code: number | undefined, diff --git a/src/client/streamableHttp.test.ts b/src/client/streamableHttp.test.ts index be70b8b4..dcd76528 100644 --- a/src/client/streamableHttp.test.ts +++ b/src/client/streamableHttp.test.ts @@ -1,4 +1,4 @@ -import { StreamableHTTPClientTransport, StreamableHTTPReconnectionOptions } from "./streamableHttp.js"; +import { StreamableHTTPClientTransport, StreamableHTTPReconnectionOptions, StartSSEOptions } from "./streamableHttp.js"; import { OAuthClientProvider, UnauthorizedError } from "./auth.js"; import { JSONRPCMessage } from "../types.js"; @@ -461,7 +461,7 @@ describe("StreamableHTTPClientTransport", () => { transport = new StreamableHTTPClientTransport(new URL("http://localhost:1234/mcp"), { fetch: fetchWithAuth }); await transport.start(); - await (transport as unknown as { _startOrAuthSse: (opts: any) => Promise })._startOrAuthSse({}); + await (transport as unknown as { _startOrAuthSse: (opts: StartSSEOptions) => Promise })._startOrAuthSse({}); await transport.send({ jsonrpc: "2.0", method: "test", params: {}, id: "1" } as JSONRPCMessage); @@ -559,7 +559,7 @@ describe("StreamableHTTPClientTransport", () => { // Second retry - should double (2^1 * 100 = 200) expect(getDelay(1)).toBe(200); - // Third retry - should double again (2^2 * 100 = 400) + // Third retry - should double again (2^2 * 100 = 400) expect(getDelay(2)).toBe(400); // Fourth retry - should double again (2^3 * 100 = 800) diff --git a/src/client/streamableHttp.ts b/src/client/streamableHttp.ts index ceaecd83..b81f1a5d 100644 --- a/src/client/streamableHttp.ts +++ b/src/client/streamableHttp.ts @@ -1,10 +1,8 @@ -import { Transport } from "../shared/transport.js"; +import { Transport, FetchLike } from "../shared/transport.js"; import { isInitializedNotification, isJSONRPCRequest, isJSONRPCResponse, JSONRPCMessage, JSONRPCMessageSchema } from "../types.js"; import { auth, AuthResult, extractResourceMetadataUrl, OAuthClientProvider, UnauthorizedError } from "./auth.js"; import { EventSourceParserStream } from "eventsource-parser/stream"; -export type FetchLike = (url: string | URL, init?: RequestInit) => Promise; - // Default reconnection options for StreamableHTTP connections const DEFAULT_STREAMABLE_HTTP_RECONNECTION_OPTIONS: StreamableHTTPReconnectionOptions = { initialReconnectionDelay: 1000, @@ -25,7 +23,7 @@ export class StreamableHTTPError extends Error { /** * Options for starting or authenticating an SSE connection */ -interface StartSSEOptions { +export interface StartSSEOptions { /** * The resumption token used to continue long-running requests that were interrupted. * @@ -260,15 +258,15 @@ const response = await (this._fetch ?? fetch)(this._url, { private _normalizeHeaders(headers: HeadersInit | undefined): Record { if (!headers) return {}; - + if (headers instanceof Headers) { return Object.fromEntries(headers.entries()); } - + if (Array.isArray(headers)) { return Object.fromEntries(headers); } - + return { ...headers as Record }; } diff --git a/src/shared/transport.ts b/src/shared/transport.ts index 96b291fa..386b6bae 100644 --- a/src/shared/transport.ts +++ b/src/shared/transport.ts @@ -1,10 +1,12 @@ import { JSONRPCMessage, MessageExtraInfo, RequestId } from "../types.js"; +export type FetchLike = (url: string | URL, init?: RequestInit) => Promise; + /** * Options for sending a JSON-RPC message. */ export type TransportSendOptions = { - /** + /** * If present, `relatedRequestId` is used to indicate to the transport which incoming request to associate this outgoing message with. */ relatedRequestId?: RequestId; @@ -38,7 +40,7 @@ export interface Transport { /** * Sends a JSON-RPC message (request or response). - * + * * If present, `relatedRequestId` is used to indicate to the transport which incoming request to associate this outgoing message with. */ send(message: JSONRPCMessage, options?: TransportSendOptions): Promise; @@ -64,9 +66,9 @@ export interface Transport { /** * Callback for when a message (request or response) is received over the connection. - * + * * Includes the requestInfo and authInfo if the transport is authenticated. - * + * * The requestInfo can be used to get the original request information (headers, etc.) */ onmessage?: (message: JSONRPCMessage, extra?: MessageExtraInfo) => void;