From 60160d12958fb3e497dc65a99effd171e23bd391 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Tue, 13 May 2025 16:21:26 -0400 Subject: [PATCH 01/11] Add www-authenticate to Access-Control-Expose-Headers list for responses. This fixes #168 --- server/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/index.ts b/server/src/index.ts index c967b60c..f2a47d38 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -44,7 +44,7 @@ const { values } = parseArgs({ const app = express(); app.use(cors()); app.use((req, res, next) => { - res.header("Access-Control-Expose-Headers", "mcp-session-id"); + res.header("Access-Control-Expose-Headers", ["mcp-session-id", "www-authenticate"]); next(); }); From 6152a89442ca75dc6205289191dca41fb4ae8b1f Mon Sep 17 00:00:00 2001 From: cliffhall Date: Tue, 13 May 2025 16:41:25 -0400 Subject: [PATCH 02/11] prettier --- server/src/index.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/src/index.ts b/server/src/index.ts index f2a47d38..0f9b3404 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -44,7 +44,10 @@ const { values } = parseArgs({ const app = express(); app.use(cors()); app.use((req, res, next) => { - res.header("Access-Control-Expose-Headers", ["mcp-session-id", "www-authenticate"]); + res.header("Access-Control-Expose-Headers", [ + "mcp-session-id", + "WWW-Authenticate", + ]); next(); }); From 4654ff75767e1cd4ee50bf5b9ae18596c5f45d3f Mon Sep 17 00:00:00 2001 From: Cliff Hall Date: Sat, 28 Jun 2025 16:22:49 -0400 Subject: [PATCH 03/11] refactor: set auth header via helper --- server/src/index.ts | 133 ++++++++++++++++++++++++++++---------------- 1 file changed, 85 insertions(+), 48 deletions(-) diff --git a/server/src/index.ts b/server/src/index.ts index 9f12348d..90fbeb40 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -90,6 +90,12 @@ app.use((req, res, next) => { next(); }); +const maybeSetAuthHeader = (res: express.Response, header?: string) => { + if (header) { + res.setHeader("WWW-Authenticate", header); + } +}; + const webAppTransports: Map = new Map(); // Web app transports by web app sessionId const serverTransports: Map = new Map(); // Server Transports by web app sessionId @@ -174,66 +180,85 @@ const authMiddleware = ( next(); }; -const createTransport = async (req: express.Request): Promise => { +const createTransport = async ( + req: express.Request, +): Promise<{ transport: Transport; authHeader?: string }> => { const query = req.query; console.log("Query parameters:", JSON.stringify(query)); + const originalFetch = globalThis.fetch; + let authHeader: string | undefined; + globalThis.fetch = async (...args: Parameters) => { + const response = await originalFetch(...args); + if (response.status === 401 && response.headers.has("WWW-Authenticate")) { + authHeader = response.headers.get("WWW-Authenticate") ?? undefined; + } + return response; + }; + const transportType = query.transportType as string; - if (transportType === "stdio") { - const command = query.command as string; - const origArgs = shellParseArgs(query.args as string) as string[]; - const queryEnv = query.env ? JSON.parse(query.env as string) : {}; - const env = { ...process.env, ...defaultEnvironment, ...queryEnv }; + try { + if (transportType === "stdio") { + const command = query.command as string; + const origArgs = shellParseArgs(query.args as string) as string[]; + const queryEnv = query.env ? JSON.parse(query.env as string) : {}; + const env = { ...process.env, ...defaultEnvironment, ...queryEnv }; - const { cmd, args } = findActualExecutable(command, origArgs); + const { cmd, args } = findActualExecutable(command, origArgs); - console.log(`STDIO transport: command=${cmd}, args=${args}`); + console.log(`STDIO transport: command=${cmd}, args=${args}`); - const transport = new StdioClientTransport({ - command: cmd, - args, - env, - stderr: "pipe", - }); + const transport = new StdioClientTransport({ + command: cmd, + args, + env, + stderr: "pipe", + }); - await transport.start(); - return transport; - } else if (transportType === "sse") { - const url = query.url as string; + await transport.start(); + return { transport, authHeader }; + } else if (transportType === "sse") { + const url = query.url as string; - const headers = getHttpHeaders(req, transportType); + const headers = getHttpHeaders(req, transportType); - console.log( - `SSE transport: url=${url}, headers=${JSON.stringify(headers)}`, - ); + console.log( + `SSE transport: url=${url}, headers=${JSON.stringify(headers)}`, + ); - const transport = new SSEClientTransport(new URL(url), { - eventSourceInit: { - fetch: (url, init) => fetch(url, { ...init, headers }), - }, - requestInit: { - headers, - }, - }); - await transport.start(); - return transport; - } else if (transportType === "streamable-http") { - const headers = getHttpHeaders(req, transportType); - - const transport = new StreamableHTTPClientTransport( - new URL(query.url as string), - { + const transport = new SSEClientTransport(new URL(url), { + eventSourceInit: { + fetch: (url, init) => fetch(url, { ...init, headers }), + }, requestInit: { headers, }, - }, - ); - await transport.start(); - return transport; - } else { - console.error(`Invalid transport type: ${transportType}`); - throw new Error("Invalid transport type specified"); + }); + await transport.start(); + return { transport, authHeader }; + } else if (transportType === "streamable-http") { + const headers = getHttpHeaders(req, transportType); + + const transport = new StreamableHTTPClientTransport( + new URL(query.url as string), + { + requestInit: { + headers, + }, + }, + ); + await transport.start(); + return { transport, authHeader }; + } else { + console.error(`Invalid transport type: ${transportType}`); + throw new Error("Invalid transport type specified"); + } + } catch (error) { + (error as any).authHeader = authHeader; + throw error; + } finally { + globalThis.fetch = originalFetch; } }; @@ -272,8 +297,12 @@ app.post( try { console.log("New StreamableHttp connection request"); try { - serverTransport = await createTransport(req); + const { transport, authHeader } = await createTransport(req); + serverTransport = transport; + maybeSetAuthHeader(res, authHeader); } catch (error) { + const header = (error as any).authHeader; + maybeSetAuthHeader(res, header); if (error instanceof SseError && error.code === 401) { console.error( "Received 401 Unauthorized from MCP server:", @@ -377,9 +406,13 @@ app.get( console.log("New STDIO connection request"); let serverTransport: Transport | undefined; try { - serverTransport = await createTransport(req); + const { transport, authHeader } = await createTransport(req); + serverTransport = transport; console.log("Created server transport"); + maybeSetAuthHeader(res, authHeader); } catch (error) { + const header = (error as any).authHeader; + maybeSetAuthHeader(res, header); if (error instanceof SseError && error.code === 401) { console.error( "Received 401 Unauthorized from MCP server. Authentication failure.", @@ -446,8 +479,12 @@ app.get( ); let serverTransport: Transport | undefined; try { - serverTransport = await createTransport(req); + const { transport, authHeader } = await createTransport(req); + serverTransport = transport; + maybeSetAuthHeader(res, authHeader); } catch (error) { + const header = (error as any).authHeader; + maybeSetAuthHeader(res, header); if (error instanceof SseError && error.code === 401) { console.error( "Received 401 Unauthorized from MCP server. Authentication failure.", From 1795dcf14ee35cb03660b28d836c4055a6a2a980 Mon Sep 17 00:00:00 2001 From: Cliff Hall Date: Sat, 28 Jun 2025 16:53:09 -0400 Subject: [PATCH 04/11] Refine auth header capture --- server/src/index.ts | 62 +++++++++++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/server/src/index.ts b/server/src/index.ts index 90fbeb40..e8182bcc 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -96,6 +96,33 @@ const maybeSetAuthHeader = (res: express.Response, header?: string) => { } }; +interface ErrorWithAuthHeader extends Error { + authHeader?: string; +} + +const captureAuthHeader = async ( + fn: () => Promise, +): Promise<{ result: T; header?: string }> => { + const originalFetch = globalThis.fetch; + let header: string | undefined; + globalThis.fetch = async (...args: Parameters) => { + const response = await originalFetch(...args); + if (response.status === 401 && response.headers.has("WWW-Authenticate")) { + header = response.headers.get("WWW-Authenticate") ?? undefined; + } + return response; + }; + + try { + return { result: await fn(), header }; + } catch (error) { + (error as ErrorWithAuthHeader).authHeader = header; + throw error; + } finally { + globalThis.fetch = originalFetch; + } +}; + const webAppTransports: Map = new Map(); // Web app transports by web app sessionId const serverTransports: Map = new Map(); // Server Transports by web app sessionId @@ -186,19 +213,9 @@ const createTransport = async ( const query = req.query; console.log("Query parameters:", JSON.stringify(query)); - const originalFetch = globalThis.fetch; - let authHeader: string | undefined; - globalThis.fetch = async (...args: Parameters) => { - const response = await originalFetch(...args); - if (response.status === 401 && response.headers.has("WWW-Authenticate")) { - authHeader = response.headers.get("WWW-Authenticate") ?? undefined; - } - return response; - }; - - const transportType = query.transportType as string; + const { result: transport, header } = await captureAuthHeader(async () => { + const transportType = query.transportType as string; - try { if (transportType === "stdio") { const command = query.command as string; const origArgs = shellParseArgs(query.args as string) as string[]; @@ -217,7 +234,7 @@ const createTransport = async ( }); await transport.start(); - return { transport, authHeader }; + return transport; } else if (transportType === "sse") { const url = query.url as string; @@ -236,7 +253,7 @@ const createTransport = async ( }, }); await transport.start(); - return { transport, authHeader }; + return transport; } else if (transportType === "streamable-http") { const headers = getHttpHeaders(req, transportType); @@ -249,17 +266,14 @@ const createTransport = async ( }, ); await transport.start(); - return { transport, authHeader }; + return transport; } else { console.error(`Invalid transport type: ${transportType}`); throw new Error("Invalid transport type specified"); } - } catch (error) { - (error as any).authHeader = authHeader; - throw error; - } finally { - globalThis.fetch = originalFetch; - } + }); + + return { transport, authHeader: header }; }; app.get( @@ -301,7 +315,7 @@ app.post( serverTransport = transport; maybeSetAuthHeader(res, authHeader); } catch (error) { - const header = (error as any).authHeader; + const header = (error as ErrorWithAuthHeader).authHeader; maybeSetAuthHeader(res, header); if (error instanceof SseError && error.code === 401) { console.error( @@ -411,7 +425,7 @@ app.get( console.log("Created server transport"); maybeSetAuthHeader(res, authHeader); } catch (error) { - const header = (error as any).authHeader; + const header = (error as ErrorWithAuthHeader).authHeader; maybeSetAuthHeader(res, header); if (error instanceof SseError && error.code === 401) { console.error( @@ -483,7 +497,7 @@ app.get( serverTransport = transport; maybeSetAuthHeader(res, authHeader); } catch (error) { - const header = (error as any).authHeader; + const header = (error as ErrorWithAuthHeader).authHeader; maybeSetAuthHeader(res, header); if (error instanceof SseError && error.code === 401) { console.error( From 7b1fbb3af89f594bccdfad0ec24f10a94c83495f Mon Sep 17 00:00:00 2001 From: Cliff Hall Date: Sat, 28 Jun 2025 17:20:16 -0400 Subject: [PATCH 05/11] Handle auth headers without global fetch --- server/src/index.ts | 75 ++++++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 39 deletions(-) diff --git a/server/src/index.ts b/server/src/index.ts index e8182bcc..658db4ca 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -96,31 +96,13 @@ const maybeSetAuthHeader = (res: express.Response, header?: string) => { } }; -interface ErrorWithAuthHeader extends Error { +class TransportCreationError extends Error { authHeader?: string; } -const captureAuthHeader = async ( - fn: () => Promise, -): Promise<{ result: T; header?: string }> => { - const originalFetch = globalThis.fetch; - let header: string | undefined; - globalThis.fetch = async (...args: Parameters) => { - const response = await originalFetch(...args); - if (response.status === 401 && response.headers.has("WWW-Authenticate")) { - header = response.headers.get("WWW-Authenticate") ?? undefined; - } - return response; - }; - - try { - return { result: await fn(), header }; - } catch (error) { - (error as ErrorWithAuthHeader).authHeader = header; - throw error; - } finally { - globalThis.fetch = originalFetch; - } +const setAuthHeaderFromError = (res: express.Response, error: unknown) => { + const header = (error as TransportCreationError).authHeader; + maybeSetAuthHeader(res, header); }; const webAppTransports: Map = new Map(); // Web app transports by web app sessionId @@ -213,9 +195,22 @@ const createTransport = async ( const query = req.query; console.log("Query parameters:", JSON.stringify(query)); - const { result: transport, header } = await captureAuthHeader(async () => { - const transportType = query.transportType as string; + const originalFetch = globalThis.fetch; + let authHeader: string | undefined; + const interceptingFetch = async ( + ...args: Parameters + ): Promise => { + const response = await originalFetch(...args); + if (response.status === 401 && response.headers.has("WWW-Authenticate")) { + authHeader = response.headers.get("WWW-Authenticate") ?? undefined; + } + return response; + }; + + const transportType = query.transportType as string; + + try { if (transportType === "stdio") { const command = query.command as string; const origArgs = shellParseArgs(query.args as string) as string[]; @@ -234,7 +229,7 @@ const createTransport = async ( }); await transport.start(); - return transport; + return { transport, authHeader }; } else if (transportType === "sse") { const url = query.url as string; @@ -246,14 +241,14 @@ const createTransport = async ( const transport = new SSEClientTransport(new URL(url), { eventSourceInit: { - fetch: (url, init) => fetch(url, { ...init, headers }), - }, + fetch: (url, init) => interceptingFetch(url, { ...init, headers }), + } as EventSourceInit & { fetch?: typeof fetch }, requestInit: { headers, }, }); await transport.start(); - return transport; + return { transport, authHeader }; } else if (transportType === "streamable-http") { const headers = getHttpHeaders(req, transportType); @@ -262,18 +257,23 @@ const createTransport = async ( { requestInit: { headers, - }, + // Cast to allow non-standard fetch property + fetch: interceptingFetch, + } as RequestInit & { fetch?: typeof fetch }, }, ); await transport.start(); - return transport; + return { transport, authHeader }; } else { console.error(`Invalid transport type: ${transportType}`); throw new Error("Invalid transport type specified"); } - }); - - return { transport, authHeader: header }; + } catch (error) { + (error as TransportCreationError).authHeader = authHeader; + throw error; + } finally { + // nothing to clean up + } }; app.get( @@ -315,8 +315,7 @@ app.post( serverTransport = transport; maybeSetAuthHeader(res, authHeader); } catch (error) { - const header = (error as ErrorWithAuthHeader).authHeader; - maybeSetAuthHeader(res, header); + setAuthHeaderFromError(res, error); if (error instanceof SseError && error.code === 401) { console.error( "Received 401 Unauthorized from MCP server:", @@ -425,8 +424,7 @@ app.get( console.log("Created server transport"); maybeSetAuthHeader(res, authHeader); } catch (error) { - const header = (error as ErrorWithAuthHeader).authHeader; - maybeSetAuthHeader(res, header); + setAuthHeaderFromError(res, error); if (error instanceof SseError && error.code === 401) { console.error( "Received 401 Unauthorized from MCP server. Authentication failure.", @@ -497,8 +495,7 @@ app.get( serverTransport = transport; maybeSetAuthHeader(res, authHeader); } catch (error) { - const header = (error as ErrorWithAuthHeader).authHeader; - maybeSetAuthHeader(res, header); + setAuthHeaderFromError(res, error); if (error instanceof SseError && error.code === 401) { console.error( "Received 401 Unauthorized from MCP server. Authentication failure.", From 33f172a15f52f87e1fec89cbe02ba3a9d44ed800 Mon Sep 17 00:00:00 2001 From: Cliff Hall Date: Sat, 28 Jun 2025 17:29:39 -0400 Subject: [PATCH 06/11] Update server/src/index.ts Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- server/src/index.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/server/src/index.ts b/server/src/index.ts index 658db4ca..276a1c2e 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -101,8 +101,10 @@ class TransportCreationError extends Error { } const setAuthHeaderFromError = (res: express.Response, error: unknown) => { - const header = (error as TransportCreationError).authHeader; - maybeSetAuthHeader(res, header); + if (error && typeof error === "object" && "authHeader" in error) { + const header = (error as { authHeader?: string }).authHeader; + maybeSetAuthHeader(res, header); + } }; const webAppTransports: Map = new Map(); // Web app transports by web app sessionId From fa4e7fb900ee2ff49b8c353bb9df7cbf11137ea9 Mon Sep 17 00:00:00 2001 From: Cliff Hall Date: Sat, 28 Jun 2025 17:30:28 -0400 Subject: [PATCH 07/11] Update server/src/index.ts adding a guard to ensure error is an object before attempting to attach the authHeader property. Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- server/src/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/src/index.ts b/server/src/index.ts index 276a1c2e..867680c2 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -271,7 +271,9 @@ const createTransport = async ( throw new Error("Invalid transport type specified"); } } catch (error) { - (error as TransportCreationError).authHeader = authHeader; + if (error && typeof error === "object") { + (error as TransportCreationError).authHeader = authHeader; + } throw error; } finally { // nothing to clean up From 57448b2b27d3f305f3e4c010f960b0d44867cc63 Mon Sep 17 00:00:00 2001 From: Cliff Hall Date: Sat, 28 Jun 2025 17:53:04 -0400 Subject: [PATCH 08/11] refactor: robust auth header handling --- server/src/index.ts | 148 ++++++++++++++++++++++++++++++-------------- 1 file changed, 100 insertions(+), 48 deletions(-) diff --git a/server/src/index.ts b/server/src/index.ts index 9f12348d..d7ada12f 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -90,6 +90,23 @@ app.use((req, res, next) => { next(); }); +const maybeSetAuthHeader = (res: express.Response, header?: string) => { + if (header) { + res.setHeader("WWW-Authenticate", header); + } +}; + +const setAuthHeaderFromError = (res: express.Response, error: unknown) => { + if ( + error && + typeof error === "object" && + "authHeader" in error && + typeof (error as { authHeader?: unknown }).authHeader === "string" + ) { + maybeSetAuthHeader(res, (error as { authHeader: string }).authHeader); + } +}; + const webAppTransports: Map = new Map(); // Web app transports by web app sessionId const serverTransports: Map = new Map(); // Server Transports by web app sessionId @@ -174,66 +191,92 @@ const authMiddleware = ( next(); }; -const createTransport = async (req: express.Request): Promise => { +const createTransport = async ( + req: express.Request, +): Promise<{ transport: Transport; authHeader?: string }> => { const query = req.query; console.log("Query parameters:", JSON.stringify(query)); + const originalFetch = globalThis.fetch; + let authHeader: string | undefined; + + const interceptingFetch = async ( + ...args: Parameters + ): Promise => { + const response = await originalFetch(...args); + if (response.status === 401 && response.headers.has("WWW-Authenticate")) { + authHeader = response.headers.get("WWW-Authenticate") ?? undefined; + } + return response; + }; + const transportType = query.transportType as string; - if (transportType === "stdio") { - const command = query.command as string; - const origArgs = shellParseArgs(query.args as string) as string[]; - const queryEnv = query.env ? JSON.parse(query.env as string) : {}; - const env = { ...process.env, ...defaultEnvironment, ...queryEnv }; + try { + if (transportType === "stdio") { + const command = query.command as string; + const origArgs = shellParseArgs(query.args as string) as string[]; + const queryEnv = query.env ? JSON.parse(query.env as string) : {}; + const env = { ...process.env, ...defaultEnvironment, ...queryEnv }; - const { cmd, args } = findActualExecutable(command, origArgs); + const { cmd, args } = findActualExecutable(command, origArgs); - console.log(`STDIO transport: command=${cmd}, args=${args}`); + console.log(`STDIO transport: command=${cmd}, args=${args}`); - const transport = new StdioClientTransport({ - command: cmd, - args, - env, - stderr: "pipe", - }); + const transport = new StdioClientTransport({ + command: cmd, + args, + env, + stderr: "pipe", + }); - await transport.start(); - return transport; - } else if (transportType === "sse") { - const url = query.url as string; + await transport.start(); + return { transport, authHeader }; + } else if (transportType === "sse") { + const url = query.url as string; - const headers = getHttpHeaders(req, transportType); + const headers = getHttpHeaders(req, transportType); - console.log( - `SSE transport: url=${url}, headers=${JSON.stringify(headers)}`, - ); + console.log( + `SSE transport: url=${url}, headers=${JSON.stringify(headers)}`, + ); - const transport = new SSEClientTransport(new URL(url), { - eventSourceInit: { - fetch: (url, init) => fetch(url, { ...init, headers }), - }, - requestInit: { - headers, - }, - }); - await transport.start(); - return transport; - } else if (transportType === "streamable-http") { - const headers = getHttpHeaders(req, transportType); - - const transport = new StreamableHTTPClientTransport( - new URL(query.url as string), - { + const transport = new SSEClientTransport(new URL(url), { + eventSourceInit: { + fetch: (url, init) => interceptingFetch(url, { ...init, headers }), + } as EventSourceInit & { fetch?: typeof fetch }, requestInit: { headers, }, - }, - ); - await transport.start(); - return transport; - } else { - console.error(`Invalid transport type: ${transportType}`); - throw new Error("Invalid transport type specified"); + }); + await transport.start(); + return { transport, authHeader }; + } else if (transportType === "streamable-http") { + const headers = getHttpHeaders(req, transportType); + + const transport = new StreamableHTTPClientTransport( + new URL(query.url as string), + { + requestInit: { + headers, + // Cast to allow non-standard fetch property + fetch: interceptingFetch, + } as RequestInit & { fetch?: typeof fetch }, + }, + ); + await transport.start(); + return { transport, authHeader }; + } else { + console.error(`Invalid transport type: ${transportType}`); + throw new Error("Invalid transport type specified"); + } + } catch (error) { + if (error && typeof error === "object") { + (error as { authHeader?: string }).authHeader = authHeader; + } + throw error; + } finally { + // nothing to clean up } }; @@ -272,8 +315,11 @@ app.post( try { console.log("New StreamableHttp connection request"); try { - serverTransport = await createTransport(req); + const { transport, authHeader } = await createTransport(req); + serverTransport = transport; + maybeSetAuthHeader(res, authHeader); } catch (error) { + setAuthHeaderFromError(res, error); if (error instanceof SseError && error.code === 401) { console.error( "Received 401 Unauthorized from MCP server:", @@ -377,9 +423,12 @@ app.get( console.log("New STDIO connection request"); let serverTransport: Transport | undefined; try { - serverTransport = await createTransport(req); + const { transport, authHeader } = await createTransport(req); + serverTransport = transport; console.log("Created server transport"); + maybeSetAuthHeader(res, authHeader); } catch (error) { + setAuthHeaderFromError(res, error); if (error instanceof SseError && error.code === 401) { console.error( "Received 401 Unauthorized from MCP server. Authentication failure.", @@ -446,8 +495,11 @@ app.get( ); let serverTransport: Transport | undefined; try { - serverTransport = await createTransport(req); + const { transport, authHeader } = await createTransport(req); + serverTransport = transport; + maybeSetAuthHeader(res, authHeader); } catch (error) { + setAuthHeaderFromError(res, error); if (error instanceof SseError && error.code === 401) { console.error( "Received 401 Unauthorized from MCP server. Authentication failure.", From 6d47e22c02ead64ee41a537a60622ba559d6f07d Mon Sep 17 00:00:00 2001 From: Cliff Hall Date: Sat, 28 Jun 2025 18:04:22 -0400 Subject: [PATCH 09/11] Update server/src/index.ts Narrow rather than cast for error handling Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- server/src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/index.ts b/server/src/index.ts index d7ada12f..5edad1c3 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -101,9 +101,9 @@ const setAuthHeaderFromError = (res: express.Response, error: unknown) => { error && typeof error === "object" && "authHeader" in error && - typeof (error as { authHeader?: unknown }).authHeader === "string" + typeof error.authHeader === "string" ) { - maybeSetAuthHeader(res, (error as { authHeader: string }).authHeader); + maybeSetAuthHeader(res, error.authHeader); } }; From 16420753eade43ad86e3eef1978eb41dd1899b82 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Mon, 7 Jul 2025 15:35:13 -0400 Subject: [PATCH 10/11] Use SDK version 1.15.0, which allows custom fetch --- package-lock.json | 13 +++++++++---- package.json | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index ce3b7ba7..0de6103b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "@modelcontextprotocol/inspector-cli": "^0.15.0", "@modelcontextprotocol/inspector-client": "^0.15.0", "@modelcontextprotocol/inspector-server": "^0.15.0", - "@modelcontextprotocol/sdk": "^1.13.1", + "@modelcontextprotocol/sdk": "^1.15.0", "concurrently": "^9.0.1", "open": "^10.1.0", "shell-quote": "^1.8.2", @@ -37,6 +37,9 @@ "prettier": "3.3.3", "rimraf": "^6.0.1", "typescript": "^5.4.2" + }, + "engines": { + "node": ">=22.7.5" } }, "cli": { @@ -2006,15 +2009,17 @@ "link": true }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.13.1.tgz", - "integrity": "sha512-8q6+9aF0yA39/qWT/uaIj6zTpC+Qu07DnN/lb9mjoquCJsAh6l3HyYqc9O3t2j7GilseOQOQimLg7W3By6jqvg==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.15.0.tgz", + "integrity": "sha512-67hnl/ROKdb03Vuu0YOr+baKTvf1/5YBHBm9KnZdjdAh8hjt4FRCPD5ucwxGB237sBpzlqQsLy1PFu7z/ekZ9Q==", + "license": "MIT", "dependencies": { "ajv": "^6.12.6", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", diff --git a/package.json b/package.json index 5d5fe52f..cd8f2d24 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "@modelcontextprotocol/inspector-cli": "^0.15.0", "@modelcontextprotocol/inspector-client": "^0.15.0", "@modelcontextprotocol/inspector-server": "^0.15.0", - "@modelcontextprotocol/sdk": "^1.13.1", + "@modelcontextprotocol/sdk": "^1.15.0", "concurrently": "^9.0.1", "open": "^10.1.0", "shell-quote": "^1.8.2", From cf935ad4d23f504335fcceb98fcedb9c25b54eef Mon Sep 17 00:00:00 2001 From: cliffhall Date: Mon, 7 Jul 2025 17:06:48 -0400 Subject: [PATCH 11/11] Correct the location of fetch in transport constructor options --- server/src/index.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/server/src/index.ts b/server/src/index.ts index 5edad1c3..714ea998 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -242,12 +242,10 @@ const createTransport = async ( ); const transport = new SSEClientTransport(new URL(url), { - eventSourceInit: { - fetch: (url, init) => interceptingFetch(url, { ...init, headers }), - } as EventSourceInit & { fetch?: typeof fetch }, requestInit: { headers, }, + fetch: (url, init) => interceptingFetch(url, { ...init, headers }), }); await transport.start(); return { transport, authHeader }; @@ -259,9 +257,8 @@ const createTransport = async ( { requestInit: { headers, - // Cast to allow non-standard fetch property - fetch: interceptingFetch, - } as RequestInit & { fetch?: typeof fetch }, + }, + fetch: (url, init) => interceptingFetch(url, { ...init, headers }), }, ); await transport.start();