Highlights
MCP v2 (2026-07-28) server architecture in @mastra/mcp@2.0.0
@mastra/mcp is now rebuilt on the MCP 2026-07-28 revision: no initialize handshake/session headers, and tools that need input use first-class suspend/resume (context.suspend() + context.resumeData) with signed, self-contained continuation state (requestState). Server routes now return explicit { status: 'suspended', suspendPayload, resumeSchema } vs { status: 'completed', output }.
New vector stores: Azure AI Search + Weaviate
Two new MastraVector backends land: @mastra/azure-ai-search@0.1.0 (Azure AI Search with auto-indexed filterable metadata for Memory/RAG) and @mastra/weaviate@0.1.0 (full MastraVector contract with rich Mongo-style metadata filtering and deterministic vector ID mapping).
Observability trace querying gets “real” pagination, discovery, and delta polling (core/server/client + ClickHouse/DuckDB/PG)
Advanced trace queries now support page-based pagination (with totals), bounded field/value discovery APIs, and delta polling cursors to transition from initial load → incremental updates efficiently. Trace list results also include root-span summary fields (name, createdAt, metadata, inputPreview, etc.) so UIs can render useful lists without fetching full traces.
Durable-agent performance: stop persisting running checkpoints by default
Durable agents no longer write running snapshots unless recovery.durableAgents: 'auto' is enabled (or you override via shouldPersistSnapshot), eliminating large amounts of storage churn on normal runs while keeping pending/paused/suspended snapshots for human-in-the-loop resume.
Token-budgeted conversation history trimming with messageHistory
Memory now supports messageHistory: { maxTokens, atMaxRemoveTokens? } to keep context within a token budget by dropping oldest remembered messages in chunks, persisting the trim boundary per thread so future turns stay within budget without deleting stored messages.
Breaking Changes
- Agent Controller stream message events changed: one full
message_start, then ID-addressedmessage_updatedeltas;message_endnow contains only the message ID (store by ID and apply updates). @mastra/mcp@2.0.0is a major rewrite: MCP 2026-07-28 only (legacy handshake removed), suspend/resume replaces elicitation APIs, and several server/client transport and protocol surfaces were removed per the MCP v2 migration guide.
Changelog
@mastra/core@1.68.0
Minor Changes
-
Make the workspace grep text-extension whitelist extensible. Added
.sas,.log, and.jsonlto the built-in text extensions and MIME type map so they are searchable by default. Added an optionaltextExtensionsoption toMastraFilesystemOptions(exposed viafilesystem.isTextFile()) so consumers can register additional text extensions. When an explicit file path is grepped but its extension isn't recognized as text, the grep summary now reports it as skipped so "no matches" is distinguishable from "never searched". (#24003)import { LocalFilesystem } from '@mastra/core/workspace'; // Register extra extensions as searchable text files const filesystem = new LocalFilesystem({ basePath: process.cwd(), textExtensions: ['.sasx', '.myext'], }); filesystem.isTextFile('report.sasx'); // true
-
Added consistent, resumable
prune()execution for storage adapters, including bounded work, pause intervals, and cancellation. (#23466)const controller = new AbortController(); await storage.prune({ maxBatches: 10, maxRows: 10_000, pauseMs: 25, signal: controller.signal, });
-
Added list-compatible page pagination to advanced trace queries while preserving keyset cursors. (#24061)
const result = await client.queryTraces({ timeRange, pagination: { page: 0, perPage: 25 }, });
-
Added support for refreshing advertised agent peer details without reclaiming thread ownership. (#23696)
const updated = agent.updateThreadPeerAdvertisement({ resourceId: 'resource-1', threadId: 'thread-1', peer: { title: 'Updated thread title', metadata: { mode: 'review' } }, });
-
Added `orderBy` support when listing datasets, dataset items, experiments and experiment results so Studio and API consumers can sort server-side instead of receiving a fixed newest-first order. (#24567)
```ts
const { datasets } = await mastra.datasets.listDatasets({ orderBy: { field: "name", direction: "ASC" } });
const { items } = await dataset.listItems({ orderBy: { field: "createdAt", direction: "ASC" } });
```Allowed fields: datasets (`createdAt`, `updatedAt`, `name`), items (`createdAt`, `updatedAt`), experiments (`createdAt`, `status`), experiment results (`startedAt`, `createdAt`). Unknown fields are rejected. Also exports a `resolveListOrderBy` helper for storage adapters.
-
Added root span details to queryTraces results: name, entityId, parentSpanId, createdAt, metadata, and inputPreview. Trace lists can display these fields without fetching each full trace. createdAt uses the root span start time; inputPreview contains a shortened input preview rather than the full input. (#23958)
const { traces } = await client.queryTraces({ timeRange: { from: '2026-09-01T00:00:00Z', to: '2026-09-15T00:00:00Z' }, }); // Previously required fetching the full trace: console.log(traces[0]?.name, traces[0]?.inputPreview, traces[0]?.metadata);
Also in: @mastra/clickhouse@1.20.0, @mastra/duckdb@1.10.0, @mastra/pg@1.26.0, @mastra/playground-ui@56.0.0, @mastra/server@1.68.0
-
Added dataset snapshot format utilities to validate portable identities, preserve authored JSON fields and required item creation and update timestamps, and detect artifact changes with an integrity digest. These utilities do not read or write dataset storage. Both helpers accept a configurable
maxBytesbudget (4 MiB by default), independent of the artifact format and integrity digest. (#23902)import { parseDatasetSnapshot } from '@mastra/core/datasets'; const snapshot = parseDatasetSnapshot(artifactJson, { maxBytes: 8 * 1024 * 1024 });
-
Added
notScorable(). Return it from a scorer step when a run has nothing to evaluate, for example a refund judge on a chat that never called the refund tool. Remaining steps are skipped, so the judge is never called and the run stays out of that scorer's averages.runEvals()omitsverdictwhen every configured gate or threshold was not scorable. (#24378)import { createScorer, notScorable } from '@mastra/core/evals'; import { extractToolCalls } from '@mastra/evals/scorers/utils'; const refundJudge = createScorer({ id: 'refund-judge', description: 'Judges refund handling', type: 'agent', judge: { model: 'openai/gpt-5-mini', instructions: '...' }, }) .preprocess(({ run }) => { const { tools } = extractToolCalls(run.output); return tools.includes('refundCustomer') ? { tools } : notScorable('refundCustomer was not called'); }) .generateScore({ description: 'Score the refund handling from 0 to 1', createPrompt: ({ run }) => `Rate the refund handling: ${JSON.stringify(run.output)}`, });
Reading the result.
scorer.run()is either scored or skipped. ChecknotScorablebefore usingscoreas a number, including on scorers that never skip:const result = await refundJudge.run(input); if (result.notScorable) { // skipped — no score } else { result.score; }
-
Added
generateTitle.emitEventso HTTP and stream clients receive the generated thread title without polling. (#24247)Thread titles are generated in the background after a run finishes. The
onTitleGeneratedcallback only works for in-process callers, so an app driving an agent over HTTP had no way to know when the title was ready (#21203).With
emitEvent: true, the run stream waits for the title and emits it as a transientdata-thread-titlechunk beforefinish:const memory = new Memory({ options: { generateTitle: { emitEvent: true, }, }, }); // Consumers read it from the run's stream before the `finish` chunk. for await (const chunk of stream.fullStream) { if (chunk.type === 'data-thread-title') { console.log(chunk.data.threadId, chunk.data.title); } }
The chunk is transient, so it is never persisted as part of the conversation. The default stays fully non-blocking: without
emitEvent, title generation still runs in the background and does not delay the stream.The
generateTitleobject also acceptsminMessages(minimum number of thread messages before a title is generated, default1) and an optionalmodel(defaults to the agent's own model), so title generation can run on a smaller or cheaper model than the conversation.Durable and evented agents don't emit the chunk yet; the title is still generated and persisted.
-
Added sandbox start options forwarding so providers can support cancellable startup operations. (#24451)
-
Durable agents no longer persist
runningcheckpoints by default, andcreateDurableAgent()accepts a newshouldPersistSnapshotoption to control snapshot persistence (#23915). (#23978)Previously, durable agents wrote a full workflow snapshot to storage on every step of every run, including
runningcheckpoints that are only read by crash recovery. With recovery left at its default (recovery.durableAgents: 'off'), those writes were pure overhead — a single agent turn could generate over a thousand storage statements.What changed
- Durable agents still always persist
pending,paused, andsuspendedsnapshots, so human-in-the-loop resume and tool approval keep working with no configuration. runningcheckpoints are now only written when the Mastra instance setsrecovery.durableAgents: 'auto', which is the setting that consumes them.createDurableAgent(), theDurableAgentconstructor, and the agent-leveldurableconfig accept ashouldPersistSnapshotpredicate to override the policy.- Mastra logs a warning if a custom predicate excludes
suspendedorpaused(breaks human-in-the-loop resume), or excludesrunningwhile automatic recovery is enabled (makes the agent invisible to recovery). - Evented agents are unaffected: they always persist the full snapshot set (their engine coordinates workers through storage) and log a warning if
shouldPersistSnapshotis set.
Action required if you use manual recovery: if you call
listActiveRuns(),recover(), orrecoverActiveRuns()without settingrecovery.durableAgents: 'auto', opt back intorunningcheckpoints:const durableAgent = createDurableAgent({ agent, shouldPersistSnapshot: ({ workflowStatus }) => ['pending', 'paused', 'suspended', 'running'].includes(workflowStatus), });
- Durable agents still always persist
-
Added delta polling to advanced trace queries, including a cursor on numbered pages for the initial-load-to-poll handoff. Delta cursors bind the predicate and time range; keyset pagination remains unchanged. (#24329)
// Before: load a numbered page. const page = await client.queryTraces({ timeRange, pagination: { page: 0, perPage: 100 } }); // After: continue polling from that page without rescanning it. const updates = await client.queryTraces({ timeRange, mode: 'delta', after: page.deltaCursor, limit: 100 });
Polling returns newly completed roots and root completions. It does not provide deletion notifications or guarantee re-emission after related-record changes.
-
Added Hono handler and route types to the server exports. (#23992)
import type { MiddlewareHandler } from '@mastra/core/server'; const middleware: MiddlewareHandler = async (_context, next) => { await next(); };
-
Added
structuredOutput.instructionssupport for JSON prompt injection, so you can replace the serialized JSON schema in the prompt with your own compact instructions (#24176)When
jsonPromptInjectionis active and no separate structuringmodelis configured, a caller-suppliedstructuredOutput.instructionsstring is now injected into the prompt in place of the serialized JSON schema, in both'system'and'inline'modes. On large schemas this removes thousands of tokens from every model call.const result = await agent.generate('Extract the customer name.', { structuredOutput: { schema: z.object({ name: z.string() }), jsonPromptInjection: 'system', instructions: 'Return a JSON object with a name field.', }, });
Output is still validated against
schema, so you stay responsible for keepinginstructionsin sync with the fields you need. When no separate structuringmodelis configured,instructionsis also serialized across the durable agent boundary, so the same behavior applies to durable runs. Behavior is unchanged wheninstructionsis absent or blank: the serialized schema is still injected as before. -
Added A2A v1.0 remote subagent delegation with explicit protocol selection on
A2AAgent. Existing integrations continue to use v0.3 by default. (#24134)const remoteAgent = new A2AAgent({ url: 'https://example.com/.well-known/agent-card.json', protocolVersion: '1.0', });
-
Added an
apioption to custom OpenAI-compatible model configs so a customurlcan target the OpenAI Responses API. Setapi: "responses"alongsideurlto reach/v1/responses(for example to combine function tools with reasoning models on gateways that require it); it defaults to"chat", so existing configurations are unchanged. (#24029)const agent = new Agent({ id: 'my-agent', name: 'My Agent', instructions: 'You are a helpful assistant', model: { id: 'custom/my-model', url: 'https://your-endpoint.com/v1', api: 'responses', }, });
-
Added the
MCP_SERVER_REQUESTspan type,MCPServerRequestAttributes, andEntityType.MCP_SERVERfor requests served by a MastraMCPServer. Added askipToolSpantool execution option so a caller that already owns a span can run a tool without an extraTOOL_CALLspan: (#24150)await tool.execute(args, { tracingContext: { currentSpan: requestSpan }, skipToolSpan: true, });
See #23921
-
Added
usedFallbackValueto agentgenerate()andstream()results. WithstructuredOutput.errorStrategy: 'fallback',result.objectwas previously indistinguishable from a real answer once the configuredfallbackValuehad been substituted:finishReasonstayed'stop',tripwirestayed empty, and the only marker was ametadata.fallbackflag on the internalobject-resultchunk, which never reached the result. The result — and theonFinishcallback payload — now report the substitution directly, for both the native and separate-structuring-model paths. (#24424)const result = await agent.generate('Summarize the ticket.', { structuredOutput: { schema, errorStrategy: 'fallback', fallbackValue: { summary: 'unknown', tags: [] } }, }); if (result.usedFallbackValue) { // result.object is the fallback, not something the model produced }
-
Add a
messageHistorymemory option for token-budgeted conversation history.messageHistory: { maxTokens, atMaxRemoveTokens? }counts the complete prompt against the token budget and drops the oldest remembered messages in chunks. It never removes the current turn's input, responses, context, or system messages. During agent runs, a per-thread boundary is advanced and persisted so trimmed history stays out of subsequent turns without deleting stored messages. (#23238)When
messageHistoryis set without an explicitlastMessages, the default 10-message cap is dropped so the token budget alone defines the window.lastMessagesremains supported and can be combined withmessageHistory, but counting messages is a poor proxy for context size andlastMessagesis now soft-deprecated in favour ofmessageHistory.import { Memory } from '@mastra/memory'; const memory = new Memory({ options: { messageHistory: { maxTokens: 8_000, atMaxRemoveTokens: 2_000 }, }, });
Also in: @mastra/client-js@1.47.0, @mastra/memory@1.31.0, @mastra/server@1.68.0
-
Added canonical trace-query field descriptors and bounded discovery contracts for queryable fields and values. (#24057)
import { getTraceQueryCanonicalFieldDescriptors, parseGetTraceQueryValuesArgs, planTraceQueryValues, } from '@mastra/core/storage'; const fields = getTraceQueryCanonicalFieldDescriptors('spans'); const values = await storage.getTraceQueryValues( planTraceQueryValues( parseGetTraceQueryValuesArgs({ timeRange: { from: '2026-08-01T00:00:00Z', to: '2026-08-02T00:00:00Z' }, predicateScope: 'spans', path: 'model', }), ), );
-
Added MCP 2026-07-28 server contracts to
MCPServerBasewhile keeping MCP 1.x servers working unchanged. (#23875)- Added
mcpVersiontoMCPServerBase. A server that sets it to2resolvesexecuteTooltoMCPToolExecutionResultV2, which reports a suspended tool ({ status: 'suspended', suspendPayload, resumeSchema }) instead of a bare result. Thrown errors and schema failures still reject. Existing 1.x servers need no new properties. - Added
context.mcp.protocolVersion, set to'2026-07-28'by 2.x servers.context.mcp.extra,logandprogresskeep the same shape on both server versions. - Added
suspend,resumeDataandsuspendPayloadat the top level of the tool execution context for direct and MCP 2.x execution. Agents and workflows keep nesting them underagentandworkflowuntil the next core major. - Added
suspendPayloadto tools resumed by agents (including durable agents) and workflows, alongsideresumeData. - Deprecated the surfaces MCP 2026-07-28 removed, for removal in the next core major:
startSSE,startHonoSSE,MCPServerSSEOptions,MCPServerHonoSSEOptions,MCPServerHTTPOptions.options,context.mcp.elicitation.sendRequest,context.mcp.extra.sendRequestandcontext.mcp.extra.sendNotification. On a 2.x server the deprecatedcontext.mcpmembers throw with a message naming the replacement.startSSEandstartHonoSSEare no longer abstract, so 2.x servers do not implement them.
Tools that need input mid-execution use the suspend/resume primitives
createToolalready has:import { createTool } from '@mastra/core/tools'; import { z } from 'zod'; const confirm = createTool({ id: 'confirm', description: 'Ask for confirmation', inputSchema: z.object({ amount: z.number() }), outputSchema: z.boolean(), suspendSchema: z.object({ phase: z.literal('confirm'), amount: z.number() }), resumeSchema: z.object({ confirmed: z.boolean() }), execute: async ({ amount }, context) => { await context.mcp?.log?.('info', 'asking for confirmation', { amount }); if (!context.resumeData) { await context.suspend?.({ phase: 'confirm', amount }); return; } return context.resumeData.confirmed; }, });
- Added
-
Added a stable resource-limit error for bounded trace-query field and value discovery. (#24169)
import { TraceQueryResourceLimitError, planTraceQueryValues } from '@mastra/core/storage'; const plan = planTraceQueryValues({ timeRange, predicateScope: 'spans', path: 'model', search: 'claude', limit: 25, }); try { await observability.getTraceQueryValues(plan); } catch (error) { if (error instanceof TraceQueryResourceLimitError) { console.error(error.code); } }
Patch Changes
-
Fix
agent.generate()/.stream()reporting a callerabortSignalcancellation as a processor tripwire. When a run is aborted and no processor triggered a tripwire, the result now reportsfinishReason: 'aborted'and leavestripwireundefined, instead of synthesizing a generic{ reason: 'Processor tripwire triggered' }. Genuine processor tripwires are unaffected. (#24008) -
Update provider registry and model documentation with latest models and providers (
1e68460) -
Improved runtime portability by using Web Crypto for cryptographic operations that do not require changes to existing synchronous APIs. (#24369)
-
Fixed durable agents passing
stepNumber: 0and an emptystepslist toprocessLLMRequest,processLLMResponse, andprocessOutputStepon every step. Processor hooks now receive the correct zero-based step index and the running step list, matching non-durable agents. Fixes #24279 (#24293) -
Fixed claimed thread owners acting on a redelivered idle signal twice. A signal that a PubSub backend redelivers is now handled once while the runtime still remembers its request id, so it no longer queues the turn again or starts a second run for the same run id. If the reply to the caller never reached the backend, the redelivery re-sends it instead of reprocessing the signal. (#24467)
-
Documented the
SpanOutputProcessor.process()contract: mutate the span you receive and return the same instance, or returnundefinedto drop it. Returning a copy is not supported becauseexportSpan()andisValidare instance members of the live span. Related to #23796 (#24048)Also in: @mastra/observability@1.17.9
-
Fixed goals that never ended after reaching their evaluation budget while waiting for user input. Once a goal uses all of its
maxRunsevaluations, later chat turns now park it aspausedwith the budget reason. Previously they reported it as still running and rendered acontinueverdict on every turn. RaisemaxRunsand resume the goal to continue it. (#24402) -
Fixed structured output fallback instructions to include the requested JSON schema. (#24457)
-
Ignore malformed numeric
Retry-Aftervalues instead of parsing them as years and delaying retries up to the configured cap. (#23997) -
Fixed streamed tool events and assistant messages to retain their originating thread when a session switches threads. Consumers can use tool-event threadId to ignore delayed output from a previous conversation. (#24313)
-
Fixed span output processors that return a copy of the span instead of the same instance. Previously this threw
TypeError: processedSpan?.exportSpan is not a functionout ofstartSpan()for started/updated spans, and silently dropped every ended span. Now the span is dropped with a logged processor error naming the processor, and theSensitiveDataFilter.process()docstring correctly states that it mutates the span in place. Fixes #23796 (#24048)Also in: @mastra/observability@1.17.9
-
createCodingAgentnow repairs recoverable bad requests instead of replaying them. (#24469)The default error processors ran the blind stream retry first. It claims these rejections, so a request that
ProviderHistoryCompatorPrefillErrorHandlerknows how to fix was retried unchanged, earned the same rejection, and surfaced as a failed turn. Both repair processors now run ahead of it.What changes for you: a coding agent hitting a malformed tool-call id or an assistant-prefill rejection now retries a corrected request rather than an identical one. The retry budget is unchanged.
Pass your own
errorProcessorsto opt out; the default list is only used when you pass none. -
Fixed query-parser, static-site generation, parseBody, and XSS security issues by updating hono to 4.13.7. (#24027)
Also in: @mastra/deployer@1.68.0, @mastra/editor@0.15.1, @mastra/factory@0.16.0, @mastra/hono@1.7.10, @mastra/inngest@1.9.0, @mastra/mcp@2.0.0, @mastra/mcp-docs-server@1.2.27, @mastra/mcp-registry-registry@1.1.3, @mastra/next@0.2.26, @mastra/server@1.68.0, @mastra/tanstack-start@0.2.26
-
Fixed image and file URLs returned from a tool's toModelOutput being corrupted before reaching the model. Fixes #22618 (#24371)
- Remote image-url and file-url tool results are no longer rewritten into a media part with the URL stuffed into the Base64-only data field, and their providerOptions are no longer dropped.
- URL parts are preserved as-is and converted to the correct shape for the target model's specification version: image-url/file-url for v3 models, url-tagged file parts for v4 models.
- Messages persisted by older versions with a URL in the media data field are healed the same way.
-
Fixed structured output to reject responses truncated by token limits or content filters. (#24464)
-
Fixed model settings returned by an input processor being ignored when the agent also configured them. Fixes #22395 (#24429)
Values returned from
processInputSteporprepareStepnow take effect for the model call, includingmaxRetries. Previously any setting the model list also specified won the conflict, so a processor could not change it. This affected single-model agents as well as fallback chains.A processor that returns only some settings (for example just
{ temperature }) keeps the configured retry and timeout limits. Inference telemetry now reports the settings the model actually received. -
Fixed agent and workflow delegation so model-driven resumes use framework-persisted suspended tool-call identity, including falsy resume payloads, and cannot select sibling runs by supplying a run ID. Successful resumes now retire every persisted representation of only the selected suspension. (#24258)
-
Fix durable agents dropping
writer.custom()/writer.write()emissions from tools. The durable tool-call step now provides awriter(ToolStream) in the tool execution context, so tools resolved from the Mastra registry on cross-process runs (e.g. an@mastra/inngestworker) receive a working writer instead ofundefined. Fixes #24196. (#24229) -
Fixed ModelRouter URL capability discovery failures being hidden and permanently cached. See #24436. (#24454)
-
Fix
EventedAgent.executeWorkflow()emitting a run's terminal error from an un-awaited.catch, which could surface as anunhandledRejectionduring shutdown. Both terminal-error emission sites now route throughemitErrorInBackground(), so a publish failure (e.g. pubsub/storage closed while the run finishes) is logged as a warning instead of crashing the process — matching theDurableAgentbehavior fixed in #23168. (#24083) -
Fixed queued follow-up messages being saved without an answer after the active run is aborted. (#23926)
- Pending signals wait for a fresh run instead of being consumed by an aborted run.
- Queued messages no longer inherit the previous run's cancellation signal. Explicit cancellation supplied for a queued message is preserved.
- Pending signals stay ahead of idle messages when preparation fails, including after cancellation of a queued startup.
- Forwarding signals to a new thread owner does not duplicate the same ID already waiting in that owner's pre-run or pending signal queue.
-
Fixed durable agent approval resumes so live assistant events and token usage are recorded once. (#23116) (#24265)
-
Fixed attachment download failures bypassing error processors in regular agent runs. Processors can now repair the message context and retry when a historical attachment becomes unavailable. (#23988)
-
Fixed completed workflow runs remaining in memory after resume. (#24267)
-
Breaking change (#22978)
Agent Controller streams now emit one complete
message_startpayload, followed by ID-addressedmessage_updateevents for text, reasoning, and message-part changes.message_endnow contains only the message ID.Migration
Previously, consumers read the complete message from each update. Now, store the start payload by ID and apply subsequent updates to that message:
if (event.type === 'message_start') messages.set(event.message.id, event.message); if (event.type === 'message_update') applyUpdate(messages.get(event.id), event.event);
Also in: @mastra/client-js@1.47.0, @mastra/code-sdk@1.8.0, @mastra/server@1.68.0
-
CommonJS consumers can now use core token counting, slug generation, and workspace operations without ESM loading errors. (#24272)
-
Fixed evented workflows failing with "condition is not a function" when a dountil or dowhile loop body is a nested workflow and events go through a serializing pubsub such as Redis Streams. The loop condition is now read from the live workflow registry instead of the serialized event payload, which cannot carry functions. Fixes #23111. (#24366)
-
Surface filesystem read failures in the workspace
greptool instead of silently reporting them as a complete "0 matches" search. (#24053)- Partial-search reporting. When a directory cannot be listed or a file cannot be read, those failures are now counted and appended to the result summary (
N paths skipped: read error) so a partial search is distinguishable from a genuinely empty one. - Missing targets. If the target path does not exist (
ENOENT, orENOTDIRwhen a path component is a file), the summary reportstarget path not found: nothing searchedrather than a plain "0 matches". - Strict mode. A new construction-time
strictoption makes any such read failure throw instead of being skipped, for callers that want to fail fast. .gitignorehandling.loadGitignorenow only swallows a genuinely-absent.gitignore(ENOENT) and rethrows permission/IO errors, which previously changed the search scope silently.
Enable strict mode when constructing the workspace tools to fail fast when any part of the target cannot be read:
import { createWorkspaceTools, WORKSPACE_TOOLS } from '@mastra/core/workspace'; const tools = await createWorkspaceTools(workspace, undefined, { grep: { strict: true } }); // Throws instead of reporting a partial result when a path cannot be read. const result = await tools[WORKSPACE_TOOLS.FILESYSTEM.GREP].execute({ pattern: 'needle' }, { workspace });
- Partial-search reporting. When a directory cannot be listed or a file cannot be read, those failures are now counted and appended to the result summary (
-
Fixed tool approval failing on runs with large workflow snapshots.
agent.approveToolCall(),declineToolCall(), andresumeStream({ toolCallId })could throwAGENT_RESUME_TOOL_CALL_NOT_SUSPENDEDfor a run that was genuinely suspended. This happened when saving a large snapshot took longer than the fixed 2-second validation window. The validator now waits while the run is still persisting its suspension, and rejects right away when the tool call is stale or the run has already finished. Fixes #22413. (#24171) -
Fixed aborting suspended agent runs so parked tool calls are denied, the thread is released, and messages sent immediately after Stop receive a response. (#24266)
-
Fixed workflow and agent delegation tools adopting a malformed model-supplied
suspendedToolRunId. Some models emit the literal string"null"for this optional auto-resume field on fresh calls; because that string is truthy, two independent workflow-tool calls could collide on a single run (silently dropping one), and agent delegation could try to resume a non-existent run and crash. Sentinel strings ("null","undefined","none","nil") are now treated as absent at every point where the field crosses the model boundary. (#24121)Behavior change: workflow tools now only honor a supplied
suspendedToolRunIdtogether withresumeData— fresh calls always receive a framework-generated unique run id. If you pinnedargs.suspendedToolRunIdin abeforeToolCallhook (the documented workaround for run-id collisions), that pin is no longer applied on fresh calls — and is no longer needed. Fixes #23739. -
Fixed workspace
requireReadBeforeWritefalsely rejecting writes with "has not been read" after suspend/resume and between conversation turns (#23772). Read records now persist per memory thread in thethreadStatestorage domain — the same store that holds task lists and goal objectives — so a file read before a tool suspends (for example, awaiting plan approval or arequireApprovaltool) no longer needs a wasteful re-read after the run resumes, including on serverless runtimes where the process is torn down between suspend and resume when a persistent storage adapter (for example LibSQL or Postgres) is configured. No configuration is needed: with in-memory storage, records survive suspend/resume within the same process, and runs without a memory thread or Mastra storage fall back to per-run tracking. Files modified on disk after being read still require a re-read before writing. Read records are scoped to the filesystem they were read from (provider + base path), so threads that resolve a different workspace or filesystem between requests cannot satisfy the write gate with a read from a different file store. (#24122) -
Fixed structured output with a top-level array of primitives (e.g.
z.array(z.string())orz.array(z.number())) silently resolvingresponse.objectto an empty array. Array elements that are strings, numbers, booleans or null are now returned fromgenerate(),stream().objectandobjectStream, and the final result is validated against what the model actually returned. Fixes #23980. (#24055) -
Fixed structured output fallback warnings to use the agent logger and aligned the loop fallback logger with error-level defaults. (#24455)
-
Lowered the implicit error-processor retry cap from 10 to 3 and made it visible. Configuring
errorProcessorswithout an explicitmaxProcessorRetriespreviously allowed a processor that always requests a retry to drive 11 model calls for a single turn, silently and regardless ofmaxRetries: 0. The cap is now 3 (4 model calls worst case) and a one-time warning is logged naming the setting to configure. Every built-in error processor self-limits to at most one retry, so only a processor that never stops asking is affected; callers who need a larger budget can setmaxProcessorRetriesexplicitly. (#24503)Also aligned the durable execution path with the standard loop:
processAPIErrornow runs on the final attempt too, so a processor can observe and report a terminal failure instead of being skipped once the retry budget is spent. -
Fixed unbinding a thread so it no longer aborts the run a different agent instance is executing. Detaching from a thread, switching threads, or tearing down a session now stops only run work owned by the local process; explicit cancellation still stops the run wherever it is executing. Thread peer discovery now marks advertisements published by the agent that asked, so a caller can tell its own threads apart from an in-process peer agent's. (#24510)
-
Fixed idle wake signals losing the caller's request context and misreporting their outcome when the thread had a claimed owner. (#24347)
A wake that starts a run on a locally claimed owner now applies the incoming
streamOptions.requestContextto that run. The claimed owner's own stream options were used verbatim, so a dispatcher waking a session on behalf of an authenticated caller started the run without that identity, and downstream lookups that require a caller — a workspace resolver, for example — failed. The owner's remaining options stay authoritative, since the run executes inside the owner's session.The same path now reports
wakeinstead ofdeliver.deliverpromises that no run started locally and that the signal joined a run already in flight; callers that waited on that run, or re-sent because they believed it was still busy, never saw the work happen.A claimed owner in another process is unchanged: the wake event carries no
requestContext, so a remote owner still starts the run with its own options. -
Bound the output that the
execute_commandtool retains while a foreground command streams. Previously the tool kept its own unbounded copy of stdout and stderr, which was only read on the error path but could exhaust memory or kill the process withRangeError: Invalid string lengthon very large command output. (#24486) -
Fixed a WebSocket denial-of-service advisory by updating ws to 8.21.3. (#24027)
Also in: @mastra/deployer@1.68.0, @mastra/hono@1.7.10, @mastra/voice-google-gemini-live@0.14.11, @mastra/voice-inworld@0.4.3, @mastra/voice-openai-realtime@0.14.1, @mastra/voice-xai-realtime@0.2.11
-
Add an
openai-orphan-item-idcompat rule so a turn can recover when stored history contains an assistant message with an OpenAIitemId(msg_…) but noreasoningitem. OpenAI's Responses API replays such a message as anitem_referenceand rejects the request with a non-retryable 400 (Item 'msg_…' of type 'message' was provided without its required 'reasoning' item), which today ends the turn. (#24345)What changes for you. A thread that was permanently stuck on that 400 can now recover on the same turn. The rule removes the item references from every message shaped like the orphan so they replay by value, then asks for one retry. Azure is covered on the same footing as OpenAI. Unrelated provider data survives the repair: cache counts, reasoning-token counts and logprobs are left intact.
You have to install it. A plain
Agenthas no compat processor configured. RegisterProviderHistoryCompatinerrorProcessors— reactive recovery needs the error lane, and on the durable path the API-error pass runs only when that list is non-empty.import { Agent } from '@mastra/core/agent'; import { ProviderHistoryCompat } from '@mastra/core/processors'; const agent = new Agent({ // ... errorProcessors: [new ProviderHistoryCompat()], });
The repair is in-memory for the current turn. The healed message is not written back to storage, so each later turn on that thread still spends one rejected request before recovering — the same behavior as the existing
anthropic-tool-id-formatrule.It fires only after that error, never on a thread that has not hit it. When it does fire it repairs every orphan-shaped message in the history, because the error names only the first item and one retry is available. Valid reasoning-free messages caught by that breadth still replay correctly, by value rather than by reference. One exception: a hosted
tool_searchresult cannot replay by value, so it is dropped from the prompt rather than replayed. On a genuinely orphaned message that is the right outcome; on a valid message swept along with it, the model loses that search result and would have to look it up again. -
Fixed a processor-forced mid-turn retry discarding steps the assistant had already completed. When an output processor aborted a step with
{ retry: true }, the whole in-flight assistant message was deleted. That took the reasoning and tool calls from earlier steps in the same turn that had already been accepted. The retry now discards only the rejected step. The model still never re-sees the rejected answer, and it keeps every step it had already accepted. (#24344)Two things stop happening on this retry path as a result. An accepted tool call is no longer thrown away and run a second time. And with OpenAI reasoning models, the saved message no longer ends up carrying an assistant
itemId(msg_…) with no matchingreasoningitem. OpenAI rejects that shape with a non-retryable HTTP 400:Item 'msg_…' of type 'message' was provided without its required 'reasoning' item. The rejection then recurs whenever that stored history is replayed on a later turn (#22291). -
Fixed replay of OpenAI-hosted
tool_searchacross turns. The Responses API gives a hosted search's call and its output distinct item ids (tsc_…/tso_…); Mastra now keeps both on the stored tool part and splits them back apart when building a prompt, so each side replays as its ownitem_referenceinstead of the same one twice. Hosted searches are also kept provider-executed through a round trip, so their result is no longer re-serialized as a client-modetool_search_output. (#23611)Conversations recorded before this fix kept only one of the two ids, so that hosted search pair can no longer be replayed faithfully — the single id would be referenced twice. A completed hosted search (succeeded or errored) with only one id is now omitted when building a prompt, and the model rediscovers the tool on the next turn; the rest of the conversation is unaffected and the part is still retained in response messages, so nothing is deleted from stored history. In-flight searches, which legitimately carry only a call id, and client-executed tools named
tool_searchare untouched. -
Fixed thread aborts so callers can require the intended run to still be active before it is stopped. (#24452)
-
Fixed manually renamed thread titles being replaced by Observational Memory. Explicitly regenerating a title enables automatic title updates again, as do programmatic title writes that opt out of pinning: (#23791)
await session.thread.rename({ title: 'Initial title', pin: false });
Fixes #22421
Also in: @mastra/memory@1.31.0
-
Fixed tool calls missing from MODEL_GENERATION span output when agents run through the streaming loop or durable workflows. Observability exporters such as PostHog now receive the tool calls, so PostHog's Tools tab and
$ai_output_choicesshow them for streamed generations. Fixes #24291 (#24306) -
Reduced the core install footprint by embedding the MCP context declarations instead of installing the MCP server SDK. Existing MCP tool context types remain compatible with the SDK. (#23998)
-
Improved file-based storage performance by removing a redundant filesystem stat call for every directory entry when listing domain and skill files, and by skipping the ISO date check for strings that can't be dates. As part of this change, file listings no longer follow symbolic links, so a symlink pointing at a stored file or directory is no longer included in results. Fixes #23752 (#24056)
-
Fixed active goals being reported to the agent as cancelled, and stopped the goal being repeated in the model's context on every step. (#24342)
A goal that was still running could be projected as having no objective, which the agent reads as "the goal was cancelled" and stops working on it. That happened when the goal state processor could not reach storage, including when
inputProcessorswas configured as a function and the processor never received the Mastra instance. The last known objective is now kept when storage cannot be read, and the instance is propagated to processors contributed by signal providers. A stale cached pause record could also be trusted over storage; the cached record is now only trusted when it shows the goal active, and storage is re-read otherwise.The projection is append-only, so re-emitting it duplicated the objective in context instead of updating it. It re-emitted on every attempt because the change it keyed on advanced each time. An objective that is already in context is now left alone.
-
Fixed a chat-library security advisory by updating chat to 4.37.0. Agent and factory chat APIs are unchanged. (#24027)
Also in: @mastra/factory@0.16.0
-
Fixed the model capability registry trusting a nested provider's capabilities over the gateway actually serving the request. When a gateway such as OpenRouter lists a routed model (e.g.
openrouter/deepseek/deepseek-v4-flash) without attachment support, that answer is now authoritative instead of falling back to the upstream provider's file, which caused Observational Memory to forward images to endpoints that reject them ("No endpoints found that support image input"). (#23685) -
Fixed shared tools exposing
_backgroundto agents that do not support background execution. (#23120)Agents now advertise
_backgroundonly for eligible tools.suspendedToolRunIdandresumeDataremain scoped to resumable tools. Repeated schema conversions no longer add nested validators. -
Fixed
mastra_workspace_read_filenever returning media parts with strict-schema providers (e.g. OpenAI, Vercel AI Gateway). Media surfacing is now decided from the file's mime type and tool config instead of the absence of the optionalencodingargument, so configured media withinmaxMediaBytesis returned as a native file/image part regardless of the model-suppliedencoding. (#24082) -
Improved
Agent.listSuspendedRuns()performance when filtering bythreadId. The thread filter is now passed down to the storage query, so supporting storage adapters narrow results inside the database instead of loading and parsing every suspended snapshot for the resource. Fixes #22627 (#24376)await agent.listSuspendedRuns({ threadId: 'thread-123' });
-
Fixed streamed
PIIDetectorredaction so sensitive values split across chunks are redacted and overlapping detections do not remove neighboring text. Redacted streams may briefly delay trailing text until a later text or non-text chunk. (#24189) -
Fixed invalid model timeout settings being silently ignored. (#24463)
-
Fixed background tool results to carry task identity and lifecycle status in metadata, including resumed tasks and streamed completions. Consumers can identify background work without interpreting tool output text. (#24313)
-
Fixed subscribed thread streams missing signals that arrive while an aborted run is being cleaned up. (#23696)
-
Fixed durable agents giving delegated sub-agents a shared, parent-derived memory scope instead of one derived from the calling user. The durable execution path now stamps the caller's thread and resource identity onto delegated agent-tool calls, matching the regular agent loop, so each user's sub-agent conversations stay isolated and memory continuity works across turns. Fixes #23903. (#23981)
-
Fixed the
skill_readtool corrupting binary skill files. A PNG or PDF is now reported asBinary file: <path> (<bytes>)with its exact size and is never decoded into the model context. Previously the file was decoded as UTF-8 before its bytes were inspected, which inflated the byte count and could put garbled text into the conversation. Binary detection now also covers NUL-free binaries such as PDFs. Text files anywhere in the skill, including underassets/, still read as text. (#24101) -
Fixed skill discovery for
Workspaceinstances that use a dynamicfilesystemresolver. (#24317)When
skillsis configured withoutskillSource, discovery now uses the filesystem resolved for the request. It no longer reads skills from the server's local disk, so host-local skills cannot appear for other tenants and each tenant's own skills are found.Skill discovery and search state are isolated per resolved filesystem, with a bounded cache so per-request filesystems do not grow the search index. Unscoped
workspace.search()no longer returns request-scoped skill documents (from dynamicskillsresolvers or resolver-backed filesystems) and still returns up totopKregular documents. Static filesystems, explicitskillSource, and the no-filesystem fallback are unchanged.const workspace = new Workspace({ filesystem: ({ requestContext }) => getTenantFilesystem(requestContext.get('orgId')), skills: ['skills'], }); // Now reads from the tenant's filesystem, not process.cwd() const scoped = await workspace.skills!.getScoped!({ requestContext }); await scoped.list();
-
Fixed fallback models restarting from the primary model between agent tool-call steps. (#23725)
-
Fixed requests failing with a 400 "Requests ending with a model turn are not supported" error on Gemini 3 models when the conversation ends with an assistant message. Fixes #23320. (#23609)
- The trailing-message guard that Anthropic models already had under native structured output now also covers Google, Vertex AI, and gateway-routed Gemini 3+ models, for every request rather than only structured-output ones.
- The guard is attached whenever an agent has input processors, because a processor can switch the model mid-step. It checks the final model before running and is skipped entirely, with no processor span, when that model does not need it.
- The guard mirrors prompt conversion: assistant messages that end on a tool result are left alone, and history that ends on assistant text followed by an unfinished tool call is guarded correctly.
- The synthetic continuation turn is added as request-only context instead of being saved to the thread, so memory and chat UIs no longer show a "Continue." or "Generate the structured response." message the user never sent.
PrefillErrorHandleralso recognizes the Gemini error so the reactive retry path covers it too.- Explicitly versioned Gemini 2.x models and Anthropic prefill behavior are unchanged. Unversioned Google ids such as
gemini-flash-latestorgemma-*are guarded conservatively because they can resolve to a Gemini 3 model.
-
Fixed a memory and connection leak in
DurableAgent. After a run finished, the automatic cleanup timer released the run's registry state but left the stream subscription attached for the life of the process, so memory (and on Redis/Valkey streams, a client connection per run) grew with every turn.stream(),resume(), andrecover()now release the subscription during automatic cleanup, the same wayobserve()already did. Fixes #24070. (#24104) -
Fix
stableStringifydropping own__proto__keys, which collapsed distinct values onto one cache key (affecting message dedup inCacheKeyGenerator.fromDBPartsand the agent response cache). (#24081) -
Removed the redundant direct Ajv dependency. Schema compatibility bundles its validator and standalone types without requiring a separate Ajv installation. (#23998)
-
Fixed token usage and finish reason handling when a model router model is wrapped with AI SDK v7
wrapLanguageModel. The AI SDK compatibility shim nests usage and finish reason one level deeper, which maderesult.usagecome back as the string"0[object Object]…"and made multi-step agent turns run tomaxSteps. Mastra now unwraps repeated envelopes so token counts stay numbers and the loop stops onstop. Fixes #23735 and #23746 (#23914) -
Fixed commands that read stdin hanging until timeout (#24336)
Commands that read standard input without being given anything to read — a bare
cat,greporrgwith no path argument,read— used to block until the command timeout expired, leaving tools stuck for minutes.executeCommand()now runs commands with standard input closed, so anything that reads stdin sees end-of-input immediately and exits:// previously hung until the timeout when the command read stdin await sandbox.executeCommand('/bin/sh', ['-c', 'rg -n "pattern" --files-with-matches | head']);
execute_commandwithbackground: truealso closes standard input: a background command that reads stdin now sees end-of-input instead of staying alive until it is killed. Retrieving that background process's handle no longer provides a writable stdin — useprocesses.spawn()with the default'pipe'mode for interactive processes.processes.spawn()keeps a writable stdin by default so long-running processes can be driven withsendStdin(). It now also accepts a publicstdinModeoption — pass'ignore'to close stdin when nothing will feed it:// opt-in: close stdin on a spawned process const handle = await sandbox.processes.spawn('node server.js', { stdinMode: 'ignore' });
Output-only execution paths (
executeCommand()andexecute_commandwithbackground: true) pass this option automatically. Honored by the local, Docker, and E2B providers; other providers may not expose stdin control. -
Fixed execute command exit events to preserve provider termination details for Studio status displays. (#24453)
-
Fixed model router cache initialization so Cloudflare Workers can load bundles before request handling begins. (#24454)
-
Sanitize delegated agent lifecycle payloads in thread-stream broadcasts to prevent conversation data from compounding across delegation levels. (#24399)
-
Fixed strict structured-output failures when using a separate structuring model. Failed requests now retry up to
maxProcessorRetries. Warn and fallback behavior is unchanged. (#24059) -
Fixed agent thread streams and durable stream adapters leaving PubSub deliveries unacknowledged. Every delivered event is now acknowledged once handled, so persistent backends like Redis Streams or GCP Pub/Sub no longer accumulate pending messages on these subscriptions. (#24462)
-
Throw a clear isolation error when LocalSandbox is configured to use macOS Seatbelt on Windows. (#24273)
-
Fixed durable agents on cross-process engines (like Inngest) dropping
requestContextvalues written by input processors. (#24133)Values set with
requestContext.set(...)inside an input processor now reach tools and scorers running on a separate worker process. Framework-internal entries (model version overrides, memory instances, auth tokens) are still kept out of the persisted workflow input. Fixes #23904 -
Fix
ToolCallFilterwithpreserveModelOutputretaining raw fallback tool results in the model prompt. Filtered results now preserve only explicitly produced compact model output, while raw tool arguments and results remain excluded. Fixes #22630. (#23962) -
Fixed
TokenLimiterProcessortruncation (strategy: 'truncate') splitting UTF-16 surrogate pairs. (#24096)Truncated text that ends inside an emoji or other astral character no longer contains a lone surrogate. Lone surrogates are replaced with
U+FFFD, so the output round-trips through UTF-8 unchanged and strict JSON consumers can reuse truncated messages as history. This matches the repair already applied to workspace tool output. -
Fixed structured output failures to preserve validation errors and raw invalid model output for diagnostics. (#24465)
-
Fixed sub-agent failures being reported to the supervisor as an empty successful result when using
stream(). A sub-agent whose model call fails (for example an invalid API key) now produces an error tool result in bothgenerate()andstream(), with a generic error message by default. The underlying cause remains available to the completion hook and diagnostics rather than being included in the supervisor's tool-result text.onDelegationCompletereceivessuccess: falsewith the error, and callingbail()from that hook on a failed delegation now stops the supervisor loop as expected. Partial streamed child messages and available result metadata remain available to the completion hook. Failed invocations retain child-thread references so Studio can restore the partial transcript after reload. Background failures follow the configured retry policy, with completion hooks invoked per attempt. A failure hook'sresultTextreplaces the error text seen by the supervisor without recovering the failed delegation or discarding its original error. (#23420) -
Fixed unrecorded conditional arms appearing successful when time-travelling past them. Preserve explicit replacement output for recorded failed arms. (#24184)
-
Fixed workflow experiments skipping a suspended branch with resume data when an earlier suspended branch has none. The dataset experiment auto-resume loop now scans all suspended branches and resumes the first one with matching
resumeSteps/resumeData, instead of stopping at the first suspended branch. (#24041) -
Allow setting
resourceIdon workflow schedules so scheduled runs are attributed to a resource. The optionalresourceIdis accepted on create and update, returned in schedule responses, and carried through both the scheduler and manual fire paths into the run snapshot, enabling multi-tenant correlation and filtering.schedules.list({ resourceId })now matches workflow schedules too. Unlike agent schedules (whereresourceIdis part of thread identity), a workflow schedule'sresourceIdis pure run-attribution metadata and can be updated via PATCH.resourceIdis optional, so existing schedules and callers are unaffected. (#24173)// Attribute scheduled runs to a resource const schedule = await mastra.schedules.create({ workflowId: 'daily-report', cron: '0 9 * * *', resourceId: 'tenant-123', }); // resourceId can be updated later await mastra.schedules.update(schedule.id, { resourceId: 'tenant-456' });
Also in: @mastra/server@1.68.0
-
Add
'canceled'to the public workflow step-status contract.StepResult,SerializedStepResult, and the derivedWorkflowStepStatusnow include aStepCanceledvariant, matching thestatus: 'canceled'results the runtime already emits and persists for canceled control-flow steps (e.g.foreachand loops). Typed consumers ofgetWorkflowRunById(),WorkflowState.steps, and lifecycle callback step results can now represent canceled steps without casts. (#24098)import type { WorkflowStepStatus } from '@mastra/core/workflows'; const run = await workflow.getWorkflowRunById(runId); const step = run?.steps?.['process-items']; if (step && !Array.isArray(step)) { const status: WorkflowStepStatus = step.status; // may now be 'canceled' if (status === 'canceled') { console.log('canceled with partial output:', step.output); } }
Note: if you have an exhaustive
switchor aRecord<WorkflowStepStatus, ...>over step statuses, TypeScript will now require a'canceled'case. This reflects a value the runtime was already producing. -
Fixed durable agent runs so thread title generation no longer delays completion. (#24335)
@mastra/ai-sdk@1.10.4
Patch Changes
- Fixed progressive streams for deeply nested agents.
data-tool-agentanddata-tool-agent-stepparts now include the ordered delegation path, nesting depth, and immediate parent agent ID. (#21735)
@mastra/arize@1.3.17
Patch Changes
- Map the OpenInference
LLMspan kind to the exportedchatcall (model_inference) instead ofmodel_generationandmodel_step, so Phoenix counts each model call's tokens once. The generation loop and its steps are nowCHAINspans. (#23910)
@mastra/arthur@0.4.17
Patch Changes
- Map the OpenInference
LLMspan kind to the exportedchatcall (model_inference) instead ofmodel_generationandmodel_step, so each model call's tokens are counted once. The generation loop and its steps are nowCHAINspans. (#23910)
@mastra/auth-auth0@1.2.4
Patch Changes
-
Fixed a jose security advisory by updating jose to 6.2.11. Auth token verification APIs are unchanged. (#24027)
Also in: @mastra/auth-google@0.1.3, @mastra/auth-neon@0.3.3, @mastra/auth-okta@0.2.3
@mastra/auth-better-auth@1.1.6
Patch Changes
- Fixed Better Auth so attackers cannot tell whether an account exists or send requests as a signed-in user from another site. Updated better-auth to 1.7.4. (#24027)
@mastra/auth-clerk@1.3.0
Minor Changes
-
Added single-organization login restriction to
@mastra/auth-clerk. SetorganizationIdororganizationSlug(or theCLERK_ORGANIZATION_ID/CLERK_ORGANIZATION_SLUGenv vars) to only allow members of one Clerk organization to sign in. Non-members are denied during authorization and SSO callback. (#24188)new MastraAuthClerk({ jwksUri: process.env.CLERK_JWKS_URI, publishableKey: process.env.CLERK_PUBLISHABLE_KEY, secretKey: process.env.CLERK_SECRET_KEY, organizationId: process.env.CLERK_ORGANIZATION_ID, });
Patch Changes
- Fixed a stored XSS advisory in Clerk by updating @clerk/backend to 3.17.2.
createClerkClientand the rest of the Mastra Clerk auth API are unchanged. (#24027)
@mastra/azure-ai-search@0.1.0
Minor Changes
-
Add
@mastra/azure-ai-search, a vector store backed by Azure AI Search. Use it anywhere Mastra accepts a vector store, including RAG pipelines and Memory semantic recall. Metadata filtering works out of the box: filterable fields are added to the index the first time a metadata key is written (autoIndexMetadata, on by default), so Memory'sthread_id/resource_idfilters need no schema setup. Any string is accepted as a vector ID. Hybrid (vector + full-text), semantic, and multi-vector queries are available throughhybridQuery(),advancedQuery(), andmultiVectorQuery(). (#24103)import { AzureAISearchVector } from '@mastra/azure-ai-search'; const store = new AzureAISearchVector({ id: 'azure-search-vectors', endpoint: process.env.AZURE_AI_SEARCH_ENDPOINT!, credential: process.env.AZURE_AI_SEARCH_CREDENTIAL!, }); await store.createIndex({ indexName: 'my-collection', dimension: 1536 }); await store.upsert({ indexName: 'my-collection', vectors: embeddings });
@mastra/browser-viewer@0.2.4
Patch Changes
-
Remove unused dependency (#23998)
Also in: @mastra/mcp-docs-server@1.2.27, @mastra/mongodb@1.18.9
@mastra/clickhouse@1.20.0
Minor Changes
-
Added discovery-specific ClickHouse timeout and memory budgets with stable resource-limit errors. (#24169)
const store = new ClickhouseStoreVNext({ id: 'clickhouse-storage', url: 'http://localhost:8123', username: 'default', password: 'password', observability: { traceQuery: { discovery: { timeoutMs: 5_000, memoryLimitBytes: 256 * 1024 * 1024, }, }, }, });
-
Added idempotent TTL updates for existing ClickHouse observability tables through
applyRetention(). When every observability signal has a retention period, deletion-request records expire after the longest signal retention plus 30 days. If any signal is unbounded, deletion-request records remain unbounded so they continue to prevent deleted data from being reintroduced. (#23466)const observability = new ObservabilityStorageClickhouseVNext({ client, retention: { tracing: 30, logs: 30, metrics: 30, scores: 90, feedback: 90, }, }); await observability.applyRetention();
-
Added list-compatible page pagination for advanced trace queries in ClickHouse storage. (#24061)
const result = await client.queryTraces({ timeRange, pagination: { page: 0, perPage: 25 }, });
-
Added ClickHouse support for handing numbered trace-query pages to delta polling, with stable cursor tie-breaking and the existing two-day retention window. (#24329)
Numbered pages remain available without a polling cursor when the installed core version lacks trace-query delta support.
// Start with a numbered page. const page = await client.queryTraces({ timeRange, pagination: { page: 0, perPage: 100 } }); // Continue with delta polling. const delta = await client.queryTraces({ timeRange, mode: 'delta', after: page.deltaCursor });
-
Added bounded trace-query field and value discovery for ClickHouse observability storage. (#24075)
const observability = await storage.getStore('observability'); const fields = await observability?.getTraceQueryObservedFields(fieldsPlan); const values = await observability?.getTraceQueryValues(valuesPlan);
Patch Changes
-
Added an index on ClickHouse deletion requests so the check that blocks updates to deleted feedback reads fewer rows instead of scanning every deletion request in the tenant scope. Existing deployments pick up the index automatically on the next start; previously written data is covered as it merges. (#23990)
-
Fixed rewritten observability scores so ordinary reads and trace predicates use compact current state and consistently return the latest sequentially written value for each score ID. Overlapping concurrent rewrites of one score ID have an undefined winner. (#24242)
@mastra/client-js@1.47.0
Minor Changes
-
Added page-based pagination for advanced trace queries. Paginated responses include
paginationmetadata withtotal,page,perPage, andhasMore. (#24061)const result = await client.queryTraces({ timeRange, pagination: { page: 0, perPage: 25 }, });
-
Added typed
queryTraceThreads()methods for querying thread identities across eligible traces. (#23920)const result = await mastraClient.queryTraceThreads({ traces: { timeRange: { from: '2026-08-01T00:00:00Z', to: '2026-09-01T00:00:00Z' }, }, });
queryTraces()remains trace-only, whilequeryTraceThreads()returns observability-derived thread identities. -
Added Client JS methods for bounded trace-query field and value discovery. Requests can now override client-level retry and abort settings with the per-request
retriesandsignaloptions. Retry counts must be non-negative safe integers, and aborted requests stop without retrying. (#24109)const fields = await mastraClient.getTraceQueryFields({ timeRange, predicateScope: 'trace', }); const values = await mastraClient.getTraceQueryValues({ timeRange, predicateScope: 'spans', path: 'model', });
-
Added delta polling inputs and response types for queryTraces(), including the cursor returned with numbered pages. (#24329)
// Start with a numbered page. const page = await client.queryTraces({ timeRange, pagination: { page: 0, perPage: 100 } }); // Continue with delta polling. const delta = await client.queryTraces({ timeRange, mode: 'delta', after: page.deltaCursor });
Patch Changes
-
Accept A2A v1 PascalCase JSON-RPC method names when the
A2A-Version: 1.0header is present. Normalize method names before dispatch and streaming response selection while preserving legacy slash-style methods. (#24260)For example, retrieve an existing task with
GetTask(replace the agent and task IDs with your own):POST /api/a2a/my-agent HTTP/1.1 Content-Type: application/json A2A-Version: 1.0 {"jsonrpc":"2.0","id":"request-1","method":"GetTask","params":{"id":"task-1"}}
Also in: @mastra/server@1.68.0
-
Added (#24261)
Added methods to
MastraClient.getA2AV1()to create, get, list, and delete task push-notification configurations without switching to the v0.3 client. List results include pagination metadata.For an existing
MastraClientinstance, register a callback for a task:const a2a = client.getA2AV1('agent-id'); await a2a.createTaskPushNotificationConfig({ tenant: 'tenant-1', id: 'config-1', taskId: 'task-1', url: 'https://example.com/callback', token: 'callback-token', authentication: { scheme: 'Bearer', credentials: 'callback-secret' }, });
-
Add pagination support to listThreadMessages (#23669)
The
listThreadMessagesclient API now accepts pagination parameters to allow fetching previous chunks of conversation history.const messages = await client.listThreadMessages('thread-123', { page: 1, perPage: 40, });
-
Fixed a jose security advisory by updating jose to 6.2.11. Client JWT helpers are unchanged. (#24027)
-
Added expectedRunId support when aborting agent thread runs. (#24452)
-
Fixed an uncaught
ERR_INVALID_STATEerror when a consumer cancels an agent stream after itsfinishchunk. (#24303)Stream cancellation no longer produces an unhandled rejection.
-
Improved client request and response types so they stay aligned with Mastra server routes. (#23961)
-
Fixed request context query parsing for POST requests. (#23961)
Also in: @mastra/express@1.5.12, @mastra/fastify@1.5.12, @mastra/hono@1.7.10, @mastra/playground-ui@56.0.0, @mastra/server@1.68.0
-
Fixed
agent.stream()cancellation in@mastra/client-js. Cancelling a returned stream now aborts the underlying HTTP request and stops pending client-tool executions and follow-up requests. Fixes #24271. (#24310)Added a per-call
abortSignaloption tostream(),streamUntilIdle(),resumeStream(),resumeStreamUntilIdle(),approveToolCall(),declineToolCall(),streamLegacy(),generate()andgenerateLegacy(). It is merged with the client-wideabortSignal, and aborted requests are not retried.const controller = new AbortController(); const response = await agent.stream('Hello', { abortSignal: controller.signal }); // later controller.abort();
-
MCPTool.execute()is typed as the REST route's response ({ result }or{ status: 'suspended', suspendPayload, resumeSchema }) and acceptsresumeDataandsuspendPayloadso a suspended tool on a 2026-07-28 MCP server can be continued. (#23875)const tool = client.getMcpServerTool('returns', 'createReturn'); const first = await tool.execute({ data: { orderId: 'ord_1' } }); if ('status' in first && first.status === 'suspended') { const done = await tool.execute({ data: { orderId: 'ord_1' }, resumeData: { confirmed: true }, suspendPayload: first.suspendPayload, }); }
-
Added an optional
notScorablefield on experiment item score results so clients can tell a skipped run apart from a scorer error. (#24378)for (const score of item.scores) { if (score.notScorable) { // skipped — score and error are null } else if (score.error) { // scorer failed } else { // score.score is a number } }
Also in: @mastra/server@1.68.0
-
GetWorkflowRunByIdResponse.serializedStepGraphis typed as the coreSerializedStepFlowEntry[], likeGetWorkflowResponse.stepGraph, instead of the generated route shape. (#24030) -
Clients can now send the full
generateTitleconfiguration with a memory config:minMessages(minimum thread messages before a title is generated),emitEvent(stream the generated title as a transientdata-thread-titlechunk beforefinish), and an optionalmodel(defaults to the agent's model). (#24247)const agent = client.getAgent('assistant'); const response = await agent.stream('Plan my trip to Kyoto', { memory: { thread: 'thread-1', resource: 'user-1', options: { generateTitle: { emitEvent: true, minMessages: 2 }, }, }, }); await response.processDataStream({ onChunk: async chunk => { if (chunk.type === 'data-thread-title') { console.log(chunk.data.threadId, chunk.data.title); } }, });
-
Fix
getA2AV1()to send PascalCase A2A v1 JSON-RPC method names for message and task operations, enabling interoperability with v1-compliant servers. The v0.3 client is unchanged. (#24262) -
Added `orderBy` to `listDatasets`, `listDatasetItems`, `listExperiments`, `listDatasetExperiments` and `listDatasetExperimentResults`. (#24567)
```ts
await client.listDatasets({ orderBy: { field: "updatedAt", direction: "DESC" } });
```
@mastra/cloudflare-sandbox@0.5.0
Minor Changes
-
Added bucket mounts to
CloudflareSandboxso files can survive container sleep. Cloudflare stops an idle container aftersleepAfter, and the next command starts fresh, so/workspacewas silently lost between agent turns. The sandbox now implements the Workspacemountshook: anyS3Filesystem(including Cloudflare R2) listed undermountsis mounted through the bridge onstart(). A slept container boots without its mounts, so the sandbox detects and re-mounts any dropped paths before the next filesystem operation, keeping mounted data durable across sleep. (#24100)const workspace = new Workspace({ sandbox: new CloudflareSandbox({ baseUrl, apiToken }), mounts: { '/workspace/data': new S3Filesystem({ bucket: 'agent-data', region: 'auto', endpoint, accessKeyId, secretAccessKey, }), }, });
The default instructions now tell the model that
/workspaceis scratch space that does not survive between commands and list the mounted paths to use instead. Fixes #23706.
@mastra/code-sdk@1.8.0
Minor Changes
-
Added native background tool and delegated subagent support to Mastra Code sessions. (#19960)
Set
backgroundTools.enabledtotruein Mastra Code settings to make read-only workspace tools and the Alexandria expert background-eligible. Eligible tools remain foreground by default, and agents can opt individual calls intodeferredorawaitedexecution through the Core_background.dispositionoverride.Mastra Code factory results now expose
backgroundCompletionEvents, which publishes reconciledcompleted,failed, andcancelledevents for the originating resource and thread:const mastraCode = await createMastraCode(options); const unsubscribe = mastraCode.backgroundCompletionEvents.subscribe(event => { console.log(event.taskId, event.status); });
-
Zero-config MCP OAuth now identifies Mastra Code with its Client ID Metadata Document (
https://code.mastra.ai/.well-known/oauth-client/mastracode.json) instead of dynamic client registration, which@mastra/mcp2.x no longer performs. Servers whose authorization server accepts URL-based client IDs keep working with a bareurlentry; servers that require a registered client needoauth.clientIdinmcp.json. (#23876){ "mcpServers": { "notes": { "url": "https://notes.example.com/mcp" }, "billing": { "url": "https://billing.example.com/mcp", "oauth": { "clientId": "mastra-code-billing", "scopes": ["invoices:read"] } } } } -
Added
context.getStorage()for plugins that start nested controllers. It supplies the host's storage, backend, and vector instances so plugins can avoid opening shared SQLite files through separate native libraries. (#24132)const sharedStorage = context.getStorage?.(); if (!sharedStorage) throw new Error('Shared storage requires a newer Mastra Code host.'); const nested = await bootLocalAgentController({ cwd: context.cwd, ...sharedStorage });
The host owns these instances. Nested controllers must not close them or run storage maintenance on them.
Patch Changes
-
Rotate OAuth accounts automatically on eligible request failures. When the active account is rate-limited, quota-exhausted, or fails authentication after one forced token refresh, Mastra Code activates the next account in the pool and retries the request. Server errors and outages exhaust the transient retry budget first and then surface without another account being activated. Every switch appears in the transcript as a one-line notice and is persisted in thread history. (#23711)
Add accounts through the TUI —
/loginon an already-connected provider offers Add another account:/login → Add another account # completes OAuth, returns to the manager → (submenu) Set as active # optional; rotation happens on demand anywayNo configuration is needed beyond having two or more accounts for a provider; rotation walks the provider's accounts in insertion order, starting from the account the pool is currently on.
-
Fixed a zod version mismatch that could make tool and workflow schemas built by these packages incompatible with schemas from @mastra/core. (#24428)
Also in: @mastra/factory@0.16.0
-
Fixed low-priority fire-and-forget signals being reported as failed after they were queued for notification summaries. Summarized reply requests now persist the notification but return a clear error instead of falsely recording a reply obligation, so callers can send a new request at a priority that routes directly. Policy-discarded signals remain retryable. (#23696)
-
Fixed Mastra Code exporting its own traces into whichever Mastra project's
.envit was launched from, and crashing on startup when that project'sMASTRA_PROJECT_IDwas not a valid id. Mastra Code cloud observability now only reads its own environment variables (or the/observability connectsettings) and ignoresMASTRA_PLATFORM_OBSERVABILITY_ENDPOINTfrom the environment. (#24627)If you configured Mastra Code cloud observability through environment variables, rename them:
# before export MASTRA_CLOUD_ACCESS_TOKEN=... export MASTRA_PROJECT_ID=... # after export MASTRACODE_CLOUD_ACCESS_TOKEN=... export MASTRACODE_PROJECT_ID=...
-
Added multiple OAuth accounts per provider. Sign in with as many accounts per provider as you like. Running
/loginon an already-connected provider opens an account manager. There you can add another account, switch the active one, re-authenticate, or remove accounts. Added accounts stay inactive until you select one. Accounts carry labels — email for ChatGPT/xAI, GitHub login for Copilot. Credentials keep the sameauth.jsonslot format, so existing setups are untouched. (#23709)Account ids are assigned once, when an account is first registered, and no longer derived from the refresh token — so refreshing a token or re-authenticating an account no longer changes which account it is, and adding an account you already have updates it instead of registering a duplicate for the same subscription. Existing
auth.jsonfiles are read as-is; registered accounts additionally learn their provider's stable account identifier on next load, where the provider exposes one.Add and select accounts from
/login:/login # choose a provider you are already signed in to Add another account # sign in again; the new account is registered but inactive Set as active # make an added account the one new requests use -
Fixed the goal box appearing twice in the transcript. (#24342)
Starting or resuming a goal drew the goal box locally and the agent also echoed the same reminder back into the live transcript, so the goal was shown twice in a row. The box is now rendered once, from the echoed reminder.
The reminder keeps the goal's attempt budget and judge model, so the box shows both instead of dropping the attempt count. The same reminder is also deduplicated, so a repeated signal cannot render the box a second time.
-
Improved cross-agent discovery with live thread titles and passive sender attribution for individually delivered peer signals. (#23696)
Inbound peer signals now expose the stable sender id as
sourcePeerId, including fire-and-forget messages, whilereturnPeerIdis included only when a reply is required. Low-priority signals may first appear as a count-only notification summary and expose their full attribution when opened from the notification inbox:<notification sourcePeerId="code-agent:resource-1:thread-1" expectsReply="false"> Peer work completed. </notification>
-
Added fallback model packs and pack-specific subscription routing. In
/models, you can configure a fallback chain and choose the OAuth account each model in a pack uses. A selected account is used exclusively for that model: if it fails, the request moves to the pack's fallback chain instead of another account, so a heavy model cannot spend a second subscription's quota.Automatickeeps rotating through the provider's accounts in insertion order, starting from the account the pool is currently on, and a fallback pack applies its own routing. Pack hops remain visible in the transcript and persist when you reopen the thread. (#23725)Configure both from
/models— select a pack, then:/models → Set fallback… # choose the pack to hop to when this pool is exhausted → Set subscription routing… # per model: pin one account, or AutomaticCustom packs can also define an observational memory model. When set, the OM observer and reflector resolve from the active pack and its fallback chain — so OM keeps working when a pack's provider is down. Packs without an OM model keep using your standalone OM configuration, and explicit
/omoverrides still win.Both settings live in
settings.jsonif you prefer to edit them directly:{ "customModelPacks": [ { "name": "Daily", "models": { "build": "anthropic/claude-sonnet-4-6", "memory": "anthropic/claude-haiku-4-5" } } ], "models": { "packFallbacks": { "custom:Daily": "anthropic" }, "packAccountPreferences": { "custom:Daily": { "anthropic/claude-sonnet-4-6": "anthropic:a1b2c3d4" } } } } -
Fixed cross-agent signals so a peer stays reachable after a session moves to another conversation. A session advertised only its active thread, so peers that had saved an earlier thread could no longer send messages to it after the user started a new thread or switched threads. Every thread a session has loaded now stays claimed, and a wake sent to a saved thread runs on that thread instead of the session's current one. Peer listings no longer show a session's own earlier threads as discoverable agents. (#24510)
@mastra/codemod@1.1.3
Patch Changes
-
Fixed codemod previews and result summaries so they appear in the terminal. (#24530)
-
Fixed codemods so projects under hidden parent directories are processed while hidden directories inside the target remain excluded. (#24528)
-
Fixed v0 RuntimeContext imports from
@mastra/core/diso they migrate to RequestContext. (#24472) -
Fixed unknown codemod names so they fail clearly before processing files. (#24470)
-
Fixed verbose codemod runs so they pass a valid diagnostic level to jscodeshift. (#24251)
@mastra/connect@0.2.0
Minor Changes
-
Added the generated Linear provider with 46 tools for issues, projects, cycles, teams, users, workflow states, comments, attachments, relations, and labels. (#23527)
connect()now automatically exposes the Linear toolset when the project has a matchinglinearconnection. SetMASTRA_LINEAR_CONNECTION_IDor passintegrations.linear.connectionIdwhen the project has multiple Linear connections. -
Added automatic discovery of MCP-backed integrations from the Mastra Platform catalog. Connected MCP providers require no checked-in provider registration:
connect()discovers their tools through Platform, keeps provider credentials outside the application process, and preserves Platform proxy analytics. Discovered MCP tools require tool approval unless the application lists them inautoApproveTools. (#23996)import { connect } from '@mastra/connect'; const tools = connect({ projectId: process.env.MASTRA_PROJECT_ID, client: { accessToken: process.env.MASTRA_PLATFORM_ACCESS_TOKEN }, integrations: { // An MCP-backed integration attached to the project; tools are discovered at runtime. neon: { autoApproveTools: ['neon_list_projects', 'neon_describe_project'] }, }, });
-
Added broad Resend and incident.io tool coverage backed by Platform connections. Resend covers email operations and account resources such as domains, templates, audiences, contacts, broadcasts, and webhooks. incident.io covers incident response, alerts, on-call data, teams, users, postmortems, and catalog reads. (#23631)
import { connect } from '@mastra/connect'; const tools = connect({ projectId: process.env.MASTRA_PROJECT_ID, client: { accessToken: process.env.MASTRA_PLATFORM_ACCESS_TOKEN }, integrations: { resend: { allowTools: ['resend_send_email', 'resend_get_email'] }, 'incident-io': { allowTools: ['incident_io_list_incidents'] }, }, });
-
Added Snowflake tools backed by Platform connections using the OAuth snowflake integration. Agents can run SQL statements (with async statement polling and cancellation) and browse warehouses, databases, schemas, tables, columns, views, stages, streams, tasks, roles, and users. The execute-statement tool runs any SQL the connection's Snowflake role permits, so scope the connected user to least privilege or restrict the toolset with allowTools. (#24066)
import { connect } from '@mastra/connect'; const tools = connect({ projectId: process.env.MASTRA_PROJECT_ID, client: { accessToken: process.env.MASTRA_PLATFORM_ACCESS_TOKEN }, integrations: { snowflake: { allowTools: ['snowflake_execute_statement', 'snowflake_list_tables'] }, }, });
-
Add a generated Jira provider with 37 tools covering issue lifecycle (create, update, transition, link), comments, worklogs, watchers, projects, users, and metadata lookups. The tool runtime now supports the template
updateMetadatahelper by caching derived connection facts (such as the Atlassian cloud ID) in memory for the lifetime of a toolset, so tools skip repeat discovery round-trips. (#24066) -
Added built-in Clerk and WorkOS providers with 42 and 40 tools respectively for identity, organization, directory, connection, invitation, membership, and domain administration. Provider tools can now forward repeated query parameters for multi-value filters. (#23527)
-
Added built-in Anthropic, Notion, OpenAI, and Supabase providers. Generated tools can now read safe connection configuration and metadata through the platform proxy, enabling providers with connection-specific API hosts. (#23527)
Patch Changes
-
Fixed OpenAI image generation tools to send base64 images to models as multimodal image content instead of JSON text, preventing generated images from consuming the text context window. Updated the generated OpenAI tools to the current API parameters. (#23527)
-
Removed warnings for providers that do not have project connections. Connect now silently skips unavailable providers while continuing to report actionable connection problems. (#23527)
-
Raise the
@mastra/corepeer floor to>=1.68.0-0to match@mastra/mcp2.x, which@mastra/connectuses for catalog MCP discovery. (#23876) -
Validate
baseUrlOverrideon the connection-proxy client before it is forwarded as a request header. Only absolute HTTPS URLs without embedded credentials are accepted; unparseable values, non-HTTPS schemes, and userinfo-bearing URLs are rejected withinvalid_optionsso a compromised connection config cannot redirect authenticated proxy traffic to an unintended origin. (#23527)
@mastra/daytona@0.11.1
Patch Changes
- Fixed Daytona sandbox destruction so temporary deletion failures are reported and can be retried. (#24449)
@mastra/deployer@1.68.0
Patch Changes
-
Fixed deploy builds to stop when a workspace package import cannot be bundled. Add the package as a direct dependency or correct the workspace package configuration. (#24210)
-
Fixed builds to reject unresolved subpath imports from externalized workspace packages instead of producing bundles that fail at runtime. (#24450)
-
Fixed builds that use dependency subpath imports when the package exposes a nested module package.json. Fixes #12535. (#24137)
-
Fixed builds with externals so workspace subpath imports used by other workspace packages are compiled instead of failing at runtime. Fixes #22851. (#24131)
-
Fixed build output manifests including dependencies from inactive NODE_ENV branches. (#24385)
-
Fixed build dependency resolution so bundled output stays consistent when a project is built from its app directory or from a monorepo root. (#24212)
-
Fixed builds with configured externals by preventing the analyzer from loading those dependencies. (#24549)
-
Fixed deployment builds to reuse and update the source package-manager lockfile while installing dependencies. (#24226)
-
Fixed monorepo builds when packaging scoped workspace dependencies with ESM-only slug generation. (#24272)
@mastra/docker@0.9.0
Minor Changes
-
Add a
DockerTemplateAPI for preparing reusable, content-addressed baseline images for the local Docker sandbox, with the same builder and repo-template contract as the E2B and platform providers. (#24199)Prepare an environment once — a base image plus ordered setup commands, env vars, and package installs — then spawn multiple disposable
DockerSandboxes from it via the newtemplateoption. Each sandbox is a fresh container with its own writable layer over the shared read-only image, so their filesystems are independent. The baseline is produced by synthesizing aDockerfileand runningdocker build, so setup is baked into reproducible, cached layers.import { DockerSandbox, DockerTemplate, createDockerRepoTemplate } from '@mastra/docker'; const template = new DockerTemplate({ baseImage: 'node:22-slim' }) .aptInstall(['git', 'ca-certificates']) .runCmd('git clone --depth=1 https://example.com/repo /workspace/app') .setWorkdir('/workspace/app') .runCmd('npm ci'); // Builds the image on first start() and reuses it afterwards; the sandbox's // working directory follows the template's setWorkdir(). const a = new DockerSandbox({ template }); const b = new DockerSandbox({ template }); // Repository checkout pinned to the current head of a branch, rebuilt when it moves. const sandbox = new DockerSandbox({ template: createDockerRepoTemplate({ getRepositoryAccess: async () => ({ cloneUrl: 'https://github.com/acme/app.git' }), setupCommand: ['npm ci', 'npm run build'], }), });
- Immutable, chainable builder methods (
from,setWorkdir,setEnvs,runCmd,runWithSecrets,aptInstall,pipInstall,npmInstall) matching the E2B/platform builders. - Content-addressed image tag (
mastra-template:<hash>);build()is idempotent and reuses an existing image unless{ force: true }is passed. Build failures are returned as{ status: 'failed' }and retried on the next attempt. DockerSandbox({ template })accepts a template or an async template factory resolved once per container-creatingstart().- Build-time secrets via
runWithSecrets(command, { secrets, output }): the step runs in a throwaway build stage forked from the steps before it, secret values are passed by value ({ secrets }on the template orbuild()) and delivered through BuildKit secret mounts, and onlyoutputis copied into the image, so values never land in any layer, history entry, or build-cache metadata, nor in the template identity. createDockerRepoTemplate({ getRepositoryAccess, ref, setupCommand, buildEnv, workingDirectory })prepares a repository checkout and setup as a template factory, resolving the head ofrefon each sandbox start and pinning it into the identity. The credential fromgetRepositoryAccessis used only for the head lookup and the clone stage.
- Immutable, chainable builder methods (
-
Added AbortSignal cancellation for Docker template builds, repository template resolution, and lazy sandbox starts. Cancelling startup stops local template-preparation streams and sessions, rejects with
SandboxAbortErrorwhile preserving the signal's custom reason as the error cause, and leaves the template retryable. (#24451)const startController = new AbortController(); const start = sandbox.start({ abortSignal: startController.signal }); startController.abort(new Error('request cancelled')); try { await start; } catch (error) { if (!(error instanceof SandboxAbortError)) throw error; } const buildController = new AbortController(); const build = template.build({ abortSignal: buildController.signal }); buildController.abort(new Error('request cancelled')); try { await build; } catch (error) { if (!(error instanceof SandboxAbortError)) throw error; }
Patch Changes
-
Fixed Docker sandbox process kills keeping helper response streams open. (#24448)
-
Fixed commands that read stdin hanging until timeout (#24336)
Commands that read standard input without being given anything to read — a bare
cat, orgrep/rgwith no path argument — blocked until the command timeout expired. The exec no longer attaches stdin unless something will feed it, so these commands see end-of-input and exit immediately.processes.spawn()is unchanged: it still attaches stdin by default so long-running processes can be driven withsendStdin().
@mastra/dsql@1.5.0
Minor Changes
-
Added configurable age-based pruning for observability spans. (#23466)
import { DSQLStore } from '@mastra/dsql'; const storage = new DSQLStore({ id: 'dsql-storage', host: 'abc123.dsql.us-east-1.on.aws', retention: { observability: { spans: { maxAge: '30d', batchSize: 1_000 }, }, }, }); await storage.prune({ maxBatches: 10, maxRows: 10_000, pauseMs: 25 });
@mastra/duckdb@1.10.0
Minor Changes
-
Added list-compatible page pagination for advanced trace queries in DuckDB storage. (#24061)
const result = await client.queryTraces({ timeRange, pagination: { page: 0, perPage: 25 }, });
-
Added bounded trace-query field and value discovery for DuckDB observability storage. (#24075)
const observability = await storage.getStore('observability'); const fields = await observability?.getTraceQueryObservedFields(fieldsPlan); const values = await observability?.getTraceQueryValues(valuesPlan);
-
Added configurable age-based pruning for DuckDB observability spans, metrics, logs, scores, and feedback. (#23466)
Before
DuckDB observability data was retained until it was deleted explicitly.
After
const storage = new DuckDBStore({ path: 'mastra.duckdb', retention: { observability: { spans: { maxAge: '30d' }, logs: { maxAge: '7d' }, }, }, }); await storage.prune();
-
Added DuckDB support for handing numbered trace-query pages to delta polling. The initial page and polling watermark share a snapshot, and polls detect completed root writes. (#24329)
Numbered pages remain available without a polling cursor when the installed core version lacks trace-query delta support.
// Start with a numbered page. const page = await client.queryTraces({ timeRange, pagination: { page: 0, perPage: 100 } }); // Continue with delta polling. const delta = await client.queryTraces({ timeRange, mode: 'delta', after: page.deltaCursor });
Patch Changes
-
Fixed repeated score IDs in a single batch so the last accepted observability score remains current. (#24242)
-
Returned stable trace-query resource-limit errors when DuckDB reports memory exhaustion during discovery. (#24169)
@mastra/e2b@0.12.1
Patch Changes
-
Fixed commands that read stdin hanging until timeout (#24336)
Commands that read standard input without being given anything to read — a bare
cat, orgrep/rgwith no path argument — blocked until the command timeout expired. Commands run throughexecuteCommand()no longer keep stdin open, so these commands see end-of-input and exit immediately.processes.spawn()is unchanged: it still keeps stdin open so long-running processes can be driven withsendStdin(). -
Honor the configured
timeoutwhen reconnecting to or resuming an existing E2B sandbox. Previouslytimeoutwas only applied on sandbox creation; the reconnect and resume paths calledSandbox.connectwithouttimeoutMs, so the e2b SDK fell back to its 5-minute default. With the defaultlifecycle.onTimeout: 'pause', this meant a paused sandbox always came back with a 5-minute window regardless of the configuredtimeout. BothSandbox.connectpaths now forwardtimeoutMs, so a resumed sandbox gets at least the configured window. (#24636) -
Commands without an explicit timeout now use the sandbox timeout (5 minutes by default) instead of hitting E2B's 60 second connection deadline and failing with
[deadline_exceeded]. (#24484)
@mastra/editor@0.15.1
Patch Changes
-
ComposioToolProvider.listTools()now rejects with the original SDK error when the Composio catalog request fails (auth, rate limit, network, outage) instead of resolving a successful empty page. An empty result now reliably means the catalog returned no matching tools. (#24292) -
Fix
editor.agent.clearCache()leaving version-specific stored agents registered with Mastra. Agents hydrated viaversionId,versionNumber, or a status override skip the value cache but were still registered in the runtime registry, so a no-ID clear-all never evicted them. The Editor agent namespace now tracks the stored-agent IDs it registers and evicts any remaining ones during clear-all, while code-defined agents remain registered. (#24000) -
Derive inline workspace identity from a canonical, key-order-independent hash so semantically equivalent configs resolve to the same
inline-<hash>ID. Previously the ID was hashed from rawJSON.stringify, which preserves object insertion order, so reordered-key configs produced different IDs and created duplicate stored workspaces with unstable references. Array order and value differences remain significant. (#24009) -
Fixed conditional processor graphs running their fallback branch alongside a matching rule. A default branch (a condition with no rules) now runs only when no explicit rule matches. The internal pass-through runs only when no rule matches and no default exists. A fallback processor no longer mutates messages or causes side effects when an explicit condition already matched. (#24095)
-
Allow the editor to run alongside @mastra/mcp 2.x, whose hydrated servers use the core MCP v2 base. (#23876)
-
Fixed hydrating a stored processor graph so that graph nodes reusing the same processor no longer collapse into a single workflow step. Each node now keeps its own unique step identity, preventing configured nodes from overwriting each other and keeping parallel and conditional branch results distinguishable. (#24099)
@mastra/express@1.5.12
Patch Changes
-
Fixed denial-of-service and CRLF injection advisories by updating @fastify/busboy to 3.2.2. (#24027)
Also in: @mastra/fastify@1.5.12, @mastra/koa@1.7.12, @mastra/nestjs@0.2.27
@mastra/factory@0.16.0
Minor Changes
-
Brought incident.io follow-up intake to full parity with Linear and Jira. (#24319)
- Connections: in-app connect and reconnect for Platform-managed incident.io accounts, runtime discovery of multiple installations, and no more
MASTRA_INCIDENT_IO_CONNECTION_ID. - Auto-ingestion: observed follow-ups materialize as Work-board cards through configurable event rules (
followUpObserved,followUpClosed), with close events transitioning cards to done/canceled. - Agent tools: board runs get
incidentio_get_follow_upfor reading follow-up details. - Board UX: follow-up cards carry assignee, creator, labels, priority, and incident metadata, plus the same Investigate/Build actions and work-item menu as Linear cards.
- Routing: teams choose which Factory and board receive follow-ups; incidents stay unrouted.
import { IncidentioIntegration } from '@mastra/factory'; const incidentio = new IncidentioIntegration({ apiKey: process.env.INCIDENT_IO_API_KEY!, // Optionally override the default follow-up rules: rules: { followUpClosed: null }, // disable automatic close transitions });
- Connections: in-app connect and reconnect for Platform-managed incident.io accounts, runtime discovery of multiple installations, and no more
-
Added an explicit
deliverychoice to Factory worker guidance. (#24138)Use
delivery: "send"for immediate guidance ordelivery: "queue"to wait for the worker’s current run to complete. -
Added GitLab as a Factory source-control and work-intake provider for direct and Platform-managed deployments, with the same session, board, review, and notification behaviour that GitHub repositories get. Both credential modes can back Factory sessions with GitLab repositories. (#24604)
- Intake discovers GitLab projects, ingests issues with labels, label colours, assignees, weight-derived priority, and comment counts, reads discussions, adds comments, updates issue state, and routes selected projects to factories from the Settings UI. GitLab issue and GitHub issue routing are stored separately.
- Version control registers repositories, manages the merge request lifecycle, creates and edits merge request notes, manages diff-anchored review discussions, and adds or removes individual reviewers. Direct tokens and Platform connection credentials both provide authenticated clone and push for repository-backed sessions.
- Webhook ingress verifies
X-Gitlab-Tokenand routes supported issue, note, and merge request events into Factory rules. Unsupported events, including push events, are acknowledged without changing board state. Issue and merge request reconcilers cover missed terminal events and settle cards the same way the GitHub reconcilers do. - Sessions that open a merge request through
source_control_create_change_requestare subscribed to it automatically. New notes, closes, and merges wake the subscribed session as the user who created it, with a notification that links to the merge request.gitlab_subscribe_mrandgitlab_unsubscribe_mrmanage subscriptions by hand,gitlab_get_issuefetches a routed issue with its discussion, andGET /web/gitlab/subscriptionslists a thread's subscriptions for the UI. - Review board runs use the
factory-gitlab-reviewandfactory-gitlab-rereviewskills, check out the merge request head, and re-review when new commits arrive. The UI names merge requests as!n, shows merged and closed states, and offers GitLab in onboarding, repository settings, intake routing, and board empty states.
GitLab approvals are exposed as an approval snapshot and are used for submitted
approvereviews; submittedcommentreviews become merge request notes. Individual listed approvals and comment reviews can be fetched through synthetic review IDs. GitLab has no equivalent for mutable pending reviews, request-changes reviews, approval dismissal, team review requests, or synchronous rebase-and-merge, so those operations fail explicitly with a not-supported response.import { GitLabIntegration } from '@mastra/factory/integrations/gitlab/integration'; const gitlab = new GitLabIntegration({ baseUrl: 'https://gitlab.example.com', accessToken: process.env.GITLAB_ACCESS_TOKEN!, accessTokenType: 'group', // Or 'personal'. webhookSecret: process.env.GITLAB_WEBHOOK_SECRET, });
Direct mode reads the same values from
GITLAB_ACCESS_TOKEN,GITLAB_ACCESS_TOKEN_TYPE,GITLAB_BASE_URL, andGITLAB_WEBHOOK_SECRETwhen constructor options are omitted. Personal and Group Access Tokens are both supported; useapiandwrite_repositoryscopes.GITLAB_BASE_URLmust use HTTPS except for loopback development instances.When Platform credentials are configured, Factory discovers active GitLab connections for the organization, and
MASTRA_GITLAB_CONNECTION_IDoptionally pins one of them. Requests go through/v2/connections/{connectionId}/proxy, andMASTRA_GITLAB_WEBHOOK_SECRETverifies webhooks. Explicit direct credentials take precedence. Platform-managed clone and push resolve a fresh repository credential for the selected connection; the connection selector itself is never used as a Git token. -
Added Linear team intake sources so Factory boards can ingest active projectless issues while preserving project precedence for overlapping selections. (#23929)
Select a whole Linear team under Settings, Intake, or send the team source id directly. Team source ids come from
GET /web/linear/teams; the Platform-backed integration issues opaque ids, the self-managed integration useslinear-team:<teamId>.await fetch('/web/intake/config', { method: 'PUT', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ linear: { enabled: true, sourceIds: ['linear-team:team-1', 'project-1'] }, }), });
A team source syncs active issues in the triage, backlog, unstarted, and started states, including issues without a project. When a project and its team are both selected, the project selection takes precedence for that project's issues.
Rerouting a Linear issue no longer creates a duplicate card. The Factory that already has the card keeps it, and the card's details stay available there. A new card appears on the newly routed Factory only after the existing card is finished.
-
Added a Jira Cloud intake integration for the Software Factory with full Linear-equivalent behavior, supporting both direct credentials and Platform-managed connections. (#20579)
Direct mode: set
JIRA_BASE_URL,JIRA_EMAIL, andJIRA_API_TOKENto use a deployment-global Atlassian API token — no OAuth app setup, intended for self-hosted/single-tenant deployments. Platform mode: withMASTRA_PLATFORM_ACCESS_TOKENorMASTRA_PLATFORM_SECRET_KEYconfigured, Factory automatically discovers visible Platform Jira connections (multiple sites supported) and proxies Jira requests through the Platform integrations service; an explicitly configuredJiraIntegrationtakes precedence.Factory settings and onboarding connect Jira accounts in-app, select Jira projects as intake sources, and route each project to a Factory board. Observed issues on routed projects materialize automatically as work items, closed issues transition their linked card to done or canceled, and both Jira integrations accept
rulesoverrides for theissueObservedandissueClosedevents. A background reconciliation worker keeps imported work items fresh (MASTRACODE_JIRA_RECONCILE_ENABLED,MASTRACODE_JIRA_RECONCILE_INTERVAL_MS). Work cards preserve Jira descriptions, labels, reporters, assignees, priority, project, site, state, and timestamps, appear in the board's teammate filters, and offer the same investigate and build actions as Linear issues. Agents getjira_get_issueandjira_create_commenttools, including on automated board runs.
Patch Changes
-
Fixed GitHub and Linear intake selections being personal: which repositories and Linear projects feed a Factory board is now one organization-wide setting, so every member sees the same intake instead of an empty board until they enable the sources themselves. Existing per-member selections are merged into the shared one on the next start (a source stays selected if any member was syncing it), and the Intake settings now carry the Org-wide badge. (#23982)
-
Fixed Factory plan handoffs when Auto-approve plans is off. (#24001)
Plan agents now leave work items in Planning until a maintainer approves the plan. Factory does not queue a build before that approval.
Preapproved plans and projects with Auto-approve plans enabled continue to advance automatically. Arming an autonomous run does not approve a plan.
Fixes #23742.
-
Fix
prepareRunStartreplaying a dead run binding after abort recovery. When a stage was re-entered following an abort, the replay guard matched the prior pending-start row by kickoff key alone and returned its original (already revoked) binding, so the re-entry re-bound the dead session and never used the freshly minted one. The replay branch now honors a pending-start row only when its binding is still active; a revoked or missing binding is discarded so a fresh binding is minted instead. (#24097) -
Fixed synchronous
onAcceptedhook failures rejecting an already-committed Factory transition and skipping itsstage_movedaudit record. Synchronous throws are now isolated and logged the same way as asynchronous rejections. (#24304) -
Fixed Factory session threads keeping their initial work-item title forever. Those titles are derived from the work item rather than typed by a user, so they no longer opt out of automatic title updates. (#23791)
-
Fixed integration feed publishers so an integration that implements
feedPublisher()withoutchannels()now receives work-item comment feed events. Previously the publisher was only wired for integrations that also provided a chat channel, so non-channel feed mirrors (webhooks, issue trackers) were silently never called. (#23999) -
Fixed git credentials appearing in command lines and remote URLs inside session sandboxes. Clone, fetch, push and pull request creation now receive the token through the process environment only. Branch names git would refuse (
a..b,x.lock, a trailing/) are now rejected when saved instead of failing later at checkout. (#24604) -
Fixed Platform-managed GitLab connections created with a personal access token not being discovered. Factory now lists connections from every GitLab credential flow the Platform offers, and the documentation no longer presents
MASTRA_GITLAB_CONNECTION_IDas required; it only pins discovery to one connection. (#24630) -
Platform-connected GitLab deployments now receive issue, note, merge request and push events by polling the Platform event log, so they no longer need a direct project webhook to Factory or a shared
MASTRA_GITLAB_WEBHOOK_SECRET. Polling is on by default and controlled withMASTRA_PLATFORM_GITLAB_POLLING_ENABLEDandMASTRA_PLATFORM_GITLAB_POLLING_INTERVAL_MS. (#24631)A Platform-connected deployment needs no webhook configuration; polling starts with the integration:
import { PlatformGitLabIntegration } from '@mastra/factory/integrations/platform/gitlab/integration'; // Discovers every active Platform GitLab connection and polls its event log. const gitlab = new PlatformGitLabIntegration(); // Tune or disable polling per deployment instead of through the environment. const quiet = new PlatformGitLabIntegration({ pollingIntervalMs: 60_000 }); const webhookOnly = new PlatformGitLabIntegration({ pollingEnabled: false });
-
Fixed the Factory keeping a review or work run executing after an external terminal transition revoked its binding. Terminal-stage cleanup now aborts the live run on each retired seat (leaving alone the seat that drove its own transition and any successor that took the session over), a resumed session whose binding was revoked on a settled item now surfaces a clear retirement error instead of silently dropping its transition tool, and a settled card no longer retries its close-out to the attempt limit as a false blocked state. (#24005)
-
Fixed Factory sign-in button contrast when the app uses a light theme. (#24006)
-
Fixed Factory re-entry re-appending the entire skill document on same-stage re-runs. This happened, for example, when a review re-triggered as a pull request was updated. The session now continues with a compact message that references the already-active skill and carries only the fresh context. This avoids re-pasting the full skill body every time and sharply cuts redundant prompt-cache token usage. (#24084)
-
Fixed Factory skill dispatch sending a second kickoff into a binding whose previous run is still in flight. A new skill decision for a binding now waits for that binding's live run to end before delivering. This holds across dispatcher replicas: the dispatcher hosting a run records its ownership in the shared open-run ledger and heartbeats it, and a replica that sees a fresh claim from another owner retries later instead of starting a duplicate. A stale record left behind by a crashed run no longer blocks the binding. Delivery outcomes that could not be confirmed now fail with the stable codes
skill_delivery_ambiguousandrun_terminal_event_missinginstead ofunknown. (#23968) -
Fixed sessions woken by a pull request comment or close failing with
missing-user-contextorNo usable openai credential. Woken runs now execute as the user who subscribed, with that organization's credentials available. (#24604) -
Improved Factory PR reviews and re-reviews: (#24626)
- The reviewer writes its own design for the problem before opening the diff, then judges whether the PR's approach and scope are justified — not only whether the implementation works.
- Before every verdict it checks its own requested changes: why each belongs in this PR, what happens if the author follows them exactly, and whether each verification probe could actually detect the failure it claims to rule out.
- Requested changes in the posted review are written as direct, evidence-backed change requests with no optional tiers.
- The reviewer loads bundled per-category review guidance (behavior change, bug fix, public API, schema/storage, security, and others) matched to the change, before opening the diff and again as the change reveals further categories.
@mastra/github-signals@0.5.0
Minor Changes
-
Added authorization for GitHub App bot comments when the app is owned by the repository organization or by a user with authorized repository access. Explicitly ignored bots and bots whose app ownership cannot be resolved remain denied. (#24246)
import { GithubSignals } from '@mastra/github-signals'; const githubSignals = new GithubSignals({ authorizedPermissions: ['admin', 'maintain', 'write'], authorizedBots: ['coderabbitai[bot]', 'devin-ai-integration[bot]'], });
Bots not listed in
authorizedBotscan now trigger notifications when their GitHub App is owned by the repository organization or by a user with one of the configuredauthorizedPermissions.
Patch Changes
- Fixed PR syncing failing with a 401 when an expired GitHub token was exported in the environment. Each sync now resolves a fresh credential from the
ghCLI and uses it when one is available, so a staleGITHUB_TOKENorGH_TOKENis replaced instead of being sent to GitHub. Author permission and GitHub app owner lookups use the same credential. (8808c0e)
@mastra/hono@1.7.10
Patch Changes
-
Fixed custom upload routes waiting for body parsing when no fine-grained authorization provider is configured. (#24563)
-
Fixed a Hono security advisory (CVE-2026-84363 family) by raising the hono dependency and peer ranges to
^4.13.5. (#24308)Also in: @mastra/mcp@2.0.0, @mastra/next@0.2.26, @mastra/tanstack-start@0.2.26
@mastra/inngest@1.9.0
Minor Changes
- Added a
shouldPersistSnapshotoption tocreateInngestAgent()for API symmetry withcreateDurableAgent(). InngestAgent logs a warning and ignores it: Inngest's step memoization and replay own durability, so Mastra snapshots are only persisted forsuspendedruns (human-in-the-loop resume). (#23978)
Patch Changes
-
Fixed a crash risk in Inngest durable agents where failing to publish an error event to pubsub (for example when the realtime endpoint is unreachable) became an unhandled promise rejection instead of a logged warning. Error reporting is now best-effort: publish failures are caught and logged, matching how abort requests already behave. (#24125)
-
Fixed durable agent abort requests being ignored when the run executes on an Inngest worker. Calling abort() on a run in another process now stops generation and ends the stream with finishReason "abort". Fixes #22543. (#24368)
-
Fixed processor steps created with the Inngest adapter's createStep losing their saved state between calls. Processor state written in one phase (for example processInput) is now visible in later phases and in chained processor steps, matching the behavior of steps created with @mastra/core/workflows. Fixes #23671. (#24163)
-
Fixed completed Inngest workflow runs remaining in memory after resume. (#24267)
-
Fixed durable agents on Inngest never running output processors, persisting memory, or generating thread titles at the end of a turn. Finalization was wrapped in a nested Inngest step, which the Inngest protocol does not support — in HTTP/serve deployments the run hung and never emitted its finish event. Finish side effects now run directly inside the existing durable step boundary, matching the built-in durable engine. Fixes #23815 and #22450. (#24125)
@mastra/laminar@1.3.19
Patch Changes
- Exported
MCP_SERVER_REQUESTspans withSpanKind.SERVER. (#24150)
@mastra/langfuse@1.5.8
Patch Changes
-
Fixed exact OpenRouter generation costs in observability spans and Langfuse exports, including BYOK upstream charges. (#20336)
Also in: @mastra/observability@1.17.9
-
Fixed the Langfuse exporter sending each span's input and output twice. The payload was written to the observation's input and output fields and then repeated inside its metadata, roughly doubling what Langfuse ingests and stores for workflow, agent and processor spans. Traces look the same as before: the values still appear in the observation's input and output, only the duplicate copy in the metadata is gone. Model generation and tool call spans were never affected. Fixes #23955. (#23956)
@mastra/libsql@1.23.1
Patch Changes
-
Fixed unique-index update errors in the Factory storage adapters. Updates now throw
UniqueViolationError, the same error inserts already threw, so callers can handle a duplicate claim consistently. (#23929)Also in: @mastra/pg@1.26.0
-
Added storage-level filtering for
Agent.listSuspendedRuns()thread lookups. The thread id embedded in suspended run snapshots is now filtered inside SQLite instead of loading every suspended snapshot into the application. Part of #22627 (#24376) -
Dataset, dataset item, experiment and experiment result listings now honor the
orderByoption instead of always returning a fixed order. This requires@mastra/core1.68.0 or newer. (#24567)Also in: @mastra/mongodb@1.18.9, @mastra/mysql@0.10.0, @mastra/pg@1.26.0, @mastra/spanner@1.8.0
-
Fixed
prune()to use one cutoff instant across all retained tables so rows near the retention boundary are handled consistently. (#23466)Also in: @mastra/pg@1.26.0
@mastra/mcp@2.0.0
Major Changes
-
Rebuilt
@mastra/mcpon the MCP 2026-07-28 revision. Servers serve that revision only, and every request is self-contained: there is noinitializehandshake, session header,ping, or standalone HTTP+SSE transport. Streamable HTTP responses still stream as Server-Sent Events. Requires@mastra/core1.68 or newer. The full migration guide is at/reference/migrations/mcp-v2. (#23876)Migration. A tool that needs input from the caller no longer awaits
context.mcp.elicitation.sendRequest(). It callscontext.suspend(payload)and returns; the server answersinput_required, and when the caller replies the tool runs again withcontext.resumeDataandcontext.suspendPayload. On the client,mcp.elicitation.onRequest()becomes a per-serverinputRequestshandler.execute: async ({ orderId }, context) => { - const answer = await context.mcp!.elicitation.sendRequest({ message: 'Address?', requestedSchema }); - if (answer.action !== 'accept') return { confirmed: false }; - return { confirmed: await book(orderId, answer.content.address) }; + if (!context.resumeData) return context.suspend({ phase: 'address' }); + return { confirmed: await book(orderId, context.resumeData.address) }; },- mcp.elicitation.onRequest('returns', async params => askUser(params)); + const mcp = new MCPClient({ + servers: { returns: { url, inputRequests: async ({ key, params }) => askUser(key, params) } }, + });
import { createTool } from '@mastra/core/tools'; import { MCPClient, MCPServer } from '@mastra/mcp'; import { z } from 'zod'; const