@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.
@@ -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
- private listToolsHandlerIsRegistered: boolean = false;
63
- private callToolHandlerIsRegistered: boolean = false;
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
- if (opts.resources.resourceTemplates) {
148
- this.registerListResourceTemplatesHandler();
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
- // Connect the MCP server instance to the new transport
1041
- await this.server.connect(transport);
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
  }