Skip to content

mastra

v@mastra/[email protected] Breaking

This release includes breaking changes for platform teams planning a safe upgrade.

✓ No known CVEs patched
Read the diff → Tool health → What is this tool? →

✓ No known CVEs patched in this version

Topics

agents ai chatbots evals javascript llm
+7 more
mcp nextjs nodejs reactjs tts typescript workflows

Affected surfaces

auth rbac breaking_upgrade

Summary

AI summary

Broad release touches Patch Changes, Minor Changes, Highlights, and https://github.com/mastra-ai/mastra/issues/17167.

Full changelog

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/[email protected]

Minor Changes

  • Added channels.threadContext.addSystemMessage to opt out of the built-in channel system message. By default, AgentChannels injects a short system message telling the agent which channel/platform a request came from (DM vs public, bot identity, etc.). Set addSystemMessage: false to skip it: (#17171)

    new Agent({
      channels: {
        adapters: { slack: createSlackAdapter() },
        threadContext: {
          addSystemMessage: false,
        },
      },
    });
    
  • Added agent.sendMessage() and agent.queueMessage() APIs for sending user-authored input into agent threads. These are intended to be used with agent.subscribeToThread() and replace lower-level agent.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 requireToolApproval function 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. CoreToolBuilder now preserves a needsApprovalFn attached directly to a tool instance.
    • The global requireToolApproval option on agent.stream/agent.generate now 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. Returning true requires approval for that call; false allows 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-derived needsApprovalFn) is authoritative for that tool and overrides the global setting, so a tool can still opt out of approval by returning false even 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/core exports NeedsApprovalFn and declares the optional needsApprovalFn property on the Tool class. The MCP client and the agent runtime now share this typed contract instead of reaching through any. This is additive — no public API changes.
  • 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 editor option:

    new Agent({
      name: 'Weather Agent',
      model,
      editor: {
        instructions: true,
        tools: { description: true },
      },
    });
    

    The editor applies stored overrides only for fields the editor config owns, so locked fields keep their code-defined values. Per-agent editor: false locks an agent entirely.

    MastraEditor accepts a source setting that picks the editing experience:

    new MastraEditor({ source: 'code' });
    
    • source: 'code' — the editor auto-wires a FilesystemStore (defaulting to ./mastra/editor/, overridable with codePath) 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_connections storage 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: true per 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 the getRunOutput() 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-wait and POST /agent-builder/:actionId/resume-no-wait routes 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.

    resumeNoWait is intentionally additive in v1. In Mastra v2 the fire-and-forget behavior is planned to become the default behavior of resumeAsync() (mirroring start/resume semantics), at which point resumeNoWait and the resume-no-wait routes will be removed. The code paths carry TODO(v2) comments documenting this consolidation.

  • Added experimental Code Mode for agents. createCodeMode returns an execute_typescript tool plus generated instructions that let an agent write one TypeScript program to orchestrate your tools (batch with Promise.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)

    FilesystemVersionedHelpers now 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 in listVersions (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
    removeDomainFile helpers, and broadens GitHistory.getFileAtCommit to 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 voice per request. (#17345)

    You can now pass voice as a resolver, just like instructions, tools, and model. Mastra runs the resolver on each getVoice() 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 voice keeps 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. The agent.voice getter throws when voice is a resolver because it has no request context; use agent.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 resumeSteps keyed 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.resumeSteps or metadata.resumeData as 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.resolveResourceId to control which resourceId owns 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 disableInit option to the MastraVector base class. When set to true, vector stores skip creating schemas, extensions, tables, and indexes at application startup. This matches the existing disableInit behavior 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_INIT environment 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)
    via MastraCompositeStore.__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 StorageMastraRef interface 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 toolProviders shape 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 ToolProvider interface for custom providers

    Providers 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 @deprecated shims for back-compat.

    Connections list responses use page/perPage pagination, matching the rest of the server surface.

    Both stored agents (editor.agent.getById(...)) and code-defined agents with stored overrides (editor.agent.applyStoredOverrides(...)) resolve toolProviders at request time, merging provider-resolved tools alongside code/registry/MCP/integration tools.

    Stored agents that don't set toolProviders continue 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 filterMessagesForPersistence unconditionally 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() and agent.resumeGenerate() use the resume payload as the AGENT_RUN span 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 systemMessages wiping tagged system messages owned by other processors (e.g. observational memory). Processor args.systemMessages now 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 via messageList.getAllSystemMessages(). (#16950)

  • Moved shared voice primitives and route metadata into the new @internal/voice package so voice providers no longer depend on @mastra/core and server voice routes share the same route definitions. (#16725)

    @mastra/core/voice continues to re-export the voice APIs for backwards compatibility.

  • Fixed AgentExecutionOptions<undefined> (and its public/inner variants) incorrectly requiring a structuredOutput property. When the output type is undefined or null — including a fully-nullish union such as undefined | nullstructuredOutput is now correctly optional, regardless of the strictNullChecks setting. This is the shape produced by AgentConfig.defaultOptions, so defaultOptions: { maxSteps: 50 } now type-checks without a spurious structuredOutput requirement. Object output types still require structuredOutput as 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 jsonb column (workflow snapshot) and text column (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 stream outputWriter now share the same per-processor state map as the main model-output path, so state set in processOutputStream while handling content chunks is visible when the lifecycle chunk is processed (and in processOutputResult). 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-start chunk and partial text) 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 a close() lifecycle hook (such as LibSQLStore) 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 updateThread as an abstract method on the MastraMemory base class and implement it in MockMemory. Previously the method existed only on the concrete Memory subclass, so calling updateThread on a variable typed as MastraMemory (or any other MastraMemory subclass) 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() and agent.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 jsonPromptInjection to the scorer judge config so users can opt out of native response_format for 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 content parts into model-native media output when building model prompts, without requiring MCP tools to persist duplicate media payloads in providerMetadata.mastra.modelOutput. (#16866)

    return {
      content: [
        { type: 'text', text: 'Screenshot captured' },
        { type: 'image', data: base64Png, mimeType: 'image/png' },
      ],
    };
    
  • Fixed MASTRA_TELEMETRY_DISABLED opt-out detection. The values 1, true, and yes (case-insensitive, trimmed) now reliably disable telemetry in both @mastra/core enterprise events and the mastra CLI's PostHog analytics. (#16990)

    Previously, @mastra/core only treated the literal string '1' as disabled, so common opt-out values like MASTRA_TELEMETRY_DISABLED=true silently kept telemetry on.

    The mastra CLI's PosthogAnalytics constructor 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 bufferSize option (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 metadata field for attaching custom key-value data such as displayName, author, or category. 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/[email protected]

Minor Changes

  • Add waitUntil support to browser_click, browser_press, and browser_select. When provided, the tool waits for the page to reach the given load state (load, domcontentloaded, or networkidle) after the action completes, preventing the next browser_snapshot from 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 createThreadManager config option to inject a custom thread manager factory
    • Exported AgentBrowserThreadManager class and related types (AgentBrowserSession, AgentBrowserThreadManagerConfig, CreateAgentBrowserThreadManager)
    • Changed several internal members from private to protected to support subclassing

Patch Changes

@mastra/[email protected]

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/[email protected]

Patch Changes

  • Fixed processor middleware so args.systemMessages only 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/[email protected]

Minor Changes

  • Added region support to Blaxel sandboxes: new BlaxelSandbox({ region: 'eu-west-1' }). When omitted, Mastra falls back to BL_REGION and then auto. (#16555)

Patch Changes

@mastra/[email protected]

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-normalizes language before the request, and structured JSON (brd_json=1) is only requested when the search format is json so callers can obtain a true raw SERP response. (#17341)

@mastra/[email protected]

Minor Changes

  • Initial release: Firecrawl Browser Sandbox integration for Mastra. (#15724)

    FirecrawlBrowser extends AgentBrowser to 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/[email protected]

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() or stream() 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/[email protected]

Patch Changes

  • Added the tool_provider_connections storage 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/[email protected]

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 editor config, so only owned fields (instructions, tools, or tool descriptions) are persisted and fields locked by the editor config are stripped.

    The system packages response also reports the active editor source so 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 the getRunOutput() 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-wait and POST /agent-builder/:actionId/resume-no-wait routes 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.

    resumeNoWait is intentionally additive in v1. In Mastra v2 the fire-and-forget behavior is planned to become the default behavior of resumeAsync() (mirroring start/resume semantics), at which point resumeNoWait and the resume-no-wait routes will be removed. The code paths carry TODO(v2) comments documenting this consolidation.

  • Added a PATCH /tool-providers/:providerId/connections/:connectionId endpoint 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 toolProviders shape 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 ToolProvider interface for custom providers

    Providers 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 @deprecated shims for back-compat.

    Connections list responses use page/perPage pagination, matching the rest of the server surface.

    Both stored agents (editor.agent.getById(...)) and code-defined agents with stored overrides (editor.agent.applyStoredOverrides(...)) resolve toolProviders at request time, merging provider-resolved tools alongside code/registry/MCP/integration tools.

    Stored agents that don't set toolProviders continue 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 connectionId

    DELETE /tool-providers/:providerId/connections/:connectionId and
    GET …/usage now return 403 when storage is configured but no persisted
    row matches the supplied connectionId and the caller isn't an admin.
    Previously these routes fell through to the caller's own authorId, which
    let non-admin callers probe (and trigger provider-side revokeConnection
    for) IDs that didn't belong to them.

    Aligned authorize label validation with stored label rules

    POST /tool-providers/:providerId/authorize now enforces the same label
    rules the stored toolProviders config uses (min(1), max(32),
    /^[A-Za-z0-9 _-]+$/). Labels that pass authorize are now guaranteed to
    pass downstream stored-agent validation.

    SDK forwards toolkit on 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' });
    

    disconnectConnection now forwards params.toolkit (previously dropped)
    and getConnectionUsage accepts 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 metadata field for attaching custom key-value data such as displayName, author, or category. 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/[email protected]

Patch Changes

  • Added the tool_provider_connections storage 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/[email protected]

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/[email protected]

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() or stream() 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/[email protected]

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/[email protected]

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 served index.html, so window.MASTRA_TELEMETRY_DISABLED was always falsy in the browser and the playground React app initialized PostHog regardless of the user's .env. The dev server now propagates process.env.MASTRA_TELEMETRY_DISABLED to the browser, where the playground applies the same canonical opt-out parsing as the rest of the framework. (#16990)

    Before: Setting MASTRA_TELEMETRY_DISABLED=true in .env had no effect on playground network requests to PostHog.

    After:

    # .env
    MASTRA_TELEMETRY_DISABLED=true
    

    Playground 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 React useChat() hook remains opt-in for SDK consumers via enableThreadSignals: true. (#17313)

@mastra/[email protected]

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 React useChat() hook remains opt-in for SDK consumers via enableThreadSignals: true. (#17313)

@mastra/[email protected]

Minor Changes

  • Added name option to DockerSandbox for setting the container's display name. (#17266)

    Previously, DockerSandbox did not forward a container name to Docker, so containers were created with random names like gracious_tu even though the docs implied id was used for naming. The new name option (defaults to id) is now passed to docker run --name and 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/[email protected]

Patch Changes

  • Fixed DuckDB "Conflicting lock is held" error on mastra dev hot reload. DuckDBStore now releases its native file lock on shutdown so the restarted dev process can reopen the same database file. (#17413)

@mastra/[email protected]

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 with lifecycle: { onTimeout: 'pause' } (replacing the removed betaCreate/autoPause), and requires e2b >= 2.24.0. (#17261)

@mastra/[email protected]

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 editor option:

    new Agent({
      name: 'Weather Agent',
      model,
      editor: {
        instructions: true,
        tools: { description: true },
      },
    });
    

    The editor applies stored overrides only for fields the editor config owns, so locked fields keep their code-defined values. Per-agent editor: false locks an agent entirely.

    MastraEditor accepts a source setting that picks the editing experience:

    new MastraEditor({ source: 'code' });
    
    • source: 'code' — the editor auto-wires a FilesystemStore (defaulting to ./mastra/editor/, overridable with codePath) 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 toolProviders shape 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 ToolProvider interface for custom providers

    Providers 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 @deprecated shims for back-compat.

    Connections list responses use page/perPage pagination, matching the rest of the server surface.

    Both stored agents (editor.agent.getById(...)) and code-defined agents with stored overrides (editor.agent.applyStoredOverrides(...)) resolve toolProviders at request time, merging provider-resolved tools alongside code/registry/MCP/integration tools.

    Stored agents that don't set toolProviders continue 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, and overloaded), 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 own errorProcessors to createBuilderAgent to extend or replace these defaults. (#17481)

@mastra/[email protected]

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/[email protected]

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, or pathParamSchema losing field path information when consumers pin zod@^3. Responses now return the actual field name in issues[].field (e.g. "agent_id") instead of "unknown" with the raw Zod issues serialized into issues[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/[email protected]

Patch Changes

  • Fixed FastEmbed so repeated embedding calls reuse loaded models instead of loading a new model each time. (#17303)

@mastra/[email protected]

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-Origin from @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, or pathParamSchema losing field path information when consumers pin zod@^3. Responses now return the actual field name in issues[].field (e.g. "agent_id") instead of "unknown" with the raw Zod issues serialized into issues[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/[email protected]

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, or pathParamSchema losing field path information when consumers pin zod@^3. Responses now return the actual field name in issues[].field (e.g. "agent_id") instead of "unknown" with the raw Zod issues serialized into issues[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/[email protected]

Minor Changes

  • Add fire-and-forget workflow resume that returns immediately with { runId } without awaiting the run output. (#17230)

    For @mastra/inngest, this skips the getRunOutput() 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-wait and POST /agent-builder/:actionId/resume-no-wait routes 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.

    resumeNoWait is intentionally additive in v1. In Mastra v2 the fire-and-forget behavior is planned to become the default behavior of resumeAsync() (mirroring start/resume semantics), at which point resumeNoWait and the resume-no-wait routes will be removed. The code paths carry TODO(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 as serve(), including nested and cron workflows. serve() is unchanged.

Patch Changes

  • Fixed processor workflow steps so args.systemMessages only 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/[email protected]

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, or pathParamSchema losing field path information when consumers pin zod@^3. Responses now return the actual field name in issues[].field (e.g. "agent_id") instead of "unknown" with the raw Zod issues serialized into issues[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/[email protected]

Minor Changes

  • Added the tool_provider_connections storage 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 to LibSQLStore that releases SQLite file handles and cleans up the WAL/shm sidecar files. Previously these handles stayed open until the process exited, which on Windows caused EBUSY errors when removing the storage directory after shutdown. Mastra.shutdown() now calls close() 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/[email protected]

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/[email protected]

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: true per 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 content parts into model-native media output when building model prompts, without requiring MCP tools to persist duplicate media payloads in providerMetadata.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 requireToolApproval function 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. CoreToolBuilder now preserves a needsApprovalFn attached directly to a tool instance.
    • The global requireToolApproval option on agent.stream/agent.generate now 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. Returning true requires approval for that call; false allows 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-derived needsApprovalFn) is authoritative for that tool and overrides the global setting, so a tool can still opt out of approval by returning false even 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/core exports NeedsApprovalFn and declares the optional needsApprovalFn property on the Tool class. The MCP client and the agent runtime now share this typed contract instead of reaching through any. This is additive — no public API changes.
  • 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/[email protected]

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.bufferOnIdle to opt idle turns into background observation buffering and carry the signal sender needed for background notifications. (#17181)

  • Fixed Memory.saveMessages not populating role, content, and created_at in the vector store metadata. Calls to GET /api/memory/search now return matches with the full message shape regardless of whether messages were saved through agent.generate/agent.stream or written directly via Memory.saveMessages (for example through the POST /api/memory/save-messages HTTP route used by external agents). (#16381)

@mastra/[email protected]

Patch Changes

  • Fixed sandbox execution results to report killed and timed out commands. (#17281)

@mastra/[email protected]

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/[email protected]

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, or pathParamSchema losing field path information when consumers pin a different zod major than the one bundled with this adapter. Responses now return the actual field name in issues[].field (e.g. "agent_id") instead of "unknown" with the raw Zod issues serialized into issues[0].message. (#17172)

    ValidationError.zodError is now typed as ZodErrorLike (a structural subset of ZodError exposing issues[]) so consumers pinning a different zod major still type-check. The runtime value is unchanged; cast to your installed ZodError type 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/[email protected]

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 estimatedCost for 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/[email protected]

Minor Changes

  • Added the disableInit option to the MastraVector base class. When set to true, vector stores skip creating schemas, extensions, tables, and indexes at application startup. This matches the existing disableInit behavior 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_INIT environment 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 PostgresStore ignoring an explicit ssl option when the connectionString also carries an sslmode=/ssl= query param. node-postgres re-parses the connection string and Object.assigns the URL-derived ssl over the explicit one, so a config like { connectionString: '...?sslmode=require', ssl: { rejectUnauthorized: false } } silently dropped rejectUnauthorized: false and failed with UNABLE_TO_GET_ISSUER_CERT_LOCALLY against self-signed CAs. The connection-string branch now parses the URL and applies the explicit ssl last, while still honoring URL-driven SSL when no ssl option is provided. Fixes #17307. (#17356)

  • Improved Postgres memory message save performance (#17351)

@mastra/[email protected]

Minor Changes

  • Made ButtonsGroup compose joined controls (searchbar + dropdown pills, split buttons, steppers) cleanly, and improved InputGroup so it drops straight into one. (#17259)

    • ButtonsGroup with spacing="close" fuses outline, filled and Select segments into one pill with a single clean divider, a complete focus ring (no missing side), and no consumer width classes.
    • InputGroup fills a flex row on its own, matches a same-size sibling height, and propagates size via data-size (no React context) — so an icon + input segment composes inside a ButtonsGroup pill with no layout classes.

    Use InputGroup (icon as an InputGroupAddon, optional clear button as an InputGroupButton) 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 to Input, InputGroup, Searchbar, and Textarea. (#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, and InputGroup with a block-style addon) keep a softer rounded-xl corner.
    • Added filled and outline variants for consumers that need to choose between the new surface treatment and a quieter border-only treatment.
    • The unstyled variant of Input and Textarea no longer leaks the browser default focus outline.

    Input, Textarea, and InputGroup default to the filled surface. Searchbar and ListSearch default to the outline (transparent) treatment. For Searchbar this matches its previous transparent look. ListSearch previously rendered a filled (bg-surface2), rounded-lg box, so its search fields across the list pages now read as transparent, fully-rounded pills — pass variant="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_typescript programs 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.SectionHeading component for small-caps section labels (with an optional leading icon) inside a DataPanel.Content. DataCodeSection now renders through it, and DataPanel.Header hides 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 SideDialog body 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

    DrawerContent is 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 the SwipeToOpen and NonModal Storybook examples).

    Base UI's text-selectable region (the Drawer.Content part — pointer drags inside it select text instead of closing the drawer) is now exported as DrawerInteractive. 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 ElementSelect export from @mastra/playground-ui. Use the Select primitives 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 Spinner to render the new compact loader by default and added variant="pulse" for longer data-loading states. Removed the color prop so the loader defaults to the neutral text token and color overrides go through className. (#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-ui stability by removing legacy runtime UI dependencies without changing SideDialog, MainSidebar, or accessibility behavior. Nested SideDialog levels now stack consistently, so multi-level flows behave predictably. (#16959)

  • Added an is404NotFoundError helper to detect 404 Not Found responses from the Mastra client, alongside the existing is401UnauthorizedError and is403ForbiddenError helpers. 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/[email protected]

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 React useChat() hook remains opt-in for SDK consumers via enableThreadSignals: true. (#17313)

@mastra/[email protected]

Patch Changes

  • Fixed Gemini REST tool calls failing for z.discriminatedUnion, z.lazy, and z.tuple inputs. GoogleSchemaCompatLayer now rewrites JSON Schema 2020-12 keywords into the OpenAPI 3.0 Schema Object subset that Gemini expects: oneOfanyOf, constenum, tuple items: [array]items: { anyOf: [...] }, nullable anyOf collapse, $ref inlining 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 like z.string().transform(JSON.parse) is advertised as a string rather than string | number | boolean | null. (#17357)

@mastra/[email protected]

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 editor config, so only owned fields (instructions, tools, or tool descriptions) are persisted and fields locked by the editor config are stripped.

    The system packages response also reports the active editor source so clients can render the correct editing experience.

  • Added isZodError helper and ZodErrorLike type, exported from @mastra/server/server-adapter (and @mastra/server/handlers/error). Use these instead of instanceof ZodError when handling validation errors in custom server adapters or middleware so the check survives consumers that pin a different zod package 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 the getRunOutput() 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-wait and POST /agent-builder/:actionId/resume-no-wait routes 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.

    resumeNoWait is intentionally additive in v1. In Mastra v2 the fire-and-forget behavior is planned to become the default behavior of resumeAsync() (mirroring start/resume semantics), at which point resumeNoWait and the resume-no-wait routes will be removed. The code paths carry TODO(v2) comments documenting this consolidation.

  • Add experimental HTTP message routes for agent threads. Servers now expose POST /agents/:agentId/send-message and POST /agents/:agentId/queue-message for message-first input while keeping /agents/:agentId/signals available for lower-level signals and compatibility. (#17237)

  • Added a PATCH /tool-providers/:providerId/connections/:connectionId endpoint 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 toolProviders shape 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 ToolProvider interface for custom providers

    Providers 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 @deprecated shims for back-compat.

    Connections list responses use page/perPage pagination, matching the rest of the server surface.

    Both stored agents (editor.agent.getById(...)) and code-defined agents with stored overrides (editor.agent.applyStoredOverrides(...)) resolve toolProviders at request time, merging provider-resolved tools alongside code/registry/MCP/integration tools.

    Stored agents that don't set toolProviders continue 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/voice package so voice providers no longer depend on @mastra/core and server voice routes share the same route definitions. (#16725)

    @mastra/core/voice continues to re-export the voice APIs for backwards compatibility.

  • Fix custom providers appearing twice in Studio's provider selector. (#17441)

    In dev mode, GatewayRegistry registers custom gateways so PROVIDER_REGISTRY already contains them with prefixed keys (e.g. "melioffice/genai"). The /agents/providers handler then called gateway.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 in allProviders from PROVIDER_REGISTRY.

  • Fixed a startup crash that affected deployments pinning an older @mastra/core version. The server now boots successfully even when the installed @mastra/core doesn't include the Agent Builder runtime. (#17382)

    Symptom

    Deployed servers failed to start with ERR_MODULE_NOT_FOUND pointing 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 MastraEditor with 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 favoriteCount to 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 connectionId

    DELETE /tool-providers/:providerId/connections/:connectionId and
    GET …/usage now return 403 when storage is configured but no persisted
    row matches the supplied connectionId and the caller isn't an admin.
    Previously these routes fell through to the caller's own authorId, which
    let non-admin callers probe (and trigger provider-side revokeConnection
    for) IDs that didn't belong to them.

    Aligned authorize label validation with stored label rules

    POST /tool-providers/:providerId/authorize now enforces the same label
    rules the stored toolProviders config uses (min(1), max(32),
    /^[A-Za-z0-9 _-]+$/). Labels that pass authorize are now guaranteed to
    pass downstream stored-agent validation.

    SDK forwards toolkit on 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' });
    

    disconnectConnection now forwards params.toolkit (previously dropped)
    and getConnectionUsage accepts an optional { toolkit } parameter so
    toolkit-scoped connection lookups disambiguate correctly server-side.

  • Lazy-load @mastra/core/tool-provider inside the tool-provider handler so (#17248)
    @mastra/server evaluates under any peer-compatible @mastra/core (peer floor
    remains >=1.34.0-0). The handler no longer imports SHARED_BUCKET_ID or
    UnknownToolProviderError at module load — SHARED_BUCKET_ID is mirrored as a
    local literal (verified in lockstep with core via a regression test), and
    UnknownToolProviderError is resolved via a cached await import(...) inside
    resolveProvider so the real class identity is preserved for instanceof.

    OSS users running Mastra without a MastraEditor are unaffected: every
    tool-provider route still short-circuits with HTTP 500 "Editor is not
    configured" via requireEditor(...) before any core/tool-provider value is
    touched. Users with a MastraEditor already 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 metadata field for attaching custom key-value data such as displayName, author, or category. 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/[email protected]

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 favoriteCount on the parent record atomically.
    • Channels for multi-platform installations and per-platform configuration.

    Enabling favorites also adds favorited-first ordering and favoritedOnly / entityIds filtering to agents.list() and skills.list(), and surfaces favoriteCount on 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/[email protected]

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, sudo access, exposed ports, command execution, and background processes via the process manager. This is distinct from the existing VercelSandbox, which runs commands as stateless Vercel serverless Functions and is unchanged. Also exports VercelMicroVMProcessManager and the vercelMicroVMSandboxProvider editor descriptor (provider id vercel-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/[email protected]

Patch Changes

  • Moved shared voice primitives and route metadata into the new @internal/voice package so voice providers no longer depend on @mastra/core and server voice routes share the same route definitions. (#16725)

    @mastra/core/voice continues to re-export the voice APIs for backwards compatibility.

@mastra/[email protected]

Patch Changes

  • Moved shared voice primitives and route metadata into the new @internal/voice package so voice providers no longer depend on @mastra/core and server voice routes share the same route definitions. (#16725)

    @mastra/core/voice continues to re-export the voice APIs for backwards compatibility.

@mastra/[email protected]

Patch Changes

  • Moved shared voice primitives and route metadata into the new @internal/voice package so voice providers no longer depend on @mastra/core and server voice routes share the same route definitions. (#16725)

    @mastra/core/voice continues to re-export the voice APIs for backwards compatibility.

@mastra/[email protected]

Patch Changes

  • Moved shared voice primitives and route metadata into the new @internal/voice package so voice providers no longer depend on @mastra/core and server voice routes share the same route definitions. (#16725)

    @mastra/core/voice continues to re-export the voice APIs for backwards compatibility.

@mastra/[email protected]

Patch Changes

  • Moved shared voice primitives and route metadata into the new @internal/voice package so voice providers no longer depend on @mastra/core and server voice routes share the same route definitions. (#16725)

    @mastra/core/voice continues to re-export the voice APIs for backwards compatibility.

@mastra/[email protected]

Patch Changes

  • Moved shared voice primitives and route metadata into the new @internal/voice package so voice providers no longer depend on @mastra/core and server voice routes share the same route definitions. (#16725)

    @mastra/core/voice continues to re-export the voice APIs for backwards compatibility.

[@mastra/[email protected]](https://github.com/mastra-ai/mas

Weekly OSS security release digest.

The CVE patches and breaking changes that affected production tools this week. One email, every Sunday.

No spam, unsubscribe anytime.

Share this release

Track mastra

Get notified when new releases ship.

Sign up free

About mastra

From the team behind Gatsby, Mastra is a framework for building AI-powered applications and agents with a modern TypeScript stack.

All releases →

Beta — feedback welcome: [email protected]