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

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) {
@@ -231,7 +232,29 @@ var InternalMastraMCPClient = class extends MastraBase {
231
232
  });
232
233
  }
233
234
  convertInputSchema(inputSchema) {
234
- return isZodType(inputSchema) ? inputSchema : resolveSerializedZodOutput(jsonSchemaToZod(inputSchema));
235
+ if (isZodType(inputSchema)) {
236
+ return inputSchema;
237
+ }
238
+ try {
239
+ const rawShape = jsonSchemaObjectToZodRawShape(inputSchema);
240
+ return z.object(rawShape);
241
+ } catch (error) {
242
+ let errorDetails;
243
+ if (error instanceof Error) {
244
+ errorDetails = error.stack;
245
+ } else {
246
+ try {
247
+ errorDetails = JSON.stringify(error);
248
+ } catch {
249
+ errorDetails = String(error);
250
+ }
251
+ }
252
+ this.log("error", "Failed to convert JSON schema to Zod schema using zodFromJsonSchema", {
253
+ error: errorDetails,
254
+ originalJsonSchema: inputSchema
255
+ });
256
+ throw new Error(errorDetails);
257
+ }
235
258
  }
236
259
  async tools() {
237
260
  this.log("debug", `Requesting tools from MCP server`);
@@ -239,36 +262,43 @@ var InternalMastraMCPClient = class extends MastraBase {
239
262
  const toolsRes = {};
240
263
  tools.forEach((tool) => {
241
264
  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;
265
+ try {
266
+ const mastraTool = createTool({
267
+ id: `${this.name}_${tool.name}`,
268
+ description: tool.description || "",
269
+ inputSchema: this.convertInputSchema(tool.inputSchema),
270
+ execute: async ({ context }) => {
271
+ try {
272
+ this.log("debug", `Executing tool: ${tool.name}`, { toolArgs: context });
273
+ const res = await this.client.callTool(
274
+ {
275
+ name: tool.name,
276
+ arguments: context
277
+ },
278
+ CallToolResultSchema,
279
+ {
280
+ timeout: this.timeout
281
+ }
282
+ );
283
+ this.log("debug", `Tool executed successfully: ${tool.name}`);
284
+ return res;
285
+ } catch (e) {
286
+ this.log("error", `Error calling tool: ${tool.name}`, {
287
+ error: e instanceof Error ? e.stack : JSON.stringify(e, null, 2),
288
+ toolArgs: context
289
+ });
290
+ throw e;
291
+ }
267
292
  }
293
+ });
294
+ if (tool.name) {
295
+ toolsRes[tool.name] = mastraTool;
268
296
  }
269
- });
270
- if (tool.name) {
271
- toolsRes[tool.name] = mastraTool;
297
+ } catch (toolCreationError) {
298
+ this.log("error", `Failed to create Mastra tool wrapper for MCP tool: ${tool.name}`, {
299
+ error: toolCreationError instanceof Error ? toolCreationError.stack : String(toolCreationError),
300
+ mcpToolDefinition: tool
301
+ });
272
302
  }
273
303
  });
274
304
  return toolsRes;
@@ -470,10 +500,95 @@ var MCPConfiguration = class extends MCPClient {
470
500
  );
471
501
  }
472
502
  };
503
+ var MAXIMUM_MESSAGE_SIZE = 4 * 1024 * 1024;
504
+ var SSETransport = class {
505
+ messageUrl;
506
+ stream;
507
+ _sessionId;
508
+ onclose;
509
+ onerror;
510
+ onmessage;
511
+ /**
512
+ * Creates a new SSETransport, which will direct the MPC client to POST messages to messageUrl
513
+ */
514
+ constructor(messageUrl, stream) {
515
+ this.messageUrl = messageUrl;
516
+ this.stream = stream;
517
+ this._sessionId = crypto.randomUUID();
518
+ this.stream.onAbort(() => {
519
+ void this.close();
520
+ });
521
+ }
522
+ get sessionId() {
523
+ return this._sessionId;
524
+ }
525
+ // start() is automatically called after MCP Server connects to the transport
526
+ async start() {
527
+ if (this.stream == null) {
528
+ throw new Error("Stream not initialized");
529
+ }
530
+ if (this.stream.closed) {
531
+ throw new Error("SSE transport already closed!");
532
+ }
533
+ await this.stream.writeSSE({
534
+ event: "endpoint",
535
+ data: `${this.messageUrl}?sessionId=${this.sessionId}`
536
+ });
537
+ }
538
+ async handlePostMessage(context) {
539
+ if (this.stream?.closed == null) {
540
+ return context.text("SSE connection not established", 500);
541
+ }
542
+ try {
543
+ const contentType = context.req.header("content-type") || "";
544
+ if (!contentType.includes("application/json")) {
545
+ throw new Error(`Unsupported content-type: ${contentType}`);
546
+ }
547
+ const contentLength = Number.parseInt(context.req.header("content-length") || "0", 10);
548
+ if (contentLength > MAXIMUM_MESSAGE_SIZE) {
549
+ throw new Error(`Request body too large: ${contentLength} bytes`);
550
+ }
551
+ const body = await context.req.json();
552
+ await this.handleMessage(body);
553
+ return context.text("Accepted", 202);
554
+ } catch (error) {
555
+ this.onerror?.(error);
556
+ return context.text("Error", 400);
557
+ }
558
+ }
559
+ /**
560
+ * 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.
561
+ */
562
+ async handleMessage(message) {
563
+ let parsedMessage;
564
+ try {
565
+ parsedMessage = JSONRPCMessageSchema.parse(message);
566
+ } catch (error) {
567
+ this.onerror?.(error);
568
+ throw error;
569
+ }
570
+ this.onmessage?.(parsedMessage);
571
+ }
572
+ async close() {
573
+ if (this.stream?.closed) {
574
+ this.stream.abort();
575
+ }
576
+ }
577
+ async send(message) {
578
+ if (this.stream?.closed) {
579
+ throw new Error("Not connected");
580
+ }
581
+ await this.stream.writeSSE({
582
+ event: "message",
583
+ data: JSON.stringify(message)
584
+ });
585
+ }
586
+ };
473
587
  var MCPServer = class extends MCPServerBase {
474
588
  server;
475
589
  stdioTransport;
476
590
  sseTransport;
591
+ sseHonoTransports;
477
592
  streamableHTTPTransport;
478
593
  listToolsHandlerIsRegistered = false;
479
594
  callToolHandlerIsRegistered = false;
@@ -489,6 +604,12 @@ var MCPServer = class extends MCPServerBase {
489
604
  getSseTransport() {
490
605
  return this.sseTransport;
491
606
  }
607
+ /**
608
+ * Get the current SSE Hono transport.
609
+ */
610
+ getSseHonoTransport(sessionId) {
611
+ return this.sseHonoTransports.get(sessionId);
612
+ }
492
613
  /**
493
614
  * Get the current streamable HTTP transport.
494
615
  */
@@ -507,6 +628,7 @@ var MCPServer = class extends MCPServerBase {
507
628
  this.logger.info(
508
629
  `Initialized MCPServer '${name}' v${version} with tools: ${Object.keys(this.convertedTools).join(", ")}`
509
630
  );
631
+ this.sseHonoTransports = /* @__PURE__ */ new Map();
510
632
  this.registerListToolsHandler();
511
633
  this.registerCallToolHandler();
512
634
  }
@@ -678,6 +800,43 @@ var MCPServer = class extends MCPServerBase {
678
800
  res.end();
679
801
  }
680
802
  }
803
+ /**
804
+ * Handles MCP-over-SSE protocol for user-provided HTTP servers.
805
+ * Call this from your HTTP server for both the SSE and message endpoints.
806
+ *
807
+ * @param url Parsed URL of the incoming request
808
+ * @param ssePath Path for establishing the SSE connection (e.g. '/sse')
809
+ * @param messagePath Path for POSTing client messages (e.g. '/message')
810
+ * @param context Incoming Hono context
811
+ */
812
+ async startHonoSSE({ url, ssePath, messagePath, context }) {
813
+ if (url.pathname === ssePath) {
814
+ return streamSSE(context, async (stream) => {
815
+ await this.connectHonoSSE({
816
+ messagePath,
817
+ stream
818
+ });
819
+ });
820
+ } else if (url.pathname === messagePath) {
821
+ this.logger.debug("Received message");
822
+ const sessionId = context.req.query("sessionId");
823
+ this.logger.debug("Received message for sessionId", { sessionId });
824
+ if (!sessionId) {
825
+ return context.text("No sessionId provided", 400);
826
+ }
827
+ if (!this.sseHonoTransports.has(sessionId)) {
828
+ return context.text(`No transport found for sessionId ${sessionId}`, 400);
829
+ }
830
+ const message = await this.sseHonoTransports.get(sessionId)?.handlePostMessage(context);
831
+ if (!message) {
832
+ return context.text("Transport not found", 400);
833
+ }
834
+ return message;
835
+ } else {
836
+ this.logger.debug("Unknown path:", { path: url.pathname });
837
+ return context.text("Unknown path", 404);
838
+ }
839
+ }
681
840
  /**
682
841
  * Handles MCP-over-StreamableHTTP protocol for user-provided HTTP servers.
683
842
  * Call this from your HTTP server for the streamable HTTP endpoint.
@@ -725,14 +884,6 @@ var MCPServer = class extends MCPServerBase {
725
884
  res.end();
726
885
  }
727
886
  }
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
887
  async connectSSE({
737
888
  messagePath,
738
889
  res
@@ -748,6 +899,29 @@ var MCPServer = class extends MCPServerBase {
748
899
  this.sseTransport = void 0;
749
900
  });
750
901
  }
902
+ async connectHonoSSE({ messagePath, stream }) {
903
+ this.logger.debug("Received SSE connection");
904
+ const sseTransport = new SSETransport(messagePath, stream);
905
+ const sessionId = sseTransport.sessionId;
906
+ this.logger.debug("SSE Transport created with sessionId:", { sessionId });
907
+ this.sseHonoTransports.set(sessionId, sseTransport);
908
+ stream.onAbort(() => {
909
+ this.logger.debug("SSE Transport aborted with sessionId:", { sessionId });
910
+ this.sseHonoTransports.delete(sessionId);
911
+ });
912
+ await this.server.connect(sseTransport);
913
+ this.server.onclose = async () => {
914
+ this.logger.debug("SSE Transport closed with sessionId:", { sessionId });
915
+ this.sseHonoTransports.delete(sessionId);
916
+ await this.server.close();
917
+ };
918
+ while (true) {
919
+ const sessionIds = Array.from(this.sseHonoTransports.keys() || []);
920
+ this.logger.debug("Active Hono SSE sessions:", { sessionIds });
921
+ await stream.write(":keep-alive\n\n");
922
+ await stream.sleep(6e4);
923
+ }
924
+ }
751
925
  /**
752
926
  * Close the MCP server and all its connections
753
927
  */
@@ -763,6 +937,12 @@ var MCPServer = class extends MCPServerBase {
763
937
  await this.sseTransport.close?.();
764
938
  this.sseTransport = void 0;
765
939
  }
940
+ if (this.sseHonoTransports) {
941
+ for (const transport of this.sseHonoTransports.values()) {
942
+ await transport.close?.();
943
+ }
944
+ this.sseHonoTransports.clear();
945
+ }
766
946
  if (this.streamableHTTPTransport) {
767
947
  await this.streamableHTTPTransport.close?.();
768
948
  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.4",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -26,18 +26,22 @@
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
+ "@hono/node-server": "^1.13.8",
40
+ "@mendable/firecrawl-js": "^1.24.0",
38
41
  "@microsoft/api-extractor": "^7.52.5",
39
42
  "@types/node": "^20.17.27",
40
43
  "eslint": "^9.23.0",
44
+ "hono-mcp-server-sse-transport": "0.0.6",
41
45
  "tsup": "^8.4.0",
42
46
  "tsx": "^4.19.3",
43
47
  "typescript": "^5.8.2",