@@ -5,7 +5,10 @@ import {
55 SseError ,
66} from "@modelcontextprotocol/sdk/client/sse.js" ;
77import { 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" ;
912import {
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