@mastra/mcp 0.10.5-alpha.1 → 0.10.5
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 +38 -0
- package/dist/_tsup-dts-rollup.d.cts +84 -51
- package/dist/_tsup-dts-rollup.d.ts +84 -51
- package/dist/index.cjs +403 -398
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +404 -399
- package/integration-tests/node_modules/.bin/mastra +21 -0
- package/package.json +3 -3
- package/src/client/client.test.ts +310 -1
- package/src/client/client.ts +21 -2
- package/src/client/configuration.ts +22 -2
- package/src/client/elicitationActions.ts +26 -0
- package/src/client/index.ts +1 -1
- package/src/server/server.test.ts +349 -0
- package/src/server/server.ts +424 -457
- package/src/server/types.ts +25 -1
package/src/server/server.ts
CHANGED
|
@@ -40,6 +40,8 @@ import type {
|
|
|
40
40
|
ServerCapabilities,
|
|
41
41
|
Prompt,
|
|
42
42
|
CallToolResult,
|
|
43
|
+
ElicitResult,
|
|
44
|
+
ElicitRequest,
|
|
43
45
|
} from '@modelcontextprotocol/sdk/types.js';
|
|
44
46
|
import type { SSEStreamingApi } from 'hono/streaming';
|
|
45
47
|
import { streamSSE } from 'hono/streaming';
|
|
@@ -47,29 +49,15 @@ import { SSETransport } from 'hono-mcp-server-sse-transport';
|
|
|
47
49
|
import { z } from 'zod';
|
|
48
50
|
import { ServerPromptActions } from './promptActions';
|
|
49
51
|
import { ServerResourceActions } from './resourceActions';
|
|
50
|
-
import type {
|
|
51
|
-
MCPServerPromptMessagesCallback,
|
|
52
|
-
MCPServerPrompts,
|
|
53
|
-
MCPServerResourceContentCallback,
|
|
54
|
-
MCPServerResources,
|
|
55
|
-
} from './types';
|
|
56
|
-
|
|
52
|
+
import type { MCPServerPrompts, MCPServerResources, ElicitationActions, MCPTool } from './types';
|
|
57
53
|
export class MCPServer extends MCPServerBase {
|
|
58
54
|
private server: Server;
|
|
59
55
|
private stdioTransport?: StdioServerTransport;
|
|
60
56
|
private sseTransport?: SSEServerTransport;
|
|
61
57
|
private sseHonoTransports: Map<string, SSETransport>;
|
|
62
58
|
private streamableHTTPTransports: Map<string, StreamableHTTPServerTransport> = new Map();
|
|
63
|
-
|
|
64
|
-
private
|
|
65
|
-
private listResourcesHandlerIsRegistered: boolean = false;
|
|
66
|
-
private readResourceHandlerIsRegistered: boolean = false;
|
|
67
|
-
private listResourceTemplatesHandlerIsRegistered: boolean = false;
|
|
68
|
-
private subscribeResourceHandlerIsRegistered: boolean = false;
|
|
69
|
-
private unsubscribeResourceHandlerIsRegistered: boolean = false;
|
|
70
|
-
|
|
71
|
-
private listPromptsHandlerIsRegistered: boolean = false;
|
|
72
|
-
private getPromptHandlerIsRegistered: boolean = false;
|
|
59
|
+
// Track server instances for each HTTP session
|
|
60
|
+
private httpServerInstances: Map<string, Server> = new Map();
|
|
73
61
|
|
|
74
62
|
private definedResources?: Resource[];
|
|
75
63
|
private definedResourceTemplates?: ResourceTemplate[];
|
|
@@ -79,6 +67,7 @@ export class MCPServer extends MCPServerBase {
|
|
|
79
67
|
private subscriptions: Set<string> = new Set();
|
|
80
68
|
public readonly resources: ServerResourceActions;
|
|
81
69
|
public readonly prompts: ServerPromptActions;
|
|
70
|
+
public readonly elicitation: ElicitationActions;
|
|
82
71
|
|
|
83
72
|
/**
|
|
84
73
|
* Get the current stdio transport.
|
|
@@ -120,6 +109,7 @@ export class MCPServer extends MCPServerBase {
|
|
|
120
109
|
const capabilities: ServerCapabilities = {
|
|
121
110
|
tools: {},
|
|
122
111
|
logging: { enabled: true },
|
|
112
|
+
elicitation: {},
|
|
123
113
|
};
|
|
124
114
|
|
|
125
115
|
if (opts.resources) {
|
|
@@ -137,24 +127,9 @@ export class MCPServer extends MCPServerBase {
|
|
|
137
127
|
);
|
|
138
128
|
|
|
139
129
|
this.sseHonoTransports = new Map();
|
|
140
|
-
this.registerListToolsHandler();
|
|
141
|
-
this.registerCallToolHandler();
|
|
142
|
-
if (opts.resources) {
|
|
143
|
-
this.registerListResourcesHandler();
|
|
144
|
-
this.registerReadResourceHandler({ getResourcesCallback: opts.resources.getResourceContent });
|
|
145
|
-
this.registerSubscribeResourceHandler();
|
|
146
|
-
this.registerUnsubscribeResourceHandler();
|
|
147
130
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
if (opts.prompts) {
|
|
153
|
-
this.registerListPromptsHandler();
|
|
154
|
-
this.registerGetPromptHandler({
|
|
155
|
-
getPromptMessagesCallback: opts.prompts.getPromptMessages,
|
|
156
|
-
});
|
|
157
|
-
}
|
|
131
|
+
// Register all handlers on the main server instance
|
|
132
|
+
this.registerHandlersOnServer(this.server);
|
|
158
133
|
|
|
159
134
|
this.resources = new ServerResourceActions({
|
|
160
135
|
getSubscriptions: () => this.subscriptions,
|
|
@@ -175,6 +150,400 @@ export class MCPServer extends MCPServerBase {
|
|
|
175
150
|
this.definedPrompts = undefined;
|
|
176
151
|
},
|
|
177
152
|
});
|
|
153
|
+
|
|
154
|
+
this.elicitation = {
|
|
155
|
+
sendRequest: async request => {
|
|
156
|
+
return this.handleElicitationRequest(request);
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Handle an elicitation request by sending it to the connected client.
|
|
163
|
+
* This method sends an elicitation/create request to the client and waits for the response.
|
|
164
|
+
*
|
|
165
|
+
* @param request - The elicitation request containing message and schema
|
|
166
|
+
* @param serverInstance - Optional server instance to use; defaults to main server for backward compatibility
|
|
167
|
+
* @returns Promise that resolves to the client's response
|
|
168
|
+
*/
|
|
169
|
+
private async handleElicitationRequest(
|
|
170
|
+
request: ElicitRequest['params'],
|
|
171
|
+
serverInstance?: Server,
|
|
172
|
+
): Promise<ElicitResult> {
|
|
173
|
+
this.logger.debug(`Sending elicitation request: ${request.message}`);
|
|
174
|
+
|
|
175
|
+
const server = serverInstance || this.server;
|
|
176
|
+
const response = await server.elicitInput(request);
|
|
177
|
+
|
|
178
|
+
this.logger.debug(`Received elicitation response: ${JSON.stringify(response)}`);
|
|
179
|
+
|
|
180
|
+
return response;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Creates a new Server instance configured with all handlers for HTTP sessions.
|
|
185
|
+
* Each HTTP client connection gets its own Server instance to avoid routing conflicts.
|
|
186
|
+
*/
|
|
187
|
+
private createServerInstance(): Server {
|
|
188
|
+
const capabilities: ServerCapabilities = {
|
|
189
|
+
tools: {},
|
|
190
|
+
logging: { enabled: true },
|
|
191
|
+
elicitation: {},
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
if (this.resourceOptions) {
|
|
195
|
+
capabilities.resources = { subscribe: true, listChanged: true };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (this.promptOptions) {
|
|
199
|
+
capabilities.prompts = { listChanged: true };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const serverInstance = new Server({ name: this.name, version: this.version }, { capabilities });
|
|
203
|
+
|
|
204
|
+
// Register all handlers on the new server instance
|
|
205
|
+
this.registerHandlersOnServer(serverInstance);
|
|
206
|
+
|
|
207
|
+
return serverInstance;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Registers all MCP handlers on a given server instance.
|
|
212
|
+
* This allows us to create multiple server instances with identical functionality.
|
|
213
|
+
*/
|
|
214
|
+
private registerHandlersOnServer(serverInstance: Server) {
|
|
215
|
+
// List tools handler
|
|
216
|
+
serverInstance.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
217
|
+
this.logger.debug('Handling ListTools request');
|
|
218
|
+
return {
|
|
219
|
+
tools: Object.values(this.convertedTools).map(tool => {
|
|
220
|
+
const toolSpec: any = {
|
|
221
|
+
name: tool.name,
|
|
222
|
+
description: tool.description,
|
|
223
|
+
inputSchema: tool.parameters.jsonSchema,
|
|
224
|
+
};
|
|
225
|
+
if (tool.outputSchema) {
|
|
226
|
+
toolSpec.outputSchema = tool.outputSchema.jsonSchema;
|
|
227
|
+
}
|
|
228
|
+
return toolSpec;
|
|
229
|
+
}),
|
|
230
|
+
};
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// Call tool handler
|
|
234
|
+
serverInstance.setRequestHandler(CallToolRequestSchema, async request => {
|
|
235
|
+
const startTime = Date.now();
|
|
236
|
+
try {
|
|
237
|
+
const tool = this.convertedTools[request.params.name] as MCPTool;
|
|
238
|
+
if (!tool) {
|
|
239
|
+
this.logger.warn(`CallTool: Unknown tool '${request.params.name}' requested.`);
|
|
240
|
+
return {
|
|
241
|
+
content: [{ type: 'text', text: `Unknown tool: ${request.params.name}` }],
|
|
242
|
+
isError: true,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const validation = tool.parameters.validate?.(request.params.arguments ?? {});
|
|
247
|
+
if (validation && !validation.success) {
|
|
248
|
+
this.logger.warn(`CallTool: Invalid tool arguments for '${request.params.name}'`, {
|
|
249
|
+
errors: validation.error,
|
|
250
|
+
});
|
|
251
|
+
return {
|
|
252
|
+
content: [{ type: 'text', text: `Invalid tool arguments: ${JSON.stringify(validation.error)}` }],
|
|
253
|
+
isError: true,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
if (!tool.execute) {
|
|
257
|
+
this.logger.warn(`CallTool: Tool '${request.params.name}' does not have an execute function.`);
|
|
258
|
+
return {
|
|
259
|
+
content: [{ type: 'text', text: `Tool '${request.params.name}' does not have an execute function.` }],
|
|
260
|
+
isError: true,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Create session-aware elicitation for this tool execution
|
|
265
|
+
const sessionElicitation = {
|
|
266
|
+
sendRequest: async (request: ElicitRequest['params']) => {
|
|
267
|
+
return this.handleElicitationRequest(request, serverInstance);
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
const result = await tool.execute(validation?.value, {
|
|
272
|
+
messages: [],
|
|
273
|
+
toolCallId: '',
|
|
274
|
+
elicitation: sessionElicitation,
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
this.logger.debug(`CallTool: Tool '${request.params.name}' executed successfully with result:`, result);
|
|
278
|
+
const duration = Date.now() - startTime;
|
|
279
|
+
this.logger.info(`Tool '${request.params.name}' executed successfully in ${duration}ms.`);
|
|
280
|
+
|
|
281
|
+
const response: CallToolResult = { isError: false, content: [] };
|
|
282
|
+
|
|
283
|
+
if (tool.outputSchema) {
|
|
284
|
+
if (!result.structuredContent) {
|
|
285
|
+
throw new Error(`Tool ${request.params.name} has an output schema but no structured content was provided.`);
|
|
286
|
+
}
|
|
287
|
+
const outputValidation = tool.outputSchema.validate?.(result.structuredContent ?? {});
|
|
288
|
+
if (outputValidation && !outputValidation.success) {
|
|
289
|
+
this.logger.warn(`CallTool: Invalid structured content for '${request.params.name}'`, {
|
|
290
|
+
errors: outputValidation.error,
|
|
291
|
+
});
|
|
292
|
+
throw new Error(
|
|
293
|
+
`Invalid structured content for tool ${request.params.name}: ${JSON.stringify(outputValidation.error)}`,
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
response.structuredContent = result.structuredContent;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (response.structuredContent) {
|
|
300
|
+
response.content = [{ type: 'text', text: JSON.stringify(response.structuredContent) }];
|
|
301
|
+
} else {
|
|
302
|
+
response.content = [
|
|
303
|
+
{
|
|
304
|
+
type: 'text',
|
|
305
|
+
text: typeof result === 'string' ? result : JSON.stringify(result),
|
|
306
|
+
},
|
|
307
|
+
];
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return response;
|
|
311
|
+
} catch (error) {
|
|
312
|
+
const duration = Date.now() - startTime;
|
|
313
|
+
if (error instanceof z.ZodError) {
|
|
314
|
+
this.logger.warn('Invalid tool arguments', {
|
|
315
|
+
tool: request.params.name,
|
|
316
|
+
errors: error.errors,
|
|
317
|
+
duration: `${duration}ms`,
|
|
318
|
+
});
|
|
319
|
+
return {
|
|
320
|
+
content: [
|
|
321
|
+
{
|
|
322
|
+
type: 'text',
|
|
323
|
+
text: `Invalid arguments: ${error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ')}`,
|
|
324
|
+
},
|
|
325
|
+
],
|
|
326
|
+
isError: true,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
this.logger.error(`Tool execution failed: ${request.params.name}`, { error });
|
|
330
|
+
return {
|
|
331
|
+
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
332
|
+
isError: true,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// Register resource handlers if resources are configured
|
|
338
|
+
if (this.resourceOptions) {
|
|
339
|
+
this.registerResourceHandlersOnServer(serverInstance);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Register prompt handlers if prompts are configured
|
|
343
|
+
if (this.promptOptions) {
|
|
344
|
+
this.registerPromptHandlersOnServer(serverInstance);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Registers resource-related handlers on a server instance.
|
|
350
|
+
*/
|
|
351
|
+
private registerResourceHandlersOnServer(serverInstance: Server) {
|
|
352
|
+
const capturedResourceOptions = this.resourceOptions;
|
|
353
|
+
if (!capturedResourceOptions) return;
|
|
354
|
+
|
|
355
|
+
// List resources handler
|
|
356
|
+
if (capturedResourceOptions.listResources) {
|
|
357
|
+
serverInstance.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
358
|
+
this.logger.debug('Handling ListResources request');
|
|
359
|
+
if (this.definedResources) {
|
|
360
|
+
return { resources: this.definedResources };
|
|
361
|
+
} else {
|
|
362
|
+
try {
|
|
363
|
+
const resources = await capturedResourceOptions.listResources!();
|
|
364
|
+
this.definedResources = resources;
|
|
365
|
+
this.logger.debug(`Fetched and cached ${this.definedResources.length} resources.`);
|
|
366
|
+
return { resources: this.definedResources };
|
|
367
|
+
} catch (error) {
|
|
368
|
+
this.logger.error('Error fetching resources via listResources():', { error });
|
|
369
|
+
throw error;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Read resource handler
|
|
376
|
+
if (capturedResourceOptions.getResourceContent) {
|
|
377
|
+
serverInstance.setRequestHandler(ReadResourceRequestSchema, async request => {
|
|
378
|
+
const startTime = Date.now();
|
|
379
|
+
const uri = request.params.uri;
|
|
380
|
+
this.logger.debug(`Handling ReadResource request for URI: ${uri}`);
|
|
381
|
+
|
|
382
|
+
if (!this.definedResources) {
|
|
383
|
+
const resources = await this.resourceOptions?.listResources?.();
|
|
384
|
+
if (!resources) throw new Error('Failed to load resources');
|
|
385
|
+
this.definedResources = resources;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const resource = this.definedResources?.find(r => r.uri === uri);
|
|
389
|
+
|
|
390
|
+
if (!resource) {
|
|
391
|
+
this.logger.warn(`ReadResource: Unknown resource URI '${uri}' requested.`);
|
|
392
|
+
throw new Error(`Resource not found: ${uri}`);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
try {
|
|
396
|
+
const resourcesOrResourceContent = await capturedResourceOptions.getResourceContent({ uri });
|
|
397
|
+
const resourcesContent = Array.isArray(resourcesOrResourceContent)
|
|
398
|
+
? resourcesOrResourceContent
|
|
399
|
+
: [resourcesOrResourceContent];
|
|
400
|
+
const contents: ResourceContents[] = resourcesContent.map(resourceContent => {
|
|
401
|
+
const contentItem: ResourceContents = {
|
|
402
|
+
uri: resource.uri,
|
|
403
|
+
mimeType: resource.mimeType,
|
|
404
|
+
};
|
|
405
|
+
if ('text' in resourceContent) {
|
|
406
|
+
contentItem.text = resourceContent.text;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if ('blob' in resourceContent) {
|
|
410
|
+
contentItem.blob = resourceContent.blob;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return contentItem;
|
|
414
|
+
});
|
|
415
|
+
const duration = Date.now() - startTime;
|
|
416
|
+
this.logger.info(`Resource '${uri}' read successfully in ${duration}ms.`);
|
|
417
|
+
return {
|
|
418
|
+
contents,
|
|
419
|
+
};
|
|
420
|
+
} catch (error) {
|
|
421
|
+
const duration = Date.now() - startTime;
|
|
422
|
+
this.logger.error(`Failed to get content for resource URI '${uri}' in ${duration}ms`, { error });
|
|
423
|
+
throw error;
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Resource templates handler
|
|
429
|
+
if (capturedResourceOptions.resourceTemplates) {
|
|
430
|
+
serverInstance.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
|
|
431
|
+
this.logger.debug('Handling ListResourceTemplates request');
|
|
432
|
+
if (this.definedResourceTemplates) {
|
|
433
|
+
return { resourceTemplates: this.definedResourceTemplates };
|
|
434
|
+
} else {
|
|
435
|
+
try {
|
|
436
|
+
const templates = await capturedResourceOptions.resourceTemplates!();
|
|
437
|
+
this.definedResourceTemplates = templates;
|
|
438
|
+
this.logger.debug(`Fetched and cached ${this.definedResourceTemplates.length} resource templates.`);
|
|
439
|
+
return { resourceTemplates: this.definedResourceTemplates };
|
|
440
|
+
} catch (error) {
|
|
441
|
+
this.logger.error('Error fetching resource templates via resourceTemplates():', { error });
|
|
442
|
+
throw error;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Subscribe/unsubscribe handlers
|
|
449
|
+
serverInstance.setRequestHandler(SubscribeRequestSchema, async (request: { params: { uri: string } }) => {
|
|
450
|
+
const uri = request.params.uri;
|
|
451
|
+
this.logger.info(`Received resources/subscribe request for URI: ${uri}`);
|
|
452
|
+
this.subscriptions.add(uri);
|
|
453
|
+
return {};
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
serverInstance.setRequestHandler(UnsubscribeRequestSchema, async (request: { params: { uri: string } }) => {
|
|
457
|
+
const uri = request.params.uri;
|
|
458
|
+
this.logger.info(`Received resources/unsubscribe request for URI: ${uri}`);
|
|
459
|
+
this.subscriptions.delete(uri);
|
|
460
|
+
return {};
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Registers prompt-related handlers on a server instance.
|
|
466
|
+
*/
|
|
467
|
+
private registerPromptHandlersOnServer(serverInstance: Server) {
|
|
468
|
+
const capturedPromptOptions = this.promptOptions;
|
|
469
|
+
if (!capturedPromptOptions) return;
|
|
470
|
+
|
|
471
|
+
// List prompts handler
|
|
472
|
+
if (capturedPromptOptions.listPrompts) {
|
|
473
|
+
serverInstance.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
474
|
+
this.logger.debug('Handling ListPrompts request');
|
|
475
|
+
if (this.definedPrompts) {
|
|
476
|
+
return {
|
|
477
|
+
prompts: this.definedPrompts?.map(p => ({ ...p, version: p.version ?? undefined })),
|
|
478
|
+
};
|
|
479
|
+
} else {
|
|
480
|
+
try {
|
|
481
|
+
const prompts = await capturedPromptOptions.listPrompts();
|
|
482
|
+
for (const prompt of prompts) {
|
|
483
|
+
PromptSchema.parse(prompt);
|
|
484
|
+
}
|
|
485
|
+
this.definedPrompts = prompts;
|
|
486
|
+
this.logger.debug(`Fetched and cached ${this.definedPrompts.length} prompts.`);
|
|
487
|
+
return {
|
|
488
|
+
prompts: this.definedPrompts?.map(p => ({ ...p, version: p.version ?? undefined })),
|
|
489
|
+
};
|
|
490
|
+
} catch (error) {
|
|
491
|
+
this.logger.error('Error fetching prompts via listPrompts():', {
|
|
492
|
+
error: error instanceof Error ? error.message : String(error),
|
|
493
|
+
});
|
|
494
|
+
throw error;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Get prompt handler
|
|
501
|
+
if (capturedPromptOptions.getPromptMessages) {
|
|
502
|
+
serverInstance.setRequestHandler(
|
|
503
|
+
GetPromptRequestSchema,
|
|
504
|
+
async (request: { params: { name: string; version?: string; arguments?: any } }) => {
|
|
505
|
+
const startTime = Date.now();
|
|
506
|
+
const { name, version, arguments: args } = request.params;
|
|
507
|
+
if (!this.definedPrompts) {
|
|
508
|
+
const prompts = await this.promptOptions?.listPrompts?.();
|
|
509
|
+
if (!prompts) throw new Error('Failed to load prompts');
|
|
510
|
+
this.definedPrompts = prompts;
|
|
511
|
+
}
|
|
512
|
+
// Select prompt by name and version (if provided)
|
|
513
|
+
let prompt;
|
|
514
|
+
if (version) {
|
|
515
|
+
prompt = this.definedPrompts?.find(p => p.name === name && p.version === version);
|
|
516
|
+
} else {
|
|
517
|
+
// Select the first matching name if no version is provided.
|
|
518
|
+
prompt = this.definedPrompts?.find(p => p.name === name);
|
|
519
|
+
}
|
|
520
|
+
if (!prompt) throw new Error(`Prompt "${name}"${version ? ` (version ${version})` : ''} not found`);
|
|
521
|
+
// Validate required arguments
|
|
522
|
+
if (prompt.arguments) {
|
|
523
|
+
for (const arg of prompt.arguments) {
|
|
524
|
+
if (arg.required && (args?.[arg.name] === undefined || args?.[arg.name] === null)) {
|
|
525
|
+
throw new Error(`Missing required argument: ${arg.name}`);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
try {
|
|
530
|
+
let messages: any[] = [];
|
|
531
|
+
if (capturedPromptOptions.getPromptMessages) {
|
|
532
|
+
messages = await capturedPromptOptions.getPromptMessages({ name, version, args });
|
|
533
|
+
}
|
|
534
|
+
const duration = Date.now() - startTime;
|
|
535
|
+
this.logger.info(
|
|
536
|
+
`Prompt '${name}'${version ? ` (version ${version})` : ''} retrieved successfully in ${duration}ms.`,
|
|
537
|
+
);
|
|
538
|
+
return { prompt, messages };
|
|
539
|
+
} catch (error) {
|
|
540
|
+
const duration = Date.now() - startTime;
|
|
541
|
+
this.logger.error(`Failed to get content for prompt '${name}' in ${duration}ms`, { error });
|
|
542
|
+
throw error;
|
|
543
|
+
}
|
|
544
|
+
},
|
|
545
|
+
);
|
|
546
|
+
}
|
|
178
547
|
}
|
|
179
548
|
|
|
180
549
|
private convertAgentsToTools(
|
|
@@ -315,6 +684,7 @@ export class MCPServer extends MCPServerBase {
|
|
|
315
684
|
runtimeContext: new RuntimeContext(),
|
|
316
685
|
description: workflowToolDefinition.description,
|
|
317
686
|
};
|
|
687
|
+
|
|
318
688
|
const coreTool = makeCoreTool(workflowToolDefinition, options) as InternalCoreTool;
|
|
319
689
|
|
|
320
690
|
workflowTools[workflowToolName] = {
|
|
@@ -412,417 +782,6 @@ export class MCPServer extends MCPServerBase {
|
|
|
412
782
|
return allConvertedTools;
|
|
413
783
|
}
|
|
414
784
|
|
|
415
|
-
/**
|
|
416
|
-
* Register the ListTools handler for listing all available tools.
|
|
417
|
-
*/
|
|
418
|
-
private registerListToolsHandler() {
|
|
419
|
-
if (this.listToolsHandlerIsRegistered) {
|
|
420
|
-
return;
|
|
421
|
-
}
|
|
422
|
-
this.listToolsHandlerIsRegistered = true;
|
|
423
|
-
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
424
|
-
this.logger.debug('Handling ListTools request');
|
|
425
|
-
return {
|
|
426
|
-
tools: Object.values(this.convertedTools).map(tool => {
|
|
427
|
-
const toolSpec: any = {
|
|
428
|
-
name: tool.name,
|
|
429
|
-
description: tool.description,
|
|
430
|
-
inputSchema: tool.parameters.jsonSchema,
|
|
431
|
-
};
|
|
432
|
-
if (tool.outputSchema) {
|
|
433
|
-
toolSpec.outputSchema = tool.outputSchema.jsonSchema;
|
|
434
|
-
}
|
|
435
|
-
return toolSpec;
|
|
436
|
-
}),
|
|
437
|
-
};
|
|
438
|
-
});
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
/**
|
|
442
|
-
* Register the CallTool handler for executing a tool by name.
|
|
443
|
-
*/
|
|
444
|
-
private registerCallToolHandler() {
|
|
445
|
-
if (this.callToolHandlerIsRegistered) {
|
|
446
|
-
return;
|
|
447
|
-
}
|
|
448
|
-
this.callToolHandlerIsRegistered = true;
|
|
449
|
-
this.server.setRequestHandler(CallToolRequestSchema, async request => {
|
|
450
|
-
const startTime = Date.now();
|
|
451
|
-
try {
|
|
452
|
-
const tool = this.convertedTools[request.params.name];
|
|
453
|
-
if (!tool) {
|
|
454
|
-
this.logger.warn(`CallTool: Unknown tool '${request.params.name}' requested.`);
|
|
455
|
-
return {
|
|
456
|
-
content: [{ type: 'text', text: `Unknown tool: ${request.params.name}` }],
|
|
457
|
-
isError: true,
|
|
458
|
-
};
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
const validation = tool.parameters.validate?.(request.params.arguments ?? {});
|
|
462
|
-
if (validation && !validation.success) {
|
|
463
|
-
this.logger.warn(`CallTool: Invalid tool arguments for '${request.params.name}'`, {
|
|
464
|
-
errors: validation.error,
|
|
465
|
-
});
|
|
466
|
-
return {
|
|
467
|
-
content: [{ type: 'text', text: `Invalid tool arguments: ${JSON.stringify(validation.error)}` }],
|
|
468
|
-
isError: true,
|
|
469
|
-
};
|
|
470
|
-
}
|
|
471
|
-
if (!tool.execute) {
|
|
472
|
-
this.logger.warn(`CallTool: Tool '${request.params.name}' does not have an execute function.`);
|
|
473
|
-
return {
|
|
474
|
-
content: [{ type: 'text', text: `Tool '${request.params.name}' does not have an execute function.` }],
|
|
475
|
-
isError: true,
|
|
476
|
-
};
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
const result = await tool.execute(validation?.value, { messages: [], toolCallId: '' });
|
|
480
|
-
|
|
481
|
-
this.logger.debug(`CallTool: Tool '${request.params.name}' executed successfully with result:`, result);
|
|
482
|
-
const duration = Date.now() - startTime;
|
|
483
|
-
this.logger.info(`Tool '${request.params.name}' executed successfully in ${duration}ms.`);
|
|
484
|
-
|
|
485
|
-
const response: CallToolResult = { isError: false, content: [] };
|
|
486
|
-
|
|
487
|
-
if (tool.outputSchema) {
|
|
488
|
-
if (!result.structuredContent) {
|
|
489
|
-
throw new Error(`Tool ${request.params.name} has an output schema but no structured content was provided.`);
|
|
490
|
-
}
|
|
491
|
-
const outputValidation = tool.outputSchema.validate?.(result.structuredContent ?? {});
|
|
492
|
-
if (outputValidation && !outputValidation.success) {
|
|
493
|
-
this.logger.warn(`CallTool: Invalid structured content for '${request.params.name}'`, {
|
|
494
|
-
errors: outputValidation.error,
|
|
495
|
-
});
|
|
496
|
-
throw new Error(
|
|
497
|
-
`Invalid structured content for tool ${request.params.name}: ${JSON.stringify(outputValidation.error)}`,
|
|
498
|
-
);
|
|
499
|
-
}
|
|
500
|
-
response.structuredContent = result.structuredContent;
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
if (result.content) {
|
|
504
|
-
response.content = result.content;
|
|
505
|
-
} else if (response.structuredContent) {
|
|
506
|
-
response.content = [{ type: 'text', text: JSON.stringify(response.structuredContent) }];
|
|
507
|
-
} else {
|
|
508
|
-
response.content = [
|
|
509
|
-
{
|
|
510
|
-
type: 'text',
|
|
511
|
-
text: typeof result === 'string' ? result : JSON.stringify(result),
|
|
512
|
-
},
|
|
513
|
-
];
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
return response;
|
|
517
|
-
} catch (error) {
|
|
518
|
-
const duration = Date.now() - startTime;
|
|
519
|
-
if (error instanceof z.ZodError) {
|
|
520
|
-
this.logger.warn('Invalid tool arguments', {
|
|
521
|
-
tool: request.params.name,
|
|
522
|
-
errors: error.errors,
|
|
523
|
-
duration: `${duration}ms`,
|
|
524
|
-
});
|
|
525
|
-
return {
|
|
526
|
-
content: [
|
|
527
|
-
{
|
|
528
|
-
type: 'text',
|
|
529
|
-
text: `Invalid arguments: ${error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ')}`,
|
|
530
|
-
},
|
|
531
|
-
],
|
|
532
|
-
isError: true,
|
|
533
|
-
};
|
|
534
|
-
}
|
|
535
|
-
this.logger.error(`Tool execution failed: ${request.params.name}`, { error });
|
|
536
|
-
return {
|
|
537
|
-
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
538
|
-
isError: true,
|
|
539
|
-
};
|
|
540
|
-
}
|
|
541
|
-
});
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
/**
|
|
545
|
-
* Register the ListResources handler for listing all available resources.
|
|
546
|
-
*/
|
|
547
|
-
private registerListResourcesHandler() {
|
|
548
|
-
if (this.listResourcesHandlerIsRegistered) {
|
|
549
|
-
return;
|
|
550
|
-
}
|
|
551
|
-
this.listResourcesHandlerIsRegistered = true;
|
|
552
|
-
const capturedResourceOptions = this.resourceOptions; // Capture for TS narrowing
|
|
553
|
-
|
|
554
|
-
if (!capturedResourceOptions?.listResources) {
|
|
555
|
-
this.logger.warn('ListResources capability not supported by server configuration.');
|
|
556
|
-
return;
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
560
|
-
this.logger.debug('Handling ListResources request');
|
|
561
|
-
if (this.definedResources) {
|
|
562
|
-
return { resources: this.definedResources };
|
|
563
|
-
} else {
|
|
564
|
-
try {
|
|
565
|
-
const resources = await capturedResourceOptions.listResources();
|
|
566
|
-
// Cache the resources
|
|
567
|
-
this.definedResources = resources;
|
|
568
|
-
this.logger.debug(`Fetched and cached ${this.definedResources.length} resources.`);
|
|
569
|
-
return { resources: this.definedResources };
|
|
570
|
-
} catch (error) {
|
|
571
|
-
this.logger.error('Error fetching resources via listResources():', { error });
|
|
572
|
-
// Re-throw to let the MCP Server SDK handle formatting the error response
|
|
573
|
-
throw error;
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
});
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
/**
|
|
580
|
-
* Register the ReadResource handler for reading a resource by URI.
|
|
581
|
-
*/
|
|
582
|
-
private registerReadResourceHandler({
|
|
583
|
-
getResourcesCallback,
|
|
584
|
-
}: {
|
|
585
|
-
getResourcesCallback: MCPServerResourceContentCallback;
|
|
586
|
-
}) {
|
|
587
|
-
if (this.readResourceHandlerIsRegistered) {
|
|
588
|
-
return;
|
|
589
|
-
}
|
|
590
|
-
this.readResourceHandlerIsRegistered = true;
|
|
591
|
-
this.server.setRequestHandler(ReadResourceRequestSchema, async request => {
|
|
592
|
-
const startTime = Date.now();
|
|
593
|
-
const uri = request.params.uri;
|
|
594
|
-
this.logger.debug(`Handling ReadResource request for URI: ${uri}`);
|
|
595
|
-
|
|
596
|
-
if (!this.definedResources) {
|
|
597
|
-
const resources = await this.resourceOptions?.listResources?.();
|
|
598
|
-
if (!resources) throw new Error('Failed to load resources');
|
|
599
|
-
this.definedResources = resources;
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
const resource = this.definedResources?.find(r => r.uri === uri);
|
|
603
|
-
|
|
604
|
-
if (!resource) {
|
|
605
|
-
this.logger.warn(`ReadResource: Unknown resource URI '${uri}' requested.`);
|
|
606
|
-
throw new Error(`Resource not found: ${uri}`);
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
try {
|
|
610
|
-
const resourcesOrResourceContent = await getResourcesCallback({ uri });
|
|
611
|
-
const resourcesContent = Array.isArray(resourcesOrResourceContent)
|
|
612
|
-
? resourcesOrResourceContent
|
|
613
|
-
: [resourcesOrResourceContent];
|
|
614
|
-
const contents: ResourceContents[] = resourcesContent.map(resourceContent => {
|
|
615
|
-
const contentItem: ResourceContents = {
|
|
616
|
-
uri: resource.uri,
|
|
617
|
-
mimeType: resource.mimeType,
|
|
618
|
-
};
|
|
619
|
-
if ('text' in resourceContent) {
|
|
620
|
-
contentItem.text = resourceContent.text;
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
if ('blob' in resourceContent) {
|
|
624
|
-
contentItem.blob = resourceContent.blob;
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
return contentItem;
|
|
628
|
-
});
|
|
629
|
-
const duration = Date.now() - startTime;
|
|
630
|
-
this.logger.info(`Resource '${uri}' read successfully in ${duration}ms.`);
|
|
631
|
-
return {
|
|
632
|
-
contents,
|
|
633
|
-
};
|
|
634
|
-
} catch (error) {
|
|
635
|
-
const duration = Date.now() - startTime;
|
|
636
|
-
this.logger.error(`Failed to get content for resource URI '${uri}' in ${duration}ms`, { error });
|
|
637
|
-
throw error;
|
|
638
|
-
}
|
|
639
|
-
});
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
/**
|
|
643
|
-
* Register the ListResourceTemplates handler.
|
|
644
|
-
*/
|
|
645
|
-
private registerListResourceTemplatesHandler() {
|
|
646
|
-
if (this.listResourceTemplatesHandlerIsRegistered) {
|
|
647
|
-
return;
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
// If this method is called, this.resourceOptions and this.resourceOptions.resourceTemplates should exist
|
|
651
|
-
// due to the constructor logic checking opts.resources.resourceTemplates.
|
|
652
|
-
if (!this.resourceOptions || typeof this.resourceOptions.resourceTemplates !== 'function') {
|
|
653
|
-
this.logger.warn(
|
|
654
|
-
'ListResourceTemplates handler called, but resourceTemplates function is not available on resourceOptions or not a function.',
|
|
655
|
-
);
|
|
656
|
-
// Register a handler that returns empty templates if not properly configured.
|
|
657
|
-
this.server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
|
|
658
|
-
this.logger.debug('Handling ListResourceTemplates request (no templates configured or resourceOptions issue)');
|
|
659
|
-
return { resourceTemplates: [] };
|
|
660
|
-
});
|
|
661
|
-
this.listResourceTemplatesHandlerIsRegistered = true;
|
|
662
|
-
return;
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
// Typescript can now infer resourceTemplatesFn is a function.
|
|
666
|
-
const resourceTemplatesFn = this.resourceOptions.resourceTemplates;
|
|
667
|
-
|
|
668
|
-
this.listResourceTemplatesHandlerIsRegistered = true;
|
|
669
|
-
this.server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
|
|
670
|
-
this.logger.debug('Handling ListResourceTemplates request');
|
|
671
|
-
if (this.definedResourceTemplates) {
|
|
672
|
-
return { resourceTemplates: this.definedResourceTemplates };
|
|
673
|
-
} else {
|
|
674
|
-
try {
|
|
675
|
-
const templates = await resourceTemplatesFn(); // Safe to call now
|
|
676
|
-
this.definedResourceTemplates = templates;
|
|
677
|
-
this.logger.debug(`Fetched and cached ${this.definedResourceTemplates.length} resource templates.`);
|
|
678
|
-
return { resourceTemplates: this.definedResourceTemplates };
|
|
679
|
-
} catch (error) {
|
|
680
|
-
this.logger.error('Error fetching resource templates via resourceTemplates():', { error });
|
|
681
|
-
// Re-throw to let the MCP Server SDK handle formatting the error response
|
|
682
|
-
throw error;
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
});
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
/**
|
|
689
|
-
* Register the SubscribeResource handler.
|
|
690
|
-
*/
|
|
691
|
-
private registerSubscribeResourceHandler() {
|
|
692
|
-
if (this.subscribeResourceHandlerIsRegistered) {
|
|
693
|
-
return;
|
|
694
|
-
}
|
|
695
|
-
if (!SubscribeRequestSchema) {
|
|
696
|
-
this.logger.warn('SubscribeRequestSchema not available, cannot register SubscribeResource handler.');
|
|
697
|
-
return;
|
|
698
|
-
}
|
|
699
|
-
this.subscribeResourceHandlerIsRegistered = true;
|
|
700
|
-
this.server.setRequestHandler(SubscribeRequestSchema, async (request: { params: { uri: string } }) => {
|
|
701
|
-
const uri = request.params.uri;
|
|
702
|
-
this.logger.info(`Received resources/subscribe request for URI: ${uri}`);
|
|
703
|
-
this.subscriptions.add(uri);
|
|
704
|
-
return {};
|
|
705
|
-
});
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
/**
|
|
709
|
-
* Register the UnsubscribeResource handler.
|
|
710
|
-
*/
|
|
711
|
-
private registerUnsubscribeResourceHandler() {
|
|
712
|
-
if (this.unsubscribeResourceHandlerIsRegistered) {
|
|
713
|
-
return;
|
|
714
|
-
}
|
|
715
|
-
this.unsubscribeResourceHandlerIsRegistered = true;
|
|
716
|
-
|
|
717
|
-
this.server.setRequestHandler(UnsubscribeRequestSchema, async (request: { params: { uri: string } }) => {
|
|
718
|
-
const uri = request.params.uri;
|
|
719
|
-
this.logger.info(`Received resources/unsubscribe request for URI: ${uri}`);
|
|
720
|
-
this.subscriptions.delete(uri);
|
|
721
|
-
return {};
|
|
722
|
-
});
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
/**
|
|
726
|
-
* Register the ListPrompts handler.
|
|
727
|
-
*/
|
|
728
|
-
private registerListPromptsHandler() {
|
|
729
|
-
if (this.listPromptsHandlerIsRegistered) {
|
|
730
|
-
return;
|
|
731
|
-
}
|
|
732
|
-
this.listPromptsHandlerIsRegistered = true;
|
|
733
|
-
const capturedPromptOptions = this.promptOptions;
|
|
734
|
-
|
|
735
|
-
if (!capturedPromptOptions?.listPrompts) {
|
|
736
|
-
this.logger.warn('ListPrompts capability not supported by server configuration.');
|
|
737
|
-
return;
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
this.server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
741
|
-
this.logger.debug('Handling ListPrompts request');
|
|
742
|
-
if (this.definedPrompts) {
|
|
743
|
-
return {
|
|
744
|
-
prompts: this.definedPrompts?.map(p => ({ ...p, version: p.version ?? undefined })),
|
|
745
|
-
};
|
|
746
|
-
} else {
|
|
747
|
-
try {
|
|
748
|
-
const prompts = await capturedPromptOptions.listPrompts();
|
|
749
|
-
// Parse and cache the prompts
|
|
750
|
-
for (const prompt of prompts) {
|
|
751
|
-
PromptSchema.parse(prompt);
|
|
752
|
-
}
|
|
753
|
-
this.definedPrompts = prompts;
|
|
754
|
-
this.logger.debug(`Fetched and cached ${this.definedPrompts.length} prompts.`);
|
|
755
|
-
return {
|
|
756
|
-
prompts: this.definedPrompts?.map(p => ({ ...p, version: p.version ?? undefined })),
|
|
757
|
-
};
|
|
758
|
-
} catch (error) {
|
|
759
|
-
this.logger.error('Error fetching prompts via listPrompts():', {
|
|
760
|
-
error: error instanceof Error ? error.message : String(error),
|
|
761
|
-
});
|
|
762
|
-
// Re-throw to let the MCP Server SDK handle formatting the error response
|
|
763
|
-
throw error;
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
});
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
/**
|
|
770
|
-
* Register the GetPrompt handler.
|
|
771
|
-
*/
|
|
772
|
-
private registerGetPromptHandler({
|
|
773
|
-
getPromptMessagesCallback,
|
|
774
|
-
}: {
|
|
775
|
-
getPromptMessagesCallback?: MCPServerPromptMessagesCallback;
|
|
776
|
-
}) {
|
|
777
|
-
if (this.getPromptHandlerIsRegistered) return;
|
|
778
|
-
this.getPromptHandlerIsRegistered = true;
|
|
779
|
-
// Accept optional version parameter in prompts/get
|
|
780
|
-
this.server.setRequestHandler(
|
|
781
|
-
GetPromptRequestSchema,
|
|
782
|
-
async (request: { params: { name: string; version?: string; arguments?: any } }) => {
|
|
783
|
-
const startTime = Date.now();
|
|
784
|
-
const { name, version, arguments: args } = request.params;
|
|
785
|
-
if (!this.definedPrompts) {
|
|
786
|
-
const prompts = await this.promptOptions?.listPrompts?.();
|
|
787
|
-
if (!prompts) throw new Error('Failed to load prompts');
|
|
788
|
-
this.definedPrompts = prompts;
|
|
789
|
-
}
|
|
790
|
-
// Select prompt by name and version (if provided)
|
|
791
|
-
let prompt;
|
|
792
|
-
if (version) {
|
|
793
|
-
prompt = this.definedPrompts?.find(p => p.name === name && p.version === version);
|
|
794
|
-
} else {
|
|
795
|
-
// Select the first matching name if no version is provided.
|
|
796
|
-
prompt = this.definedPrompts?.find(p => p.name === name);
|
|
797
|
-
}
|
|
798
|
-
if (!prompt) throw new Error(`Prompt "${name}"${version ? ` (version ${version})` : ''} not found`);
|
|
799
|
-
// Validate required arguments
|
|
800
|
-
if (prompt.arguments) {
|
|
801
|
-
for (const arg of prompt.arguments) {
|
|
802
|
-
if (arg.required && (args?.[arg.name] === undefined || args?.[arg.name] === null)) {
|
|
803
|
-
throw new Error(`Missing required argument: ${arg.name}`);
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
try {
|
|
808
|
-
let messages: any[] = [];
|
|
809
|
-
if (getPromptMessagesCallback) {
|
|
810
|
-
messages = await getPromptMessagesCallback({ name, version, args });
|
|
811
|
-
}
|
|
812
|
-
const duration = Date.now() - startTime;
|
|
813
|
-
this.logger.info(
|
|
814
|
-
`Prompt '${name}'${version ? ` (version ${version})` : ''} retrieved successfully in ${duration}ms.`,
|
|
815
|
-
);
|
|
816
|
-
return { prompt, messages };
|
|
817
|
-
} catch (error) {
|
|
818
|
-
const duration = Date.now() - startTime;
|
|
819
|
-
this.logger.error(`Failed to get content for prompt '${name}' in ${duration}ms`, { error });
|
|
820
|
-
throw error;
|
|
821
|
-
}
|
|
822
|
-
},
|
|
823
|
-
);
|
|
824
|
-
}
|
|
825
|
-
|
|
826
785
|
/**
|
|
827
786
|
* Start the MCP server using stdio transport (for Windsurf integration).
|
|
828
787
|
*/
|
|
@@ -1067,15 +1026,24 @@ export class MCPServer extends MCPServerBase {
|
|
|
1067
1026
|
`startHTTP: Streamable HTTP transport closed for session ${closedSessionId}, removing from map.`,
|
|
1068
1027
|
);
|
|
1069
1028
|
this.streamableHTTPTransports.delete(closedSessionId);
|
|
1029
|
+
// Also clean up the server instance for this session
|
|
1030
|
+
if (this.httpServerInstances.has(closedSessionId)) {
|
|
1031
|
+
this.httpServerInstances.delete(closedSessionId);
|
|
1032
|
+
this.logger.debug(`startHTTP: Cleaned up server instance for closed session ${closedSessionId}`);
|
|
1033
|
+
}
|
|
1070
1034
|
}
|
|
1071
1035
|
};
|
|
1072
1036
|
|
|
1073
|
-
//
|
|
1074
|
-
|
|
1037
|
+
// Create a new server instance for this HTTP session
|
|
1038
|
+
const sessionServerInstance = this.createServerInstance();
|
|
1039
|
+
|
|
1040
|
+
// Connect the new server instance to the new transport
|
|
1041
|
+
await sessionServerInstance.connect(transport);
|
|
1075
1042
|
|
|
1076
|
-
// Store the transport when the session is initialized
|
|
1043
|
+
// Store both the transport and server instance when the session is initialized
|
|
1077
1044
|
if (transport.sessionId) {
|
|
1078
1045
|
this.streamableHTTPTransports.set(transport.sessionId, transport);
|
|
1046
|
+
this.httpServerInstances.set(transport.sessionId, sessionServerInstance);
|
|
1079
1047
|
this.logger.debug(
|
|
1080
1048
|
`startHTTP: Streamable HTTP session initialized and stored with ID: ${transport.sessionId}`,
|
|
1081
1049
|
);
|
|
@@ -1232,14 +1200,6 @@ export class MCPServer extends MCPServerBase {
|
|
|
1232
1200
|
* Close the MCP server and all its connections
|
|
1233
1201
|
*/
|
|
1234
1202
|
async close() {
|
|
1235
|
-
this.callToolHandlerIsRegistered = false;
|
|
1236
|
-
this.listToolsHandlerIsRegistered = false;
|
|
1237
|
-
this.listResourcesHandlerIsRegistered = false;
|
|
1238
|
-
this.readResourceHandlerIsRegistered = false;
|
|
1239
|
-
this.listResourceTemplatesHandlerIsRegistered = false;
|
|
1240
|
-
this.subscribeResourceHandlerIsRegistered = false;
|
|
1241
|
-
this.unsubscribeResourceHandlerIsRegistered = false;
|
|
1242
|
-
|
|
1243
1203
|
try {
|
|
1244
1204
|
if (this.stdioTransport) {
|
|
1245
1205
|
await this.stdioTransport.close?.();
|
|
@@ -1255,13 +1215,20 @@ export class MCPServer extends MCPServerBase {
|
|
|
1255
1215
|
}
|
|
1256
1216
|
this.sseHonoTransports.clear();
|
|
1257
1217
|
}
|
|
1258
|
-
// Close all active Streamable HTTP transports
|
|
1218
|
+
// Close all active Streamable HTTP transports and their server instances
|
|
1259
1219
|
if (this.streamableHTTPTransports) {
|
|
1260
1220
|
for (const transport of this.streamableHTTPTransports.values()) {
|
|
1261
1221
|
await transport.close?.();
|
|
1262
1222
|
}
|
|
1263
1223
|
this.streamableHTTPTransports.clear();
|
|
1264
1224
|
}
|
|
1225
|
+
// Close all HTTP server instances
|
|
1226
|
+
if (this.httpServerInstances) {
|
|
1227
|
+
for (const serverInstance of this.httpServerInstances.values()) {
|
|
1228
|
+
await serverInstance.close?.();
|
|
1229
|
+
}
|
|
1230
|
+
this.httpServerInstances.clear();
|
|
1231
|
+
}
|
|
1265
1232
|
await this.server.close();
|
|
1266
1233
|
this.logger.info('MCP server closed.');
|
|
1267
1234
|
} catch (error) {
|