github mastra-ai/mastra @mastra/core@1.36.0
May 20, 2026

6 hours ago

Highlights

Stored Entity HTTP APIs (Agents/Skills/Workspaces) + Builder/Registry Introspection

@mastra/server adds a full HTTP surface for stored entities (CRUD, versioning, activation/restore) including favorites metadata, plus builder introspection endpoints and an external skill-registry proxy that Studio and @mastra/client-js can consume.

Browser Automation & Screencast Session Probing (incl. Stored Agents)

New browser-provider/editor primitives wire browser automation into the editor, while deployers/adapters add a probeable GET /agents/:agentId/browser/session endpoint and client SDK methods (agent.browserSession() / agent.closeBrowser()) so UIs can safely open screencast WebSockets only when supported—now also working for stored agents via editor fallback lookup.

Delta Polling for Observability List APIs (Server/Client/Stores/UI)

Observability list endpoints now support mode: 'delta' with cursors across core + @mastra/server, with store support in DuckDB/ClickHouse (and in-memory alignment) and typed client support; Playground UI’s traces list updates live via delta polling instead of full-page refetches.

Harder Authorization Defaults: FGA Coverage + ANY-of Permissions + Safer Context Keys

FGA execution checks are now enforced consistently across agents/tools/memory/workflows (requiring authenticated requestContext when invoking protected APIs), and both FGA checks and route permissions accept permission arrays (ANY-of semantics); server auth middleware also writes namespaced request-context keys to avoid collisions.

Convex Production Upgrades: Native Vector Search + Durable Server Cache

@mastra/convex adds ConvexNativeVector backed by schema vector indexes/ctx.vectorSearch for production-grade retrieval, plus ConvexServerCache to persist durable stream replay and response-cache state in Convex.

Breaking Changes

  • AgentSignalContents narrowed to string | (TextPart | FilePart)[] (no longer accepts wrapped BaseMessageListInput shapes); update agent.sendSignal callers accordingly.
  • @mastra/playground-ui Button variants changed: cta/contrast/link removed in favor of default/primary/outline/ghost; ButtonWithTooltip removed (use Button with tooltip prop).
  • PopoverContent no longer forwards onOpenAutoFocus/onCloseAutoFocus (use initialFocus/finalFocus instead).

Changelog

@mastra/core@1.36.0

Minor Changes

  • Added activateAfterIdle: "auto" for Observational Memory early activation. (#16663)

    Mastra can now choose an idle activation timeout from the active model provider's prompt cache behavior. OpenAI also respects providerOptions.openai.promptCacheRetention when available.

    const memory = new Memory({
      options: {
        observationalMemory: {
          model: 'google/gemini-2.5-flash',
          activateAfterIdle: 'auto',
          activateOnProviderChange: true,
        },
      },
    });
  • Added support for permission arrays in FGA checks and route configuration. When an array is provided, the user needs any one of the listed permissions (logical OR). (#16605)

    Affected types

    • FGACheckParams.permission
    • FGARouteConfig.permission
    • FGARouteInfo.requiresPermission
    • FGADeniedError.permission
    • CheckFGAOptions.permission

    Single-permission usage continues to work unchanged.

    // Before — single permission only
    await fga.check({
      resource: { type: 'agent', id: 'abc' },
      permission: 'agents:read',
    });
    
    // After — single permission or array (ANY-of)
    await fga.check({
      resource: { type: 'agent', id: 'abc' },
      permission: ['agents:read', 'agents:execute'],
    });
  • Added consistent FGA execution checks across agents, tools, memory, and workflows to prevent unauthenticated executions when FGA is configured. Pass an authenticated user through requestContext when invoking protected APIs directly: (#16651)

    const requestContext = new RequestContext();
    requestContext.set('user', user);
    
    await agent.generate('Summarize this thread', {
      requestContext,
    });
  • Added the EditorFavorite* types and an optional favorites namespace on IMastraEditor so editor implementations can expose favoriting of stored agents and skills. (#16749)

    import type {
      IMastraEditor,
      IEditorFavoritesNamespace,
      EditorFavoriteTargetInput,
      EditorFavoriteToggleResult,
    } from '@mastra/core/editor';
    
    interface IMastraEditor {
      // ...existing members...
      favorites?: IEditorFavoritesNamespace;
    }

    The favorites field is optional — existing implementations of IMastraEditor continue to work unchanged. @mastra/editor ships a default EditorFavoritesNamespace that wires this up against the storage favorites domain.

    Also renamed AgentFeatures.stars to AgentFeatures.favorites in @mastra/core/agent-builder/ee so the feature flag aligns with the storage column (favoriteCount), HTTP routes (/favorite), and the editor favorites namespace. The field had no functional consumers, so this is a name-only change.

  • Enterprise edition now automatically captures PostHog telemetry for EE license checks and feature usage, including license validation status, RBAC access resolution, FGA authorization calls, and EE feature invocation metadata. Telemetry is enabled by default for EE customers and can be disabled with MASTRA_TELEMETRY_DISABLED=1; community users are unaffected. (#16660)

  • Added new editor configuration primitives for browser providers, agent builder integration, and stored-agent visibility. (#16778)

    New: BrowserProvider interface

    Implement a browser provider to expose browser automation tools to agents via the editor. Each provider declares an id, name, and config schema, then returns a MastraBrowser instance from createBrowser.

    import type { BrowserProvider } from '@mastra/core/editor';
    
    const myProvider: BrowserProvider = {
      id: 'my-browser',
      name: 'My Browser',
      description: 'Custom browser automation',
      configSchema: z.object({ apiKey: z.string() }),
      createBrowser: async config => {
        return createMyBrowser(config.apiKey);
      },
    };

    New: MastraEditorConfig.browsers and .builder

    Wire browser providers and agent-builder options into the editor:

    new MastraEditor({
      browsers: { 'my-browser': myProvider },
      builder: { features: { agent: { favorites: true } } },
    });

    New: visibility on updateAgentMeta

    Set an agent's visibility (private or public) through the editor namespace:

    await editor.agent.updateAgentMeta('agent-id', { visibility: 'public' });
  • publishSkill now returns the full skill file tree so consumers can persist the UI-facing tree alongside storage blobs without re-walking the source. (#16666)

    import { publishSkill } from '@mastra/core/workspace';
    
    const result = await publishSkill({ workspace, skillId, source });
    
    // New: nested tree of folders + files; binary content base64-encoded.
    for (const node of result.files) {
      console.log(node.type, node.path);
    }

    Also added two optional capability methods to IMastraEditor for server-side gating of builder-aware behavior:

    interface IMastraEditor {
      // ...existing members...
      hasEnabledBuilderConfig?(): boolean;
      resolveBuilder?(): Promise<IAgentBuilder | undefined>;
    }

    Both methods are optional — existing implementations of IMastraEditor continue to work unchanged. Servers that consume them treat undefined / missing implementation as "no builder configured."

  • Added route-specific CORS configuration so credentialed cross-origin access can be limited to selected custom routes and channel webhooks. (#16689)

    registerApiRoute('/customer-webhook', {
      method: 'POST',
      cors: {
        origin: ['https://customer-saas.example'],
        credentials: true,
      },
      handler: async c => c.json({ ok: true }),
    });
    new Agent({
      id: 'support-agent',
      name: 'Support Agent',
      instructions: '...',
      model,
      channels: {
        adapters: {
          web: {
            adapter: createWebAdapter(),
            cors: {
              origin: ['https://customer-saas.example'],
              credentials: true,
            },
          },
        },
      },
    });

    Use server.cors for one global CORS policy across the server:

    new Mastra({
      server: {
        cors: {
          origin: '*',
        },
      },
    });
  • Narrowed AgentSignalContents from BaseMessageListInput to string | (TextPart | FilePart)[]. (#16622)

    Fixed two signal-content bugs:

    • user-message signal attributes now reach the LLM
    • multimodal non-user-message signals no longer lose file parts

    Callers that previously passed wrapped message shapes to agent.sendSignal should now pass a bare string or a bare parts array.

    Before:
    { type: 'user-message', contents: [{ role: 'user', content: [{ type: 'text', text: 'hi' }] }] }

    After:
    { type: 'user-message', contents: [{ type: 'text', text: 'hi' }] }

    Added an optional providerOptions field to agent.sendSignal that flows through to the resulting prompt turn (as providerOptions on the LLM message) and is persisted on the stored signal message (as content.providerMetadata).

  • publishSkillFromSource() (and collectSkillForPublish()) now return a files field containing the full skill source as a tree of StorageSkillFileNode entries with base64-encoded blob content — handy for storing a UI-facing copy of a skill alongside its content-addressable tree: (#16673)

    const { snapshot, tree, files } = await publishSkillFromSource({ source });
    // files: StorageSkillFileNode[] — name, mimeType, base64 content per node

    Existing callers that only destructure { snapshot, tree } are unaffected; the field is additive.

    Also adds parseSkillSnapshotFromFiles() for parsing skill snapshot frontmatter from a flat file list (used by the registry install flow):

    import { parseSkillSnapshotFromFiles, type SkillSnapshotFile } from '@mastra/core/workspace';
    
    const files: SkillSnapshotFile[] = [{ path: 'SKILL.md', content: '...' }, ...];
    const snapshot = parseSkillSnapshotFromFiles(files);
  • Added delta polling support for observability list APIs in core, DuckDB, and ClickHouse. (#16632)

Patch Changes

  • Update provider registry and model documentation with latest models and providers (452036a)

  • Fixed task_update to auto-demote previously in_progress tasks instead of returning an error when moving another task to in_progress. (#16843)

  • Fixed durable agents to honor activeTools when streaming. (#16646)

  • Fixed type error when a tool calls suspend(...) inside execute while also declaring an outputSchema. The execute return type now allows void in addition to the declared output shape, so the idiomatic return await suspend(...) pattern type-checks correctly. (#16799)

  • Fixed a startup bug in MastraCompositeStore.init() when using default or editor. (#16786)

    Before this fix, the composite initialized inner domains directly and could skip parent store initialization. That could skip adapter setup steps and cause missing-table errors during startup (most visibly with LibSQLStore on a local file).

    Now, MastraCompositeStore.init() runs parent default and editor initialization first, then initializes only domains not already covered by those parents. This preserves adapter-specific initialization behavior and prevents startup races.

    Fixes #16782.

  • Fixed CompositeAuth incorrectly advertising SSO, session, and user provider capabilities when no inner provider supports them. Studio would show an SSO login button even when no provider had SSO configured, leading to 401 errors on login attempts. The duck-typing check now verifies that interface methods are actual functions rather than just present on the prototype chain. (#16664)

  • Hide internal workflow spans from Mastra-owned plumbing in exported traces. (#16631)

  • Channels now serialize messages per thread to keep conversations in order, and the tool approval flow is fixed end-to-end: (#16517)

    • Messages arriving while the agent is busy are delivered into the running agent loop instead of starting a new, conflicting stream on the same thread. Each Mastra thread shares one subscription, and channel/author facts (platform, message id, author name) are surfaced on the stored message under providerMetadata.mastra.channels.<platform>.
    • Tool approval flow: approving now drains the resumed run so the card is updated with the tool result and any follow-up assistant text is posted. Denying now resumes the run via declineToolCall instead of leaving it suspended. agent.subscribeToThread() consumers also receive chunks from resumed runs (the subscription used to drop the second registration for a resumed run that kept its original runId).
    • The original ChannelConfig is now exposed via the new AgentChannels.channelConfig field so channel providers can merge with existing adapters instead of replacing them.
    • Bumped chat to ^4.29.0.
  • Added the internalUsage?: UsageStats field to AIBaseAttributes, so any span type can carry token usage rolled up from internal descendant spans. Populated automatically by @mastra/observability when an internal MODEL_GENERATION ends inside a non-internal ancestor. (#16434)

  • Fixed trajectory scorers so tool calls stored only in V2 content.parts are included in extracted eval steps. (#15439)

  • Fixed thread metadata updates to merge with existing fields instead of replacing them. Previously, updating a thread's metadata would silently drop any fields not included in the update. Now existing metadata fields are preserved when updating. (#16846)

    Fixed MockMemory working memory tool to support partial JSON updates when using schema-based working memory. Previously, sending a partial update would overwrite all existing data. Now for schema-based configs, unchanged fields are preserved automatically (matching @mastra/memory behavior).

    Fixed MockMemory constructor to preserve workingMemory config options (like schema) when enableWorkingMemory is true.

  • Improved MastraCode quiet mode so terminal sessions are easier to scan. (#16771)

    • Quiet mode is now the default for new installs, and existing classic users get a one-time prompt to choose whether to enable it.
    • Added compact tool previews with a configurable preview-line limit, including an option to hide previews.
    • Improved repeated tool-call rendering, path continuation handling, task wrapping, shell/error previews, and spacing between tools, messages, plans, and completed subagents.
    • Added edited line ranges to workspace edit results so tool UIs can show where replacements happened.
  • Remove hardcoded /api/ prefix check from registerApiRoute(). The check incorrectly rejected custom routes starting with /api/ even when users configured a different apiPrefix. Reserved-path validation is already handled at the server adapter level using the actual configured prefix. (#16859)

  • Fixed infinite recursion in RequestContext.toJSON() when multiple (#16686)
    RequestContext instances reference each other through stored values.
    Previously, serializing such cross-context cycles would cause a CPU hang.
    Cyclic references are now detected and omitted from the serialized output,
    consistent with how circular references within a single context are handled.

  • Fixed crash in CacheKeyGenerator.fromAIV4Part when a tool-invocation part has undefined toolInvocation. This can happen when observational memory seals a partially-streamed assistant message. Also guarded MessageMerger against the same condition. (#16773)

  • Agent signals can now coordinate active thread runs across agents that share a PubSub instance, so thread subscribers and signal senders can observe the same run instead of being limited to one runtime instance. (#16665)

    import { Agent } from '@mastra/core/agent';
    import { EventEmitterPubSub } from '@mastra/core/events';
    
    const pubsub = new EventEmitterPubSub();
    const agent = new Agent({
      id: 'agent',
      name: 'Agent',
      instructions: 'Help the user',
      model,
      pubsub,
    });
  • Exposed formatSkillActivation(skill) from @mastra/core/workspace. It returns the activation payload — instructions plus references, scripts, and assets listings — that the built-in skill tool uses, so callers (e.g. an explicit /skill/<name> slash command) can produce the same output without duplicating the formatting logic. (#16618)

    Also preserves the user-invocable skill frontmatter field in workspace skill metadata.

    import { formatSkillActivation } from '@mastra/core/workspace';
    
    const content = formatSkillActivation(skill);
  • Fixed sub-agent streams so nested tool input progress is emitted while tool arguments are still being generated. This lets UIs show delegated agents preparing tool calls before the final tool input is available. Fixes #16422. (#16553)

  • Fixed backgroundTasks: { enabled: true } silently dispatching foreground-only tools to the background. (#16792)

    Previously, enabling backgroundTasks on the Mastra instance injected a system prompt into every agent that taught the LLM to flip any tool to background by passing _background: { enabled: true } in its arguments, and the resolver honored that override unconditionally. Models would readily do this for short, deterministic tools — a plain calculator could return "Background task started…" instead of { result: 42 }, breaking agent.generate() / agent.stream() for tool-using flows.

    The LLM _background override is now treated as a modifier on tools the developer has opted in at the tool or agent layer, not a standalone opt-in. If a tool hasn't been opted in via tool-level background: { enabled: true } or agent-level backgroundTasks: { tools: { … } } (or tools: 'all'), _background.enabled: true from the model is ignored and the tool runs in the foreground. Opted-in tools continue to honor LLM overrides for enabled, timeoutMs, and maxRetries as documented.

    Fixes #16783

  • Fixed scheduler performance and correctness issues that could cause excessive Postgres CPU and noisy failed runs. (#16805)

    • Added missing indexes on the schedules tables that the tick loop polls every 10 seconds: (status, next_fire_at) on mastra_schedules and (schedule_id, actual_fire_at) on mastra_schedule_triggers. Without these the scheduler performed a full sequential scan on every tick.
    • The scheduler tick loop is now only started when at least one workflow declares a schedule (or scheduler.enabled is set explicitly), so deployments without scheduled workflows no longer poll the database at all.
    • The scheduler now validates that a schedule's target workflow is still registered before firing it. Schedules whose target workflow has been removed from the Mastra config are skipped for a short grace window and then deleted, so stale rows stop producing failed workflow runs forever.
    • The scheduler is now lazily initialized inside startWorkers(). Accessing mastra.scheduler before startWorkers() runs throws a descriptive error instead of returning a half-initialized instance.
  • Fixed Mastra getting stuck after a storage startup failure. Previously, if storage couldn't start up (for example, because the database was briefly unreachable), Mastra would keep returning the same error for every operation until the process was restarted. Now the next storage operation tries to start storage again, so brief outages recover on their own. Storage startup failures are also logged, so the problem is visible even when a later retry succeeds. (#16427)

  • Restore MastraCode local command execution to inherit parent environment variables while redacting env-shaped and secret-looking workspace trace data. (#16691)

  • Added a Unix socket PubSub transport and wired the Mastra Code TUI through a per-resource socket so local sessions can coordinate thread streams across processes. Programmatic createMastraCode usage remains opt-in: (#16669)

    await createMastraCode({ unixSocketPubSub: true });
  • Fix in-memory observability storage to match the contract validated against DuckDB/ClickHouse vNext adapters. (#16808)

    Previously, when running Mastra with the default in-memory storage, several observability operations behaved differently than they would against a production database:

    • getSpans threw 'This storage provider does not support batch-fetching spans'. It now batch-fetches spans by id within a trace, enabling the optimized getBranch path on in-memory storage.
    • batchCreateLogs, batchCreateMetrics, createScore/batchCreateScores, createFeedback/batchCreateFeedback appended duplicate records on retry. They now upsert by id, preserving the cursor id so delta polling does not re-emit the record. This makes client retries safe.
    • Discovery operations (getEntityTypes, getEntityNames, getServiceNames, getEnvironments, getTags) only inspected spans. They now also scan logs and metrics, so dimensions emitted on those surfaces are surfaced in discovery results.
    • getMetricTimeSeries merged grouped series whose label values contained the | character (e.g. {segmentA: 'a', segmentB: 'b|c'} collided with {segmentA: 'a|b', segmentB: 'c'}). Series are now keyed on the original label tuple, so colliding display names remain distinct series.

@mastra/ai-sdk@1.4.3

Patch Changes

  • Fixed sub-agent streams so nested tool input progress is emitted while tool arguments are still being generated. This lets UIs show delegated agents preparing tool calls before the final tool input is available. Fixes #16422. (#16553)

@mastra/auth-okta@0.0.3

Patch Changes

  • Fix endpoint URL construction for Okta org authorization servers. (#15694)

    MastraAuthOkta concatenated /v1/authorize (and /token, /keys, /logout) directly onto OKTA_ISSUER. That yields the right endpoint for a custom authorization server (https://{domain}/oauth2/default.../oauth2/default/v1/authorize), but 404s on an Okta org authorization server (https://{domain}.../v1/authorize, whereas the real org endpoint is .../oauth2/v1/authorize).

    An internal endpointBase is now derived from the issuer — verbatim when it already contains /oauth2/, otherwise ${issuer}/oauth2 — and used for the authorize, token, keys, and logout URLs. JWT iss-claim validation still uses the raw issuer so token validation stays correct on both server types. Trailing slashes on the issuer are also normalized so OKTA_ISSUER=https://{domain}/ no longer produces .../oauth2//v1/....

@mastra/auth-workos@1.5.0

Minor Changes

  • FGA check() and require() now accept an array of permissions and short-circuit on the first one that resolves to allow (ANY-of semantics). Single-permission usage continues to work unchanged. (#16605)

    // Before — one permission per call
    await fgaProvider.check({
      user,
      resource: { type: 'agent', id: 'abc' },
      permission: 'agents:read',
    });
    
    // After — single permission or ANY-of array
    await fgaProvider.check({
      user,
      resource: { type: 'agent', id: 'abc' },
      permission: ['agents:read', 'agents:execute'],
    });

    When all permissions in the array are denied, the thrown FGADeniedError lists them as any of [a, b, c] in its message.

Patch Changes

@mastra/clickhouse@1.9.0

Minor Changes

  • Added delta polling support for observability list APIs in core, DuckDB, and ClickHouse. (#16632)

Patch Changes

  • Fixed duplicate entries in the ClickHouse v-next observability discovery endpoints — tags, services, environments, entities, metric names, and metric labels now return each value once. Existing deployments are reconciled automatically on next startup; no manual migration required. (#16798)

@mastra/client-js@1.20.0

Minor Changes

  • Added agent.browserSession(threadId?) and agent.closeBrowser(threadId?) to the Agent resource, plus a GetAgentBrowserSessionResponse type. (#16668)

    browserSession probes the server's browser session state before opening a screencast WebSocket, so the connection is only made when the server has screencast support installed and an active session exists for the thread. closeBrowser ends the agent's browser session (or a single thread's session if threadId is passed). Both methods go through the configured client baseUrl and apiPrefix, so they work with servers mounted under a non-default API prefix.

    const probe = await client.getAgent('my-agent').browserSession(threadId);
    if (probe.screencastAvailable && probe.hasSession) {
      // safe to open the screencast WebSocket
    }
    
    await client.getAgent('my-agent').closeBrowser(threadId);
  • Added typed client-side resources for the stored-entity HTTP surface so you no longer have to hand-roll fetch calls. (#16666)

    import { MastraClient } from '@mastra/client-js';
    
    const client = new MastraClient({ baseUrl: 'http://localhost:4111' });
    
    // List/get with favorite metadata
    const { items } = await client.storedAgents.list({ page: 1, perPage: 20 });
    const agent = await client.storedAgents.get(items[0].id);
    console.log(agent.favoriteCount, agent.isFavorited);
    
    // Favorite toggle
    await client.storedAgents.favorite(agent.id);
    await client.storedAgents.unfavorite(agent.id);
    
    // Versioning + publish
    const draft = await client.storedSkills.create({
      /* ... */
    });
    const published = await client.storedSkills.publish(draft.id);
    await client.storedSkills.restore(draft.id, { version: 1 });

    Also regenerated route-types.generated.ts to cover the new editor-builder introspection routes (/editor/builder/settings, /editor/builder/infrastructure) and the external skill-registry endpoints under /editor/builder/registries (list, search, popular, preview, install).

  • Added delta polling support for observability list endpoints. (#16632)

    const page = await client.observability.listTraces({
      mode: 'page',
      filters: { entityName: 'agent-1' },
    });
    
    const delta = await client.observability.listTraces({
      mode: 'delta',
      filters: { entityName: 'agent-1' },
      after: page.deltaCursor,
    });

    Use mode: 'delta' to fetch only new items after the last cursor.

    Page-mode responses include pagination and deltaCursor when delta polling is supported. Delta-mode responses include delta and do not include pagination.

    If you read these responses directly in typed code, note that pagination is only included in page mode.

  • Narrowed AgentSignalContents from BaseMessageListInput to string | (TextPart | FilePart)[]. (#16622)

    Fixed two signal-content bugs:

    • user-message signal attributes now reach the LLM
    • multimodal non-user-message signals no longer lose file parts

    Callers that previously passed wrapped message shapes to agent.sendSignal should now pass a bare string or a bare parts array.

    Before:
    { type: 'user-message', contents: [{ role: 'user', content: [{ type: 'text', text: 'hi' }] }] }

    After:
    { type: 'user-message', contents: [{ type: 'text', text: 'hi' }] }

    Added an optional providerOptions field to agent.sendSignal that flows through to the resulting prompt turn (as providerOptions on the LLM message) and is persisted on the stored signal message (as content.providerMetadata).

Patch Changes

  • Fixed provider and dataset item history response types to include fields returned by the API. (#16213)

  • Fixed an issue where recursive client-tool continuations after resumeStream (and resumeStreamUntilIdle) incorrectly re-hit the one-shot resume endpoint instead of falling back to the regular stream endpoint. The resume routes consume server-side resumeData and cannot be replayed, so client-tool continuations now route to /stream and /stream-until-idle respectively. (#16670)

  • Fixed the playground memory configuration display for agents using observationalMemory: true. (#16213)

@mastra/convex@1.1.0

Minor Changes

  • Added native Convex vector search support for production workloads. The new ConvexNativeVector adapter uses (#16729)
    Convex schema-defined vector indexes and ctx.vectorSearch instead of loading vectors through ConvexVector and
    scoring them in JavaScript.

    Define a native vector table in convex/schema.ts:

    import { defineSchema } from 'convex/server';
    import { defineMastraNativeVectorTable } from '@mastra/convex/schema';
    
    export default defineSchema({
      docs_vectors: defineMastraNativeVectorTable({
        dimensions: 1536,
      }),
    });

    Export the native vector handlers:

    import { mastraNativeVectorAction, mastraNativeVectorMutation, mastraNativeVectorQuery } from '@mastra/convex/server';
    
    export const query = mastraNativeVectorAction;
    export const read = mastraNativeVectorQuery;
    export const write = mastraNativeVectorMutation;

    Then configure ConvexNativeVector in your Mastra app:

    import { ConvexNativeVector } from '@mastra/convex';
    
    const vectorStore = new ConvexNativeVector({
      id: 'convex-native-vectors',
      deploymentUrl: process.env.CONVEX_URL!,
      adminAuthToken: process.env.CONVEX_ADMIN_KEY!,
      indexes: {
        docs: {
          tableName: 'docs_vectors',
          vectorIndexName: 'by_embedding',
          dimension: 1536,
        },
      },
    });
  • Added ConvexServerCache so Convex-backed Mastra apps can keep durable stream replay and response cache state in Convex. (#16736)

    import { ConvexServerCache } from '@mastra/convex';
    
    const cache = new ConvexServerCache({
      deploymentUrl: process.env.CONVEX_URL!,
      adminAuthToken: process.env.CONVEX_ADMIN_KEY!,
    });

    The package also exports the Convex cache schema tables and server mutation for mounting the cache handler in a Convex app.
    Existing Convex users who adopt the cache must add mastra_cache and mastra_cache_list_items to their Convex schema, mount the mastraCache handler, and deploy the schema update.

Patch Changes

@mastra/datadog@1.2.1

Patch Changes

  • Fixed Datadog LLM output tool calls so they render as structured tool call blocks. (#16789)

  • Fixed Datadog LLM span input formatting to remove empty user messages. (#16785)

@mastra/deployer@1.36.0

Minor Changes

  • Added route-specific CORS configuration so credentialed cross-origin access can be limited to selected custom routes and channel webhooks. (#16689)

    registerApiRoute('/customer-webhook', {
      method: 'POST',
      cors: {
        origin: ['https://customer-saas.example'],
        credentials: true,
      },
      handler: async c => c.json({ ok: true }),
    });
    new Agent({
      id: 'support-agent',
      name: 'Support Agent',
      instructions: '...',
      model,
      channels: {
        adapters: {
          web: {
            adapter: createWebAdapter(),
            cors: {
              origin: ['https://customer-saas.example'],
              credentials: true,
            },
          },
        },
      },
    });

    Use server.cors for one global CORS policy across the server:

    new Mastra({
      server: {
        cors: {
          origin: '*',
        },
      },
    });

Patch Changes

  • Browser streaming now works for stored agents. The deployer's getToolset first checks the runtime agent registry, then falls back to the editor's stored-agent lookup, so agents created at runtime through the editor can stream browser sessions without being pre-registered in code. (#16778)

  • When browser streaming is unavailable (the ws and @hono/node-ws packages aren't installed, or the deployer is running in a serverless environment), the deployer now registers a fallback GET /api/agents/:agentId/browser/session route that returns { hasSession: false, screencastAvailable: false }. This lets clients detect that screencast won't work and skip the WebSocket upgrade instead of triggering a noisy reconnect loop. (#16668)

  • Bumped the @mastra/core peer dependency floor from >=1.32.0-0 to >=1.34.0-0. (#16666)

@mastra/deployer-cloud@1.36.0

Patch Changes

  • Bumped the @mastra/core peer dependency floor from >=1.32.0-0 to >=1.34.0-0. (#16666)

@mastra/deployer-cloudflare@1.1.37

Patch Changes

  • Bumped the @mastra/core peer dependency floor from >=1.32.0-0 to >=1.34.0-0. (#16666)

@mastra/deployer-netlify@1.1.13

Patch Changes

  • Bumped the @mastra/core peer dependency floor from >=1.32.0-0 to >=1.34.0-0. (#16666)

@mastra/deployer-vercel@1.1.31

Patch Changes

  • Bumped the @mastra/core peer dependency floor from >=1.32.0-0 to >=1.34.0-0. (#16666)

@mastra/duckdb@1.4.0

Minor Changes

  • Added delta polling support for observability list APIs in core, DuckDB, and ClickHouse. (#16632)

Patch Changes

  • Fixed DuckDB observability storage so dev server restarts no longer fail when replaying cursor migrations. (#16803)

@mastra/editor@0.9.0

Minor Changes

  • EditorWorkspaceNamespace can now snapshot a live Workspace for persistence — the reverse of hydrateSnapshotToWorkspace(): (#16673)

    const snapshot = await editor.workspace.snapshotFromWorkspace(runtimeWorkspace);
    await editor.workspace.create({ id: 'my-workspace', ...snapshot });

    snapshotFromWorkspace() is async and awaits sandbox.getInfo() and filesystem.getInfo() so async providers like CompositeFilesystem keep their mount metadata in the stored config.

    Also includes two smaller behavioral fixes:

    • EditorSkillNamespace.publishSkillFromSource() stores the new files field on the published skill version and strips undefined keys before calling update() (libsql/pg adapters reject undefined bind arguments).
    • CrudEditorNamespace.clearCache(id) always calls onCacheEvict(id), even when the entity wasn't cached, so subclasses can clean up runtime registries for version-specific lookups that bypass the editor cache.
  • Added an editor.favorites namespace so direct (non-HTTP) callers can favorite, unfavorite, and query favorited stored agents/skills through the editor instance. (#16749)

    import { MastraEditor } from '@mastra/editor';
    
    const editor = new MastraEditor({ mastra });
    
    // Toggle
    await editor.favorites.favorite({ userId, entityType: 'agent', entityId });
    await editor.favorites.unfavorite({ userId, entityType: 'agent', entityId });
    
    // Lookups
    const isFav = await editor.favorites.isFavorited({ userId, entityType: 'agent', entityId });
    const favSet = await editor.favorites.isFavoritedBatch({ userId, entityType: 'agent', entityIds });
    const ids = await editor.favorites.listFavoritedIds({ userId, entityType: 'agent' });

    The namespace performs the storage mutation only — visibility and ownership enforcement still belong to the caller (the HTTP route handlers in @mastra/server already do this).

Patch Changes

@mastra/express@1.3.23

Patch Changes

  • Updated the adapter permission check to read user permissions from the new namespaced request-context key mastra__userPermissions (was userPermissions). This matches the namespaced keys that @mastra/server's core auth middleware now writes and avoids collisions with caller-supplied context entries. (#16605)

    No action needed for typical users — install the matching @mastra/server release and the adapter will continue to enforce route permissions exactly as before.

  • Bumped the @mastra/core peer dependency floor from >=1.32.0-0 to >=1.34.0-0. (#16666)

@mastra/fastembed@1.1.0

Minor Changes

  • Replace the abandoned fastembed npm dependency with a maintained, vendored implementation. The public API and all embedding models remain unchanged — no migration needed. (#16772)

@mastra/fastify@1.3.23

Patch Changes

  • Updated the adapter permission check to read user permissions from the new namespaced request-context key mastra__userPermissions (was userPermissions). This matches the namespaced keys that @mastra/server's core auth middleware now writes and avoids collisions with caller-supplied context entries. (#16605)

    No action needed for typical users — install the matching @mastra/server release and the adapter will continue to enforce route permissions exactly as before.

  • Bumped the @mastra/core peer dependency floor from >=1.32.0-0 to >=1.34.0-0. (#16666)

@mastra/hono@1.4.18

Patch Changes

  • Updated the adapter permission check to read user permissions from the new namespaced request-context key mastra__userPermissions (was userPermissions). This matches the namespaced keys that @mastra/server's core auth middleware now writes and avoids collisions with caller-supplied context entries. (#16605)

    No action needed for typical users — install the matching @mastra/server release and the adapter will continue to enforce route permissions exactly as before.

  • Bumped the @mastra/core peer dependency floor from >=1.32.0-0 to >=1.34.0-0. (#16666)

  • Added GET /agents/:agentId/browser/session endpoint (under the configured apiPrefix, default /api) that reports whether a screencast WebSocket should be opened for an agent and thread. Clients can probe this before upgrading to a WebSocket to avoid idle connections and reconnect storms. (#16668)

    curl "http://localhost:4111/api/agents/my-agent/browser/session?threadId=thread-1"
    # {"hasSession":true,"screencastAvailable":true}

    The response shape is { hasSession: boolean, screencastAvailable: true }. screencastAvailable is always true when this route is registered; the deployer registers a fallback that returns { hasSession: false, screencastAvailable: false } when browser streaming packages aren't installed, so clients can use the same probe in both cases.

    setupBrowserStream now accepts an optional apiPrefix so the probe and existing POST /agents/:agentId/browser/close routes are mounted under the same prefix as the rest of the server. The deployer wires this from mastra.getServer().apiPrefix automatically.

  • The Hono adapter now awaits getToolset calls in browser-stream routes, supporting deployers that resolve agents asynchronously (such as stored agents looked up via the editor). (#16778)

@mastra/koa@1.5.6

Patch Changes

  • Updated the adapter permission check to read user permissions from the new namespaced request-context key mastra__userPermissions (was userPermissions). This matches the namespaced keys that @mastra/server's core auth middleware now writes and avoids collisions with caller-supplied context entries. (#16605)

    No action needed for typical users — install the matching @mastra/server release and the adapter will continue to enforce route permissions exactly as before.

  • Bumped the @mastra/core peer dependency floor from >=1.32.0-0 to >=1.34.0-0. (#16666)

@mastra/libsql@1.11.1

Patch Changes

  • Fixed scheduler performance and correctness issues that could cause excessive Postgres CPU and noisy failed runs. (#16805)
    • Added missing indexes on the schedules tables that the tick loop polls every 10 seconds: (status, next_fire_at) on mastra_schedules and (schedule_id, actual_fire_at) on mastra_schedule_triggers. Without these the scheduler performed a full sequential scan on every tick.
    • The scheduler tick loop is now only started when at least one workflow declares a schedule (or scheduler.enabled is set explicitly), so deployments without scheduled workflows no longer poll the database at all.
    • The scheduler now validates that a schedule's target workflow is still registered before firing it. Schedules whose target workflow has been removed from the Mastra config are skipped for a short grace window and then deleted, so stale rows stop producing failed workflow runs forever.
    • The scheduler is now lazily initialized inside startWorkers(). Accessing mastra.scheduler before startWorkers() runs throws a descriptive error instead of returning a half-initialized instance.

@mastra/mcp@1.8.0

Minor Changes

  • Added MCP tool annotations to the requireToolApproval context and exposed them on tools returned from listTools() / listToolsets(). (#16784)

    The requireToolApproval callback now receives the server-advertised annotations (title, readOnlyHint, destructiveHint, idempotentHint, openWorldHint) alongside toolName and args. This lets you write declarative approval policies instead of hardcoding tool name lists. Annotations are also propagated onto Mastra tools as tool.mcp.annotations so apps can render them in UI.

    Security caveat (per the MCP spec): annotations are hints, not guarantees. Clients MUST treat them as untrusted unless they come from a trusted server. Do not use annotations alone as a security boundary for servers you do not control — set requireToolApproval: true for those. When the server omits annotations entirely, this field is undefined, so policies can distinguish "no annotations" from "annotated as safe".

    import { MCPClient } from '@mastra/mcp';
    
    // Before — hardcoded tool name lists, server-specific
    const mcp = new MCPClient({
      servers: {
        github: {
          url: new URL('https://example.com/mcp'),
          requireToolApproval: ({ toolName }) => toolName === 'delete_repo',
        },
      },
    });
    
    // After — annotation-driven, works across any trusted MCP server
    const mcp = new MCPClient({
      servers: {
        github: {
          url: new URL('https://example.com/mcp'),
          requireToolApproval: ({ annotations }) => {
            if (!annotations) return true;
            if (annotations.readOnlyHint) return false;
            if (annotations.destructiveHint) return true;
            return false;
          },
        },
      },
    });
    
    // Annotations are also visible on tools returned by listTools()
    const tools = await mcp.listTools();
    for (const tool of Object.values(tools)) {
      console.log(tool.mcp?.annotations);
    }

    Closes #16766.

Patch Changes

  • Fixed an issue where OAuth token requests dropped client_id and client_secret for confidential clients. The provider previously shipped an empty addClientAuthentication method that satisfied the MCP SDK's existence check and short-circuited its default credential attachment, causing invalid_request errors on token exchange and refresh against confidential-client OAuth servers. The empty stub has been removed so the SDK's built-in client authentication runs again. See #16854. (#16862)

  • Close previous SSE transport before accepting a new connection in MCPServer.connectSSE(). Previously, sequential SSE connections to the same server would fail with "Already connected to a transport" because the underlying protocol was never closed when the previous client disconnected. (#16695)

@mastra/memory@1.19.0

Minor Changes

  • Added activateAfterIdle: "auto" for Observational Memory early activation. (#16663)

    Mastra can now choose an idle activation timeout from the active model provider's prompt cache behavior. OpenAI also respects providerOptions.openai.promptCacheRetention when available.

    const memory = new Memory({
      options: {
        observationalMemory: {
          model: 'google/gemini-2.5-flash',
          activateAfterIdle: 'auto',
          activateOnProviderChange: true,
        },
      },
    });
  • Add observeAttachments to ObservationConfig for Observational Memory. Use it to control whether image/file parts on observed messages are forwarded to the Observer model alongside their placeholder text lines. (#16671)

    • true (default) — forward all attachments (existing behavior).
    • false — drop all attachments; placeholders still appear in the observer transcript.
    • string[] — allowlist of mimeType patterns, e.g. ['image/*'] or ['application/pdf']. Matching is case-insensitive and supports exact, type/*, and * patterns.

    Useful when the Observer model is text-only (some DeepSeek endpoints, etc.) while the main agent uses a multimodal model. Tool-result attachments are filtered with the same rule.

    new Memory({
      options: {
        observationalMemory: {
          observation: {
            model: 'deepseek/deepseek-chat', // text-only observer
            observeAttachments: false, // or e.g. ['image/*', 'application/pdf']
          },
        },
      },
    });

Patch Changes

  • feat(memory): start background buffering of unobserved messages when agent goes idle (#16694)

    In OM buffering mode, when the agent goes idle (turn.end()), any unobserved messages are now buffered in the background via a fire-and-forget buffer() call. This ensures observations are computed proactively rather than waiting for the next turn's step.prepare().

@mastra/nestjs@0.1.7

Patch Changes

  • Bumped the @mastra/core peer dependency floor from >=1.32.0-0 to >=1.34.0-0. (#16666)

@mastra/observability@1.13.0

Minor Changes

  • Roll up token usage from internal MODEL_GENERATION spans onto the closest exported ancestor span. When tracingPolicy.internal filters a model call out of exported traces, its tokens used to disappear from both the trace UI and metrics. Now: (#16434)

    • The visible ancestor (e.g. PROCESSOR_RUN, AGENT_RUN) gets an internalUsage attribute summing the tokens consumed by its hidden descendants — so a Mastra-owned processor that runs an internal agent (moderation, PII detector, structured output, etc.) shows its aggregate cost on the visible PROCESSOR_RUN span.
    • Token / cost metrics still emit, but are attributed via labels to the visible ancestor instead of the hidden agent.

    No action required — the rollup applies automatically whenever an internal MODEL_GENERATION ends inside a non-internal ancestor.

  • MastraStorageExporter now notifies custom exporters and connected integrations when it cannot persist observability events, such as unsupported storage or retries being exceeded. This matches the behavior already available on DefaultExporter. (#16755)

    Also fixed an issue in both exporters where span updates waiting on their parent span could be silently lost if a later flush in the same cycle failed.

Patch Changes

@mastra/pg@1.11.1

Patch Changes

  • Fixed scheduler performance and correctness issues that could cause excessive Postgres CPU and noisy failed runs. (#16805)
    • Added missing indexes on the schedules tables that the tick loop polls every 10 seconds: (status, next_fire_at) on mastra_schedules and (schedule_id, actual_fire_at) on mastra_schedule_triggers. Without these the scheduler performed a full sequential scan on every tick.
    • The scheduler tick loop is now only started when at least one workflow declares a schedule (or scheduler.enabled is set explicitly), so deployments without scheduled workflows no longer poll the database at all.
    • The scheduler now validates that a schedule's target workflow is still registered before firing it. Schedules whose target workflow has been removed from the Mastra config are skipped for a short grace window and then deleted, so stale rows stop producing failed workflow runs forever.
    • The scheduler is now lazily initialized inside startWorkers(). Accessing mastra.scheduler before startWorkers() runs throws a descriptive error instead of returning a half-initialized instance.

@mastra/playground-ui@29.0.0

Minor Changes

  • Added ContextMenu for right-click interactions. Supports submenus, checkbox and radio items, keyboard shortcuts, and a destructive variant for dangerous actions like delete. (#16791)

    import { ContextMenu } from '@mastra/playground-ui';
    
    <ContextMenu>
      <ContextMenu.Trigger className="…">Right click here</ContextMenu.Trigger>
      <ContextMenu.Content>
        <ContextMenu.Item>Rename</ContextMenu.Item>
        <ContextMenu.Item variant="destructive">Delete</ContextMenu.Item>
      </ContextMenu.Content>
    </ContextMenu>;
  • Removed ButtonWithTooltip from @mastra/playground-ui. Use Button with the tooltip prop instead. (#16719)

    Migration

    // before
    import { ButtonWithTooltip } from '@mastra/playground-ui';
    
    <ButtonWithTooltip tooltipContent="Search">
      <Search />
    </ButtonWithTooltip>;
    
    // after
    import { Button } from '@mastra/playground-ui';
    
    <Button tooltip="Search">
      <Search />
    </Button>;

    tooltip supports the same values as tooltipContent. Icon-only buttons that pass a string tooltip now also get it as their aria-label automatically, matching how labelled controls have always behaved. Pass an explicit aria-label to override.

  • Added a custom date range option to the Metrics page date picker. You can now filter metrics by an arbitrary start and end date and time, matching the Traces page, alongside the existing relative presets (last 24 hours, 3, 7, 14, and 30 days). (#16832)

    The selected range is reflected in the URL so it can be bookmarked or shared:

    /metrics?period=custom&dateFrom=2026-05-01T00:00:00.000Z&dateTo=2026-05-07T23:59:59.999Z
  • Refreshed Button + Card design system tokens. (#16769)

    Button variants (breaking): consolidated to default, primary, outline, ghost. The cta, contrast, and unused link variants have been removed. primary now uses a high-contrast neutral6 fill instead of surface4, so it reads clearly as the form submit action in both themes.

    // Before
    <Button variant="cta">Save</Button>
    <Button variant="contrast">Done</Button>
    <Button variant="link">Open</Button>
    
    // After
    <Button variant="primary">Save</Button>     // cta  primary (no brand green; theme-aware high contrast)
    <Button variant="primary">Done</Button>     // contrast  primary (same recipe, renamed)
    <Button as="a" href="…" variant="ghost">Open</Button>  // link  ghost (or plain <a> for inline text links)

    New tokens: --surface-overlay-soft and --surface-overlay-strong — alpha overlays of the opposite-theme color, used by SectionCard header strip and DashboardCard fill so cards read consistently on any surface.

    Other:

    • DashboardCard radius reduced to rounded-xl and padding tightened to px-4 py-3 for better grid density.
    • SectionCard wrapper no longer fills its background — header strip + border carry definition.
    • Dark surface2 / surface3 darkened slightly (16.84% → 16%, 19.13% → 18%) so the main frame reads as a distinct surface.
    • Dark border1 / border2 alphas bumped (6% → 7%, 10% → 11%) for closer dark/light parity.
    • Removed deprecated --section-card-* tokens and their @utility blocks.

Patch Changes

  • Added a destructive variant on DropdownMenu.Item to highlight dangerous actions like delete. (#16791)

    <DropdownMenu.Item variant="destructive">Delete project</DropdownMenu.Item>
  • PopoverContent no longer forwards the underlying library's auto-focus event handlers (onOpenAutoFocus, onCloseAutoFocus). To control focus when the popover opens or closes, use initialFocus and finalFocus. (#16791)

    // Before
    <PopoverContent onOpenAutoFocus={(e) => e.preventDefault()} />
    
    // After
    <PopoverContent initialFocus={false} />
  • Fixed a crash in filter menus with nested submenus (such as the Filter on the Agent review page) that showed "MenuPortal must be used within Menu". The submenu content now uses the design system's DropdownMenu.SubContent instead of the underlying library's portal directly. (#16829)

  • Fixed type definitions and shared UI component types for @mastra/playground-ui. (#16213)

  • AlertDialog's API and behavior are unchanged — asChild on AlertDialog.Trigger, and AlertDialog.Action and AlertDialog.Cancel, all work exactly as before. (Internally it now builds on Base UI primitives.) (#16824)

  • Moved the Collapsible component to Base UI, with a smoother height-based expand and collapse animation. The public API is unchanged — asChild on CollapsibleTrigger still works. (#16825)

  • Upgraded @base-ui/react to 1.5, making popups noticeably faster — components built on Base UI such as Tooltip, Popover, DropdownMenu and ContextMenu now open and close more quickly. (#16819)

  • Moved the Level icon from its own column into the Name column, next to the trace name, on the Observability traces list. (#16712)

  • The Traces list now updates live via delta polling. Previously the list was refetched every 10 seconds, replacing the whole page with no signal about what changed; now new traces appear within a few seconds of being created, with a brief highlight to draw attention. Status changes on already-visible rows (running → success / error) also propagate without intervention, and returning to the tab after being idle re-syncs from a fresh cursor. (#16727)

    New useTraces return fields

    • isRefetching — true while any meaningful refetch is in flight. Use it to drive a heartbeat indicator.
    • autoRefetch / setAutoRefetch — pause / resume all automatic polling so the consumer can render an opt-out toggle.
    • recentlyAddedKeysSet<string> of traceId:spanId for rows that just arrived via delta polling. Drives the temporary highlight in TracesListView.

    New polling config

    Every timing in the hook is tunable per-instance via a new polling option:

    import { useTraces, type TracesPollingConfig } from '@mastra/playground-ui';
    
    useTraces({
      filters,
      listMode,
      polling: {
        deltaPollIntervalMs: 10_000,
        idleGuardThresholdMs: 5 * 60_000,
      },
    });

    Omitted fields fall through to the defaults (delta poll every 5s, idle reset after 15 min, status refresh every 60s, etc).

    TracesListView

    New optional recentlyAddedKeys?: Set<string> prop. Rows whose traceId:spanId is in the set get the animate-row-highlight class — a brief fade-out to transparent, added to index.css.

    Compatibility

    Requires @mastra/server and @mastra/client-js at the versions that ship the observability delta-polling endpoints, and a store that opts into delta polling (@mastra/clickhouse, @mastra/duckdb, and the in-memory store today). When unavailable — older server or a store without delta capability — the hook silently falls back to page-mode interval refetching. No consumer changes required.

  • Added align and stack variants to PageLayout.Row. Use stack="responsive" for top bars that should collapse to a vertical stack on narrow viewports, and align="center" to vertically center children. Applied the new variants to the Prompts and Workflows top bars so the search field and primary action share a single row on desktop and stack on mobile. (#16714)

    <PageLayout.Row align="center" stack="responsive">
      <ListSearch ... />
      <Button ...>Create</Button>
    </PageLayout.Row>
  • Fixed the playground memory configuration display for agents using observationalMemory: true. (#16213)

  • Fixed double-counted cache token costs in the Metrics dashboard. The Model Usage & Cost table and the Token Usage by Agent table were summing cache read/write costs on top of the total input cost, which already includes them. (#16737)

  • Migrated the Tooltip primitive to Base UI while preserving the existing API. The popup explicitly sets role="tooltip" so consumers can keep querying it via getByRole('tooltip') (Base UI does not add this role automatically). Existing <TooltipTrigger asChild> usage continues to work unchanged, and Base UI's native render prop is now also supported on TooltipTrigger so consumers wrapping anchors, custom router links, or icons can pass the element directly without an asChild adapter: (#16713)

    // Still supported
    <TooltipTrigger asChild>
      <Button>Save</Button>
    </TooltipTrigger>
    
    // New: pass the element via Base UI's native API
    <TooltipTrigger render={<Button>Save</Button>} />

    Also fixed the arrow rendering so the diagonal stroke meets the popup outline at the exact same pixel center on every side, removing the ~1px seam previously visible where the arrow joined the popup edge.

  • Migrated the Slider component to base-ui with a refined neutral visual design. (#16788)

    What changed

    • Replaced @radix-ui/react-slider with @base-ui/react/slider as the underlying primitive
    • Refreshed visuals: thin rounded thumb with white border and neutral inside, opacity-based track that adapts to any surface, neutral filled indicator (no green/accent color)
    • Larger click target via padded Slider.Control and an invisible hit area on the thumb so it is easier to grab
    • Added cursor-pointer on the control and cursor-not-allowed when disabled
    • Removed the now unused @radix-ui/react-slider and @radix-ui/react-tabs dependencies

    API compatibility

    The public API is preserved. onValueChange and onValueCommitted are wrapped so consumers always receive number[], even though base-ui returns number | number[] internally. Existing call sites like <Slider value={[temperature]} onValueChange={value => setTemperature(value[0])} /> continue to work without changes.

  • Moved the Dialog component to Base UI. The public API is unchanged — asChild on DialogTrigger and DialogClose still works the same way, and open/close animations behave as before. (#16821)

@mastra/react@0.4.0

Minor Changes

  • Added clientTools option to useChat's generate/stream calls for forwarding browser-side tools to the agent on each invocation. (#16778)

    import { useChat } from '@mastra/react';
    
    const { generate } = useChat({ agentId: 'my-agent' });
    
    await generate({
      messages: [{ role: 'user', content: 'Show a toast that says hi' }],
      clientTools: {
        showToast: {
          description: 'Show a toast to the user',
          inputSchema: z.object({ message: z.string() }),
          execute: ({ message }) => toast(message),
        },
      },
    });

    Client tools are forwarded as-is to the underlying agent.generate() and agent.stream() calls.

  • Narrowed AgentSignalContents from BaseMessageListInput to string | (TextPart | FilePart)[]. (#16622)

    Fixed two signal-content bugs:

    • user-message signal attributes now reach the LLM
    • multimodal non-user-message signals no longer lose file parts

    Callers that previously passed wrapped message shapes to agent.sendSignal should now pass a bare string or a bare parts array.

    Before:
    { type: 'user-message', contents: [{ role: 'user', content: [{ type: 'text', text: 'hi' }] }] }

    After:
    { type: 'user-message', contents: [{ type: 'text', text: 'hi' }] }

    Added an optional providerOptions field to agent.sendSignal that flows through to the resulting prompt turn (as providerOptions on the LLM message) and is persisted on the stored signal message (as content.providerMetadata).

Patch Changes

@mastra/server@1.36.0

Minor Changes

  • Added delta polling support for observability list endpoints. (#16632)

    const page = await client.observability.listTraces({
      mode: 'page',
      filters: { entityName: 'agent-1' },
    });
    
    const delta = await client.observability.listTraces({
      mode: 'delta',
      filters: { entityName: 'agent-1' },
      after: page.deltaCursor,
    });

    Use mode: 'delta' to fetch only new items after the last cursor.

    Page-mode responses include pagination and deltaCursor when delta polling is supported. Delta-mode responses include delta and do not include pagination.

    If you read these responses directly in typed code, note that pagination is only included in page mode.

  • Added automatic FGA metadata for stored resource routes plus optional request scope isolation for stored resource APIs. Enable protected-route coverage with provider options: (#16651)

    const fga = new MastraFGAWorkos({
      resourceMapping,
      permissionMapping,
      requireForProtectedRoutes: true,
      auditProtectedRoutes: 'warn',
    });
  • Added an HTTP surface for stored agents/skills/workspaces, plus introspection endpoints for the agent-builder and an external skill-registry proxy. Studio and the client SDK use these endpoints to back the new "stored entity" management UI. (#16666)

    # Browse + manage stored entities (responses include favoriteCount + isFavorited)
    GET    /stored/agents?visibility=public&page=1&perPage=20
    GET    /stored/agents/:id
    POST   /stored/agents
    PATCH  /stored/agents/:id
    DELETE /stored/agents/:id
    
    # Versioning
    POST   /stored/skills/:id/publish
    POST   /stored/skills/:id/activate
    POST   /stored/skills/:id/restore
    
    # Favorites
    PUT    /stored/agents/:id/favorite
    DELETE /stored/agents/:id/favorite
    
    # Builder introspection
    GET    /editor/builder/settings
    GET    /editor/builder/infrastructure
    
    # External skill registry proxy (skills.sh)
    GET    /editor/builder/registries
    GET    /editor/builder/registries/:registryId/search
    GET    /editor/builder/registries/:registryId/popular
    GET    /editor/builder/registries/:registryId/skills/:owner/:repo/preview
    POST   /editor/builder/registries/:registryId/skills/:owner/:repo/install

    Highlights:

    • Visibility + authorship gating. Stored agents/skills now resolve a caller's author identity from the request context. Non-admin users only see their own + public entities. Admins see everything.
    • Favorites. List/get responses include favoriteCount and the caller's isFavorited flag. PUT/DELETE /stored/{agents|skills}/:id/favorite toggle the favorite for the caller.
    • Avatar validation. Stored-agent/skill metadata avatars are validated through a new validateMetadataAvatarUrl helper (rejects payloads over the size limit or with malformed base64).
    • Model-policy enforcement. Stored-agent create/update routes invoke assertModelAllowed via the new resolveBuilderModelPolicy helper. Disallowed models map to HTTP 422 with a structured body — { code, attempted, offendingLabel, allowed } — via handleError's new ModelNotAllowedError mapping.
    • Builder introspection. GET /editor/builder/settings returns feature flags, configuration, picker visibility, and model policy. GET /editor/builder/infrastructure reports browser-provider and sandbox status. Both default to { enabled: false } when no MastraEditor is configured.
    • External skill registry. /editor/builder/registries/* proxies the public skills.sh catalog so the builder UI can browse and install registered skills.

    This also bumps the @mastra/core peer dependency floor to >=1.34.0-0 (see the separate changeset) because the new handlers and error mapping import runtime values from @mastra/core/agent-builder/ee.

  • Narrowed AgentSignalContents from BaseMessageListInput to string | (TextPart | FilePart)[]. (#16622)

    Fixed two signal-content bugs:

    • user-message signal attributes now reach the LLM
    • multimodal non-user-message signals no longer lose file parts

    Callers that previously passed wrapped message shapes to agent.sendSignal should now pass a bare string or a bare parts array.

    Before:
    { type: 'user-message', contents: [{ role: 'user', content: [{ type: 'text', text: 'hi' }] }] }

    After:
    { type: 'user-message', contents: [{ type: 'text', text: 'hi' }] }

    Added an optional providerOptions field to agent.sendSignal that flows through to the resulting prompt turn (as providerOptions on the LLM message) and is persisted on the stored signal message (as content.providerMetadata).

  • Routes can now require any one of multiple permissions by passing an array to requiresPermission. When an array is provided, the request is allowed if the caller holds any of the listed permissions. Existing single-string usage continues to work. (#16605)

    // Before — single permission only
    {
      path: '/v1/things',
      method: 'GET',
      requiresPermission: 'things:read',
      handler,
    }
    
    // After — single permission or ANY-of array
    {
      path: '/v1/things/:id/stream',
      method: 'GET',
      requiresPermission: ['things:read', 'things:execute'],
      handler,
    }

    Denial messages now read Missing required permission: a or b or c when an array is used.

    New endpoint

    GET /api/auth/roles/:roleId/permissions returns the resolved permission list for a role. Useful for client-side gating and admin tooling.

    const res = await fetch('/api/auth/roles/admin/permissions', { credentials: 'include' });
    // { "roleId": "admin", "permissions": ["*"] }

    Namespaced request-context keys (non-breaking)

    coreAuthMiddleware now writes user state under namespaced keys (mastra__user, mastra__userPermissions, mastra__userRoles) in addition to the existing bare keys (user, userPermissions, userRoles). The bare keys are still written for backward compatibility, so existing middleware, integrations, and built-in handlers that read requestContext.get('user') continue to work unchanged.

    New code should prefer the namespaced constants to avoid collisions with caller-supplied request-context entries:

    import { MASTRA_USER_KEY, MASTRA_USER_PERMISSIONS_KEY, MASTRA_USER_ROLES_KEY } from '@mastra/server/auth';
    
    const user = requestContext.get(MASTRA_USER_KEY);
    const permissions = requestContext.get(MASTRA_USER_PERMISSIONS_KEY) as string[] | undefined;
    const roles = requestContext.get(MASTRA_USER_ROLES_KEY) as string[] | undefined;

    The bare keys (user, userPermissions, userRoles) remain populated and are considered the documented public surface for this release; a future major release may deprecate them.

    Route permission derivation

    getEffectivePermission() now recognizes stored resource families (stored-agents, stored-skills, stored-prompt-blocks, stored-mcp-clients, stored-scorers, stored-workspaces) and publish / activate / restore action suffixes on stored-resource routes. Return type widened to string | string[] | null to support routes that map to multiple permissions.

Patch Changes

  • Fixed stored resource updates to preserve existing metadata keys. (#16651)

  • Hardened the stored-agent and stored-skill favorite toggle endpoints (PUT/DELETE /stored/{agents,skills}/:id/favorite) so callers can no longer favorite or unfavorite entities outside their tenant scope. (#16749)

    Deployments that configure storedResources.scope now get the same 404-on-mismatch protection on favorite toggles that already applied to read/update/delete. Single-tenant deployments are unaffected.

    Also corrected JSDoc on stored-agent and stored-skill handlers to reference the canonical resource/action names (stored-agents:read, stored-skills:write).

  • Fix DELETE custom routes forwarding JSON request bodies. (#16857)

  • Fixed CompositeAuth incorrectly advertising SSO, session, and user provider capabilities when no inner provider supports them. Studio would show an SSO login button even when no provider had SSO configured, leading to 401 errors on login attempts. The duck-typing check now verifies that interface methods are actual functions rather than just present on the prototype chain. (#16664)

  • Fixed provider and dataset item history response types to include fields returned by the API. (#16213)

  • Widened BrowserStreamConfig.getToolset to support async lookup. Existing synchronous implementations continue to work — the type now accepts MastraBrowser | undefined or Promise<MastraBrowser | undefined>. (#16778)

    This unblocks server-adapter implementations that need to resolve agents asynchronously (for example, hydrating stored agents from storage on first browser-stream connection).

    import { setupBrowserStream } from '@mastra/server';
    
    await setupBrowserStream(app, {
      getToolset: async agentId => {
        const agent = await resolveAgent(agentId);
        return agent?.browser;
      },
      apiPrefix,
    });
  • Align stored-entity authorship checks with their RBAC resource names. Stored-agent and stored-skill handlers were calling hasAdminBypass / assertReadAccess / assertWriteAccess / resolveAuthorFilter with resource: 'agents' and resource: 'skills', but the routes are gated by stored-agents:* / stored-skills:* permissions. An admin granted stored-agents:* (or stored-skills:*) without the global * wildcard would pass route authorization but be treated as a non-admin inside the handler, so they could not list, read, or update private records owned by other users. Handlers now use stored-agents and stored-skills as the authorship resource string, matching the permission strings emitted by the route layer. (#16666)

  • Restore backwards compatibility for the legacy GET /api/memory/threads?orderBy=<field>&sortDirection=<dir> query shape emitted by @mastra/client-js < 1.18 (and any hand-rolled HTTP clients written against pre-1.0 docs). Server 1.31.0 turned that shape into a hard 400 (Invalid input: expected object, received undefined); the legacy bare-string + sortDirection pair is now transparently fused into the current { orderBy: { field, direction } } object shape before validation, so pinned clients keep working. (#16745)

    Current callers using bracket notation (orderBy[field]=...&orderBy[direction]=...) or a JSON-stringified orderBy are unaffected.

  • Bumped the @mastra/core peer dependency floor from >=1.32.0-0 to >=1.34.0-0. (#16666)

  • Fixed the playground memory configuration display for agents using observationalMemory: true. (#16213)

@mastra/slack@1.2.1

Patch Changes

    • SlackProvider.connect() now merges with existing channel adapters instead of replacing them, preserving adapters the agent author already configured (e.g. Discord). (#16517)
    • Slack interactive payloads (button clicks, modal submissions) no longer return 400 Malformed JSON body. The provider only JSON-parses the body for the events callback path and forwards form-urlencoded payloads to the adapter's webhook handler unchanged.
    • Bumped chat to ^4.29.0.

@mastra/stagehand@0.2.3

Patch Changes

  • Silence Stagehand console logging by default and route logs through a logger hook to avoid corrupting host TUIs. (#16677)

@mastra/temporal@0.1.6

Patch Changes

  • Bumped the @mastra/core peer dependency floor from >=1.32.0-0 to >=1.34.0-0. (#16666)

@mastra/voice-openai-realtime@0.12.4

Patch Changes

  • Fixed OpenAI Realtime voice output streaming for current audio event names. (#16763)

  • Fixed user audio transcriptions missing or duplicating segments when OpenAI Realtime sends completion events. (#16759)

Other updated packages

The following packages were updated with dependency changes only:

Don't miss a new mastra release

NewReleases is sending notifications on new releases.