break: remove winston in favor of the debug package (#25)

* break: remove winston in favor of the debug package
* fix: add missing test coverage dev dependency
This commit is contained in:
Ben Burns
2025-03-19 18:36:14 +13:00
committed by GitHub
parent 399bf78cd8
commit 72b740ffcf
11 changed files with 734 additions and 576 deletions
+17 -21
View File
@@ -3,7 +3,7 @@
[![npm version](https://img.shields.io/npm/v/@langchain/mcp-adapters.svg)](https://www.npmjs.com/package/@langchain/mcp-adapters)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
This library provides a lightweight wrapper that makes [Anthropic Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) tools compatible with [LangChain.js](https://github.com/langchain-ai/langchainjs) and [LangGraph.js](https://github.com/langchain-ai/langgraphjs).
This library provides a lightweight wrapper that makes[Anthropic Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) tools compatible with [LangChain.js](https://github.com/langchain-ai/langchainjs) and [LangGraph.js](https://github.com/langchain-ai/langgraphjs).
## Features
@@ -25,7 +25,6 @@ This library provides a lightweight wrapper that makes [Anthropic Model Context
- Optimized for OpenAI, Anthropic, and Google models
- 🛠️ **Development Features**
- Comprehensive logging system
- Flexible configuration options
- Robust error handling
@@ -491,33 +490,30 @@ When using in browsers:
### Debug Logging
Logging is disabled by default for optimal performance. Enable logging when needed for diagnostics:
This package makes use of the [debug](https://www.npmjs.com/package/debug) package for debug logging.
```typescript
import { enableLogging, disableLogging } from '@langchain/mcp-adapters';
Logging is disabled by default, and can be enabled by setting the `DEBUG` environment variable as per
the instructions in the debug package.
// Enable logging at info level (default)
enableLogging();
To output all debug logs from this package:
// Or specify a specific level
enableLogging('debug'); // Most verbose
enableLogging('info'); // General information
enableLogging('warn'); // Warnings only
enableLogging('error'); // Errors only
// Disable logging when done
disableLogging();
```bash
DEBUG='@langchain/mcp-adapters:*'
```
You can also access the logger directly:
To output debug logs only from the `client` module:
```typescript
import { logger } from '@langchain/mcp-adapters';
// Advanced logging configuration
logger.level = 'debug';
```bash
DEBUG='@langchain/mcp-adapters:client'
```
To output debug logs only from the `tools` module:
```bash
DEBUG='@langchain/mcp-adapters:tools'
```
## License
MIT
-109
View File
@@ -1,109 +0,0 @@
import { describe, test, expect, beforeEach, afterEach, vi } from 'vitest';
// Mock fs and path modules
vi.mock('fs', () => ({
existsSync: vi.fn(),
mkdirSync: vi.fn(),
writeFileSync: vi.fn(),
unlinkSync: vi.fn(),
accessSync: vi.fn(),
}));
vi.mock('path', () => ({
join: vi.fn(),
}));
const fs = await import('fs');
const path = await import('path');
const winston = await import('winston');
describe('Logger', () => {
// Store original console.warn implementation
const originalConsoleWarn = console.warn;
let consoleWarnMock: any;
beforeEach(() => {
// Clear module cache to ensure logger is reinitialized
vi.resetModules();
// Mock console.warn to capture warnings
consoleWarnMock = vi.spyOn(console, 'warn').mockImplementation((..._args) => {});
// Configure path.join to return predictable paths
(path.join as any).mockImplementation((...args: string[]) => args.join('/'));
// Reset fs mock implementation
(fs.existsSync as any).mockReset();
(fs.mkdirSync as any).mockReset();
(fs.writeFileSync as any).mockReset();
(fs.unlinkSync as any).mockReset();
});
afterEach(() => {
// Restore console.warn
consoleWarnMock.mockRestore();
console.warn = originalConsoleWarn;
});
test('should fallback to console-only logging when directory creation fails', async () => {
// Mock fs.existsSync to return false (directory doesn't exist)
(fs.existsSync as any).mockReturnValue(false);
// Mock fs.mkdirSync to throw an error
(fs.mkdirSync as any).mockImplementation(() => {
throw new Error('Permission denied');
});
// Import logger (after mocks are set up)
const logger = (await import('../src/logger.js')).default;
// Verify console warning was logged
expect(consoleWarnMock).toHaveBeenCalledWith(
expect.stringContaining('Unable to set up file logging')
);
expect(consoleWarnMock).toHaveBeenCalledWith('Falling back to console logging only');
// Ensure logger was created with only console transport
expect(logger.transports.length).toBe(1);
expect(logger.transports[0]).toBeInstanceOf(winston.transports.Console);
});
test('should fallback to console-only logging when write permission test fails', async () => {
// Mock fs.existsSync to return true (directory exists)
(fs.existsSync as any).mockReturnValue(true);
// Mock fs.writeFileSync to throw an error
(fs.writeFileSync as any).mockImplementation(() => {
throw new Error('Permission denied');
});
// Import logger (after mocks are set up)
const logger = (await import('../src/logger.js')).default;
// Verify console warning was logged
expect(consoleWarnMock).toHaveBeenCalledWith(
expect.stringContaining('Unable to set up file logging')
);
expect(consoleWarnMock).toHaveBeenCalledWith('Falling back to console logging only');
// Ensure logger was created with only console transport
expect(logger.transports.length).toBe(1);
expect(logger.transports[0]).toBeInstanceOf(winston.transports.Console);
});
test('should set up file transports when permissions are available', async () => {
// Mock all the file operations to succeed
(fs.mkdirSync as any).mockImplementation(() => true);
(fs.accessSync as any).mockImplementation(() => true);
(fs.existsSync as any).mockReturnValue(true);
(fs.writeFileSync as any).mockImplementation(() => undefined);
// Import logger directly
const loggerModule = await import('../src/logger.js');
const logger = loggerModule.default;
// Just verify logger was created - don't worry about warnings
expect(logger).toBeDefined();
expect(typeof logger.debug).toBe('function');
expect(typeof logger.info).toBe('function');
expect(typeof logger.warn).toBe('function');
expect(typeof logger.error).toBe('function');
});
});
@@ -14,7 +14,7 @@ import fs from 'fs';
import path from 'path';
// MCP client imports
import { MultiServerMCPClient, enableLogging } from '../src/index.js';
import { MultiServerMCPClient } from '../src/index.js';
// Load environment variables from .env file
dotenv.config();
@@ -63,9 +63,6 @@ async function runExample() {
let client: MultiServerMCPClient | null = null;
try {
// Enable logging for better visibility
enableLogging('info');
// Create the multiple servers configuration file
createMultipleServersConfigFile();
+639 -261
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -43,7 +43,7 @@
"dependencies": {
"@dmitryrechkin/json-schema-to-zod": "^1.0.1",
"@modelcontextprotocol/sdk": "^1.7.0",
"winston": "^3.17.0"
"debug": "^4.4.0"
},
"peerDependencies": {
"@langchain/core": "^0.3.40"
@@ -55,6 +55,7 @@
"@eslint/js": "^9.21.0",
"@langchain/langgraph": "^0.2.56",
"@langchain/openai": "^0.4.4",
"@types/debug": "^4.1.12",
"@types/node": "^22.13.10",
"@typescript-eslint/eslint-plugin": "^7.3.1",
"@typescript-eslint/parser": "^7.3.1",
+56 -45
View File
@@ -5,7 +5,14 @@ import { StructuredToolInterface } from '@langchain/core/tools';
import { loadMcpTools } from './tools.js';
import * as fs from 'fs';
import * as path from 'path';
import logger from './logger.js';
import debug from 'debug';
const {
default: { name: packageName },
} = await import('../package.json');
const moduleName = 'client';
const debugLog = debug(`${packageName}:${moduleName}`);
/**
* Configuration for stdio transport connection
@@ -119,14 +126,16 @@ export class MultiServerMCPClient {
try {
const defaultConfigPath = path.join(process.cwd(), 'mcp.json');
if (fs.existsSync(defaultConfigPath)) {
logger.info(`Found default configuration at ${defaultConfigPath}, loading automatically`);
debugLog(
`INFO: Found default configuration at ${defaultConfigPath}, loading automatically`
);
const config = MultiServerMCPClient.loadConfigFromFile(defaultConfigPath);
return MultiServerMCPClient.processConnections(config.servers);
} else {
logger.debug('No default mcp.json found in root directory');
debugLog(`INFO: No default mcp.json found in root directory`);
}
} catch (error) {
logger.warn(`Failed to load default configuration: ${error}`);
debugLog(`WARN: Failed to load default configuration: ${error}`);
// Do not throw here, just continue with no configs
}
}
@@ -143,7 +152,7 @@ export class MultiServerMCPClient {
// Validate that config has a servers property
if (!config || typeof config !== 'object' || !('servers' in config)) {
logger.error(`Invalid MCP configuration from ${configPath}: missing 'servers' property`);
debugLog(`ERROR: Invalid MCP configuration from ${configPath}: missing 'servers' property`);
throw new MCPClientError(`Invalid MCP configuration: missing 'servers' property`);
}
@@ -178,7 +187,7 @@ export class MultiServerMCPClient {
if (envValue) {
config.env[key] = envValue;
} else {
logger.warn(`Environment variable ${envVar} not found for server "${serverName}"`);
debugLog(`WARN: Environment variable ${envVar} not found for server "${serverName}"`);
}
}
}
@@ -224,7 +233,7 @@ export class MultiServerMCPClient {
for (const [serverName, config] of Object.entries(connections)) {
if (typeof config !== 'object' || config === null) {
logger.warn(`Invalid configuration for server "${serverName}". Skipping.`);
debugLog(`WARN: Invalid configuration for server "${serverName}". Skipping.`);
continue;
}
@@ -532,10 +541,10 @@ export class MultiServerMCPClient {
client.connections = MultiServerMCPClient.processConnections(config.servers);
}
logger.info(`Loaded MCP configuration from ${configPath}`);
debugLog(`INFO: Loaded MCP configuration from ${configPath}`);
return client;
} catch (error) {
logger.error(`Failed to load MCP configuration from ${configPath}: ${error}`);
debugLog(`ERROR: Failed to load MCP configuration from ${configPath}: ${error}`);
throw new MCPClientError(`Failed to load MCP configuration: ${error}`);
}
}
@@ -548,12 +557,12 @@ export class MultiServerMCPClient {
*/
async initializeConnections(): Promise<Map<string, StructuredToolInterface[]>> {
if (!this.connections || Object.keys(this.connections).length === 0) {
logger.warn('No connections to initialize');
debugLog(`WARN: No connections to initialize`);
return new Map();
}
for (const [serverName, connection] of Object.entries(this.connections)) {
logger.info(`Initializing connection to server "${serverName}"...`);
debugLog(`INFO: Initializing connection to server "${serverName}"...`);
if (connection.transport === 'stdio') {
await this.initializeStdioConnection(serverName, connection);
@@ -580,8 +589,8 @@ export class MultiServerMCPClient {
): Promise<void> {
const { command, args, env, restart } = connection;
logger.debug(
`Creating stdio transport for server "${serverName}" with command: ${command} ${args.join(' ')}`
debugLog(
`DEBUG: Creating stdio transport for server "${serverName}" with command: ${command} ${args.join(' ')}`
);
const transport = new StdioClientTransport({
@@ -614,7 +623,7 @@ export class MultiServerMCPClient {
this.clients.set(serverName, client);
const cleanup = async () => {
logger.debug(`Closing stdio transport for server "${serverName}"`);
debugLog(`DEBUG: Closing stdio transport for server "${serverName}"`);
await transport.close();
};
@@ -641,7 +650,7 @@ export class MultiServerMCPClient {
// Only attempt restart if we haven't cleaned up
if (this.clients.has(serverName)) {
logger.info(`Process for server "${serverName}" exited, attempting to restart...`);
debugLog(`INFO: Process for server "${serverName}" exited, attempting to restart...`);
await this.attemptReconnect(serverName, connection, restart.maxAttempts, restart.delayMs);
}
};
@@ -656,7 +665,7 @@ export class MultiServerMCPClient {
): Promise<void> {
const { url, headers, useNodeEventSource, reconnect } = connection;
logger.debug(`Creating SSE transport for server "${serverName}" with URL: ${url}`);
debugLog(`DEBUG: Creating SSE transport for server "${serverName}" with URL: ${url}`);
try {
const transport = await this.createSSETransport(serverName, url, headers, useNodeEventSource);
@@ -684,7 +693,7 @@ export class MultiServerMCPClient {
this.clients.set(serverName, client);
const cleanup = async () => {
logger.debug(`Closing SSE transport for server "${serverName}"`);
debugLog(`DEBUG: Closing SSE transport for server "${serverName}"`);
await transport.close();
};
@@ -714,7 +723,7 @@ export class MultiServerMCPClient {
return new SSEClientTransport(new URL(url));
}
logger.debug(`Using custom headers for SSE transport to server "${serverName}"`);
debugLog(`DEBUG: Using custom headers for SSE transport to server "${serverName}"`);
// If useNodeEventSource is true, try Node.js implementations
if (useNodeEventSource) {
@@ -722,11 +731,11 @@ export class MultiServerMCPClient {
}
// For browser environments, use the basic requestInit approach
logger.debug(
`Using browser EventSource for server "${serverName}". Headers may not be applied correctly.`
debugLog(
`DEBUG: Using browser EventSource for server "${serverName}". Headers may not be applied correctly.`
);
logger.debug(
`For better headers support in browsers, consider using a custom SSE implementation.`
debugLog(
`DEBUG: For better headers support in browsers, consider using a custom SSE implementation.`
);
return new SSEClientTransport(new URL(url), {
@@ -748,8 +757,8 @@ export class MultiServerMCPClient {
const ExtendedEventSourceModule = await import('extended-eventsource');
const ExtendedEventSource = ExtendedEventSourceModule.EventSource;
logger.debug(`Using Extended EventSource for server "${serverName}"`);
logger.debug(`Setting headers for Extended EventSource: ${JSON.stringify(headers)}`);
debugLog(`DEBUG: Using Extended EventSource for server "${serverName}"`);
debugLog(`DEBUG: Setting headers for Extended EventSource: ${JSON.stringify(headers)}`);
// Override the global EventSource with the extended implementation
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -763,8 +772,8 @@ export class MultiServerMCPClient {
});
} catch (extendedError) {
// Fall back to standard eventsource if extended-eventsource is not available
logger.debug(
`Extended EventSource not available, falling back to standard EventSource: ${extendedError}`
debugLog(
`DEBUG: Extended EventSource not available, falling back to standard EventSource: ${extendedError}`
);
try {
@@ -772,8 +781,8 @@ export class MultiServerMCPClient {
const EventSourceModule = await import('eventsource');
const EventSource = EventSourceModule.default;
logger.debug(`Using Node.js EventSource for server "${serverName}"`);
logger.debug(`Setting headers for EventSource: ${JSON.stringify(headers)}`);
debugLog(`DEBUG: Using Node.js EventSource for server "${serverName}"`);
debugLog(`DEBUG: Setting headers for EventSource: ${JSON.stringify(headers)}`);
// Override the global EventSource with the Node.js implementation
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -786,8 +795,8 @@ export class MultiServerMCPClient {
requestInit: { headers },
});
} catch (nodeError) {
logger.warn(
`Failed to load EventSource packages for server "${serverName}". Headers may not be applied to SSE connection: ${nodeError}`
debugLog(
`WARN: Failed to load EventSource packages for server "${serverName}". Headers may not be applied to SSE connection: ${nodeError}`
);
// Last resort fallback
@@ -816,7 +825,9 @@ export class MultiServerMCPClient {
// Only attempt reconnect if we haven't cleaned up
if (this.clients.has(serverName)) {
logger.info(`SSE connection for server "${serverName}" closed, attempting to reconnect...`);
debugLog(
`INFO: SSE connection for server "${serverName}" closed, attempting to reconnect...`
);
await this.attemptReconnect(
serverName,
connection,
@@ -832,10 +843,10 @@ export class MultiServerMCPClient {
*/
private async loadToolsForServer(serverName: string, client: Client): Promise<void> {
try {
logger.debug(`Loading tools for server "${serverName}"...`);
debugLog(`DEBUG: Loading tools for server "${serverName}"...`);
const tools = await loadMcpTools(client);
this.serverNameToTools.set(serverName, tools);
logger.info(`Successfully loaded ${tools.length} tools from server "${serverName}"`);
debugLog(`INFO: Successfully loaded ${tools.length} tools from server "${serverName}"`);
} catch (error) {
throw new MCPClientError(`Failed to load tools from server "${serverName}": ${error}`);
}
@@ -864,8 +875,8 @@ export class MultiServerMCPClient {
while (!connected && (maxAttempts === undefined || attempts < maxAttempts)) {
attempts++;
logger.info(
`Reconnection attempt ${attempts}${maxAttempts ? `/${maxAttempts}` : ''} for server "${serverName}"`
debugLog(
`INFO: Reconnection attempt ${attempts}${maxAttempts ? `/${maxAttempts}` : ''} for server "${serverName}"`
);
try {
@@ -884,17 +895,17 @@ export class MultiServerMCPClient {
// Check if connected
if (this.clients.has(serverName)) {
connected = true;
logger.info(`Successfully reconnected to server "${serverName}"`);
debugLog(`INFO: Successfully reconnected to server "${serverName}"`);
}
} catch (error) {
logger.error(
`Failed to reconnect to server "${serverName}" (attempt ${attempts}): ${error}`
debugLog(
`ERROR: Failed to reconnect to server "${serverName}" (attempt ${attempts}): ${error}`
);
}
}
if (!connected) {
logger.error(`Failed to reconnect to server "${serverName}" after ${attempts} attempts`);
debugLog(`ERROR: Failed to reconnect to server "${serverName}" after ${attempts} attempts`);
}
}
@@ -965,13 +976,13 @@ export class MultiServerMCPClient {
* Close all connections.
*/
async close(): Promise<void> {
logger.info('Closing all MCP connections...');
debugLog(`INFO: Closing all MCP connections...`);
for (const cleanup of this.cleanupFunctions) {
try {
await cleanup();
} catch (error) {
logger.error(`Error during cleanup: ${error}`);
debugLog(`ERROR: Error during cleanup: ${error}`);
}
}
@@ -980,7 +991,7 @@ export class MultiServerMCPClient {
this.serverNameToTools.clear();
this.transportInstances.clear();
logger.info('All MCP connections closed');
debugLog(`INFO: All MCP connections closed`);
}
/**
@@ -1077,10 +1088,10 @@ export class MultiServerMCPClient {
this.connections = MultiServerMCPClient.processConnections(config.servers);
}
logger.info(`Added MCP configuration from ${configPath}`);
debugLog(`INFO: Added MCP configuration from ${configPath}`);
return this;
} catch (error) {
logger.error(`Failed to add MCP configuration from ${configPath}: ${error}`);
debugLog(`ERROR: Failed to add MCP configuration from ${configPath}: ${error}`);
throw new MCPClientError(`Failed to add MCP configuration: ${error}`);
}
}
@@ -1104,7 +1115,7 @@ export class MultiServerMCPClient {
this.connections = processedConnections;
}
logger.info(`Added ${Object.keys(processedConnections).length} connections to client`);
debugLog(`INFO: Added ${Object.keys(processedConnections).length} connections to client`);
return this;
}
-1
View File
@@ -1,2 +1 @@
export { MultiServerMCPClient } from './client.js';
export { logger, enableLogging, disableLogging } from './logger.js';
-124
View File
@@ -1,124 +0,0 @@
import * as winston from 'winston';
import * as fs from 'fs';
import * as path from 'path';
/**
* Logging levels:
* error: 0 - Severe errors that cause the application to crash or malfunction
* warn: 1 - Warnings that don't stop the application but should be addressed
* info: 2 - General information about application operation
* http: 3 - HTTP request/response information
* debug: 4 - Detailed debugging information
*/
const levels = {
error: 0,
warn: 1,
info: 2,
http: 3,
debug: 4,
};
/**
* By default, logging is set to the silent level unless explicitly enabled
* This makes logging opt-in rather than enabled by default
*/
const defaultLevel = 'silent';
/**
* Define colors for each log level to improve readability in the console.
*/
const colors = {
error: 'red',
warn: 'yellow',
info: 'green',
http: 'magenta',
debug: 'white',
};
// Add colors to Winston
winston.addColors(colors);
/**
* Define the format for log messages.
* We include a timestamp, colorize the output, and format the message.
*/
const format = winston.format.combine(
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss:ms' }),
winston.format.colorize({ all: true }),
winston.format.printf(info => `${info.timestamp} ${info.level}: ${info.message}`)
);
/**
* Define the transports for log messages.
* Always log to console, and only log to files if we have permission.
*/
// Base transports array (always include console)
const transports: winston.transport[] = [
// Console transport
new winston.transports.Console(),
];
// Attempt to add file transports only if we have write permissions
try {
const logsDir = path.join(process.cwd(), 'logs');
// Create logs directory if it doesn't exist
if (!fs.existsSync(logsDir)) {
fs.mkdirSync(logsDir, { recursive: true });
}
// Test write permissions with a small file
const testFile = path.join(logsDir, '.permissions-test');
fs.writeFileSync(testFile, 'test');
fs.unlinkSync(testFile);
// If we reach here, we have write permissions - add file transports
transports.push(
// File transport for errors
new winston.transports.File({
filename: path.join(logsDir, 'error.log'),
level: 'error',
}),
// File transport for all logs
new winston.transports.File({
filename: path.join(logsDir, 'all.log'),
})
);
} catch (error) {
// If any error occurs during the file operations, log to console only
console.warn(
`Unable to set up file logging: ${error instanceof Error ? error.message : String(error)}`
);
console.warn('Falling back to console logging only');
}
/**
* Create the logger instance with our configuration.
* By default, logging is disabled (silent) but can be enabled by setting the level.
*/
export const logger = winston.createLogger({
level: defaultLevel, // Start with silent logging by default
levels,
format,
transports,
});
/**
* Enable logging at the specified level.
*
* @param level - The log level to enable ('error', 'warn', 'info', 'http', 'debug')
*/
export function enableLogging(level: keyof typeof levels | 'silent' = 'info'): void {
logger.level = level;
logger.debug(`Logging enabled at level: ${level}`);
}
/**
* Disable all logging.
*/
export function disableLogging(): void {
logger.level = 'silent';
}
export default logger;
+15 -8
View File
@@ -6,7 +6,14 @@ import {
} from '@langchain/core/tools';
import { JSONSchema, JSONSchemaToZod } from '@dmitryrechkin/json-schema-to-zod';
import logger from './logger.js';
import debug from 'debug';
const {
default: { name: packageName },
} = await import('../package.json');
const moduleName = 'tools';
const debugLog = debug(`${packageName}:${moduleName}`);
interface TextContent {
type: 'text';
@@ -67,7 +74,7 @@ function _convertCallToolResult(
// Check for errors
if (result.isError) {
logger.error('MCP tool returned an error result');
debugLog('ERROR: MCP tool returned an error result');
throw new ToolException(
typeof finalTextOutput === 'string' ? finalTextOutput : textOutput.join('\n')
);
@@ -96,7 +103,7 @@ async function _callTool(
args: Record<string, unknown>
): Promise<string | [string | string[], NonTextContent[] | null]> {
try {
logger.info(`Calling tool ${name}(${JSON.stringify(args)})`);
debugLog(`INFO: Calling tool ${name}(${JSON.stringify(args)})`);
const result = await client.callTool({
name,
arguments: args,
@@ -108,7 +115,7 @@ async function _callTool(
content: result.content || [],
});
logger.info(`Tool ${name} returned: ${JSON.stringify({ textContent, nonTextContent })}`);
debugLog(`INFO: Tool ${name} returned: ${JSON.stringify({ textContent, nonTextContent })}`);
// Return based on the response format
if (responseFormat === 'content_and_artifact') {
@@ -118,7 +125,7 @@ async function _callTool(
// Default to returning just the text content
return typeof textContent === 'string' ? textContent : textContent.join('\n');
} catch (error) {
logger.error(`Error calling tool ${name}: ${String(error)}`);
debugLog(`ERROR: Error calling tool ${name}: ${String(error)}`);
if (error instanceof ToolException) {
throw error;
}
@@ -140,7 +147,7 @@ export async function loadMcpTools(
): Promise<StructuredToolInterface[]> {
// Get tools in a single operation
const toolsResponse = await client.listTools();
logger.info(`Found ${toolsResponse.tools?.length || 0} MCP tools`);
debugLog(`INFO: Found ${toolsResponse.tools?.length || 0} MCP tools`);
// Filter out tools without names and convert in a single map operation
return (toolsResponse.tools || [])
@@ -156,10 +163,10 @@ export async function loadMcpTools(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
func: _callTool.bind(null, client, tool.name, responseFormat) as any,
});
logger.debug(`Successfully loaded tool: ${dst.name}`);
debugLog(`INFO: Successfully loaded tool: ${dst.name}`);
return dst;
} catch (error) {
logger.error(`Failed to load tool "${tool.name}":`, error);
debugLog(`ERROR: Failed to load tool "${tool.name}":`, error);
if (throwOnLoadError) {
throw error;
}
+2 -1
View File
@@ -10,7 +10,8 @@
"skipLibCheck": true,
"sourceMap": true,
"downlevelIteration": true,
"noEmit": true // we just want type checking - no need to output for inclusion in the published module
"noEmit": true, // we just want type checking - no need to output for inclusion in the published module
"resolveJsonModule": true
},
"include": ["examples/**/*", "src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts"]
+2 -1
View File
@@ -10,7 +10,8 @@
"strict": true,
"skipLibCheck": true,
"sourceMap": true,
"downlevelIteration": true
"downlevelIteration": true,
"resolveJsonModule": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts", "examples/**/*"]