@mastra/mcp 1.0.0-beta.0 → 1.0.0-beta.10
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 +303 -0
- package/README.md +250 -24
- package/dist/__fixtures__/tools.d.ts +8 -5
- 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 +79 -132
- 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 +3 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/oauth-provider.d.ts +230 -0
- package/dist/client/oauth-provider.d.ts.map +1 -0
- package/dist/client/types.d.ts +237 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/docs/README.md +33 -0
- package/dist/docs/SKILL.md +46 -0
- package/dist/docs/SOURCE_MAP.json +59 -0
- package/dist/docs/mcp/01-overview.md +372 -0
- package/dist/docs/mcp/02-publishing-mcp-server.md +111 -0
- package/dist/docs/tools/01-reference.md +2306 -0
- package/dist/docs/tools-mcp/01-mcp-overview.md +384 -0
- package/dist/index.cjs +851 -107
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +774 -92
- package/dist/index.js.map +1 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/oauth-middleware.d.ts +142 -0
- package/dist/server/oauth-middleware.d.ts.map +1 -0
- package/dist/server/server.d.ts +20 -4
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/types.d.ts +11 -4
- package/dist/server/types.d.ts.map +1 -1
- package/dist/shared/index.d.ts +2 -0
- package/dist/shared/index.d.ts.map +1 -0
- package/dist/shared/oauth-types.d.ts +137 -0
- package/dist/shared/oauth-types.d.ts.map +1 -0
- package/package.json +17 -14
- 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';
|
|
@@ -22,10 +22,11 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
|
22
22
|
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
|
+
export { UnauthorizedError, auth, buildDiscoveryUrls, discoverAuthorizationServerMetadata, discoverOAuthMetadata, discoverOAuthProtectedResourceMetadata, exchangeAuthorization, extractResourceMetadataUrl, parseErrorResponse, refreshAuthorization, registerClient, selectResourceURL, startAuthorization } from '@modelcontextprotocol/sdk/client/auth.js';
|
|
25
26
|
|
|
26
|
-
// src/client/
|
|
27
|
+
// src/client/client.ts
|
|
27
28
|
|
|
28
|
-
// src/client/
|
|
29
|
+
// src/client/actions/elicitation.ts
|
|
29
30
|
var ElicitationClientActions = class {
|
|
30
31
|
client;
|
|
31
32
|
logger;
|
|
@@ -78,6 +79,23 @@ var ElicitationClientActions = class {
|
|
|
78
79
|
this.client.setElicitationRequestHandler(handler);
|
|
79
80
|
}
|
|
80
81
|
};
|
|
82
|
+
|
|
83
|
+
// src/client/actions/progress.ts
|
|
84
|
+
var ProgressClientActions = class {
|
|
85
|
+
client;
|
|
86
|
+
logger;
|
|
87
|
+
constructor({ client, logger }) {
|
|
88
|
+
this.client = client;
|
|
89
|
+
this.logger = logger;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Set a notification handler for progress updates.
|
|
93
|
+
* @param handler The callback function to handle progress notifications.
|
|
94
|
+
*/
|
|
95
|
+
onUpdate(handler) {
|
|
96
|
+
this.client.setProgressNotificationHandler(handler);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
81
99
|
var PromptClientActions = class {
|
|
82
100
|
client;
|
|
83
101
|
logger;
|
|
@@ -366,6 +384,8 @@ var ResourceClientActions = class {
|
|
|
366
384
|
};
|
|
367
385
|
|
|
368
386
|
// src/client/client.ts
|
|
387
|
+
var DEFAULT_SERVER_CONNECT_TIMEOUT_MSEC = 3e3;
|
|
388
|
+
var SSE_FALLBACK_STATUS_CODES = [400, 404, 405];
|
|
369
389
|
function convertLogLevelToLoggerMethod(level) {
|
|
370
390
|
switch (level) {
|
|
371
391
|
case "debug":
|
|
@@ -390,15 +410,21 @@ var InternalMastraMCPClient = class extends MastraBase {
|
|
|
390
410
|
timeout;
|
|
391
411
|
logHandler;
|
|
392
412
|
enableServerLogs;
|
|
413
|
+
enableProgressTracking;
|
|
393
414
|
serverConfig;
|
|
394
415
|
transport;
|
|
395
416
|
currentOperationContext = null;
|
|
417
|
+
exitHookUnsubscribe;
|
|
418
|
+
sigTermHandler;
|
|
419
|
+
_roots;
|
|
396
420
|
/** Provides access to resource operations (list, read, subscribe, etc.) */
|
|
397
421
|
resources;
|
|
398
422
|
/** Provides access to prompt operations (list, get, notifications) */
|
|
399
423
|
prompts;
|
|
400
424
|
/** Provides access to elicitation operations (request handling) */
|
|
401
425
|
elicitation;
|
|
426
|
+
/** Provides access to progress operations (notifications) */
|
|
427
|
+
progress;
|
|
402
428
|
/**
|
|
403
429
|
* @internal
|
|
404
430
|
*/
|
|
@@ -415,7 +441,15 @@ var InternalMastraMCPClient = class extends MastraBase {
|
|
|
415
441
|
this.logHandler = server.logger;
|
|
416
442
|
this.enableServerLogs = server.enableServerLogs ?? true;
|
|
417
443
|
this.serverConfig = server;
|
|
418
|
-
|
|
444
|
+
this.enableProgressTracking = !!server.enableProgressTracking;
|
|
445
|
+
this._roots = server.roots ?? [];
|
|
446
|
+
const hasRoots = this._roots.length > 0 || !!capabilities.roots;
|
|
447
|
+
const clientCapabilities = {
|
|
448
|
+
...capabilities,
|
|
449
|
+
elicitation: {},
|
|
450
|
+
// Auto-enable roots capability if roots are provided
|
|
451
|
+
...hasRoots ? { roots: { listChanged: true, ...capabilities.roots ?? {} } } : {}
|
|
452
|
+
};
|
|
419
453
|
this.client = new Client(
|
|
420
454
|
{
|
|
421
455
|
name,
|
|
@@ -426,9 +460,13 @@ var InternalMastraMCPClient = class extends MastraBase {
|
|
|
426
460
|
}
|
|
427
461
|
);
|
|
428
462
|
this.setupLogging();
|
|
463
|
+
if (hasRoots) {
|
|
464
|
+
this.setupRootsHandler();
|
|
465
|
+
}
|
|
429
466
|
this.resources = new ResourceClientActions({ client: this, logger: this.logger });
|
|
430
467
|
this.prompts = new PromptClientActions({ client: this, logger: this.logger });
|
|
431
468
|
this.elicitation = new ElicitationClientActions({ client: this, logger: this.logger });
|
|
469
|
+
this.progress = new ProgressClientActions({ client: this, logger: this.logger });
|
|
432
470
|
}
|
|
433
471
|
/**
|
|
434
472
|
* Log a message at the specified level
|
|
@@ -467,6 +505,63 @@ var InternalMastraMCPClient = class extends MastraBase {
|
|
|
467
505
|
);
|
|
468
506
|
}
|
|
469
507
|
}
|
|
508
|
+
/**
|
|
509
|
+
* Set up handler for roots/list requests from the server.
|
|
510
|
+
*
|
|
511
|
+
* Per MCP spec (https://modelcontextprotocol.io/specification/2025-11-25/client/roots):
|
|
512
|
+
* When a server sends a roots/list request, the client responds with the configured roots.
|
|
513
|
+
*/
|
|
514
|
+
setupRootsHandler() {
|
|
515
|
+
this.log("debug", "Setting up roots/list request handler");
|
|
516
|
+
this.client.setRequestHandler(ListRootsRequestSchema, async () => {
|
|
517
|
+
this.log("debug", `Responding to roots/list request with ${this._roots.length} roots`);
|
|
518
|
+
return { roots: this._roots };
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Get the currently configured roots.
|
|
523
|
+
*
|
|
524
|
+
* @returns Array of configured filesystem roots
|
|
525
|
+
*/
|
|
526
|
+
get roots() {
|
|
527
|
+
return [...this._roots];
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Update the list of filesystem roots and notify the server.
|
|
531
|
+
*
|
|
532
|
+
* Per MCP spec, when roots change, the client sends a `notifications/roots/list_changed`
|
|
533
|
+
* notification to inform the server that it should re-fetch the roots list.
|
|
534
|
+
*
|
|
535
|
+
* @param roots - New list of filesystem roots
|
|
536
|
+
*
|
|
537
|
+
* @example
|
|
538
|
+
* ```typescript
|
|
539
|
+
* await client.setRoots([
|
|
540
|
+
* { uri: 'file:///home/user/projects', name: 'Projects' },
|
|
541
|
+
* { uri: 'file:///tmp', name: 'Temp' }
|
|
542
|
+
* ]);
|
|
543
|
+
* ```
|
|
544
|
+
*/
|
|
545
|
+
async setRoots(roots) {
|
|
546
|
+
this.log("debug", `Updating roots to ${roots.length} entries`);
|
|
547
|
+
this._roots = [...roots];
|
|
548
|
+
await this.sendRootsListChanged();
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Send a roots/list_changed notification to the server.
|
|
552
|
+
*
|
|
553
|
+
* Per MCP spec, clients that support `listChanged` MUST send this notification
|
|
554
|
+
* when the list of roots changes. The server will then call roots/list to get
|
|
555
|
+
* the updated list.
|
|
556
|
+
*/
|
|
557
|
+
async sendRootsListChanged() {
|
|
558
|
+
if (!this.transport) {
|
|
559
|
+
this.log("debug", "Cannot send roots/list_changed: not connected");
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
this.log("debug", "Sending notifications/roots/list_changed");
|
|
563
|
+
await this.client.notification({ method: "notifications/roots/list_changed" });
|
|
564
|
+
}
|
|
470
565
|
async connectStdio(command) {
|
|
471
566
|
this.log("debug", `Using Stdio transport for command: ${command}`);
|
|
472
567
|
try {
|
|
@@ -483,7 +578,7 @@ var InternalMastraMCPClient = class extends MastraBase {
|
|
|
483
578
|
}
|
|
484
579
|
}
|
|
485
580
|
async connectHttp(url) {
|
|
486
|
-
const { requestInit, eventSourceInit, authProvider } = this.serverConfig;
|
|
581
|
+
const { requestInit, eventSourceInit, authProvider, connectTimeout, fetch: fetch2 } = this.serverConfig;
|
|
487
582
|
this.log("debug", `Attempting to connect to URL: ${url}`);
|
|
488
583
|
let shouldTrySSE = url.pathname.endsWith(`/sse`);
|
|
489
584
|
if (!shouldTrySSE) {
|
|
@@ -492,25 +587,33 @@ var InternalMastraMCPClient = class extends MastraBase {
|
|
|
492
587
|
const streamableTransport = new StreamableHTTPClientTransport(url, {
|
|
493
588
|
requestInit,
|
|
494
589
|
reconnectionOptions: this.serverConfig.reconnectionOptions,
|
|
495
|
-
authProvider
|
|
590
|
+
authProvider,
|
|
591
|
+
fetch: fetch2
|
|
496
592
|
});
|
|
497
593
|
await this.client.connect(streamableTransport, {
|
|
498
|
-
timeout:
|
|
499
|
-
// this is hardcoded to 3s because the long default timeout would be extremely slow for sse backwards compat (60s)
|
|
500
|
-
3e3
|
|
501
|
-
)
|
|
594
|
+
timeout: connectTimeout ?? DEFAULT_SERVER_CONNECT_TIMEOUT_MSEC
|
|
502
595
|
});
|
|
503
596
|
this.transport = streamableTransport;
|
|
504
597
|
this.log("debug", "Successfully connected using Streamable HTTP transport.");
|
|
505
598
|
} catch (error) {
|
|
506
599
|
this.log("debug", `Streamable HTTP transport failed: ${error}`);
|
|
600
|
+
const status = error?.code;
|
|
601
|
+
if (status !== void 0 && !SSE_FALLBACK_STATUS_CODES.includes(status)) {
|
|
602
|
+
throw error;
|
|
603
|
+
}
|
|
507
604
|
shouldTrySSE = true;
|
|
508
605
|
}
|
|
509
606
|
}
|
|
510
607
|
if (shouldTrySSE) {
|
|
511
608
|
this.log("debug", "Falling back to deprecated HTTP+SSE transport...");
|
|
512
609
|
try {
|
|
513
|
-
const
|
|
610
|
+
const sseEventSourceInit = fetch2 ? { ...eventSourceInit, fetch: fetch2 } : eventSourceInit;
|
|
611
|
+
const sseTransport = new SSEClientTransport(url, {
|
|
612
|
+
requestInit,
|
|
613
|
+
eventSourceInit: sseEventSourceInit,
|
|
614
|
+
authProvider,
|
|
615
|
+
fetch: fetch2
|
|
616
|
+
});
|
|
514
617
|
await this.client.connect(sseTransport, { timeout: this.serverConfig.timeout ?? this.timeout });
|
|
515
618
|
this.transport = sseTransport;
|
|
516
619
|
this.log("debug", "Successfully connected using deprecated HTTP+SSE transport.");
|
|
@@ -563,14 +666,19 @@ var InternalMastraMCPClient = class extends MastraBase {
|
|
|
563
666
|
reject(e);
|
|
564
667
|
}
|
|
565
668
|
});
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
669
|
+
if (!this.exitHookUnsubscribe) {
|
|
670
|
+
this.exitHookUnsubscribe = asyncExitHook(
|
|
671
|
+
async () => {
|
|
672
|
+
this.log("debug", `Disconnecting MCP server during exit`);
|
|
673
|
+
await this.disconnect();
|
|
674
|
+
},
|
|
675
|
+
{ wait: 5e3 }
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
if (!this.sigTermHandler) {
|
|
679
|
+
this.sigTermHandler = () => gracefulExit();
|
|
680
|
+
process.on("SIGTERM", this.sigTermHandler);
|
|
681
|
+
}
|
|
574
682
|
this.log("debug", `Successfully connected to MCP server`);
|
|
575
683
|
return this.isConnected;
|
|
576
684
|
}
|
|
@@ -605,8 +713,64 @@ var InternalMastraMCPClient = class extends MastraBase {
|
|
|
605
713
|
throw e;
|
|
606
714
|
} finally {
|
|
607
715
|
this.transport = void 0;
|
|
608
|
-
this.isConnected =
|
|
716
|
+
this.isConnected = null;
|
|
717
|
+
if (this.exitHookUnsubscribe) {
|
|
718
|
+
this.exitHookUnsubscribe();
|
|
719
|
+
this.exitHookUnsubscribe = void 0;
|
|
720
|
+
}
|
|
721
|
+
if (this.sigTermHandler) {
|
|
722
|
+
process.off("SIGTERM", this.sigTermHandler);
|
|
723
|
+
this.sigTermHandler = void 0;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* Checks if an error indicates a session invalidation that requires reconnection.
|
|
729
|
+
*
|
|
730
|
+
* Common session-related errors include:
|
|
731
|
+
* - "No valid session ID provided" (HTTP 400)
|
|
732
|
+
* - "Server not initialized" (HTTP 400)
|
|
733
|
+
* - "Not connected" (protocol state error)
|
|
734
|
+
* - Connection refused errors
|
|
735
|
+
*
|
|
736
|
+
* @param error - The error to check
|
|
737
|
+
* @returns true if the error indicates a session problem requiring reconnection
|
|
738
|
+
*
|
|
739
|
+
* @internal
|
|
740
|
+
*/
|
|
741
|
+
isSessionError(error) {
|
|
742
|
+
if (!(error instanceof Error)) {
|
|
743
|
+
return false;
|
|
744
|
+
}
|
|
745
|
+
const errorMessage = error.message.toLowerCase();
|
|
746
|
+
return errorMessage.includes("no valid session") || errorMessage.includes("session") || errorMessage.includes("server not initialized") || errorMessage.includes("not connected") || errorMessage.includes("http 400") || errorMessage.includes("http 401") || errorMessage.includes("http 403") || errorMessage.includes("econnrefused") || errorMessage.includes("fetch failed") || errorMessage.includes("connection refused");
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Forces a reconnection to the MCP server by disconnecting and reconnecting.
|
|
750
|
+
*
|
|
751
|
+
* This is useful when the session becomes invalid (e.g., after server restart)
|
|
752
|
+
* and the client needs to establish a fresh connection.
|
|
753
|
+
*
|
|
754
|
+
* @returns Promise resolving when reconnection is complete
|
|
755
|
+
* @throws {Error} If reconnection fails
|
|
756
|
+
*
|
|
757
|
+
* @internal
|
|
758
|
+
*/
|
|
759
|
+
async forceReconnect() {
|
|
760
|
+
this.log("debug", "Forcing reconnection to MCP server...");
|
|
761
|
+
try {
|
|
762
|
+
if (this.transport) {
|
|
763
|
+
await this.transport.close();
|
|
764
|
+
}
|
|
765
|
+
} catch (e) {
|
|
766
|
+
this.log("debug", "Error during force disconnect (ignored)", {
|
|
767
|
+
error: e instanceof Error ? e.message : String(e)
|
|
768
|
+
});
|
|
609
769
|
}
|
|
770
|
+
this.transport = void 0;
|
|
771
|
+
this.isConnected = null;
|
|
772
|
+
await this.connect();
|
|
773
|
+
this.log("debug", "Successfully reconnected to MCP server");
|
|
610
774
|
}
|
|
611
775
|
async listResources() {
|
|
612
776
|
this.log("debug", `Requesting resources from MCP server`);
|
|
@@ -694,6 +858,12 @@ var InternalMastraMCPClient = class extends MastraBase {
|
|
|
694
858
|
return handler(request.params);
|
|
695
859
|
});
|
|
696
860
|
}
|
|
861
|
+
setProgressNotificationHandler(handler) {
|
|
862
|
+
this.log("debug", "Setting progress notification handler");
|
|
863
|
+
this.client.setNotificationHandler(ProgressNotificationSchema, (notification) => {
|
|
864
|
+
handler(notification.params);
|
|
865
|
+
});
|
|
866
|
+
}
|
|
697
867
|
async convertInputSchema(inputSchema) {
|
|
698
868
|
if (isZodType(inputSchema)) {
|
|
699
869
|
return inputSchema;
|
|
@@ -767,7 +937,7 @@ var InternalMastraMCPClient = class extends MastraBase {
|
|
|
767
937
|
}
|
|
768
938
|
async tools() {
|
|
769
939
|
this.log("debug", `Requesting tools from MCP server`);
|
|
770
|
-
const { tools } = await this.client.listTools({ timeout: this.timeout });
|
|
940
|
+
const { tools } = await this.client.listTools({}, { timeout: this.timeout });
|
|
771
941
|
const toolsRes = {};
|
|
772
942
|
for (const tool of tools) {
|
|
773
943
|
this.log("debug", `Processing tool: ${tool.name}`);
|
|
@@ -780,12 +950,14 @@ var InternalMastraMCPClient = class extends MastraBase {
|
|
|
780
950
|
execute: async (input, context) => {
|
|
781
951
|
const previousContext = this.currentOperationContext;
|
|
782
952
|
this.currentOperationContext = context?.requestContext || null;
|
|
783
|
-
|
|
784
|
-
this.log("debug", `Executing tool: ${tool.name}`, { toolArgs: input });
|
|
953
|
+
const executeToolCall = async () => {
|
|
954
|
+
this.log("debug", `Executing tool: ${tool.name}`, { toolArgs: input, runId: context?.runId });
|
|
785
955
|
const res = await this.client.callTool(
|
|
786
956
|
{
|
|
787
957
|
name: tool.name,
|
|
788
|
-
arguments: input
|
|
958
|
+
arguments: input,
|
|
959
|
+
// Use runId as progress token if available, otherwise generate a random UUID
|
|
960
|
+
...this.enableProgressTracking ? { _meta: { progressToken: context?.runId || crypto.randomUUID() } } : {}
|
|
789
961
|
},
|
|
790
962
|
CallToolResultSchema,
|
|
791
963
|
{
|
|
@@ -793,8 +965,31 @@ var InternalMastraMCPClient = class extends MastraBase {
|
|
|
793
965
|
}
|
|
794
966
|
);
|
|
795
967
|
this.log("debug", `Tool executed successfully: ${tool.name}`);
|
|
968
|
+
if (res.structuredContent !== void 0) {
|
|
969
|
+
return res.structuredContent;
|
|
970
|
+
}
|
|
796
971
|
return res;
|
|
972
|
+
};
|
|
973
|
+
try {
|
|
974
|
+
return await executeToolCall();
|
|
797
975
|
} catch (e) {
|
|
976
|
+
if (this.isSessionError(e)) {
|
|
977
|
+
this.log("debug", `Session error detected for tool ${tool.name}, attempting reconnection...`, {
|
|
978
|
+
error: e instanceof Error ? e.message : String(e)
|
|
979
|
+
});
|
|
980
|
+
try {
|
|
981
|
+
await this.forceReconnect();
|
|
982
|
+
this.log("debug", `Retrying tool ${tool.name} after reconnection...`);
|
|
983
|
+
return await executeToolCall();
|
|
984
|
+
} catch (reconnectError) {
|
|
985
|
+
this.log("error", `Reconnection or retry failed for tool ${tool.name}`, {
|
|
986
|
+
originalError: e instanceof Error ? e.message : String(e),
|
|
987
|
+
reconnectError: reconnectError instanceof Error ? reconnectError.stack : String(reconnectError),
|
|
988
|
+
toolArgs: input
|
|
989
|
+
});
|
|
990
|
+
throw e;
|
|
991
|
+
}
|
|
992
|
+
}
|
|
798
993
|
this.log("error", `Error calling tool: ${tool.name}`, {
|
|
799
994
|
error: e instanceof Error ? e.stack : JSON.stringify(e, null, 2),
|
|
800
995
|
toolArgs: input
|
|
@@ -818,8 +1013,6 @@ var InternalMastraMCPClient = class extends MastraBase {
|
|
|
818
1013
|
return toolsRes;
|
|
819
1014
|
}
|
|
820
1015
|
};
|
|
821
|
-
|
|
822
|
-
// src/client/configuration.ts
|
|
823
1016
|
var mcpClientInstances = /* @__PURE__ */ new Map();
|
|
824
1017
|
var MCPClient = class extends MastraBase {
|
|
825
1018
|
serverConfigs = {};
|
|
@@ -892,6 +1085,48 @@ To fix this you have three different options:
|
|
|
892
1085
|
this.addToInstanceCache();
|
|
893
1086
|
return this;
|
|
894
1087
|
}
|
|
1088
|
+
/**
|
|
1089
|
+
* Provides access to progress-related operations for tracking long-running operations.
|
|
1090
|
+
*
|
|
1091
|
+
* Progress tracking allows MCP servers to send updates about the status of ongoing operations,
|
|
1092
|
+
* providing real-time feedback to users about task completion and current state.
|
|
1093
|
+
*
|
|
1094
|
+
* @example
|
|
1095
|
+
* ```typescript
|
|
1096
|
+
* // Set up handler for progress updates from a server
|
|
1097
|
+
* await mcp.progress.onUpdate('serverName', (params) => {
|
|
1098
|
+
* console.log(`Progress: ${params.progress}%`);
|
|
1099
|
+
* console.log(`Status: ${params.message}`);
|
|
1100
|
+
*
|
|
1101
|
+
* if (params.total) {
|
|
1102
|
+
* console.log(`Completed ${params.progress} of ${params.total} items`);
|
|
1103
|
+
* }
|
|
1104
|
+
* });
|
|
1105
|
+
* ```
|
|
1106
|
+
*/
|
|
1107
|
+
get progress() {
|
|
1108
|
+
this.addToInstanceCache();
|
|
1109
|
+
return {
|
|
1110
|
+
onUpdate: async (serverName, handler) => {
|
|
1111
|
+
try {
|
|
1112
|
+
const internalClient = await this.getConnectedClientForServer(serverName);
|
|
1113
|
+
return internalClient.progress.onUpdate(handler);
|
|
1114
|
+
} catch (err) {
|
|
1115
|
+
throw new MastraError(
|
|
1116
|
+
{
|
|
1117
|
+
id: "MCP_CLIENT_ON_UPDATE_PROGRESS_FAILED",
|
|
1118
|
+
domain: ErrorDomain.MCP,
|
|
1119
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1120
|
+
details: {
|
|
1121
|
+
serverName
|
|
1122
|
+
}
|
|
1123
|
+
},
|
|
1124
|
+
err
|
|
1125
|
+
);
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
};
|
|
1129
|
+
}
|
|
895
1130
|
/**
|
|
896
1131
|
* Provides access to elicitation-related operations for interactive user input collection.
|
|
897
1132
|
*
|
|
@@ -1580,14 +1815,204 @@ To fix this you have three different options:
|
|
|
1580
1815
|
}
|
|
1581
1816
|
};
|
|
1582
1817
|
|
|
1583
|
-
//
|
|
1818
|
+
// src/client/oauth-provider.ts
|
|
1819
|
+
var InMemoryOAuthStorage = class {
|
|
1820
|
+
data = /* @__PURE__ */ new Map();
|
|
1821
|
+
set(key, value) {
|
|
1822
|
+
this.data.set(key, value);
|
|
1823
|
+
}
|
|
1824
|
+
get(key) {
|
|
1825
|
+
return this.data.get(key);
|
|
1826
|
+
}
|
|
1827
|
+
delete(key) {
|
|
1828
|
+
this.data.delete(key);
|
|
1829
|
+
}
|
|
1830
|
+
clear() {
|
|
1831
|
+
this.data.clear();
|
|
1832
|
+
}
|
|
1833
|
+
};
|
|
1834
|
+
var MCPOAuthClientProvider = class {
|
|
1835
|
+
_redirectUrl;
|
|
1836
|
+
_clientMetadata;
|
|
1837
|
+
storage;
|
|
1838
|
+
onRedirect;
|
|
1839
|
+
generateState;
|
|
1840
|
+
_clientInfo;
|
|
1841
|
+
constructor(options) {
|
|
1842
|
+
this._redirectUrl = options.redirectUrl;
|
|
1843
|
+
this._clientMetadata = options.clientMetadata;
|
|
1844
|
+
this._clientInfo = options.clientInformation;
|
|
1845
|
+
this.storage = options.storage ?? new InMemoryOAuthStorage();
|
|
1846
|
+
this.onRedirect = options.onRedirectToAuthorization;
|
|
1847
|
+
this.generateState = options.stateGenerator ?? (() => crypto.randomUUID());
|
|
1848
|
+
}
|
|
1849
|
+
/**
|
|
1850
|
+
* The URL to redirect the user agent to after authorization.
|
|
1851
|
+
*/
|
|
1852
|
+
get redirectUrl() {
|
|
1853
|
+
return this._redirectUrl;
|
|
1854
|
+
}
|
|
1855
|
+
/**
|
|
1856
|
+
* Metadata about this OAuth client.
|
|
1857
|
+
*/
|
|
1858
|
+
get clientMetadata() {
|
|
1859
|
+
return this._clientMetadata;
|
|
1860
|
+
}
|
|
1861
|
+
/**
|
|
1862
|
+
* Returns a OAuth2 state parameter.
|
|
1863
|
+
*/
|
|
1864
|
+
async state() {
|
|
1865
|
+
return this.generateState();
|
|
1866
|
+
}
|
|
1867
|
+
/**
|
|
1868
|
+
* Loads information about this OAuth client.
|
|
1869
|
+
*/
|
|
1870
|
+
async clientInformation() {
|
|
1871
|
+
if (this._clientInfo) {
|
|
1872
|
+
return this._clientInfo;
|
|
1873
|
+
}
|
|
1874
|
+
const stored = await this.storage.get("client_info");
|
|
1875
|
+
if (stored) {
|
|
1876
|
+
try {
|
|
1877
|
+
return JSON.parse(stored);
|
|
1878
|
+
} catch {
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
return void 0;
|
|
1882
|
+
}
|
|
1883
|
+
/**
|
|
1884
|
+
* Saves dynamically registered client information.
|
|
1885
|
+
*/
|
|
1886
|
+
async saveClientInformation(clientInformation) {
|
|
1887
|
+
this._clientInfo = clientInformation;
|
|
1888
|
+
await this.storage.set("client_info", JSON.stringify(clientInformation));
|
|
1889
|
+
}
|
|
1890
|
+
/**
|
|
1891
|
+
* Loads existing OAuth tokens.
|
|
1892
|
+
*/
|
|
1893
|
+
async tokens() {
|
|
1894
|
+
const stored = await this.storage.get("tokens");
|
|
1895
|
+
if (stored) {
|
|
1896
|
+
try {
|
|
1897
|
+
return JSON.parse(stored);
|
|
1898
|
+
} catch {
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
return void 0;
|
|
1902
|
+
}
|
|
1903
|
+
/**
|
|
1904
|
+
* Stores new OAuth tokens after successful authorization.
|
|
1905
|
+
*/
|
|
1906
|
+
async saveTokens(tokens) {
|
|
1907
|
+
await this.storage.set("tokens", JSON.stringify(tokens));
|
|
1908
|
+
}
|
|
1909
|
+
/**
|
|
1910
|
+
* Invoked to redirect the user agent to the authorization URL.
|
|
1911
|
+
*/
|
|
1912
|
+
async redirectToAuthorization(authorizationUrl) {
|
|
1913
|
+
if (this.onRedirect) {
|
|
1914
|
+
await this.onRedirect(authorizationUrl);
|
|
1915
|
+
} else {
|
|
1916
|
+
console.info(`Authorization required. Please visit: ${authorizationUrl.toString()}`);
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
/**
|
|
1920
|
+
* Saves a PKCE code verifier before redirecting to authorization.
|
|
1921
|
+
*/
|
|
1922
|
+
async saveCodeVerifier(codeVerifier) {
|
|
1923
|
+
await this.storage.set("code_verifier", codeVerifier);
|
|
1924
|
+
}
|
|
1925
|
+
/**
|
|
1926
|
+
* Loads the PKCE code verifier for validating authorization result.
|
|
1927
|
+
*/
|
|
1928
|
+
async codeVerifier() {
|
|
1929
|
+
const verifier = await this.storage.get("code_verifier");
|
|
1930
|
+
if (!verifier) {
|
|
1931
|
+
throw new Error("No code verifier found. Authorization flow may not have started properly.");
|
|
1932
|
+
}
|
|
1933
|
+
return verifier;
|
|
1934
|
+
}
|
|
1935
|
+
/**
|
|
1936
|
+
* Optional: Custom client authentication for token requests.
|
|
1937
|
+
* Uses default behavior if not implemented.
|
|
1938
|
+
*/
|
|
1939
|
+
async addClientAuthentication(_headers, _params, _url, _metadata) {
|
|
1940
|
+
}
|
|
1941
|
+
/**
|
|
1942
|
+
* Invalidate credentials when server indicates they're no longer valid.
|
|
1943
|
+
*/
|
|
1944
|
+
async invalidateCredentials(scope) {
|
|
1945
|
+
switch (scope) {
|
|
1946
|
+
case "all":
|
|
1947
|
+
await this.storage.delete("tokens");
|
|
1948
|
+
await this.storage.delete("client_info");
|
|
1949
|
+
await this.storage.delete("code_verifier");
|
|
1950
|
+
this._clientInfo = void 0;
|
|
1951
|
+
break;
|
|
1952
|
+
case "client":
|
|
1953
|
+
await this.storage.delete("client_info");
|
|
1954
|
+
this._clientInfo = void 0;
|
|
1955
|
+
break;
|
|
1956
|
+
case "tokens":
|
|
1957
|
+
await this.storage.delete("tokens");
|
|
1958
|
+
break;
|
|
1959
|
+
case "verifier":
|
|
1960
|
+
await this.storage.delete("code_verifier");
|
|
1961
|
+
break;
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
/**
|
|
1965
|
+
* Clear all stored OAuth data.
|
|
1966
|
+
* Useful for logging out or resetting state.
|
|
1967
|
+
*/
|
|
1968
|
+
async clear() {
|
|
1969
|
+
await this.invalidateCredentials("all");
|
|
1970
|
+
}
|
|
1971
|
+
/**
|
|
1972
|
+
* Check if the provider has valid (non-expired) tokens.
|
|
1973
|
+
*/
|
|
1974
|
+
async hasValidTokens() {
|
|
1975
|
+
const currentTokens = await this.tokens();
|
|
1976
|
+
if (!currentTokens) return false;
|
|
1977
|
+
if (!currentTokens.access_token) return false;
|
|
1978
|
+
return true;
|
|
1979
|
+
}
|
|
1980
|
+
};
|
|
1981
|
+
function createSimpleTokenProvider(accessToken, options) {
|
|
1982
|
+
const tokens = {
|
|
1983
|
+
access_token: accessToken,
|
|
1984
|
+
token_type: options.tokenType ?? "Bearer",
|
|
1985
|
+
refresh_token: options.refreshToken,
|
|
1986
|
+
expires_in: options.expiresIn,
|
|
1987
|
+
scope: options.scope
|
|
1988
|
+
};
|
|
1989
|
+
const storage = new InMemoryOAuthStorage();
|
|
1990
|
+
storage.set("tokens", JSON.stringify(tokens));
|
|
1991
|
+
if (options.clientInformation) {
|
|
1992
|
+
storage.set("client_info", JSON.stringify(options.clientInformation));
|
|
1993
|
+
}
|
|
1994
|
+
return new MCPOAuthClientProvider({
|
|
1995
|
+
redirectUrl: options.redirectUrl,
|
|
1996
|
+
clientMetadata: options.clientMetadata,
|
|
1997
|
+
clientInformation: options.clientInformation,
|
|
1998
|
+
storage
|
|
1999
|
+
});
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
// ../../node_modules/.pnpm/hono@4.11.3/node_modules/hono/dist/utils/stream.js
|
|
1584
2003
|
var StreamingApi = class {
|
|
1585
2004
|
writer;
|
|
1586
2005
|
encoder;
|
|
1587
2006
|
writable;
|
|
1588
2007
|
abortSubscribers = [];
|
|
1589
2008
|
responseReadable;
|
|
2009
|
+
/**
|
|
2010
|
+
* Whether the stream has been aborted.
|
|
2011
|
+
*/
|
|
1590
2012
|
aborted = false;
|
|
2013
|
+
/**
|
|
2014
|
+
* Whether the stream has been closed normally.
|
|
2015
|
+
*/
|
|
1591
2016
|
closed = false;
|
|
1592
2017
|
constructor(writable, _readable) {
|
|
1593
2018
|
this.writable = writable;
|
|
@@ -1639,6 +2064,10 @@ var StreamingApi = class {
|
|
|
1639
2064
|
onAbort(listener) {
|
|
1640
2065
|
this.abortSubscribers.push(listener);
|
|
1641
2066
|
}
|
|
2067
|
+
/**
|
|
2068
|
+
* Abort the stream.
|
|
2069
|
+
* You can call this method when stream is aborted by external event.
|
|
2070
|
+
*/
|
|
1642
2071
|
abort() {
|
|
1643
2072
|
if (!this.aborted) {
|
|
1644
2073
|
this.aborted = true;
|
|
@@ -1647,7 +2076,7 @@ var StreamingApi = class {
|
|
|
1647
2076
|
}
|
|
1648
2077
|
};
|
|
1649
2078
|
|
|
1650
|
-
// ../../node_modules/.pnpm/hono@4.
|
|
2079
|
+
// ../../node_modules/.pnpm/hono@4.11.3/node_modules/hono/dist/helper/streaming/utils.js
|
|
1651
2080
|
var isOldBunVersion = () => {
|
|
1652
2081
|
const version = typeof Bun !== "undefined" ? Bun.version : void 0;
|
|
1653
2082
|
if (version === void 0) {
|
|
@@ -1658,7 +2087,7 @@ var isOldBunVersion = () => {
|
|
|
1658
2087
|
return result;
|
|
1659
2088
|
};
|
|
1660
2089
|
|
|
1661
|
-
// ../../node_modules/.pnpm/hono@4.
|
|
2090
|
+
// ../../node_modules/.pnpm/hono@4.11.3/node_modules/hono/dist/utils/html.js
|
|
1662
2091
|
var HtmlEscapedCallbackPhase = {
|
|
1663
2092
|
Stringify: 1};
|
|
1664
2093
|
var resolveCallback = async (str, phase, preserveCallbacks, context, buffer) => {
|
|
@@ -1689,7 +2118,7 @@ var resolveCallback = async (str, phase, preserveCallbacks, context, buffer) =>
|
|
|
1689
2118
|
}
|
|
1690
2119
|
};
|
|
1691
2120
|
|
|
1692
|
-
// ../../node_modules/.pnpm/hono@4.
|
|
2121
|
+
// ../../node_modules/.pnpm/hono@4.11.3/node_modules/hono/dist/helper/streaming/sse.js
|
|
1693
2122
|
var SSEStreamingApi = class extends StreamingApi {
|
|
1694
2123
|
constructor(writable, readable) {
|
|
1695
2124
|
super(writable, readable);
|
|
@@ -2137,7 +2566,16 @@ var MCPServer = class extends MCPServerBase {
|
|
|
2137
2566
|
if (opts.prompts) {
|
|
2138
2567
|
capabilities.prompts = { listChanged: true };
|
|
2139
2568
|
}
|
|
2140
|
-
this.server = new Server(
|
|
2569
|
+
this.server = new Server(
|
|
2570
|
+
{
|
|
2571
|
+
name: this.name,
|
|
2572
|
+
version: this.version
|
|
2573
|
+
},
|
|
2574
|
+
{
|
|
2575
|
+
capabilities,
|
|
2576
|
+
...this.instructions ? { instructions: this.instructions } : {}
|
|
2577
|
+
}
|
|
2578
|
+
);
|
|
2141
2579
|
this.logger.info(
|
|
2142
2580
|
`Initialized MCPServer '${this.name}' v${this.version} (ID: ${this.id}) with tools: ${Object.keys(this.convertedTools).join(", ")} and resources. Capabilities: ${JSON.stringify(capabilities)}`
|
|
2143
2581
|
);
|
|
@@ -2162,8 +2600,8 @@ var MCPServer = class extends MCPServerBase {
|
|
|
2162
2600
|
}
|
|
2163
2601
|
});
|
|
2164
2602
|
this.elicitation = {
|
|
2165
|
-
sendRequest: async (request) => {
|
|
2166
|
-
return this.handleElicitationRequest(request);
|
|
2603
|
+
sendRequest: async (request, options) => {
|
|
2604
|
+
return this.handleElicitationRequest(request, void 0, options);
|
|
2167
2605
|
}
|
|
2168
2606
|
};
|
|
2169
2607
|
}
|
|
@@ -2173,15 +2611,51 @@ var MCPServer = class extends MCPServerBase {
|
|
|
2173
2611
|
*
|
|
2174
2612
|
* @param request - The elicitation request containing message and schema
|
|
2175
2613
|
* @param serverInstance - Optional server instance to use; defaults to main server for backward compatibility
|
|
2614
|
+
* @param options - Optional request options (timeout, signal, etc.)
|
|
2176
2615
|
* @returns Promise that resolves to the client's response
|
|
2177
2616
|
*/
|
|
2178
|
-
async handleElicitationRequest(request, serverInstance) {
|
|
2617
|
+
async handleElicitationRequest(request, serverInstance, options) {
|
|
2179
2618
|
this.logger.debug(`Sending elicitation request: ${request.message}`);
|
|
2180
2619
|
const server = serverInstance || this.server;
|
|
2181
|
-
const response = await server.elicitInput(request);
|
|
2620
|
+
const response = await server.elicitInput(request, options);
|
|
2182
2621
|
this.logger.debug(`Received elicitation response: ${JSON.stringify(response)}`);
|
|
2183
2622
|
return response;
|
|
2184
2623
|
}
|
|
2624
|
+
/**
|
|
2625
|
+
* Reads and parses the JSON body from an HTTP request.
|
|
2626
|
+
* If the request body was already parsed by middleware (e.g., express.json()),
|
|
2627
|
+
* it uses the pre-parsed body from req.body. Otherwise, it reads from the stream.
|
|
2628
|
+
*
|
|
2629
|
+
* This allows the MCP server to work with Express apps that use express.json()
|
|
2630
|
+
* globally without requiring special route exclusions.
|
|
2631
|
+
*
|
|
2632
|
+
* @param req - The incoming HTTP request
|
|
2633
|
+
* @param options - Optional configuration
|
|
2634
|
+
* @param options.preParsedOnly - If true, only return pre-parsed body from middleware,
|
|
2635
|
+
* returning undefined if not available. This allows the caller to fall back to
|
|
2636
|
+
* their own body reading logic (e.g., SDK's getRawBody with size limits).
|
|
2637
|
+
*/
|
|
2638
|
+
async readJsonBody(req, options) {
|
|
2639
|
+
const reqWithBody = req;
|
|
2640
|
+
if (reqWithBody.body !== void 0) {
|
|
2641
|
+
return reqWithBody.body;
|
|
2642
|
+
}
|
|
2643
|
+
if (options?.preParsedOnly) {
|
|
2644
|
+
return void 0;
|
|
2645
|
+
}
|
|
2646
|
+
return new Promise((resolve, reject) => {
|
|
2647
|
+
let data = "";
|
|
2648
|
+
req.on("data", (chunk) => data += chunk);
|
|
2649
|
+
req.on("end", () => {
|
|
2650
|
+
try {
|
|
2651
|
+
resolve(JSON.parse(data));
|
|
2652
|
+
} catch (e) {
|
|
2653
|
+
reject(e);
|
|
2654
|
+
}
|
|
2655
|
+
});
|
|
2656
|
+
req.on("error", reject);
|
|
2657
|
+
});
|
|
2658
|
+
}
|
|
2185
2659
|
/**
|
|
2186
2660
|
* Creates a new Server instance configured with all handlers for HTTP sessions.
|
|
2187
2661
|
* Each HTTP client connection gets its own Server instance to avoid routing conflicts.
|
|
@@ -2198,7 +2672,16 @@ var MCPServer = class extends MCPServerBase {
|
|
|
2198
2672
|
if (this.promptOptions) {
|
|
2199
2673
|
capabilities.prompts = { listChanged: true };
|
|
2200
2674
|
}
|
|
2201
|
-
const serverInstance = new Server(
|
|
2675
|
+
const serverInstance = new Server(
|
|
2676
|
+
{
|
|
2677
|
+
name: this.name,
|
|
2678
|
+
version: this.version
|
|
2679
|
+
},
|
|
2680
|
+
{
|
|
2681
|
+
capabilities,
|
|
2682
|
+
...this.instructions ? { instructions: this.instructions } : {}
|
|
2683
|
+
}
|
|
2684
|
+
);
|
|
2202
2685
|
this.registerHandlersOnServer(serverInstance);
|
|
2203
2686
|
return serverInstance;
|
|
2204
2687
|
}
|
|
@@ -2219,6 +2702,12 @@ var MCPServer = class extends MCPServerBase {
|
|
|
2219
2702
|
if (tool.outputSchema) {
|
|
2220
2703
|
toolSpec.outputSchema = tool.outputSchema.jsonSchema;
|
|
2221
2704
|
}
|
|
2705
|
+
if (tool.mcp?.annotations) {
|
|
2706
|
+
toolSpec.annotations = tool.mcp.annotations;
|
|
2707
|
+
}
|
|
2708
|
+
if (tool.mcp?._meta) {
|
|
2709
|
+
toolSpec._meta = tool.mcp._meta;
|
|
2710
|
+
}
|
|
2222
2711
|
return toolSpec;
|
|
2223
2712
|
})
|
|
2224
2713
|
};
|
|
@@ -2267,8 +2756,8 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
|
|
|
2267
2756
|
};
|
|
2268
2757
|
}
|
|
2269
2758
|
const sessionElicitation = {
|
|
2270
|
-
sendRequest: async (request2) => {
|
|
2271
|
-
return this.handleElicitationRequest(request2, serverInstance);
|
|
2759
|
+
sendRequest: async (request2, options) => {
|
|
2760
|
+
return this.handleElicitationRequest(request2, serverInstance, options);
|
|
2272
2761
|
}
|
|
2273
2762
|
};
|
|
2274
2763
|
const mcpOptions = {
|
|
@@ -2568,7 +3057,16 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
|
|
|
2568
3057
|
`Executing agent tool '${agentToolName}' for agent '${agent.name}' with message: "${inputData.message}"`
|
|
2569
3058
|
);
|
|
2570
3059
|
try {
|
|
2571
|
-
const
|
|
3060
|
+
const proxiedContext = context?.requestContext || new RequestContext();
|
|
3061
|
+
if (context?.mcp?.extra) {
|
|
3062
|
+
Object.entries(context.mcp.extra).forEach(([key, value]) => {
|
|
3063
|
+
proxiedContext.set(key, value);
|
|
3064
|
+
});
|
|
3065
|
+
}
|
|
3066
|
+
const response = await agent.generate(inputData.message, {
|
|
3067
|
+
...context ?? {},
|
|
3068
|
+
requestContext: proxiedContext
|
|
3069
|
+
});
|
|
2572
3070
|
return response;
|
|
2573
3071
|
} catch (error) {
|
|
2574
3072
|
this.logger.error(`Error executing agent tool '${agentToolName}' for agent '${agent.name}':`, error);
|
|
@@ -2632,10 +3130,16 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
|
|
|
2632
3130
|
inputData
|
|
2633
3131
|
);
|
|
2634
3132
|
try {
|
|
2635
|
-
const
|
|
3133
|
+
const proxiedContext = context?.requestContext || new RequestContext();
|
|
3134
|
+
if (context?.mcp?.extra) {
|
|
3135
|
+
Object.entries(context.mcp.extra).forEach(([key, value]) => {
|
|
3136
|
+
proxiedContext.set(key, value);
|
|
3137
|
+
});
|
|
3138
|
+
}
|
|
3139
|
+
const run2 = await workflow.createRun({ runId: proxiedContext?.get("runId") });
|
|
2636
3140
|
const response = await run2.start({
|
|
2637
3141
|
inputData,
|
|
2638
|
-
requestContext:
|
|
3142
|
+
requestContext: proxiedContext,
|
|
2639
3143
|
tracingContext: context?.tracingContext
|
|
2640
3144
|
});
|
|
2641
3145
|
return response;
|
|
@@ -2791,7 +3295,7 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
|
|
|
2791
3295
|
*
|
|
2792
3296
|
* @example
|
|
2793
3297
|
* ```typescript
|
|
2794
|
-
* import http from 'http';
|
|
3298
|
+
* import http from 'node:http';
|
|
2795
3299
|
*
|
|
2796
3300
|
* const httpServer = http.createServer(async (req, res) => {
|
|
2797
3301
|
* await server.startSSE({
|
|
@@ -2822,7 +3326,8 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
|
|
|
2822
3326
|
res.end("SSE connection not established");
|
|
2823
3327
|
return;
|
|
2824
3328
|
}
|
|
2825
|
-
await this.
|
|
3329
|
+
const parsedBody = await this.readJsonBody(req, { preParsedOnly: true });
|
|
3330
|
+
await this.sseTransport.handlePostMessage(req, res, parsedBody);
|
|
2826
3331
|
} else {
|
|
2827
3332
|
this.logger.debug("Unknown path:", { path: url.pathname });
|
|
2828
3333
|
res.writeHead(404);
|
|
@@ -2949,8 +3454,8 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
|
|
|
2949
3454
|
*
|
|
2950
3455
|
* @example
|
|
2951
3456
|
* ```typescript
|
|
2952
|
-
* import http from 'http';
|
|
2953
|
-
* import { randomUUID } from 'crypto';
|
|
3457
|
+
* import http from 'node:http';
|
|
3458
|
+
* import { randomUUID } from 'node:crypto';
|
|
2954
3459
|
*
|
|
2955
3460
|
* const httpServer = http.createServer(async (req, res) => {
|
|
2956
3461
|
* await server.startHTTP({
|
|
@@ -2994,7 +3499,7 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
|
|
|
2994
3499
|
httpPath,
|
|
2995
3500
|
req,
|
|
2996
3501
|
res,
|
|
2997
|
-
options
|
|
3502
|
+
options
|
|
2998
3503
|
}) {
|
|
2999
3504
|
this.logger.debug(`startHTTP: Received ${req.method} request to ${url.pathname}`);
|
|
3000
3505
|
if (url.pathname !== httpPath) {
|
|
@@ -3003,11 +3508,18 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
|
|
|
3003
3508
|
res.end();
|
|
3004
3509
|
return;
|
|
3005
3510
|
}
|
|
3006
|
-
|
|
3007
|
-
|
|
3511
|
+
const isStatelessMode = options?.serverless || options && "sessionIdGenerator" in options && options.sessionIdGenerator === void 0;
|
|
3512
|
+
if (isStatelessMode) {
|
|
3513
|
+
this.logger.debug("startHTTP: Running in stateless mode (serverless or sessionIdGenerator: undefined)");
|
|
3008
3514
|
await this.handleServerlessRequest(req, res);
|
|
3009
3515
|
return;
|
|
3010
3516
|
}
|
|
3517
|
+
const mergedOptions = {
|
|
3518
|
+
sessionIdGenerator: () => randomUUID(),
|
|
3519
|
+
// default: enabled
|
|
3520
|
+
...options
|
|
3521
|
+
// user-provided overrides default
|
|
3522
|
+
};
|
|
3011
3523
|
const sessionId = req.headers["mcp-session-id"];
|
|
3012
3524
|
let transport;
|
|
3013
3525
|
this.logger.debug(
|
|
@@ -3022,40 +3534,18 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
|
|
|
3022
3534
|
`startHTTP: Handling GET request for existing session ${sessionId}. Calling transport.handleRequest.`
|
|
3023
3535
|
);
|
|
3024
3536
|
}
|
|
3025
|
-
const body = req.method === "POST" ? await
|
|
3026
|
-
let data = "";
|
|
3027
|
-
req.on("data", (chunk) => data += chunk);
|
|
3028
|
-
req.on("end", () => {
|
|
3029
|
-
try {
|
|
3030
|
-
resolve(JSON.parse(data));
|
|
3031
|
-
} catch (e) {
|
|
3032
|
-
reject(e);
|
|
3033
|
-
}
|
|
3034
|
-
});
|
|
3035
|
-
req.on("error", reject);
|
|
3036
|
-
}) : void 0;
|
|
3537
|
+
const body = req.method === "POST" ? await this.readJsonBody(req) : void 0;
|
|
3037
3538
|
await transport.handleRequest(req, res, body);
|
|
3038
3539
|
} else {
|
|
3039
3540
|
this.logger.debug(`startHTTP: No existing Streamable HTTP session ID found. ${req.method}`);
|
|
3040
3541
|
if (req.method === "POST") {
|
|
3041
|
-
const body = await
|
|
3042
|
-
let data = "";
|
|
3043
|
-
req.on("data", (chunk) => data += chunk);
|
|
3044
|
-
req.on("end", () => {
|
|
3045
|
-
try {
|
|
3046
|
-
resolve(JSON.parse(data));
|
|
3047
|
-
} catch (e) {
|
|
3048
|
-
reject(e);
|
|
3049
|
-
}
|
|
3050
|
-
});
|
|
3051
|
-
req.on("error", reject);
|
|
3052
|
-
});
|
|
3542
|
+
const body = await this.readJsonBody(req);
|
|
3053
3543
|
const { isInitializeRequest } = await import('@modelcontextprotocol/sdk/types.js');
|
|
3054
3544
|
if (isInitializeRequest(body)) {
|
|
3055
3545
|
this.logger.debug("startHTTP: Received Streamable HTTP initialize request, creating new transport.");
|
|
3056
3546
|
transport = new StreamableHTTPServerTransport({
|
|
3057
|
-
...
|
|
3058
|
-
sessionIdGenerator:
|
|
3547
|
+
...mergedOptions,
|
|
3548
|
+
sessionIdGenerator: mergedOptions.sessionIdGenerator,
|
|
3059
3549
|
onsessioninitialized: (id) => {
|
|
3060
3550
|
this.streamableHTTPTransports.set(id, transport);
|
|
3061
3551
|
}
|
|
@@ -3159,18 +3649,7 @@ Provided arguments: ${JSON.stringify(request.params.arguments, null, 2)}`
|
|
|
3159
3649
|
async handleServerlessRequest(req, res) {
|
|
3160
3650
|
try {
|
|
3161
3651
|
this.logger.debug(`handleServerlessRequest: Received ${req.method} request`);
|
|
3162
|
-
const body = req.method === "POST" ? await
|
|
3163
|
-
let data = "";
|
|
3164
|
-
req.on("data", (chunk) => data += chunk);
|
|
3165
|
-
req.on("end", () => {
|
|
3166
|
-
try {
|
|
3167
|
-
resolve(JSON.parse(data));
|
|
3168
|
-
} catch (e) {
|
|
3169
|
-
reject(new Error(`Invalid JSON in request body: ${e instanceof Error ? e.message : String(e)}`));
|
|
3170
|
-
}
|
|
3171
|
-
});
|
|
3172
|
-
req.on("error", reject);
|
|
3173
|
-
}) : void 0;
|
|
3652
|
+
const body = req.method === "POST" ? await this.readJsonBody(req) : void 0;
|
|
3174
3653
|
this.logger.debug(`handleServerlessRequest: Processing ${req.method} request`, {
|
|
3175
3654
|
method: body?.method,
|
|
3176
3655
|
id: body?.id
|
|
@@ -3600,7 +4079,210 @@ Provided arguments: ${JSON.stringify(args, null, 2)}`,
|
|
|
3600
4079
|
}
|
|
3601
4080
|
}
|
|
3602
4081
|
};
|
|
4082
|
+
function escapeHeaderValue(value) {
|
|
4083
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
4084
|
+
}
|
|
4085
|
+
function generateWWWAuthenticateHeader(options = {}) {
|
|
4086
|
+
const params = [];
|
|
4087
|
+
if (options.resourceMetadataUrl) {
|
|
4088
|
+
params.push(`resource_metadata="${escapeHeaderValue(options.resourceMetadataUrl)}"`);
|
|
4089
|
+
}
|
|
4090
|
+
if (options.additionalParams) {
|
|
4091
|
+
for (const [key, value] of Object.entries(options.additionalParams)) {
|
|
4092
|
+
params.push(`${key}="${escapeHeaderValue(value)}"`);
|
|
4093
|
+
}
|
|
4094
|
+
}
|
|
4095
|
+
if (params.length === 0) {
|
|
4096
|
+
return "Bearer";
|
|
4097
|
+
}
|
|
4098
|
+
return `Bearer ${params.join(", ")}`;
|
|
4099
|
+
}
|
|
4100
|
+
function generateProtectedResourceMetadata(config) {
|
|
4101
|
+
return {
|
|
4102
|
+
resource: config.resource,
|
|
4103
|
+
authorization_servers: config.authorizationServers,
|
|
4104
|
+
scopes_supported: config.scopesSupported ?? ["mcp:read", "mcp:write"],
|
|
4105
|
+
bearer_methods_supported: ["header"],
|
|
4106
|
+
...config.resourceName && { resource_name: config.resourceName },
|
|
4107
|
+
...config.resourceDocumentation && {
|
|
4108
|
+
resource_documentation: config.resourceDocumentation
|
|
4109
|
+
}
|
|
4110
|
+
};
|
|
4111
|
+
}
|
|
4112
|
+
function extractBearerToken(authHeader) {
|
|
4113
|
+
if (!authHeader) return void 0;
|
|
4114
|
+
const prefix = "bearer ";
|
|
4115
|
+
if (authHeader.length <= prefix.length) return void 0;
|
|
4116
|
+
if (authHeader.slice(0, prefix.length).toLowerCase() !== prefix) return void 0;
|
|
4117
|
+
const token = authHeader.slice(prefix.length).trim();
|
|
4118
|
+
return token || void 0;
|
|
4119
|
+
}
|
|
4120
|
+
|
|
4121
|
+
// src/server/oauth-middleware.ts
|
|
4122
|
+
function createOAuthMiddleware(options) {
|
|
4123
|
+
const { oauth, mcpPath = "/mcp", logger } = options;
|
|
4124
|
+
const protectedResourceMetadata = generateProtectedResourceMetadata(oauth);
|
|
4125
|
+
const wellKnownPath = "/.well-known/oauth-protected-resource";
|
|
4126
|
+
const resourceMetadataUrl = new URL(wellKnownPath, oauth.resource).toString();
|
|
4127
|
+
return async function oauthMiddleware(req, res, url) {
|
|
4128
|
+
logger?.debug?.(`OAuth middleware: ${req.method} ${url.pathname}`);
|
|
4129
|
+
if (url.pathname === wellKnownPath && req.method === "GET") {
|
|
4130
|
+
logger?.debug?.("OAuth middleware: Serving Protected Resource Metadata");
|
|
4131
|
+
res.writeHead(200, {
|
|
4132
|
+
"Content-Type": "application/json",
|
|
4133
|
+
"Cache-Control": "max-age=3600",
|
|
4134
|
+
"Access-Control-Allow-Origin": "*"
|
|
4135
|
+
});
|
|
4136
|
+
res.end(JSON.stringify(protectedResourceMetadata));
|
|
4137
|
+
return { proceed: false, handled: true };
|
|
4138
|
+
}
|
|
4139
|
+
if (url.pathname === wellKnownPath && req.method === "OPTIONS") {
|
|
4140
|
+
res.writeHead(204, {
|
|
4141
|
+
"Access-Control-Allow-Origin": "*",
|
|
4142
|
+
"Access-Control-Allow-Methods": "GET, OPTIONS",
|
|
4143
|
+
"Access-Control-Allow-Headers": "Content-Type",
|
|
4144
|
+
"Access-Control-Max-Age": "86400"
|
|
4145
|
+
});
|
|
4146
|
+
res.end();
|
|
4147
|
+
return { proceed: false, handled: true };
|
|
4148
|
+
}
|
|
4149
|
+
if (!url.pathname.startsWith(mcpPath)) {
|
|
4150
|
+
return { proceed: true, handled: false };
|
|
4151
|
+
}
|
|
4152
|
+
const authHeader = req.headers["authorization"];
|
|
4153
|
+
const token = extractBearerToken(authHeader);
|
|
4154
|
+
if (!token) {
|
|
4155
|
+
logger?.debug?.("OAuth middleware: No bearer token provided");
|
|
4156
|
+
res.writeHead(401, {
|
|
4157
|
+
"Content-Type": "application/json",
|
|
4158
|
+
"WWW-Authenticate": generateWWWAuthenticateHeader({ resourceMetadataUrl })
|
|
4159
|
+
});
|
|
4160
|
+
res.end(
|
|
4161
|
+
JSON.stringify({
|
|
4162
|
+
error: "unauthorized",
|
|
4163
|
+
error_description: "Bearer token required"
|
|
4164
|
+
})
|
|
4165
|
+
);
|
|
4166
|
+
return { proceed: false, handled: true };
|
|
4167
|
+
}
|
|
4168
|
+
if (oauth.validateToken) {
|
|
4169
|
+
logger?.debug?.("OAuth middleware: Validating token");
|
|
4170
|
+
const validationResult = await oauth.validateToken(token, oauth.resource);
|
|
4171
|
+
if (!validationResult.valid) {
|
|
4172
|
+
logger?.debug?.(`OAuth middleware: Token validation failed: ${validationResult.error}`);
|
|
4173
|
+
res.writeHead(401, {
|
|
4174
|
+
"Content-Type": "application/json",
|
|
4175
|
+
"WWW-Authenticate": generateWWWAuthenticateHeader({
|
|
4176
|
+
resourceMetadataUrl,
|
|
4177
|
+
additionalParams: {
|
|
4178
|
+
error: validationResult.error || "invalid_token",
|
|
4179
|
+
...validationResult.errorDescription && {
|
|
4180
|
+
error_description: validationResult.errorDescription
|
|
4181
|
+
}
|
|
4182
|
+
}
|
|
4183
|
+
})
|
|
4184
|
+
});
|
|
4185
|
+
res.end(
|
|
4186
|
+
JSON.stringify({
|
|
4187
|
+
error: validationResult.error || "invalid_token",
|
|
4188
|
+
error_description: validationResult.errorDescription || "Token validation failed"
|
|
4189
|
+
})
|
|
4190
|
+
);
|
|
4191
|
+
return { proceed: false, handled: true, tokenValidation: validationResult };
|
|
4192
|
+
}
|
|
4193
|
+
logger?.debug?.("OAuth middleware: Token validated successfully");
|
|
4194
|
+
return { proceed: true, handled: false, tokenValidation: validationResult };
|
|
4195
|
+
}
|
|
4196
|
+
logger?.debug?.("OAuth middleware: No token validation configured, accepting token");
|
|
4197
|
+
return {
|
|
4198
|
+
proceed: true,
|
|
4199
|
+
handled: false,
|
|
4200
|
+
tokenValidation: { valid: true }
|
|
4201
|
+
};
|
|
4202
|
+
};
|
|
4203
|
+
}
|
|
4204
|
+
function createStaticTokenValidator(validTokens) {
|
|
4205
|
+
const tokenSet = new Set(validTokens);
|
|
4206
|
+
return async (token) => {
|
|
4207
|
+
if (tokenSet.has(token)) {
|
|
4208
|
+
return { valid: true, scopes: ["mcp:read", "mcp:write"] };
|
|
4209
|
+
}
|
|
4210
|
+
return {
|
|
4211
|
+
valid: false,
|
|
4212
|
+
error: "invalid_token",
|
|
4213
|
+
errorDescription: "Token not recognized"
|
|
4214
|
+
};
|
|
4215
|
+
};
|
|
4216
|
+
}
|
|
4217
|
+
function createIntrospectionValidator(introspectionEndpoint, clientCredentials) {
|
|
4218
|
+
return async (token, resource) => {
|
|
4219
|
+
try {
|
|
4220
|
+
const headers = {
|
|
4221
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
4222
|
+
};
|
|
4223
|
+
if (clientCredentials) {
|
|
4224
|
+
if (clientCredentials.clientId.includes(":")) {
|
|
4225
|
+
return {
|
|
4226
|
+
valid: false,
|
|
4227
|
+
error: "invalid_request",
|
|
4228
|
+
errorDescription: "clientId cannot contain a colon character per RFC 7617"
|
|
4229
|
+
};
|
|
4230
|
+
}
|
|
4231
|
+
const credentials = Buffer.from(`${clientCredentials.clientId}:${clientCredentials.clientSecret}`).toString(
|
|
4232
|
+
"base64"
|
|
4233
|
+
);
|
|
4234
|
+
headers["Authorization"] = `Basic ${credentials}`;
|
|
4235
|
+
}
|
|
4236
|
+
const response = await fetch(introspectionEndpoint, {
|
|
4237
|
+
method: "POST",
|
|
4238
|
+
headers,
|
|
4239
|
+
body: new URLSearchParams({
|
|
4240
|
+
token,
|
|
4241
|
+
token_type_hint: "access_token"
|
|
4242
|
+
})
|
|
4243
|
+
});
|
|
4244
|
+
if (!response.ok) {
|
|
4245
|
+
return {
|
|
4246
|
+
valid: false,
|
|
4247
|
+
error: "server_error",
|
|
4248
|
+
errorDescription: `Introspection failed: ${response.status}`
|
|
4249
|
+
};
|
|
4250
|
+
}
|
|
4251
|
+
const data = await response.json();
|
|
4252
|
+
if (!data.active) {
|
|
4253
|
+
return {
|
|
4254
|
+
valid: false,
|
|
4255
|
+
error: "invalid_token",
|
|
4256
|
+
errorDescription: "Token is not active"
|
|
4257
|
+
};
|
|
4258
|
+
}
|
|
4259
|
+
if (data.aud) {
|
|
4260
|
+
const audiences = Array.isArray(data.aud) ? data.aud : [data.aud];
|
|
4261
|
+
if (!audiences.includes(resource)) {
|
|
4262
|
+
return {
|
|
4263
|
+
valid: false,
|
|
4264
|
+
error: "invalid_token",
|
|
4265
|
+
errorDescription: "Token audience does not match this resource"
|
|
4266
|
+
};
|
|
4267
|
+
}
|
|
4268
|
+
}
|
|
4269
|
+
return {
|
|
4270
|
+
valid: true,
|
|
4271
|
+
scopes: data.scope?.trim().split(" ").filter((s) => s !== "") || [],
|
|
4272
|
+
subject: data.sub,
|
|
4273
|
+
expiresAt: data.exp,
|
|
4274
|
+
claims: data
|
|
4275
|
+
};
|
|
4276
|
+
} catch (error) {
|
|
4277
|
+
return {
|
|
4278
|
+
valid: false,
|
|
4279
|
+
error: "server_error",
|
|
4280
|
+
errorDescription: error instanceof Error ? error.message : "Introspection failed"
|
|
4281
|
+
};
|
|
4282
|
+
}
|
|
4283
|
+
};
|
|
4284
|
+
}
|
|
3603
4285
|
|
|
3604
|
-
export { MCPClient, MCPServer };
|
|
4286
|
+
export { InMemoryOAuthStorage, InternalMastraMCPClient, MCPClient, MCPOAuthClientProvider, MCPServer, createIntrospectionValidator, createOAuthMiddleware, createSimpleTokenProvider, createStaticTokenValidator, extractBearerToken, generateProtectedResourceMetadata, generateWWWAuthenticateHeader };
|
|
3605
4287
|
//# sourceMappingURL=index.js.map
|
|
3606
4288
|
//# sourceMappingURL=index.js.map
|