@@ -36,6 +36,7 @@ export type ResolvedRunConfig = {
3636export type McpRuntime = {
3737 servers : PolicyMcpClientConfig [ ] ;
3838 toolNames : string [ ] ;
39+ skipped ?: { id : string ; reason : string } [ ] ;
3940} ;
4041
4142type ResolveRunConfigInput = {
@@ -110,6 +111,20 @@ export const resolveRunConfig = (input: ResolveRunConfigInput): ResolvedRunConfi
110111 input . userId ,
111112 input . workspaceId
112113 ) ;
114+ if ( mcpRuntime . skipped && mcpRuntime . skipped . length > 0 ) {
115+ const skippedIds = new Set ( mcpRuntime . skipped . map ( ( entry ) => entry . id ) ) ;
116+ effectiveRunConfig . enabledMcpServerIds = effectiveRunConfig . enabledMcpServerIds . filter (
117+ ( id ) => ! skippedIds . has ( id )
118+ ) ;
119+ effectiveRunConfig . unavailableResources = [
120+ ...( effectiveRunConfig . unavailableResources ?? [ ] ) ,
121+ ...mcpRuntime . skipped . map ( ( entry ) => ( {
122+ kind : "mcp-server" as const ,
123+ id : entry . id ,
124+ reason : entry . reason
125+ } ) )
126+ ] ;
127+ }
113128 enforceSkillMcpPolicy ( skillSelection . effectiveToolPolicy . allowedTools , mcpRuntime . toolNames ) ;
114129
115130 return {
@@ -441,78 +456,112 @@ const resolveMcpRuntime = (
441456) : McpRuntime => {
442457 const usedNames = new Set < string > ( ) ;
443458 const toolNames : string [ ] = [ ] ;
444- const servers = serverIds . map ( ( id ) : PolicyMcpClientConfig => {
445- const resource = metadataStore . configResources . get ( {
446- id,
447- workspace_id : workspaceId ,
448- user_id : userId ,
449- kind : "mcp-server"
450- } ) ;
451- const transport = stringRecordValue ( resource . payload , "transport" ) ?? "streamable-http" ;
452- if ( transport !== "streamable-http" && transport !== "sse" && transport !== "stdio" ) {
453- throw new Error ( `MCP_TRANSPORT_UNSUPPORTED:${ id } :${ transport } ` ) ;
459+ const servers : PolicyMcpClientConfig [ ] = [ ] ;
460+ const skipped : { id : string ; reason : string } [ ] = [ ] ;
461+ for ( const id of serverIds ) {
462+ try {
463+ servers . push (
464+ buildMcpServerConfig ( {
465+ id,
466+ metadataStore,
467+ usedNames,
468+ toolNames,
469+ userId,
470+ workspaceId
471+ } )
472+ ) ;
473+ } catch ( error ) {
474+ skipped . push ( {
475+ id,
476+ reason : error instanceof Error ? error . message : String ( error )
477+ } ) ;
454478 }
455- const urlOrCommand = stringRecordValue ( resource . payload , "serverUrl" ) ?? stringRecordValue ( resource . payload , "url" ) ;
456- if ( ! urlOrCommand ) {
457- throw new Error ( `MCP_SERVER_URL_REQUIRED:${ id } ` ) ;
479+ }
480+ return {
481+ servers,
482+ toolNames,
483+ ...( skipped . length > 0 ? { skipped } : { } )
484+ } ;
485+ } ;
486+
487+ const buildMcpServerConfig = ( input : {
488+ id : string ;
489+ metadataStore : MetadataStore ;
490+ usedNames : Set < string > ;
491+ toolNames : string [ ] ;
492+ userId : string ;
493+ workspaceId : string ;
494+ } ) : PolicyMcpClientConfig => {
495+ const { id, metadataStore, usedNames, toolNames, userId, workspaceId } = input ;
496+ const resource = metadataStore . configResources . get ( {
497+ id,
498+ workspace_id : workspaceId ,
499+ user_id : userId ,
500+ kind : "mcp-server"
501+ } ) ;
502+ const transport = stringRecordValue ( resource . payload , "transport" ) ?? "streamable-http" ;
503+ if ( transport !== "streamable-http" && transport !== "sse" && transport !== "stdio" ) {
504+ throw new Error ( `MCP_TRANSPORT_UNSUPPORTED:${ id } :${ transport } ` ) ;
505+ }
506+ const urlOrCommand = stringRecordValue ( resource . payload , "serverUrl" ) ?? stringRecordValue ( resource . payload , "url" ) ;
507+ if ( ! urlOrCommand ) {
508+ throw new Error ( `MCP_SERVER_URL_REQUIRED:${ id } ` ) ;
509+ }
510+ const manifest = resource . payload . toolManifest ;
511+ if ( ! Array . isArray ( manifest ) ) {
512+ throw new Error ( `MCP_TOOL_MANIFEST_REQUIRED:${ id } ` ) ;
513+ }
514+ const toolAllowlist = stringArrayRecordValue ( resource . payload , "toolAllowlist" )
515+ ?? csvRecordValue ( resource . payload , "toolAllowlist" ) ;
516+ manifest . forEach ( ( tool ) => {
517+ if ( ! isRecord ( tool ) || typeof tool . name !== "string" ) {
518+ throw new Error ( `MCP_TOOL_MANIFEST_INVALID:${ id } ` ) ;
458519 }
459- const manifest = resource . payload . toolManifest ;
460- if ( ! Array . isArray ( manifest ) ) {
461- throw new Error ( `MCP_TOOL_MANIFEST_REQUIRED:${ id } ` ) ;
520+ if ( ! matchesMcpToolAllowlist ( id , tool . name , toolAllowlist ) ) {
521+ return ;
462522 }
463- const toolAllowlist = stringArrayRecordValue ( resource . payload , "toolAllowlist" )
464- ?? csvRecordValue ( resource . payload , "toolAllowlist" ) ;
465- manifest . forEach ( ( tool ) => {
466- if ( ! isRecord ( tool ) || typeof tool . name !== "string" ) {
467- throw new Error ( `MCP_TOOL_MANIFEST_INVALID:${ id } ` ) ;
468- }
469- if ( ! matchesMcpToolAllowlist ( id , tool . name , toolAllowlist ) ) {
470- return ;
471- }
472- const baseName = `mcp__${ sanitizeMcpName ( id ) } __${ sanitizeMcpName ( tool . name ) } ` ;
473- let resolved = baseName . slice ( 0 , 64 ) ;
474- let suffix = 1 ;
475- while ( usedNames . has ( resolved ) ) {
476- const marker = `_${ suffix } ` ;
477- resolved = `${ baseName . slice ( 0 , 64 - marker . length ) } ${ marker } ` ;
478- suffix += 1 ;
479- }
480- usedNames . add ( resolved ) ;
481- toolNames . push ( resolved ) ;
482- } ) ;
483- const secret = resource . secret_ref
484- ? metadataStore . secrets . get ( { ref : resource . secret_ref , workspace_id : workspaceId , user_id : userId } )
485- : { } ;
486- const headers = resolveMcpHeaders ( resource . payload , secret ) ;
487- const timeoutMs = numericRecordValue ( resource . payload , "timeoutMs" ) ?? numericRecordValue ( resource . payload , "timeout_ms" ) ;
488- const common = {
489- serverId : id ,
490- ...( toolAllowlist && toolAllowlist . length > 0 ? { toolAllowlist } : { } ) ,
491- ...( timeoutMs !== undefined ? { timeoutMs : Math . max ( 1000 , Math . min ( 10 * 60 * 1000 , Math . floor ( timeoutMs ) ) ) } : { } )
492- } ;
493- if ( transport === "stdio" ) {
494- const stdio = resolveStdioCommand ( resource . payload , urlOrCommand ) ;
495- const cwd = stringRecordValue ( resource . payload , "cwd" ) ;
496- const env = recordStringMapValue ( resource . payload . env ) ;
497- return {
498- ...common ,
499- type : "stdio" ,
500- command : stdio . command ,
501- ...( stdio . args . length > 0 ? { args : stdio . args } : { } ) ,
502- ...( cwd ? { cwd } : { } ) ,
503- ...( env ? { env } : { } )
504- } ;
523+ const baseName = `mcp__${ sanitizeMcpName ( id ) } __${ sanitizeMcpName ( tool . name ) } ` ;
524+ let resolved = baseName . slice ( 0 , 64 ) ;
525+ let suffix = 1 ;
526+ while ( usedNames . has ( resolved ) ) {
527+ const marker = `_${ suffix } ` ;
528+ resolved = `${ baseName . slice ( 0 , 64 - marker . length ) } ${ marker } ` ;
529+ suffix += 1 ;
505530 }
531+ usedNames . add ( resolved ) ;
532+ toolNames . push ( resolved ) ;
533+ } ) ;
534+ const secret = resource . secret_ref
535+ ? metadataStore . secrets . get ( { ref : resource . secret_ref , workspace_id : workspaceId , user_id : userId } )
536+ : { } ;
537+ const headers = resolveMcpHeaders ( resource . payload , secret ) ;
538+ const timeoutMs = numericRecordValue ( resource . payload , "timeoutMs" ) ?? numericRecordValue ( resource . payload , "timeout_ms" ) ;
539+ const common = {
540+ serverId : id ,
541+ ...( toolAllowlist && toolAllowlist . length > 0 ? { toolAllowlist } : { } ) ,
542+ ...( timeoutMs !== undefined ? { timeoutMs : Math . max ( 1000 , Math . min ( 10 * 60 * 1000 , Math . floor ( timeoutMs ) ) ) } : { } )
543+ } ;
544+ if ( transport === "stdio" ) {
545+ const stdio = resolveStdioCommand ( resource . payload , urlOrCommand ) ;
546+ const cwd = stringRecordValue ( resource . payload , "cwd" ) ;
547+ const env = recordStringMapValue ( resource . payload . env ) ;
506548 return {
507- type : transport === "streamable-http" ? "http" : "sse" ,
508- url : urlOrCommand ,
509- serverId : id ,
510- ...( toolAllowlist && toolAllowlist . length > 0 ? { toolAllowlist } : { } ) ,
511- ...( timeoutMs !== undefined ? { timeoutMs : Math . max ( 1000 , Math . min ( 10 * 60 * 1000 , Math . floor ( timeoutMs ) ) ) } : { } ) ,
512- ...( Object . keys ( headers ) . length > 0 ? { headers } : { } )
549+ ... common ,
550+ type : "stdio" ,
551+ command : stdio . command ,
552+ ...( stdio . args . length > 0 ? { args : stdio . args } : { } ) ,
553+ ...( cwd ? { cwd } : { } ) ,
554+ ...( env ? { env } : { } )
513555 } ;
514- } ) ;
515- return { servers, toolNames } ;
556+ }
557+ return {
558+ type : transport === "streamable-http" ? "http" : "sse" ,
559+ url : urlOrCommand ,
560+ serverId : id ,
561+ ...( toolAllowlist && toolAllowlist . length > 0 ? { toolAllowlist } : { } ) ,
562+ ...( timeoutMs !== undefined ? { timeoutMs : Math . max ( 1000 , Math . min ( 10 * 60 * 1000 , Math . floor ( timeoutMs ) ) ) } : { } ) ,
563+ ...( Object . keys ( headers ) . length > 0 ? { headers } : { } )
564+ } ;
516565} ;
517566
518567const resolveMcpHeaders = (
0 commit comments