@mastra/mcp 2.9.0-alpha.1 → 2.9.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/client.d.ts +42 -0
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/configuration.d.ts +6 -0
- package/dist/client/configuration.d.ts.map +1 -1
- package/dist/client/types.d.ts +5 -2
- package/dist/client/types.d.ts.map +1 -1
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +32 -32
- package/dist/docs/references/docs-connections-mcp.md +7 -6
- package/dist/docs/references/reference-tools-mcp-client.md +26 -7
- package/dist/index.cjs +108 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +108 -5
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.cjs
CHANGED
|
@@ -15870,6 +15870,16 @@ function shouldDetachPersistentTransportRequest(init) {
|
|
|
15870
15870
|
return false;
|
|
15871
15871
|
}
|
|
15872
15872
|
}
|
|
15873
|
+
/** Whether a request body is a single JSON-RPC response: the client's reply to a server request. */
|
|
15874
|
+
function isJsonRpcResponseBody(body) {
|
|
15875
|
+
if (typeof body !== "string") return false;
|
|
15876
|
+
try {
|
|
15877
|
+
const message = JSON.parse(body);
|
|
15878
|
+
return typeof message === "object" && message !== null && !Array.isArray(message) && !("method" in message) && "id" in message && ("result" in message || "error" in message);
|
|
15879
|
+
} catch {
|
|
15880
|
+
return false;
|
|
15881
|
+
}
|
|
15882
|
+
}
|
|
15873
15883
|
const TASK_WAIT_ERROR_IDS = {
|
|
15874
15884
|
timeout: "MCP_CLIENT_TOOL_TASK_TIMEOUT",
|
|
15875
15885
|
expired: "MCP_CLIENT_TOOL_TASK_EXPIRED",
|
|
@@ -16098,6 +16108,19 @@ var InternalMastraMCPClient = class extends _mastra_core_base.MastraBase {
|
|
|
16098
16108
|
clientConnectionOnClose;
|
|
16099
16109
|
_authState;
|
|
16100
16110
|
operationContextStore = new async_hooks.AsyncLocalStorage();
|
|
16111
|
+
/**
|
|
16112
|
+
* The requestContext of the tool call whose execution opened the connection. Only the
|
|
16113
|
+
* transport's fetch reads it, so the handshake and the streams the connection keeps open are
|
|
16114
|
+
* sent as that call. Messages arriving on those streams are delivered outside it, so server logs
|
|
16115
|
+
* are not labelled with it and requests their handlers send are not sent as that call.
|
|
16116
|
+
*/
|
|
16117
|
+
connectionContextStore = new async_hooks.AsyncLocalStorage();
|
|
16118
|
+
/**
|
|
16119
|
+
* The connection context of the transport a server message arrived on, as it was when that
|
|
16120
|
+
* transport was created, set while the message is delivered. The transport's fetch reads it only for the client's replies to server requests,
|
|
16121
|
+
* which belong to the connection rather than to the handler that produced them.
|
|
16122
|
+
*/
|
|
16123
|
+
replyContextStore = new async_hooks.AsyncLocalStorage();
|
|
16101
16124
|
exitHookUnsubscribe;
|
|
16102
16125
|
sigTermHandler;
|
|
16103
16126
|
sigHupHandler;
|
|
@@ -16290,7 +16313,7 @@ var InternalMastraMCPClient = class extends _mastra_core_base.MastraBase {
|
|
|
16290
16313
|
if (allowedHosts !== void 0) assertHostAllowed(url, allowedHosts);
|
|
16291
16314
|
const policyUserFetch = userFetch && allowedHosts !== void 0 ? wrapFetchWithHostPolicy(userFetch, allowedHosts) : void 0;
|
|
16292
16315
|
const fetch = (requestUrl, init) => {
|
|
16293
|
-
const requestContext = this.
|
|
16316
|
+
const requestContext = this.fetchRequestContext(init);
|
|
16294
16317
|
const executeFetch = () => {
|
|
16295
16318
|
if (allowedHosts === void 0) return userFetch ? userFetch(requestUrl, init, requestContext) : globalThis.fetch(requestUrl, init);
|
|
16296
16319
|
if (policyUserFetch) return policyUserFetch(requestUrl, init, requestContext);
|
|
@@ -16305,6 +16328,7 @@ var InternalMastraMCPClient = class extends _mastra_core_base.MastraBase {
|
|
|
16305
16328
|
fetch
|
|
16306
16329
|
});
|
|
16307
16330
|
observeEventStreamRequests(transport, this.noteEventStreamSent);
|
|
16331
|
+
this.deliverOutsideConnectionContext(transport);
|
|
16308
16332
|
this.connectingTransport = transport;
|
|
16309
16333
|
try {
|
|
16310
16334
|
this.assertSameLifecycle(generation);
|
|
@@ -16326,6 +16350,59 @@ var InternalMastraMCPClient = class extends _mastra_core_base.MastraBase {
|
|
|
16326
16350
|
}
|
|
16327
16351
|
}
|
|
16328
16352
|
/**
|
|
16353
|
+
* Connects as a tool call: the handshake and the streams the connection keeps open reach the
|
|
16354
|
+
* transport's fetch with the call's requestContext, through the connection context. Any enclosing
|
|
16355
|
+
* operation scope is exited first. It would take priority in fetch, and the new streams and stdio
|
|
16356
|
+
* child would deliver every later server message in it.
|
|
16357
|
+
*/
|
|
16358
|
+
connectAsCall(requestContext, connect) {
|
|
16359
|
+
return this.operationContextStore.exit(() => this.connectionContextStore.run(requestContext, connect));
|
|
16360
|
+
}
|
|
16361
|
+
/**
|
|
16362
|
+
* The requestContext the transport's fetch passes on for an outgoing request. An operation scope
|
|
16363
|
+
* owns its requests, even when its requestContext is null. Outside one, the handshake and the
|
|
16364
|
+
* streams the connection keeps open use the connection context, and a reply to a server request
|
|
16365
|
+
* uses the context of the connection that delivered the request.
|
|
16366
|
+
*/
|
|
16367
|
+
fetchRequestContext(init) {
|
|
16368
|
+
const operationContext = this.operationContextStore.getStore();
|
|
16369
|
+
if (operationContext !== void 0) return operationContext;
|
|
16370
|
+
const connectionContext = this.connectionContextStore.getStore();
|
|
16371
|
+
if (connectionContext !== void 0) return connectionContext;
|
|
16372
|
+
const replyContext = this.replyContextStore.getStore();
|
|
16373
|
+
if (replyContext !== void 0 && isJsonRpcResponseBody(init?.body)) return replyContext;
|
|
16374
|
+
return null;
|
|
16375
|
+
}
|
|
16376
|
+
/**
|
|
16377
|
+
* Delivers every message the server sends outside the connection context, from the handshake on.
|
|
16378
|
+
* A server log, notification or request arriving on a stream the opening call started belongs to
|
|
16379
|
+
* no call, so a request its handler sends must not be sent as that call. The client's reply to a
|
|
16380
|
+
* server request still is, through `replyContextStore`.
|
|
16381
|
+
*
|
|
16382
|
+
* The reply context is the connection context this transport was created in, captured once. Read
|
|
16383
|
+
* at delivery time, it would carry over to a connection that a handler of an older connection's
|
|
16384
|
+
* message opens, such as a `reconnectServer()` from a server log callback.
|
|
16385
|
+
*
|
|
16386
|
+
* The SDK assigns `onmessage` during `connect()` and swaps it while probing the server, so every
|
|
16387
|
+
* assignment is wrapped. A handler that wraps an already wrapped one (`interceptTaskResults`, or
|
|
16388
|
+
* the probe restoring its saved handler) runs with the same reply context.
|
|
16389
|
+
*/
|
|
16390
|
+
deliverOutsideConnectionContext(transport) {
|
|
16391
|
+
const replyContext = this.connectionContextStore.getStore() ?? null;
|
|
16392
|
+
let deliver;
|
|
16393
|
+
Object.defineProperty(transport, "onmessage", {
|
|
16394
|
+
configurable: true,
|
|
16395
|
+
enumerable: true,
|
|
16396
|
+
get: () => deliver,
|
|
16397
|
+
set: (handler) => {
|
|
16398
|
+
deliver = handler && ((message, extra) => {
|
|
16399
|
+
const dispatch = () => handler.call(transport, message, extra);
|
|
16400
|
+
this.connectionContextStore.exit(() => this.replyContextStore.run(replyContext, dispatch));
|
|
16401
|
+
});
|
|
16402
|
+
}
|
|
16403
|
+
});
|
|
16404
|
+
}
|
|
16405
|
+
/**
|
|
16329
16406
|
* Detaches whatever transport is still attached to the underlying SDK Client.
|
|
16330
16407
|
*
|
|
16331
16408
|
* The SDK assigns its internal `_transport` before `transport.start()` and never
|
|
@@ -16663,7 +16740,8 @@ var InternalMastraMCPClient = class extends _mastra_core_base.MastraBase {
|
|
|
16663
16740
|
this.serverImplementation = void 0;
|
|
16664
16741
|
}
|
|
16665
16742
|
this.assertSameLifecycle(generation);
|
|
16666
|
-
|
|
16743
|
+
const callContext = this.operationContextStore.getStore();
|
|
16744
|
+
await (callContext === void 0 ? this.connect(generation) : this.connectAsCall(callContext, () => this.connect(generation)));
|
|
16667
16745
|
this.log("debug", "Successfully reconnected to MCP server");
|
|
16668
16746
|
})();
|
|
16669
16747
|
this.reconnectPromise = reconnectPromise;
|
|
@@ -17156,8 +17234,8 @@ var InternalMastraMCPClient = class extends _mastra_core_base.MastraBase {
|
|
|
17156
17234
|
},
|
|
17157
17235
|
...tool.outputSchema ? { toModelOutput: createStructuredToolToModelOutput() } : {},
|
|
17158
17236
|
execute: async (input, context) => {
|
|
17159
|
-
if (serverMeta.connectFirst) await this.connect();
|
|
17160
17237
|
const operationContext = context?.requestContext ?? null;
|
|
17238
|
+
if (serverMeta.connectFirst) await this.connectAsCall(operationContext, () => this.connect());
|
|
17161
17239
|
return this.operationContextStore.run(operationContext, async () => {
|
|
17162
17240
|
const suspendHost = context?.agent ?? context?.workflow;
|
|
17163
17241
|
const toolCallSpan = context?.tracingContext?.currentSpan;
|
|
@@ -19096,6 +19174,7 @@ onListChanged: async (serverName, handler) => {
|
|
|
19096
19174
|
errorId: "MCP_CLIENT_GET_TOOLS_FAILED",
|
|
19097
19175
|
logMessage: "Failed to list tools from server:"
|
|
19098
19176
|
}, options);
|
|
19177
|
+
const keySources = /* @__PURE__ */ new Map();
|
|
19099
19178
|
for (const { serverName, value, error, duration } of settled) {
|
|
19100
19179
|
durations[serverName] = duration;
|
|
19101
19180
|
if (error !== void 0) {
|
|
@@ -19103,7 +19182,7 @@ onListChanged: async (serverName, handler) => {
|
|
|
19103
19182
|
errorDetails[serverName] = error;
|
|
19104
19183
|
continue;
|
|
19105
19184
|
}
|
|
19106
|
-
for (const [toolName, toolConfig] of Object.entries(value)) connectedTools
|
|
19185
|
+
for (const [toolName, toolConfig] of Object.entries(value)) this.setNamespacedTool(connectedTools, keySources, serverName, toolName, toolConfig);
|
|
19107
19186
|
}
|
|
19108
19187
|
const result = {
|
|
19109
19188
|
tools: connectedTools,
|
|
@@ -19275,16 +19354,18 @@ onListChanged: async (serverName, handler) => {
|
|
|
19275
19354
|
async toolsFromDefinitions({ definitions: catalog }) {
|
|
19276
19355
|
const configuredServers = new Set(Object.keys(this.serverConfigs));
|
|
19277
19356
|
const tools = {};
|
|
19357
|
+
const keySources = /* @__PURE__ */ new Map();
|
|
19278
19358
|
for (const [serverName, definitions] of Object.entries(catalog)) {
|
|
19279
19359
|
if (!configuredServers.has(serverName)) {
|
|
19280
19360
|
this.logger.warn("Skipping cached MCP tool definitions for a server that is no longer configured", { serverName });
|
|
19281
19361
|
continue;
|
|
19282
19362
|
}
|
|
19283
19363
|
for (const [toolName, definition] of Object.entries(modelVisibleDefinitions(definitions))) try {
|
|
19284
|
-
|
|
19364
|
+
const tool = await this.toolFromDefinition({
|
|
19285
19365
|
serverName,
|
|
19286
19366
|
definition
|
|
19287
19367
|
});
|
|
19368
|
+
this.setNamespacedTool(tools, keySources, serverName, toolName, tool);
|
|
19288
19369
|
} catch (error) {
|
|
19289
19370
|
if (!(error instanceof _mastra_core_error.MastraError && error.id === "MCP_CLIENT_INVALID_TOOL_INPUT_SCHEMA")) throw error;
|
|
19290
19371
|
}
|
|
@@ -19345,6 +19426,28 @@ onListChanged: async (serverName, handler) => {
|
|
|
19345
19426
|
} : result;
|
|
19346
19427
|
}
|
|
19347
19428
|
/**
|
|
19429
|
+
* Stores a tool under its `serverName_toolName` key. Server and tool names can both contain `_`,
|
|
19430
|
+
* so two different pairs can produce one key (`local` + `x_status` and `local_x` + `status`).
|
|
19431
|
+
* The later pair keeps the key, as before; this logs a warning instead of dropping a tool silently.
|
|
19432
|
+
*/
|
|
19433
|
+
setNamespacedTool(tools, keySources, serverName, toolName, tool) {
|
|
19434
|
+
const key = `${serverName}_${toolName}`;
|
|
19435
|
+
const replaced = keySources.get(key);
|
|
19436
|
+
if (replaced) this.logger.warn(`MCP tool key "${key}" from server "${serverName}" (tool "${toolName}") replaces the tool from server "${replaced.serverName}" (tool "${replaced.toolName}"), which produces the same key. Only the tool from "${serverName}" is kept. Rename one of the servers in \`servers\` to keep both.`, {
|
|
19437
|
+
key,
|
|
19438
|
+
replaced,
|
|
19439
|
+
replacedBy: {
|
|
19440
|
+
serverName,
|
|
19441
|
+
toolName
|
|
19442
|
+
}
|
|
19443
|
+
});
|
|
19444
|
+
keySources.set(key, {
|
|
19445
|
+
serverName,
|
|
19446
|
+
toolName
|
|
19447
|
+
});
|
|
19448
|
+
tools[key] = tool;
|
|
19449
|
+
}
|
|
19450
|
+
/**
|
|
19348
19451
|
* Runs a per-server discovery `operation` against every configured server
|
|
19349
19452
|
* concurrently, isolating and logging per-server failures. Results are
|
|
19350
19453
|
* returned in configuration order (`Object.keys(serverConfigs)`) so callers
|