1- import type { Logger } from '@forestadmin/datasource-toolkit' ;
1+ import type { Logger , LoggerLevel } from '@forestadmin/datasource-toolkit' ;
22
33import { MultiServerMCPClient } from '@langchain/mcp-adapters' ;
44
55import { McpConnectionError } from './types/errors' ;
66import McpServerRemoteTool from './types/mcp-server-remote-tool' ;
77
8+ const UNREACHABLE_ERROR_CODES = [
9+ 'ECONNREFUSED' ,
10+ 'ENOTFOUND' ,
11+ 'ETIMEDOUT' ,
12+ 'ENETUNREACH' ,
13+ 'EHOSTUNREACH' ,
14+ ] ;
15+
16+ function isServerUnreachable ( error : Error ) : boolean {
17+ const errorCode = ( error as NodeJS . ErrnoException ) . code ;
18+
19+ if ( errorCode && UNREACHABLE_ERROR_CODES . includes ( errorCode ) ) {
20+ return true ;
21+ }
22+
23+ const { message } = error ;
24+
25+ return UNREACHABLE_ERROR_CODES . some ( code => message . includes ( code ) ) ;
26+ }
27+
28+ function getLogLevelForError ( error : Error ) : LoggerLevel {
29+ return isServerUnreachable ( error ) ? 'Warn' : 'Error' ;
30+ }
31+
832export type McpConfiguration = {
933 configs : MultiServerMCPClient [ 'config' ] [ 'mcpServers' ] ;
1034} & Omit < MultiServerMCPClient [ 'config' ] , 'mcpServers' > ;
@@ -39,7 +63,8 @@ export default class McpClient {
3963 ) ;
4064 this . tools . push ( ...extendedTools ) ;
4165 } catch ( error ) {
42- this . logger ?.( 'Error' , `Error loading tools for ${ name } ` , error as Error ) ;
66+ const logLevel = getLogLevelForError ( error as Error ) ;
67+ this . logger ?.( logLevel , `Error loading tools for ${ name } ` , error as Error ) ;
4368 errors . push ( { server : name , error : error as Error } ) ;
4469 }
4570 } ) ,
@@ -48,8 +73,10 @@ export default class McpClient {
4873 // Surface partial failures to provide better feedback
4974 if ( errors . length > 0 ) {
5075 const errorMessage = errors . map ( e => `${ e . server } : ${ e . error . message } ` ) . join ( '; ' ) ;
76+ const allConnectionErrors = errors . every ( e => isServerUnreachable ( e . error ) ) ;
77+ const summaryLogLevel = allConnectionErrors ? 'Warn' : 'Error' ;
5178 this . logger ?.(
52- 'Error' ,
79+ summaryLogLevel ,
5380 `Failed to load tools from ${ errors . length } /${ Object . keys ( this . mcpClients ) . length } ` +
5481 `MCP server(s): ${ errorMessage } ` ,
5582 ) ;
@@ -72,7 +99,8 @@ export default class McpClient {
7299 await this . closeConnections ( ) ;
73100 } catch ( cleanupError ) {
74101 // Log but don't throw - we don't want to mask the original connection error
75- this . logger ?.( 'Error' , 'Error during test connection cleanup' , cleanupError as Error ) ;
102+ const logLevel = getLogLevelForError ( cleanupError as Error ) ;
103+ this . logger ?.( logLevel , 'Error during test connection cleanup' , cleanupError as Error ) ;
76104 }
77105 }
78106 }
@@ -88,14 +116,16 @@ export default class McpClient {
88116
89117 if ( failures . length > 0 ) {
90118 failures . forEach ( ( { name, result } ) => {
91- this . logger ?.(
92- 'Error' ,
93- `Failed to close MCP connection for ${ name } ` ,
94- ( result as PromiseRejectedResult ) . reason ,
95- ) ;
119+ const error = ( result as PromiseRejectedResult ) . reason ;
120+ const logLevel = getLogLevelForError ( error ) ;
121+ this . logger ?.( logLevel , `Failed to close MCP connection for ${ name } ` , error ) ;
96122 } ) ;
123+ const allConnectionErrors = failures . every ( ( { result } ) =>
124+ isServerUnreachable ( ( result as PromiseRejectedResult ) . reason ) ,
125+ ) ;
126+ const summaryLogLevel = allConnectionErrors ? 'Warn' : 'Error' ;
97127 this . logger ?.(
98- 'Error' ,
128+ summaryLogLevel ,
99129 `Failed to close ${ failures . length } /${ results . length } MCP connections. ` +
100130 `This may result in resource leaks.` ,
101131 ) ;
0 commit comments