@mastra/mcp 2.8.0-alpha.1 → 2.8.0-alpha.2
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 +6 -1
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/tasks/suspend.d.ts +25 -0
- package/dist/client/tasks/suspend.d.ts.map +1 -0
- package/dist/client/tasks/wait.d.ts +14 -0
- package/dist/client/tasks/wait.d.ts.map +1 -1
- package/dist/client/types.d.ts +30 -0
- package/dist/client/types.d.ts.map +1 -1
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/reference-tools-mcp-client.md +45 -1
- package/dist/docs/references/reference-tools-mcp-server.md +67 -0
- package/dist/index.cjs +323 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +323 -16
- package/dist/index.js.map +1 -1
- package/dist/server/model-output-to-mcp.d.ts +14 -0
- package/dist/server/model-output-to-mcp.d.ts.map +1 -0
- package/dist/server/server.d.ts +14 -1
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/types.d.ts +31 -1
- package/dist/server/types.d.ts.map +1 -1
- package/package.json +7 -7
package/dist/index.cjs
CHANGED
|
@@ -15423,6 +15423,57 @@ var TaskRequester = class {
|
|
|
15423
15423
|
};
|
|
15424
15424
|
}
|
|
15425
15425
|
};
|
|
15426
|
+
// src/client/tasks/suspend.ts
|
|
15427
|
+
const MCP_INPUT_REQUEST_SUSPEND_TYPE = "mcp-input-request";
|
|
15428
|
+
const SuspendPayloadSchema = zod_v4.z.object({
|
|
15429
|
+
type: zod_v4.z.literal(MCP_INPUT_REQUEST_SUSPEND_TYPE),
|
|
15430
|
+
serverName: zod_v4.z.string(),
|
|
15431
|
+
toolName: zod_v4.z.string(),
|
|
15432
|
+
taskId: zod_v4.z.string().min(1),
|
|
15433
|
+
createdAt: zod_v4.z.string(),
|
|
15434
|
+
requestedAt: zod_v4.z.string().optional(),
|
|
15435
|
+
requests: TaskInputRequestsSchema
|
|
15436
|
+
});
|
|
15437
|
+
const ResumeDataSchema = zod_v4.z.object({ inputResponses: zod_v4.z.record(zod_v4.z.string(), zod_v4.z.object({
|
|
15438
|
+
action: zod_v4.z.enum([
|
|
15439
|
+
"accept",
|
|
15440
|
+
"decline",
|
|
15441
|
+
"cancel"
|
|
15442
|
+
]),
|
|
15443
|
+
content: zod_v4.z.record(zod_v4.z.string(), zod_v4.z.unknown()).optional()
|
|
15444
|
+
})) });
|
|
15445
|
+
/**
|
|
15446
|
+
* Reads a resume of a tool call that suspended on MCP task input. Returns `undefined` when the call
|
|
15447
|
+
* didn't suspend that way, so other resumes (tool approval, for one) take the normal path.
|
|
15448
|
+
*/
|
|
15449
|
+
function readInputRequestResume(host, expected) {
|
|
15450
|
+
const raw = host?.suspendPayload;
|
|
15451
|
+
if (!raw || typeof raw !== "object" || raw.type !== "mcp-input-request") return;
|
|
15452
|
+
const payload = SuspendPayloadSchema.safeParse(raw);
|
|
15453
|
+
if (!payload.success) return {
|
|
15454
|
+
ok: false,
|
|
15455
|
+
reason: `the suspended MCP input request is malformed`
|
|
15456
|
+
};
|
|
15457
|
+
if (payload.data.serverName !== expected.serverName || payload.data.toolName !== expected.toolName) return {
|
|
15458
|
+
ok: false,
|
|
15459
|
+
reason: `the suspended MCP input request belongs to another tool`
|
|
15460
|
+
};
|
|
15461
|
+
const resume = ResumeDataSchema.safeParse(host?.resumeData);
|
|
15462
|
+
if (!resume.success) return {
|
|
15463
|
+
ok: false,
|
|
15464
|
+
reason: `resume data must be { inputResponses: { [key]: { action: 'accept' | 'decline' | 'cancel', content? } } }`
|
|
15465
|
+
};
|
|
15466
|
+
const missing = Object.keys(payload.data.requests).filter((key) => !Object.hasOwn(resume.data.inputResponses, key));
|
|
15467
|
+
if (missing.length > 0) return {
|
|
15468
|
+
ok: false,
|
|
15469
|
+
reason: `resume data has no answer for ${missing.join(", ")}`
|
|
15470
|
+
};
|
|
15471
|
+
return {
|
|
15472
|
+
ok: true,
|
|
15473
|
+
payload: payload.data,
|
|
15474
|
+
inputResponses: resume.data.inputResponses
|
|
15475
|
+
};
|
|
15476
|
+
}
|
|
15426
15477
|
// src/client/tasks/wait.ts
|
|
15427
15478
|
/** Why a task wait stopped without a terminal task. */
|
|
15428
15479
|
var TaskWaitError = class extends Error {
|
|
@@ -15451,12 +15502,6 @@ const defaultSleep = (ms, signal) => new Promise((resolve, reject) => {
|
|
|
15451
15502
|
};
|
|
15452
15503
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
15453
15504
|
});
|
|
15454
|
-
/**
|
|
15455
|
-
* Polls a remote task until it reaches a terminal status, answering its input requests on the way.
|
|
15456
|
-
*
|
|
15457
|
-
* Aborting the signal or exceeding `maxWaitMs` sends `tasks/cancel` (fire-and-forget) and rejects.
|
|
15458
|
-
* Each input request key is answered at most once, however many polls list it.
|
|
15459
|
-
*/
|
|
15460
15505
|
async function waitForTask(port, created, options = {}) {
|
|
15461
15506
|
const sleep = options.sleep ?? defaultSleep;
|
|
15462
15507
|
const now = options.now ?? Date.now;
|
|
@@ -15507,7 +15552,21 @@ async function waitForTask(port, created, options = {}) {
|
|
|
15507
15552
|
const { inputRequests } = task;
|
|
15508
15553
|
const pending = Object.entries(inputRequests).filter(([key]) => !answered.has(key));
|
|
15509
15554
|
if (pending.length > 0) {
|
|
15510
|
-
const
|
|
15555
|
+
const supplied = options.inputResponses ?? {};
|
|
15556
|
+
const unanswered = pending.filter(([key]) => !Object.hasOwn(supplied, key));
|
|
15557
|
+
const responses = {};
|
|
15558
|
+
for (const [key] of pending) if (Object.hasOwn(supplied, key)) responses[key] = supplied[key];
|
|
15559
|
+
if (unanswered.length > 0 && !options.inputRequests && options.stopAtInput) {
|
|
15560
|
+
if (Object.keys(responses).length > 0) {
|
|
15561
|
+
for (const key of Object.keys(responses)) answered.add(key);
|
|
15562
|
+
await bounded(port.update(task.taskId, responses, { signal: stop.signal }));
|
|
15563
|
+
}
|
|
15564
|
+
return {
|
|
15565
|
+
...task,
|
|
15566
|
+
inputRequests: Object.fromEntries(unanswered)
|
|
15567
|
+
};
|
|
15568
|
+
}
|
|
15569
|
+
if (unanswered.length > 0) Object.assign(responses, await bounded(answerInputRequests(unanswered, task, options.inputRequests, stop.signal)));
|
|
15511
15570
|
for (const key of Object.keys(responses)) answered.add(key);
|
|
15512
15571
|
await bounded(port.update(task.taskId, responses, { signal: stop.signal }));
|
|
15513
15572
|
task = await poll();
|
|
@@ -17027,7 +17086,43 @@ var InternalMastraMCPClient = class extends _mastra_core_base.MastraBase {
|
|
|
17027
17086
|
if (serverMeta.connectFirst) await this.connect();
|
|
17028
17087
|
const operationContext = context?.requestContext ?? null;
|
|
17029
17088
|
return this.operationContextStore.run(operationContext, async () => {
|
|
17089
|
+
const suspendHost = context?.agent ?? context?.workflow;
|
|
17090
|
+
const toolCallSpan = context?.tracingContext?.currentSpan;
|
|
17030
17091
|
const executeToolCall = async () => {
|
|
17092
|
+
const resume = this.taskOptions ? readInputRequestResume(suspendHost, {
|
|
17093
|
+
serverName: this.name,
|
|
17094
|
+
toolName: tool.name
|
|
17095
|
+
}) : void 0;
|
|
17096
|
+
if (resume) {
|
|
17097
|
+
if (!resume.ok) throw new _mastra_core_error.MastraError({
|
|
17098
|
+
id: "MCP_CLIENT_TOOL_TASK_RESUME_INVALID",
|
|
17099
|
+
domain: _mastra_core_error.ErrorDomain.MCP,
|
|
17100
|
+
category: _mastra_core_error.ErrorCategory.USER,
|
|
17101
|
+
text: `Can't resume MCP tool ${tool.name}: ${resume.reason}`,
|
|
17102
|
+
details: {
|
|
17103
|
+
toolName: tool.name,
|
|
17104
|
+
serverName: this.name
|
|
17105
|
+
}
|
|
17106
|
+
});
|
|
17107
|
+
const { payload, inputResponses } = resume;
|
|
17108
|
+
this.log("debug", `Resuming tool ${tool.name} on MCP task ${payload.taskId}`);
|
|
17109
|
+
toolCallSpan?.update({ metadata: { mcpTaskKey: taskKeyOf(payload.taskId) } });
|
|
17110
|
+
const waiting = {
|
|
17111
|
+
taskId: payload.taskId,
|
|
17112
|
+
status: "input_required",
|
|
17113
|
+
createdAt: payload.createdAt,
|
|
17114
|
+
lastUpdatedAt: payload.createdAt,
|
|
17115
|
+
ttlMs: null,
|
|
17116
|
+
inputRequests: payload.requests
|
|
17117
|
+
};
|
|
17118
|
+
const res = await this.awaitToolTask(tool.name, waiting, {
|
|
17119
|
+
signal: context?.abortSignal,
|
|
17120
|
+
writer: context?.writer,
|
|
17121
|
+
suspend: suspendHost?.suspend,
|
|
17122
|
+
inputResponses
|
|
17123
|
+
});
|
|
17124
|
+
return res && finishToolCall(res);
|
|
17125
|
+
}
|
|
17031
17126
|
this.log("debug", `Executing tool: ${tool.name}`, {
|
|
17032
17127
|
toolArgs: input,
|
|
17033
17128
|
runId: context?.runId
|
|
@@ -17045,13 +17140,17 @@ var InternalMastraMCPClient = class extends _mastra_core_base.MastraBase {
|
|
|
17045
17140
|
timeout: this.timeout,
|
|
17046
17141
|
signal: context?.abortSignal
|
|
17047
17142
|
}));
|
|
17048
|
-
const toolCallSpan = context?.tracingContext?.currentSpan;
|
|
17049
17143
|
if (toolCallSpan && serverSpan) toolCallSpan.links = [...toolCallSpan.links ?? [], serverSpan];
|
|
17050
17144
|
const task = this.taskOptions ? takeTaskHandle(res) : void 0;
|
|
17051
17145
|
if (!task) return finishToolCall(res);
|
|
17052
17146
|
this.log("debug", `Tool ${tool.name} continues as MCP task ${task.taskId}`);
|
|
17053
17147
|
toolCallSpan?.update({ metadata: { mcpTaskKey: taskKeyOf(task.taskId) } });
|
|
17054
|
-
|
|
17148
|
+
const taskResult = await this.awaitToolTask(tool.name, task, {
|
|
17149
|
+
signal: context?.abortSignal,
|
|
17150
|
+
writer: context?.writer,
|
|
17151
|
+
suspend: suspendHost?.suspend
|
|
17152
|
+
});
|
|
17153
|
+
return taskResult && finishToolCall(taskResult);
|
|
17055
17154
|
};
|
|
17056
17155
|
const finishToolCall = async (res) => {
|
|
17057
17156
|
if (res.isError && this.onToolError === "throw") {
|
|
@@ -17138,8 +17237,13 @@ var InternalMastraMCPClient = class extends _mastra_core_base.MastraBase {
|
|
|
17138
17237
|
maxRetries: 0
|
|
17139
17238
|
};
|
|
17140
17239
|
}
|
|
17141
|
-
/**
|
|
17142
|
-
|
|
17240
|
+
/**
|
|
17241
|
+
* Waits for a remote task created by a tool call and returns its `CallToolResult`.
|
|
17242
|
+
*
|
|
17243
|
+
* When the task needs input that no `inputRequests` handler answers and the caller can `suspend`,
|
|
17244
|
+
* suspends with the requests and returns `undefined`, leaving the task open for the answers.
|
|
17245
|
+
*/
|
|
17246
|
+
async awaitToolTask(toolName, task, { signal, writer, suspend, inputResponses }) {
|
|
17143
17247
|
const details = {
|
|
17144
17248
|
toolName,
|
|
17145
17249
|
serverName: this.name,
|
|
@@ -17178,6 +17282,8 @@ var InternalMastraMCPClient = class extends _mastra_core_base.MastraBase {
|
|
|
17178
17282
|
}, task, {
|
|
17179
17283
|
signal,
|
|
17180
17284
|
inputRequests: this.serverConfig.inputRequests,
|
|
17285
|
+
stopAtInput: !this.serverConfig.inputRequests && suspend !== void 0,
|
|
17286
|
+
inputResponses,
|
|
17181
17287
|
pollIntervalMs: this.taskOptions?.pollIntervalMs,
|
|
17182
17288
|
maxWaitMs: this.taskOptions?.maxWaitMs,
|
|
17183
17289
|
onStatus
|
|
@@ -17189,6 +17295,17 @@ var InternalMastraMCPClient = class extends _mastra_core_base.MastraBase {
|
|
|
17189
17295
|
throw fail("MCP_CLIENT_TOOL_TASK_POLL_FAILED", `Polling MCP task ${task.taskId} failed: ${reason}`, error);
|
|
17190
17296
|
}
|
|
17191
17297
|
switch (terminal.status) {
|
|
17298
|
+
case "input_required":
|
|
17299
|
+
await suspend({
|
|
17300
|
+
type: MCP_INPUT_REQUEST_SUSPEND_TYPE,
|
|
17301
|
+
serverName: this.name,
|
|
17302
|
+
toolName,
|
|
17303
|
+
taskId: terminal.taskId,
|
|
17304
|
+
createdAt: terminal.createdAt,
|
|
17305
|
+
requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
17306
|
+
requests: terminal.inputRequests
|
|
17307
|
+
});
|
|
17308
|
+
return;
|
|
17192
17309
|
case "completed": return terminal.result;
|
|
17193
17310
|
case "cancelled": throw fail("MCP_CLIENT_TOOL_TASK_CANCELLED", terminal.statusMessage ?? `MCP task ${task.taskId} was cancelled`);
|
|
17194
17311
|
case "failed": throw fail("MCP_CLIENT_TOOL_TASK_FAILED", (typeof terminal.error.message === "string" ? terminal.error.message : void 0) ?? terminal.statusMessage ?? `MCP task ${task.taskId} failed`);
|
|
@@ -20931,6 +21048,161 @@ function echoes(body, challenge) {
|
|
|
20931
21048
|
const b = Buffer.from(challenge);
|
|
20932
21049
|
return a.length === b.length && (0, crypto$1.timingSafeEqual)(a, b);
|
|
20933
21050
|
}
|
|
21051
|
+
// src/server/model-output-to-mcp.ts
|
|
21052
|
+
const DATA_URI = /^data:([^;,]*)((?:;[^;,]*)*),(.*)$/is;
|
|
21053
|
+
/**
|
|
21054
|
+
* Maps the value a Mastra tool's `toModelOutput` returns (an AI SDK tool-result output such as
|
|
21055
|
+
* `{ type: 'text' }`, `{ type: 'json' }` or `{ type: 'content', value: [...] }`) to MCP content blocks.
|
|
21056
|
+
*
|
|
21057
|
+
* Returns `undefined` for any other shape, meaning the caller keeps its default result.
|
|
21058
|
+
*/
|
|
21059
|
+
function modelOutputToMcpContent(value, toolName) {
|
|
21060
|
+
if (!isRecord(value)) return void 0;
|
|
21061
|
+
switch (value.type) {
|
|
21062
|
+
case "text": return typeof value.value === "string" ? success([{
|
|
21063
|
+
type: "text",
|
|
21064
|
+
text: value.value
|
|
21065
|
+
}]) : void 0;
|
|
21066
|
+
case "json": return success([{
|
|
21067
|
+
type: "text",
|
|
21068
|
+
text: stringify(value.value)
|
|
21069
|
+
}]);
|
|
21070
|
+
case "error-text": return typeof value.value === "string" ? {
|
|
21071
|
+
isError: true,
|
|
21072
|
+
content: [{
|
|
21073
|
+
type: "text",
|
|
21074
|
+
text: value.value
|
|
21075
|
+
}]
|
|
21076
|
+
} : void 0;
|
|
21077
|
+
case "error-json": return {
|
|
21078
|
+
isError: true,
|
|
21079
|
+
content: [{
|
|
21080
|
+
type: "text",
|
|
21081
|
+
text: stringify(value.value)
|
|
21082
|
+
}]
|
|
21083
|
+
};
|
|
21084
|
+
case "content": return Array.isArray(value.value) ? success(value.value.map((part, index) => partToBlock(part, toolName, index))) : void 0;
|
|
21085
|
+
default: return;
|
|
21086
|
+
}
|
|
21087
|
+
}
|
|
21088
|
+
function partToBlock(part, toolName, index) {
|
|
21089
|
+
if (!isRecord(part)) return {
|
|
21090
|
+
type: "text",
|
|
21091
|
+
text: stringify(part)
|
|
21092
|
+
};
|
|
21093
|
+
switch (part.type) {
|
|
21094
|
+
case "text":
|
|
21095
|
+
if (typeof part.text === "string") return {
|
|
21096
|
+
type: "text",
|
|
21097
|
+
text: part.text
|
|
21098
|
+
};
|
|
21099
|
+
break;
|
|
21100
|
+
case "media":
|
|
21101
|
+
case "image-data":
|
|
21102
|
+
case "file-data": {
|
|
21103
|
+
const fallback = part.type === "image-data" ? "image/jpeg" : "application/octet-stream";
|
|
21104
|
+
const mediaType = typeof part.mediaType === "string" && part.mediaType ? part.mediaType : void 0;
|
|
21105
|
+
if (typeof part.data === "string") {
|
|
21106
|
+
const uri = parseDataUri(part.data);
|
|
21107
|
+
if (uri) return mediaBlock(uri.data, mediaType ?? (uri.mediaType || fallback), toolName, index);
|
|
21108
|
+
if (/^data:/i.test(part.data)) break;
|
|
21109
|
+
return mediaBlock(part.data, mediaType ?? fallback, toolName, index);
|
|
21110
|
+
}
|
|
21111
|
+
if (part.data instanceof Uint8Array) return mediaBlock(Buffer.from(part.data).toString("base64"), mediaType ?? fallback, toolName, index);
|
|
21112
|
+
break;
|
|
21113
|
+
}
|
|
21114
|
+
case "image-url":
|
|
21115
|
+
case "file-url": {
|
|
21116
|
+
if (typeof part.url !== "string") break;
|
|
21117
|
+
const uri = parseDataUri(part.url);
|
|
21118
|
+
if (!uri) return {
|
|
21119
|
+
type: "text",
|
|
21120
|
+
text: part.url
|
|
21121
|
+
};
|
|
21122
|
+
const fallback = part.type === "image-url" ? "image/jpeg" : "application/octet-stream";
|
|
21123
|
+
const mediaType = typeof part.mediaType === "string" && part.mediaType ? part.mediaType : void 0;
|
|
21124
|
+
return mediaBlock(uri.data, mediaType ?? (uri.mediaType || fallback), toolName, index);
|
|
21125
|
+
}
|
|
21126
|
+
}
|
|
21127
|
+
return {
|
|
21128
|
+
type: "text",
|
|
21129
|
+
text: stringify(part)
|
|
21130
|
+
};
|
|
21131
|
+
}
|
|
21132
|
+
function mediaBlock(data, mediaType, toolName, index) {
|
|
21133
|
+
const separator = mediaType.indexOf(";");
|
|
21134
|
+
const mimeType = separator === -1 ? mediaType.toLowerCase() : mediaType.slice(0, separator).toLowerCase() + mediaType.slice(separator);
|
|
21135
|
+
if (mimeType.startsWith("image/")) return {
|
|
21136
|
+
type: "image",
|
|
21137
|
+
data,
|
|
21138
|
+
mimeType
|
|
21139
|
+
};
|
|
21140
|
+
if (mimeType.startsWith("audio/")) return {
|
|
21141
|
+
type: "audio",
|
|
21142
|
+
data,
|
|
21143
|
+
mimeType
|
|
21144
|
+
};
|
|
21145
|
+
return {
|
|
21146
|
+
type: "resource",
|
|
21147
|
+
resource: {
|
|
21148
|
+
uri: `mastra://tool-output/${encodeURIComponent(toolName)}/${index}`,
|
|
21149
|
+
mimeType,
|
|
21150
|
+
blob: data
|
|
21151
|
+
}
|
|
21152
|
+
};
|
|
21153
|
+
}
|
|
21154
|
+
/**
|
|
21155
|
+
* Splits a `data:` URI into its media type (with parameters such as `charset`, minus the `base64`
|
|
21156
|
+
* marker) and Base64 payload. Returns `undefined` if it can't be decoded.
|
|
21157
|
+
*/
|
|
21158
|
+
function parseDataUri(value) {
|
|
21159
|
+
const match = DATA_URI.exec(value);
|
|
21160
|
+
if (!match) return void 0;
|
|
21161
|
+
const [, type = "", params = "", payload = ""] = match;
|
|
21162
|
+
const parameters = params.split(";").map((param) => param.trim()).filter(Boolean);
|
|
21163
|
+
const base64 = parameters.some((param) => param.toLowerCase() === "base64");
|
|
21164
|
+
let data = payload;
|
|
21165
|
+
if (!base64) {
|
|
21166
|
+
const bytes = decodePercentEncoded(payload);
|
|
21167
|
+
if (!bytes) return void 0;
|
|
21168
|
+
data = bytes.toString("base64");
|
|
21169
|
+
}
|
|
21170
|
+
const mediaType = type.trim().toLowerCase();
|
|
21171
|
+
const kept = parameters.filter((param) => param.toLowerCase() !== "base64");
|
|
21172
|
+
return {
|
|
21173
|
+
mediaType: mediaType && kept.length ? [mediaType, ...kept].join(";") : mediaType,
|
|
21174
|
+
data
|
|
21175
|
+
};
|
|
21176
|
+
}
|
|
21177
|
+
/** Decodes `%XX` escapes as raw octets (RFC 2397), not UTF-8 text. Returns `undefined` for a malformed escape. */
|
|
21178
|
+
function decodePercentEncoded(payload) {
|
|
21179
|
+
const input = Buffer.from(payload, "utf8");
|
|
21180
|
+
const output = Buffer.alloc(input.length);
|
|
21181
|
+
let length = 0;
|
|
21182
|
+
for (let index = 0; index < input.length; index++) {
|
|
21183
|
+
if (input[index] !== 37) {
|
|
21184
|
+
output[length++] = input[index];
|
|
21185
|
+
continue;
|
|
21186
|
+
}
|
|
21187
|
+
const hex = input.toString("latin1", index + 1, index + 3);
|
|
21188
|
+
if (!/^[0-9a-f]{2}$/i.test(hex)) return void 0;
|
|
21189
|
+
output[length++] = Number.parseInt(hex, 16);
|
|
21190
|
+
index += 2;
|
|
21191
|
+
}
|
|
21192
|
+
return output.subarray(0, length);
|
|
21193
|
+
}
|
|
21194
|
+
function success(content) {
|
|
21195
|
+
return {
|
|
21196
|
+
isError: false,
|
|
21197
|
+
content
|
|
21198
|
+
};
|
|
21199
|
+
}
|
|
21200
|
+
function stringify(value) {
|
|
21201
|
+
return JSON.stringify(value) ?? String(value);
|
|
21202
|
+
}
|
|
21203
|
+
function isRecord(value) {
|
|
21204
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21205
|
+
}
|
|
20934
21206
|
// src/server/tasks/store.ts
|
|
20935
21207
|
/** How a principal is stored and shown. Principals can carry user and subject ids, so only their hash is kept. */
|
|
20936
21208
|
function principalKeyOf(principal) {
|
|
@@ -22660,6 +22932,7 @@ var MCPServer = class extends _mastra_core_mcp.MCPServerBase {
|
|
|
22660
22932
|
tasksInspector;
|
|
22661
22933
|
webhooks;
|
|
22662
22934
|
legacy;
|
|
22935
|
+
toolResultOptions;
|
|
22663
22936
|
tasks;
|
|
22664
22937
|
/** Where tasks are kept: `tasks.store`, or the store picked on first use. */
|
|
22665
22938
|
taskStore;
|
|
@@ -22683,6 +22956,7 @@ var MCPServer = class extends _mastra_core_mcp.MCPServerBase {
|
|
|
22683
22956
|
this.requestStateCodec = this.createRequestStateCodec(config.requestState);
|
|
22684
22957
|
this.eventsOptions = config.eventsOptions ?? {};
|
|
22685
22958
|
this.legacy = config.legacy ?? "reject";
|
|
22959
|
+
this.toolResultOptions = config.toolResult ?? {};
|
|
22686
22960
|
this.tasks = resolveTasksConfig(config.tasks, {
|
|
22687
22961
|
tools: Object.keys(config.tools ?? {}),
|
|
22688
22962
|
workflows: Object.keys(config.workflows ?? {}),
|
|
@@ -23303,7 +23577,7 @@ var MCPServer = class extends _mastra_core_mcp.MCPServerBase {
|
|
|
23303
23577
|
resumeSchema: execution.resumeSchema
|
|
23304
23578
|
});
|
|
23305
23579
|
}
|
|
23306
|
-
const result = server.projectCallToolResult(this.
|
|
23580
|
+
const result = server.projectCallToolResult(await this.completedToolResult(name, tool, execution.output, requestContext), this.toolInfo(name, tool).outputSchema);
|
|
23307
23581
|
this.logger.info(`Tool '${name}' finished in ${Date.now() - startedAt}ms.`);
|
|
23308
23582
|
return result;
|
|
23309
23583
|
} catch (error) {
|
|
@@ -23370,7 +23644,7 @@ var MCPServer = class extends _mastra_core_mcp.MCPServerBase {
|
|
|
23370
23644
|
suspendPayload: options.suspendPayload
|
|
23371
23645
|
},
|
|
23372
23646
|
initialAttempt: () => ({
|
|
23373
|
-
outcome: settled.then((outcome) => "error" in outcome ? this.toolErrorOutcome(name, outcome.error) : this.toTaskOutcome(name, tool, outcome.execution)),
|
|
23647
|
+
outcome: settled.then((outcome) => "error" in outcome ? this.toolErrorOutcome(name, outcome.error) : this.toTaskOutcome(name, tool, outcome.execution, requestContext)),
|
|
23374
23648
|
abort: (reason) => controller.abort(reason)
|
|
23375
23649
|
})
|
|
23376
23650
|
}) };
|
|
@@ -23481,7 +23755,7 @@ var MCPServer = class extends _mastra_core_mcp.MCPServerBase {
|
|
|
23481
23755
|
suspendPayload,
|
|
23482
23756
|
tracingContext: span ? { currentSpan: span } : void 0
|
|
23483
23757
|
});
|
|
23484
|
-
return this.toTaskOutcome(name, tool, execution);
|
|
23758
|
+
return await this.toTaskOutcome(name, tool, execution, requestContext);
|
|
23485
23759
|
} catch (error) {
|
|
23486
23760
|
return this.toolErrorOutcome(name, error);
|
|
23487
23761
|
}
|
|
@@ -23565,7 +23839,8 @@ var MCPServer = class extends _mastra_core_mcp.MCPServerBase {
|
|
|
23565
23839
|
}
|
|
23566
23840
|
};
|
|
23567
23841
|
}
|
|
23568
|
-
|
|
23842
|
+
/** Never rejects: a failure building the result completes the task with the tool error result. */
|
|
23843
|
+
async toTaskOutcome(name, tool, execution, requestContext) {
|
|
23569
23844
|
if (execution.status === "suspended") {
|
|
23570
23845
|
this.logger.debug(`Tool task '${name}' requires client input.`);
|
|
23571
23846
|
return {
|
|
@@ -23575,7 +23850,11 @@ var MCPServer = class extends _mastra_core_mcp.MCPServerBase {
|
|
|
23575
23850
|
};
|
|
23576
23851
|
}
|
|
23577
23852
|
if (tool.mcp?.toolType === "agent" && execution.output?.finishReason === "suspended") return completed(errorResult$1(`Agent '${name.slice(4)}' paused for tool approval or input, which MCP tasks don't support yet`));
|
|
23578
|
-
|
|
23853
|
+
try {
|
|
23854
|
+
return completed(await this.completedToolResult(name, tool, execution.output, requestContext));
|
|
23855
|
+
} catch (error) {
|
|
23856
|
+
return this.toolErrorOutcome(name, error);
|
|
23857
|
+
}
|
|
23579
23858
|
}
|
|
23580
23859
|
/** A tool that throws inside a task completes it with `isError`, as a synchronous call would answer. */
|
|
23581
23860
|
toolErrorOutcome(name, error) {
|
|
@@ -23890,6 +24169,34 @@ var MCPServer = class extends _mastra_core_mcp.MCPServerBase {
|
|
|
23890
24169
|
output
|
|
23891
24170
|
};
|
|
23892
24171
|
}
|
|
24172
|
+
/**
|
|
24173
|
+
* The result of a tool call that completed: the default result, built from `toModelOutput` when
|
|
24174
|
+
* `toolResult.modelOutput` is on, then passed through `toolResult.transform`. Throws what
|
|
24175
|
+
* `toModelOutput` or `transform` throw.
|
|
24176
|
+
*/
|
|
24177
|
+
async completedToolResult(name, tool, output, requestContext) {
|
|
24178
|
+
let result = this.toCallToolResult(name, tool, output);
|
|
24179
|
+
const { modelOutput = false, transform } = this.toolResultOptions;
|
|
24180
|
+
if (modelOutput && !result.isError && tool.toModelOutput && output != null) {
|
|
24181
|
+
const mapped = modelOutputToMcpContent(await tool.toModelOutput(output), name);
|
|
24182
|
+
if (mapped?.isError) result = {
|
|
24183
|
+
isError: true,
|
|
24184
|
+
content: mapped.content,
|
|
24185
|
+
...result._meta ? { _meta: result._meta } : {}
|
|
24186
|
+
};
|
|
24187
|
+
else if (mapped) result = {
|
|
24188
|
+
...result,
|
|
24189
|
+
content: result.structuredContent !== void 0 ? [...result.content, ...mapped.content] : mapped.content
|
|
24190
|
+
};
|
|
24191
|
+
}
|
|
24192
|
+
if (transform) result = await transform({
|
|
24193
|
+
toolName: name,
|
|
24194
|
+
output,
|
|
24195
|
+
result,
|
|
24196
|
+
requestContext
|
|
24197
|
+
}) ?? result;
|
|
24198
|
+
return result;
|
|
24199
|
+
}
|
|
23893
24200
|
toCallToolResult(name, tool, value) {
|
|
23894
24201
|
if ((0, _mastra_core_tools.isValidationError)(value)) {
|
|
23895
24202
|
this.logger.warn(`Tool '${name}' rejected its input.`, { error: value.message });
|