@mastra/mcp 0.10.5-alpha.0 → 0.10.5-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/.turbo/turbo-build.log +7 -7
- package/CHANGELOG.md +23 -0
- package/dist/_tsup-dts-rollup.d.cts +167 -126
- package/dist/_tsup-dts-rollup.d.ts +167 -126
- package/dist/index.cjs +624 -391
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +620 -387
- package/integration-tests/node_modules/.bin/vitest +2 -2
- package/integration-tests/package.json +2 -2
- package/package.json +7 -7
- package/src/__fixtures__/tools.ts +0 -9
- package/src/client/client.test.ts +310 -1
- package/src/client/client.ts +66 -3
- 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 +433 -0
- package/src/server/server.ts +431 -429
- package/src/server/types.ts +25 -1
package/src/server/server.ts
CHANGED
|
@@ -39,6 +39,9 @@ import type {
|
|
|
39
39
|
ResourceTemplate,
|
|
40
40
|
ServerCapabilities,
|
|
41
41
|
Prompt,
|
|
42
|
+
CallToolResult,
|
|
43
|
+
ElicitResult,
|
|
44
|
+
ElicitRequest,
|
|
42
45
|
} from '@modelcontextprotocol/sdk/types.js';
|
|
43
46
|
import type { SSEStreamingApi } from 'hono/streaming';
|
|
44
47
|
import { streamSSE } from 'hono/streaming';
|
|
@@ -46,29 +49,15 @@ import { SSETransport } from 'hono-mcp-server-sse-transport';
|
|
|
46
49
|
import { z } from 'zod';
|
|
47
50
|
import { ServerPromptActions } from './promptActions';
|
|
48
51
|
import { ServerResourceActions } from './resourceActions';
|
|
49
|
-
import type {
|
|
50
|
-
MCPServerPromptMessagesCallback,
|
|
51
|
-
MCPServerPrompts,
|
|
52
|
-
MCPServerResourceContentCallback,
|
|
53
|
-
MCPServerResources,
|
|
54
|
-
} from './types';
|
|
55
|
-
|
|
52
|
+
import type { MCPServerPrompts, MCPServerResources, ElicitationActions, MCPTool } from './types';
|
|
56
53
|
export class MCPServer extends MCPServerBase {
|
|
57
54
|
private server: Server;
|
|
58
55
|
private stdioTransport?: StdioServerTransport;
|
|
59
56
|
private sseTransport?: SSEServerTransport;
|
|
60
57
|
private sseHonoTransports: Map<string, SSETransport>;
|
|
61
58
|
private streamableHTTPTransports: Map<string, StreamableHTTPServerTransport> = new Map();
|
|
62
|
-
|
|
63
|
-
private
|
|
64
|
-
private listResourcesHandlerIsRegistered: boolean = false;
|
|
65
|
-
private readResourceHandlerIsRegistered: boolean = false;
|
|
66
|
-
private listResourceTemplatesHandlerIsRegistered: boolean = false;
|
|
67
|
-
private subscribeResourceHandlerIsRegistered: boolean = false;
|
|
68
|
-
private unsubscribeResourceHandlerIsRegistered: boolean = false;
|
|
69
|
-
|
|
70
|
-
private listPromptsHandlerIsRegistered: boolean = false;
|
|
71
|
-
private getPromptHandlerIsRegistered: boolean = false;
|
|
59
|
+
// Track server instances for each HTTP session
|
|
60
|
+
private httpServerInstances: Map<string, Server> = new Map();
|
|
72
61
|
|
|
73
62
|
private definedResources?: Resource[];
|
|
74
63
|
private definedResourceTemplates?: ResourceTemplate[];
|
|
@@ -78,6 +67,7 @@ export class MCPServer extends MCPServerBase {
|
|
|
78
67
|
private subscriptions: Set<string> = new Set();
|
|
79
68
|
public readonly resources: ServerResourceActions;
|
|
80
69
|
public readonly prompts: ServerPromptActions;
|
|
70
|
+
public readonly elicitation: ElicitationActions;
|
|
81
71
|
|
|
82
72
|
/**
|
|
83
73
|
* Get the current stdio transport.
|
|
@@ -119,6 +109,7 @@ export class MCPServer extends MCPServerBase {
|
|
|
119
109
|
const capabilities: ServerCapabilities = {
|
|
120
110
|
tools: {},
|
|
121
111
|
logging: { enabled: true },
|
|
112
|
+
elicitation: {},
|
|
122
113
|
};
|
|
123
114
|
|
|
124
115
|
if (opts.resources) {
|
|
@@ -136,24 +127,9 @@ export class MCPServer extends MCPServerBase {
|
|
|
136
127
|
);
|
|
137
128
|
|
|
138
129
|
this.sseHonoTransports = new Map();
|
|
139
|
-
this.registerListToolsHandler();
|
|
140
|
-
this.registerCallToolHandler();
|
|
141
|
-
if (opts.resources) {
|
|
142
|
-
this.registerListResourcesHandler();
|
|
143
|
-
this.registerReadResourceHandler({ getResourcesCallback: opts.resources.getResourceContent });
|
|
144
|
-
this.registerSubscribeResourceHandler();
|
|
145
|
-
this.registerUnsubscribeResourceHandler();
|
|
146
130
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
if (opts.prompts) {
|
|
152
|
-
this.registerListPromptsHandler();
|
|
153
|
-
this.registerGetPromptHandler({
|
|
154
|
-
getPromptMessagesCallback: opts.prompts.getPromptMessages,
|
|
155
|
-
});
|
|
156
|
-
}
|
|
131
|
+
// Register all handlers on the main server instance
|
|
132
|
+
this.registerHandlersOnServer(this.server);
|
|
157
133
|
|
|
158
134
|
this.resources = new ServerResourceActions({
|
|
159
135
|
getSubscriptions: () => this.subscriptions,
|
|
@@ -174,6 +150,400 @@ export class MCPServer extends MCPServerBase {
|
|
|
174
150
|
this.definedPrompts = undefined;
|
|
175
151
|
},
|
|
176
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
|
+
}
|
|
177
547
|
}
|
|
178
548
|
|
|
179
549
|
private convertAgentsToTools(
|
|
@@ -314,12 +684,14 @@ export class MCPServer extends MCPServerBase {
|
|
|
314
684
|
runtimeContext: new RuntimeContext(),
|
|
315
685
|
description: workflowToolDefinition.description,
|
|
316
686
|
};
|
|
687
|
+
|
|
317
688
|
const coreTool = makeCoreTool(workflowToolDefinition, options) as InternalCoreTool;
|
|
318
689
|
|
|
319
690
|
workflowTools[workflowToolName] = {
|
|
320
691
|
name: workflowToolName,
|
|
321
692
|
description: coreTool.description,
|
|
322
693
|
parameters: coreTool.parameters,
|
|
694
|
+
outputSchema: coreTool.outputSchema,
|
|
323
695
|
execute: coreTool.execute!,
|
|
324
696
|
toolType: 'workflow',
|
|
325
697
|
};
|
|
@@ -369,6 +741,7 @@ export class MCPServer extends MCPServerBase {
|
|
|
369
741
|
name: toolName,
|
|
370
742
|
description: coreTool.description,
|
|
371
743
|
parameters: coreTool.parameters,
|
|
744
|
+
outputSchema: coreTool.outputSchema,
|
|
372
745
|
execute: coreTool.execute!,
|
|
373
746
|
};
|
|
374
747
|
this.logger.info(`Registered explicit tool: '${toolName}'`);
|
|
@@ -409,387 +782,6 @@ export class MCPServer extends MCPServerBase {
|
|
|
409
782
|
return allConvertedTools;
|
|
410
783
|
}
|
|
411
784
|
|
|
412
|
-
/**
|
|
413
|
-
* Register the ListTools handler for listing all available tools.
|
|
414
|
-
*/
|
|
415
|
-
private registerListToolsHandler() {
|
|
416
|
-
if (this.listToolsHandlerIsRegistered) {
|
|
417
|
-
return;
|
|
418
|
-
}
|
|
419
|
-
this.listToolsHandlerIsRegistered = true;
|
|
420
|
-
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
421
|
-
this.logger.debug('Handling ListTools request');
|
|
422
|
-
return {
|
|
423
|
-
tools: Object.values(this.convertedTools).map(tool => ({
|
|
424
|
-
name: tool.name,
|
|
425
|
-
description: tool.description,
|
|
426
|
-
inputSchema: tool.parameters.jsonSchema,
|
|
427
|
-
})),
|
|
428
|
-
};
|
|
429
|
-
});
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
/**
|
|
433
|
-
* Register the CallTool handler for executing a tool by name.
|
|
434
|
-
*/
|
|
435
|
-
private registerCallToolHandler() {
|
|
436
|
-
if (this.callToolHandlerIsRegistered) {
|
|
437
|
-
return;
|
|
438
|
-
}
|
|
439
|
-
this.callToolHandlerIsRegistered = true;
|
|
440
|
-
this.server.setRequestHandler(CallToolRequestSchema, async request => {
|
|
441
|
-
const startTime = Date.now();
|
|
442
|
-
try {
|
|
443
|
-
const tool = this.convertedTools[request.params.name];
|
|
444
|
-
if (!tool) {
|
|
445
|
-
this.logger.warn(`CallTool: Unknown tool '${request.params.name}' requested.`);
|
|
446
|
-
return {
|
|
447
|
-
content: [{ type: 'text', text: `Unknown tool: ${request.params.name}` }],
|
|
448
|
-
isError: true,
|
|
449
|
-
};
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
this.logger.debug(`CallTool: Invoking '${request.params.name}' with arguments:`, request.params.arguments);
|
|
453
|
-
|
|
454
|
-
const validation = tool.parameters.validate?.(request.params.arguments ?? {});
|
|
455
|
-
if (validation && !validation.success) {
|
|
456
|
-
this.logger.warn(`CallTool: Invalid tool arguments for '${request.params.name}'`, {
|
|
457
|
-
errors: validation.error,
|
|
458
|
-
});
|
|
459
|
-
return {
|
|
460
|
-
content: [{ type: 'text', text: `Invalid tool arguments: ${JSON.stringify(validation.error)}` }],
|
|
461
|
-
isError: true,
|
|
462
|
-
};
|
|
463
|
-
}
|
|
464
|
-
if (!tool.execute) {
|
|
465
|
-
this.logger.warn(`CallTool: Tool '${request.params.name}' does not have an execute function.`);
|
|
466
|
-
return {
|
|
467
|
-
content: [{ type: 'text', text: `Tool '${request.params.name}' does not have an execute function.` }],
|
|
468
|
-
isError: true,
|
|
469
|
-
};
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
const result = await tool.execute(validation?.value, { messages: [], toolCallId: '' });
|
|
473
|
-
const duration = Date.now() - startTime;
|
|
474
|
-
this.logger.info(`Tool '${request.params.name}' executed successfully in ${duration}ms.`);
|
|
475
|
-
return {
|
|
476
|
-
content: [
|
|
477
|
-
{
|
|
478
|
-
type: 'text',
|
|
479
|
-
text: typeof result === 'string' ? result : JSON.stringify(result),
|
|
480
|
-
},
|
|
481
|
-
],
|
|
482
|
-
isError: false,
|
|
483
|
-
};
|
|
484
|
-
} catch (error) {
|
|
485
|
-
const duration = Date.now() - startTime;
|
|
486
|
-
if (error instanceof z.ZodError) {
|
|
487
|
-
this.logger.warn('Invalid tool arguments', {
|
|
488
|
-
tool: request.params.name,
|
|
489
|
-
errors: error.errors,
|
|
490
|
-
duration: `${duration}ms`,
|
|
491
|
-
});
|
|
492
|
-
return {
|
|
493
|
-
content: [
|
|
494
|
-
{
|
|
495
|
-
type: 'text',
|
|
496
|
-
text: `Invalid arguments: ${error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ')}`,
|
|
497
|
-
},
|
|
498
|
-
],
|
|
499
|
-
isError: true,
|
|
500
|
-
};
|
|
501
|
-
}
|
|
502
|
-
this.logger.error(`Tool execution failed: ${request.params.name}`, { error });
|
|
503
|
-
return {
|
|
504
|
-
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
|
|
505
|
-
isError: true,
|
|
506
|
-
};
|
|
507
|
-
}
|
|
508
|
-
});
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
/**
|
|
512
|
-
* Register the ListResources handler for listing all available resources.
|
|
513
|
-
*/
|
|
514
|
-
private registerListResourcesHandler() {
|
|
515
|
-
if (this.listResourcesHandlerIsRegistered) {
|
|
516
|
-
return;
|
|
517
|
-
}
|
|
518
|
-
this.listResourcesHandlerIsRegistered = true;
|
|
519
|
-
const capturedResourceOptions = this.resourceOptions; // Capture for TS narrowing
|
|
520
|
-
|
|
521
|
-
if (!capturedResourceOptions?.listResources) {
|
|
522
|
-
this.logger.warn('ListResources capability not supported by server configuration.');
|
|
523
|
-
return;
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
527
|
-
this.logger.debug('Handling ListResources request');
|
|
528
|
-
if (this.definedResources) {
|
|
529
|
-
return { resources: this.definedResources };
|
|
530
|
-
} else {
|
|
531
|
-
try {
|
|
532
|
-
const resources = await capturedResourceOptions.listResources();
|
|
533
|
-
// Cache the resources
|
|
534
|
-
this.definedResources = resources;
|
|
535
|
-
this.logger.debug(`Fetched and cached ${this.definedResources.length} resources.`);
|
|
536
|
-
return { resources: this.definedResources };
|
|
537
|
-
} catch (error) {
|
|
538
|
-
this.logger.error('Error fetching resources via listResources():', { error });
|
|
539
|
-
// Re-throw to let the MCP Server SDK handle formatting the error response
|
|
540
|
-
throw error;
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
});
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
/**
|
|
547
|
-
* Register the ReadResource handler for reading a resource by URI.
|
|
548
|
-
*/
|
|
549
|
-
private registerReadResourceHandler({
|
|
550
|
-
getResourcesCallback,
|
|
551
|
-
}: {
|
|
552
|
-
getResourcesCallback: MCPServerResourceContentCallback;
|
|
553
|
-
}) {
|
|
554
|
-
if (this.readResourceHandlerIsRegistered) {
|
|
555
|
-
return;
|
|
556
|
-
}
|
|
557
|
-
this.readResourceHandlerIsRegistered = true;
|
|
558
|
-
this.server.setRequestHandler(ReadResourceRequestSchema, async request => {
|
|
559
|
-
const startTime = Date.now();
|
|
560
|
-
const uri = request.params.uri;
|
|
561
|
-
this.logger.debug(`Handling ReadResource request for URI: ${uri}`);
|
|
562
|
-
|
|
563
|
-
if (!this.definedResources) {
|
|
564
|
-
const resources = await this.resourceOptions?.listResources?.();
|
|
565
|
-
if (!resources) throw new Error('Failed to load resources');
|
|
566
|
-
this.definedResources = resources;
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
const resource = this.definedResources?.find(r => r.uri === uri);
|
|
570
|
-
|
|
571
|
-
if (!resource) {
|
|
572
|
-
this.logger.warn(`ReadResource: Unknown resource URI '${uri}' requested.`);
|
|
573
|
-
throw new Error(`Resource not found: ${uri}`);
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
try {
|
|
577
|
-
const resourcesOrResourceContent = await getResourcesCallback({ uri });
|
|
578
|
-
const resourcesContent = Array.isArray(resourcesOrResourceContent)
|
|
579
|
-
? resourcesOrResourceContent
|
|
580
|
-
: [resourcesOrResourceContent];
|
|
581
|
-
const contents: ResourceContents[] = resourcesContent.map(resourceContent => {
|
|
582
|
-
const contentItem: ResourceContents = {
|
|
583
|
-
uri: resource.uri,
|
|
584
|
-
mimeType: resource.mimeType,
|
|
585
|
-
};
|
|
586
|
-
if ('text' in resourceContent) {
|
|
587
|
-
contentItem.text = resourceContent.text;
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
if ('blob' in resourceContent) {
|
|
591
|
-
contentItem.blob = resourceContent.blob;
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
return contentItem;
|
|
595
|
-
});
|
|
596
|
-
const duration = Date.now() - startTime;
|
|
597
|
-
this.logger.info(`Resource '${uri}' read successfully in ${duration}ms.`);
|
|
598
|
-
return {
|
|
599
|
-
contents,
|
|
600
|
-
};
|
|
601
|
-
} catch (error) {
|
|
602
|
-
const duration = Date.now() - startTime;
|
|
603
|
-
this.logger.error(`Failed to get content for resource URI '${uri}' in ${duration}ms`, { error });
|
|
604
|
-
throw error;
|
|
605
|
-
}
|
|
606
|
-
});
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
/**
|
|
610
|
-
* Register the ListResourceTemplates handler.
|
|
611
|
-
*/
|
|
612
|
-
private registerListResourceTemplatesHandler() {
|
|
613
|
-
if (this.listResourceTemplatesHandlerIsRegistered) {
|
|
614
|
-
return;
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
// If this method is called, this.resourceOptions and this.resourceOptions.resourceTemplates should exist
|
|
618
|
-
// due to the constructor logic checking opts.resources.resourceTemplates.
|
|
619
|
-
if (!this.resourceOptions || typeof this.resourceOptions.resourceTemplates !== 'function') {
|
|
620
|
-
this.logger.warn(
|
|
621
|
-
'ListResourceTemplates handler called, but resourceTemplates function is not available on resourceOptions or not a function.',
|
|
622
|
-
);
|
|
623
|
-
// Register a handler that returns empty templates if not properly configured.
|
|
624
|
-
this.server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
|
|
625
|
-
this.logger.debug('Handling ListResourceTemplates request (no templates configured or resourceOptions issue)');
|
|
626
|
-
return { resourceTemplates: [] };
|
|
627
|
-
});
|
|
628
|
-
this.listResourceTemplatesHandlerIsRegistered = true;
|
|
629
|
-
return;
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
// Typescript can now infer resourceTemplatesFn is a function.
|
|
633
|
-
const resourceTemplatesFn = this.resourceOptions.resourceTemplates;
|
|
634
|
-
|
|
635
|
-
this.listResourceTemplatesHandlerIsRegistered = true;
|
|
636
|
-
this.server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
|
|
637
|
-
this.logger.debug('Handling ListResourceTemplates request');
|
|
638
|
-
if (this.definedResourceTemplates) {
|
|
639
|
-
return { resourceTemplates: this.definedResourceTemplates };
|
|
640
|
-
} else {
|
|
641
|
-
try {
|
|
642
|
-
const templates = await resourceTemplatesFn(); // Safe to call now
|
|
643
|
-
this.definedResourceTemplates = templates;
|
|
644
|
-
this.logger.debug(`Fetched and cached ${this.definedResourceTemplates.length} resource templates.`);
|
|
645
|
-
return { resourceTemplates: this.definedResourceTemplates };
|
|
646
|
-
} catch (error) {
|
|
647
|
-
this.logger.error('Error fetching resource templates via resourceTemplates():', { error });
|
|
648
|
-
// Re-throw to let the MCP Server SDK handle formatting the error response
|
|
649
|
-
throw error;
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
});
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
/**
|
|
656
|
-
* Register the SubscribeResource handler.
|
|
657
|
-
*/
|
|
658
|
-
private registerSubscribeResourceHandler() {
|
|
659
|
-
if (this.subscribeResourceHandlerIsRegistered) {
|
|
660
|
-
return;
|
|
661
|
-
}
|
|
662
|
-
if (!SubscribeRequestSchema) {
|
|
663
|
-
this.logger.warn('SubscribeRequestSchema not available, cannot register SubscribeResource handler.');
|
|
664
|
-
return;
|
|
665
|
-
}
|
|
666
|
-
this.subscribeResourceHandlerIsRegistered = true;
|
|
667
|
-
this.server.setRequestHandler(SubscribeRequestSchema as any, async (request: { params: { uri: string } }) => {
|
|
668
|
-
const uri = request.params.uri;
|
|
669
|
-
this.logger.info(`Received resources/subscribe request for URI: ${uri}`);
|
|
670
|
-
this.subscriptions.add(uri);
|
|
671
|
-
return {};
|
|
672
|
-
});
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
/**
|
|
676
|
-
* Register the UnsubscribeResource handler.
|
|
677
|
-
*/
|
|
678
|
-
private registerUnsubscribeResourceHandler() {
|
|
679
|
-
if (this.unsubscribeResourceHandlerIsRegistered) {
|
|
680
|
-
return;
|
|
681
|
-
}
|
|
682
|
-
this.unsubscribeResourceHandlerIsRegistered = true;
|
|
683
|
-
|
|
684
|
-
this.server.setRequestHandler(UnsubscribeRequestSchema as any, async (request: { params: { uri: string } }) => {
|
|
685
|
-
const uri = request.params.uri;
|
|
686
|
-
this.logger.info(`Received resources/unsubscribe request for URI: ${uri}`);
|
|
687
|
-
this.subscriptions.delete(uri);
|
|
688
|
-
return {};
|
|
689
|
-
});
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
/**
|
|
693
|
-
* Register the ListPrompts handler.
|
|
694
|
-
*/
|
|
695
|
-
private registerListPromptsHandler() {
|
|
696
|
-
if (this.listPromptsHandlerIsRegistered) {
|
|
697
|
-
return;
|
|
698
|
-
}
|
|
699
|
-
this.listPromptsHandlerIsRegistered = true;
|
|
700
|
-
const capturedPromptOptions = this.promptOptions;
|
|
701
|
-
|
|
702
|
-
if (!capturedPromptOptions?.listPrompts) {
|
|
703
|
-
this.logger.warn('ListPrompts capability not supported by server configuration.');
|
|
704
|
-
return;
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
this.server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
708
|
-
this.logger.debug('Handling ListPrompts request');
|
|
709
|
-
if (this.definedPrompts) {
|
|
710
|
-
return {
|
|
711
|
-
prompts: this.definedPrompts?.map(p => ({ ...p, version: p.version ?? undefined })),
|
|
712
|
-
};
|
|
713
|
-
} else {
|
|
714
|
-
try {
|
|
715
|
-
const prompts = await capturedPromptOptions.listPrompts();
|
|
716
|
-
// Parse and cache the prompts
|
|
717
|
-
for (const prompt of prompts) {
|
|
718
|
-
PromptSchema.parse(prompt);
|
|
719
|
-
}
|
|
720
|
-
this.definedPrompts = prompts;
|
|
721
|
-
this.logger.debug(`Fetched and cached ${this.definedPrompts.length} prompts.`);
|
|
722
|
-
return {
|
|
723
|
-
prompts: this.definedPrompts?.map(p => ({ ...p, version: p.version ?? undefined })),
|
|
724
|
-
};
|
|
725
|
-
} catch (error) {
|
|
726
|
-
this.logger.error('Error fetching prompts via listPrompts():', {
|
|
727
|
-
error: error instanceof Error ? error.message : String(error),
|
|
728
|
-
});
|
|
729
|
-
// Re-throw to let the MCP Server SDK handle formatting the error response
|
|
730
|
-
throw error;
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
});
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
/**
|
|
737
|
-
* Register the GetPrompt handler.
|
|
738
|
-
*/
|
|
739
|
-
private registerGetPromptHandler({
|
|
740
|
-
getPromptMessagesCallback,
|
|
741
|
-
}: {
|
|
742
|
-
getPromptMessagesCallback?: MCPServerPromptMessagesCallback;
|
|
743
|
-
}) {
|
|
744
|
-
if (this.getPromptHandlerIsRegistered) return;
|
|
745
|
-
this.getPromptHandlerIsRegistered = true;
|
|
746
|
-
// Accept optional version parameter in prompts/get
|
|
747
|
-
this.server.setRequestHandler(
|
|
748
|
-
GetPromptRequestSchema,
|
|
749
|
-
async (request: { params: { name: string; version?: string; arguments?: any } }) => {
|
|
750
|
-
const startTime = Date.now();
|
|
751
|
-
const { name, version, arguments: args } = request.params;
|
|
752
|
-
if (!this.definedPrompts) {
|
|
753
|
-
const prompts = await this.promptOptions?.listPrompts?.();
|
|
754
|
-
if (!prompts) throw new Error('Failed to load prompts');
|
|
755
|
-
this.definedPrompts = prompts;
|
|
756
|
-
}
|
|
757
|
-
// Select prompt by name and version (if provided)
|
|
758
|
-
let prompt;
|
|
759
|
-
if (version) {
|
|
760
|
-
prompt = this.definedPrompts?.find(p => p.name === name && p.version === version);
|
|
761
|
-
} else {
|
|
762
|
-
// Select the first matching name if no version is provided.
|
|
763
|
-
prompt = this.definedPrompts?.find(p => p.name === name);
|
|
764
|
-
}
|
|
765
|
-
if (!prompt) throw new Error(`Prompt "${name}"${version ? ` (version ${version})` : ''} not found`);
|
|
766
|
-
// Validate required arguments
|
|
767
|
-
if (prompt.arguments) {
|
|
768
|
-
for (const arg of prompt.arguments) {
|
|
769
|
-
if (arg.required && (args?.[arg.name] === undefined || args?.[arg.name] === null)) {
|
|
770
|
-
throw new Error(`Missing required argument: ${arg.name}`);
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
try {
|
|
775
|
-
let messages: any[] = [];
|
|
776
|
-
if (getPromptMessagesCallback) {
|
|
777
|
-
messages = await getPromptMessagesCallback({ name, version, args });
|
|
778
|
-
}
|
|
779
|
-
const duration = Date.now() - startTime;
|
|
780
|
-
this.logger.info(
|
|
781
|
-
`Prompt '${name}'${version ? ` (version ${version})` : ''} retrieved successfully in ${duration}ms.`,
|
|
782
|
-
);
|
|
783
|
-
return { prompt, messages };
|
|
784
|
-
} catch (error) {
|
|
785
|
-
const duration = Date.now() - startTime;
|
|
786
|
-
this.logger.error(`Failed to get content for prompt '${name}' in ${duration}ms`, { error });
|
|
787
|
-
throw error;
|
|
788
|
-
}
|
|
789
|
-
},
|
|
790
|
-
);
|
|
791
|
-
}
|
|
792
|
-
|
|
793
785
|
/**
|
|
794
786
|
* Start the MCP server using stdio transport (for Windsurf integration).
|
|
795
787
|
*/
|
|
@@ -1034,15 +1026,24 @@ export class MCPServer extends MCPServerBase {
|
|
|
1034
1026
|
`startHTTP: Streamable HTTP transport closed for session ${closedSessionId}, removing from map.`,
|
|
1035
1027
|
);
|
|
1036
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
|
+
}
|
|
1037
1034
|
}
|
|
1038
1035
|
};
|
|
1039
1036
|
|
|
1040
|
-
//
|
|
1041
|
-
|
|
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);
|
|
1042
1042
|
|
|
1043
|
-
// Store the transport when the session is initialized
|
|
1043
|
+
// Store both the transport and server instance when the session is initialized
|
|
1044
1044
|
if (transport.sessionId) {
|
|
1045
1045
|
this.streamableHTTPTransports.set(transport.sessionId, transport);
|
|
1046
|
+
this.httpServerInstances.set(transport.sessionId, sessionServerInstance);
|
|
1046
1047
|
this.logger.debug(
|
|
1047
1048
|
`startHTTP: Streamable HTTP session initialized and stored with ID: ${transport.sessionId}`,
|
|
1048
1049
|
);
|
|
@@ -1199,14 +1200,6 @@ export class MCPServer extends MCPServerBase {
|
|
|
1199
1200
|
* Close the MCP server and all its connections
|
|
1200
1201
|
*/
|
|
1201
1202
|
async close() {
|
|
1202
|
-
this.callToolHandlerIsRegistered = false;
|
|
1203
|
-
this.listToolsHandlerIsRegistered = false;
|
|
1204
|
-
this.listResourcesHandlerIsRegistered = false;
|
|
1205
|
-
this.readResourceHandlerIsRegistered = false;
|
|
1206
|
-
this.listResourceTemplatesHandlerIsRegistered = false;
|
|
1207
|
-
this.subscribeResourceHandlerIsRegistered = false;
|
|
1208
|
-
this.unsubscribeResourceHandlerIsRegistered = false;
|
|
1209
|
-
|
|
1210
1203
|
try {
|
|
1211
1204
|
if (this.stdioTransport) {
|
|
1212
1205
|
await this.stdioTransport.close?.();
|
|
@@ -1222,13 +1215,20 @@ export class MCPServer extends MCPServerBase {
|
|
|
1222
1215
|
}
|
|
1223
1216
|
this.sseHonoTransports.clear();
|
|
1224
1217
|
}
|
|
1225
|
-
// Close all active Streamable HTTP transports
|
|
1218
|
+
// Close all active Streamable HTTP transports and their server instances
|
|
1226
1219
|
if (this.streamableHTTPTransports) {
|
|
1227
1220
|
for (const transport of this.streamableHTTPTransports.values()) {
|
|
1228
1221
|
await transport.close?.();
|
|
1229
1222
|
}
|
|
1230
1223
|
this.streamableHTTPTransports.clear();
|
|
1231
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
|
+
}
|
|
1232
1232
|
await this.server.close();
|
|
1233
1233
|
this.logger.info('MCP server closed.');
|
|
1234
1234
|
} catch (error) {
|
|
@@ -1283,7 +1283,7 @@ export class MCPServer extends MCPServerBase {
|
|
|
1283
1283
|
* @returns An object containing an array of tool information.
|
|
1284
1284
|
*/
|
|
1285
1285
|
public getToolListInfo(): {
|
|
1286
|
-
tools: Array<{ name: string; description?: string; inputSchema: any; toolType?: MCPToolType }>;
|
|
1286
|
+
tools: Array<{ name: string; description?: string; inputSchema: any; outputSchema?: any; toolType?: MCPToolType }>;
|
|
1287
1287
|
} {
|
|
1288
1288
|
this.logger.debug(`Getting tool list information for MCPServer '${this.name}'`);
|
|
1289
1289
|
return {
|
|
@@ -1292,6 +1292,7 @@ export class MCPServer extends MCPServerBase {
|
|
|
1292
1292
|
name: tool.name,
|
|
1293
1293
|
description: tool.description,
|
|
1294
1294
|
inputSchema: tool.parameters?.jsonSchema || tool.parameters,
|
|
1295
|
+
outputSchema: tool.outputSchema?.jsonSchema || tool.outputSchema,
|
|
1295
1296
|
toolType: tool.toolType,
|
|
1296
1297
|
})),
|
|
1297
1298
|
};
|
|
@@ -1304,7 +1305,7 @@ export class MCPServer extends MCPServerBase {
|
|
|
1304
1305
|
*/
|
|
1305
1306
|
public getToolInfo(
|
|
1306
1307
|
toolId: string,
|
|
1307
|
-
): { name: string; description?: string; inputSchema: any; toolType?: MCPToolType } | undefined {
|
|
1308
|
+
): { name: string; description?: string; inputSchema: any; outputSchema?: any; toolType?: MCPToolType } | undefined {
|
|
1308
1309
|
const tool = this.convertedTools[toolId];
|
|
1309
1310
|
if (!tool) {
|
|
1310
1311
|
this.logger.debug(`Tool '${toolId}' not found on MCPServer '${this.name}'`);
|
|
@@ -1315,6 +1316,7 @@ export class MCPServer extends MCPServerBase {
|
|
|
1315
1316
|
name: tool.name,
|
|
1316
1317
|
description: tool.description,
|
|
1317
1318
|
inputSchema: tool.parameters?.jsonSchema || tool.parameters,
|
|
1319
|
+
outputSchema: tool.outputSchema?.jsonSchema || tool.outputSchema,
|
|
1318
1320
|
toolType: tool.toolType,
|
|
1319
1321
|
};
|
|
1320
1322
|
}
|