@mastra/mcp 1.0.0-beta.2 → 1.0.0-beta.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/CHANGELOG.md +106 -0
- package/dist/__fixtures__/tools.d.ts +1 -1
- package/dist/__fixtures__/tools.d.ts.map +1 -1
- package/dist/client/{elicitationActions.d.ts → actions/elicitation.d.ts} +2 -2
- package/dist/client/actions/elicitation.d.ts.map +1 -0
- package/dist/client/actions/progress.d.ts +23 -0
- package/dist/client/actions/progress.d.ts.map +1 -0
- package/dist/client/{promptActions.d.ts → actions/prompt.d.ts} +2 -2
- package/dist/client/actions/prompt.d.ts.map +1 -0
- package/dist/client/{resourceActions.d.ts → actions/resource.d.ts} +2 -2
- package/dist/client/actions/resource.d.ts.map +1 -0
- package/dist/client/client.d.ts +78 -138
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/configuration.d.ts +23 -1
- package/dist/client/configuration.d.ts.map +1 -1
- package/dist/client/index.d.ts +2 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/types.d.ts +188 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/index.cjs +333 -91
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +314 -73
- package/dist/index.js.map +1 -1
- package/dist/server/server.d.ts +19 -4
- package/dist/server/server.d.ts.map +1 -1
- package/package.json +9 -7
- package/dist/client/elicitationActions.d.ts.map +0 -1
- package/dist/client/promptActions.d.ts.map +0 -1
- package/dist/client/resourceActions.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
+
import $RefParser from '@apidevtools/json-schema-ref-parser';
|
|
1
2
|
import { MastraBase } from '@mastra/core/base';
|
|
2
3
|
import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
|
|
3
|
-
import { DEFAULT_REQUEST_TIMEOUT_MSEC } from '@modelcontextprotocol/sdk/shared/protocol.js';
|
|
4
|
-
import equal from 'fast-deep-equal';
|
|
5
|
-
import { v5 } from 'uuid';
|
|
6
|
-
import $RefParser from '@apidevtools/json-schema-ref-parser';
|
|
7
4
|
import { createTool } from '@mastra/core/tools';
|
|
8
|
-
import {
|
|
5
|
+
import { isZodType, makeCoreTool } from '@mastra/core/utils';
|
|
9
6
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
10
7
|
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
11
8
|
import { StdioClientTransport, getDefaultEnvironment } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
12
9
|
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
13
|
-
import {
|
|
10
|
+
import { DEFAULT_REQUEST_TIMEOUT_MSEC } from '@modelcontextprotocol/sdk/shared/protocol.js';
|
|
11
|
+
import { ListRootsRequestSchema, ListResourcesResultSchema, ReadResourceResultSchema, ListResourceTemplatesResultSchema, ListPromptsResultSchema, GetPromptResultSchema, PromptListChangedNotificationSchema, ResourceUpdatedNotificationSchema, ResourceListChangedNotificationSchema, ElicitRequestSchema, ProgressNotificationSchema, ListToolsRequestSchema, CallToolRequestSchema, SetLevelRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListResourceTemplatesRequestSchema, SubscribeRequestSchema, UnsubscribeRequestSchema, ListPromptsRequestSchema, PromptSchema, GetPromptRequestSchema, CallToolResultSchema, ErrorCode, JSONRPCMessageSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
14
12
|
import { asyncExitHook, gracefulExit } from 'exit-hook';
|
|
15
13
|
import { z } from 'zod';
|
|
16
14
|
import { convertJsonSchemaToZod } from 'zod-from-json-schema';
|
|
17
15
|
import { convertJsonSchemaToZod as convertJsonSchemaToZod$1 } from 'zod-from-json-schema-v3';
|
|
16
|
+
import equal from 'fast-deep-equal';
|
|
17
|
+
import { v5 } from 'uuid';
|
|
18
18
|
import { randomUUID } from 'crypto';
|
|
19
19
|
import { MCPServerBase } from '@mastra/core/mcp';
|
|
20
20
|
import { RequestContext } from '@mastra/core/request-context';
|
|
@@ -23,9 +23,9 @@ import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
|
|
|
23
23
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
24
24
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
25
25
|
|
|
26
|
-
// src/client/
|
|
26
|
+
// src/client/client.ts
|
|
27
27
|
|
|
28
|
-
// src/client/
|
|
28
|
+
// src/client/actions/elicitation.ts
|
|
29
29
|
var ElicitationClientActions = class {
|
|
30
30
|
client;
|
|
31
31
|
logger;
|
|
@@ -78,6 +78,23 @@ var ElicitationClientActions = class {
|
|
|
78
78
|
this.client.setElicitationRequestHandler(handler);
|
|
79
79
|
}
|
|
80
80
|
};
|
|
81
|
+
|
|
82
|
+
// src/client/actions/progress.ts
|
|
83
|
+
var ProgressClientActions = class {
|
|
84
|
+
client;
|
|
85
|
+
logger;
|
|
86
|
+
constructor({ client, logger }) {
|
|
87
|
+
this.client = client;
|
|
88
|
+
this.logger = logger;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Set a notification handler for progress updates.
|
|
92
|
+
* @param handler The callback function to handle progress notifications.
|
|
93
|
+
*/
|
|
94
|
+
onUpdate(handler) {
|
|
95
|
+
this.client.setProgressNotificationHandler(handler);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
81
98
|
var PromptClientActions = class {
|
|
82
99
|
client;
|
|
83
100
|
logger;
|
|
@@ -391,15 +408,21 @@ var InternalMastraMCPClient = class extends MastraBase {
|
|
|
391
408
|
timeout;
|
|
392
409
|
logHandler;
|
|
393
410
|
enableServerLogs;
|
|
411
|
+
enableProgressTracking;
|
|
394
412
|
serverConfig;
|
|
395
413
|
transport;
|
|
396
414
|
currentOperationContext = null;
|
|
415
|
+
exitHookUnsubscribe;
|
|
416
|
+
sigTermHandler;
|
|
417
|
+
_roots;
|
|
397
418
|
/** Provides access to resource operations (list, read, subscribe, etc.) */
|
|
398
419
|
resources;
|
|
399
420
|
/** Provides access to prompt operations (list, get, notifications) */
|
|
400
421
|
prompts;
|
|
401
422
|
/** Provides access to elicitation operations (request handling) */
|
|
402
423
|
elicitation;
|
|
424
|
+
/** Provides access to progress operations (notifications) */
|
|
425
|
+
progress;
|
|
403
426
|
/**
|
|
404
427
|
* @internal
|
|
405
428
|
*/
|
|
@@ -416,7 +439,15 @@ var InternalMastraMCPClient = class extends MastraBase {
|
|
|
416
439
|
this.logHandler = server.logger;
|
|
417
440
|
this.enableServerLogs = server.enableServerLogs ?? true;
|
|
418
441
|
this.serverConfig = server;
|
|
419
|
-
|
|
442
|
+
this.enableProgressTracking = !!server.enableProgressTracking;
|
|
443
|
+
this._roots = server.roots ?? [];
|
|
444
|
+
const hasRoots = this._roots.length > 0 || !!capabilities.roots;
|
|
445
|
+
const clientCapabilities = {
|
|
446
|
+
...capabilities,
|
|
447
|
+
elicitation: {},
|
|
448
|
+
// Auto-enable roots capability if roots are provided
|
|
449
|
+
...hasRoots ? { roots: { listChanged: true, ...capabilities.roots ?? {} } } : {}
|
|
450
|
+
};
|
|
420
451
|
this.client = new Client(
|
|
421
452
|
{
|
|
422
453
|
name,
|
|
@@ -427,9 +458,13 @@ var InternalMastraMCPClient = class extends MastraBase {
|
|
|
427
458
|
}
|
|
428
459
|
);
|
|
429
460
|
this.setupLogging();
|
|
461
|
+
if (hasRoots) {
|
|
462
|
+
this.setupRootsHandler();
|
|
463
|
+
}
|
|
430
464
|
this.resources = new ResourceClientActions({ client: this, logger: this.logger });
|
|
431
465
|
this.prompts = new PromptClientActions({ client: this, logger: this.logger });
|
|
432
466
|
this.elicitation = new ElicitationClientActions({ client: this, logger: this.logger });
|
|
467
|
+
this.progress = new ProgressClientActions({ client: this, logger: this.logger });
|
|
433
468
|
}
|
|
434
469
|
/**
|
|
435
470
|
* Log a message at the specified level
|
|
@@ -468,6 +503,63 @@ var InternalMastraMCPClient = class extends MastraBase {
|
|
|
468
503
|
);
|
|
469
504
|
}
|
|
470
505
|
}
|
|
506
|
+
/**
|
|
507
|
+
* Set up handler for roots/list requests from the server.
|
|
508
|
+
*
|
|
509
|
+
* Per MCP spec (https://modelcontextprotocol.io/specification/2025-11-25/client/roots):
|
|
510
|
+
* When a server sends a roots/list request, the client responds with the configured roots.
|
|
511
|
+
*/
|
|
512
|
+
setupRootsHandler() {
|
|
513
|
+
this.log("debug", "Setting up roots/list request handler");
|
|
514
|
+
this.client.setRequestHandler(ListRootsRequestSchema, async () => {
|
|
515
|
+
this.log("debug", `Responding to roots/list request with ${this._roots.length} roots`);
|
|
516
|
+
return { roots: this._roots };
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Get the currently configured roots.
|
|
521
|
+
*
|
|
522
|
+
* @returns Array of configured filesystem roots
|
|
523
|
+
*/
|
|
524
|
+
get roots() {
|
|
525
|
+
return [...this._roots];
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Update the list of filesystem roots and notify the server.
|
|
529
|
+
*
|
|
530
|
+
* Per MCP spec, when roots change, the client sends a `notifications/roots/list_changed`
|
|
531
|
+
* notification to inform the server that it should re-fetch the roots list.
|
|
532
|
+
*
|
|
533
|
+
* @param roots - New list of filesystem roots
|
|
534
|
+
*
|
|
535
|
+
* @example
|
|
536
|
+
* ```typescript
|
|
537
|
+
* await client.setRoots([
|
|
538
|
+
* { uri: 'file:///home/user/projects', name: 'Projects' },
|
|
539
|
+
* { uri: 'file:///tmp', name: 'Temp' }
|
|
540
|
+
* ]);
|
|
541
|
+
* ```
|
|
542
|
+
*/
|
|
543
|
+
async setRoots(roots) {
|
|
544
|
+
this.log("debug", `Updating roots to ${roots.length} entries`);
|
|
545
|
+
this._roots = [...roots];
|
|
546
|
+
await this.sendRootsListChanged();
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Send a roots/list_changed notification to the server.
|
|
550
|
+
*
|
|
551
|
+
* Per MCP spec, clients that support `listChanged` MUST send this notification
|
|
552
|
+
* when the list of roots changes. The server will then call roots/list to get
|
|
553
|
+
* the updated list.
|
|
554
|
+
*/
|
|
555
|
+
async sendRootsListChanged() {
|
|
556
|
+
if (!this.transport) {
|
|
557
|
+
this.log("debug", "Cannot send roots/list_changed: not connected");
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
this.log("debug", "Sending notifications/roots/list_changed");
|
|
561
|
+
await this.client.notification({ method: "notifications/roots/list_changed" });
|
|
562
|
+
}
|
|
471
563
|
async connectStdio(command) {
|
|
472
564
|
this.log("debug", `Using Stdio transport for command: ${command}`);
|
|
473
565
|
try {
|
|
@@ -561,14 +653,19 @@ var InternalMastraMCPClient = class extends MastraBase {
|
|
|
561
653
|
reject(e);
|
|
562
654
|
}
|
|
563
655
|
});
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
656
|
+
if (!this.exitHookUnsubscribe) {
|
|
657
|
+
this.exitHookUnsubscribe = asyncExitHook(
|
|
658
|
+
async () => {
|
|
659
|
+
this.log("debug", `Disconnecting MCP server during exit`);
|
|
660
|
+
await this.disconnect();
|
|
661
|
+
},
|
|
662
|
+
{ wait: 5e3 }
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
if (!this.sigTermHandler) {
|
|
666
|
+
this.sigTermHandler = () => gracefulExit();
|
|
667
|
+
process.on("SIGTERM", this.sigTermHandler);
|
|
668
|
+
}
|
|
572
669
|
this.log("debug", `Successfully connected to MCP server`);
|
|
573
670
|
return this.isConnected;
|
|
574
671
|
}
|
|
@@ -603,8 +700,63 @@ var InternalMastraMCPClient = class extends MastraBase {
|
|
|
603
700
|
throw e;
|
|
604
701
|
} finally {
|
|
605
702
|
this.transport = void 0;
|
|
606
|
-
this.isConnected =
|
|
703
|
+
this.isConnected = null;
|
|
704
|
+
if (this.exitHookUnsubscribe) {
|
|
705
|
+
this.exitHookUnsubscribe();
|
|
706
|
+
this.exitHookUnsubscribe = void 0;
|
|
707
|
+
}
|
|
708
|
+
if (this.sigTermHandler) {
|
|
709
|
+
process.off("SIGTERM", this.sigTermHandler);
|
|
710
|
+
this.sigTermHandler = void 0;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Checks if an error indicates a session invalidation that requires reconnection.
|
|
716
|
+
*
|
|
717
|
+
* Common session-related errors include:
|
|
718
|
+
* - "No valid session ID provided" (HTTP 400)
|
|
719
|
+
* - "Server not initialized" (HTTP 400)
|
|
720
|
+
* - Connection refused errors
|
|
721
|
+
*
|
|
722
|
+
* @param error - The error to check
|
|
723
|
+
* @returns true if the error indicates a session problem requiring reconnection
|
|
724
|
+
*
|
|
725
|
+
* @internal
|
|
726
|
+
*/
|
|
727
|
+
isSessionError(error) {
|
|
728
|
+
if (!(error instanceof Error)) {
|
|
729
|
+
return false;
|
|
730
|
+
}
|
|
731
|
+
const errorMessage = error.message.toLowerCase();
|
|
732
|
+
return errorMessage.includes("no valid session") || errorMessage.includes("session") || errorMessage.includes("server not initialized") || errorMessage.includes("http 400") || errorMessage.includes("http 401") || errorMessage.includes("http 403") || errorMessage.includes("econnrefused") || errorMessage.includes("fetch failed") || errorMessage.includes("connection refused");
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Forces a reconnection to the MCP server by disconnecting and reconnecting.
|
|
736
|
+
*
|
|
737
|
+
* This is useful when the session becomes invalid (e.g., after server restart)
|
|
738
|
+
* and the client needs to establish a fresh connection.
|
|
739
|
+
*
|
|
740
|
+
* @returns Promise resolving when reconnection is complete
|
|
741
|
+
* @throws {Error} If reconnection fails
|
|
742
|
+
*
|
|
743
|
+
* @internal
|
|
744
|
+
*/
|
|
745
|
+
async forceReconnect() {
|
|
746
|
+
this.log("debug", "Forcing reconnection to MCP server...");
|
|
747
|
+
try {
|
|
748
|
+
if (this.transport) {
|
|
749
|
+
await this.transport.close();
|
|
750
|
+
}
|
|
751
|
+
} catch (e) {
|
|
752
|
+
this.log("debug", "Error during force disconnect (ignored)", {
|
|
753
|
+
error: e instanceof Error ? e.message : String(e)
|
|
754
|
+
});
|
|
607
755
|
}
|
|
756
|
+
this.transport = void 0;
|
|
757
|
+
this.isConnected = null;
|
|
758
|
+
await this.connect();
|
|
759
|
+
this.log("debug", "Successfully reconnected to MCP server");
|
|
608
760
|
}
|
|
609
761
|
async listResources() {
|
|
610
762
|
this.log("debug", `Requesting resources from MCP server`);
|
|
@@ -692,6 +844,12 @@ var InternalMastraMCPClient = class extends MastraBase {
|
|
|
692
844
|
return handler(request.params);
|
|
693
845
|
});
|
|
694
846
|
}
|
|
847
|
+
setProgressNotificationHandler(handler) {
|
|
848
|
+
this.log("debug", "Setting progress notification handler");
|
|
849
|
+
this.client.setNotificationHandler(ProgressNotificationSchema, (notification) => {
|
|
850
|
+
handler(notification.params);
|
|
851
|
+
});
|
|
852
|
+
}
|
|
695
853
|
async convertInputSchema(inputSchema) {
|
|
696
854
|
if (isZodType(inputSchema)) {
|
|
697
855
|
return inputSchema;
|
|
@@ -765,7 +923,7 @@ var InternalMastraMCPClient = class extends MastraBase {
|
|
|
765
923
|
}
|
|
766
924
|
async tools() {
|
|
767
925
|
this.log("debug", `Requesting tools from MCP server`);
|
|
768
|
-
const { tools } = await this.client.listTools({ timeout: this.timeout });
|
|
926
|
+
const { tools } = await this.client.listTools({}, { timeout: this.timeout });
|
|
769
927
|
const toolsRes = {};
|
|
770
928
|
for (const tool of tools) {
|
|
771
929
|
this.log("debug", `Processing tool: ${tool.name}`);
|
|
@@ -778,12 +936,14 @@ var InternalMastraMCPClient = class extends MastraBase {
|
|
|
778
936
|
execute: async (input, context) => {
|
|
779
937
|
const previousContext = this.currentOperationContext;
|
|
780
938
|
this.currentOperationContext = context?.requestContext || null;
|
|
781
|
-
|
|
782
|
-
this.log("debug", `Executing tool: ${tool.name}`, { toolArgs: input });
|
|
939
|
+
const executeToolCall = async () => {
|
|
940
|
+
this.log("debug", `Executing tool: ${tool.name}`, { toolArgs: input, runId: context?.runId });
|
|
783
941
|
const res = await this.client.callTool(
|
|
784
942
|
{
|
|
785
943
|
name: tool.name,
|
|
786
|
-
arguments: input
|
|
944
|
+
arguments: input,
|
|
945
|
+
// Use runId as progress token if available, otherwise generate a random UUID
|
|
946
|
+
...this.enableProgressTracking ? { _meta: { progressToken: context?.runId || crypto.randomUUID() } } : {}
|
|
787
947
|
},
|
|
788
948
|
CallToolResultSchema,
|
|
789
949
|
{
|
|
@@ -791,8 +951,31 @@ var InternalMastraMCPClient = class extends MastraBase {
|
|
|
791
951
|
}
|
|
792
952
|
);
|
|
793
953
|
this.log("debug", `Tool executed successfully: ${tool.name}`);
|
|
954
|
+
if (res.structuredContent !== void 0) {
|
|
955
|
+
return res.structuredContent;
|
|
956
|
+
}
|
|
794
957
|
return res;
|
|
958
|
+
};
|
|
959
|
+
try {
|
|
960
|
+
return await executeToolCall();
|
|
795
961
|
} catch (e) {
|
|
962
|
+
if (this.isSessionError(e)) {
|
|
963
|
+
this.log("debug", `Session error detected for tool ${tool.name}, attempting reconnection...`, {
|
|
964
|
+
error: e instanceof Error ? e.message : String(e)
|
|
965
|
+
});
|
|
966
|
+
try {
|
|
967
|
+
await this.forceReconnect();
|
|
968
|
+
this.log("debug", `Retrying tool ${tool.name} after reconnection...`);
|
|
969
|
+
return await executeToolCall();
|
|
970
|
+
} catch (reconnectError) {
|
|
971
|
+
this.log("error", `Reconnection or retry failed for tool ${tool.name}`, {
|
|
972
|
+
originalError: e instanceof Error ? e.message : String(e),
|
|
973
|
+
reconnectError: reconnectError instanceof Error ? reconnectError.stack : String(reconnectError),
|
|
974
|
+
toolArgs: input
|
|
975
|
+
});
|
|
976
|
+
throw e;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
796
979
|
this.log("error", `Error calling tool: ${tool.name}`, {
|
|
797
980
|
error: e instanceof Error ? e.stack : JSON.stringify(e, null, 2),
|
|
798
981
|
toolArgs: input
|
|
@@ -816,8 +999,6 @@ var InternalMastraMCPClient = class extends MastraBase {
|
|
|
816
999
|
return toolsRes;
|
|
817
1000
|
}
|
|
818
1001
|
};
|
|
819
|
-
|
|
820
|
-
// src/client/configuration.ts
|
|
821
1002
|
var mcpClientInstances = /* @__PURE__ */ new Map();
|
|
822
1003
|
var MCPClient = class extends MastraBase {
|
|
823
1004
|
serverConfigs = {};
|
|
@@ -890,6 +1071,48 @@ To fix this you have three different options:
|
|
|
890
1071
|
this.addToInstanceCache();
|
|
891
1072
|
return this;
|
|
892
1073
|
}
|
|
1074
|
+
/**
|
|
1075
|
+
* Provides access to progress-related operations for tracking long-running operations.
|
|
1076
|
+
*
|
|
1077
|
+
* Progress tracking allows MCP servers to send updates about the status of ongoing operations,
|
|
1078
|
+
* providing real-time feedback to users about task completion and current state.
|
|
1079
|
+
*
|
|
1080
|
+
* @example
|
|
1081
|
+
* ```typescript
|
|
1082
|
+
* // Set up handler for progress updates from a server
|
|
1083
|
+
* await mcp.progress.onUpdate('serverName', (params) => {
|
|
1084
|
+
* console.log(`Progress: ${params.progress}%`);
|
|
1085
|
+
* console.log(`Status: ${params.message}`);
|
|
1086
|
+
*
|
|
1087
|
+
* if (params.total) {
|
|
1088
|
+
* console.log(`Completed ${params.progress} of ${params.total} items`);
|
|
1089
|
+
* }
|
|
1090
|
+
* });
|
|
1091
|
+
* ```
|
|
1092
|
+
*/
|
|
1093
|
+
get progress() {
|
|
1094
|
+
this.addToInstanceCache();
|
|
1095
|
+
return {
|
|
1096
|
+
onUpdate: async (serverName, handler) => {
|
|
1097
|
+
try {
|
|
1098
|
+
const internalClient = await this.getConnectedClientForServer(serverName);
|
|
1099
|
+
return internalClient.progress.onUpdate(handler);
|
|
1100
|
+
} catch (err) {
|
|
1101
|
+
throw new MastraError(
|
|
1102
|
+
{
|
|
1103
|
+
id: "MCP_CLIENT_ON_UPDATE_PROGRESS_FAILED",
|
|
1104
|
+
domain: ErrorDomain.MCP,
|
|
1105
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1106
|
+
details: {
|
|
1107
|
+
serverName
|
|
1108
|
+
}
|
|
1109
|
+
},
|
|
1110
|
+
err
|
|
1111
|
+
);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
893
1116
|
/**
|
|
894
1117
|
* Provides access to elicitation-related operations for interactive user input collection.
|
|
895
1118
|
*
|
|
@@ -2180,6 +2403,41 @@ var MCPServer = class extends MCPServerBase {
|
|
|
2180
2403
|
this.logger.debug(`Received elicitation response: ${JSON.stringify(response)}`);
|
|
2181
2404
|
return response;
|
|
2182
2405
|
}
|
|
2406
|
+
/**
|
|
2407
|
+
* Reads and parses the JSON body from an HTTP request.
|
|
2408
|
+
* If the request body was already parsed by middleware (e.g., express.json()),
|
|
2409
|
+
* it uses the pre-parsed body from req.body. Otherwise, it reads from the stream.
|
|
2410
|
+
*
|
|
2411
|
+
* This allows the MCP server to work with Express apps that use express.json()
|
|
2412
|
+
* globally without requiring special route exclusions.
|
|
2413
|
+
*
|
|
2414
|
+
* @param req - The incoming HTTP request
|
|
2415
|
+
* @param options - Optional configuration
|
|
2416
|
+
* @param options.preParsedOnly - If true, only return pre-parsed body from middleware,
|
|
2417
|
+
* returning undefined if not available. This allows the caller to fall back to
|
|
2418
|
+
* their own body reading logic (e.g., SDK's getRawBody with size limits).
|
|
2419
|
+
*/
|
|
2420
|
+
async readJsonBody(req, options) {
|
|
2421
|
+
const reqWithBody = req;
|
|
2422
|
+
if (reqWithBody.body !== void 0) {
|
|
2423
|
+
return reqWithBody.body;
|
|
2424
|
+
}
|
|
2425
|
+
if (options?.preParsedOnly) {
|
|
2426
|
+
return void 0;
|
|
2427
|
+
}
|
|
2428
|
+
return new Promise((resolve, reject) => {
|
|
2429
|
+
let data = "";
|
|
2430
|
+
req.on("data", (chunk) => data += chunk);
|
|
2431
|
+
req.on("end", () => {
|
|
2432
|
+
try {
|
|
2433
|
+
resolve(JSON.parse(data));
|
|
2434
|
+
} catch (e) {
|
|
2435
|
+
reject(e);
|
|
2436
|
+
}
|
|
2437
|
+
});
|
|
2438
|
+
req.on("error", reject);
|
|
2439
|
+
});
|
|
2440
|
+
}
|
|
2183
2441
|
/**
|
|
2184
2442
|
* Creates a new Server instance configured with all handlers for HTTP sessions.
|
|
2185
2443
|
* Each HTTP client connection gets its own Server instance to avoid routing conflicts.
|
|
@@ -2568,7 +2826,9 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
|
|
|
2568
2826
|
try {
|
|
2569
2827
|
const proxiedContext = context?.requestContext || new RequestContext();
|
|
2570
2828
|
if (context?.mcp?.extra) {
|
|
2571
|
-
|
|
2829
|
+
Object.entries(context.mcp.extra).forEach(([key, value]) => {
|
|
2830
|
+
proxiedContext.set(key, value);
|
|
2831
|
+
});
|
|
2572
2832
|
}
|
|
2573
2833
|
const response = await agent.generate(inputData.message, {
|
|
2574
2834
|
...context ?? {},
|
|
@@ -2637,10 +2897,16 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
|
|
|
2637
2897
|
inputData
|
|
2638
2898
|
);
|
|
2639
2899
|
try {
|
|
2640
|
-
const
|
|
2900
|
+
const proxiedContext = context?.requestContext || new RequestContext();
|
|
2901
|
+
if (context?.mcp?.extra) {
|
|
2902
|
+
Object.entries(context.mcp.extra).forEach(([key, value]) => {
|
|
2903
|
+
proxiedContext.set(key, value);
|
|
2904
|
+
});
|
|
2905
|
+
}
|
|
2906
|
+
const run2 = await workflow.createRun({ runId: proxiedContext?.get("runId") });
|
|
2641
2907
|
const response = await run2.start({
|
|
2642
2908
|
inputData,
|
|
2643
|
-
requestContext:
|
|
2909
|
+
requestContext: proxiedContext,
|
|
2644
2910
|
tracingContext: context?.tracingContext
|
|
2645
2911
|
});
|
|
2646
2912
|
return response;
|
|
@@ -2796,7 +3062,7 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
|
|
|
2796
3062
|
*
|
|
2797
3063
|
* @example
|
|
2798
3064
|
* ```typescript
|
|
2799
|
-
* import http from 'http';
|
|
3065
|
+
* import http from 'node:http';
|
|
2800
3066
|
*
|
|
2801
3067
|
* const httpServer = http.createServer(async (req, res) => {
|
|
2802
3068
|
* await server.startSSE({
|
|
@@ -2827,7 +3093,8 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
|
|
|
2827
3093
|
res.end("SSE connection not established");
|
|
2828
3094
|
return;
|
|
2829
3095
|
}
|
|
2830
|
-
await this.
|
|
3096
|
+
const parsedBody = await this.readJsonBody(req, { preParsedOnly: true });
|
|
3097
|
+
await this.sseTransport.handlePostMessage(req, res, parsedBody);
|
|
2831
3098
|
} else {
|
|
2832
3099
|
this.logger.debug("Unknown path:", { path: url.pathname });
|
|
2833
3100
|
res.writeHead(404);
|
|
@@ -2954,8 +3221,8 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
|
|
|
2954
3221
|
*
|
|
2955
3222
|
* @example
|
|
2956
3223
|
* ```typescript
|
|
2957
|
-
* import http from 'http';
|
|
2958
|
-
* import { randomUUID } from 'crypto';
|
|
3224
|
+
* import http from 'node:http';
|
|
3225
|
+
* import { randomUUID } from 'node:crypto';
|
|
2959
3226
|
*
|
|
2960
3227
|
* const httpServer = http.createServer(async (req, res) => {
|
|
2961
3228
|
* await server.startHTTP({
|
|
@@ -2999,7 +3266,7 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
|
|
|
2999
3266
|
httpPath,
|
|
3000
3267
|
req,
|
|
3001
3268
|
res,
|
|
3002
|
-
options
|
|
3269
|
+
options
|
|
3003
3270
|
}) {
|
|
3004
3271
|
this.logger.debug(`startHTTP: Received ${req.method} request to ${url.pathname}`);
|
|
3005
3272
|
if (url.pathname !== httpPath) {
|
|
@@ -3008,11 +3275,18 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
|
|
|
3008
3275
|
res.end();
|
|
3009
3276
|
return;
|
|
3010
3277
|
}
|
|
3011
|
-
|
|
3012
|
-
|
|
3278
|
+
const isStatelessMode = options?.serverless || options && "sessionIdGenerator" in options && options.sessionIdGenerator === void 0;
|
|
3279
|
+
if (isStatelessMode) {
|
|
3280
|
+
this.logger.debug("startHTTP: Running in stateless mode (serverless or sessionIdGenerator: undefined)");
|
|
3013
3281
|
await this.handleServerlessRequest(req, res);
|
|
3014
3282
|
return;
|
|
3015
3283
|
}
|
|
3284
|
+
const mergedOptions = {
|
|
3285
|
+
sessionIdGenerator: () => randomUUID(),
|
|
3286
|
+
// default: enabled
|
|
3287
|
+
...options
|
|
3288
|
+
// user-provided overrides default
|
|
3289
|
+
};
|
|
3016
3290
|
const sessionId = req.headers["mcp-session-id"];
|
|
3017
3291
|
let transport;
|
|
3018
3292
|
this.logger.debug(
|
|
@@ -3027,40 +3301,18 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
|
|
|
3027
3301
|
`startHTTP: Handling GET request for existing session ${sessionId}. Calling transport.handleRequest.`
|
|
3028
3302
|
);
|
|
3029
3303
|
}
|
|
3030
|
-
const body = req.method === "POST" ? await
|
|
3031
|
-
let data = "";
|
|
3032
|
-
req.on("data", (chunk) => data += chunk);
|
|
3033
|
-
req.on("end", () => {
|
|
3034
|
-
try {
|
|
3035
|
-
resolve(JSON.parse(data));
|
|
3036
|
-
} catch (e) {
|
|
3037
|
-
reject(e);
|
|
3038
|
-
}
|
|
3039
|
-
});
|
|
3040
|
-
req.on("error", reject);
|
|
3041
|
-
}) : void 0;
|
|
3304
|
+
const body = req.method === "POST" ? await this.readJsonBody(req) : void 0;
|
|
3042
3305
|
await transport.handleRequest(req, res, body);
|
|
3043
3306
|
} else {
|
|
3044
3307
|
this.logger.debug(`startHTTP: No existing Streamable HTTP session ID found. ${req.method}`);
|
|
3045
3308
|
if (req.method === "POST") {
|
|
3046
|
-
const body = await
|
|
3047
|
-
let data = "";
|
|
3048
|
-
req.on("data", (chunk) => data += chunk);
|
|
3049
|
-
req.on("end", () => {
|
|
3050
|
-
try {
|
|
3051
|
-
resolve(JSON.parse(data));
|
|
3052
|
-
} catch (e) {
|
|
3053
|
-
reject(e);
|
|
3054
|
-
}
|
|
3055
|
-
});
|
|
3056
|
-
req.on("error", reject);
|
|
3057
|
-
});
|
|
3309
|
+
const body = await this.readJsonBody(req);
|
|
3058
3310
|
const { isInitializeRequest } = await import('@modelcontextprotocol/sdk/types.js');
|
|
3059
3311
|
if (isInitializeRequest(body)) {
|
|
3060
3312
|
this.logger.debug("startHTTP: Received Streamable HTTP initialize request, creating new transport.");
|
|
3061
3313
|
transport = new StreamableHTTPServerTransport({
|
|
3062
|
-
...
|
|
3063
|
-
sessionIdGenerator:
|
|
3314
|
+
...mergedOptions,
|
|
3315
|
+
sessionIdGenerator: mergedOptions.sessionIdGenerator,
|
|
3064
3316
|
onsessioninitialized: (id) => {
|
|
3065
3317
|
this.streamableHTTPTransports.set(id, transport);
|
|
3066
3318
|
}
|
|
@@ -3164,18 +3416,7 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
|
|
|
3164
3416
|
async handleServerlessRequest(req, res) {
|
|
3165
3417
|
try {
|
|
3166
3418
|
this.logger.debug(`handleServerlessRequest: Received ${req.method} request`);
|
|
3167
|
-
const body = req.method === "POST" ? await
|
|
3168
|
-
let data = "";
|
|
3169
|
-
req.on("data", (chunk) => data += chunk);
|
|
3170
|
-
req.on("end", () => {
|
|
3171
|
-
try {
|
|
3172
|
-
resolve(JSON.parse(data));
|
|
3173
|
-
} catch (e) {
|
|
3174
|
-
reject(new Error(`Invalid JSON in request body: ${e instanceof Error ? e.message : String(e)}`));
|
|
3175
|
-
}
|
|
3176
|
-
});
|
|
3177
|
-
req.on("error", reject);
|
|
3178
|
-
}) : void 0;
|
|
3419
|
+
const body = req.method === "POST" ? await this.readJsonBody(req) : void 0;
|
|
3179
3420
|
this.logger.debug(`handleServerlessRequest: Processing ${req.method} request`, {
|
|
3180
3421
|
method: body?.method,
|
|
3181
3422
|
id: body?.id
|
|
@@ -3606,6 +3847,6 @@ Provided arguments: ${JSON.stringify(args, null, 2)}`,
|
|
|
3606
3847
|
}
|
|
3607
3848
|
};
|
|
3608
3849
|
|
|
3609
|
-
export { MCPClient, MCPServer };
|
|
3850
|
+
export { InternalMastraMCPClient, MCPClient, MCPServer };
|
|
3610
3851
|
//# sourceMappingURL=index.js.map
|
|
3611
3852
|
//# sourceMappingURL=index.js.map
|