Highlights
OAuth ToolProviders runtime for stored agents (connections + toolkit-scoped tool resolution)
Mastra now includes the v1 ToolProvider runtime plus server routes, editor wiring, and client SDK methods to manage OAuth-backed connections and let stored agents pin specific connections per toolkit at execution time (including per-author vs shared vs caller-supplied scopes).
New MySQL storage adapter
@mastra/mysql adds a first-party MySQL storage backend with broad domain coverage (memory, threads, workflows, observability, agents, etc.), including reliable fresh-DB initialization and index creation fixes.
New cloud sandbox and browser deployment options (Vercel MicroVM + Firecrawl Browser)
@mastra/vercel introduces VercelMicroVMSandbox (ephemeral Firecracker MicroVMs with persistent in-session FS, ports, and background processes), and @mastra/browser-firecrawl ships a Firecrawl-hosted Chrome sandbox while keeping the same @mastra/agent-browser tool surface.
Code Mode (experimental): TypeScript orchestration tool for agents
createCodeMode() lets agents generate and run a single TypeScript program to orchestrate multiple tool calls (batching with Promise.all, aggregations, real computation) while tools still execute host-side with validation and tracing.
Message-first + approval-first thread APIs (send/queue messages, subscription-native tool approval, fire-and-forget resumes)
New agent.sendMessage() / agent.queueMessage() and matching server routes enable higher-level “user message into thread” flows (vs low-level signals), tool approvals can now resume via the active thread subscription (with message queueing during approval), and workflows add resumeNoWait/resumeAsync() fire-and-forget semantics returning { runId } immediately.
Breaking Changes
- None called out in this changelog.
Changelog
@mastra/core@1.38.0
Minor Changes
-
Added
channels.threadContext.addSystemMessageto opt out of the built-in channel system message. By default,AgentChannelsinjects a short system message telling the agent which channel/platform a request came from (DM vs public, bot identity, etc.). SetaddSystemMessage: falseto skip it: (#17171)new Agent({ channels: { adapters: { slack: createSlackAdapter() }, threadContext: { addSystemMessage: false, }, }, });
-
Added
agent.sendMessage()andagent.queueMessage()APIs for sending user-authored input into agent threads. These are intended to be used withagent.subscribeToThread()and replace lower-levelagent.sendSignal()calls for regular user messages. (#17191)await agent.sendMessage('Continue with the latest user input', { resourceId, threadId }); await agent.queueMessage('Follow up after the active turn finishes', { resourceId, threadId }); await agent.sendMessage( { contents: [ { type: 'text', text: 'What is in this image?' }, { type: 'file', data: imageBase64, mediaType: 'image/png', filename: 'screenshot.png' }, ], }, { resourceId, threadId }, );
-
Support conditional, function-based tool approvals. (#17337)
- MCP tools that wrap a server-level
requireToolApprovalfunction are now honored end-to-end. The per-tool approval function was previously dropped when the agent converted MCP tools (it kept only the boolean flag), so conditional approval silently fell back to always-on.CoreToolBuildernow preserves aneedsApprovalFnattached directly to a tool instance. - The global
requireToolApprovaloption onagent.stream/agent.generatenow accepts a function in addition to a boolean. It is evaluated per tool call with the tool name, arguments, and request context, enabling policies such as regex allowlists on tool names. Returningtruerequires approval for that call;falseallows it. On error the call defaults to requiring approval. When a function policy is set, tool calls run sequentially so approval suspensions don't race. Durable agents and stored agents continue to accept only a boolean (a function degrades to requiring approval for every call, since their options must be serializable).
// Approve only tool calls whose name is not on an allowlist. const allowlist = /^(get|list|search)_/; await agent.generate('...', { requireToolApproval: ({ toolName }) => !allowlist.test(toolName), });
- Precedence is unchanged from before: a per-tool approval function (
createTool({ requireApproval: fn })or an MCP-derivedneedsApprovalFn) is authoritative for that tool and overrides the global setting, so a tool can still opt out of approval by returningfalseeven when the global option is on. The only new behavior is that the global option may now be a function in addition to a boolean. - The previously implicit, runtime-attached per-tool approval predicate is now a typed contract:
@mastra/coreexportsNeedsApprovalFnand declares the optionalneedsApprovalFnproperty on theToolclass. The MCP client and the agent runtime now share this typed contract instead of reaching throughany. This is additive — no public API changes.
- MCP tools that wrap a server-level
-
Added harness events for session lifecycle updates, mode changes, model changes, and cloned threads. (#17290)
Users can now subscribe to harness events to observe harness activity.
Example
const unsubscribe = harness.subscribe(event => { console.log(event.id, event.type); });
-
Added agent override support to the agent and editor APIs. (#17227)
Code-defined agents can now declare which fields Studio may edit with the
editoroption:new Agent({ name: 'Weather Agent', model, editor: { instructions: true, tools: { description: true }, }, });
The editor applies stored overrides only for fields the
editorconfig owns, so locked fields keep their code-defined values. Per-agenteditor: falselocks an agent entirely.MastraEditoraccepts asourcesetting that picks the editing experience:new MastraEditor({ source: 'code' });
source: 'code'— the editor auto-wires aFilesystemStore(defaulting to./mastra/editor/, overridable withcodePath) when no editor storage is supplied, and persists overrides as deterministic per-agent JSON files.source: 'db'(default) — keeps the existing storage-backed flow against whatever storage the project has configured.
-
Added the
tool_provider_connectionsstorage domain. Stored agents can now persist per-agent ToolProvider config that round-trips on read/write/create. Runtime connection resolution (per-author, shared, caller-supplied) ships in a follow-up PR. (#17247)What you can do
- Pin a connection on a stored agent's config and have it round-trip on read/write/create.
- Persist multiple connections per toolkit so a follow-up runtime PR can fan-out to the right one at execution time.
Example
import { LibSQLStore } from '@mastra/libsql'; const storage = new LibSQLStore({ url: process.env.DATABASE_URL }); // Persist an OAuth connection that an agent can pin later await storage.toolProviders.upsertConnection({ authorId: 'user-123', providerId: 'composio', connectionId: 'auth_abc', toolkit: 'gmail', label: 'Work inbox', scope: 'per-author', }); // List a user's own connections (admin can omit authorId to list across users) const { items } = await storage.toolProviders.listConnectionsByAuthor({ authorId: 'user-123', providerId: 'composio', });
Additive — existing stored agents continue to work unchanged. The runtime that consumes this domain ships in a follow-up PR.
PR 1 of 3 split from #17224.
-
Added opt-in MCP server instructions forwarding into agent system prompts. (#17155)
When an MCP server advertises instructions during initialization, you can now forward that guidance into the system prompt of agents that use the server's tools. This is opt-in — set
forwardInstructions: trueper server to enable it. Forwarded instructions are injected into the agent's system prompt, so only enable this for servers you trust.const mcp = new MCPClient({ servers: { db: { url: new URL('http://localhost:4111/mcp'), forwardInstructions: true, // opt in; defaults to false instructionsMaxLength: 512, // max chars forwarded per server }, }, }); const agent = new Agent({ id: 'db-agent', name: 'DB Agent', instructions: 'Help with database changes.', model, tools: await mcp.listTools(), });
You can always inspect cached instructions without forwarding them:
const instructions = mcp.getServerInstructions(); // => { db: 'Always validate before migrating.', other: undefined }
-
Add fire-and-forget workflow resume that returns immediately with
{ runId }without awaiting the run output. (#17230)For
@mastra/inngest, this skips thegetRunOutput()polling that previously raced a realtime subscription against the Inngest runs API and could surface spurious 404s even though the durable workflow was running fine.Run.resumeAsync()added to core: dispatches the resume in the background and returns{ runId }immediately. Engines that poll for results (Inngest) override it to skip polling entirely.InngestRun.resumeAsync()sends the resume event and returns{ runId }, skipping polling. Send-time failures (bad payload, event send failure) still reject synchronously and roll back the snapshot.- New
POST /workflows/:workflowId/resume-no-waitandPOST /agent-builder/:actionId/resume-no-waitroutes return{ runId }immediately. - New client SDK
run.resumeNoWait()resolves with{ runId }.
The existing
resumeAsync()client/server surface is unchanged and still resolves with the full workflow result, so there is no breaking change.resumeNoWaitis intentionally additive in v1. In Mastra v2 the fire-and-forget behavior is planned to become the default behavior ofresumeAsync()(mirroringstart/resumesemantics), at which pointresumeNoWaitand theresume-no-waitroutes will be removed. The code paths carryTODO(v2)comments documenting this consolidation. -
Added experimental Code Mode for agents.
createCodeModereturns anexecute_typescripttool plus generated instructions that let an agent write one TypeScript program to orchestrate your tools (batch withPromise.all, aggregate, and do math in a real runtime) instead of calling tools one at a time. Tools still run on the host with full validation and tracing; only the orchestration code runs in a workspace sandbox. (#17324)import { createCodeMode, createTool } from '@mastra/core/tools'; const { tool, instructions } = createCodeMode({ tools: { getTopProducts, getProductRatings }, }); const agent = new Agent({ instructions: ['You are a helpful assistant.', instructions], tools: { execute_typescript: tool }, });
-
Add per-entity file persistence and per-entity git history to
FilesystemVersionedHelpers. (#17225)FilesystemVersionedHelpersnow accepts three optional hooks that let a storage
domain split a published entity across many per-entity JSON files (e.g.
agents/<id>.json) instead of one shared map file:perEntityFilesDir— directory (under the FilesystemDB root) for per-entity files.shouldPersistToPerEntityFile(entity)— decide per published entity whether
to write its snapshot to a per-entity file.perEntitySnapshotFilter(snapshot, entity)— filter the snapshot before
writing it to the per-entity file (e.g. drop fields the user does not own).
When configured, the helper:
- Reads per-entity files on hydrate (alongside the shared map file).
- Writes published snapshots to per-entity files with stable alphabetical key
ordering for friendly diffs. - Walks per-entity file git history and surfaces each commit as a read-only
version inlistVersions(in addition to the existing shared-file git
history). - Skips writing an empty shared map file when every published entity is
persisted to per-entity files, so a code-only project does not end up with an
empty stub committed to git.
Also adds
FilesystemDB.listDomainFiles,domainFileExists, and
removeDomainFilehelpers, and broadensGitHistory.getFileAtCommitto be
generic so callers can request a per-entity snapshot type rather than the
shared-map shape.Example — configure a domain to persist each entity to its own file:
import { FilesystemDB, FilesystemVersionedHelpers } from '@mastra/core/storage'; const db = new FilesystemDB('./mastra/editor'); // Entities that should be persisted as their own files. const codeModeEntityIds = new Set(['support-bot']); const agents = new FilesystemVersionedHelpers({ db, entitiesFile: 'agents.json', parentIdField: 'agentId', name: 'agents', versionMetadataFields: ['id', 'agentId', 'versionNumber', 'createdAt'], // New per-entity hooks: perEntityFilesDir: 'agents', // Decide per entity whether it gets its own file (vs. the shared agents.json). shouldPersistToPerEntityFile: entity => codeModeEntityIds.has(entity.id), // Drop fields that should not live in the per-entity file. perEntitySnapshotFilter: snapshot => { const { model, ...userOwned } = snapshot; return userOwned; }, }); // Published snapshots are now written to ./mastra/editor/agents/<id>.json, // and each git commit to those files shows up as a read-only version. const versions = await agents.listVersions({ agentId: 'support-bot' }, 'agentId');
-
Added support for resolving an agent's
voiceper request. (#17345)You can now pass
voiceas a resolver, just likeinstructions,tools, andmodel. Mastra runs the resolver on eachgetVoice()call and returns a fresh, session-owned voice instance. This fixes concurrent realtime and speech-to-speech sessions on a single deployed agent, where a shared voice instance previously let one session overwrite another session's WebSocket, tools, and instructions.A static
voicekeeps its existing shared behavior, so this change is backward compatible.Before
const agent = new Agent({ name: 'support-line', voice: new GeminiLiveVoice({ apiKey: KEY }), // shared across every session });
After
const agent = new Agent({ name: 'support-line', voice: ({ requestContext }) => new GeminiLiveVoice({ apiKey: requestContext.get('apiKey') }), }); const voice = await agent.getVoice({ requestContext }); // owns its own ws/tools/instructions await voice.connect();
The caller owns the lifecycle of a resolver instance and should call
disconnect()when the session ends. Theagent.voicegetter throws whenvoiceis a resolver because it has no request context; useagent.getVoice({ requestContext })instead. -
Workflows that suspend during dataset experiments now resume automatically. Provide resume data in your dataset items and the workflow will continue execution through multiple suspend/resume cycles. (#17378)
For multi-step workflows, use
resumeStepskeyed by step ID:const item = { input: { prompt: 'Draft a blog post' }, resumeSteps: { 'approval-step': { approved: true } }, };
For single-step workflows, use flat
resumeData:const item = { input: { prompt: 'Draft a blog post' }, resumeData: { approved: true }, };
Storage-backed items can use
metadata.resumeStepsormetadata.resumeDataas fallback. When no resume data is provided, the suspend payload is returned as output with guidance on how to add it. (#15382) -
Added request-aware filtering for ToolSearchProcessor search, load, and active tools. The filter hook receives the resolved tool ID as
toolName. (#16088)new ToolSearchProcessor({ tools, filter: ({ toolName, requestContext }) => { const plan = requestContext?.get('plan'); return plan === 'pro' || !toolName.startsWith('premium_'); }, });
-
Added
channels.resolveResourceIdto control whichresourceIdowns a channel thread's memory, separately from who sent the message. Useful for SSO apps that want a user's memory shared across web and a Feishu/Lark DM, or group chats scoped to the conversation instead of the sender. Only affects newly-created threads; return the provided default to keep current behavior. (#17471)new Agent({ // ... channels: { adapters: { slack: createSlackAdapter() }, resolveResourceId: async ({ thread, message }) => { if (thread.isDM) return resolveSsoUserId(message); // shared with web return thread.channelId; // group owns the memory }, }, });
-
Added the
disableInitoption to theMastraVectorbase class. When set totrue, vector stores skip creating schemas, extensions, tables, and indexes at application startup. This matches the existingdisableInitbehavior on storage adapters and is useful for deployments where schemas and indexes are created ahead of time by a privileged database role, while the application runs with a least-privilege role. (#17272)Usage
const vector = new PgVector({ id: 'vectors', connectionString: process.env.DATABASE_URL, disableInit: true, });
The
MASTRA_DISABLE_STORAGE_INITenvironment variable also disables vector init, so a single flag prevents both storage and vector stores from creating schemas, tables, or indexes at startup. -
Storage adapters can now receive a narrow back-pointer to the Mastra instance (#17226)
viaMastraCompositeStore.__registerMastra. This mirrors the existing
registration pattern on agents, workflows, memory, scorers and processors,
and lets a storage domain look up agents and editor config without pulling
the full Mastra type into the storage layer (which would create a circular
import).The reference is cascaded automatically to any parent composites and owned
domain stores, and is wired both during Mastra construction and via
setStorage. The editor is registered after storage so editor-driven
storage overlays observe the assigned storage.A new
StorageMastraRefinterface exposes only the methods storage needs
today (getAgentById,getEditor).// Inside a domain store, read the registered reference after Mastra wires it up: class MyAgentsStore extends AgentsStorage { protected getEditorConfig(agentId: string) { // `this.mastra` is populated by MastraCompositeStore.__registerMastra, // which runs during Mastra construction and on setStorage(). const agent = this.mastra?.getAgentById?.(agentId); if (agent?.source !== 'code') return undefined; return agent.__getEditorConfig?.(); } }
-
Added the v1 ToolProvider runtime, server routes, client SDK methods, and editor wiring that power OAuth-backed integrations on stored agents. (#17248)
Stored agents can now pin OAuth connections per toolkit
A stored agent's config accepts a new
toolProvidersshape that tells the runtime which connection to bind for each toolkit at execution time. Connections can be scoped per-author, shared across an org, or supplied by the caller.{ toolProviders: { composio: { connections: { gmail: [{ kind: 'author', toolkit: 'gmail', connectionId: 'auth_abc', scope: 'per-author' }], }, tools: { GMAIL_FETCH_EMAILS: { toolkit: 'gmail' }, }, }, }, }
New client SDK surface for managing connections
import { MastraClient } from '@mastra/client-js'; const client = new MastraClient({ baseUrl: '…' }); const composio = client.toolProvider('composio'); const { items } = await composio.listConnections({ toolkit: 'gmail' }); await composio.disconnectConnection('auth_abc');
New
ToolProviderinterface for custom providersProviders implement a VNext surface (
listToolkitsVNext,listToolsVNext,resolveToolsVNext) plus the auth round-trip (authorize,getAuthStatus,listConnections,disconnectConnection,listConnectionFields,health). The Composio provider has been rewritten on this surface; the older catalog methods remain as@deprecatedshims for back-compat.Connections list responses use
page/perPagepagination, matching the rest of the server surface.Both stored agents (
editor.agent.getById(...)) and code-defined agents with stored overrides (editor.agent.applyStoredOverrides(...)) resolvetoolProvidersat request time, merging provider-resolved tools alongside code/registry/MCP/integration tools.Stored agents that don't set
toolProviderscontinue to work unchanged. The Studio/Builder UI ships separately.
Patch Changes
-
Improved per-message latency in channels by removing two awaited storage round-trips from the chat message dispatch path. (#17185)
-
Fixed Observational Memory and other tagged system context being lost when an agent uses Channels. Channel-specific context now adds itself alongside other processors' system messages instead of replacing them. (#17168)
-
Fixed output processors so they receive agent step lifecycle chunks during streaming. (#16687)
-
Fixed UnixSocketPubSub streaming so a slow or stuck subscriber no longer blocks active local streams or other subscribers. (#17302)
-
Added subscription-native tool approval APIs so approving or declining a tool call resumes through the active thread subscription instead of requiring a separate continuation stream. New messages are queued while a tool approval is waiting, preventing overlapping runs from duplicating approval requests. (#17311)
await agent.sendToolApproval({ resourceId: 'user-123', threadId: 'thread-123', toolCallId: 'tool-call-123', approved: true, });
-
Fixed
filterMessagesForPersistenceunconditionally trimming whitespace from text parts, which caused spaces between words to be lost when text parts were split by token boundaries in the streaming span-based persistence. The trim now only applies when working memory tags are actually stripped. (#17404) -
Fixed resumed agent observability spans so
agent.resumeStream()andagent.resumeGenerate()use the resume payload as theAGENT_RUNspan input instead of an empty array. (#17134)Resumed spans now also link back to the suspended trace when persisted tracing context is available, so human-in-the-loop approval flows show the decision payload and remain connected in tracing backends. Fixes #17075.
-
Fixed BatchPartsProcessor dropping the final stream part when a stopWhen condition stops the agent on a non-text part (such as a tool result). The processor batches text deltas and previously deferred the next non-text part to the following stream iteration; if the loop stopped on that part, it was lost. BatchPartsProcessor now returns the flushed batch and hands the non-text part back to the output processor runner, which re-drives it through the full output processor chain. As a result the final tool result always reaches the stream, and the flushed batch still passes through any downstream output processors (e.g. a moderation or PII processor configured after BatchPartsProcessor) instead of bypassing them. Fixes #17094. (#17342)
-
Fixed Convex workflow storage to save concurrent workflow updates atomically. (#16641)
-
Fixed processor-returned
systemMessageswiping tagged system messages owned by other processors (e.g. observational memory). Processorargs.systemMessagesnow exposes only the untagged system message bucket, so tagged messages owned by other processors are no longer round-tripped through the replacement API.MessageList.replaceAllSystemMessages()replaces only the untagged bucket and leaves tagged buckets intact. Final model input still receives both viamessageList.getAllSystemMessages(). (#16950) -
Moved shared voice primitives and route metadata into the new
@internal/voicepackage so voice providers no longer depend on@mastra/coreand server voice routes share the same route definitions. (#16725)@mastra/core/voicecontinues to re-export the voice APIs for backwards compatibility. -
Fixed
AgentExecutionOptions<undefined>(and its public/inner variants) incorrectly requiring astructuredOutputproperty. When the output type isundefinedornull— including a fully-nullish union such asundefined | null—structuredOutputis now correctly optional, regardless of thestrictNullCheckssetting. This is the shape produced byAgentConfig.defaultOptions, sodefaultOptions: { maxSteps: 50 }now type-checks without a spuriousstructuredOutputrequirement. Object output types still requirestructuredOutputas before. (#17131) -
Fixes a crash where OpenAI rejects resumed tool-approval requests with
AI_APICallError: Duplicate item found with id rs_*. (#17439)The same message stored in PostgreSQL's
jsonbcolumn (workflow snapshot) andtextcolumn (messages) could have different JSON key orders, causing the deduplication check to treat them as different messages and send the same reasoning item twice. Both representations now compare equal regardless of key order. -
Fixed a render crash when loading stored threads containing signal messages (such as system reminders). Non-user signal data parts are now merged onto the neighboring assistant message instead of becoming standalone system messages that break assistant-ui. (#17429)
-
Added Alibaba provider support for Qwen models. You can now use Qwen, DashScope, and other Alibaba models with automatic provider detection. (#17433)
Example usage:
import { Mastra } from '@mastra/core'; const mastra = new Mastra(); // Use any Alibaba variant - automatically detected const agent = mastra.getAgent('myAgent'); const result = await agent.generate({ model: '__GATEWAY_ALIBABA_MODEL__', messages: [{ role: 'user', content: 'Hello' }], });
Works with all Alibaba variants (alibaba, alibaba-cn, alibaba-coding-plan, etc.) and future variants like alibaba-coding-plan-cn-v2.
-
Fixed observational memory replaying previously observed assistant responses during reprocessing, so past assistant messages no longer reappear in later turns. (#17338)
-
Use
queueMessage()for Harness follow-up scheduling while preserving queued follow-up display state. (#17191) -
Asset download errors now include the failing URL so callers can identify which media link broke and recover from it (e.g. drop the dead part on retry). The URL appears redacted (query string and fragment stripped) in the error message and in full on
error.details.url. (#17069) -
Fixed output processor state continuity for step lifecycle chunks during streaming. Lifecycle chunks (e.g.
step-finish) routed through the streamoutputWriternow share the same per-processor state map as the main model-output path, so state set inprocessOutputStreamwhile handling content chunks is visible when the lifecycle chunk is processed (and inprocessOutputResult). Previously these chunks were handled with an isolated, empty state. (#17370)Note: as part of routing lifecycle chunks through output processors, an aborted stream now surfaces content the model produced before the abort (e.g. a
text-startchunk and partialtext) instead of dropping it. Consumers that assumed an aborted result always had empty text may now observe partial output. -
Removed a stale AI SDK UI utils dependency from @mastra/core so projects using Zod 4 do not get Zod 3 peer dependency warnings from core. (#16994)
-
Fixed AGENT_RUN spans not closing when an agent stream is aborted mid-flight (e.g. browser disconnect or
AbortController.abort()). Aborted runs now end with{ status: 'aborted', reason: 'abort' }so traces are exported to observability backends. (#17203) -
Improved active workflow run listing latency. (#17374)
-
Mastra.shutdown()now releases storage resources automatically. Stores that expose aclose()lifecycle hook (such asLibSQLStore) are closed during shutdown, so file handles are freed and the storage directory can be removed cleanly afterward, including on Windows. (#17306)const mastra = new Mastra({ storage }); // Storage is closed for you — no manual cleanup needed await mastra.shutdown();
-
Add
updateThreadas an abstract method on theMastraMemorybase class and implement it inMockMemory. Previously the method existed only on the concreteMemorysubclass, so callingupdateThreadon a variable typed asMastraMemory(or any otherMastraMemorysubclass) produced a TypeScript error. Callers can now rename or re-title threads through the base class API without casting. (#17130) -
Fixed sub-agent version resolution in supervisor mode. Sub-agents now inherit the parent's draft/published version semantics — when chatting in the editor (draft mode), sub-agents resolve to their latest draft version; in the main agent chat (published mode), sub-agents resolve to their published version. Previously, sub-agents without explicit per-agent version overrides always fell back to the code-defined default, ignoring the parent's version context. (#17165)
-
Fixed read_file line ranges when offsets are past the end of a file (#17275)
-
Fixed FGA-enabled MCP servers so OAuth authInfo can be mapped to a Mastra user before tools/list and tools/call authorization. (#17475)
-
Fixed forEach workflow steps losing completed outputs and status during resume. (#17294)
-
Reduced overhead when streaming tool calls in agentic workflows (#17354)
-
Fixed noisy "Cannot get workflow run. Mastra storage is not initialized" debug logs that appeared on every
agent.generate()andagent.stream()call when the agent's Mastra instance had storage configured. (#17344)The internal workflow that runs each agent call never received the parent Mastra instance, so it could not see configured storage and logged the warning before falling back to in-memory state. It now receives the Mastra instance. It still does not write any of its own snapshots to your storage, so no extra rows are created.
-
Fixed Google model routing to accept GOOGLE_GENERATIVE_AI_API_KEY when GOOGLE_API_KEY is not set. (#17343)
-
Fixed grep context output so overlapping matches are shown once. (#17274)
-
Added
jsonPromptInjectionto the scorerjudgeconfig so users can opt out of nativeresponse_formatfor models that don't support it (e.g. some Groq Llama models). Previously, every scorer invocation made a wasted 400 API call before falling back to prompt injection. (#17046)import { createScorer } from '@mastra/core/evals'; const scorer = createScorer({ id: 'translation-quality', description: 'Evaluates translation quality', judge: { model: 'groq/llama-3.3-70b-versatile', instructions: 'You are an expert evaluator…', jsonPromptInjection: true, // skip the unsupported `response_format` attempt }, });
Fixes #17040.
-
Added native multimodal tool-result support. Core now converts MCP-style tool results with image and audio
contentparts into model-native media output when building model prompts, without requiring MCP tools to persist duplicate media payloads inproviderMetadata.mastra.modelOutput. (#16866)return { content: [ { type: 'text', text: 'Screenshot captured' }, { type: 'image', data: base64Png, mimeType: 'image/png' }, ], };
-
Fixed
MASTRA_TELEMETRY_DISABLEDopt-out detection. The values1,true, andyes(case-insensitive, trimmed) now reliably disable telemetry in both@mastra/coreenterprise events and themastraCLI's PostHog analytics. (#16990)Previously,
@mastra/coreonly treated the literal string'1'as disabled, so common opt-out values likeMASTRA_TELEMETRY_DISABLED=truesilently kept telemetry on.The
mastraCLI'sPosthogAnalyticsconstructor now also short-circuits when telemetry is disabled — no disk I/O, no tracking ID generation, no PostHog client. Previously the config file (mastra-cli.json) was written even when telemetry was disabled.Example:
# .env — any of these now reliably disable telemetry MASTRA_TELEMETRY_DISABLED=true MASTRA_TELEMETRY_DISABLED=1 MASTRA_TELEMETRY_DISABLED=yes -
Improved PIIDetector streaming performance. (#17377)
- Removed per-chunk LLM calls during streaming PII checks.
- Added local regex detection for common PII types (email, phone, SSN, credit card, IP address, API keys, URLs, UUIDs, crypto wallets, and IBAN).
- Added regex carryover buffer across chunk boundaries to catch split PII patterns.
- Buffered context-dependent PII types (names, addresses, dates of birth) with periodic LLM calls at configurable thresholds.
- Added
bufferSizeoption (default: 200) to control LLM buffer flush threshold. - Reduced streaming API cost, latency, and rate-limit pressure.
Closes #16466.
-
Fixed a TypeScript TS2589 "type instantiation is excessively deep" error when using Mastra alongside deeply-generic libraries such as @hono/zod-openapi. (#17339)
-
Improved observability and error isolation in the v1 ToolProvider runtime. (#17248)
Better visibility into connection-scope misconfiguration
When an agent runs with a stored ToolProvider connection whose scope cannot be resolved from the request context, the runtime now logs a one-shot warning and falls back to a shared bucket instead of silently routing every caller to the same OAuth account. Multi-tenant deployments get a clear signal when their identity wiring isn't reaching the runtime.
One bad toolkit no longer disables sibling providers
If a provider returns more connections for a toolkit than its declared capabilities allow, the runtime now logs and skips that toolkit instead of throwing. Other providers and other toolkits on the same agent continue to resolve normally.
-
Workflows now support an optional
metadatafield for attaching custom key-value data such asdisplayName,author, orcategory. Metadata is preserved through serialization and returned in workflow info API responses. (#17355)// Define a workflow with metadata const myWorkflow = createWorkflow({ id: 'data-processing', metadata: { displayName: 'Data Processing Pipeline', category: 'ETL', }, inputSchema: z.object({ ... }), outputSchema: z.object({ ... }), }); // Retrieve workflow info with metadata via the Mastra Server API const workflowInfo = await mastraClient.getWorkflow('data-processing'); console.log(workflowInfo.metadata?.displayName); // "Data Processing Pipeline"
@mastra/agent-browser@0.3.0
Minor Changes
-
Add
waitUntilsupport tobrowser_click,browser_press, andbrowser_select. When provided, the tool waits for the page to reach the given load state (load,domcontentloaded, ornetworkidle) after the action completes, preventing the nextbrowser_snapshotfrom capturing stale DOM when the interaction triggers navigation. The parameter is optional and behaviour is unchanged when omitted. (#17426)Usage example:
await browser_click({ ref: '@e1', waitUntil: 'domcontentloaded', timeout: 5000 });
Fixes #17397.
-
Added extensibility hooks for custom browser providers (e.g. Firecrawl Browser Sandbox). (#15724)
- New
createThreadManagerconfig option to inject a custom thread manager factory - Exported
AgentBrowserThreadManagerclass and related types (AgentBrowserSession,AgentBrowserThreadManagerConfig,CreateAgentBrowserThreadManager) - Changed several internal members from
privatetoprotectedto support subclassing
- New
Patch Changes
@mastra/agentcore@0.2.0
Minor Changes
-
Added AWS Bedrock AgentCore Runtime sandbox support. (#16642)
You can now run Workspace commands in AWS Bedrock AgentCore Runtime through a sandbox provider.
import { AgentCoreRuntimeSandbox } from '@mastra/agentcore'; const sandbox = new AgentCoreRuntimeSandbox({ region: 'us-west-2', agentRuntimeArn: process.env.AGENTCORE_RUNTIME_ARN!, }); const result = await sandbox.executeCommand('node', ['--version']);
Patch Changes
@mastra/ai-sdk@1.4.4
Patch Changes
- Fixed processor middleware so
args.systemMessagesonly contains untagged system messages. Tagged processor-owned system messages stay on the message list and are still included in the final model input. (#16950)
@mastra/blaxel@0.4.0
Minor Changes
- Added region support to Blaxel sandboxes:
new BlaxelSandbox({ region: 'eu-west-1' }). When omitted, Mastra falls back toBL_REGIONand thenauto. (#16555)
Patch Changes
@mastra/brightdata@0.2.1
Patch Changes
-
Fix Bright Data tools under Bun by replacing the SDK runtime client with fetch-based REST calls. (#16630)
-
Harden Bright Data search input handling. Country and language codes are now validated as alphabetic two-letter codes, the
getBrightDataClient().search.google()client validates and lowercase-normalizeslanguagebefore the request, and structured JSON (brd_json=1) is only requested when the searchformatisjsonso callers can obtain a true raw SERP response. (#17341)
@mastra/browser-firecrawl@0.1.0
Minor Changes
-
Initial release: Firecrawl Browser Sandbox integration for Mastra. (#15724)
FirecrawlBrowserextendsAgentBrowserto run the same deterministic browser tools (snapshot+refs, 16 tools, Playwright over CDP) against Firecrawl's cloud-hosted Chrome instances instead of local or self-hosted browsers.Features:
- Cloud-hosted Chrome via Firecrawl Browser Sandbox API
- Same tool surface as
@mastra/agent-browser(~16 browser automation tools) - Thread-scoped browser isolation (
scope: 'thread') - Automatic session cleanup on close
Usage:
import { FirecrawlBrowser } from '@mastra/browser-firecrawl'; const browser = new FirecrawlBrowser({ firecrawlApiKey: process.env.FIRECRAWL_API_KEY, scope: 'thread', }); const agent = mastra.getAgent('my-agent', { browser });
Patch Changes
@mastra/claude@0.1.0
Minor Changes
-
Added
@mastra/claude, a package for running Claude Agent SDK agents through Mastra. (#16906)Create a Claude SDK agent, register it with Mastra, and call
generate()orstream()with Mastra-compatible outputs. Runs keep Claude SDK usage, cost estimates, and observability data available to Mastra.import { ClaudeSDKAgent } from '@mastra/claude'; export const claudeAgent = new ClaudeSDKAgent({ id: 'claude-sdk-agent', description: 'Use Claude Agent SDK through Mastra.', sdkOptions: { model: process.env.CLAUDE_CODE_MODEL, cwd: process.cwd(), }, });
Patch Changes
@mastra/clickhouse@1.9.1
Patch Changes
-
Added the
tool_provider_connectionsstorage domain. Stored agents can now persist per-agent ToolProvider config that round-trips on read/write/create. Runtime connection resolution (per-author, shared, caller-supplied) ships in a follow-up PR. (#17247)What you can do
- Pin a connection on a stored agent's config and have it round-trip on read/write/create.
- Persist multiple connections per toolkit so a follow-up runtime PR can fan-out to the right one at execution time.
Example
import { LibSQLStore } from '@mastra/libsql'; const storage = new LibSQLStore({ url: process.env.DATABASE_URL }); // Persist an OAuth connection that an agent can pin later await storage.toolProviders.upsertConnection({ authorId: 'user-123', providerId: 'composio', connectionId: 'auth_abc', toolkit: 'gmail', label: 'Work inbox', scope: 'per-author', }); // List a user's own connections (admin can omit authorId to list across users) const { items } = await storage.toolProviders.listConnectionsByAuthor({ authorId: 'user-123', providerId: 'composio', });
Additive — existing stored agents continue to work unchanged. The runtime that consumes this domain ships in a follow-up PR.
PR 1 of 3 split from #17224.
@mastra/client-js@1.22.0
Minor Changes
-
Added an agent override export API and server-side ownership enforcement. (#17228)
The server and client now expose an agent override export endpoint so Studio can download an agent's overrides as JSON for review or commit workflows. Saves are enforced server-side against each agent's
editorconfig, so only owned fields (instructions, tools, or tool descriptions) are persisted and fields locked by theeditorconfig are stripped.The system packages response also reports the active editor
sourceso clients can render the correct editing experience. -
Add fire-and-forget workflow resume that returns immediately with
{ runId }without awaiting the run output. (#17230)For
@mastra/inngest, this skips thegetRunOutput()polling that previously raced a realtime subscription against the Inngest runs API and could surface spurious 404s even though the durable workflow was running fine.Run.resumeAsync()added to core: dispatches the resume in the background and returns{ runId }immediately. Engines that poll for results (Inngest) override it to skip polling entirely.InngestRun.resumeAsync()sends the resume event and returns{ runId }, skipping polling. Send-time failures (bad payload, event send failure) still reject synchronously and roll back the snapshot.- New
POST /workflows/:workflowId/resume-no-waitandPOST /agent-builder/:actionId/resume-no-waitroutes return{ runId }immediately. - New client SDK
run.resumeNoWait()resolves with{ runId }.
The existing
resumeAsync()client/server surface is unchanged and still resolves with the full workflow result, so there is no breaking change.resumeNoWaitis intentionally additive in v1. In Mastra v2 the fire-and-forget behavior is planned to become the default behavior ofresumeAsync()(mirroringstart/resumesemantics), at which pointresumeNoWaitand theresume-no-waitroutes will be removed. The code paths carryTODO(v2)comments documenting this consolidation. -
Added a
PATCH /tool-providers/:providerId/connections/:connectionIdendpoint and matching client SDK method so authors can rename a connection's display label after creation. (#17249)Rename a connection from the client SDK
import { MastraClient } from '@mastra/client-js'; const client = new MastraClient({ baseUrl: '…' }); await client.getToolProvider('composio').updateConnection('auth_abc', { label: 'Work inbox', });
Pass
label: null(or an empty string) to clear the existing label. Labels are 1–32 characters and accept letters, digits, spaces, underscores, and hyphens ([A-Za-z0-9 _-]+).Ownership enforced server-side
Non-owners get a 403 unless they hold
tool-providers:admin. Shared connections are reachable by every author. The label is stored on the connection row itself, so the rename flows to every agent that pins the connection — no per-agent edit needed. -
Added the v1 ToolProvider runtime, server routes, client SDK methods, and editor wiring that power OAuth-backed integrations on stored agents. (#17248)
Stored agents can now pin OAuth connections per toolkit
A stored agent's config accepts a new
toolProvidersshape that tells the runtime which connection to bind for each toolkit at execution time. Connections can be scoped per-author, shared across an org, or supplied by the caller.{ toolProviders: { composio: { connections: { gmail: [{ kind: 'author', toolkit: 'gmail', connectionId: 'auth_abc', scope: 'per-author' }], }, tools: { GMAIL_FETCH_EMAILS: { toolkit: 'gmail' }, }, }, }, }
New client SDK surface for managing connections
import { MastraClient } from '@mastra/client-js'; const client = new MastraClient({ baseUrl: '…' }); const composio = client.toolProvider('composio'); const { items } = await composio.listConnections({ toolkit: 'gmail' }); await composio.disconnectConnection('auth_abc');
New
ToolProviderinterface for custom providersProviders implement a VNext surface (
listToolkitsVNext,listToolsVNext,resolveToolsVNext) plus the auth round-trip (authorize,getAuthStatus,listConnections,disconnectConnection,listConnectionFields,health). The Composio provider has been rewritten on this surface; the older catalog methods remain as@deprecatedshims for back-compat.Connections list responses use
page/perPagepagination, matching the rest of the server surface.Both stored agents (
editor.agent.getById(...)) and code-defined agents with stored overrides (editor.agent.applyStoredOverrides(...)) resolvetoolProvidersat request time, merging provider-resolved tools alongside code/registry/MCP/integration tools.Stored agents that don't set
toolProviderscontinue to work unchanged. The Studio/Builder UI ships separately.
Patch Changes
-
Separated thread subscription cleanup from active-run aborts so closing or switching a listener only unsubscribes that listener, while explicit cancel still aborts the active run. (#17310)
-
Added subscription-native tool approval APIs so approving or declining a tool call resumes through the active thread subscription instead of requiring a separate continuation stream. New messages are queued while a tool approval is waiting, preventing overlapping runs from duplicating approval requests. (#17311)
await agent.sendToolApproval({ resourceId: 'user-123', threadId: 'thread-123', toolCallId: 'tool-call-123', approved: true, });
-
Fixed sub-agent version resolution in supervisor mode. Sub-agents now inherit the parent's draft/published version semantics — when chatting in the editor (draft mode), sub-agents resolve to their latest draft version; in the main agent chat (published mode), sub-agents resolve to their published version. Previously, sub-agents without explicit per-agent version overrides always fell back to the code-defined default, ignoring the parent's version context. (#17165)
-
Hardened v1 ToolProvider connection routes and SDK forwarding. (#17248)
Fail closed on unknown
connectionIdDELETE /tool-providers/:providerId/connections/:connectionIdand
GET …/usagenow return403when storage is configured but no persisted
row matches the suppliedconnectionIdand the caller isn't an admin.
Previously these routes fell through to the caller's ownauthorId, which
let non-admin callers probe (and trigger provider-siderevokeConnection
for) IDs that didn't belong to them.Aligned authorize label validation with stored label rules
POST /tool-providers/:providerId/authorizenow enforces the same label
rules the storedtoolProvidersconfig uses (min(1),max(32),
/^[A-Za-z0-9 _-]+$/). Labels that passauthorizeare now guaranteed to
pass downstream stored-agent validation.SDK forwards
toolkiton connection-scoped operations@mastra/client-js:await client.toolProviders.get('composio').disconnectConnection('ca_xxx', { toolkit: 'gmail', force: true, }); const usage = await client.toolProviders.get('composio').getConnectionUsage('ca_xxx', { toolkit: 'gmail' });
disconnectConnectionnow forwardsparams.toolkit(previously dropped)
andgetConnectionUsageaccepts an optional{ toolkit }parameter so
toolkit-scoped connection lookups disambiguate correctly server-side. -
Improved observability and error isolation in the v1 ToolProvider runtime. (#17248)
Better visibility into connection-scope misconfiguration
When an agent runs with a stored ToolProvider connection whose scope cannot be resolved from the request context, the runtime now logs a one-shot warning and falls back to a shared bucket instead of silently routing every caller to the same OAuth account. Multi-tenant deployments get a clear signal when their identity wiring isn't reaching the runtime.
One bad toolkit no longer disables sibling providers
If a provider returns more connections for a toolkit than its declared capabilities allow, the runtime now logs and skips that toolkit instead of throwing. Other providers and other toolkits on the same agent continue to resolve normally.
-
Workflows now support an optional
metadatafield for attaching custom key-value data such asdisplayName,author, orcategory. Metadata is preserved through serialization and returned in workflow info API responses. (#17355)// Define a workflow with metadata const myWorkflow = createWorkflow({ id: 'data-processing', metadata: { displayName: 'Data Processing Pipeline', category: 'ETL', }, inputSchema: z.object({ ... }), outputSchema: z.object({ ... }), }); // Retrieve workflow info with metadata via the Mastra Server API const workflowInfo = await mastraClient.getWorkflow('data-processing'); console.log(workflowInfo.metadata?.displayName); // "Data Processing Pipeline"
@mastra/cloudflare@1.4.1
Patch Changes
-
Added the
tool_provider_connectionsstorage domain. Stored agents can now persist per-agent ToolProvider config that round-trips on read/write/create. Runtime connection resolution (per-author, shared, caller-supplied) ships in a follow-up PR. (#17247)What you can do
- Pin a connection on a stored agent's config and have it round-trip on read/write/create.
- Persist multiple connections per toolkit so a follow-up runtime PR can fan-out to the right one at execution time.
Example
import { LibSQLStore } from '@mastra/libsql'; const storage = new LibSQLStore({ url: process.env.DATABASE_URL }); // Persist an OAuth connection that an agent can pin later await storage.toolProviders.upsertConnection({ authorId: 'user-123', providerId: 'composio', connectionId: 'auth_abc', toolkit: 'gmail', label: 'Work inbox', scope: 'per-author', }); // List a user's own connections (admin can omit authorId to list across users) const { items } = await storage.toolProviders.listConnectionsByAuthor({ authorId: 'user-123', providerId: 'composio', });
Additive — existing stored agents continue to work unchanged. The runtime that consumes this domain ships in a follow-up PR.
PR 1 of 3 split from #17224.
@mastra/convex@1.2.1
Patch Changes
-
Fixed Convex message lookups so they use indexed ids instead of a capped full-table scan. (#17409)
-
Fixed Convex workflow storage to save concurrent workflow updates atomically. (#16641)
-
Fixed concurrent Convex memory updates from overwriting each other (#17299)
-
Fixed forEach workflow steps losing completed outputs and status during resume. (#17294)
-
Fixed Convex JS vector scans to include all paginated vectors. (#17270)
@mastra/cursor@0.1.0
Minor Changes
-
Added
@mastra/cursor, a package for running Cursor SDK agents through Mastra. (#16906)Create a Cursor SDK agent, register it with Mastra, and call
generate()orstream()with Mastra-compatible outputs. Runs keep Cursor SDK usage and observability data available to Mastra.import { CursorSDKAgent } from '@mastra/cursor'; export const cursorAgent = new CursorSDKAgent({ id: 'cursor-sdk-agent', description: 'Use Cursor Agent SDK through Mastra.', sdkOptions: { apiKey: process.env.CURSOR_API_KEY, model: { id: process.env.CURSOR_MODEL_ID! }, local: { cwd: process.cwd(), }, }, });
Patch Changes
@mastra/daytona@0.4.1
Patch Changes
-
Fixed Daytona command execution to reject invalid environment variable names (#17279)
-
Fixed sandbox execution results to report killed and timed out commands. (#17281)
@mastra/deployer@1.38.0
Patch Changes
-
The server now installs SIGINT/SIGTERM handlers and runs
mastra.shutdown()before exiting, allowing storage backends to release resources cleanly instead of being terminated mid-flight. (#17413) -
Fixed Studio playground browser telemetry not respecting
MASTRA_TELEMETRY_DISABLED. The dev server was hardcoding an empty value into the servedindex.html, sowindow.MASTRA_TELEMETRY_DISABLEDwas always falsy in the browser and the playground React app initialized PostHog regardless of the user's.env. The dev server now propagatesprocess.env.MASTRA_TELEMETRY_DISABLEDto the browser, where the playground applies the same canonical opt-out parsing as the rest of the framework. (#16990)Before: Setting
MASTRA_TELEMETRY_DISABLED=truein.envhad no effect on playground network requests to PostHog.After:
# .env MASTRA_TELEMETRY_DISABLED=truePlayground analytics are now disabled.
-
Fixed false-positive LOCAL_STORAGE_PATH preflight errors caused by library code (e.g. Agent Builder prompt templates). Added a Rollup plugin (
mastra-local-storage-detector) to the deployer that detects host-local storage URLs during bundling — only user modules are inspected (node_modules excluded), and tree-shaken code is ignored. The CLI preflight check now reads this bundler-generated metadata instead of scanning raw bundle source. (#17286) -
Enabled Studio via the CLI and deployers to use agent signal subscriptions by default while preserving
MASTRA_AGENT_SIGNALS=false,enableThreadSignals: false, and explicit legacy Stream as opt-outs. The ReactuseChat()hook remains opt-in for SDK consumers viaenableThreadSignals: true. (#17313)
@mastra/deployer-vercel@1.1.33
Patch Changes
- Enabled Studio via the CLI and deployers to use agent signal subscriptions by default while preserving
MASTRA_AGENT_SIGNALS=false,enableThreadSignals: false, and explicit legacy Stream as opt-outs. The ReactuseChat()hook remains opt-in for SDK consumers viaenableThreadSignals: true. (#17313)
@mastra/docker@0.3.0
Minor Changes
-
Added
nameoption toDockerSandboxfor setting the container's display name. (#17266)Previously,
DockerSandboxdid not forward a container name to Docker, so containers were created with random names likegracious_tueven though the docs impliedidwas used for naming. The newnameoption (defaults toid) is now passed todocker run --nameand is sanitized to fit Docker's container-name rules ([a-zA-Z0-9_.-]).import { DockerSandbox } from '@mastra/docker'; // Before: container ended up with a random Docker-assigned name new DockerSandbox({ id: 'user-1001' }); // After: the id is used as the container name by default new DockerSandbox({ id: 'user-1001' }); // → docker ps shows 'user-1001' // Or override explicitly new DockerSandbox({ id: 'user-1001', name: 'tenant-acme-dev' });
Closes #17263.
Patch Changes
- Fixed sandbox execution results to report killed and timed out commands. (#17281)
@mastra/duckdb@1.4.1
Patch Changes
- Fixed DuckDB "Conflicting lock is held" error on
mastra devhot reload.DuckDBStorenow releases its native file lock on shutdown so the restarted dev process can reopen the same database file. (#17413)
@mastra/e2b@0.3.2
Patch Changes
- Fix E2B sandbox creation failing with "Sandbox.betaCreate is not a function" on e2b SDK 2.24.0+. The adapter now uses the stable
Sandbox.create()API withlifecycle: { onTimeout: 'pause' }(replacing the removedbetaCreate/autoPause), and requirese2b >= 2.24.0. (#17261)
@mastra/editor@0.11.0
Minor Changes
-
Added agent override support to the agent and editor APIs. (#17227)
Code-defined agents can now declare which fields Studio may edit with the
editoroption:new Agent({ name: 'Weather Agent', model, editor: { instructions: true, tools: { description: true }, }, });
The editor applies stored overrides only for fields the
editorconfig owns, so locked fields keep their code-defined values. Per-agenteditor: falselocks an agent entirely.MastraEditoraccepts asourcesetting that picks the editing experience:new MastraEditor({ source: 'code' });
source: 'code'— the editor auto-wires aFilesystemStore(defaulting to./mastra/editor/, overridable withcodePath) when no editor storage is supplied, and persists overrides as deterministic per-agent JSON files.source: 'db'(default) — keeps the existing storage-backed flow against whatever storage the project has configured.
-
Added the v1 ToolProvider runtime, server routes, client SDK methods, and editor wiring that power OAuth-backed integrations on stored agents. (#17248)
Stored agents can now pin OAuth connections per toolkit
A stored agent's config accepts a new
toolProvidersshape that tells the runtime which connection to bind for each toolkit at execution time. Connections can be scoped per-author, shared across an org, or supplied by the caller.{ toolProviders: { composio: { connections: { gmail: [{ kind: 'author', toolkit: 'gmail', connectionId: 'auth_abc', scope: 'per-author' }], }, tools: { GMAIL_FETCH_EMAILS: { toolkit: 'gmail' }, }, }, }, }
New client SDK surface for managing connections
import { MastraClient } from '@mastra/client-js'; const client = new MastraClient({ baseUrl: '…' }); const composio = client.toolProvider('composio'); const { items } = await composio.listConnections({ toolkit: 'gmail' }); await composio.disconnectConnection('auth_abc');
New
ToolProviderinterface for custom providersProviders implement a VNext surface (
listToolkitsVNext,listToolsVNext,resolveToolsVNext) plus the auth round-trip (authorize,getAuthStatus,listConnections,disconnectConnection,listConnectionFields,health). The Composio provider has been rewritten on this surface; the older catalog methods remain as@deprecatedshims for back-compat.Connections list responses use
page/perPagepagination, matching the rest of the server surface.Both stored agents (
editor.agent.getById(...)) and code-defined agents with stored overrides (editor.agent.applyStoredOverrides(...)) resolvetoolProvidersat request time, merging provider-resolved tools alongside code/registry/MCP/integration tools.Stored agents that don't set
toolProviderscontinue to work unchanged. The Studio/Builder UI ships separately.
Patch Changes
-
Improved observability and error isolation in the v1 ToolProvider runtime. (#17248)
Better visibility into connection-scope misconfiguration
When an agent runs with a stored ToolProvider connection whose scope cannot be resolved from the request context, the runtime now logs a one-shot warning and falls back to a shared bucket instead of silently routing every caller to the same OAuth account. Multi-tenant deployments get a clear signal when their identity wiring isn't reaching the runtime.
One bad toolkit no longer disables sibling providers
If a provider returns more connections for a toolkit than its declared capabilities allow, the runtime now logs and skips that toolkit instead of throwing. Other providers and other toolkits on the same agent continue to resolve normally.
-
Improved the Agent Builder system prompt so it produces more reliable agents from starter cards and freeform prompts. (#17424)
-
Agent Builder is now more resilient to transient and provider-specific stream errors out of the box. The built-in builder agent ships with three error processors enabled by default — automatic retry of transient OpenAI errors (such as
server_error,rate_limit, andoverloaded), recovery from Anthropic 400 prefill rejections, and per-provider history-shape fixes — so flaky LLM calls no longer end the conversation. You can still pass your ownerrorProcessorstocreateBuilderAgentto extend or replace these defaults. (#17481)
@mastra/evals@1.2.4
Patch Changes
- Fixed the hallucination and tool-usage scorers returning incorrect scores when observable memory is enabled. These scorers now detect tool calls in every message format, so responses are no longer wrongly scored as fully hallucinated or as using zero tools. (#17321)
@mastra/express@1.3.26
Patch Changes
-
Added sseFlushOnConnect route option to scope the SSE connected comment to subscribe endpoints only (#17158)
-
Fixed validation error responses on routes with
bodySchema,queryParamSchema, orpathParamSchemalosing field path information when consumers pinzod@^3. Responses now return the actual field name inissues[].field(e.g."agent_id") instead of"unknown"with the raw Zod issues serialized intoissues[0].message. Fixes #17167. (#17172) -
Scoped the SSE connected comment to subscribe routes only and added SSE comment passthrough for Fastify and NestJS adapters (#17158)
@mastra/fastembed@1.1.2
Patch Changes
- Fixed FastEmbed so repeated embedding calls reuse loaded models instead of loading a new model each time. (#17303)
@mastra/fastify@1.3.26
Patch Changes
-
Added sseFlushOnConnect route option to scope the SSE connected comment to subscribe endpoints only (#17158)
-
Fixed custom API route responses dropping headers set by Fastify plugins. Headers applied via Fastify hooks (e.g.
Access-Control-Allow-Originfrom@fastify/cors) were overwritten when the adapter hijacked the reply to stream the custom route response. The adapter now merges hook-set headers into the response before hijack — matching the behavior already implemented for streaming routes. (#15719) -
Fixed validation error responses on routes with
bodySchema,queryParamSchema, orpathParamSchemalosing field path information when consumers pinzod@^3. Responses now return the actual field name inissues[].field(e.g."agent_id") instead of"unknown"with the raw Zod issues serialized intoissues[0].message. Fixes #17167. (#17172) -
Scoped the SSE connected comment to subscribe routes only and added SSE comment passthrough for Fastify and NestJS adapters (#17158)
@mastra/hono@1.4.21
Patch Changes
-
Added sseFlushOnConnect route option to scope the SSE connected comment to subscribe endpoints only (#17158)
-
Fixed validation error responses on routes with
bodySchema,queryParamSchema, orpathParamSchemalosing field path information when consumers pinzod@^3. Responses now return the actual field name inissues[].field(e.g."agent_id") instead of"unknown"with the raw Zod issues serialized intoissues[0].message. Fixes #17167. (#17172) -
Scoped the SSE connected comment to subscribe routes only and added SSE comment passthrough for Fastify and NestJS adapters (#17158)
@mastra/inngest@1.5.0
Minor Changes
-
Add fire-and-forget workflow resume that returns immediately with
{ runId }without awaiting the run output. (#17230)For
@mastra/inngest, this skips thegetRunOutput()polling that previously raced a realtime subscription against the Inngest runs API and could surface spurious 404s even though the durable workflow was running fine.Run.resumeAsync()added to core: dispatches the resume in the background and returns{ runId }immediately. Engines that poll for results (Inngest) override it to skip polling entirely.InngestRun.resumeAsync()sends the resume event and returns{ runId }, skipping polling. Send-time failures (bad payload, event send failure) still reject synchronously and roll back the snapshot.- New
POST /workflows/:workflowId/resume-no-waitandPOST /agent-builder/:actionId/resume-no-waitroutes return{ runId }immediately. - New client SDK
run.resumeNoWait()resolves with{ runId }.
The existing
resumeAsync()client/server surface is unchanged and still resolves with the full workflow result, so there is no breaking change.resumeNoWaitis intentionally additive in v1. In Mastra v2 the fire-and-forget behavior is planned to become the default behavior ofresumeAsync()(mirroringstart/resumesemantics), at which pointresumeNoWaitand theresume-no-waitroutes will be removed. The code paths carryTODO(v2)comments documenting this consolidation. -
Added
connect()to support Inngest Connect for Mastra workflows. Use this when running workflow execution in a dedicated long-running worker process that should not expose an inbound HTTP endpoint: (#17064)import { connect } from '@mastra/inngest/connect'; await connect({ mastra, inngest, instanceId: 'worker-1', maxWorkerConcurrency: 10, });
connect()uses the same Mastra workflow functions asserve(), including nested and cron workflows.serve()is unchanged.
Patch Changes
- Fixed processor workflow steps so
args.systemMessagesonly contains untagged system messages. Tagged processor-owned system messages stay on the message list and are still included in the final model input. (#16950)
@mastra/koa@1.5.9
Patch Changes
-
Added sseFlushOnConnect route option to scope the SSE connected comment to subscribe endpoints only (#17158)
-
Fixed validation error responses on routes with
bodySchema,queryParamSchema, orpathParamSchemalosing field path information when consumers pinzod@^3. Responses now return the actual field name inissues[].field(e.g."agent_id") instead of"unknown"with the raw Zod issues serialized intoissues[0].message. Fixes #17167. (#17172) -
Scoped the SSE connected comment to subscribe routes only and added SSE comment passthrough for Fastify and NestJS adapters (#17158)
@mastra/libsql@1.12.0
Minor Changes
-
Added the
tool_provider_connectionsstorage domain. Stored agents can now persist per-agent ToolProvider config that round-trips on read/write/create. Runtime connection resolution (per-author, shared, caller-supplied) ships in a follow-up PR. (#17247)What you can do
- Pin a connection on a stored agent's config and have it round-trip on read/write/create.
- Persist multiple connections per toolkit so a follow-up runtime PR can fan-out to the right one at execution time.
Example
import { LibSQLStore } from '@mastra/libsql'; const storage = new LibSQLStore({ url: process.env.DATABASE_URL }); // Persist an OAuth connection that an agent can pin later await storage.toolProviders.upsertConnection({ authorId: 'user-123', providerId: 'composio', connectionId: 'auth_abc', toolkit: 'gmail', label: 'Work inbox', scope: 'per-author', }); // List a user's own connections (admin can omit authorId to list across users) const { items } = await storage.toolProviders.listConnectionsByAuthor({ authorId: 'user-123', providerId: 'composio', });
Additive — existing stored agents continue to work unchanged. The runtime that consumes this domain ships in a follow-up PR.
PR 1 of 3 split from #17224.
Patch Changes
-
Added a public
close()method toLibSQLStorethat releases SQLite file handles and cleans up the WAL/shm sidecar files. Previously these handles stayed open until the process exited, which on Windows causedEBUSYerrors when removing the storage directory after shutdown.Mastra.shutdown()now callsclose()automatically, so you no longer need to reach into private fields. (#17306)const storage = new LibSQLStore({ id: 'my-store', url: 'file:./dev.db' }); // Release all file handles, including WAL/shm sidecar files await storage.close(); // Now safe to remove the storage directory on all platforms, including Windows await fs.rm('./dev.db', { recursive: true, force: true });
@mastra/loggers@1.1.2
Patch Changes
- Added messageKey option to PinoLogger for compatibility with structured-log aggregators. Set messageKey: 'message' to emit log messages under the message field expected by Google Cloud Logging, Datadog, ECS, and AWS CloudWatch. (#17450)
@mastra/mcp@1.9.0
Minor Changes
-
Added opt-in MCP server instructions forwarding into agent system prompts. (#17155)
When an MCP server advertises instructions during initialization, you can now forward that guidance into the system prompt of agents that use the server's tools. This is opt-in — set
forwardInstructions: trueper server to enable it. Forwarded instructions are injected into the agent's system prompt, so only enable this for servers you trust.const mcp = new MCPClient({ servers: { db: { url: new URL('http://localhost:4111/mcp'), forwardInstructions: true, // opt in; defaults to false instructionsMaxLength: 512, // max chars forwarded per server }, }, }); const agent = new Agent({ id: 'db-agent', name: 'DB Agent', instructions: 'Help with database changes.', model, tools: await mcp.listTools(), });
You can always inspect cached instructions without forwarding them:
const instructions = mcp.getServerInstructions(); // => { db: 'Always validate before migrating.', other: undefined }
-
Added native multimodal tool-result support. Core now converts MCP-style tool results with image and audio
contentparts into model-native media output when building model prompts, without requiring MCP tools to persist duplicate media payloads inproviderMetadata.mastra.modelOutput. (#16866)return { content: [ { type: 'text', text: 'Screenshot captured' }, { type: 'image', data: base64Png, mimeType: 'image/png' }, ], };
Patch Changes
-
Support conditional, function-based tool approvals. (#17337)
- MCP tools that wrap a server-level
requireToolApprovalfunction are now honored end-to-end. The per-tool approval function was previously dropped when the agent converted MCP tools (it kept only the boolean flag), so conditional approval silently fell back to always-on.CoreToolBuildernow preserves aneedsApprovalFnattached directly to a tool instance. - The global
requireToolApprovaloption onagent.stream/agent.generatenow accepts a function in addition to a boolean. It is evaluated per tool call with the tool name, arguments, and request context, enabling policies such as regex allowlists on tool names. Returningtruerequires approval for that call;falseallows it. On error the call defaults to requiring approval. When a function policy is set, tool calls run sequentially so approval suspensions don't race. Durable agents and stored agents continue to accept only a boolean (a function degrades to requiring approval for every call, since their options must be serializable).
// Approve only tool calls whose name is not on an allowlist. const allowlist = /^(get|list|search)_/; await agent.generate('...', { requireToolApproval: ({ toolName }) => !allowlist.test(toolName), });
- Precedence is unchanged from before: a per-tool approval function (
createTool({ requireApproval: fn })or an MCP-derivedneedsApprovalFn) is authoritative for that tool and overrides the global setting, so a tool can still opt out of approval by returningfalseeven when the global option is on. The only new behavior is that the global option may now be a function in addition to a boolean. - The previously implicit, runtime-attached per-tool approval predicate is now a typed contract:
@mastra/coreexportsNeedsApprovalFnand declares the optionalneedsApprovalFnproperty on theToolclass. The MCP client and the agent runtime now share this typed contract instead of reaching throughany. This is additive — no public API changes.
- MCP tools that wrap a server-level
-
Close the stale MCP transport before reconnecting so SSE connections no longer leak orphaned EventSource instances and accumulate server-side sessions on implicit reconnect. (#17326)
-
Fixed FGA-enabled MCP servers so OAuth authInfo can be mapped to a Mastra user before tools/list and tools/call authorization. (#17475)
@mastra/memory@1.20.1
Patch Changes
-
Fixed observational memory replaying previously observed assistant responses during reprocessing, so past assistant messages no longer reappear in later turns. (#17338)
-
Fixed a crash in Cloudflare Workers when using a Zod schema for working memory. Working-memory input is now validated directly by the provided schema validator, which avoids runtime restrictions in Cloudflare Workers. (#17327)
-
Preserve system-reminder filtering for normalized reactive signal metadata. (#17191)
-
Added
observation.bufferOnIdleto opt idle turns into background observation buffering and carry the signal sender needed for background notifications. (#17181) -
Fixed
Memory.saveMessagesnot populatingrole,content, andcreated_atin the vector store metadata. Calls toGET /api/memory/searchnow return matches with the full message shape regardless of whether messages were saved throughagent.generate/agent.streamor written directly viaMemory.saveMessages(for example through thePOST /api/memory/save-messagesHTTP route used by external agents). (#16381)
@mastra/modal@0.2.1
Patch Changes
- Fixed sandbox execution results to report killed and timed out commands. (#17281)
@mastra/mysql@0.1.0
Minor Changes
-
Added the MySQL storage adapter for Mastra. Use it as a storage backend with the same domain coverage as the other first-party adapters (memory, threads, workflows, observability, agents, and more). (#17446)
import { MySQLStore } from '@mastra/mysql'; const store = new MySQLStore({ connectionString: 'mysql://user:password@localhost:3306/mastra', });
This release also makes table and index setup reliable on a brand-new database:
- Fixed store initialization failing on a fresh database. Idempotency for favorites is now enforced by the table's primary key instead of a separate index that MySQL rejected, which previously aborted setup and left the connection pool unusable.
- Fixed default performance indexes silently failing to be created. Indexes on text columns now include a key-length prefix so they are created instead of skipped.
Patch Changes
@mastra/nestjs@0.1.10
Patch Changes
-
Added sseFlushOnConnect route option to scope the SSE connected comment to subscribe endpoints only (#17158)
-
Fixed validation error responses on routes with
bodySchema,queryParamSchema, orpathParamSchemalosing field path information when consumers pin a differentzodmajor than the one bundled with this adapter. Responses now return the actual field name inissues[].field(e.g."agent_id") instead of"unknown"with the raw Zod issues serialized intoissues[0].message. (#17172)ValidationError.zodErroris now typed asZodErrorLike(a structural subset ofZodErrorexposingissues[]) so consumers pinning a differentzodmajor still type-check. The runtime value is unchanged; cast to your installedZodErrortype if you need its instance methods.Fixes #17167.
-
Scoped the SSE connected comment to subscribe routes only and added SSE comment passthrough for Fastify and NestJS adapters (#17158)
@mastra/observability@1.14.1
Patch Changes
-
Added documentation explaining how to query and retrieve metric data from Mastra's observability store. Developers can now learn how to aggregate metrics, break them down by labels, visualize time series, and calculate percentiles using the in-process store API, HTTP endpoints, or CLI commands. (#17178)
-
Fix null
estimatedCostfor OpenRouter models whose id carries a vendor prefix and a dotted version (e.g.google/gemini-2.5-flash). These previously failed to match the pricing data (gemini-2-5-flash), leaving cost unreported in Studio's "Total Model Cost". Cost is now estimated correctly for these routes. (#17140) -
Added support for costs supplied by external SDK agent integrations. (#16906)
When an SDK agent records an estimated cost on its model generation span, observability now carries that cost onto the auto-extracted model token metric. This lets storage-backed metric queries and dashboards display costs reported by external agent SDKs, even when Mastra cannot calculate the cost from its own pricing registry.
@mastra/pg@1.12.0
Minor Changes
-
Added the
disableInitoption to theMastraVectorbase class. When set totrue, vector stores skip creating schemas, extensions, tables, and indexes at application startup. This matches the existingdisableInitbehavior on storage adapters and is useful for deployments where schemas and indexes are created ahead of time by a privileged database role, while the application runs with a least-privilege role. (#17272)Usage
const vector = new PgVector({ id: 'vectors', connectionString: process.env.DATABASE_URL, disableInit: true, });
The
MASTRA_DISABLE_STORAGE_INITenvironment variable also disables vector init, so a single flag prevents both storage and vector stores from creating schemas, tables, or indexes at startup.
Patch Changes
-
Fixed
PostgresStoreignoring an explicitssloption when theconnectionStringalso carries ansslmode=/ssl=query param. node-postgres re-parses the connection string andObject.assigns the URL-derivedsslover the explicit one, so a config like{ connectionString: '...?sslmode=require', ssl: { rejectUnauthorized: false } }silently droppedrejectUnauthorized: falseand failed withUNABLE_TO_GET_ISSUER_CERT_LOCALLYagainst self-signed CAs. The connection-string branch now parses the URL and applies the explicitssllast, while still honoring URL-driven SSL when nossloption is provided. Fixes #17307. (#17356) -
Improved Postgres memory message save performance (#17351)
@mastra/playground-ui@31.0.0
Minor Changes
-
Made
ButtonsGroupcompose joined controls (searchbar + dropdown pills, split buttons, steppers) cleanly, and improvedInputGroupso it drops straight into one. (#17259)ButtonsGroupwithspacing="close"fuses outline, filled andSelectsegments into one pill with a single clean divider, a complete focus ring (no missing side), and no consumer width classes.InputGroupfills a flex row on its own, matches a same-size sibling height, and propagates size viadata-size(no React context) — so an icon + input segment composes inside aButtonsGrouppill with no layout classes.
Use
InputGroup(icon as anInputGroupAddon, optional clear button as anInputGroupButton) to build an icon input — it owns the box, focus, hover and error states on the focusable wrapper:import { ButtonsGroup, InputGroup, InputGroupAddon, InputGroupInput, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, } from '@mastra/playground-ui'; <ButtonsGroup spacing="close"> <InputGroup variant="outline"> <InputGroupAddon align="inline-start"> <SearchIcon /> </InputGroupAddon> <InputGroupInput placeholder="Search projects..." /> </InputGroup> <Select value={sort} onValueChange={setSort}> <SelectTrigger className="rounded-full"> <SelectValue /> </SelectTrigger> <SelectContent align="end">{/* options */}</SelectContent> </Select> </ButtonsGroup>;
-
Refined the focus state of form inputs in
@mastra/playground-ui. Applies toInput,InputGroup,Searchbar, andTextarea. (#17259)- Removed the green border and glow that appeared on focus.
- On focus, the field shows a subtle background shift and brightens its border to a neutral tone, so the focused field stays clearly visible on any underlying surface.
- Made single-line inputs fully rounded to match the design system. Multi-line surfaces (
Textarea, andInputGroupwith a block-style addon) keep a softerrounded-xlcorner. - Added
filledandoutlinevariants for consumers that need to choose between the new surface treatment and a quieter border-only treatment. - The
unstyledvariant ofInputandTextareano longer leaks the browser default focus outline.
Input,Textarea, andInputGroupdefault to thefilledsurface.SearchbarandListSearchdefault to theoutline(transparent) treatment. ForSearchbarthis matches its previous transparent look.ListSearchpreviously rendered a filled (bg-surface2),rounded-lgbox, so its search fields across the list pages now read as transparent, fully-rounded pills — passvariant="filled"to keep them on a filled surface:import { Input, InputGroup, InputGroupAddon, InputGroupInput, Searchbar } from '@mastra/playground-ui'; <Input placeholder="Name" /> <Input variant="outline" placeholder="Name" /> <InputGroup variant="outline"> <InputGroupAddon> <SearchIcon /> </InputGroupAddon> <InputGroupInput placeholder="Email" /> </InputGroup> <Searchbar label="Search agents" placeholder="Search agents..." onSearch={handleSearch} /> <Searchbar variant="filled" label="Search agents" placeholder="Search agents..." onSearch={handleSearch} />
Patch Changes
-
Fixed syntax highlighting in Studio code blocks. Shiki tokens now render with per-token colors (keywords, strings, identifiers) instead of flat monochrome text, and Code Mode
execute_typescriptprograms display as a formatted, highlighted TypeScript block instead of a one-line JSON string. (#17324) -
Improved studio load time by only bundling the CodeMirror and Shiki languages the editor actually uses, and removed a redundant TypeScript pass from the playground-ui build. (#17406)
-
Improved RadioGroup styling with neutral selected states, cleaner focus outlines, and surface-aware disabled states. (#17401)
-
Added a
DataPanel.SectionHeadingcomponent for small-caps section labels (with an optional leading icon) inside aDataPanel.Content.DataCodeSectionnow renders through it, andDataPanel.Headerhides its bottom border when the panel is collapsed (header-only) so an empty panel no longer shows a stray divider. (#17464)<DataPanel.SectionHeading icon={<FileInputIcon />}>Input</DataPanel.SectionHeading>
-
Pointer drags inside the
SideDialogbody now select text reliably instead of fighting with the close-swipe gesture. The popup chrome (header, edges) still closes the drawer on drag. (#16959)Drawer composition
DrawerContentis now the shadcn-style opinionated bundle (DrawerPortal+DrawerBackdrop+DrawerViewport+DrawerPopup, with a handle bar on top/bottom-anchored drawers and a fade-out when a nested drawer covers the parent). Most drawers can now be written as:<Drawer> <DrawerTrigger>…</DrawerTrigger> <DrawerContent> <DrawerHeader>…</DrawerHeader> <DrawerBody>…</DrawerBody> </DrawerContent> </Drawer>
The low-level primitives (
DrawerPortal,DrawerBackdrop,DrawerViewport,DrawerPopup) remain exported for drawers that need a custom portal target, non-modal page behavior, or chrome outside the popup (see theSwipeToOpenandNonModalStorybook examples).Base UI's text-selectable region (the
Drawer.Contentpart — pointer drags inside it select text instead of closing the drawer) is now exported asDrawerInteractive. Migration:// Before import { DrawerContent } from '@mastra/playground-ui'; <DrawerContent render={<div>...</div>} />; // After import { DrawerInteractive } from '@mastra/playground-ui'; <DrawerInteractive render={<div>...</div>} />;
-
Removed the unused
ElementSelectexport from@mastra/playground-ui. Use theSelectprimitives instead. (#17417)// Before import { ElementSelect } from '@mastra/playground-ui'; <ElementSelect name="status" value={status} onChange={setStatus} options={['Draft', 'Published']} />; // After import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@mastra/playground-ui'; <Select name="status" value={status} onValueChange={setStatus}> <SelectTrigger> <SelectValue placeholder="Select..." /> </SelectTrigger> <SelectContent> <SelectItem value="draft">Draft</SelectItem> <SelectItem value="published">Published</SelectItem> </SelectContent> </Select>;
-
Changed
Spinnerto render the new compact loader by default and addedvariant="pulse"for longer data-loading states. Removed thecolorprop so the loader defaults to the neutral text token and color overrides go throughclassName. (#17455)Migration note
Before:
<Spinner color={Colors.neutral3} />
After:
<Spinner className="text-neutral3" /> <Spinner variant="pulse" className="text-neutral1" />
-
Fixed dropdowns, menus, comboboxes, and popovers being unclickable when opened inside a SideDialog (for example the dataset selector in the "Save as Dataset Item" panel on the Traces tab). These popups now render inside the dialog so they stay interactive within the modal drawer. (#17479)
-
Agent Builder starter agents now use the admin-configured default model when the model policy has one set. Previously, the starter ignored the admin default and always picked the first entry from the picker allowlist, which surfaced as "default model gets over-written by agent builder" on agents created from starter cards or the freeform prompt. (#17424)
When no admin default is set, behavior is unchanged: the starter falls back to the first allowed model, then to the hardcoded fallback.
-
Improved
@mastra/playground-uistability by removing legacy runtime UI dependencies without changingSideDialog,MainSidebar, or accessibility behavior. NestedSideDialoglevels now stack consistently, so multi-level flows behave predictably. (#16959) -
Added an
is404NotFoundErrorhelper to detect 404 Not Found responses from the Mastra client, alongside the existingis401UnauthorizedErrorandis403ForbiddenErrorhelpers. Use it to show a clear not-found state when a resource no longer exists. (#17460)import { is404NotFoundError } from '@mastra/playground-ui'; try { await client.getDataset(id); } catch (error) { if (is404NotFoundError(error)) { // show a not-found state instead of a generic error } }
-
Improved Checkbox styling with neutral selected states, cleaner focus outlines, and smoother state transitions. (#17400)
-
Improved switch focus, disabled, and motion states. (#17416)
@mastra/react@0.4.3
Patch Changes
-
Fixed canonical user signal echoes so messages sent through the agent-signals path appear in chat history when they move from pending to active. (#17309)
-
Separated thread subscription cleanup from active-run aborts so closing or switching a listener only unsubscribes that listener, while explicit cancel still aborts the active run. (#17310)
-
Added subscription-native tool approval APIs so approving or declining a tool call resumes through the active thread subscription instead of requiring a separate continuation stream. New messages are queued while a tool approval is waiting, preventing overlapping runs from duplicating approval requests. (#17311)
await agent.sendToolApproval({ resourceId: 'user-123', threadId: 'thread-123', toolCallId: 'tool-call-123', approved: true, });
-
Enabled Studio via the CLI and deployers to use agent signal subscriptions by default while preserving
MASTRA_AGENT_SIGNALS=false,enableThreadSignals: false, and explicit legacy Stream as opt-outs. The ReactuseChat()hook remains opt-in for SDK consumers viaenableThreadSignals: true. (#17313)
@mastra/schema-compat@1.2.11
Patch Changes
-
Fixed Gemini REST tool calls failing for
z.discriminatedUnion,z.lazy, andz.tupleinputs.GoogleSchemaCompatLayernow rewrites JSON Schema 2020-12 keywords into the OpenAPI 3.0 Schema Object subset that Gemini expects:oneOf→anyOf,const→enum, tupleitems: [array]→items: { anyOf: [...] }, nullableanyOfcollapse,$refinlining with recursive schema support, and stripping of$schema/additionalProperties/propertyNames. Fixes #17057. (#17179) -
Fixed Zod 4 schemas with
.transform()producing the wrong JSON Schema for structured output and tool calling. The generated schema now describes the pre-transform input the model must produce instead of the post-transform output, so a field likez.string().transform(JSON.parse)is advertised as astringrather thanstring | number | boolean | null. (#17357)
@mastra/server@1.38.0
Minor Changes
-
Added an agent override export API and server-side ownership enforcement. (#17228)
The server and client now expose an agent override export endpoint so Studio can download an agent's overrides as JSON for review or commit workflows. Saves are enforced server-side against each agent's
editorconfig, so only owned fields (instructions, tools, or tool descriptions) are persisted and fields locked by theeditorconfig are stripped.The system packages response also reports the active editor
sourceso clients can render the correct editing experience. -
Added
isZodErrorhelper andZodErrorLiketype, exported from@mastra/server/server-adapter(and@mastra/server/handlers/error). Use these instead ofinstanceof ZodErrorwhen handling validation errors in custom server adapters or middleware so the check survives consumers that pin a differentzodpackage instance than the one bundled with@mastra/server. (#17172)import { isZodError } from '@mastra/server/server-adapter'; try { await schema.parseAsync(input); } catch (error) { if (isZodError(error)) { // structural check — works across zod v3/v4 realms return formatValidationError(error); } throw error; }
Underpins the fix for #17167.
-
Add fire-and-forget workflow resume that returns immediately with
{ runId }without awaiting the run output. (#17230)For
@mastra/inngest, this skips thegetRunOutput()polling that previously raced a realtime subscription against the Inngest runs API and could surface spurious 404s even though the durable workflow was running fine.Run.resumeAsync()added to core: dispatches the resume in the background and returns{ runId }immediately. Engines that poll for results (Inngest) override it to skip polling entirely.InngestRun.resumeAsync()sends the resume event and returns{ runId }, skipping polling. Send-time failures (bad payload, event send failure) still reject synchronously and roll back the snapshot.- New
POST /workflows/:workflowId/resume-no-waitandPOST /agent-builder/:actionId/resume-no-waitroutes return{ runId }immediately. - New client SDK
run.resumeNoWait()resolves with{ runId }.
The existing
resumeAsync()client/server surface is unchanged and still resolves with the full workflow result, so there is no breaking change.resumeNoWaitis intentionally additive in v1. In Mastra v2 the fire-and-forget behavior is planned to become the default behavior ofresumeAsync()(mirroringstart/resumesemantics), at which pointresumeNoWaitand theresume-no-waitroutes will be removed. The code paths carryTODO(v2)comments documenting this consolidation. -
Add experimental HTTP message routes for agent threads. Servers now expose
POST /agents/:agentId/send-messageandPOST /agents/:agentId/queue-messagefor message-first input while keeping/agents/:agentId/signalsavailable for lower-level signals and compatibility. (#17237) -
Added a
PATCH /tool-providers/:providerId/connections/:connectionIdendpoint and matching client SDK method so authors can rename a connection's display label after creation. (#17249)Rename a connection from the client SDK
import { MastraClient } from '@mastra/client-js'; const client = new MastraClient({ baseUrl: '…' }); await client.getToolProvider('composio').updateConnection('auth_abc', { label: 'Work inbox', });
Pass
label: null(or an empty string) to clear the existing label. Labels are 1–32 characters and accept letters, digits, spaces, underscores, and hyphens ([A-Za-z0-9 _-]+).Ownership enforced server-side
Non-owners get a 403 unless they hold
tool-providers:admin. Shared connections are reachable by every author. The label is stored on the connection row itself, so the rename flows to every agent that pins the connection — no per-agent edit needed. -
Added the v1 ToolProvider runtime, server routes, client SDK methods, and editor wiring that power OAuth-backed integrations on stored agents. (#17248)
Stored agents can now pin OAuth connections per toolkit
A stored agent's config accepts a new
toolProvidersshape that tells the runtime which connection to bind for each toolkit at execution time. Connections can be scoped per-author, shared across an org, or supplied by the caller.{ toolProviders: { composio: { connections: { gmail: [{ kind: 'author', toolkit: 'gmail', connectionId: 'auth_abc', scope: 'per-author' }], }, tools: { GMAIL_FETCH_EMAILS: { toolkit: 'gmail' }, }, }, }, }
New client SDK surface for managing connections
import { MastraClient } from '@mastra/client-js'; const client = new MastraClient({ baseUrl: '…' }); const composio = client.toolProvider('composio'); const { items } = await composio.listConnections({ toolkit: 'gmail' }); await composio.disconnectConnection('auth_abc');
New
ToolProviderinterface for custom providersProviders implement a VNext surface (
listToolkitsVNext,listToolsVNext,resolveToolsVNext) plus the auth round-trip (authorize,getAuthStatus,listConnections,disconnectConnection,listConnectionFields,health). The Composio provider has been rewritten on this surface; the older catalog methods remain as@deprecatedshims for back-compat.Connections list responses use
page/perPagepagination, matching the rest of the server surface.Both stored agents (
editor.agent.getById(...)) and code-defined agents with stored overrides (editor.agent.applyStoredOverrides(...)) resolvetoolProvidersat request time, merging provider-resolved tools alongside code/registry/MCP/integration tools.Stored agents that don't set
toolProviderscontinue to work unchanged. The Studio/Builder UI ships separately.
Patch Changes
-
Separated thread subscription cleanup from active-run aborts so closing or switching a listener only unsubscribes that listener, while explicit cancel still aborts the active run. (#17310)
-
Added sseFlushOnConnect route option to scope the SSE connected comment to subscribe endpoints only (#17158)
-
Added subscription-native tool approval APIs so approving or declining a tool call resumes through the active thread subscription instead of requiring a separate continuation stream. New messages are queued while a tool approval is waiting, preventing overlapping runs from duplicating approval requests. (#17311)
await agent.sendToolApproval({ resourceId: 'user-123', threadId: 'thread-123', toolCallId: 'tool-call-123', approved: true, });
-
Moved shared voice primitives and route metadata into the new
@internal/voicepackage so voice providers no longer depend on@mastra/coreand server voice routes share the same route definitions. (#16725)@mastra/core/voicecontinues to re-export the voice APIs for backwards compatibility. -
Fix custom providers appearing twice in Studio's provider selector. (#17441)
In dev mode,
GatewayRegistryregisters custom gateways soPROVIDER_REGISTRYalready contains them with prefixed keys (e.g."melioffice/genai"). The/agents/providershandler then calledgateway.fetchProviders()again and re-added them, but the live call returns raw unprefixed keys (e.g."genai") which after prefixing produce the same key — however in some cases the keys differed, causing both entries to appear in the UI.The fix skips adding a provider from the live
fetchProviders()call if it is already present inallProvidersfromPROVIDER_REGISTRY. -
Fixed a startup crash that affected deployments pinning an older
@mastra/coreversion. The server now boots successfully even when the installed@mastra/coredoesn't include the Agent Builder runtime. (#17382)Symptom
Deployed servers failed to start with
ERR_MODULE_NOT_FOUNDpointing at@mastra/core/dist/agent-builder/ee/index.js, even on apps that never used the Agent Builder.What changed
The server no longer eagerly loads the Agent Builder runtime at boot. It's loaded on demand, only when a request actually needs it on an app that has configured a
MastraEditorwith builder support.No application code changes required.
-
Fixed sub-agent version resolution in supervisor mode. Sub-agents now inherit the parent's draft/published version semantics — when chatting in the editor (draft mode), sub-agents resolve to their latest draft version; in the main agent chat (published mode), sub-agents resolve to their published version. Previously, sub-agents without explicit per-agent version overrides always fell back to the code-defined default, ignoring the parent's version context. (#17165)
-
Stored agent and skill POST, PATCH, and skill publish responses now include
isFavorited, matching GET behavior. Clients can read favorite status from write responses without an extra GET request. (#17246)Under auth-off, write responses also omit
favoriteCountto match GET, so the response shape is consistent across all single-entity endpoints. -
Fixed memory status reporting for agents that do not support Mastra memory. The memory status endpoint now preserves storage fallback for regular agents while allowing integrations to opt out of memory UI. (#16906)
-
Scoped the SSE connected comment to subscribe routes only and added SSE comment passthrough for Fastify and NestJS adapters (#17158)
-
Hardened v1 ToolProvider connection routes and SDK forwarding. (#17248)
Fail closed on unknown
connectionIdDELETE /tool-providers/:providerId/connections/:connectionIdand
GET …/usagenow return403when storage is configured but no persisted
row matches the suppliedconnectionIdand the caller isn't an admin.
Previously these routes fell through to the caller's ownauthorId, which
let non-admin callers probe (and trigger provider-siderevokeConnection
for) IDs that didn't belong to them.Aligned authorize label validation with stored label rules
POST /tool-providers/:providerId/authorizenow enforces the same label
rules the storedtoolProvidersconfig uses (min(1),max(32),
/^[A-Za-z0-9 _-]+$/). Labels that passauthorizeare now guaranteed to
pass downstream stored-agent validation.SDK forwards
toolkiton connection-scoped operations@mastra/client-js:await client.toolProviders.get('composio').disconnectConnection('ca_xxx', { toolkit: 'gmail', force: true, }); const usage = await client.toolProviders.get('composio').getConnectionUsage('ca_xxx', { toolkit: 'gmail' });
disconnectConnectionnow forwardsparams.toolkit(previously dropped)
andgetConnectionUsageaccepts an optional{ toolkit }parameter so
toolkit-scoped connection lookups disambiguate correctly server-side. -
Lazy-load
@mastra/core/tool-providerinside the tool-provider handler so (#17248)
@mastra/serverevaluates under any peer-compatible@mastra/core(peer floor
remains>=1.34.0-0). The handler no longer importsSHARED_BUCKET_IDor
UnknownToolProviderErrorat module load —SHARED_BUCKET_IDis mirrored as a
local literal (verified in lockstep with core via a regression test), and
UnknownToolProviderErroris resolved via a cachedawait import(...)inside
resolveProviderso the real class identity is preserved forinstanceof.OSS users running
Mastrawithout aMastraEditorare unaffected: every
tool-provider route still short-circuits with HTTP 500 "Editor is not
configured" viarequireEditor(...)before any core/tool-provider value is
touched. Users with aMastraEditoralready pull a compatible core
transitively through@mastra/editor. Tool-provider routes require the new
core exports at request time only — older cores surface a clear runtime error
instead of crashing the server at boot. -
Improved observability and error isolation in the v1 ToolProvider runtime. (#17248)
Better visibility into connection-scope misconfiguration
When an agent runs with a stored ToolProvider connection whose scope cannot be resolved from the request context, the runtime now logs a one-shot warning and falls back to a shared bucket instead of silently routing every caller to the same OAuth account. Multi-tenant deployments get a clear signal when their identity wiring isn't reaching the runtime.
One bad toolkit no longer disables sibling providers
If a provider returns more connections for a toolkit than its declared capabilities allow, the runtime now logs and skips that toolkit instead of throwing. Other providers and other toolkits on the same agent continue to resolve normally.
-
Workflows now support an optional
metadatafield for attaching custom key-value data such asdisplayName,author, orcategory. Metadata is preserved through serialization and returned in workflow info API responses. (#17355)// Define a workflow with metadata const myWorkflow = createWorkflow({ id: 'data-processing', metadata: { displayName: 'Data Processing Pipeline', category: 'ETL', }, inputSchema: z.object({ ... }), outputSchema: z.object({ ... }), }); // Retrieve workflow info with metadata via the Mastra Server API const workflowInfo = await mastraClient.getWorkflow('data-processing'); console.log(workflowInfo.metadata?.displayName); // "Data Processing Pipeline"
@mastra/spanner@1.1.0
Minor Changes
-
Added five new storage domains to the Google Cloud Spanner adapter: workspaces, datasets, experiments, favorites, and channels. The Spanner store now covers the full set of editor and evaluation domains. (#17472)
What's new
- Datasets versioned dataset items with historical snapshots and as-of reads (time-travel reads, per-item history, batched insert/delete).
- Experiments with per-item results, review-status aggregation, and pagination.
- Workspaces with versioned configuration snapshots (filesystem, sandbox, mounts, search, skills, tools), mirroring the existing thin-record + versions pattern.
- Favorites for agents and skills, maintaining a denormalized
favoriteCounton the parent record atomically. - Channels for multi-platform installations and per-platform configuration.
Enabling favorites also adds favorited-first ordering and
favoritedOnly/entityIdsfiltering toagents.list()andskills.list(), and surfacesfavoriteCounton skill records.const storage = new SpannerStore({ id: 'spanner-storage', projectId: process.env.SPANNER_PROJECT_ID!, instanceId: process.env.SPANNER_INSTANCE_ID!, databaseId: process.env.SPANNER_DATABASE_ID!, }); const datasets = await storage.getStore('datasets'); const ds = await datasets?.createDataset({ name: 'eval-set' }); const favorites = await storage.getStore('favorites'); await favorites?.favorite({ userId: 'u1', entityType: 'agent', entityId: 'agent-1' });
Patch Changes
@mastra/vercel@0.2.0
Minor Changes
-
Added
VercelMicroVMSandbox, a new workspace sandbox provider backed by the Vercel Sandbox ephemeral Firecracker MicroVM product (@vercel/sandbox). It provides a persistent in-session filesystem,sudoaccess, exposed ports, command execution, and background processes via the process manager. This is distinct from the existingVercelSandbox, which runs commands as stateless Vercel serverless Functions and is unchanged. Also exportsVercelMicroVMProcessManagerand thevercelMicroVMSandboxProvidereditor descriptor (provider idvercel-microvm). Closes #16704. (#17332)import { Workspace } from '@mastra/core/workspace'; import { VercelMicroVMSandbox } from '@mastra/vercel'; const workspace = new Workspace({ sandbox: new VercelMicroVMSandbox({ runtime: 'node24', timeout: 600_000, ports: [3000], }), }); await workspace.init(); const result = await workspace.sandbox.executeCommand('node', ['--version']);
Patch Changes
@mastra/voice-aws-nova-sonic@0.1.2
Patch Changes
-
Moved shared voice primitives and route metadata into the new
@internal/voicepackage so voice providers no longer depend on@mastra/coreand server voice routes share the same route definitions. (#16725)@mastra/core/voicecontinues to re-export the voice APIs for backwards compatibility.
@mastra/voice-azure@0.11.1
Patch Changes
-
Moved shared voice primitives and route metadata into the new
@internal/voicepackage so voice providers no longer depend on@mastra/coreand server voice routes share the same route definitions. (#16725)@mastra/core/voicecontinues to re-export the voice APIs for backwards compatibility.
@mastra/voice-cloudflare@0.12.2
Patch Changes
-
Moved shared voice primitives and route metadata into the new
@internal/voicepackage so voice providers no longer depend on@mastra/coreand server voice routes share the same route definitions. (#16725)@mastra/core/voicecontinues to re-export the voice APIs for backwards compatibility.
@mastra/voice-deepgram@0.12.1
Patch Changes
-
Moved shared voice primitives and route metadata into the new
@internal/voicepackage so voice providers no longer depend on@mastra/coreand server voice routes share the same route definitions. (#16725)@mastra/core/voicecontinues to re-export the voice APIs for backwards compatibility.
@mastra/voice-elevenlabs@0.12.1
Patch Changes
-
Moved shared voice primitives and route metadata into the new
@internal/voicepackage so voice providers no longer depend on@mastra/coreand server voice routes share the same route definitions. (#16725)@mastra/core/voicecontinues to re-export the voice APIs for backwards compatibility.
@mastra/voice-gladia@0.12.1
Patch Changes
-
Moved shared voice primitives and route metadata into the new
@internal/voicepackage so voice providers no longer depend on@mastra/coreand server voice routes share the same route definitions. (#16725)@mastra/core/voicecontinues to re-export the voice APIs for backwards compatibility.