@mastra/mcp 0.5.0-alpha.3 → 0.5.0-alpha.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,15 +1,15 @@
1
1
  import { MastraBase } from '@mastra/core/base';
2
2
  import { createTool } from '@mastra/core/tools';
3
- import { resolveSerializedZodOutput, isZodType } from '@mastra/core/utils';
3
+ import { isZodType } from '@mastra/core/utils';
4
4
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
5
5
  import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
6
6
  import { StdioClientTransport, getDefaultEnvironment } from '@modelcontextprotocol/sdk/client/stdio.js';
7
7
  import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
8
8
  import { DEFAULT_REQUEST_TIMEOUT_MSEC } from '@modelcontextprotocol/sdk/shared/protocol.js';
9
- import { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesResultSchema, CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';
9
+ import { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesResultSchema, CallToolResultSchema, JSONRPCMessageSchema } from '@modelcontextprotocol/sdk/types.js';
10
10
  import { asyncExitHook, gracefulExit } from 'exit-hook';
11
- import jsonSchemaToZod from 'json-schema-to-zod';
12
11
  import { z } from 'zod';
12
+ import { jsonSchemaObjectToZodRawShape } from 'zod-from-json-schema';
13
13
  import equal from 'fast-deep-equal';
14
14
  import { v5 } from 'uuid';
15
15
  import { randomUUID } from 'crypto';
@@ -20,6 +20,7 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
20
20
  import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
21
21
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
22
22
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
23
+ import { streamSSE } from 'hono/streaming';
23
24
 
24
25
  // src/client.ts
25
26
  function convertLogLevelToLoggerMethod(level) {
@@ -48,6 +49,7 @@ var InternalMastraMCPClient = class extends MastraBase {
48
49
  enableServerLogs;
49
50
  serverConfig;
50
51
  transport;
52
+ currentOperationContext = null;
51
53
  constructor({
52
54
  name,
53
55
  version = "1.0.0",
@@ -88,7 +90,8 @@ var InternalMastraMCPClient = class extends MastraBase {
88
90
  message: msg,
89
91
  timestamp: /* @__PURE__ */ new Date(),
90
92
  serverName: this.name,
91
- details
93
+ details,
94
+ runtimeContext: this.currentOperationContext
92
95
  });
93
96
  }
94
97
  }
@@ -231,7 +234,29 @@ var InternalMastraMCPClient = class extends MastraBase {
231
234
  });
232
235
  }
233
236
  convertInputSchema(inputSchema) {
234
- return isZodType(inputSchema) ? inputSchema : resolveSerializedZodOutput(jsonSchemaToZod(inputSchema));
237
+ if (isZodType(inputSchema)) {
238
+ return inputSchema;
239
+ }
240
+ try {
241
+ const rawShape = jsonSchemaObjectToZodRawShape(inputSchema);
242
+ return z.object(rawShape);
243
+ } catch (error) {
244
+ let errorDetails;
245
+ if (error instanceof Error) {
246
+ errorDetails = error.stack;
247
+ } else {
248
+ try {
249
+ errorDetails = JSON.stringify(error);
250
+ } catch {
251
+ errorDetails = String(error);
252
+ }
253
+ }
254
+ this.log("error", "Failed to convert JSON schema to Zod schema using zodFromJsonSchema", {
255
+ error: errorDetails,
256
+ originalJsonSchema: inputSchema
257
+ });
258
+ throw new Error(errorDetails);
259
+ }
235
260
  }
236
261
  async tools() {
237
262
  this.log("debug", `Requesting tools from MCP server`);
@@ -239,36 +264,47 @@ var InternalMastraMCPClient = class extends MastraBase {
239
264
  const toolsRes = {};
240
265
  tools.forEach((tool) => {
241
266
  this.log("debug", `Processing tool: ${tool.name}`);
242
- const mastraTool = createTool({
243
- id: `${this.name}_${tool.name}`,
244
- description: tool.description || "",
245
- inputSchema: this.convertInputSchema(tool.inputSchema),
246
- execute: async ({ context }) => {
247
- try {
248
- this.log("debug", `Executing tool: ${tool.name}`, { toolArgs: context });
249
- const res = await this.client.callTool(
250
- {
251
- name: tool.name,
252
- arguments: context
253
- },
254
- CallToolResultSchema,
255
- {
256
- timeout: this.timeout
257
- }
258
- );
259
- this.log("debug", `Tool executed successfully: ${tool.name}`);
260
- return res;
261
- } catch (e) {
262
- this.log("error", `Error calling tool: ${tool.name}`, {
263
- error: e instanceof Error ? e.stack : JSON.stringify(e, null, 2),
264
- toolArgs: context
265
- });
266
- throw e;
267
+ try {
268
+ const mastraTool = createTool({
269
+ id: `${this.name}_${tool.name}`,
270
+ description: tool.description || "",
271
+ inputSchema: this.convertInputSchema(tool.inputSchema),
272
+ execute: async ({ context, runtimeContext }) => {
273
+ const previousContext = this.currentOperationContext;
274
+ this.currentOperationContext = runtimeContext || null;
275
+ try {
276
+ this.log("debug", `Executing tool: ${tool.name}`, { toolArgs: context });
277
+ const res = await this.client.callTool(
278
+ {
279
+ name: tool.name,
280
+ arguments: context
281
+ },
282
+ CallToolResultSchema,
283
+ {
284
+ timeout: this.timeout
285
+ }
286
+ );
287
+ this.log("debug", `Tool executed successfully: ${tool.name}`);
288
+ return res;
289
+ } catch (e) {
290
+ this.log("error", `Error calling tool: ${tool.name}`, {
291
+ error: e instanceof Error ? e.stack : JSON.stringify(e, null, 2),
292
+ toolArgs: context
293
+ });
294
+ throw e;
295
+ } finally {
296
+ this.currentOperationContext = previousContext;
297
+ }
267
298
  }
299
+ });
300
+ if (tool.name) {
301
+ toolsRes[tool.name] = mastraTool;
268
302
  }
269
- });
270
- if (tool.name) {
271
- toolsRes[tool.name] = mastraTool;
303
+ } catch (toolCreationError) {
304
+ this.log("error", `Failed to create Mastra tool wrapper for MCP tool: ${tool.name}`, {
305
+ error: toolCreationError instanceof Error ? toolCreationError.stack : String(toolCreationError),
306
+ mcpToolDefinition: tool
307
+ });
272
308
  }
273
309
  });
274
310
  return toolsRes;
@@ -470,10 +506,95 @@ var MCPConfiguration = class extends MCPClient {
470
506
  );
471
507
  }
472
508
  };
509
+ var MAXIMUM_MESSAGE_SIZE = 4 * 1024 * 1024;
510
+ var SSETransport = class {
511
+ messageUrl;
512
+ stream;
513
+ _sessionId;
514
+ onclose;
515
+ onerror;
516
+ onmessage;
517
+ /**
518
+ * Creates a new SSETransport, which will direct the MPC client to POST messages to messageUrl
519
+ */
520
+ constructor(messageUrl, stream) {
521
+ this.messageUrl = messageUrl;
522
+ this.stream = stream;
523
+ this._sessionId = crypto.randomUUID();
524
+ this.stream.onAbort(() => {
525
+ void this.close();
526
+ });
527
+ }
528
+ get sessionId() {
529
+ return this._sessionId;
530
+ }
531
+ // start() is automatically called after MCP Server connects to the transport
532
+ async start() {
533
+ if (this.stream == null) {
534
+ throw new Error("Stream not initialized");
535
+ }
536
+ if (this.stream.closed) {
537
+ throw new Error("SSE transport already closed!");
538
+ }
539
+ await this.stream.writeSSE({
540
+ event: "endpoint",
541
+ data: `${this.messageUrl}?sessionId=${this.sessionId}`
542
+ });
543
+ }
544
+ async handlePostMessage(context) {
545
+ if (this.stream?.closed == null) {
546
+ return context.text("SSE connection not established", 500);
547
+ }
548
+ try {
549
+ const contentType = context.req.header("content-type") || "";
550
+ if (!contentType.includes("application/json")) {
551
+ throw new Error(`Unsupported content-type: ${contentType}`);
552
+ }
553
+ const contentLength = Number.parseInt(context.req.header("content-length") || "0", 10);
554
+ if (contentLength > MAXIMUM_MESSAGE_SIZE) {
555
+ throw new Error(`Request body too large: ${contentLength} bytes`);
556
+ }
557
+ const body = await context.req.json();
558
+ await this.handleMessage(body);
559
+ return context.text("Accepted", 202);
560
+ } catch (error) {
561
+ this.onerror?.(error);
562
+ return context.text("Error", 400);
563
+ }
564
+ }
565
+ /**
566
+ * Handle a client message, regardless of how it arrived. This can be used to inform the server of messages that arrive via a means different than HTTP POST.
567
+ */
568
+ async handleMessage(message) {
569
+ let parsedMessage;
570
+ try {
571
+ parsedMessage = JSONRPCMessageSchema.parse(message);
572
+ } catch (error) {
573
+ this.onerror?.(error);
574
+ throw error;
575
+ }
576
+ this.onmessage?.(parsedMessage);
577
+ }
578
+ async close() {
579
+ if (this.stream?.closed) {
580
+ this.stream.abort();
581
+ }
582
+ }
583
+ async send(message) {
584
+ if (this.stream?.closed) {
585
+ throw new Error("Not connected");
586
+ }
587
+ await this.stream.writeSSE({
588
+ event: "message",
589
+ data: JSON.stringify(message)
590
+ });
591
+ }
592
+ };
473
593
  var MCPServer = class extends MCPServerBase {
474
594
  server;
475
595
  stdioTransport;
476
596
  sseTransport;
597
+ sseHonoTransports;
477
598
  streamableHTTPTransport;
478
599
  listToolsHandlerIsRegistered = false;
479
600
  callToolHandlerIsRegistered = false;
@@ -489,6 +610,12 @@ var MCPServer = class extends MCPServerBase {
489
610
  getSseTransport() {
490
611
  return this.sseTransport;
491
612
  }
613
+ /**
614
+ * Get the current SSE Hono transport.
615
+ */
616
+ getSseHonoTransport(sessionId) {
617
+ return this.sseHonoTransports.get(sessionId);
618
+ }
492
619
  /**
493
620
  * Get the current streamable HTTP transport.
494
621
  */
@@ -507,6 +634,7 @@ var MCPServer = class extends MCPServerBase {
507
634
  this.logger.info(
508
635
  `Initialized MCPServer '${name}' v${version} with tools: ${Object.keys(this.convertedTools).join(", ")}`
509
636
  );
637
+ this.sseHonoTransports = /* @__PURE__ */ new Map();
510
638
  this.registerListToolsHandler();
511
639
  this.registerCallToolHandler();
512
640
  }
@@ -678,6 +806,43 @@ var MCPServer = class extends MCPServerBase {
678
806
  res.end();
679
807
  }
680
808
  }
809
+ /**
810
+ * Handles MCP-over-SSE protocol for user-provided HTTP servers.
811
+ * Call this from your HTTP server for both the SSE and message endpoints.
812
+ *
813
+ * @param url Parsed URL of the incoming request
814
+ * @param ssePath Path for establishing the SSE connection (e.g. '/sse')
815
+ * @param messagePath Path for POSTing client messages (e.g. '/message')
816
+ * @param context Incoming Hono context
817
+ */
818
+ async startHonoSSE({ url, ssePath, messagePath, context }) {
819
+ if (url.pathname === ssePath) {
820
+ return streamSSE(context, async (stream) => {
821
+ await this.connectHonoSSE({
822
+ messagePath,
823
+ stream
824
+ });
825
+ });
826
+ } else if (url.pathname === messagePath) {
827
+ this.logger.debug("Received message");
828
+ const sessionId = context.req.query("sessionId");
829
+ this.logger.debug("Received message for sessionId", { sessionId });
830
+ if (!sessionId) {
831
+ return context.text("No sessionId provided", 400);
832
+ }
833
+ if (!this.sseHonoTransports.has(sessionId)) {
834
+ return context.text(`No transport found for sessionId ${sessionId}`, 400);
835
+ }
836
+ const message = await this.sseHonoTransports.get(sessionId)?.handlePostMessage(context);
837
+ if (!message) {
838
+ return context.text("Transport not found", 400);
839
+ }
840
+ return message;
841
+ } else {
842
+ this.logger.debug("Unknown path:", { path: url.pathname });
843
+ return context.text("Unknown path", 404);
844
+ }
845
+ }
681
846
  /**
682
847
  * Handles MCP-over-StreamableHTTP protocol for user-provided HTTP servers.
683
848
  * Call this from your HTTP server for the streamable HTTP endpoint.
@@ -725,14 +890,6 @@ var MCPServer = class extends MCPServerBase {
725
890
  res.end();
726
891
  }
727
892
  }
728
- async handlePostMessage(req, res) {
729
- if (!this.sseTransport) {
730
- res.writeHead(503);
731
- res.end("SSE connection not established");
732
- return;
733
- }
734
- await this.sseTransport.handlePostMessage(req, res);
735
- }
736
893
  async connectSSE({
737
894
  messagePath,
738
895
  res
@@ -748,6 +905,29 @@ var MCPServer = class extends MCPServerBase {
748
905
  this.sseTransport = void 0;
749
906
  });
750
907
  }
908
+ async connectHonoSSE({ messagePath, stream }) {
909
+ this.logger.debug("Received SSE connection");
910
+ const sseTransport = new SSETransport(messagePath, stream);
911
+ const sessionId = sseTransport.sessionId;
912
+ this.logger.debug("SSE Transport created with sessionId:", { sessionId });
913
+ this.sseHonoTransports.set(sessionId, sseTransport);
914
+ stream.onAbort(() => {
915
+ this.logger.debug("SSE Transport aborted with sessionId:", { sessionId });
916
+ this.sseHonoTransports.delete(sessionId);
917
+ });
918
+ await this.server.connect(sseTransport);
919
+ this.server.onclose = async () => {
920
+ this.logger.debug("SSE Transport closed with sessionId:", { sessionId });
921
+ this.sseHonoTransports.delete(sessionId);
922
+ await this.server.close();
923
+ };
924
+ while (true) {
925
+ const sessionIds = Array.from(this.sseHonoTransports.keys() || []);
926
+ this.logger.debug("Active Hono SSE sessions:", { sessionIds });
927
+ await stream.write(":keep-alive\n\n");
928
+ await stream.sleep(6e4);
929
+ }
930
+ }
751
931
  /**
752
932
  * Close the MCP server and all its connections
753
933
  */
@@ -763,6 +943,12 @@ var MCPServer = class extends MCPServerBase {
763
943
  await this.sseTransport.close?.();
764
944
  this.sseTransport = void 0;
765
945
  }
946
+ if (this.sseHonoTransports) {
947
+ for (const transport of this.sseHonoTransports.values()) {
948
+ await transport.close?.();
949
+ }
950
+ this.sseHonoTransports.clear();
951
+ }
766
952
  if (this.streamableHTTPTransport) {
767
953
  await this.streamableHTTPTransport.close?.();
768
954
  this.streamableHTTPTransport = void 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mastra/mcp",
3
- "version": "0.5.0-alpha.3",
3
+ "version": "0.5.0-alpha.5",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -26,18 +26,23 @@
26
26
  "date-fns": "^4.1.0",
27
27
  "exit-hook": "^4.0.0",
28
28
  "fast-deep-equal": "^3.1.3",
29
- "json-schema-to-zod": "^2.6.0",
29
+ "hono": "^4.7.4",
30
30
  "uuid": "^11.1.0",
31
- "@mastra/core": "^0.9.4-alpha.2"
31
+ "zod-from-json-schema": "^0.0.5",
32
+ "@mastra/core": "^0.9.4-alpha.3"
32
33
  },
33
34
  "peerDependencies": {
34
35
  "zod": "^3.0.0"
35
36
  },
36
37
  "devDependencies": {
37
38
  "@ai-sdk/anthropic": "^1.1.15",
39
+ "@ai-sdk/openai": "^1.3.22",
40
+ "@hono/node-server": "^1.13.8",
41
+ "@mendable/firecrawl-js": "^1.24.0",
38
42
  "@microsoft/api-extractor": "^7.52.5",
39
43
  "@types/node": "^20.17.27",
40
44
  "eslint": "^9.23.0",
45
+ "hono-mcp-server-sse-transport": "0.0.6",
41
46
  "tsup": "^8.4.0",
42
47
  "tsx": "^4.19.3",
43
48
  "typescript": "^5.8.2",