@mastra/mcp 0.11.3-alpha.0 → 0.11.3-alpha.2
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/CHANGELOG.md +18 -0
- package/dist/index.cjs +7 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +7 -4
- package/dist/index.js.map +1 -1
- package/dist/server/server.d.ts.map +1 -1
- package/package.json +19 -6
- package/.turbo/turbo-build.log +0 -4
- package/eslint.config.js +0 -11
- package/integration-tests/node_modules/.bin/tsc +0 -21
- package/integration-tests/node_modules/.bin/tsserver +0 -21
- package/integration-tests/node_modules/.bin/vitest +0 -21
- package/integration-tests/package.json +0 -29
- package/integration-tests/src/mastra/agents/weather.ts +0 -34
- package/integration-tests/src/mastra/index.ts +0 -15
- package/integration-tests/src/mastra/mcp/index.ts +0 -46
- package/integration-tests/src/mastra/tools/weather.ts +0 -13
- package/integration-tests/src/server.test.ts +0 -238
- package/integration-tests/tsconfig.json +0 -13
- package/integration-tests/vitest.config.ts +0 -14
- package/src/__fixtures__/fire-crawl-complex-schema.ts +0 -1013
- package/src/__fixtures__/server-weather.ts +0 -16
- package/src/__fixtures__/stock-price.ts +0 -128
- package/src/__fixtures__/tools.ts +0 -94
- package/src/__fixtures__/weather.ts +0 -269
- package/src/client/client.test.ts +0 -585
- package/src/client/client.ts +0 -628
- package/src/client/configuration.test.ts +0 -856
- package/src/client/configuration.ts +0 -468
- package/src/client/elicitationActions.ts +0 -26
- package/src/client/index.ts +0 -3
- package/src/client/promptActions.ts +0 -70
- package/src/client/resourceActions.ts +0 -119
- package/src/index.ts +0 -2
- package/src/server/index.ts +0 -2
- package/src/server/promptActions.ts +0 -48
- package/src/server/resourceActions.ts +0 -90
- package/src/server/server-logging.test.ts +0 -181
- package/src/server/server.test.ts +0 -2142
- package/src/server/server.ts +0 -1442
- package/src/server/types.ts +0 -59
- package/tsconfig.build.json +0 -9
- package/tsconfig.json +0 -5
- package/tsup.config.ts +0 -17
- package/vitest.config.ts +0 -8
package/src/client/client.ts
DELETED
|
@@ -1,628 +0,0 @@
|
|
|
1
|
-
import $RefParser from '@apidevtools/json-schema-ref-parser';
|
|
2
|
-
import { MastraBase } from '@mastra/core/base';
|
|
3
|
-
import type { RuntimeContext } from '@mastra/core/di';
|
|
4
|
-
import { ErrorCategory, ErrorDomain, MastraError } from '@mastra/core/error';
|
|
5
|
-
import { createTool } from '@mastra/core/tools';
|
|
6
|
-
import { isZodType } from '@mastra/core/utils';
|
|
7
|
-
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
8
|
-
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
9
|
-
import type { SSEClientTransportOptions } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
10
|
-
import { getDefaultEnvironment, StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
11
|
-
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
12
|
-
import type { StreamableHTTPClientTransportOptions } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
13
|
-
import { DEFAULT_REQUEST_TIMEOUT_MSEC } from '@modelcontextprotocol/sdk/shared/protocol.js';
|
|
14
|
-
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
|
15
|
-
import type {
|
|
16
|
-
ClientCapabilities,
|
|
17
|
-
ElicitRequest,
|
|
18
|
-
ElicitResult,
|
|
19
|
-
GetPromptResult,
|
|
20
|
-
ListPromptsResult,
|
|
21
|
-
LoggingLevel,
|
|
22
|
-
} from '@modelcontextprotocol/sdk/types.js';
|
|
23
|
-
import {
|
|
24
|
-
CallToolResultSchema,
|
|
25
|
-
ListResourcesResultSchema,
|
|
26
|
-
ReadResourceResultSchema,
|
|
27
|
-
ResourceListChangedNotificationSchema,
|
|
28
|
-
ResourceUpdatedNotificationSchema,
|
|
29
|
-
ListResourceTemplatesResultSchema,
|
|
30
|
-
ListPromptsResultSchema,
|
|
31
|
-
GetPromptResultSchema,
|
|
32
|
-
PromptListChangedNotificationSchema,
|
|
33
|
-
ElicitRequestSchema,
|
|
34
|
-
} from '@modelcontextprotocol/sdk/types.js';
|
|
35
|
-
|
|
36
|
-
import { asyncExitHook, gracefulExit } from 'exit-hook';
|
|
37
|
-
import { z } from 'zod';
|
|
38
|
-
import { convertJsonSchemaToZod } from 'zod-from-json-schema';
|
|
39
|
-
import { convertJsonSchemaToZod as convertJsonSchemaToZodV3 } from 'zod-from-json-schema-v3';
|
|
40
|
-
import type { JSONSchema } from 'zod-from-json-schema-v3';
|
|
41
|
-
import { ElicitationClientActions } from './elicitationActions';
|
|
42
|
-
import { PromptClientActions } from './promptActions';
|
|
43
|
-
import { ResourceClientActions } from './resourceActions';
|
|
44
|
-
|
|
45
|
-
// Re-export MCP SDK LoggingLevel for convenience
|
|
46
|
-
export type { LoggingLevel } from '@modelcontextprotocol/sdk/types.js';
|
|
47
|
-
|
|
48
|
-
export interface LogMessage {
|
|
49
|
-
level: LoggingLevel;
|
|
50
|
-
message: string;
|
|
51
|
-
timestamp: Date;
|
|
52
|
-
serverName: string;
|
|
53
|
-
details?: Record<string, any>;
|
|
54
|
-
runtimeContext?: RuntimeContext | null;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export type LogHandler = (logMessage: LogMessage) => void;
|
|
58
|
-
|
|
59
|
-
// Elicitation handler type
|
|
60
|
-
export type ElicitationHandler = (request: ElicitRequest['params']) => Promise<ElicitResult>;
|
|
61
|
-
|
|
62
|
-
// Base options common to all server definitions
|
|
63
|
-
type BaseServerOptions = {
|
|
64
|
-
logger?: LogHandler;
|
|
65
|
-
timeout?: number;
|
|
66
|
-
capabilities?: ClientCapabilities;
|
|
67
|
-
enableServerLogs?: boolean;
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
type StdioServerDefinition = BaseServerOptions & {
|
|
71
|
-
command: string; // 'command' is required for Stdio
|
|
72
|
-
args?: string[];
|
|
73
|
-
env?: Record<string, string>;
|
|
74
|
-
|
|
75
|
-
url?: never; // Exclude 'url' for Stdio
|
|
76
|
-
requestInit?: never; // Exclude HTTP options for Stdio
|
|
77
|
-
eventSourceInit?: never; // Exclude HTTP options for Stdio
|
|
78
|
-
authProvider?: never; // Exclude HTTP options for Stdio
|
|
79
|
-
reconnectionOptions?: never; // Exclude Streamable HTTP specific options
|
|
80
|
-
sessionId?: never; // Exclude Streamable HTTP specific options
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
// HTTP Server Definition (Streamable HTTP or SSE fallback)
|
|
84
|
-
type HttpServerDefinition = BaseServerOptions & {
|
|
85
|
-
url: URL; // 'url' is required for HTTP
|
|
86
|
-
|
|
87
|
-
command?: never; // Exclude 'command' for HTTP
|
|
88
|
-
args?: never; // Exclude Stdio options for HTTP
|
|
89
|
-
env?: never; // Exclude Stdio options for HTTP
|
|
90
|
-
|
|
91
|
-
// Include relevant options from SDK HTTP transport types
|
|
92
|
-
requestInit?: StreamableHTTPClientTransportOptions['requestInit'];
|
|
93
|
-
eventSourceInit?: SSEClientTransportOptions['eventSourceInit'];
|
|
94
|
-
authProvider?: StreamableHTTPClientTransportOptions['authProvider'];
|
|
95
|
-
reconnectionOptions?: StreamableHTTPClientTransportOptions['reconnectionOptions'];
|
|
96
|
-
sessionId?: StreamableHTTPClientTransportOptions['sessionId'];
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
export type MastraMCPServerDefinition = StdioServerDefinition | HttpServerDefinition;
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Convert an MCP LoggingLevel to a logger method name that exists in our logger
|
|
103
|
-
*/
|
|
104
|
-
function convertLogLevelToLoggerMethod(level: LoggingLevel): 'debug' | 'info' | 'warn' | 'error' {
|
|
105
|
-
switch (level) {
|
|
106
|
-
case 'debug':
|
|
107
|
-
return 'debug';
|
|
108
|
-
case 'info':
|
|
109
|
-
case 'notice':
|
|
110
|
-
return 'info';
|
|
111
|
-
case 'warning':
|
|
112
|
-
return 'warn';
|
|
113
|
-
case 'error':
|
|
114
|
-
case 'critical':
|
|
115
|
-
case 'alert':
|
|
116
|
-
case 'emergency':
|
|
117
|
-
return 'error';
|
|
118
|
-
default:
|
|
119
|
-
// For any other levels, default to info
|
|
120
|
-
return 'info';
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
export type InternalMastraMCPClientOptions = {
|
|
125
|
-
name: string;
|
|
126
|
-
server: MastraMCPServerDefinition;
|
|
127
|
-
capabilities?: ClientCapabilities;
|
|
128
|
-
version?: string;
|
|
129
|
-
timeout?: number;
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
export class InternalMastraMCPClient extends MastraBase {
|
|
133
|
-
name: string;
|
|
134
|
-
private client: Client;
|
|
135
|
-
private readonly timeout: number;
|
|
136
|
-
private logHandler?: LogHandler;
|
|
137
|
-
private enableServerLogs?: boolean;
|
|
138
|
-
private serverConfig: MastraMCPServerDefinition;
|
|
139
|
-
private transport?: Transport;
|
|
140
|
-
private currentOperationContext: RuntimeContext | null = null;
|
|
141
|
-
public readonly resources: ResourceClientActions;
|
|
142
|
-
public readonly prompts: PromptClientActions;
|
|
143
|
-
public readonly elicitation: ElicitationClientActions;
|
|
144
|
-
constructor({
|
|
145
|
-
name,
|
|
146
|
-
version = '1.0.0',
|
|
147
|
-
server,
|
|
148
|
-
capabilities = {},
|
|
149
|
-
timeout = DEFAULT_REQUEST_TIMEOUT_MSEC,
|
|
150
|
-
}: InternalMastraMCPClientOptions) {
|
|
151
|
-
super({ name: 'MastraMCPClient' });
|
|
152
|
-
this.name = name;
|
|
153
|
-
this.timeout = timeout;
|
|
154
|
-
this.logHandler = server.logger;
|
|
155
|
-
this.enableServerLogs = server.enableServerLogs ?? true;
|
|
156
|
-
this.serverConfig = server;
|
|
157
|
-
|
|
158
|
-
const clientCapabilities = { ...capabilities, elicitation: {} };
|
|
159
|
-
|
|
160
|
-
this.client = new Client(
|
|
161
|
-
{
|
|
162
|
-
name,
|
|
163
|
-
version,
|
|
164
|
-
},
|
|
165
|
-
{
|
|
166
|
-
capabilities: clientCapabilities,
|
|
167
|
-
},
|
|
168
|
-
);
|
|
169
|
-
|
|
170
|
-
// Set up log message capturing
|
|
171
|
-
this.setupLogging();
|
|
172
|
-
|
|
173
|
-
this.resources = new ResourceClientActions({ client: this, logger: this.logger });
|
|
174
|
-
this.prompts = new PromptClientActions({ client: this, logger: this.logger });
|
|
175
|
-
this.elicitation = new ElicitationClientActions({ client: this, logger: this.logger });
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Log a message at the specified level
|
|
180
|
-
* @param level Log level
|
|
181
|
-
* @param message Log message
|
|
182
|
-
* @param details Optional additional details
|
|
183
|
-
*/
|
|
184
|
-
private log(level: LoggingLevel, message: string, details?: Record<string, any>): void {
|
|
185
|
-
// Convert MCP logging level to our logger method
|
|
186
|
-
const loggerMethod = convertLogLevelToLoggerMethod(level);
|
|
187
|
-
|
|
188
|
-
const msg = `[${this.name}] ${message}`;
|
|
189
|
-
|
|
190
|
-
// Log to internal logger
|
|
191
|
-
this.logger[loggerMethod](msg, details);
|
|
192
|
-
|
|
193
|
-
// Send to registered handler if available
|
|
194
|
-
if (this.logHandler) {
|
|
195
|
-
this.logHandler({
|
|
196
|
-
level,
|
|
197
|
-
message: msg,
|
|
198
|
-
timestamp: new Date(),
|
|
199
|
-
serverName: this.name,
|
|
200
|
-
details,
|
|
201
|
-
runtimeContext: this.currentOperationContext,
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
private setupLogging(): void {
|
|
207
|
-
if (this.enableServerLogs) {
|
|
208
|
-
this.client.setNotificationHandler(
|
|
209
|
-
z.object({
|
|
210
|
-
method: z.literal('notifications/message'),
|
|
211
|
-
params: z
|
|
212
|
-
.object({
|
|
213
|
-
level: z.string(),
|
|
214
|
-
})
|
|
215
|
-
.passthrough(),
|
|
216
|
-
}),
|
|
217
|
-
notification => {
|
|
218
|
-
const { level, ...params } = notification.params;
|
|
219
|
-
this.log(level as LoggingLevel, '[MCP SERVER LOG]', params);
|
|
220
|
-
},
|
|
221
|
-
);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
private async connectStdio(command: string) {
|
|
226
|
-
this.log('debug', `Using Stdio transport for command: ${command}`);
|
|
227
|
-
try {
|
|
228
|
-
this.transport = new StdioClientTransport({
|
|
229
|
-
command,
|
|
230
|
-
args: this.serverConfig.args,
|
|
231
|
-
env: { ...getDefaultEnvironment(), ...(this.serverConfig.env || {}) },
|
|
232
|
-
});
|
|
233
|
-
await this.client.connect(this.transport, { timeout: this.serverConfig.timeout ?? this.timeout });
|
|
234
|
-
this.log('debug', `Successfully connected to MCP server via Stdio`);
|
|
235
|
-
} catch (e) {
|
|
236
|
-
this.log('error', e instanceof Error ? e.stack || e.message : JSON.stringify(e));
|
|
237
|
-
throw e;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
private async connectHttp(url: URL) {
|
|
242
|
-
const { requestInit, eventSourceInit, authProvider } = this.serverConfig;
|
|
243
|
-
|
|
244
|
-
this.log('debug', `Attempting to connect to URL: ${url}`);
|
|
245
|
-
|
|
246
|
-
// Assume /sse means sse.
|
|
247
|
-
let shouldTrySSE = url.pathname.endsWith(`/sse`);
|
|
248
|
-
|
|
249
|
-
if (!shouldTrySSE) {
|
|
250
|
-
try {
|
|
251
|
-
// Try Streamable HTTP transport first
|
|
252
|
-
this.log('debug', 'Trying Streamable HTTP transport...');
|
|
253
|
-
const streamableTransport = new StreamableHTTPClientTransport(url, {
|
|
254
|
-
requestInit,
|
|
255
|
-
reconnectionOptions: this.serverConfig.reconnectionOptions,
|
|
256
|
-
authProvider: authProvider,
|
|
257
|
-
});
|
|
258
|
-
await this.client.connect(streamableTransport, {
|
|
259
|
-
timeout:
|
|
260
|
-
// this is hardcoded to 3s because the long default timeout would be extremely slow for sse backwards compat (60s)
|
|
261
|
-
3000,
|
|
262
|
-
});
|
|
263
|
-
this.transport = streamableTransport;
|
|
264
|
-
this.log('debug', 'Successfully connected using Streamable HTTP transport.');
|
|
265
|
-
} catch (error) {
|
|
266
|
-
this.log('debug', `Streamable HTTP transport failed: ${error}`);
|
|
267
|
-
shouldTrySSE = true;
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
if (shouldTrySSE) {
|
|
272
|
-
this.log('debug', 'Falling back to deprecated HTTP+SSE transport...');
|
|
273
|
-
try {
|
|
274
|
-
// Fallback to SSE transport
|
|
275
|
-
const sseTransport = new SSEClientTransport(url, { requestInit, eventSourceInit, authProvider });
|
|
276
|
-
await this.client.connect(sseTransport, { timeout: this.serverConfig.timeout ?? this.timeout });
|
|
277
|
-
this.transport = sseTransport;
|
|
278
|
-
this.log('debug', 'Successfully connected using deprecated HTTP+SSE transport.');
|
|
279
|
-
} catch (sseError) {
|
|
280
|
-
this.log(
|
|
281
|
-
'error',
|
|
282
|
-
`Failed to connect with SSE transport after failing to connect to Streamable HTTP transport first. SSE error: ${sseError}`,
|
|
283
|
-
);
|
|
284
|
-
throw new Error('Could not connect to server with any available HTTP transport');
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
private isConnected: Promise<boolean> | null = null;
|
|
290
|
-
|
|
291
|
-
async connect() {
|
|
292
|
-
// If a connection attempt is in progress, wait for it.
|
|
293
|
-
if (await this.isConnected) {
|
|
294
|
-
return true;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// Start new connection attempt.
|
|
298
|
-
this.isConnected = new Promise<boolean>(async (resolve, reject) => {
|
|
299
|
-
try {
|
|
300
|
-
const { command, url } = this.serverConfig;
|
|
301
|
-
|
|
302
|
-
if (command) {
|
|
303
|
-
await this.connectStdio(command);
|
|
304
|
-
} else if (url) {
|
|
305
|
-
await this.connectHttp(url);
|
|
306
|
-
} else {
|
|
307
|
-
throw new Error('Server configuration must include either a command or a url.');
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
resolve(true);
|
|
311
|
-
|
|
312
|
-
// Set up disconnect handler to reset state.
|
|
313
|
-
const originalOnClose = this.client.onclose;
|
|
314
|
-
this.client.onclose = () => {
|
|
315
|
-
this.log('debug', `MCP server connection closed`);
|
|
316
|
-
this.isConnected = null;
|
|
317
|
-
if (typeof originalOnClose === 'function') {
|
|
318
|
-
originalOnClose();
|
|
319
|
-
}
|
|
320
|
-
};
|
|
321
|
-
} catch (e) {
|
|
322
|
-
this.isConnected = null;
|
|
323
|
-
reject(e);
|
|
324
|
-
}
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
asyncExitHook(
|
|
328
|
-
async () => {
|
|
329
|
-
this.log('debug', `Disconnecting MCP server during exit`);
|
|
330
|
-
await this.disconnect();
|
|
331
|
-
},
|
|
332
|
-
{ wait: 5000 },
|
|
333
|
-
);
|
|
334
|
-
|
|
335
|
-
process.on('SIGTERM', () => gracefulExit());
|
|
336
|
-
this.log('debug', `Successfully connected to MCP server`);
|
|
337
|
-
return this.isConnected;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
/**
|
|
341
|
-
* Get the current session ID if using the Streamable HTTP transport.
|
|
342
|
-
* Returns undefined if not connected or not using Streamable HTTP.
|
|
343
|
-
*/
|
|
344
|
-
get sessionId(): string | undefined {
|
|
345
|
-
if (this.transport instanceof StreamableHTTPClientTransport) {
|
|
346
|
-
return this.transport.sessionId;
|
|
347
|
-
}
|
|
348
|
-
return undefined;
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
async disconnect() {
|
|
352
|
-
if (!this.transport) {
|
|
353
|
-
this.log('debug', 'Disconnect called but no transport was connected.');
|
|
354
|
-
return;
|
|
355
|
-
}
|
|
356
|
-
this.log('debug', `Disconnecting from MCP server`);
|
|
357
|
-
try {
|
|
358
|
-
await this.transport.close();
|
|
359
|
-
this.log('debug', 'Successfully disconnected from MCP server');
|
|
360
|
-
} catch (e) {
|
|
361
|
-
this.log('error', 'Error during MCP server disconnect', {
|
|
362
|
-
error: e instanceof Error ? e.stack : JSON.stringify(e, null, 2),
|
|
363
|
-
});
|
|
364
|
-
throw e;
|
|
365
|
-
} finally {
|
|
366
|
-
this.transport = undefined;
|
|
367
|
-
this.isConnected = Promise.resolve(false);
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
async listResources() {
|
|
372
|
-
this.log('debug', `Requesting resources from MCP server`);
|
|
373
|
-
return await this.client.request({ method: 'resources/list' }, ListResourcesResultSchema, {
|
|
374
|
-
timeout: this.timeout,
|
|
375
|
-
});
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
async readResource(uri: string) {
|
|
379
|
-
this.log('debug', `Reading resource from MCP server: ${uri}`);
|
|
380
|
-
return await this.client.request({ method: 'resources/read', params: { uri } }, ReadResourceResultSchema, {
|
|
381
|
-
timeout: this.timeout,
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
async subscribeResource(uri: string) {
|
|
386
|
-
this.log('debug', `Subscribing to resource on MCP server: ${uri}`);
|
|
387
|
-
return await this.client.request({ method: 'resources/subscribe', params: { uri } }, z.object({}), {
|
|
388
|
-
timeout: this.timeout,
|
|
389
|
-
});
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
async unsubscribeResource(uri: string) {
|
|
393
|
-
this.log('debug', `Unsubscribing from resource on MCP server: ${uri}`);
|
|
394
|
-
return await this.client.request({ method: 'resources/unsubscribe', params: { uri } }, z.object({}), {
|
|
395
|
-
timeout: this.timeout,
|
|
396
|
-
});
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
async listResourceTemplates() {
|
|
400
|
-
this.log('debug', `Requesting resource templates from MCP server`);
|
|
401
|
-
return await this.client.request({ method: 'resources/templates/list' }, ListResourceTemplatesResultSchema, {
|
|
402
|
-
timeout: this.timeout,
|
|
403
|
-
});
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
/**
|
|
407
|
-
* Fetch the list of available prompts from the MCP server.
|
|
408
|
-
*/
|
|
409
|
-
async listPrompts(): Promise<ListPromptsResult> {
|
|
410
|
-
this.log('debug', `Requesting prompts from MCP server`);
|
|
411
|
-
return await this.client.request({ method: 'prompts/list' }, ListPromptsResultSchema, {
|
|
412
|
-
timeout: this.timeout,
|
|
413
|
-
});
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
/**
|
|
417
|
-
* Get a prompt and its dynamic messages from the server.
|
|
418
|
-
* @param name The prompt name
|
|
419
|
-
* @param args Arguments for the prompt
|
|
420
|
-
* @param version (optional) The prompt version to retrieve
|
|
421
|
-
*/
|
|
422
|
-
async getPrompt({
|
|
423
|
-
name,
|
|
424
|
-
args,
|
|
425
|
-
version,
|
|
426
|
-
}: {
|
|
427
|
-
name: string;
|
|
428
|
-
args?: Record<string, any>;
|
|
429
|
-
version?: string;
|
|
430
|
-
}): Promise<GetPromptResult> {
|
|
431
|
-
this.log('debug', `Requesting prompt from MCP server: ${name}`);
|
|
432
|
-
return await this.client.request(
|
|
433
|
-
{ method: 'prompts/get', params: { name, arguments: args, version } },
|
|
434
|
-
GetPromptResultSchema,
|
|
435
|
-
{ timeout: this.timeout },
|
|
436
|
-
);
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
/**
|
|
440
|
-
* Register a handler to be called when the prompt list changes on the server.
|
|
441
|
-
* Use this to refresh cached prompt lists in the client/UI if needed.
|
|
442
|
-
*/
|
|
443
|
-
setPromptListChangedNotificationHandler(handler: () => void): void {
|
|
444
|
-
this.log('debug', 'Setting prompt list changed notification handler');
|
|
445
|
-
this.client.setNotificationHandler(PromptListChangedNotificationSchema, () => {
|
|
446
|
-
handler();
|
|
447
|
-
});
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
setResourceUpdatedNotificationHandler(
|
|
451
|
-
handler: (params: z.infer<typeof ResourceUpdatedNotificationSchema>['params']) => void,
|
|
452
|
-
): void {
|
|
453
|
-
this.log('debug', 'Setting resource updated notification handler');
|
|
454
|
-
this.client.setNotificationHandler(ResourceUpdatedNotificationSchema, notification => {
|
|
455
|
-
handler(notification.params);
|
|
456
|
-
});
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
setResourceListChangedNotificationHandler(handler: () => void): void {
|
|
460
|
-
this.log('debug', 'Setting resource list changed notification handler');
|
|
461
|
-
this.client.setNotificationHandler(ResourceListChangedNotificationSchema, () => {
|
|
462
|
-
handler();
|
|
463
|
-
});
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
setElicitationRequestHandler(handler: ElicitationHandler): void {
|
|
467
|
-
this.log('debug', 'Setting elicitation request handler');
|
|
468
|
-
this.client.setRequestHandler(ElicitRequestSchema, async request => {
|
|
469
|
-
this.log('debug', `Received elicitation request: ${request.params.message}`);
|
|
470
|
-
return handler(request.params);
|
|
471
|
-
});
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
private async convertInputSchema(
|
|
475
|
-
inputSchema: Awaited<ReturnType<Client['listTools']>>['tools'][0]['inputSchema'] | JSONSchema,
|
|
476
|
-
): Promise<z.ZodType> {
|
|
477
|
-
if (isZodType(inputSchema)) {
|
|
478
|
-
return inputSchema;
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
try {
|
|
482
|
-
await $RefParser.dereference(inputSchema);
|
|
483
|
-
const jsonSchemaToConvert = ('jsonSchema' in inputSchema ? inputSchema.jsonSchema : inputSchema) as JSONSchema;
|
|
484
|
-
if ('toJSONSchema' in z) {
|
|
485
|
-
// @ts-expect-error - zod type issue
|
|
486
|
-
return convertJsonSchemaToZod(jsonSchemaToConvert);
|
|
487
|
-
} else {
|
|
488
|
-
return convertJsonSchemaToZodV3(jsonSchemaToConvert);
|
|
489
|
-
}
|
|
490
|
-
} catch (error: unknown) {
|
|
491
|
-
let errorDetails: string | undefined;
|
|
492
|
-
if (error instanceof Error) {
|
|
493
|
-
errorDetails = error.stack;
|
|
494
|
-
} else {
|
|
495
|
-
// Attempt to stringify, fallback to String()
|
|
496
|
-
try {
|
|
497
|
-
errorDetails = JSON.stringify(error);
|
|
498
|
-
} catch {
|
|
499
|
-
errorDetails = String(error);
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
this.log('error', 'Failed to convert JSON schema to Zod schema using zodFromJsonSchema', {
|
|
503
|
-
error: errorDetails,
|
|
504
|
-
originalJsonSchema: inputSchema,
|
|
505
|
-
});
|
|
506
|
-
|
|
507
|
-
throw new MastraError({
|
|
508
|
-
id: 'MCP_TOOL_INPUT_SCHEMA_CONVERSION_FAILED',
|
|
509
|
-
domain: ErrorDomain.MCP,
|
|
510
|
-
category: ErrorCategory.USER,
|
|
511
|
-
details: { error: errorDetails ?? 'Unknown error' },
|
|
512
|
-
});
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
private async convertOutputSchema(
|
|
517
|
-
outputSchema: Awaited<ReturnType<Client['listTools']>>['tools'][0]['outputSchema'] | JSONSchema,
|
|
518
|
-
): Promise<z.ZodType | undefined> {
|
|
519
|
-
if (!outputSchema) return;
|
|
520
|
-
if (isZodType(outputSchema)) {
|
|
521
|
-
return outputSchema;
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
try {
|
|
525
|
-
await $RefParser.dereference(outputSchema);
|
|
526
|
-
const jsonSchemaToConvert = ('jsonSchema' in outputSchema ? outputSchema.jsonSchema : outputSchema) as JSONSchema;
|
|
527
|
-
if ('toJSONSchema' in z) {
|
|
528
|
-
// @ts-expect-error - zod type issue
|
|
529
|
-
return convertJsonSchemaToZod(jsonSchemaToConvert);
|
|
530
|
-
} else {
|
|
531
|
-
return convertJsonSchemaToZodV3(jsonSchemaToConvert);
|
|
532
|
-
}
|
|
533
|
-
} catch (error: unknown) {
|
|
534
|
-
let errorDetails: string | undefined;
|
|
535
|
-
if (error instanceof Error) {
|
|
536
|
-
errorDetails = error.stack;
|
|
537
|
-
} else {
|
|
538
|
-
// Attempt to stringify, fallback to String()
|
|
539
|
-
try {
|
|
540
|
-
errorDetails = JSON.stringify(error);
|
|
541
|
-
} catch {
|
|
542
|
-
errorDetails = String(error);
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
this.log('error', 'Failed to convert JSON schema to Zod schema using zodFromJsonSchema', {
|
|
546
|
-
error: errorDetails,
|
|
547
|
-
originalJsonSchema: outputSchema,
|
|
548
|
-
});
|
|
549
|
-
|
|
550
|
-
throw new MastraError({
|
|
551
|
-
id: 'MCP_TOOL_OUTPUT_SCHEMA_CONVERSION_FAILED',
|
|
552
|
-
domain: ErrorDomain.MCP,
|
|
553
|
-
category: ErrorCategory.USER,
|
|
554
|
-
details: { error: errorDetails ?? 'Unknown error' },
|
|
555
|
-
});
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
async tools() {
|
|
560
|
-
this.log('debug', `Requesting tools from MCP server`);
|
|
561
|
-
const { tools } = await this.client.listTools({ timeout: this.timeout });
|
|
562
|
-
const toolsRes: Record<string, any> = {};
|
|
563
|
-
for (const tool of tools) {
|
|
564
|
-
this.log('debug', `Processing tool: ${tool.name}`);
|
|
565
|
-
try {
|
|
566
|
-
const mastraTool = createTool({
|
|
567
|
-
id: `${this.name}_${tool.name}`,
|
|
568
|
-
description: tool.description || '',
|
|
569
|
-
inputSchema: await this.convertInputSchema(tool.inputSchema),
|
|
570
|
-
outputSchema: await this.convertOutputSchema(tool.outputSchema),
|
|
571
|
-
execute: async ({ context, runtimeContext }: { context: any; runtimeContext?: RuntimeContext | null }) => {
|
|
572
|
-
const previousContext = this.currentOperationContext;
|
|
573
|
-
this.currentOperationContext = runtimeContext || null; // Set current context
|
|
574
|
-
try {
|
|
575
|
-
this.log('debug', `Executing tool: ${tool.name}`, { toolArgs: context });
|
|
576
|
-
const res = await this.client.callTool(
|
|
577
|
-
{
|
|
578
|
-
name: tool.name,
|
|
579
|
-
arguments: context,
|
|
580
|
-
},
|
|
581
|
-
CallToolResultSchema,
|
|
582
|
-
{
|
|
583
|
-
timeout: this.timeout,
|
|
584
|
-
},
|
|
585
|
-
);
|
|
586
|
-
|
|
587
|
-
this.log('debug', `Tool executed successfully: ${tool.name}`);
|
|
588
|
-
return res;
|
|
589
|
-
} catch (e) {
|
|
590
|
-
this.log('error', `Error calling tool: ${tool.name}`, {
|
|
591
|
-
error: e instanceof Error ? e.stack : JSON.stringify(e, null, 2),
|
|
592
|
-
toolArgs: context,
|
|
593
|
-
});
|
|
594
|
-
throw e;
|
|
595
|
-
} finally {
|
|
596
|
-
this.currentOperationContext = previousContext; // Restore previous context
|
|
597
|
-
}
|
|
598
|
-
},
|
|
599
|
-
});
|
|
600
|
-
|
|
601
|
-
if (tool.name) {
|
|
602
|
-
toolsRes[tool.name] = mastraTool;
|
|
603
|
-
}
|
|
604
|
-
} catch (toolCreationError: unknown) {
|
|
605
|
-
// Catch errors during tool creation itself (e.g., if createTool has issues)
|
|
606
|
-
this.log('error', `Failed to create Mastra tool wrapper for MCP tool: ${tool.name}`, {
|
|
607
|
-
error: toolCreationError instanceof Error ? toolCreationError.stack : String(toolCreationError),
|
|
608
|
-
mcpToolDefinition: tool,
|
|
609
|
-
});
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
return toolsRes;
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
/**
|
|
618
|
-
* @deprecated MastraMCPClient is deprecated and will be removed in a future release. Please use MCPClient instead.
|
|
619
|
-
*/
|
|
620
|
-
|
|
621
|
-
export class MastraMCPClient extends InternalMastraMCPClient {
|
|
622
|
-
constructor(args: InternalMastraMCPClientOptions) {
|
|
623
|
-
super(args);
|
|
624
|
-
this.logger.warn(
|
|
625
|
-
'[DEPRECATION] MastraMCPClient is deprecated and will be removed in a future release. Please use MCPClient instead.',
|
|
626
|
-
);
|
|
627
|
-
}
|
|
628
|
-
}
|