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