@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/.turbo/turbo-build.log +7 -7
- package/CHANGELOG.md +18 -0
- package/dist/_tsup-dts-rollup.d.cts +29 -1
- package/dist/_tsup-dts-rollup.d.ts +29 -1
- package/dist/index.cjs +227 -42
- package/dist/index.js +227 -41
- package/package.json +8 -3
- package/src/__fixtures__/fire-crawl-complex-schema.ts +1013 -0
- package/src/client.test.ts +4 -4
- package/src/client.ts +77 -38
- package/src/configuration.test.ts +272 -0
- package/src/server.test.ts +183 -23
- package/src/server.ts +86 -10
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 {
|
|
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
|
-
|
|
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
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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
|
-
|
|
271
|
-
|
|
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
|
+
"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
|
-
"
|
|
29
|
+
"hono": "^4.7.4",
|
|
30
30
|
"uuid": "^11.1.0",
|
|
31
|
-
"
|
|
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",
|