fix(client): treat HTTP 401/403 on the negotiation probe as auth failures, not legacy evidence (#2564)

This commit is contained in:
Felix Weinberger 2026-07-28 00:41:02 +01:00 • committed by GitHub
commit faa7e2b470
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -17,7 +17,6 @@ import {
Client,
LOG_LEVEL_META_KEY,
ProtocolError,
SdkError,
StreamableHTTPClientTransport,
SUPPORTED_PROTOCOL_VERSIONS,
UnauthorizedError
@ -25,7 +24,6 @@ import {
import { StdioClientTransport } from '@modelcontextprotocol/client/stdio';
import type { ChatMessage, ContentPart, GenerateResult, LLMProvider, ToolCall, ToolDefinition } from '../providers/provider';
import { isRecord } from '../providers/provider';
import { completeAuthorizationWithBrowser, createOAuthProvider, findCallbackPort, isSafeBrowserUrl } from './auth';
import type { CliClientConfig, ServerConfig } from './config';
import { isHttpServer } from './config';
@ -100,16 +98,6 @@ export function resolveVersionOptions(legacy: boolean, protocolVersion?: string)
return { versionNegotiation: { mode: { pin: protocolVersion } } };
}
function unwrapUnauthorized(error: unknown): UnauthorizedError | undefined {
if (error instanceof UnauthorizedError) return error;
// Under versionNegotiation 'auto', a connect-time 401 surfaces as
// SdkError(EraNegotiationFailed) with the UnauthorizedError in error.data.cause.
if (error instanceof SdkError && isRecord(error.data) && error.data.cause instanceof UnauthorizedError) {
return error.data.cause;
}
return undefined;
}
function samplingContentToParts(content: CreateMessageRequest['params']['messages'][number]['content']): ContentPart[] {
const blocks = Array.isArray(content) ? content : [content];
const parts: ContentPart[] = [];
@ -488,7 +476,9 @@ export class McpHost {
try {
await client.connect(httpTransport);
} catch (error) {
if (!unwrapUnauthorized(error)) throw error;
// A connect-time 401 propagates UnauthorizedError unchanged
// in every negotiation mode, including the probing ones.
if (!(error instanceof UnauthorizedError)) throw error;
const finishTransport = httpTransport;
const authorized = await completeAuthorizationWithBrowser({
serverName: name,