@mastra/mcp 0.3.10-alpha.5 → 0.3.10-alpha.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +7 -7
- package/CHANGELOG.md +7 -0
- package/README.md +93 -0
- package/dist/_tsup-dts-rollup.d.cts +43 -11
- package/dist/_tsup-dts-rollup.d.ts +43 -11
- package/dist/index.cjs +4143 -14
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4143 -14
- package/package.json +2 -2
- package/src/client.ts +119 -17
- package/src/configuration.ts +11 -6
- package/src/server-logging.test.ts +189 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mastra/mcp",
|
|
3
|
-
"version": "0.3.10-alpha.
|
|
3
|
+
"version": "0.3.10-alpha.6",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"date-fns": "^4.1.0",
|
|
27
27
|
"exit-hook": "^4.0.0",
|
|
28
28
|
"uuid": "^11.1.0",
|
|
29
|
-
"@mastra/core": "^0.8.3-alpha.
|
|
29
|
+
"@mastra/core": "^0.8.3-alpha.5"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@ai-sdk/anthropic": "^1.1.15",
|
package/src/client.ts
CHANGED
|
@@ -9,28 +9,68 @@ import type { StdioServerParameters } from '@modelcontextprotocol/sdk/client/std
|
|
|
9
9
|
import { DEFAULT_REQUEST_TIMEOUT_MSEC } from '@modelcontextprotocol/sdk/shared/protocol.js';
|
|
10
10
|
import type { Protocol } from '@modelcontextprotocol/sdk/shared/protocol.js';
|
|
11
11
|
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
|
12
|
-
import type { ClientCapabilities } from '@modelcontextprotocol/sdk/types.js';
|
|
12
|
+
import type { ClientCapabilities, LoggingLevel } from '@modelcontextprotocol/sdk/types.js';
|
|
13
13
|
import { CallToolResultSchema, ListResourcesResultSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
14
14
|
|
|
15
15
|
import { asyncExitHook, gracefulExit } from 'exit-hook';
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
|
|
18
|
+
// Re-export MCP SDK LoggingLevel for convenience
|
|
19
|
+
export type { LoggingLevel } from '@modelcontextprotocol/sdk/types.js';
|
|
20
|
+
|
|
21
|
+
export interface LogMessage {
|
|
22
|
+
level: LoggingLevel;
|
|
23
|
+
message: string;
|
|
24
|
+
timestamp: Date;
|
|
25
|
+
serverName: string;
|
|
26
|
+
details?: Record<string, any>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type LogHandler = (logMessage: LogMessage) => void;
|
|
16
30
|
|
|
17
31
|
// Omit the fields we want to control from the SDK options
|
|
18
32
|
type SSEClientParameters = {
|
|
19
33
|
url: URL;
|
|
20
|
-
timeout?: number;
|
|
21
34
|
} & SSEClientTransportOptions;
|
|
22
35
|
|
|
23
|
-
type
|
|
36
|
+
export type MastraMCPServerDefinition = (StdioServerParameters | SSEClientParameters) & {
|
|
37
|
+
logger?: LogHandler;
|
|
24
38
|
timeout?: number;
|
|
39
|
+
capabilities?: ClientCapabilities;
|
|
40
|
+
enableServerLogs?: boolean;
|
|
25
41
|
};
|
|
26
42
|
|
|
27
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Convert an MCP LoggingLevel to a logger method name that exists in our logger
|
|
45
|
+
*/
|
|
46
|
+
function convertLogLevelToLoggerMethod(level: LoggingLevel): 'debug' | 'info' | 'warn' | 'error' {
|
|
47
|
+
switch (level) {
|
|
48
|
+
case 'debug':
|
|
49
|
+
return 'debug';
|
|
50
|
+
case 'info':
|
|
51
|
+
case 'notice':
|
|
52
|
+
return 'info';
|
|
53
|
+
case 'warning':
|
|
54
|
+
return 'warn';
|
|
55
|
+
case 'error':
|
|
56
|
+
case 'critical':
|
|
57
|
+
case 'alert':
|
|
58
|
+
case 'emergency':
|
|
59
|
+
return 'error';
|
|
60
|
+
default:
|
|
61
|
+
// For any other levels, default to info
|
|
62
|
+
return 'info';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
28
65
|
|
|
29
66
|
export class MastraMCPClient extends MastraBase {
|
|
30
67
|
name: string;
|
|
31
68
|
private transport: Transport;
|
|
32
69
|
private client: Client;
|
|
33
70
|
private readonly timeout: number;
|
|
71
|
+
private logHandler?: LogHandler;
|
|
72
|
+
private enableServerLogs?: boolean;
|
|
73
|
+
|
|
34
74
|
constructor({
|
|
35
75
|
name,
|
|
36
76
|
version = '1.0.0',
|
|
@@ -47,17 +87,22 @@ export class MastraMCPClient extends MastraBase {
|
|
|
47
87
|
super({ name: 'MastraMCPClient' });
|
|
48
88
|
this.name = name;
|
|
49
89
|
this.timeout = timeout;
|
|
90
|
+
this.logHandler = server.logger;
|
|
91
|
+
this.enableServerLogs = server.enableServerLogs ?? true;
|
|
92
|
+
|
|
93
|
+
// Extract log handler from server config to avoid passing it to transport
|
|
94
|
+
const { logger, enableServerLogs, ...serverConfig } = server;
|
|
50
95
|
|
|
51
|
-
if (`url` in
|
|
52
|
-
this.transport = new SSEClientTransport(
|
|
53
|
-
requestInit:
|
|
54
|
-
eventSourceInit:
|
|
96
|
+
if (`url` in serverConfig) {
|
|
97
|
+
this.transport = new SSEClientTransport(serverConfig.url, {
|
|
98
|
+
requestInit: serverConfig.requestInit,
|
|
99
|
+
eventSourceInit: serverConfig.eventSourceInit,
|
|
55
100
|
});
|
|
56
101
|
} else {
|
|
57
102
|
this.transport = new StdioClientTransport({
|
|
58
|
-
...
|
|
103
|
+
...serverConfig,
|
|
59
104
|
// without ...getDefaultEnvironment() commands like npx will fail because there will be no PATH env var
|
|
60
|
-
env: { ...getDefaultEnvironment(), ...(
|
|
105
|
+
env: { ...getDefaultEnvironment(), ...(serverConfig.env || {}) },
|
|
61
106
|
});
|
|
62
107
|
}
|
|
63
108
|
|
|
@@ -70,6 +115,53 @@ export class MastraMCPClient extends MastraBase {
|
|
|
70
115
|
capabilities,
|
|
71
116
|
},
|
|
72
117
|
);
|
|
118
|
+
|
|
119
|
+
// Set up log message capturing
|
|
120
|
+
this.setupLogging();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Log a message at the specified level
|
|
125
|
+
* @param level Log level
|
|
126
|
+
* @param message Log message
|
|
127
|
+
* @param details Optional additional details
|
|
128
|
+
*/
|
|
129
|
+
private log(level: LoggingLevel, message: string, details?: Record<string, any>): void {
|
|
130
|
+
// Convert MCP logging level to our logger method
|
|
131
|
+
const loggerMethod = convertLogLevelToLoggerMethod(level);
|
|
132
|
+
|
|
133
|
+
// Log to internal logger
|
|
134
|
+
this.logger[loggerMethod](message, details);
|
|
135
|
+
|
|
136
|
+
// Send to registered handler if available
|
|
137
|
+
if (this.logHandler) {
|
|
138
|
+
this.logHandler({
|
|
139
|
+
level,
|
|
140
|
+
message,
|
|
141
|
+
timestamp: new Date(),
|
|
142
|
+
serverName: this.name,
|
|
143
|
+
details,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private setupLogging(): void {
|
|
149
|
+
if (this.enableServerLogs) {
|
|
150
|
+
this.client.setNotificationHandler(
|
|
151
|
+
z.object({
|
|
152
|
+
method: z.literal('notifications/message'),
|
|
153
|
+
params: z
|
|
154
|
+
.object({
|
|
155
|
+
level: z.string(),
|
|
156
|
+
})
|
|
157
|
+
.passthrough(),
|
|
158
|
+
}),
|
|
159
|
+
notification => {
|
|
160
|
+
const { level, ...params } = notification.params;
|
|
161
|
+
this.log(level as LoggingLevel, '[MCP SERVER LOG]', params);
|
|
162
|
+
},
|
|
163
|
+
);
|
|
164
|
+
}
|
|
73
165
|
}
|
|
74
166
|
|
|
75
167
|
private isConnected = false;
|
|
@@ -77,12 +169,14 @@ export class MastraMCPClient extends MastraBase {
|
|
|
77
169
|
async connect() {
|
|
78
170
|
if (this.isConnected) return;
|
|
79
171
|
try {
|
|
172
|
+
this.log('debug', `Connecting to MCP server`);
|
|
80
173
|
await this.client.connect(this.transport, {
|
|
81
174
|
timeout: this.timeout,
|
|
82
175
|
});
|
|
83
176
|
this.isConnected = true;
|
|
84
177
|
const originalOnClose = this.client.onclose;
|
|
85
178
|
this.client.onclose = () => {
|
|
179
|
+
this.log('debug', `MCP server connection closed`);
|
|
86
180
|
this.isConnected = false;
|
|
87
181
|
if (typeof originalOnClose === `function`) {
|
|
88
182
|
originalOnClose();
|
|
@@ -90,38 +184,43 @@ export class MastraMCPClient extends MastraBase {
|
|
|
90
184
|
};
|
|
91
185
|
asyncExitHook(
|
|
92
186
|
async () => {
|
|
93
|
-
this.
|
|
187
|
+
this.log('debug', `Disconnecting MCP server during exit`);
|
|
94
188
|
await this.disconnect();
|
|
95
189
|
},
|
|
96
190
|
{ wait: 5000 },
|
|
97
191
|
);
|
|
98
192
|
|
|
99
193
|
process.on('SIGTERM', () => gracefulExit());
|
|
194
|
+
this.log('info', `Successfully connected to MCP server`);
|
|
100
195
|
} catch (e) {
|
|
101
|
-
this.
|
|
102
|
-
|
|
103
|
-
);
|
|
196
|
+
this.log('error', `Failed connecting to MCP server`, {
|
|
197
|
+
error: e instanceof Error ? e.stack : JSON.stringify(e, null, 2),
|
|
198
|
+
});
|
|
104
199
|
this.isConnected = false;
|
|
105
200
|
throw e;
|
|
106
201
|
}
|
|
107
202
|
}
|
|
108
203
|
|
|
109
204
|
async disconnect() {
|
|
205
|
+
this.log('debug', `Disconnecting from MCP server`);
|
|
110
206
|
return await this.client.close();
|
|
111
207
|
}
|
|
112
208
|
|
|
113
209
|
// TODO: do the type magic to return the right method type. Right now we get infinitely deep infered type errors from Zod without using "any"
|
|
114
210
|
|
|
115
211
|
async resources(): Promise<ReturnType<Protocol<any, any, any>['request']>> {
|
|
212
|
+
this.log('debug', `Requesting resources from MCP server`);
|
|
116
213
|
return await this.client.request({ method: 'resources/list' }, ListResourcesResultSchema, {
|
|
117
214
|
timeout: this.timeout,
|
|
118
215
|
});
|
|
119
216
|
}
|
|
120
217
|
|
|
121
218
|
async tools() {
|
|
219
|
+
this.log('debug', `Requesting tools from MCP server`);
|
|
122
220
|
const { tools } = await this.client.listTools({ timeout: this.timeout });
|
|
123
221
|
const toolsRes: Record<string, any> = {};
|
|
124
222
|
tools.forEach(tool => {
|
|
223
|
+
this.log('debug', `Processing tool: ${tool.name}`);
|
|
125
224
|
const s = jsonSchemaToModel(tool.inputSchema);
|
|
126
225
|
const mastraTool = createTool({
|
|
127
226
|
id: `${this.name}_${tool.name}`,
|
|
@@ -129,6 +228,7 @@ export class MastraMCPClient extends MastraBase {
|
|
|
129
228
|
inputSchema: s,
|
|
130
229
|
execute: async ({ context }) => {
|
|
131
230
|
try {
|
|
231
|
+
this.log('debug', `Executing tool: ${tool.name}`, { toolArgs: context });
|
|
132
232
|
const res = await this.client.callTool(
|
|
133
233
|
{
|
|
134
234
|
name: tool.name,
|
|
@@ -139,11 +239,13 @@ export class MastraMCPClient extends MastraBase {
|
|
|
139
239
|
timeout: this.timeout,
|
|
140
240
|
},
|
|
141
241
|
);
|
|
142
|
-
|
|
242
|
+
this.log('debug', `Tool executed successfully: ${tool.name}`);
|
|
143
243
|
return res;
|
|
144
244
|
} catch (e) {
|
|
145
|
-
|
|
146
|
-
|
|
245
|
+
this.log('error', `Error calling tool: ${tool.name}`, {
|
|
246
|
+
error: e instanceof Error ? e.stack : JSON.stringify(e, null, 2),
|
|
247
|
+
toolArgs: context,
|
|
248
|
+
});
|
|
147
249
|
throw e;
|
|
148
250
|
}
|
|
149
251
|
},
|
package/src/configuration.ts
CHANGED
|
@@ -6,16 +6,18 @@ import type { MastraMCPServerDefinition } from './client';
|
|
|
6
6
|
|
|
7
7
|
const mastraMCPConfigurationInstances = new Map<string, InstanceType<typeof MCPConfiguration>>();
|
|
8
8
|
|
|
9
|
+
export interface MCPConfigurationOptions {
|
|
10
|
+
id?: string;
|
|
11
|
+
servers: Record<string, MastraMCPServerDefinition>;
|
|
12
|
+
timeout?: number; // Optional global timeout
|
|
13
|
+
}
|
|
14
|
+
|
|
9
15
|
export class MCPConfiguration extends MastraBase {
|
|
10
16
|
private serverConfigs: Record<string, MastraMCPServerDefinition> = {};
|
|
11
17
|
private id: string;
|
|
12
18
|
private defaultTimeout: number;
|
|
13
19
|
|
|
14
|
-
constructor(args: {
|
|
15
|
-
id?: string;
|
|
16
|
-
servers: Record<string, MastraMCPServerDefinition>;
|
|
17
|
-
timeout?: number; // Optional global timeout
|
|
18
|
-
}) {
|
|
20
|
+
constructor(args: MCPConfigurationOptions) {
|
|
19
21
|
super({ name: 'MCPConfiguration' });
|
|
20
22
|
this.defaultTimeout = args.timeout ?? DEFAULT_REQUEST_TIMEOUT_MSEC;
|
|
21
23
|
this.serverConfigs = args.servers;
|
|
@@ -100,6 +102,7 @@ To fix this you have three different options:
|
|
|
100
102
|
|
|
101
103
|
this.logger.debug(`Connecting to ${name} MCP server`);
|
|
102
104
|
|
|
105
|
+
// Create client with server configuration including log handler
|
|
103
106
|
const mcpClient = new MastraMCPClient({
|
|
104
107
|
name,
|
|
105
108
|
server: config,
|
|
@@ -111,7 +114,9 @@ To fix this you have three different options:
|
|
|
111
114
|
await mcpClient.connect();
|
|
112
115
|
} catch (e) {
|
|
113
116
|
this.mcpClientsById.delete(name);
|
|
114
|
-
this.logger.error(`MCPConfiguration errored connecting to MCP server ${name}
|
|
117
|
+
this.logger.error(`MCPConfiguration errored connecting to MCP server ${name}`, {
|
|
118
|
+
error: e instanceof Error ? e.message : String(e),
|
|
119
|
+
});
|
|
115
120
|
throw e;
|
|
116
121
|
}
|
|
117
122
|
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { describe, it, expect, vi, beforeEach, afterEach, beforeAll, afterAll } from 'vitest';
|
|
4
|
+
import type { LogMessage, LoggingLevel } from './client';
|
|
5
|
+
import { MCPConfiguration } from './configuration';
|
|
6
|
+
|
|
7
|
+
// Increase test timeout for server operations
|
|
8
|
+
vi.setConfig({ testTimeout: 80000, hookTimeout: 80000 });
|
|
9
|
+
|
|
10
|
+
describe('MCP Server Logging', () => {
|
|
11
|
+
let consoleLogSpy: ReturnType<typeof vi.spyOn>;
|
|
12
|
+
let weatherProcess: ReturnType<typeof spawn>;
|
|
13
|
+
|
|
14
|
+
beforeAll(async () => {
|
|
15
|
+
// Start the weather SSE server
|
|
16
|
+
weatherProcess = spawn('npx', ['-y', 'tsx', path.join(__dirname, '__fixtures__/weather.ts')], {
|
|
17
|
+
env: { ...process.env, PORT: '60809' },
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// Wait for SSE server to be ready
|
|
21
|
+
let resolved = false;
|
|
22
|
+
await new Promise<void>((resolve, reject) => {
|
|
23
|
+
weatherProcess.on(`exit`, () => {
|
|
24
|
+
if (!resolved) reject();
|
|
25
|
+
});
|
|
26
|
+
if (weatherProcess.stderr) {
|
|
27
|
+
weatherProcess.stderr.on(`data`, chunk => {
|
|
28
|
+
console.error(chunk.toString());
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
if (weatherProcess.stdout) {
|
|
32
|
+
weatherProcess.stdout.on('data', chunk => {
|
|
33
|
+
if (chunk.toString().includes('server is running on SSE')) {
|
|
34
|
+
resolve();
|
|
35
|
+
resolved = true;
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
afterEach(() => {
|
|
47
|
+
consoleLogSpy.mockRestore();
|
|
48
|
+
vi.clearAllMocks();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
afterAll(async () => {
|
|
52
|
+
// Kill the weather SSE server
|
|
53
|
+
weatherProcess.kill('SIGINT');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should log events from specific servers to their handlers', async () => {
|
|
57
|
+
// Create individual log handlers for each server
|
|
58
|
+
const weatherLogHandler = vi.fn();
|
|
59
|
+
const stockLogHandler = vi.fn();
|
|
60
|
+
|
|
61
|
+
const config = new MCPConfiguration({
|
|
62
|
+
id: 'server-log-test',
|
|
63
|
+
servers: {
|
|
64
|
+
weather: {
|
|
65
|
+
url: new URL('http://localhost:60809/sse'),
|
|
66
|
+
log: weatherLogHandler,
|
|
67
|
+
},
|
|
68
|
+
stock: {
|
|
69
|
+
command: 'npx',
|
|
70
|
+
args: ['-y', 'tsx', path.join(__dirname, '__fixtures__/stock-price.ts')],
|
|
71
|
+
env: {
|
|
72
|
+
FAKE_CREDS: 'test',
|
|
73
|
+
},
|
|
74
|
+
log: stockLogHandler,
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
await config.getTools();
|
|
80
|
+
|
|
81
|
+
// Verify weather logs went to weather handler only
|
|
82
|
+
expect(weatherLogHandler).toHaveBeenCalled();
|
|
83
|
+
|
|
84
|
+
// Check logs contain server name
|
|
85
|
+
const weatherLogs = weatherLogHandler.mock.calls.map(call => call[0]);
|
|
86
|
+
weatherLogs.forEach(log => {
|
|
87
|
+
expect(log).toMatchObject({
|
|
88
|
+
serverName: 'weather',
|
|
89
|
+
timestamp: expect.any(Date),
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Verify stock logs went to stock handler only
|
|
94
|
+
expect(stockLogHandler).toHaveBeenCalled();
|
|
95
|
+
|
|
96
|
+
// Check logs contain server name
|
|
97
|
+
const stockLogs = stockLogHandler.mock.calls.map(call => call[0]);
|
|
98
|
+
stockLogs.forEach(log => {
|
|
99
|
+
expect(log).toMatchObject({
|
|
100
|
+
serverName: 'stock',
|
|
101
|
+
timestamp: expect.any(Date),
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Clean up
|
|
106
|
+
await config.disconnect();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should work with handlers that filter events by log level', async () => {
|
|
110
|
+
// Create a handler that only logs errors and critical/emergency messages
|
|
111
|
+
const errorCounter = { count: 0 };
|
|
112
|
+
const highSeverityHandler = vi.fn((logMessage: LogMessage) => {
|
|
113
|
+
if (['error', 'critical', 'alert', 'emergency'].includes(logMessage.level)) {
|
|
114
|
+
errorCounter.count++;
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Intentionally use a non-existent command to generate errors
|
|
119
|
+
const config = new MCPConfiguration({
|
|
120
|
+
id: 'error-log-test',
|
|
121
|
+
servers: {
|
|
122
|
+
badServer: {
|
|
123
|
+
command: 'nonexistent-command-that-will-fail',
|
|
124
|
+
args: [],
|
|
125
|
+
log: highSeverityHandler,
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// This should fail, but our logger should capture the error
|
|
131
|
+
try {
|
|
132
|
+
await config.getTools();
|
|
133
|
+
} catch {
|
|
134
|
+
// Expected to fail
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Verify error logger was called
|
|
138
|
+
expect(highSeverityHandler).toHaveBeenCalled();
|
|
139
|
+
|
|
140
|
+
// Check we logged at least one error
|
|
141
|
+
expect(errorCounter.count).toBeGreaterThan(0);
|
|
142
|
+
|
|
143
|
+
// Clean up
|
|
144
|
+
await config.disconnect();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should support console logging patterns', async () => {
|
|
148
|
+
const _logLevels: LoggingLevel[] = [
|
|
149
|
+
'debug',
|
|
150
|
+
'info',
|
|
151
|
+
'notice',
|
|
152
|
+
'warning',
|
|
153
|
+
'error',
|
|
154
|
+
'critical',
|
|
155
|
+
'alert',
|
|
156
|
+
'emergency',
|
|
157
|
+
];
|
|
158
|
+
const logMessages: string[] = [];
|
|
159
|
+
|
|
160
|
+
const consoleLogger = (logMessage: LogMessage) => {
|
|
161
|
+
const formatted = `${logMessage.level}: ${logMessage.message}`;
|
|
162
|
+
logMessages.push(formatted);
|
|
163
|
+
console.log(formatted);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const config = new MCPConfiguration({
|
|
167
|
+
id: 'console-log-test',
|
|
168
|
+
servers: {
|
|
169
|
+
echoServer: {
|
|
170
|
+
command: 'echo',
|
|
171
|
+
args: ['test'],
|
|
172
|
+
log: consoleLogger,
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
await config.getTools();
|
|
179
|
+
} catch {
|
|
180
|
+
// May fail, but we just care about logging
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Verify console.log was called
|
|
184
|
+
expect(consoleLogSpy).toHaveBeenCalled();
|
|
185
|
+
|
|
186
|
+
// Clean up
|
|
187
|
+
await config.disconnect();
|
|
188
|
+
});
|
|
189
|
+
});
|