From fa780fa85be93718f16a5f710a64bfb2ce7ec89b Mon Sep 17 00:00:00 2001 From: lanxevo3 Date: Thu, 26 Mar 2026 16:55:19 -0500 Subject: [PATCH 1/2] fix(auth): enforce form-urlencoded Content-Type for token endpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OAuth 2.1 §3.2 requires token endpoint requests to use application/x-www-form-urlencoded regardless of grant type. Add an explicit header.set() call immediately before the fetch in executeTokenRequest() to prevent any addClientAuthentication implementation from accidentally overriding the Content-Type. Fixes modelcontextprotocol/inspector#1160 --- packages/client/src/client/auth.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/client/src/client/auth.ts b/packages/client/src/client/auth.ts index 1a021be18..0391e35e9 100644 --- a/packages/client/src/client/auth.ts +++ b/packages/client/src/client/auth.ts @@ -1439,6 +1439,10 @@ export async function executeTokenRequest( applyClientAuthentication(authMethod, clientInformation as OAuthClientInformation, headers, tokenRequestParams); } + // Ensure Content-Type is always form-urlencoded for the token endpoint (OAuth 2.1 §3.2). + // Some addClientAuthentication implementations may have inadvertently set a different value. + headers.set('Content-Type', 'application/x-www-form-urlencoded'); + const response = await (fetchFn ?? fetch)(tokenUrl, { method: 'POST', headers, From 66d55db66e93f44824a130c27d6c575162845b4b Mon Sep 17 00:00:00 2001 From: lanxevo3 Date: Sat, 28 Mar 2026 15:41:16 -0500 Subject: [PATCH 2/2] fix(server): add re-entrancy guard to close() to prevent stack overflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When close() is called while cleanup() callbacks are still running (e.g. a cleanup function that triggers onclose which calls close() again), the transport enters infinite recursion causing RangeError: Maximum call stack size exceeded — especially acute when multiple transports close simultaneously. The fix adds a _closed boolean flag checked at the top of close(). If the transport is already closing, subsequent calls return immediately instead of recursing. This is safe because close() is idempotent. Also applies to the idempotent close() contract: calling close() multiple times should have no additional effect after the first call. Fixes #1699. --- packages/server/src/server/streamableHttp.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/server/src/server/streamableHttp.ts b/packages/server/src/server/streamableHttp.ts index 31053f35c..f77c71a89 100644 --- a/packages/server/src/server/streamableHttp.ts +++ b/packages/server/src/server/streamableHttp.ts @@ -230,6 +230,7 @@ export class WebStandardStreamableHTTPServerTransport implements Transport { private _requestResponseMap: Map = new Map(); private _initialized: boolean = false; private _enableJsonResponse: boolean = false; + private _closed: boolean = false; private _standaloneSseStreamId: string = '_GET_stream'; private _eventStore?: EventStore; private _onsessioninitialized?: (sessionId: string) => void | Promise; @@ -900,6 +901,9 @@ export class WebStandardStreamableHTTPServerTransport implements Transport { } async close(): Promise { + if (this._closed) return; + this._closed = true; + // Close all SSE connections for (const { cleanup } of this._streamMapping.values()) { cleanup();