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 : string [ ] = [
9+ 'ECONNREFUSED' ,
10+ 'ENOTFOUND' ,
11+ 'ETIMEDOUT' ,
12+ 'ENETUNREACH' ,
13+ 'EHOSTUNREACH' ,
14+ ] ;
15+
16+ function isServerUnreachable ( error : Error ) : boolean {
17+ const { code } = error as NodeJS . ErrnoException ;
18+
19+ return ! ! code && UNREACHABLE_ERROR_CODES . includes ( code ) ;
20+ }
21+
22+ function getLogLevelForError ( error : Error ) : LoggerLevel {
23+ return isServerUnreachable ( error ) ? 'Warn' : 'Error' ;
24+ }
25+
826export type McpConfiguration = {
927 configs : MultiServerMCPClient [ 'config' ] [ 'mcpServers' ] ;
1028} & Omit < MultiServerMCPClient [ 'config' ] , 'mcpServers' > ;
@@ -39,7 +57,8 @@ export default class McpClient {
3957 ) ;
4058 this . tools . push ( ...extendedTools ) ;
4159 } catch ( error ) {
42- this . logger ?.( 'Error' , `Error loading tools for ${ name } ` , error as Error ) ;
60+ const logLevel = getLogLevelForError ( error as Error ) ;
61+ this . logger ?.( logLevel , `Error loading tools for ${ name } ` , error as Error ) ;
4362 errors . push ( { server : name , error : error as Error } ) ;
4463 }
4564 } ) ,
@@ -48,8 +67,10 @@ export default class McpClient {
4867 // Surface partial failures to provide better feedback
4968 if ( errors . length > 0 ) {
5069 const errorMessage = errors . map ( e => `${ e . server } : ${ e . error . message } ` ) . join ( '; ' ) ;
70+ const allConnectionErrors = errors . every ( e => isServerUnreachable ( e . error ) ) ;
71+ const summaryLogLevel = allConnectionErrors ? 'Warn' : 'Error' ;
5172 this . logger ?.(
52- 'Error' ,
73+ summaryLogLevel ,
5374 `Failed to load tools from ${ errors . length } /${ Object . keys ( this . mcpClients ) . length } ` +
5475 `MCP server(s): ${ errorMessage } ` ,
5576 ) ;
@@ -72,7 +93,8 @@ export default class McpClient {
7293 await this . closeConnections ( ) ;
7394 } catch ( cleanupError ) {
7495 // 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 ) ;
96+ const logLevel = getLogLevelForError ( cleanupError as Error ) ;
97+ this . logger ?.( logLevel , 'Error during test connection cleanup' , cleanupError as Error ) ;
7698 }
7799 }
78100 }
@@ -88,14 +110,16 @@ export default class McpClient {
88110
89111 if ( failures . length > 0 ) {
90112 failures . forEach ( ( { name, result } ) => {
91- this . logger ?.(
92- 'Error' ,
93- `Failed to close MCP connection for ${ name } ` ,
94- ( result as PromiseRejectedResult ) . reason ,
95- ) ;
113+ const error = ( result as PromiseRejectedResult ) . reason ;
114+ const logLevel = getLogLevelForError ( error ) ;
115+ this . logger ?.( logLevel , `Failed to close MCP connection for ${ name } ` , error ) ;
96116 } ) ;
117+ const allConnectionErrors = failures . every ( ( { result } ) =>
118+ isServerUnreachable ( ( result as PromiseRejectedResult ) . reason ) ,
119+ ) ;
120+ const summaryLogLevel = allConnectionErrors ? 'Warn' : 'Error' ;
97121 this . logger ?.(
98- 'Error' ,
122+ summaryLogLevel ,
99123 `Failed to close ${ failures . length } /${ results . length } MCP connections. ` +
100124 `This may result in resource leaks.` ,
101125 ) ;
0 commit comments