Skip to content

Commit 6f6c952

Browse files
committed
fix: MCP oauth http error code 405
1 parent e75c19e commit 6f6c952

File tree

4 files changed

+400
-58
lines changed

4 files changed

+400
-58
lines changed

extensions/cli/src/configLoader.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ async function loadFromSource(
208208
}
209209
} catch (error) {
210210
// If we're trying user assistants and it fails, fall back to default agent
211-
if (source.type === "user-assistant") {
211+
if (source.type === "user-assistant" || source.type === "cli-flag") {
212212
console.warn(
213213
chalk.yellow(
214214
"Failed to load user assistants, falling back to default agent",

extensions/cli/src/services/MCPService.ts

Lines changed: 48 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ import {
55
SseError,
66
} from "@modelcontextprotocol/sdk/client/sse.js";
77
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
8-
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
8+
import {
9+
StreamableHTTPClientTransport,
10+
StreamableHTTPError,
11+
} from "@modelcontextprotocol/sdk/client/streamableHttp.js";
912
import {
1013
HttpMcpServer,
1114
SseMcpServer,
@@ -26,10 +29,16 @@ import {
2629
SERVICE_NAMES,
2730
} from "./types.js";
2831

29-
function is401Error(error: unknown) {
32+
// 405 is technically "not allowed" but some servers like Sanity don't return the correct error
33+
// Since we're using mcp library there's a good chance 405 means auth issue and doesn't hurt to retry with auth
34+
function isAuthError(error: unknown) {
3035
return (
3136
(error instanceof SseError && error.code === 401) ||
37+
(error instanceof SseError && error.code === 405) ||
38+
(error instanceof StreamableHTTPError && error.code === 401) ||
39+
(error instanceof StreamableHTTPError && error.code === 405) ||
3240
(error instanceof Error && error.message.includes("401")) ||
41+
(error instanceof Error && error.message.includes("405")) ||
3342
(error instanceof Error && error.message.includes("Unauthorized"))
3443
);
3544
}
@@ -53,7 +62,7 @@ export class MCPService
5362
private assistant: AssistantConfig | null = null;
5463
private isShuttingDown = false;
5564
private isHeadless: boolean | undefined;
56-
private mcpTokenCache: Map<string, string> = new Map();
65+
private apiKeyCache: Map<string, string> = new Map();
5766

5867
getDependencies(): string[] {
5968
return [SERVICE_NAMES.CONFIG, SERVICE_NAMES.AUTH];
@@ -208,7 +217,7 @@ export class MCPService
208217
return await operation();
209218
} catch (error: unknown) {
210219
// If not a 401 error, rethrow
211-
if (!is401Error(error)) {
220+
if (!isAuthError(error)) {
212221
throw error;
213222
}
214223

@@ -228,24 +237,22 @@ export class MCPService
228237
const authConfig = loadAuthConfig();
229238

230239
// Clear cached token since it's invalid
231-
this.mcpTokenCache.delete(serverName);
240+
this.apiKeyCache.delete(serverName);
232241

233242
// Fetch OAuth token from backend
234-
const identifier = serverConfig.name;
235243
const organizationSlug = authConfig?.organizationId;
236244

237245
let token: string | null = null;
238246
try {
239-
const params = new URLSearchParams({ identifier });
247+
const params = new URLSearchParams({
248+
url: serverConfig.url,
249+
});
240250
if (organizationSlug) {
241251
params.set("organizationSlug", organizationSlug);
242252
}
243-
244-
logger.debug("Fetching OAuth token for MCP server on 401", {
245-
name: serverName,
246-
identifier,
247-
organizationSlug,
248-
});
253+
if (serverConfig.sourceSlug) {
254+
params.set("slug", serverConfig.sourceSlug);
255+
}
249256

250257
const response = await get<{
251258
configured: boolean;
@@ -257,7 +264,7 @@ export class MCPService
257264

258265
if (response.data.hasCredentials && response.data.accessToken) {
259266
token = response.data.accessToken;
260-
this.mcpTokenCache.set(serverName, token);
267+
this.apiKeyCache.set(serverName, token);
261268
logger.debug("Successfully retrieved OAuth token for MCP server", {
262269
name: serverName,
263270
});
@@ -283,11 +290,7 @@ export class MCPService
283290
throw error;
284291
}
285292

286-
// Update the server config with new token and retry
287-
serverConfig.apiKey = token;
288-
logger.debug("Retrying operation with refreshed OAuth token", {
289-
name: serverName,
290-
});
293+
this.apiKeyCache.set(serverConfig.name, token);
291294

292295
return await operation();
293296
}
@@ -544,17 +547,35 @@ export class MCPService
544547

545548
try {
546549
await this.withTokenRefresh(serverConfig.name, async () => {
550+
if (serverConfig.apiKey && !this.apiKeyCache.get(serverConfig.name)) {
551+
this.apiKeyCache.set(serverConfig.name, serverConfig.apiKey);
552+
}
547553
if (serverConfig.type === "sse") {
548554
const transport = this.constructSseTransport(serverConfig);
549555
await client.connect(transport, {});
550556
} else if (serverConfig.type === "streamable-http") {
551557
const transport = this.constructHttpTransport(serverConfig);
552558
await client.connect(transport, {});
559+
} else {
560+
try {
561+
const transport = this.constructHttpTransport(serverConfig);
562+
await client.connect(transport, {});
563+
} catch (e) {
564+
logger.debug(
565+
"MCP Connection: http connection failed, falling back to sse connection",
566+
{
567+
name: serverConfig.name,
568+
error: getErrorString(e),
569+
},
570+
);
571+
const transport = this.constructSseTransport(serverConfig);
572+
await client.connect(transport, {});
573+
}
553574
}
554575
});
555576
} catch (error: unknown) {
556577
// If token refresh didn't work and it's a 401, fall back to mcp-remote
557-
if (is401Error(error) && !this.isHeadless) {
578+
if (isAuthError(error) && !this.isHeadless) {
558579
logger.debug("Falling back to mcp-remote after 401 error", {
559580
name: serverConfig.name,
560581
});
@@ -571,38 +592,6 @@ export class MCPService
571592
throw error;
572593
}
573594
}
574-
575-
if (typeof serverConfig.type === "undefined") {
576-
// Try HTTP first, then SSE
577-
try {
578-
await this.withTokenRefresh(serverConfig.name, async () => {
579-
const transport = this.constructHttpTransport(serverConfig);
580-
await client.connect(transport, {});
581-
});
582-
} catch (httpError) {
583-
logger.debug(
584-
"MCP Connection: http connection failed, falling back to sse connection",
585-
{
586-
name: serverConfig.name,
587-
error: getErrorString(httpError),
588-
},
589-
);
590-
try {
591-
await this.withTokenRefresh(serverConfig.name, async () => {
592-
const transport = this.constructSseTransport(serverConfig);
593-
await client.connect(transport, {});
594-
});
595-
} catch (sseError) {
596-
throw new Error(
597-
`MCP config with URL and no type specified failed both SSE and HTTP connection: ${sseError instanceof Error ? sseError.message : String(sseError)}`,
598-
);
599-
}
600-
}
601-
} else if (
602-
!["streamable-http", "sse", "stdio"].includes(serverConfig.type)
603-
) {
604-
throw new Error(`Unsupported transport type: ${serverConfig.type}`);
605-
}
606595
}
607596

608597
return client;
@@ -611,11 +600,12 @@ export class MCPService
611600
private constructSseTransport(
612601
serverConfig: SseMcpServer,
613602
): SSEClientTransport {
603+
const apiKey = this.apiKeyCache.get(serverConfig.name);
614604
// Merge apiKey into headers if provided
615605
const headers = {
616606
...serverConfig.requestOptions?.headers,
617-
...(serverConfig.apiKey && {
618-
Authorization: `Bearer ${serverConfig.apiKey}`,
607+
...(apiKey && {
608+
Authorization: `Bearer ${apiKey}`,
619609
}),
620610
};
621611

@@ -637,10 +627,12 @@ export class MCPService
637627
serverConfig: HttpMcpServer,
638628
): StreamableHTTPClientTransport {
639629
// Merge apiKey into headers if provided
630+
const apiKey = this.apiKeyCache.get(serverConfig.name);
631+
640632
const headers = {
641633
...serverConfig.requestOptions?.headers,
642-
...(serverConfig.apiKey && {
643-
Authorization: `Bearer ${serverConfig.apiKey}`,
634+
...(apiKey && {
635+
Authorization: `Bearer ${apiKey}`,
644636
}),
645637
};
646638

0 commit comments

Comments
 (0)