github mastra-ai/mastra @mastra/core@1.68.0
September 22, 2026

latest releases: mastracode@0.42.0, mastra@1.31.1, create-mastra@1.31.1...
7 hours ago

Highlights

MCP v2 (2026-07-28) server architecture in @mastra/mcp@2.0.0

@mastra/mcp is now rebuilt on the MCP 2026-07-28 revision: no initialize handshake/session headers, and tools that need input use first-class suspend/resume (context.suspend() + context.resumeData) with signed, self-contained continuation state (requestState). Server routes now return explicit { status: 'suspended', suspendPayload, resumeSchema } vs { status: 'completed', output }.

New vector stores: Azure AI Search + Weaviate

Two new MastraVector backends land: @mastra/azure-ai-search@0.1.0 (Azure AI Search with auto-indexed filterable metadata for Memory/RAG) and @mastra/weaviate@0.1.0 (full MastraVector contract with rich Mongo-style metadata filtering and deterministic vector ID mapping).

Observability trace querying gets “real” pagination, discovery, and delta polling (core/server/client + ClickHouse/DuckDB/PG)

Advanced trace queries now support page-based pagination (with totals), bounded field/value discovery APIs, and delta polling cursors to transition from initial load → incremental updates efficiently. Trace list results also include root-span summary fields (name, createdAt, metadata, inputPreview, etc.) so UIs can render useful lists without fetching full traces.

Durable-agent performance: stop persisting running checkpoints by default

Durable agents no longer write running snapshots unless recovery.durableAgents: 'auto' is enabled (or you override via shouldPersistSnapshot), eliminating large amounts of storage churn on normal runs while keeping pending/paused/suspended snapshots for human-in-the-loop resume.

Token-budgeted conversation history trimming with messageHistory

Memory now supports messageHistory: { maxTokens, atMaxRemoveTokens? } to keep context within a token budget by dropping oldest remembered messages in chunks, persisting the trim boundary per thread so future turns stay within budget without deleting stored messages.

Breaking Changes

  • Agent Controller stream message events changed: one full message_start, then ID-addressed message_update deltas; message_end now contains only the message ID (store by ID and apply updates).
  • @mastra/mcp@2.0.0 is a major rewrite: MCP 2026-07-28 only (legacy handshake removed), suspend/resume replaces elicitation APIs, and several server/client transport and protocol surfaces were removed per the MCP v2 migration guide.

Changelog

@mastra/core@1.68.0

Minor Changes

  • Make the workspace grep text-extension whitelist extensible. Added .sas, .log, and .jsonl to the built-in text extensions and MIME type map so they are searchable by default. Added an optional textExtensions option to MastraFilesystemOptions (exposed via filesystem.isTextFile()) so consumers can register additional text extensions. When an explicit file path is grepped but its extension isn't recognized as text, the grep summary now reports it as skipped so "no matches" is distinguishable from "never searched". (#24003)

    import { LocalFilesystem } from '@mastra/core/workspace';
    
    // Register extra extensions as searchable text files
    const filesystem = new LocalFilesystem({
      basePath: process.cwd(),
      textExtensions: ['.sasx', '.myext'],
    });
    
    filesystem.isTextFile('report.sasx'); // true
  • Added consistent, resumable prune() execution for storage adapters, including bounded work, pause intervals, and cancellation. (#23466)

    const controller = new AbortController();
    
    await storage.prune({
      maxBatches: 10,
      maxRows: 10_000,
      pauseMs: 25,
      signal: controller.signal,
    });
  • Added list-compatible page pagination to advanced trace queries while preserving keyset cursors. (#24061)

    const result = await client.queryTraces({
      timeRange,
      pagination: { page: 0, perPage: 25 },
    });
  • Added support for refreshing advertised agent peer details without reclaiming thread ownership. (#23696)

    const updated = agent.updateThreadPeerAdvertisement({
      resourceId: 'resource-1',
      threadId: 'thread-1',
      peer: { title: 'Updated thread title', metadata: { mode: 'review' } },
    });
  • Added `orderBy` support when listing datasets, dataset items, experiments and experiment results so Studio and API consumers can sort server-side instead of receiving a fixed newest-first order. (#24567)

    ```ts
    const { datasets } = await mastra.datasets.listDatasets({ orderBy: { field: "name", direction: "ASC" } });
    const { items } = await dataset.listItems({ orderBy: { field: "createdAt", direction: "ASC" } });
    ```

    Allowed fields: datasets (`createdAt`, `updatedAt`, `name`), items (`createdAt`, `updatedAt`), experiments (`createdAt`, `status`), experiment results (`startedAt`, `createdAt`). Unknown fields are rejected. Also exports a `resolveListOrderBy` helper for storage adapters.

  • Added root span details to queryTraces results: name, entityId, parentSpanId, createdAt, metadata, and inputPreview. Trace lists can display these fields without fetching each full trace. createdAt uses the root span start time; inputPreview contains a shortened input preview rather than the full input. (#23958)

    const { traces } = await client.queryTraces({
      timeRange: { from: '2026-09-01T00:00:00Z', to: '2026-09-15T00:00:00Z' },
    });
    // Previously required fetching the full trace:
    console.log(traces[0]?.name, traces[0]?.inputPreview, traces[0]?.metadata);

    Also in: @mastra/clickhouse@1.20.0, @mastra/duckdb@1.10.0, @mastra/pg@1.26.0, @mastra/playground-ui@56.0.0, @mastra/server@1.68.0

  • Added dataset snapshot format utilities to validate portable identities, preserve authored JSON fields and required item creation and update timestamps, and detect artifact changes with an integrity digest. These utilities do not read or write dataset storage. Both helpers accept a configurable maxBytes budget (4 MiB by default), independent of the artifact format and integrity digest. (#23902)

    import { parseDatasetSnapshot } from '@mastra/core/datasets';
    
    const snapshot = parseDatasetSnapshot(artifactJson, { maxBytes: 8 * 1024 * 1024 });
  • Added notScorable(). Return it from a scorer step when a run has nothing to evaluate, for example a refund judge on a chat that never called the refund tool. Remaining steps are skipped, so the judge is never called and the run stays out of that scorer's averages. runEvals() omits verdict when every configured gate or threshold was not scorable. (#24378)

    import { createScorer, notScorable } from '@mastra/core/evals';
    import { extractToolCalls } from '@mastra/evals/scorers/utils';
    
    const refundJudge = createScorer({
      id: 'refund-judge',
      description: 'Judges refund handling',
      type: 'agent',
      judge: { model: 'openai/gpt-5-mini', instructions: '...' },
    })
      .preprocess(({ run }) => {
        const { tools } = extractToolCalls(run.output);
        return tools.includes('refundCustomer') ? { tools } : notScorable('refundCustomer was not called');
      })
      .generateScore({
        description: 'Score the refund handling from 0 to 1',
        createPrompt: ({ run }) => `Rate the refund handling: ${JSON.stringify(run.output)}`,
      });

    Reading the result. scorer.run() is either scored or skipped. Check notScorable before using score as a number, including on scorers that never skip:

    const result = await refundJudge.run(input);
    if (result.notScorable) {
      // skipped — no score
    } else {
      result.score;
    }
  • Added generateTitle.emitEvent so HTTP and stream clients receive the generated thread title without polling. (#24247)

    Thread titles are generated in the background after a run finishes. The onTitleGenerated callback only works for in-process callers, so an app driving an agent over HTTP had no way to know when the title was ready (#21203).

    With emitEvent: true, the run stream waits for the title and emits it as a transient data-thread-title chunk before finish:

    const memory = new Memory({
      options: {
        generateTitle: {
          emitEvent: true,
        },
      },
    });
    
    // Consumers read it from the run's stream before the `finish` chunk.
    for await (const chunk of stream.fullStream) {
      if (chunk.type === 'data-thread-title') {
        console.log(chunk.data.threadId, chunk.data.title);
      }
    }

    The chunk is transient, so it is never persisted as part of the conversation. The default stays fully non-blocking: without emitEvent, title generation still runs in the background and does not delay the stream.

    The generateTitle object also accepts minMessages (minimum number of thread messages before a title is generated, default 1) and an optional model (defaults to the agent's own model), so title generation can run on a smaller or cheaper model than the conversation.

    Durable and evented agents don't emit the chunk yet; the title is still generated and persisted.

  • Added sandbox start options forwarding so providers can support cancellable startup operations. (#24451)

  • Durable agents no longer persist running checkpoints by default, and createDurableAgent() accepts a new shouldPersistSnapshot option to control snapshot persistence (#23915). (#23978)

    Previously, durable agents wrote a full workflow snapshot to storage on every step of every run, including running checkpoints that are only read by crash recovery. With recovery left at its default (recovery.durableAgents: 'off'), those writes were pure overhead — a single agent turn could generate over a thousand storage statements.

    What changed

    • Durable agents still always persist pending, paused, and suspended snapshots, so human-in-the-loop resume and tool approval keep working with no configuration.
    • running checkpoints are now only written when the Mastra instance sets recovery.durableAgents: 'auto', which is the setting that consumes them.
    • createDurableAgent(), the DurableAgent constructor, and the agent-level durable config accept a shouldPersistSnapshot predicate to override the policy.
    • Mastra logs a warning if a custom predicate excludes suspended or paused (breaks human-in-the-loop resume), or excludes running while automatic recovery is enabled (makes the agent invisible to recovery).
    • Evented agents are unaffected: they always persist the full snapshot set (their engine coordinates workers through storage) and log a warning if shouldPersistSnapshot is set.

    Action required if you use manual recovery: if you call listActiveRuns(), recover(), or recoverActiveRuns() without setting recovery.durableAgents: 'auto', opt back into running checkpoints:

    const durableAgent = createDurableAgent({
      agent,
      shouldPersistSnapshot: ({ workflowStatus }) =>
        ['pending', 'paused', 'suspended', 'running'].includes(workflowStatus),
    });
  • Added delta polling to advanced trace queries, including a cursor on numbered pages for the initial-load-to-poll handoff. Delta cursors bind the predicate and time range; keyset pagination remains unchanged. (#24329)

    // Before: load a numbered page.
    const page = await client.queryTraces({ timeRange, pagination: { page: 0, perPage: 100 } });
    // After: continue polling from that page without rescanning it.
    const updates = await client.queryTraces({ timeRange, mode: 'delta', after: page.deltaCursor, limit: 100 });

    Polling returns newly completed roots and root completions. It does not provide deletion notifications or guarantee re-emission after related-record changes.

  • Added Hono handler and route types to the server exports. (#23992)

    import type { MiddlewareHandler } from '@mastra/core/server';
    
    const middleware: MiddlewareHandler = async (_context, next) => {
      await next();
    };
  • Added structuredOutput.instructions support for JSON prompt injection, so you can replace the serialized JSON schema in the prompt with your own compact instructions (#24176)

    When jsonPromptInjection is active and no separate structuring model is configured, a caller-supplied structuredOutput.instructions string is now injected into the prompt in place of the serialized JSON schema, in both 'system' and 'inline' modes. On large schemas this removes thousands of tokens from every model call.

    const result = await agent.generate('Extract the customer name.', {
      structuredOutput: {
        schema: z.object({ name: z.string() }),
        jsonPromptInjection: 'system',
        instructions: 'Return a JSON object with a name field.',
      },
    });

    Output is still validated against schema, so you stay responsible for keeping instructions in sync with the fields you need. When no separate structuring model is configured, instructions is also serialized across the durable agent boundary, so the same behavior applies to durable runs. Behavior is unchanged when instructions is absent or blank: the serialized schema is still injected as before.

  • Added A2A v1.0 remote subagent delegation with explicit protocol selection on A2AAgent. Existing integrations continue to use v0.3 by default. (#24134)

    const remoteAgent = new A2AAgent({
      url: 'https://example.com/.well-known/agent-card.json',
      protocolVersion: '1.0',
    });
  • Added an api option to custom OpenAI-compatible model configs so a custom url can target the OpenAI Responses API. Set api: "responses" alongside url to reach /v1/responses (for example to combine function tools with reasoning models on gateways that require it); it defaults to "chat", so existing configurations are unchanged. (#24029)

    const agent = new Agent({
      id: 'my-agent',
      name: 'My Agent',
      instructions: 'You are a helpful assistant',
      model: {
        id: 'custom/my-model',
        url: 'https://your-endpoint.com/v1',
        api: 'responses',
      },
    });
  • Added the MCP_SERVER_REQUEST span type, MCPServerRequestAttributes, and EntityType.MCP_SERVER for requests served by a Mastra MCPServer. Added a skipToolSpan tool execution option so a caller that already owns a span can run a tool without an extra TOOL_CALL span: (#24150)

    await tool.execute(args, {
      tracingContext: { currentSpan: requestSpan },
      skipToolSpan: true,
    });

    See #23921

  • Added usedFallbackValue to agent generate() and stream() results. With structuredOutput.errorStrategy: 'fallback', result.object was previously indistinguishable from a real answer once the configured fallbackValue had been substituted: finishReason stayed 'stop', tripwire stayed empty, and the only marker was a metadata.fallback flag on the internal object-result chunk, which never reached the result. The result — and the onFinish callback payload — now report the substitution directly, for both the native and separate-structuring-model paths. (#24424)

    const result = await agent.generate('Summarize the ticket.', {
      structuredOutput: { schema, errorStrategy: 'fallback', fallbackValue: { summary: 'unknown', tags: [] } },
    });
    
    if (result.usedFallbackValue) {
      // result.object is the fallback, not something the model produced
    }
  • Add a messageHistory memory option for token-budgeted conversation history. messageHistory: { maxTokens, atMaxRemoveTokens? } counts the complete prompt against the token budget and drops the oldest remembered messages in chunks. It never removes the current turn's input, responses, context, or system messages. During agent runs, a per-thread boundary is advanced and persisted so trimmed history stays out of subsequent turns without deleting stored messages. (#23238)

    When messageHistory is set without an explicit lastMessages, the default 10-message cap is dropped so the token budget alone defines the window. lastMessages remains supported and can be combined with messageHistory, but counting messages is a poor proxy for context size and lastMessages is now soft-deprecated in favour of messageHistory.

    import { Memory } from '@mastra/memory';
    
    const memory = new Memory({
      options: {
        messageHistory: { maxTokens: 8_000, atMaxRemoveTokens: 2_000 },
      },
    });

    Also in: @mastra/client-js@1.47.0, @mastra/memory@1.31.0, @mastra/server@1.68.0

  • Added canonical trace-query field descriptors and bounded discovery contracts for queryable fields and values. (#24057)

    import {
      getTraceQueryCanonicalFieldDescriptors,
      parseGetTraceQueryValuesArgs,
      planTraceQueryValues,
    } from '@mastra/core/storage';
    
    const fields = getTraceQueryCanonicalFieldDescriptors('spans');
    const values = await storage.getTraceQueryValues(
      planTraceQueryValues(
        parseGetTraceQueryValuesArgs({
          timeRange: { from: '2026-08-01T00:00:00Z', to: '2026-08-02T00:00:00Z' },
          predicateScope: 'spans',
          path: 'model',
        }),
      ),
    );
  • Added MCP 2026-07-28 server contracts to MCPServerBase while keeping MCP 1.x servers working unchanged. (#23875)

    • Added mcpVersion to MCPServerBase. A server that sets it to 2 resolves executeTool to MCPToolExecutionResultV2, which reports a suspended tool ({ status: 'suspended', suspendPayload, resumeSchema }) instead of a bare result. Thrown errors and schema failures still reject. Existing 1.x servers need no new properties.
    • Added context.mcp.protocolVersion, set to '2026-07-28' by 2.x servers. context.mcp.extra, log and progress keep the same shape on both server versions.
    • Added suspend, resumeData and suspendPayload at the top level of the tool execution context for direct and MCP 2.x execution. Agents and workflows keep nesting them under agent and workflow until the next core major.
    • Added suspendPayload to tools resumed by agents (including durable agents) and workflows, alongside resumeData.
    • Deprecated the surfaces MCP 2026-07-28 removed, for removal in the next core major: startSSE, startHonoSSE, MCPServerSSEOptions, MCPServerHonoSSEOptions, MCPServerHTTPOptions.options, context.mcp.elicitation.sendRequest, context.mcp.extra.sendRequest and context.mcp.extra.sendNotification. On a 2.x server the deprecated context.mcp members throw with a message naming the replacement. startSSE and startHonoSSE are no longer abstract, so 2.x servers do not implement them.

    Tools that need input mid-execution use the suspend/resume primitives createTool already has:

    import { createTool } from '@mastra/core/tools';
    import { z } from 'zod';
    
    const confirm = createTool({
      id: 'confirm',
      description: 'Ask for confirmation',
      inputSchema: z.object({ amount: z.number() }),
      outputSchema: z.boolean(),
      suspendSchema: z.object({ phase: z.literal('confirm'), amount: z.number() }),
      resumeSchema: z.object({ confirmed: z.boolean() }),
      execute: async ({ amount }, context) => {
        await context.mcp?.log?.('info', 'asking for confirmation', { amount });
        if (!context.resumeData) {
          await context.suspend?.({ phase: 'confirm', amount });
          return;
        }
        return context.resumeData.confirmed;
      },
    });
  • Added a stable resource-limit error for bounded trace-query field and value discovery. (#24169)

    import { TraceQueryResourceLimitError, planTraceQueryValues } from '@mastra/core/storage';
    
    const plan = planTraceQueryValues({
      timeRange,
      predicateScope: 'spans',
      path: 'model',
      search: 'claude',
      limit: 25,
    });
    
    try {
      await observability.getTraceQueryValues(plan);
    } catch (error) {
      if (error instanceof TraceQueryResourceLimitError) {
        console.error(error.code);
      }
    }

Patch Changes

  • Fix agent.generate() / .stream() reporting a caller abortSignal cancellation as a processor tripwire. When a run is aborted and no processor triggered a tripwire, the result now reports finishReason: 'aborted' and leaves tripwire undefined, instead of synthesizing a generic { reason: 'Processor tripwire triggered' }. Genuine processor tripwires are unaffected. (#24008)

  • Update provider registry and model documentation with latest models and providers (1e68460)

  • Improved runtime portability by using Web Crypto for cryptographic operations that do not require changes to existing synchronous APIs. (#24369)

  • Fixed durable agents passing stepNumber: 0 and an empty steps list to processLLMRequest, processLLMResponse, and processOutputStep on every step. Processor hooks now receive the correct zero-based step index and the running step list, matching non-durable agents. Fixes #24279 (#24293)

  • Fixed claimed thread owners acting on a redelivered idle signal twice. A signal that a PubSub backend redelivers is now handled once while the runtime still remembers its request id, so it no longer queues the turn again or starts a second run for the same run id. If the reply to the caller never reached the backend, the redelivery re-sends it instead of reprocessing the signal. (#24467)

  • Documented the SpanOutputProcessor.process() contract: mutate the span you receive and return the same instance, or return undefined to drop it. Returning a copy is not supported because exportSpan() and isValid are instance members of the live span. Related to #23796 (#24048)

    Also in: @mastra/observability@1.17.9

  • Fixed goals that never ended after reaching their evaluation budget while waiting for user input. Once a goal uses all of its maxRuns evaluations, later chat turns now park it as paused with the budget reason. Previously they reported it as still running and rendered a continue verdict on every turn. Raise maxRuns and resume the goal to continue it. (#24402)

  • Fixed structured output fallback instructions to include the requested JSON schema. (#24457)

  • Ignore malformed numeric Retry-After values instead of parsing them as years and delaying retries up to the configured cap. (#23997)

  • Fixed streamed tool events and assistant messages to retain their originating thread when a session switches threads. Consumers can use tool-event threadId to ignore delayed output from a previous conversation. (#24313)

  • Fixed span output processors that return a copy of the span instead of the same instance. Previously this threw TypeError: processedSpan?.exportSpan is not a function out of startSpan() for started/updated spans, and silently dropped every ended span. Now the span is dropped with a logged processor error naming the processor, and the SensitiveDataFilter.process() docstring correctly states that it mutates the span in place. Fixes #23796 (#24048)

    Also in: @mastra/observability@1.17.9

  • createCodingAgent now repairs recoverable bad requests instead of replaying them. (#24469)

    The default error processors ran the blind stream retry first. It claims these rejections, so a request that ProviderHistoryCompat or PrefillErrorHandler knows how to fix was retried unchanged, earned the same rejection, and surfaced as a failed turn. Both repair processors now run ahead of it.

    What changes for you: a coding agent hitting a malformed tool-call id or an assistant-prefill rejection now retries a corrected request rather than an identical one. The retry budget is unchanged.

    Pass your own errorProcessors to opt out; the default list is only used when you pass none.

  • Fixed query-parser, static-site generation, parseBody, and XSS security issues by updating hono to 4.13.7. (#24027)

    Also in: @mastra/deployer@1.68.0, @mastra/editor@0.15.1, @mastra/factory@0.16.0, @mastra/hono@1.7.10, @mastra/inngest@1.9.0, @mastra/mcp@2.0.0, @mastra/mcp-docs-server@1.2.27, @mastra/mcp-registry-registry@1.1.3, @mastra/next@0.2.26, @mastra/server@1.68.0, @mastra/tanstack-start@0.2.26

  • Fixed image and file URLs returned from a tool's toModelOutput being corrupted before reaching the model. Fixes #22618 (#24371)

    • Remote image-url and file-url tool results are no longer rewritten into a media part with the URL stuffed into the Base64-only data field, and their providerOptions are no longer dropped.
    • URL parts are preserved as-is and converted to the correct shape for the target model's specification version: image-url/file-url for v3 models, url-tagged file parts for v4 models.
    • Messages persisted by older versions with a URL in the media data field are healed the same way.
  • Fixed structured output to reject responses truncated by token limits or content filters. (#24464)

  • Fixed model settings returned by an input processor being ignored when the agent also configured them. Fixes #22395 (#24429)

    Values returned from processInputStep or prepareStep now take effect for the model call, including maxRetries. Previously any setting the model list also specified won the conflict, so a processor could not change it. This affected single-model agents as well as fallback chains.

    A processor that returns only some settings (for example just { temperature }) keeps the configured retry and timeout limits. Inference telemetry now reports the settings the model actually received.

  • Fixed agent and workflow delegation so model-driven resumes use framework-persisted suspended tool-call identity, including falsy resume payloads, and cannot select sibling runs by supplying a run ID. Successful resumes now retire every persisted representation of only the selected suspension. (#24258)

  • Fix durable agents dropping writer.custom() / writer.write() emissions from tools. The durable tool-call step now provides a writer (ToolStream) in the tool execution context, so tools resolved from the Mastra registry on cross-process runs (e.g. an @mastra/inngest worker) receive a working writer instead of undefined. Fixes #24196. (#24229)

  • Fixed ModelRouter URL capability discovery failures being hidden and permanently cached. See #24436. (#24454)

  • Fix EventedAgent.executeWorkflow() emitting a run's terminal error from an un-awaited .catch, which could surface as an unhandledRejection during shutdown. Both terminal-error emission sites now route through emitErrorInBackground(), so a publish failure (e.g. pubsub/storage closed while the run finishes) is logged as a warning instead of crashing the process — matching the DurableAgent behavior fixed in #23168. (#24083)

  • Fixed queued follow-up messages being saved without an answer after the active run is aborted. (#23926)

    • Pending signals wait for a fresh run instead of being consumed by an aborted run.
    • Queued messages no longer inherit the previous run's cancellation signal. Explicit cancellation supplied for a queued message is preserved.
    • Pending signals stay ahead of idle messages when preparation fails, including after cancellation of a queued startup.
    • Forwarding signals to a new thread owner does not duplicate the same ID already waiting in that owner's pre-run or pending signal queue.
  • Fixed durable agent approval resumes so live assistant events and token usage are recorded once. (#23116) (#24265)

  • Fixed attachment download failures bypassing error processors in regular agent runs. Processors can now repair the message context and retry when a historical attachment becomes unavailable. (#23988)

  • Fixed completed workflow runs remaining in memory after resume. (#24267)

  • Breaking change (#22978)

    Agent Controller streams now emit one complete message_start payload, followed by ID-addressed message_update events for text, reasoning, and message-part changes. message_end now contains only the message ID.

    Migration

    Previously, consumers read the complete message from each update. Now, store the start payload by ID and apply subsequent updates to that message:

    if (event.type === 'message_start') messages.set(event.message.id, event.message);
    if (event.type === 'message_update') applyUpdate(messages.get(event.id), event.event);

    Also in: @mastra/client-js@1.47.0, @mastra/code-sdk@1.8.0, @mastra/server@1.68.0

  • CommonJS consumers can now use core token counting, slug generation, and workspace operations without ESM loading errors. (#24272)

  • Fixed evented workflows failing with "condition is not a function" when a dountil or dowhile loop body is a nested workflow and events go through a serializing pubsub such as Redis Streams. The loop condition is now read from the live workflow registry instead of the serialized event payload, which cannot carry functions. Fixes #23111. (#24366)

  • Surface filesystem read failures in the workspace grep tool instead of silently reporting them as a complete "0 matches" search. (#24053)

    • Partial-search reporting. When a directory cannot be listed or a file cannot be read, those failures are now counted and appended to the result summary (N paths skipped: read error) so a partial search is distinguishable from a genuinely empty one.
    • Missing targets. If the target path does not exist (ENOENT, or ENOTDIR when a path component is a file), the summary reports target path not found: nothing searched rather than a plain "0 matches".
    • Strict mode. A new construction-time strict option makes any such read failure throw instead of being skipped, for callers that want to fail fast.
    • .gitignore handling. loadGitignore now only swallows a genuinely-absent .gitignore (ENOENT) and rethrows permission/IO errors, which previously changed the search scope silently.

    Enable strict mode when constructing the workspace tools to fail fast when any part of the target cannot be read:

    import { createWorkspaceTools, WORKSPACE_TOOLS } from '@mastra/core/workspace';
    
    const tools = await createWorkspaceTools(workspace, undefined, { grep: { strict: true } });
    // Throws instead of reporting a partial result when a path cannot be read.
    const result = await tools[WORKSPACE_TOOLS.FILESYSTEM.GREP].execute({ pattern: 'needle' }, { workspace });
  • Fixed tool approval failing on runs with large workflow snapshots. agent.approveToolCall(), declineToolCall(), and resumeStream({ toolCallId }) could throw AGENT_RESUME_TOOL_CALL_NOT_SUSPENDED for a run that was genuinely suspended. This happened when saving a large snapshot took longer than the fixed 2-second validation window. The validator now waits while the run is still persisting its suspension, and rejects right away when the tool call is stale or the run has already finished. Fixes #22413. (#24171)

  • Fixed aborting suspended agent runs so parked tool calls are denied, the thread is released, and messages sent immediately after Stop receive a response. (#24266)

  • Fixed workflow and agent delegation tools adopting a malformed model-supplied suspendedToolRunId. Some models emit the literal string "null" for this optional auto-resume field on fresh calls; because that string is truthy, two independent workflow-tool calls could collide on a single run (silently dropping one), and agent delegation could try to resume a non-existent run and crash. Sentinel strings ("null", "undefined", "none", "nil") are now treated as absent at every point where the field crosses the model boundary. (#24121)

    Behavior change: workflow tools now only honor a supplied suspendedToolRunId together with resumeData — fresh calls always receive a framework-generated unique run id. If you pinned args.suspendedToolRunId in a beforeToolCall hook (the documented workaround for run-id collisions), that pin is no longer applied on fresh calls — and is no longer needed. Fixes #23739.

  • Fixed workspace requireReadBeforeWrite falsely rejecting writes with "has not been read" after suspend/resume and between conversation turns (#23772). Read records now persist per memory thread in the threadState storage domain — the same store that holds task lists and goal objectives — so a file read before a tool suspends (for example, awaiting plan approval or a requireApproval tool) no longer needs a wasteful re-read after the run resumes, including on serverless runtimes where the process is torn down between suspend and resume when a persistent storage adapter (for example LibSQL or Postgres) is configured. No configuration is needed: with in-memory storage, records survive suspend/resume within the same process, and runs without a memory thread or Mastra storage fall back to per-run tracking. Files modified on disk after being read still require a re-read before writing. Read records are scoped to the filesystem they were read from (provider + base path), so threads that resolve a different workspace or filesystem between requests cannot satisfy the write gate with a read from a different file store. (#24122)

  • Fixed structured output with a top-level array of primitives (e.g. z.array(z.string()) or z.array(z.number())) silently resolving response.object to an empty array. Array elements that are strings, numbers, booleans or null are now returned from generate(), stream().object and objectStream, and the final result is validated against what the model actually returned. Fixes #23980. (#24055)

  • Fixed structured output fallback warnings to use the agent logger and aligned the loop fallback logger with error-level defaults. (#24455)

  • Lowered the implicit error-processor retry cap from 10 to 3 and made it visible. Configuring errorProcessors without an explicit maxProcessorRetries previously allowed a processor that always requests a retry to drive 11 model calls for a single turn, silently and regardless of maxRetries: 0. The cap is now 3 (4 model calls worst case) and a one-time warning is logged naming the setting to configure. Every built-in error processor self-limits to at most one retry, so only a processor that never stops asking is affected; callers who need a larger budget can set maxProcessorRetries explicitly. (#24503)

    Also aligned the durable execution path with the standard loop: processAPIError now runs on the final attempt too, so a processor can observe and report a terminal failure instead of being skipped once the retry budget is spent.

  • Fixed unbinding a thread so it no longer aborts the run a different agent instance is executing. Detaching from a thread, switching threads, or tearing down a session now stops only run work owned by the local process; explicit cancellation still stops the run wherever it is executing. Thread peer discovery now marks advertisements published by the agent that asked, so a caller can tell its own threads apart from an in-process peer agent's. (#24510)

  • Fixed idle wake signals losing the caller's request context and misreporting their outcome when the thread had a claimed owner. (#24347)

    A wake that starts a run on a locally claimed owner now applies the incoming streamOptions.requestContext to that run. The claimed owner's own stream options were used verbatim, so a dispatcher waking a session on behalf of an authenticated caller started the run without that identity, and downstream lookups that require a caller — a workspace resolver, for example — failed. The owner's remaining options stay authoritative, since the run executes inside the owner's session.

    The same path now reports wake instead of deliver. deliver promises that no run started locally and that the signal joined a run already in flight; callers that waited on that run, or re-sent because they believed it was still busy, never saw the work happen.

    A claimed owner in another process is unchanged: the wake event carries no requestContext, so a remote owner still starts the run with its own options.

  • Bound the output that the execute_command tool retains while a foreground command streams. Previously the tool kept its own unbounded copy of stdout and stderr, which was only read on the error path but could exhaust memory or kill the process with RangeError: Invalid string length on very large command output. (#24486)

  • Fixed a WebSocket denial-of-service advisory by updating ws to 8.21.3. (#24027)

    Also in: @mastra/deployer@1.68.0, @mastra/hono@1.7.10, @mastra/voice-google-gemini-live@0.14.11, @mastra/voice-inworld@0.4.3, @mastra/voice-openai-realtime@0.14.1, @mastra/voice-xai-realtime@0.2.11

  • Add an openai-orphan-item-id compat rule so a turn can recover when stored history contains an assistant message with an OpenAI itemId (msg_…) but no reasoning item. OpenAI's Responses API replays such a message as an item_reference and rejects the request with a non-retryable 400 (Item 'msg_…' of type 'message' was provided without its required 'reasoning' item), which today ends the turn. (#24345)

    What changes for you. A thread that was permanently stuck on that 400 can now recover on the same turn. The rule removes the item references from every message shaped like the orphan so they replay by value, then asks for one retry. Azure is covered on the same footing as OpenAI. Unrelated provider data survives the repair: cache counts, reasoning-token counts and logprobs are left intact.

    You have to install it. A plain Agent has no compat processor configured. Register ProviderHistoryCompat in errorProcessors — reactive recovery needs the error lane, and on the durable path the API-error pass runs only when that list is non-empty.

    import { Agent } from '@mastra/core/agent';
    import { ProviderHistoryCompat } from '@mastra/core/processors';
    
    const agent = new Agent({
      // ...
      errorProcessors: [new ProviderHistoryCompat()],
    });

    The repair is in-memory for the current turn. The healed message is not written back to storage, so each later turn on that thread still spends one rejected request before recovering — the same behavior as the existing anthropic-tool-id-format rule.

    It fires only after that error, never on a thread that has not hit it. When it does fire it repairs every orphan-shaped message in the history, because the error names only the first item and one retry is available. Valid reasoning-free messages caught by that breadth still replay correctly, by value rather than by reference. One exception: a hosted tool_search result cannot replay by value, so it is dropped from the prompt rather than replayed. On a genuinely orphaned message that is the right outcome; on a valid message swept along with it, the model loses that search result and would have to look it up again.

  • Fixed a processor-forced mid-turn retry discarding steps the assistant had already completed. When an output processor aborted a step with { retry: true }, the whole in-flight assistant message was deleted. That took the reasoning and tool calls from earlier steps in the same turn that had already been accepted. The retry now discards only the rejected step. The model still never re-sees the rejected answer, and it keeps every step it had already accepted. (#24344)

    Two things stop happening on this retry path as a result. An accepted tool call is no longer thrown away and run a second time. And with OpenAI reasoning models, the saved message no longer ends up carrying an assistant itemId (msg_…) with no matching reasoning item. OpenAI rejects that shape with a non-retryable HTTP 400: Item 'msg_…' of type 'message' was provided without its required 'reasoning' item. The rejection then recurs whenever that stored history is replayed on a later turn (#22291).

  • Fixed replay of OpenAI-hosted tool_search across turns. The Responses API gives a hosted search's call and its output distinct item ids (tsc_… / tso_…); Mastra now keeps both on the stored tool part and splits them back apart when building a prompt, so each side replays as its own item_reference instead of the same one twice. Hosted searches are also kept provider-executed through a round trip, so their result is no longer re-serialized as a client-mode tool_search_output. (#23611)

    Conversations recorded before this fix kept only one of the two ids, so that hosted search pair can no longer be replayed faithfully — the single id would be referenced twice. A completed hosted search (succeeded or errored) with only one id is now omitted when building a prompt, and the model rediscovers the tool on the next turn; the rest of the conversation is unaffected and the part is still retained in response messages, so nothing is deleted from stored history. In-flight searches, which legitimately carry only a call id, and client-executed tools named tool_search are untouched.

  • Fixed thread aborts so callers can require the intended run to still be active before it is stopped. (#24452)

  • Fixed manually renamed thread titles being replaced by Observational Memory. Explicitly regenerating a title enables automatic title updates again, as do programmatic title writes that opt out of pinning: (#23791)

    await session.thread.rename({ title: 'Initial title', pin: false });

    Fixes #22421

    Also in: @mastra/memory@1.31.0

  • Fixed tool calls missing from MODEL_GENERATION span output when agents run through the streaming loop or durable workflows. Observability exporters such as PostHog now receive the tool calls, so PostHog's Tools tab and $ai_output_choices show them for streamed generations. Fixes #24291 (#24306)

  • Reduced the core install footprint by embedding the MCP context declarations instead of installing the MCP server SDK. Existing MCP tool context types remain compatible with the SDK. (#23998)

  • Improved file-based storage performance by removing a redundant filesystem stat call for every directory entry when listing domain and skill files, and by skipping the ISO date check for strings that can't be dates. As part of this change, file listings no longer follow symbolic links, so a symlink pointing at a stored file or directory is no longer included in results. Fixes #23752 (#24056)

  • Fixed active goals being reported to the agent as cancelled, and stopped the goal being repeated in the model's context on every step. (#24342)

    A goal that was still running could be projected as having no objective, which the agent reads as "the goal was cancelled" and stops working on it. That happened when the goal state processor could not reach storage, including when inputProcessors was configured as a function and the processor never received the Mastra instance. The last known objective is now kept when storage cannot be read, and the instance is propagated to processors contributed by signal providers. A stale cached pause record could also be trusted over storage; the cached record is now only trusted when it shows the goal active, and storage is re-read otherwise.

    The projection is append-only, so re-emitting it duplicated the objective in context instead of updating it. It re-emitted on every attempt because the change it keyed on advanced each time. An objective that is already in context is now left alone.

  • Fixed a chat-library security advisory by updating chat to 4.37.0. Agent and factory chat APIs are unchanged. (#24027)

    Also in: @mastra/factory@0.16.0

  • Fixed the model capability registry trusting a nested provider's capabilities over the gateway actually serving the request. When a gateway such as OpenRouter lists a routed model (e.g. openrouter/deepseek/deepseek-v4-flash) without attachment support, that answer is now authoritative instead of falling back to the upstream provider's file, which caused Observational Memory to forward images to endpoints that reject them ("No endpoints found that support image input"). (#23685)

  • Fixed shared tools exposing _background to agents that do not support background execution. (#23120)

    Agents now advertise _background only for eligible tools. suspendedToolRunId and resumeData remain scoped to resumable tools. Repeated schema conversions no longer add nested validators.

  • Fixed mastra_workspace_read_file never returning media parts with strict-schema providers (e.g. OpenAI, Vercel AI Gateway). Media surfacing is now decided from the file's mime type and tool config instead of the absence of the optional encoding argument, so configured media within maxMediaBytes is returned as a native file/image part regardless of the model-supplied encoding. (#24082)

  • Improved Agent.listSuspendedRuns() performance when filtering by threadId. The thread filter is now passed down to the storage query, so supporting storage adapters narrow results inside the database instead of loading and parsing every suspended snapshot for the resource. Fixes #22627 (#24376)

    await agent.listSuspendedRuns({ threadId: 'thread-123' });
  • Fixed streamed PIIDetector redaction so sensitive values split across chunks are redacted and overlapping detections do not remove neighboring text. Redacted streams may briefly delay trailing text until a later text or non-text chunk. (#24189)

  • Fixed invalid model timeout settings being silently ignored. (#24463)

  • Fixed background tool results to carry task identity and lifecycle status in metadata, including resumed tasks and streamed completions. Consumers can identify background work without interpreting tool output text. (#24313)

  • Fixed subscribed thread streams missing signals that arrive while an aborted run is being cleaned up. (#23696)

  • Fixed durable agents giving delegated sub-agents a shared, parent-derived memory scope instead of one derived from the calling user. The durable execution path now stamps the caller's thread and resource identity onto delegated agent-tool calls, matching the regular agent loop, so each user's sub-agent conversations stay isolated and memory continuity works across turns. Fixes #23903. (#23981)

  • Fixed the skill_read tool corrupting binary skill files. A PNG or PDF is now reported as Binary file: <path> (<bytes>) with its exact size and is never decoded into the model context. Previously the file was decoded as UTF-8 before its bytes were inspected, which inflated the byte count and could put garbled text into the conversation. Binary detection now also covers NUL-free binaries such as PDFs. Text files anywhere in the skill, including under assets/, still read as text. (#24101)

  • Fixed skill discovery for Workspace instances that use a dynamic filesystem resolver. (#24317)

    When skills is configured without skillSource, discovery now uses the filesystem resolved for the request. It no longer reads skills from the server's local disk, so host-local skills cannot appear for other tenants and each tenant's own skills are found.

    Skill discovery and search state are isolated per resolved filesystem, with a bounded cache so per-request filesystems do not grow the search index. Unscoped workspace.search() no longer returns request-scoped skill documents (from dynamic skills resolvers or resolver-backed filesystems) and still returns up to topK regular documents. Static filesystems, explicit skillSource, and the no-filesystem fallback are unchanged.

    const workspace = new Workspace({
      filesystem: ({ requestContext }) => getTenantFilesystem(requestContext.get('orgId')),
      skills: ['skills'],
    });
    
    // Now reads from the tenant's filesystem, not process.cwd()
    const scoped = await workspace.skills!.getScoped!({ requestContext });
    await scoped.list();
  • Fixed fallback models restarting from the primary model between agent tool-call steps. (#23725)

  • Fixed requests failing with a 400 "Requests ending with a model turn are not supported" error on Gemini 3 models when the conversation ends with an assistant message. Fixes #23320. (#23609)

    • The trailing-message guard that Anthropic models already had under native structured output now also covers Google, Vertex AI, and gateway-routed Gemini 3+ models, for every request rather than only structured-output ones.
    • The guard is attached whenever an agent has input processors, because a processor can switch the model mid-step. It checks the final model before running and is skipped entirely, with no processor span, when that model does not need it.
    • The guard mirrors prompt conversion: assistant messages that end on a tool result are left alone, and history that ends on assistant text followed by an unfinished tool call is guarded correctly.
    • The synthetic continuation turn is added as request-only context instead of being saved to the thread, so memory and chat UIs no longer show a "Continue." or "Generate the structured response." message the user never sent.
    • PrefillErrorHandler also recognizes the Gemini error so the reactive retry path covers it too.
    • Explicitly versioned Gemini 2.x models and Anthropic prefill behavior are unchanged. Unversioned Google ids such as gemini-flash-latest or gemma-* are guarded conservatively because they can resolve to a Gemini 3 model.
  • Fixed a memory and connection leak in DurableAgent. After a run finished, the automatic cleanup timer released the run's registry state but left the stream subscription attached for the life of the process, so memory (and on Redis/Valkey streams, a client connection per run) grew with every turn. stream(), resume(), and recover() now release the subscription during automatic cleanup, the same way observe() already did. Fixes #24070. (#24104)

  • Fix stableStringify dropping own __proto__ keys, which collapsed distinct values onto one cache key (affecting message dedup in CacheKeyGenerator.fromDBParts and the agent response cache). (#24081)

  • Removed the redundant direct Ajv dependency. Schema compatibility bundles its validator and standalone types without requiring a separate Ajv installation. (#23998)

  • Fixed token usage and finish reason handling when a model router model is wrapped with AI SDK v7 wrapLanguageModel. The AI SDK compatibility shim nests usage and finish reason one level deeper, which made result.usage come back as the string "0[object Object]…" and made multi-step agent turns run to maxSteps. Mastra now unwraps repeated envelopes so token counts stay numbers and the loop stops on stop. Fixes #23735 and #23746 (#23914)

  • Fixed commands that read stdin hanging until timeout (#24336)

    Commands that read standard input without being given anything to read — a bare cat, grep or rg with no path argument, read — used to block until the command timeout expired, leaving tools stuck for minutes.

    executeCommand() now runs commands with standard input closed, so anything that reads stdin sees end-of-input immediately and exits:

    // previously hung until the timeout when the command read stdin
    await sandbox.executeCommand('/bin/sh', ['-c', 'rg -n "pattern" --files-with-matches | head']);

    execute_command with background: true also closes standard input: a background command that reads stdin now sees end-of-input instead of staying alive until it is killed. Retrieving that background process's handle no longer provides a writable stdin — use processes.spawn() with the default 'pipe' mode for interactive processes.

    processes.spawn() keeps a writable stdin by default so long-running processes can be driven with sendStdin(). It now also accepts a public stdinMode option — pass 'ignore' to close stdin when nothing will feed it:

    // opt-in: close stdin on a spawned process
    const handle = await sandbox.processes.spawn('node server.js', { stdinMode: 'ignore' });

    Output-only execution paths (executeCommand() and execute_command with background: true) pass this option automatically. Honored by the local, Docker, and E2B providers; other providers may not expose stdin control.

  • Fixed execute command exit events to preserve provider termination details for Studio status displays. (#24453)

  • Fixed model router cache initialization so Cloudflare Workers can load bundles before request handling begins. (#24454)

  • Sanitize delegated agent lifecycle payloads in thread-stream broadcasts to prevent conversation data from compounding across delegation levels. (#24399)

  • Fixed strict structured-output failures when using a separate structuring model. Failed requests now retry up to maxProcessorRetries. Warn and fallback behavior is unchanged. (#24059)

  • Fixed agent thread streams and durable stream adapters leaving PubSub deliveries unacknowledged. Every delivered event is now acknowledged once handled, so persistent backends like Redis Streams or GCP Pub/Sub no longer accumulate pending messages on these subscriptions. (#24462)

  • Throw a clear isolation error when LocalSandbox is configured to use macOS Seatbelt on Windows. (#24273)

  • Fixed durable agents on cross-process engines (like Inngest) dropping requestContext values written by input processors. (#24133)

    Values set with requestContext.set(...) inside an input processor now reach tools and scorers running on a separate worker process. Framework-internal entries (model version overrides, memory instances, auth tokens) are still kept out of the persisted workflow input. Fixes #23904

  • Fix ToolCallFilter with preserveModelOutput retaining raw fallback tool results in the model prompt. Filtered results now preserve only explicitly produced compact model output, while raw tool arguments and results remain excluded. Fixes #22630. (#23962)

  • Fixed TokenLimiterProcessor truncation (strategy: 'truncate') splitting UTF-16 surrogate pairs. (#24096)

    Truncated text that ends inside an emoji or other astral character no longer contains a lone surrogate. Lone surrogates are replaced with U+FFFD, so the output round-trips through UTF-8 unchanged and strict JSON consumers can reuse truncated messages as history. This matches the repair already applied to workspace tool output.

  • Fixed structured output failures to preserve validation errors and raw invalid model output for diagnostics. (#24465)

  • Fixed sub-agent failures being reported to the supervisor as an empty successful result when using stream(). A sub-agent whose model call fails (for example an invalid API key) now produces an error tool result in both generate() and stream(), with a generic error message by default. The underlying cause remains available to the completion hook and diagnostics rather than being included in the supervisor's tool-result text. onDelegationComplete receives success: false with the error, and calling bail() from that hook on a failed delegation now stops the supervisor loop as expected. Partial streamed child messages and available result metadata remain available to the completion hook. Failed invocations retain child-thread references so Studio can restore the partial transcript after reload. Background failures follow the configured retry policy, with completion hooks invoked per attempt. A failure hook's resultText replaces the error text seen by the supervisor without recovering the failed delegation or discarding its original error. (#23420)

  • Fixed unrecorded conditional arms appearing successful when time-travelling past them. Preserve explicit replacement output for recorded failed arms. (#24184)

  • Fixed workflow experiments skipping a suspended branch with resume data when an earlier suspended branch has none. The dataset experiment auto-resume loop now scans all suspended branches and resumes the first one with matching resumeSteps/resumeData, instead of stopping at the first suspended branch. (#24041)

  • Allow setting resourceId on workflow schedules so scheduled runs are attributed to a resource. The optional resourceId is accepted on create and update, returned in schedule responses, and carried through both the scheduler and manual fire paths into the run snapshot, enabling multi-tenant correlation and filtering. schedules.list({ resourceId }) now matches workflow schedules too. Unlike agent schedules (where resourceId is part of thread identity), a workflow schedule's resourceId is pure run-attribution metadata and can be updated via PATCH. resourceId is optional, so existing schedules and callers are unaffected. (#24173)

    // Attribute scheduled runs to a resource
    const schedule = await mastra.schedules.create({
      workflowId: 'daily-report',
      cron: '0 9 * * *',
      resourceId: 'tenant-123',
    });
    
    // resourceId can be updated later
    await mastra.schedules.update(schedule.id, { resourceId: 'tenant-456' });

    Also in: @mastra/server@1.68.0

  • Add 'canceled' to the public workflow step-status contract. StepResult, SerializedStepResult, and the derived WorkflowStepStatus now include a StepCanceled variant, matching the status: 'canceled' results the runtime already emits and persists for canceled control-flow steps (e.g. foreach and loops). Typed consumers of getWorkflowRunById(), WorkflowState.steps, and lifecycle callback step results can now represent canceled steps without casts. (#24098)

    import type { WorkflowStepStatus } from '@mastra/core/workflows';
    
    const run = await workflow.getWorkflowRunById(runId);
    const step = run?.steps?.['process-items'];
    if (step && !Array.isArray(step)) {
      const status: WorkflowStepStatus = step.status; // may now be 'canceled'
      if (status === 'canceled') {
        console.log('canceled with partial output:', step.output);
      }
    }

    Note: if you have an exhaustive switch or a Record<WorkflowStepStatus, ...> over step statuses, TypeScript will now require a 'canceled' case. This reflects a value the runtime was already producing.

  • Fixed durable agent runs so thread title generation no longer delays completion. (#24335)

@mastra/ai-sdk@1.10.4

Patch Changes

  • Fixed progressive streams for deeply nested agents. data-tool-agent and data-tool-agent-step parts now include the ordered delegation path, nesting depth, and immediate parent agent ID. (#21735)

@mastra/arize@1.3.17

Patch Changes

  • Map the OpenInference LLM span kind to the exported chat call (model_inference) instead of model_generation and model_step, so Phoenix counts each model call's tokens once. The generation loop and its steps are now CHAIN spans. (#23910)

@mastra/arthur@0.4.17

Patch Changes

  • Map the OpenInference LLM span kind to the exported chat call (model_inference) instead of model_generation and model_step, so each model call's tokens are counted once. The generation loop and its steps are now CHAIN spans. (#23910)

@mastra/auth-auth0@1.2.4

Patch Changes

  • Fixed a jose security advisory by updating jose to 6.2.11. Auth token verification APIs are unchanged. (#24027)

    Also in: @mastra/auth-google@0.1.3, @mastra/auth-neon@0.3.3, @mastra/auth-okta@0.2.3

@mastra/auth-better-auth@1.1.6

Patch Changes

  • Fixed Better Auth so attackers cannot tell whether an account exists or send requests as a signed-in user from another site. Updated better-auth to 1.7.4. (#24027)

@mastra/auth-clerk@1.3.0

Minor Changes

  • Added single-organization login restriction to @mastra/auth-clerk. Set organizationId or organizationSlug (or the CLERK_ORGANIZATION_ID / CLERK_ORGANIZATION_SLUG env vars) to only allow members of one Clerk organization to sign in. Non-members are denied during authorization and SSO callback. (#24188)

    new MastraAuthClerk({
      jwksUri: process.env.CLERK_JWKS_URI,
      publishableKey: process.env.CLERK_PUBLISHABLE_KEY,
      secretKey: process.env.CLERK_SECRET_KEY,
      organizationId: process.env.CLERK_ORGANIZATION_ID,
    });

Patch Changes

  • Fixed a stored XSS advisory in Clerk by updating @clerk/backend to 3.17.2. createClerkClient and the rest of the Mastra Clerk auth API are unchanged. (#24027)

@mastra/azure-ai-search@0.1.0

Minor Changes

  • Add @mastra/azure-ai-search, a vector store backed by Azure AI Search. Use it anywhere Mastra accepts a vector store, including RAG pipelines and Memory semantic recall. Metadata filtering works out of the box: filterable fields are added to the index the first time a metadata key is written (autoIndexMetadata, on by default), so Memory's thread_id/resource_id filters need no schema setup. Any string is accepted as a vector ID. Hybrid (vector + full-text), semantic, and multi-vector queries are available through hybridQuery(), advancedQuery(), and multiVectorQuery(). (#24103)

    import { AzureAISearchVector } from '@mastra/azure-ai-search';
    
    const store = new AzureAISearchVector({
      id: 'azure-search-vectors',
      endpoint: process.env.AZURE_AI_SEARCH_ENDPOINT!,
      credential: process.env.AZURE_AI_SEARCH_CREDENTIAL!,
    });
    
    await store.createIndex({ indexName: 'my-collection', dimension: 1536 });
    await store.upsert({ indexName: 'my-collection', vectors: embeddings });

@mastra/browser-viewer@0.2.4

Patch Changes

  • Remove unused dependency (#23998)

    Also in: @mastra/mcp-docs-server@1.2.27, @mastra/mongodb@1.18.9

@mastra/clickhouse@1.20.0

Minor Changes

  • Added discovery-specific ClickHouse timeout and memory budgets with stable resource-limit errors. (#24169)

    const store = new ClickhouseStoreVNext({
      id: 'clickhouse-storage',
      url: 'http://localhost:8123',
      username: 'default',
      password: 'password',
      observability: {
        traceQuery: {
          discovery: {
            timeoutMs: 5_000,
            memoryLimitBytes: 256 * 1024 * 1024,
          },
        },
      },
    });
  • Added idempotent TTL updates for existing ClickHouse observability tables through applyRetention(). When every observability signal has a retention period, deletion-request records expire after the longest signal retention plus 30 days. If any signal is unbounded, deletion-request records remain unbounded so they continue to prevent deleted data from being reintroduced. (#23466)

    const observability = new ObservabilityStorageClickhouseVNext({
      client,
      retention: {
        tracing: 30,
        logs: 30,
        metrics: 30,
        scores: 90,
        feedback: 90,
      },
    });
    
    await observability.applyRetention();
  • Added list-compatible page pagination for advanced trace queries in ClickHouse storage. (#24061)

    const result = await client.queryTraces({
      timeRange,
      pagination: { page: 0, perPage: 25 },
    });
  • Added ClickHouse support for handing numbered trace-query pages to delta polling, with stable cursor tie-breaking and the existing two-day retention window. (#24329)

    Numbered pages remain available without a polling cursor when the installed core version lacks trace-query delta support.

    // Start with a numbered page.
    const page = await client.queryTraces({ timeRange, pagination: { page: 0, perPage: 100 } });
    // Continue with delta polling.
    const delta = await client.queryTraces({ timeRange, mode: 'delta', after: page.deltaCursor });
  • Added bounded trace-query field and value discovery for ClickHouse observability storage. (#24075)

    const observability = await storage.getStore('observability');
    const fields = await observability?.getTraceQueryObservedFields(fieldsPlan);
    const values = await observability?.getTraceQueryValues(valuesPlan);

Patch Changes

  • Added an index on ClickHouse deletion requests so the check that blocks updates to deleted feedback reads fewer rows instead of scanning every deletion request in the tenant scope. Existing deployments pick up the index automatically on the next start; previously written data is covered as it merges. (#23990)

  • Fixed rewritten observability scores so ordinary reads and trace predicates use compact current state and consistently return the latest sequentially written value for each score ID. Overlapping concurrent rewrites of one score ID have an undefined winner. (#24242)

@mastra/client-js@1.47.0

Minor Changes

  • Added page-based pagination for advanced trace queries. Paginated responses include pagination metadata with total, page, perPage, and hasMore. (#24061)

    const result = await client.queryTraces({
      timeRange,
      pagination: { page: 0, perPage: 25 },
    });
  • Added typed queryTraceThreads() methods for querying thread identities across eligible traces. (#23920)

    const result = await mastraClient.queryTraceThreads({
      traces: {
        timeRange: { from: '2026-08-01T00:00:00Z', to: '2026-09-01T00:00:00Z' },
      },
    });

    queryTraces() remains trace-only, while queryTraceThreads() returns observability-derived thread identities.

  • Added Client JS methods for bounded trace-query field and value discovery. Requests can now override client-level retry and abort settings with the per-request retries and signal options. Retry counts must be non-negative safe integers, and aborted requests stop without retrying. (#24109)

    const fields = await mastraClient.getTraceQueryFields({
      timeRange,
      predicateScope: 'trace',
    });
    
    const values = await mastraClient.getTraceQueryValues({
      timeRange,
      predicateScope: 'spans',
      path: 'model',
    });
  • Added delta polling inputs and response types for queryTraces(), including the cursor returned with numbered pages. (#24329)

    // Start with a numbered page.
    const page = await client.queryTraces({ timeRange, pagination: { page: 0, perPage: 100 } });
    // Continue with delta polling.
    const delta = await client.queryTraces({ timeRange, mode: 'delta', after: page.deltaCursor });

Patch Changes

  • Accept A2A v1 PascalCase JSON-RPC method names when the A2A-Version: 1.0 header is present. Normalize method names before dispatch and streaming response selection while preserving legacy slash-style methods. (#24260)

    For example, retrieve an existing task with GetTask (replace the agent and task IDs with your own):

    POST /api/a2a/my-agent HTTP/1.1
    Content-Type: application/json
    A2A-Version: 1.0
    
    {"jsonrpc":"2.0","id":"request-1","method":"GetTask","params":{"id":"task-1"}}

    Also in: @mastra/server@1.68.0

  • Added (#24261)

    Added methods to MastraClient.getA2AV1() to create, get, list, and delete task push-notification configurations without switching to the v0.3 client. List results include pagination metadata.

    For an existing MastraClient instance, register a callback for a task:

    const a2a = client.getA2AV1('agent-id');
    await a2a.createTaskPushNotificationConfig({
      tenant: 'tenant-1',
      id: 'config-1',
      taskId: 'task-1',
      url: 'https://example.com/callback',
      token: 'callback-token',
      authentication: { scheme: 'Bearer', credentials: 'callback-secret' },
    });
  • Add pagination support to listThreadMessages (#23669)

    The listThreadMessages client API now accepts pagination parameters to allow fetching previous chunks of conversation history.

    const messages = await client.listThreadMessages('thread-123', {
      page: 1,
      perPage: 40,
    });
  • Fixed a jose security advisory by updating jose to 6.2.11. Client JWT helpers are unchanged. (#24027)

  • Added expectedRunId support when aborting agent thread runs. (#24452)

  • Fixed an uncaught ERR_INVALID_STATE error when a consumer cancels an agent stream after its finish chunk. (#24303)

    Stream cancellation no longer produces an unhandled rejection.

  • Improved client request and response types so they stay aligned with Mastra server routes. (#23961)

  • Fixed request context query parsing for POST requests. (#23961)

    Also in: @mastra/express@1.5.12, @mastra/fastify@1.5.12, @mastra/hono@1.7.10, @mastra/playground-ui@56.0.0, @mastra/server@1.68.0

  • Fixed agent.stream() cancellation in @mastra/client-js. Cancelling a returned stream now aborts the underlying HTTP request and stops pending client-tool executions and follow-up requests. Fixes #24271. (#24310)

    Added a per-call abortSignal option to stream(), streamUntilIdle(), resumeStream(), resumeStreamUntilIdle(), approveToolCall(), declineToolCall(), streamLegacy(), generate() and generateLegacy(). It is merged with the client-wide abortSignal, and aborted requests are not retried.

    const controller = new AbortController();
    const response = await agent.stream('Hello', { abortSignal: controller.signal });
    // later
    controller.abort();
  • MCPTool.execute() is typed as the REST route's response ({ result } or { status: 'suspended', suspendPayload, resumeSchema }) and accepts resumeData and suspendPayload so a suspended tool on a 2026-07-28 MCP server can be continued. (#23875)

    const tool = client.getMcpServerTool('returns', 'createReturn');
    const first = await tool.execute({ data: { orderId: 'ord_1' } });
    if ('status' in first && first.status === 'suspended') {
      const done = await tool.execute({
        data: { orderId: 'ord_1' },
        resumeData: { confirmed: true },
        suspendPayload: first.suspendPayload,
      });
    }
  • Added an optional notScorable field on experiment item score results so clients can tell a skipped run apart from a scorer error. (#24378)

    for (const score of item.scores) {
      if (score.notScorable) {
        // skipped — score and error are null
      } else if (score.error) {
        // scorer failed
      } else {
        // score.score is a number
      }
    }

    Also in: @mastra/server@1.68.0

  • GetWorkflowRunByIdResponse.serializedStepGraph is typed as the core SerializedStepFlowEntry[], like GetWorkflowResponse.stepGraph, instead of the generated route shape. (#24030)

  • Clients can now send the full generateTitle configuration with a memory config: minMessages (minimum thread messages before a title is generated), emitEvent (stream the generated title as a transient data-thread-title chunk before finish), and an optional model (defaults to the agent's model). (#24247)

    const agent = client.getAgent('assistant');
    
    const response = await agent.stream('Plan my trip to Kyoto', {
      memory: {
        thread: 'thread-1',
        resource: 'user-1',
        options: {
          generateTitle: { emitEvent: true, minMessages: 2 },
        },
      },
    });
    
    await response.processDataStream({
      onChunk: async chunk => {
        if (chunk.type === 'data-thread-title') {
          console.log(chunk.data.threadId, chunk.data.title);
        }
      },
    });
  • Fix getA2AV1() to send PascalCase A2A v1 JSON-RPC method names for message and task operations, enabling interoperability with v1-compliant servers. The v0.3 client is unchanged. (#24262)

  • Added `orderBy` to `listDatasets`, `listDatasetItems`, `listExperiments`, `listDatasetExperiments` and `listDatasetExperimentResults`. (#24567)

    ```ts
    await client.listDatasets({ orderBy: { field: "updatedAt", direction: "DESC" } });
    ```

@mastra/cloudflare-sandbox@0.5.0

Minor Changes

  • Added bucket mounts to CloudflareSandbox so files can survive container sleep. Cloudflare stops an idle container after sleepAfter, and the next command starts fresh, so /workspace was silently lost between agent turns. The sandbox now implements the Workspace mounts hook: any S3Filesystem (including Cloudflare R2) listed under mounts is mounted through the bridge on start(). A slept container boots without its mounts, so the sandbox detects and re-mounts any dropped paths before the next filesystem operation, keeping mounted data durable across sleep. (#24100)

    const workspace = new Workspace({
      sandbox: new CloudflareSandbox({ baseUrl, apiToken }),
      mounts: {
        '/workspace/data': new S3Filesystem({
          bucket: 'agent-data',
          region: 'auto',
          endpoint,
          accessKeyId,
          secretAccessKey,
        }),
      },
    });

    The default instructions now tell the model that /workspace is scratch space that does not survive between commands and list the mounted paths to use instead. Fixes #23706.

@mastra/code-sdk@1.8.0

Minor Changes

  • Added native background tool and delegated subagent support to Mastra Code sessions. (#19960)

    Set backgroundTools.enabled to true in Mastra Code settings to make read-only workspace tools and the Alexandria expert background-eligible. Eligible tools remain foreground by default, and agents can opt individual calls into deferred or awaited execution through the Core _background.disposition override.

    Mastra Code factory results now expose backgroundCompletionEvents, which publishes reconciled completed, failed, and cancelled events for the originating resource and thread:

    const mastraCode = await createMastraCode(options);
    
    const unsubscribe = mastraCode.backgroundCompletionEvents.subscribe(event => {
      console.log(event.taskId, event.status);
    });
  • Zero-config MCP OAuth now identifies Mastra Code with its Client ID Metadata Document (https://code.mastra.ai/.well-known/oauth-client/mastracode.json) instead of dynamic client registration, which @mastra/mcp 2.x no longer performs. Servers whose authorization server accepts URL-based client IDs keep working with a bare url entry; servers that require a registered client need oauth.clientId in mcp.json. (#23876)

    {
      "mcpServers": {
        "notes": {
          "url": "https://notes.example.com/mcp"
        },
        "billing": {
          "url": "https://billing.example.com/mcp",
          "oauth": {
            "clientId": "mastra-code-billing",
            "scopes": ["invoices:read"]
          }
        }
      }
    }
  • Added context.getStorage() for plugins that start nested controllers. It supplies the host's storage, backend, and vector instances so plugins can avoid opening shared SQLite files through separate native libraries. (#24132)

    const sharedStorage = context.getStorage?.();
    if (!sharedStorage) throw new Error('Shared storage requires a newer Mastra Code host.');
    const nested = await bootLocalAgentController({ cwd: context.cwd, ...sharedStorage });

    The host owns these instances. Nested controllers must not close them or run storage maintenance on them.

Patch Changes

  • Rotate OAuth accounts automatically on eligible request failures. When the active account is rate-limited, quota-exhausted, or fails authentication after one forced token refresh, Mastra Code activates the next account in the pool and retries the request. Server errors and outages exhaust the transient retry budget first and then surface without another account being activated. Every switch appears in the transcript as a one-line notice and is persisted in thread history. (#23711)

    Add accounts through the TUI — /login on an already-connected provider offers Add another account:

    /login
      → Add another account        # completes OAuth, returns to the manager
      → (submenu) Set as active    # optional; rotation happens on demand anyway
    

    No configuration is needed beyond having two or more accounts for a provider; rotation walks the provider's accounts in insertion order, starting from the account the pool is currently on.

  • Fixed a zod version mismatch that could make tool and workflow schemas built by these packages incompatible with schemas from @mastra/core. (#24428)

    Also in: @mastra/factory@0.16.0

  • Fixed low-priority fire-and-forget signals being reported as failed after they were queued for notification summaries. Summarized reply requests now persist the notification but return a clear error instead of falsely recording a reply obligation, so callers can send a new request at a priority that routes directly. Policy-discarded signals remain retryable. (#23696)

  • Fixed Mastra Code exporting its own traces into whichever Mastra project's .env it was launched from, and crashing on startup when that project's MASTRA_PROJECT_ID was not a valid id. Mastra Code cloud observability now only reads its own environment variables (or the /observability connect settings) and ignores MASTRA_PLATFORM_OBSERVABILITY_ENDPOINT from the environment. (#24627)

    If you configured Mastra Code cloud observability through environment variables, rename them:

    # before
    export MASTRA_CLOUD_ACCESS_TOKEN=...
    export MASTRA_PROJECT_ID=...
    
    # after
    export MASTRACODE_CLOUD_ACCESS_TOKEN=...
    export MASTRACODE_PROJECT_ID=...
  • Added multiple OAuth accounts per provider. Sign in with as many accounts per provider as you like. Running /login on an already-connected provider opens an account manager. There you can add another account, switch the active one, re-authenticate, or remove accounts. Added accounts stay inactive until you select one. Accounts carry labels — email for ChatGPT/xAI, GitHub login for Copilot. Credentials keep the same auth.json slot format, so existing setups are untouched. (#23709)

    Account ids are assigned once, when an account is first registered, and no longer derived from the refresh token — so refreshing a token or re-authenticating an account no longer changes which account it is, and adding an account you already have updates it instead of registering a duplicate for the same subscription. Existing auth.json files are read as-is; registered accounts additionally learn their provider's stable account identifier on next load, where the provider exposes one.

    Add and select accounts from /login:

    /login               # choose a provider you are already signed in to
    Add another account  # sign in again; the new account is registered but inactive
    Set as active        # make an added account the one new requests use
    
  • Fixed the goal box appearing twice in the transcript. (#24342)

    Starting or resuming a goal drew the goal box locally and the agent also echoed the same reminder back into the live transcript, so the goal was shown twice in a row. The box is now rendered once, from the echoed reminder.

    The reminder keeps the goal's attempt budget and judge model, so the box shows both instead of dropping the attempt count. The same reminder is also deduplicated, so a repeated signal cannot render the box a second time.

  • Improved cross-agent discovery with live thread titles and passive sender attribution for individually delivered peer signals. (#23696)

    Inbound peer signals now expose the stable sender id as sourcePeerId, including fire-and-forget messages, while returnPeerId is included only when a reply is required. Low-priority signals may first appear as a count-only notification summary and expose their full attribution when opened from the notification inbox:

    <notification sourcePeerId="code-agent:resource-1:thread-1" expectsReply="false">
      Peer work completed.
    </notification>
  • Added fallback model packs and pack-specific subscription routing. In /models, you can configure a fallback chain and choose the OAuth account each model in a pack uses. A selected account is used exclusively for that model: if it fails, the request moves to the pack's fallback chain instead of another account, so a heavy model cannot spend a second subscription's quota. Automatic keeps rotating through the provider's accounts in insertion order, starting from the account the pool is currently on, and a fallback pack applies its own routing. Pack hops remain visible in the transcript and persist when you reopen the thread. (#23725)

    Configure both from /models — select a pack, then:

    /models
      → Set fallback…            # choose the pack to hop to when this pool is exhausted
      → Set subscription routing… # per model: pin one account, or Automatic
    

    Custom packs can also define an observational memory model. When set, the OM observer and reflector resolve from the active pack and its fallback chain — so OM keeps working when a pack's provider is down. Packs without an OM model keep using your standalone OM configuration, and explicit /om overrides still win.

    Both settings live in settings.json if you prefer to edit them directly:

    {
      "customModelPacks": [
        {
          "name": "Daily",
          "models": {
            "build": "anthropic/claude-sonnet-4-6",
            "memory": "anthropic/claude-haiku-4-5"
          }
        }
      ],
      "models": {
        "packFallbacks": { "custom:Daily": "anthropic" },
        "packAccountPreferences": {
          "custom:Daily": { "anthropic/claude-sonnet-4-6": "anthropic:a1b2c3d4" }
        }
      }
    }
  • Fixed cross-agent signals so a peer stays reachable after a session moves to another conversation. A session advertised only its active thread, so peers that had saved an earlier thread could no longer send messages to it after the user started a new thread or switched threads. Every thread a session has loaded now stays claimed, and a wake sent to a saved thread runs on that thread instead of the session's current one. Peer listings no longer show a session's own earlier threads as discoverable agents. (#24510)

@mastra/codemod@1.1.3

Patch Changes

  • Fixed codemod previews and result summaries so they appear in the terminal. (#24530)

  • Fixed codemods so projects under hidden parent directories are processed while hidden directories inside the target remain excluded. (#24528)

  • Fixed v0 RuntimeContext imports from @mastra/core/di so they migrate to RequestContext. (#24472)

  • Fixed unknown codemod names so they fail clearly before processing files. (#24470)

  • Fixed verbose codemod runs so they pass a valid diagnostic level to jscodeshift. (#24251)

@mastra/connect@0.2.0

Minor Changes

  • Added the generated Linear provider with 46 tools for issues, projects, cycles, teams, users, workflow states, comments, attachments, relations, and labels. (#23527)

    connect() now automatically exposes the Linear toolset when the project has a matching linear connection. Set MASTRA_LINEAR_CONNECTION_ID or pass integrations.linear.connectionId when the project has multiple Linear connections.

  • Added automatic discovery of MCP-backed integrations from the Mastra Platform catalog. Connected MCP providers require no checked-in provider registration: connect() discovers their tools through Platform, keeps provider credentials outside the application process, and preserves Platform proxy analytics. Discovered MCP tools require tool approval unless the application lists them in autoApproveTools. (#23996)

    import { connect } from '@mastra/connect';
    
    const tools = connect({
      projectId: process.env.MASTRA_PROJECT_ID,
      client: { accessToken: process.env.MASTRA_PLATFORM_ACCESS_TOKEN },
      integrations: {
        // An MCP-backed integration attached to the project; tools are discovered at runtime.
        neon: { autoApproveTools: ['neon_list_projects', 'neon_describe_project'] },
      },
    });
  • Added broad Resend and incident.io tool coverage backed by Platform connections. Resend covers email operations and account resources such as domains, templates, audiences, contacts, broadcasts, and webhooks. incident.io covers incident response, alerts, on-call data, teams, users, postmortems, and catalog reads. (#23631)

    import { connect } from '@mastra/connect';
    
    const tools = connect({
      projectId: process.env.MASTRA_PROJECT_ID,
      client: { accessToken: process.env.MASTRA_PLATFORM_ACCESS_TOKEN },
      integrations: {
        resend: { allowTools: ['resend_send_email', 'resend_get_email'] },
        'incident-io': { allowTools: ['incident_io_list_incidents'] },
      },
    });
  • Added Snowflake tools backed by Platform connections using the OAuth snowflake integration. Agents can run SQL statements (with async statement polling and cancellation) and browse warehouses, databases, schemas, tables, columns, views, stages, streams, tasks, roles, and users. The execute-statement tool runs any SQL the connection's Snowflake role permits, so scope the connected user to least privilege or restrict the toolset with allowTools. (#24066)

    import { connect } from '@mastra/connect';
    
    const tools = connect({
      projectId: process.env.MASTRA_PROJECT_ID,
      client: { accessToken: process.env.MASTRA_PLATFORM_ACCESS_TOKEN },
      integrations: {
        snowflake: { allowTools: ['snowflake_execute_statement', 'snowflake_list_tables'] },
      },
    });
  • Add a generated Jira provider with 37 tools covering issue lifecycle (create, update, transition, link), comments, worklogs, watchers, projects, users, and metadata lookups. The tool runtime now supports the template updateMetadata helper by caching derived connection facts (such as the Atlassian cloud ID) in memory for the lifetime of a toolset, so tools skip repeat discovery round-trips. (#24066)

  • Added built-in Clerk and WorkOS providers with 42 and 40 tools respectively for identity, organization, directory, connection, invitation, membership, and domain administration. Provider tools can now forward repeated query parameters for multi-value filters. (#23527)

  • Added built-in Anthropic, Notion, OpenAI, and Supabase providers. Generated tools can now read safe connection configuration and metadata through the platform proxy, enabling providers with connection-specific API hosts. (#23527)

Patch Changes

  • Fixed OpenAI image generation tools to send base64 images to models as multimodal image content instead of JSON text, preventing generated images from consuming the text context window. Updated the generated OpenAI tools to the current API parameters. (#23527)

  • Removed warnings for providers that do not have project connections. Connect now silently skips unavailable providers while continuing to report actionable connection problems. (#23527)

  • Raise the @mastra/core peer floor to >=1.68.0-0 to match @mastra/mcp 2.x, which @mastra/connect uses for catalog MCP discovery. (#23876)

  • Validate baseUrlOverride on the connection-proxy client before it is forwarded as a request header. Only absolute HTTPS URLs without embedded credentials are accepted; unparseable values, non-HTTPS schemes, and userinfo-bearing URLs are rejected with invalid_options so a compromised connection config cannot redirect authenticated proxy traffic to an unintended origin. (#23527)

@mastra/daytona@0.11.1

Patch Changes

  • Fixed Daytona sandbox destruction so temporary deletion failures are reported and can be retried. (#24449)

@mastra/deployer@1.68.0

Patch Changes

  • Fixed deploy builds to stop when a workspace package import cannot be bundled. Add the package as a direct dependency or correct the workspace package configuration. (#24210)

  • Fixed builds to reject unresolved subpath imports from externalized workspace packages instead of producing bundles that fail at runtime. (#24450)

  • Fixed builds that use dependency subpath imports when the package exposes a nested module package.json. Fixes #12535. (#24137)

  • Fixed builds with externals so workspace subpath imports used by other workspace packages are compiled instead of failing at runtime. Fixes #22851. (#24131)

  • Fixed build output manifests including dependencies from inactive NODE_ENV branches. (#24385)

  • Fixed build dependency resolution so bundled output stays consistent when a project is built from its app directory or from a monorepo root. (#24212)

  • Fixed builds with configured externals by preventing the analyzer from loading those dependencies. (#24549)

  • Fixed deployment builds to reuse and update the source package-manager lockfile while installing dependencies. (#24226)

  • Fixed monorepo builds when packaging scoped workspace dependencies with ESM-only slug generation. (#24272)

@mastra/docker@0.9.0

Minor Changes

  • Add a DockerTemplate API for preparing reusable, content-addressed baseline images for the local Docker sandbox, with the same builder and repo-template contract as the E2B and platform providers. (#24199)

    Prepare an environment once — a base image plus ordered setup commands, env vars, and package installs — then spawn multiple disposable DockerSandboxes from it via the new template option. Each sandbox is a fresh container with its own writable layer over the shared read-only image, so their filesystems are independent. The baseline is produced by synthesizing a Dockerfile and running docker build, so setup is baked into reproducible, cached layers.

    import { DockerSandbox, DockerTemplate, createDockerRepoTemplate } from '@mastra/docker';
    
    const template = new DockerTemplate({ baseImage: 'node:22-slim' })
      .aptInstall(['git', 'ca-certificates'])
      .runCmd('git clone --depth=1 https://example.com/repo /workspace/app')
      .setWorkdir('/workspace/app')
      .runCmd('npm ci');
    
    // Builds the image on first start() and reuses it afterwards; the sandbox's
    // working directory follows the template's setWorkdir().
    const a = new DockerSandbox({ template });
    const b = new DockerSandbox({ template });
    
    // Repository checkout pinned to the current head of a branch, rebuilt when it moves.
    const sandbox = new DockerSandbox({
      template: createDockerRepoTemplate({
        getRepositoryAccess: async () => ({ cloneUrl: 'https://github.com/acme/app.git' }),
        setupCommand: ['npm ci', 'npm run build'],
      }),
    });
    • Immutable, chainable builder methods (from, setWorkdir, setEnvs, runCmd, runWithSecrets, aptInstall, pipInstall, npmInstall) matching the E2B/platform builders.
    • Content-addressed image tag (mastra-template:<hash>); build() is idempotent and reuses an existing image unless { force: true } is passed. Build failures are returned as { status: 'failed' } and retried on the next attempt.
    • DockerSandbox({ template }) accepts a template or an async template factory resolved once per container-creating start().
    • Build-time secrets via runWithSecrets(command, { secrets, output }): the step runs in a throwaway build stage forked from the steps before it, secret values are passed by value ({ secrets } on the template or build()) and delivered through BuildKit secret mounts, and only output is copied into the image, so values never land in any layer, history entry, or build-cache metadata, nor in the template identity.
    • createDockerRepoTemplate({ getRepositoryAccess, ref, setupCommand, buildEnv, workingDirectory }) prepares a repository checkout and setup as a template factory, resolving the head of ref on each sandbox start and pinning it into the identity. The credential from getRepositoryAccess is used only for the head lookup and the clone stage.
  • Added AbortSignal cancellation for Docker template builds, repository template resolution, and lazy sandbox starts. Cancelling startup stops local template-preparation streams and sessions, rejects with SandboxAbortError while preserving the signal's custom reason as the error cause, and leaves the template retryable. (#24451)

    const startController = new AbortController();
    const start = sandbox.start({ abortSignal: startController.signal });
    startController.abort(new Error('request cancelled'));
    try {
      await start;
    } catch (error) {
      if (!(error instanceof SandboxAbortError)) throw error;
    }
    
    const buildController = new AbortController();
    const build = template.build({ abortSignal: buildController.signal });
    buildController.abort(new Error('request cancelled'));
    try {
      await build;
    } catch (error) {
      if (!(error instanceof SandboxAbortError)) throw error;
    }

Patch Changes

  • Fixed Docker sandbox process kills keeping helper response streams open. (#24448)

  • Fixed commands that read stdin hanging until timeout (#24336)

    Commands that read standard input without being given anything to read — a bare cat, or grep/rg with no path argument — blocked until the command timeout expired. The exec no longer attaches stdin unless something will feed it, so these commands see end-of-input and exit immediately.

    processes.spawn() is unchanged: it still attaches stdin by default so long-running processes can be driven with sendStdin().

@mastra/dsql@1.5.0

Minor Changes

  • Added configurable age-based pruning for observability spans. (#23466)

    import { DSQLStore } from '@mastra/dsql';
    
    const storage = new DSQLStore({
      id: 'dsql-storage',
      host: 'abc123.dsql.us-east-1.on.aws',
      retention: {
        observability: {
          spans: { maxAge: '30d', batchSize: 1_000 },
        },
      },
    });
    
    await storage.prune({ maxBatches: 10, maxRows: 10_000, pauseMs: 25 });

@mastra/duckdb@1.10.0

Minor Changes

  • Added list-compatible page pagination for advanced trace queries in DuckDB storage. (#24061)

    const result = await client.queryTraces({
      timeRange,
      pagination: { page: 0, perPage: 25 },
    });
  • Added bounded trace-query field and value discovery for DuckDB observability storage. (#24075)

    const observability = await storage.getStore('observability');
    const fields = await observability?.getTraceQueryObservedFields(fieldsPlan);
    const values = await observability?.getTraceQueryValues(valuesPlan);
  • Added configurable age-based pruning for DuckDB observability spans, metrics, logs, scores, and feedback. (#23466)

    Before

    DuckDB observability data was retained until it was deleted explicitly.

    After

    const storage = new DuckDBStore({
      path: 'mastra.duckdb',
      retention: {
        observability: {
          spans: { maxAge: '30d' },
          logs: { maxAge: '7d' },
        },
      },
    });
    
    await storage.prune();
  • Added DuckDB support for handing numbered trace-query pages to delta polling. The initial page and polling watermark share a snapshot, and polls detect completed root writes. (#24329)

    Numbered pages remain available without a polling cursor when the installed core version lacks trace-query delta support.

    // Start with a numbered page.
    const page = await client.queryTraces({ timeRange, pagination: { page: 0, perPage: 100 } });
    // Continue with delta polling.
    const delta = await client.queryTraces({ timeRange, mode: 'delta', after: page.deltaCursor });

Patch Changes

  • Fixed repeated score IDs in a single batch so the last accepted observability score remains current. (#24242)

  • Returned stable trace-query resource-limit errors when DuckDB reports memory exhaustion during discovery. (#24169)

@mastra/e2b@0.12.1

Patch Changes

  • Fixed commands that read stdin hanging until timeout (#24336)

    Commands that read standard input without being given anything to read — a bare cat, or grep/rg with no path argument — blocked until the command timeout expired. Commands run through executeCommand() no longer keep stdin open, so these commands see end-of-input and exit immediately.

    processes.spawn() is unchanged: it still keeps stdin open so long-running processes can be driven with sendStdin().

  • Honor the configured timeout when reconnecting to or resuming an existing E2B sandbox. Previously timeout was only applied on sandbox creation; the reconnect and resume paths called Sandbox.connect without timeoutMs, so the e2b SDK fell back to its 5-minute default. With the default lifecycle.onTimeout: 'pause', this meant a paused sandbox always came back with a 5-minute window regardless of the configured timeout. Both Sandbox.connect paths now forward timeoutMs, so a resumed sandbox gets at least the configured window. (#24636)

  • Commands without an explicit timeout now use the sandbox timeout (5 minutes by default) instead of hitting E2B's 60 second connection deadline and failing with [deadline_exceeded]. (#24484)

@mastra/editor@0.15.1

Patch Changes

  • ComposioToolProvider.listTools() now rejects with the original SDK error when the Composio catalog request fails (auth, rate limit, network, outage) instead of resolving a successful empty page. An empty result now reliably means the catalog returned no matching tools. (#24292)

  • Fix editor.agent.clearCache() leaving version-specific stored agents registered with Mastra. Agents hydrated via versionId, versionNumber, or a status override skip the value cache but were still registered in the runtime registry, so a no-ID clear-all never evicted them. The Editor agent namespace now tracks the stored-agent IDs it registers and evicts any remaining ones during clear-all, while code-defined agents remain registered. (#24000)

  • Derive inline workspace identity from a canonical, key-order-independent hash so semantically equivalent configs resolve to the same inline-<hash> ID. Previously the ID was hashed from raw JSON.stringify, which preserves object insertion order, so reordered-key configs produced different IDs and created duplicate stored workspaces with unstable references. Array order and value differences remain significant. (#24009)

  • Fixed conditional processor graphs running their fallback branch alongside a matching rule. A default branch (a condition with no rules) now runs only when no explicit rule matches. The internal pass-through runs only when no rule matches and no default exists. A fallback processor no longer mutates messages or causes side effects when an explicit condition already matched. (#24095)

  • Allow the editor to run alongside @mastra/mcp 2.x, whose hydrated servers use the core MCP v2 base. (#23876)

  • Fixed hydrating a stored processor graph so that graph nodes reusing the same processor no longer collapse into a single workflow step. Each node now keeps its own unique step identity, preventing configured nodes from overwriting each other and keeping parallel and conditional branch results distinguishable. (#24099)

@mastra/express@1.5.12

Patch Changes

  • Fixed denial-of-service and CRLF injection advisories by updating @fastify/busboy to 3.2.2. (#24027)

    Also in: @mastra/fastify@1.5.12, @mastra/koa@1.7.12, @mastra/nestjs@0.2.27

@mastra/factory@0.16.0

Minor Changes

  • Brought incident.io follow-up intake to full parity with Linear and Jira. (#24319)

    • Connections: in-app connect and reconnect for Platform-managed incident.io accounts, runtime discovery of multiple installations, and no more MASTRA_INCIDENT_IO_CONNECTION_ID.
    • Auto-ingestion: observed follow-ups materialize as Work-board cards through configurable event rules (followUpObserved, followUpClosed), with close events transitioning cards to done/canceled.
    • Agent tools: board runs get incidentio_get_follow_up for reading follow-up details.
    • Board UX: follow-up cards carry assignee, creator, labels, priority, and incident metadata, plus the same Investigate/Build actions and work-item menu as Linear cards.
    • Routing: teams choose which Factory and board receive follow-ups; incidents stay unrouted.
    import { IncidentioIntegration } from '@mastra/factory';
    
    const incidentio = new IncidentioIntegration({
      apiKey: process.env.INCIDENT_IO_API_KEY!,
      // Optionally override the default follow-up rules:
      rules: { followUpClosed: null }, // disable automatic close transitions
    });
  • Added an explicit delivery choice to Factory worker guidance. (#24138)

    Use delivery: "send" for immediate guidance or delivery: "queue" to wait for the worker’s current run to complete.

  • Added GitLab as a Factory source-control and work-intake provider for direct and Platform-managed deployments, with the same session, board, review, and notification behaviour that GitHub repositories get. Both credential modes can back Factory sessions with GitLab repositories. (#24604)

    • Intake discovers GitLab projects, ingests issues with labels, label colours, assignees, weight-derived priority, and comment counts, reads discussions, adds comments, updates issue state, and routes selected projects to factories from the Settings UI. GitLab issue and GitHub issue routing are stored separately.
    • Version control registers repositories, manages the merge request lifecycle, creates and edits merge request notes, manages diff-anchored review discussions, and adds or removes individual reviewers. Direct tokens and Platform connection credentials both provide authenticated clone and push for repository-backed sessions.
    • Webhook ingress verifies X-Gitlab-Token and routes supported issue, note, and merge request events into Factory rules. Unsupported events, including push events, are acknowledged without changing board state. Issue and merge request reconcilers cover missed terminal events and settle cards the same way the GitHub reconcilers do.
    • Sessions that open a merge request through source_control_create_change_request are subscribed to it automatically. New notes, closes, and merges wake the subscribed session as the user who created it, with a notification that links to the merge request. gitlab_subscribe_mr and gitlab_unsubscribe_mr manage subscriptions by hand, gitlab_get_issue fetches a routed issue with its discussion, and GET /web/gitlab/subscriptions lists a thread's subscriptions for the UI.
    • Review board runs use the factory-gitlab-review and factory-gitlab-rereview skills, check out the merge request head, and re-review when new commits arrive. The UI names merge requests as !n, shows merged and closed states, and offers GitLab in onboarding, repository settings, intake routing, and board empty states.

    GitLab approvals are exposed as an approval snapshot and are used for submitted approve reviews; submitted comment reviews become merge request notes. Individual listed approvals and comment reviews can be fetched through synthetic review IDs. GitLab has no equivalent for mutable pending reviews, request-changes reviews, approval dismissal, team review requests, or synchronous rebase-and-merge, so those operations fail explicitly with a not-supported response.

    import { GitLabIntegration } from '@mastra/factory/integrations/gitlab/integration';
    
    const gitlab = new GitLabIntegration({
      baseUrl: 'https://gitlab.example.com',
      accessToken: process.env.GITLAB_ACCESS_TOKEN!,
      accessTokenType: 'group', // Or 'personal'.
      webhookSecret: process.env.GITLAB_WEBHOOK_SECRET,
    });

    Direct mode reads the same values from GITLAB_ACCESS_TOKEN, GITLAB_ACCESS_TOKEN_TYPE, GITLAB_BASE_URL, and GITLAB_WEBHOOK_SECRET when constructor options are omitted. Personal and Group Access Tokens are both supported; use api and write_repository scopes. GITLAB_BASE_URL must use HTTPS except for loopback development instances.

    When Platform credentials are configured, Factory discovers active GitLab connections for the organization, and MASTRA_GITLAB_CONNECTION_ID optionally pins one of them. Requests go through /v2/connections/{connectionId}/proxy, and MASTRA_GITLAB_WEBHOOK_SECRET verifies webhooks. Explicit direct credentials take precedence. Platform-managed clone and push resolve a fresh repository credential for the selected connection; the connection selector itself is never used as a Git token.

  • Added Linear team intake sources so Factory boards can ingest active projectless issues while preserving project precedence for overlapping selections. (#23929)

    Select a whole Linear team under Settings, Intake, or send the team source id directly. Team source ids come from GET /web/linear/teams; the Platform-backed integration issues opaque ids, the self-managed integration uses linear-team:<teamId>.

    await fetch('/web/intake/config', {
      method: 'PUT',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({
        linear: { enabled: true, sourceIds: ['linear-team:team-1', 'project-1'] },
      }),
    });

    A team source syncs active issues in the triage, backlog, unstarted, and started states, including issues without a project. When a project and its team are both selected, the project selection takes precedence for that project's issues.

    Rerouting a Linear issue no longer creates a duplicate card. The Factory that already has the card keeps it, and the card's details stay available there. A new card appears on the newly routed Factory only after the existing card is finished.

  • Added a Jira Cloud intake integration for the Software Factory with full Linear-equivalent behavior, supporting both direct credentials and Platform-managed connections. (#20579)

    Direct mode: set JIRA_BASE_URL, JIRA_EMAIL, and JIRA_API_TOKEN to use a deployment-global Atlassian API token — no OAuth app setup, intended for self-hosted/single-tenant deployments. Platform mode: with MASTRA_PLATFORM_ACCESS_TOKEN or MASTRA_PLATFORM_SECRET_KEY configured, Factory automatically discovers visible Platform Jira connections (multiple sites supported) and proxies Jira requests through the Platform integrations service; an explicitly configured JiraIntegration takes precedence.

    Factory settings and onboarding connect Jira accounts in-app, select Jira projects as intake sources, and route each project to a Factory board. Observed issues on routed projects materialize automatically as work items, closed issues transition their linked card to done or canceled, and both Jira integrations accept rules overrides for the issueObserved and issueClosed events. A background reconciliation worker keeps imported work items fresh (MASTRACODE_JIRA_RECONCILE_ENABLED, MASTRACODE_JIRA_RECONCILE_INTERVAL_MS). Work cards preserve Jira descriptions, labels, reporters, assignees, priority, project, site, state, and timestamps, appear in the board's teammate filters, and offer the same investigate and build actions as Linear issues. Agents get jira_get_issue and jira_create_comment tools, including on automated board runs.

Patch Changes

  • Fixed GitHub and Linear intake selections being personal: which repositories and Linear projects feed a Factory board is now one organization-wide setting, so every member sees the same intake instead of an empty board until they enable the sources themselves. Existing per-member selections are merged into the shared one on the next start (a source stays selected if any member was syncing it), and the Intake settings now carry the Org-wide badge. (#23982)

  • Fixed Factory plan handoffs when Auto-approve plans is off. (#24001)

    Plan agents now leave work items in Planning until a maintainer approves the plan. Factory does not queue a build before that approval.

    Preapproved plans and projects with Auto-approve plans enabled continue to advance automatically. Arming an autonomous run does not approve a plan.

    Fixes #23742.

  • Fix prepareRunStart replaying a dead run binding after abort recovery. When a stage was re-entered following an abort, the replay guard matched the prior pending-start row by kickoff key alone and returned its original (already revoked) binding, so the re-entry re-bound the dead session and never used the freshly minted one. The replay branch now honors a pending-start row only when its binding is still active; a revoked or missing binding is discarded so a fresh binding is minted instead. (#24097)

  • Fixed synchronous onAccepted hook failures rejecting an already-committed Factory transition and skipping its stage_moved audit record. Synchronous throws are now isolated and logged the same way as asynchronous rejections. (#24304)

  • Fixed Factory session threads keeping their initial work-item title forever. Those titles are derived from the work item rather than typed by a user, so they no longer opt out of automatic title updates. (#23791)

  • Fixed integration feed publishers so an integration that implements feedPublisher() without channels() now receives work-item comment feed events. Previously the publisher was only wired for integrations that also provided a chat channel, so non-channel feed mirrors (webhooks, issue trackers) were silently never called. (#23999)

  • Fixed git credentials appearing in command lines and remote URLs inside session sandboxes. Clone, fetch, push and pull request creation now receive the token through the process environment only. Branch names git would refuse (a..b, x.lock, a trailing /) are now rejected when saved instead of failing later at checkout. (#24604)

  • Fixed Platform-managed GitLab connections created with a personal access token not being discovered. Factory now lists connections from every GitLab credential flow the Platform offers, and the documentation no longer presents MASTRA_GITLAB_CONNECTION_ID as required; it only pins discovery to one connection. (#24630)

  • Platform-connected GitLab deployments now receive issue, note, merge request and push events by polling the Platform event log, so they no longer need a direct project webhook to Factory or a shared MASTRA_GITLAB_WEBHOOK_SECRET. Polling is on by default and controlled with MASTRA_PLATFORM_GITLAB_POLLING_ENABLED and MASTRA_PLATFORM_GITLAB_POLLING_INTERVAL_MS. (#24631)

    A Platform-connected deployment needs no webhook configuration; polling starts with the integration:

    import { PlatformGitLabIntegration } from '@mastra/factory/integrations/platform/gitlab/integration';
    
    // Discovers every active Platform GitLab connection and polls its event log.
    const gitlab = new PlatformGitLabIntegration();
    
    // Tune or disable polling per deployment instead of through the environment.
    const quiet = new PlatformGitLabIntegration({ pollingIntervalMs: 60_000 });
    const webhookOnly = new PlatformGitLabIntegration({ pollingEnabled: false });
  • Fixed the Factory keeping a review or work run executing after an external terminal transition revoked its binding. Terminal-stage cleanup now aborts the live run on each retired seat (leaving alone the seat that drove its own transition and any successor that took the session over), a resumed session whose binding was revoked on a settled item now surfaces a clear retirement error instead of silently dropping its transition tool, and a settled card no longer retries its close-out to the attempt limit as a false blocked state. (#24005)

  • Fixed Factory sign-in button contrast when the app uses a light theme. (#24006)

  • Fixed Factory re-entry re-appending the entire skill document on same-stage re-runs. This happened, for example, when a review re-triggered as a pull request was updated. The session now continues with a compact message that references the already-active skill and carries only the fresh context. This avoids re-pasting the full skill body every time and sharply cuts redundant prompt-cache token usage. (#24084)

  • Fixed Factory skill dispatch sending a second kickoff into a binding whose previous run is still in flight. A new skill decision for a binding now waits for that binding's live run to end before delivering. This holds across dispatcher replicas: the dispatcher hosting a run records its ownership in the shared open-run ledger and heartbeats it, and a replica that sees a fresh claim from another owner retries later instead of starting a duplicate. A stale record left behind by a crashed run no longer blocks the binding. Delivery outcomes that could not be confirmed now fail with the stable codes skill_delivery_ambiguous and run_terminal_event_missing instead of unknown. (#23968)

  • Fixed sessions woken by a pull request comment or close failing with missing-user-context or No usable openai credential. Woken runs now execute as the user who subscribed, with that organization's credentials available. (#24604)

  • Improved Factory PR reviews and re-reviews: (#24626)

    • The reviewer writes its own design for the problem before opening the diff, then judges whether the PR's approach and scope are justified — not only whether the implementation works.
    • Before every verdict it checks its own requested changes: why each belongs in this PR, what happens if the author follows them exactly, and whether each verification probe could actually detect the failure it claims to rule out.
    • Requested changes in the posted review are written as direct, evidence-backed change requests with no optional tiers.
    • The reviewer loads bundled per-category review guidance (behavior change, bug fix, public API, schema/storage, security, and others) matched to the change, before opening the diff and again as the change reveals further categories.

@mastra/github-signals@0.5.0

Minor Changes

  • Added authorization for GitHub App bot comments when the app is owned by the repository organization or by a user with authorized repository access. Explicitly ignored bots and bots whose app ownership cannot be resolved remain denied. (#24246)

    import { GithubSignals } from '@mastra/github-signals';
    
    const githubSignals = new GithubSignals({
      authorizedPermissions: ['admin', 'maintain', 'write'],
      authorizedBots: ['coderabbitai[bot]', 'devin-ai-integration[bot]'],
    });

    Bots not listed in authorizedBots can now trigger notifications when their GitHub App is owned by the repository organization or by a user with one of the configured authorizedPermissions.

Patch Changes

  • Fixed PR syncing failing with a 401 when an expired GitHub token was exported in the environment. Each sync now resolves a fresh credential from the gh CLI and uses it when one is available, so a stale GITHUB_TOKEN or GH_TOKEN is replaced instead of being sent to GitHub. Author permission and GitHub app owner lookups use the same credential. (8808c0e)

@mastra/hono@1.7.10

Patch Changes

  • Fixed custom upload routes waiting for body parsing when no fine-grained authorization provider is configured. (#24563)

  • Fixed a Hono security advisory (CVE-2026-84363 family) by raising the hono dependency and peer ranges to ^4.13.5. (#24308)

    Also in: @mastra/mcp@2.0.0, @mastra/next@0.2.26, @mastra/tanstack-start@0.2.26

@mastra/inngest@1.9.0

Minor Changes

  • Added a shouldPersistSnapshot option to createInngestAgent() for API symmetry with createDurableAgent(). InngestAgent logs a warning and ignores it: Inngest's step memoization and replay own durability, so Mastra snapshots are only persisted for suspended runs (human-in-the-loop resume). (#23978)

Patch Changes

  • Fixed a crash risk in Inngest durable agents where failing to publish an error event to pubsub (for example when the realtime endpoint is unreachable) became an unhandled promise rejection instead of a logged warning. Error reporting is now best-effort: publish failures are caught and logged, matching how abort requests already behave. (#24125)

  • Fixed durable agent abort requests being ignored when the run executes on an Inngest worker. Calling abort() on a run in another process now stops generation and ends the stream with finishReason "abort". Fixes #22543. (#24368)

  • Fixed processor steps created with the Inngest adapter's createStep losing their saved state between calls. Processor state written in one phase (for example processInput) is now visible in later phases and in chained processor steps, matching the behavior of steps created with @mastra/core/workflows. Fixes #23671. (#24163)

  • Fixed completed Inngest workflow runs remaining in memory after resume. (#24267)

  • Fixed durable agents on Inngest never running output processors, persisting memory, or generating thread titles at the end of a turn. Finalization was wrapped in a nested Inngest step, which the Inngest protocol does not support — in HTTP/serve deployments the run hung and never emitted its finish event. Finish side effects now run directly inside the existing durable step boundary, matching the built-in durable engine. Fixes #23815 and #22450. (#24125)

@mastra/laminar@1.3.19

Patch Changes

  • Exported MCP_SERVER_REQUEST spans with SpanKind.SERVER. (#24150)

@mastra/langfuse@1.5.8

Patch Changes

  • Fixed exact OpenRouter generation costs in observability spans and Langfuse exports, including BYOK upstream charges. (#20336)

    Also in: @mastra/observability@1.17.9

  • Fixed the Langfuse exporter sending each span's input and output twice. The payload was written to the observation's input and output fields and then repeated inside its metadata, roughly doubling what Langfuse ingests and stores for workflow, agent and processor spans. Traces look the same as before: the values still appear in the observation's input and output, only the duplicate copy in the metadata is gone. Model generation and tool call spans were never affected. Fixes #23955. (#23956)

@mastra/libsql@1.23.1

Patch Changes

  • Fixed unique-index update errors in the Factory storage adapters. Updates now throw UniqueViolationError, the same error inserts already threw, so callers can handle a duplicate claim consistently. (#23929)

    Also in: @mastra/pg@1.26.0

  • Added storage-level filtering for Agent.listSuspendedRuns() thread lookups. The thread id embedded in suspended run snapshots is now filtered inside SQLite instead of loading every suspended snapshot into the application. Part of #22627 (#24376)

  • Dataset, dataset item, experiment and experiment result listings now honor the orderBy option instead of always returning a fixed order. This requires @mastra/core 1.68.0 or newer. (#24567)

    Also in: @mastra/mongodb@1.18.9, @mastra/mysql@0.10.0, @mastra/pg@1.26.0, @mastra/spanner@1.8.0

  • Fixed prune() to use one cutoff instant across all retained tables so rows near the retention boundary are handled consistently. (#23466)

    Also in: @mastra/pg@1.26.0

@mastra/mcp@2.0.0

Major Changes

  • Rebuilt @mastra/mcp on the MCP 2026-07-28 revision. Servers serve that revision only, and every request is self-contained: there is no initialize handshake, session header, ping, or standalone HTTP+SSE transport. Streamable HTTP responses still stream as Server-Sent Events. Requires @mastra/core 1.68 or newer. The full migration guide is at /reference/migrations/mcp-v2. (#23876)

    Migration. A tool that needs input from the caller no longer awaits context.mcp.elicitation.sendRequest(). It calls context.suspend(payload) and returns; the server answers input_required, and when the caller replies the tool runs again with context.resumeData and context.suspendPayload. On the client, mcp.elicitation.onRequest() becomes a per-server inputRequests handler.

      execute: async ({ orderId }, context) => {
    -   const answer = await context.mcp!.elicitation.sendRequest({ message: 'Address?', requestedSchema });
    -   if (answer.action !== 'accept') return { confirmed: false };
    -   return { confirmed: await book(orderId, answer.content.address) };
    +   if (!context.resumeData) return context.suspend({ phase: 'address' });
    +   return { confirmed: await book(orderId, context.resumeData.address) };
      },
    - mcp.elicitation.onRequest('returns', async params => askUser(params));
    + const mcp = new MCPClient({
    +   servers: { returns: { url, inputRequests: async ({ key, params }) => askUser(key, params) } },
    + });
    import { createTool } from '@mastra/core/tools';
    import { MCPClient, MCPServer } from '@mastra/mcp';
    import { z } from 'zod';
    
    const

Don't miss a new mastra release

NewReleases is sending notifications on new releases.