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
AgentSignalContentsnarrowed tostring | (TextPart | FilePart)[](no longer accepts wrappedBaseMessageListInputshapes); updateagent.sendSignalcallers accordingly.@mastra/playground-uiButton variants changed:cta/contrast/linkremoved in favor ofdefault/primary/outline/ghost;ButtonWithTooltipremoved (useButtonwithtooltipprop).PopoverContentno longer forwardsonOpenAutoFocus/onCloseAutoFocus(useinitialFocus/finalFocusinstead).
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.promptCacheRetentionwhen 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.permissionFGARouteConfig.permissionFGARouteInfo.requiresPermissionFGADeniedError.permissionCheckFGAOptions.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
requestContextwhen 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 optionalfavoritesnamespace onIMastraEditorso 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
favoritesfield is optional — existing implementations ofIMastraEditorcontinue to work unchanged.@mastra/editorships a defaultEditorFavoritesNamespacethat wires this up against the storagefavoritesdomain.Also renamed
AgentFeatures.starstoAgentFeatures.favoritesin@mastra/core/agent-builder/eeso the feature flag aligns with the storage column (favoriteCount), HTTP routes (/favorite), and the editorfavoritesnamespace. 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:
BrowserProviderinterfaceImplement 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
MastraBrowserinstance fromcreateBrowser.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.browsersand.builderWire browser providers and agent-builder options into the editor:
new MastraEditor({ browsers: { 'my-browser': myProvider }, builder: { features: { agent: { favorites: true } } }, });
New:
visibilityonupdateAgentMetaSet an agent's visibility (private or public) through the editor namespace:
await editor.agent.updateAgentMeta('agent-id', { visibility: 'public' });
-
publishSkillnow 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
IMastraEditorfor server-side gating of builder-aware behavior:interface IMastraEditor { // ...existing members... hasEnabledBuilderConfig?(): boolean; resolveBuilder?(): Promise<IAgentBuilder | undefined>; }
Both methods are optional — existing implementations of
IMastraEditorcontinue to work unchanged. Servers that consume them treatundefined/ 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.corsfor one global CORS policy across the server:new Mastra({ server: { cors: { origin: '*', }, }, });
-
Narrowed
AgentSignalContentsfromBaseMessageListInputtostring | (TextPart | FilePart)[]. (#16622)Fixed two signal-content bugs:
user-messagesignal attributes now reach the LLM- multimodal non-
user-messagesignals no longer lose file parts
Callers that previously passed wrapped message shapes to
agent.sendSignalshould 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
providerOptionsfield toagent.sendSignalthat flows through to the resulting prompt turn (asproviderOptionson the LLM message) and is persisted on the stored signal message (ascontent.providerMetadata). -
publishSkillFromSource()(andcollectSkillForPublish()) now return afilesfield containing the full skill source as a tree ofStorageSkillFileNodeentries 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(...)insideexecutewhile also declaring anoutputSchema. Theexecutereturn type now allowsvoidin addition to the declared output shape, so the idiomaticreturn await suspend(...)pattern type-checks correctly. (#16799) -
Fixed a startup bug in
MastraCompositeStore.init()when usingdefaultoreditor. (#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
LibSQLStoreon a local file).Now,
MastraCompositeStore.init()runs parentdefaultandeditorinitialization 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
declineToolCallinstead 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 originalrunId). - The original
ChannelConfigis now exposed via the newAgentChannels.channelConfigfield so channel providers can merge with existing adapters instead of replacing them. - Bumped
chatto^4.29.0.
- 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
-
Added the
internalUsage?: UsageStatsfield toAIBaseAttributes, so any span type can carry token usage rolled up from internal descendant spans. Populated automatically by@mastra/observabilitywhen an internalMODEL_GENERATIONends 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 fromregisterApiRoute(). The check incorrectly rejected custom routes starting with/api/even when users configured a differentapiPrefix. 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)
RequestContextinstances 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-inskilltool 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-invocableskill 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
backgroundTaskson theMastrainstance 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 }, breakingagent.generate()/agent.stream()for tool-using flows.The LLM
_backgroundoverride 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-levelbackground: { enabled: true }or agent-levelbackgroundTasks: { tools: { … } }(ortools: 'all'),_background.enabled: truefrom the model is ignored and the tool runs in the foreground. Opted-in tools continue to honor LLM overrides forenabled,timeoutMs, andmaxRetriesas 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)onmastra_schedulesand(schedule_id, actual_fire_at)onmastra_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.enabledis 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(). Accessingmastra.schedulerbeforestartWorkers()runs throws a descriptive error instead of returning a half-initialized instance.
- Added missing indexes on the schedules tables that the tick loop polls every 10 seconds:
-
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
createMastraCodeusage 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:
getSpansthrew 'This storage provider does not support batch-fetching spans'. It now batch-fetches spans by id within a trace, enabling the optimizedgetBranchpath on in-memory storage.batchCreateLogs,batchCreateMetrics,createScore/batchCreateScores,createFeedback/batchCreateFeedbackappended 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. getMetricTimeSeriesmerged 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)
MastraAuthOktaconcatenated/v1/authorize(and/token,/keys,/logout) directly ontoOKTA_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
endpointBaseis now derived from the issuer — verbatim when it already contains/oauth2/, otherwise${issuer}/oauth2— and used for the authorize, token, keys, and logout URLs. JWTiss-claim validation still uses the raw issuer so token validation stays correct on both server types. Trailing slashes on the issuer are also normalized soOKTA_ISSUER=https://{domain}/no longer produces.../oauth2//v1/....
@mastra/auth-workos@1.5.0
Minor Changes
-
FGA
check()andrequire()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
FGADeniedErrorlists them asany 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?)andagent.closeBrowser(threadId?)to theAgentresource, plus aGetAgentBrowserSessionResponsetype. (#16668)browserSessionprobes 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.closeBrowserends the agent's browser session (or a single thread's session ifthreadIdis passed). Both methods go through the configured clientbaseUrlandapiPrefix, 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
fetchcalls. (#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.tsto 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
paginationanddeltaCursorwhen delta polling is supported. Delta-mode responses includedeltaand do not includepagination.If you read these responses directly in typed code, note that
paginationis only included in page mode. -
Narrowed
AgentSignalContentsfromBaseMessageListInputtostring | (TextPart | FilePart)[]. (#16622)Fixed two signal-content bugs:
user-messagesignal attributes now reach the LLM- multimodal non-
user-messagesignals no longer lose file parts
Callers that previously passed wrapped message shapes to
agent.sendSignalshould 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
providerOptionsfield toagent.sendSignalthat flows through to the resulting prompt turn (asproviderOptionson the LLM message) and is persisted on the stored signal message (ascontent.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(andresumeStreamUntilIdle) incorrectly re-hit the one-shot resume endpoint instead of falling back to the regular stream endpoint. The resume routes consume server-sideresumeDataand cannot be replayed, so client-tool continuations now route to/streamand/stream-until-idlerespectively. (#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
ConvexNativeVectoradapter uses (#16729)
Convex schema-defined vector indexes andctx.vectorSearchinstead of loading vectors throughConvexVectorand
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
ConvexNativeVectorin 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
ConvexServerCacheso 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 addmastra_cacheandmastra_cache_list_itemsto their Convex schema, mount themastraCachehandler, 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.corsfor one global CORS policy across the server:new Mastra({ server: { cors: { origin: '*', }, }, });
Patch Changes
-
Browser streaming now works for stored agents. The deployer's
getToolsetfirst 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
wsand@hono/node-wspackages aren't installed, or the deployer is running in a serverless environment), the deployer now registers a fallbackGET /api/agents/:agentId/browser/sessionroute 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/corepeer dependency floor from>=1.32.0-0to>=1.34.0-0. (#16666)
@mastra/deployer-cloud@1.36.0
Patch Changes
- Bumped the
@mastra/corepeer dependency floor from>=1.32.0-0to>=1.34.0-0. (#16666)
@mastra/deployer-cloudflare@1.1.37
Patch Changes
- Bumped the
@mastra/corepeer dependency floor from>=1.32.0-0to>=1.34.0-0. (#16666)
@mastra/deployer-netlify@1.1.13
Patch Changes
- Bumped the
@mastra/corepeer dependency floor from>=1.32.0-0to>=1.34.0-0. (#16666)
@mastra/deployer-vercel@1.1.31
Patch Changes
- Bumped the
@mastra/corepeer dependency floor from>=1.32.0-0to>=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
-
EditorWorkspaceNamespacecan now snapshot a liveWorkspacefor persistence — the reverse ofhydrateSnapshotToWorkspace(): (#16673)const snapshot = await editor.workspace.snapshotFromWorkspace(runtimeWorkspace); await editor.workspace.create({ id: 'my-workspace', ...snapshot });
snapshotFromWorkspace()isasyncand awaitssandbox.getInfo()andfilesystem.getInfo()so async providers likeCompositeFilesystemkeep their mount metadata in the stored config.Also includes two smaller behavioral fixes:
EditorSkillNamespace.publishSkillFromSource()stores the newfilesfield on the published skill version and stripsundefinedkeys before callingupdate()(libsql/pg adapters rejectundefinedbind arguments).CrudEditorNamespace.clearCache(id)always callsonCacheEvict(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.favoritesnamespace 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/serveralready 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(wasuserPermissions). 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/serverrelease and the adapter will continue to enforce route permissions exactly as before. -
Bumped the
@mastra/corepeer dependency floor from>=1.32.0-0to>=1.34.0-0. (#16666)
@mastra/fastembed@1.1.0
Minor Changes
- Replace the abandoned
fastembednpm 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(wasuserPermissions). 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/serverrelease and the adapter will continue to enforce route permissions exactly as before. -
Bumped the
@mastra/corepeer dependency floor from>=1.32.0-0to>=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(wasuserPermissions). 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/serverrelease and the adapter will continue to enforce route permissions exactly as before. -
Bumped the
@mastra/corepeer dependency floor from>=1.32.0-0to>=1.34.0-0. (#16666) -
Added
GET /agents/:agentId/browser/sessionendpoint (under the configuredapiPrefix, 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 }.screencastAvailableis alwaystruewhen 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.setupBrowserStreamnow accepts an optionalapiPrefixso the probe and existingPOST /agents/:agentId/browser/closeroutes are mounted under the same prefix as the rest of the server. The deployer wires this frommastra.getServer().apiPrefixautomatically. -
The Hono adapter now awaits
getToolsetcalls 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(wasuserPermissions). 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/serverrelease and the adapter will continue to enforce route permissions exactly as before. -
Bumped the
@mastra/corepeer dependency floor from>=1.32.0-0to>=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)onmastra_schedulesand(schedule_id, actual_fire_at)onmastra_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.enabledis 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(). Accessingmastra.schedulerbeforestartWorkers()runs throws a descriptive error instead of returning a half-initialized instance.
- Added missing indexes on the schedules tables that the tick loop polls every 10 seconds:
@mastra/mcp@1.8.0
Minor Changes
-
Added MCP tool annotations to the
requireToolApprovalcontext and exposed them on tools returned fromlistTools()/listToolsets(). (#16784)The
requireToolApprovalcallback now receives the server-advertisedannotations(title,readOnlyHint,destructiveHint,idempotentHint,openWorldHint) alongsidetoolNameandargs. This lets you write declarative approval policies instead of hardcoding tool name lists. Annotations are also propagated onto Mastra tools astool.mcp.annotationsso 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: truefor those. When the server omits annotations entirely, this field isundefined, 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_idandclient_secretfor confidential clients. The provider previously shipped an emptyaddClientAuthenticationmethod that satisfied the MCP SDK's existence check and short-circuited its default credential attachment, causinginvalid_requesterrors 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.promptCacheRetentionwhen available.const memory = new Memory({ options: { observationalMemory: { model: 'google/gemini-2.5-flash', activateAfterIdle: 'auto', activateOnProviderChange: true, }, }, });
-
Add
observeAttachmentstoObservationConfigfor 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/corepeer dependency floor from>=1.32.0-0to>=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.internalfilters 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 aninternalUsageattribute 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 visiblePROCESSOR_RUNspan. - 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_GENERATIONends inside a non-internal ancestor. - The visible ancestor (e.g.
-
MastraStorageExporternow 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 onDefaultExporter. (#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)onmastra_schedulesand(schedule_id, actual_fire_at)onmastra_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.enabledis 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(). Accessingmastra.schedulerbeforestartWorkers()runs throws a descriptive error instead of returning a half-initialized instance.
- Added missing indexes on the schedules tables that the tick loop polls every 10 seconds:
@mastra/playground-ui@29.0.0
Minor Changes
-
Added
ContextMenufor right-click interactions. Supports submenus, checkbox and radio items, keyboard shortcuts, and adestructivevariant 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
ButtonWithTooltipfrom@mastra/playground-ui. UseButtonwith thetooltipprop 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>;
tooltipsupports the same values astooltipContent. Icon-only buttons that pass a stringtooltipnow also get it as theiraria-labelautomatically, matching how labelled controls have always behaved. Pass an explicitaria-labelto 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. Thecta,contrast, and unusedlinkvariants have been removed.primarynow uses a high-contrastneutral6fill instead ofsurface4, 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-softand--surface-overlay-strong— alpha overlays of the opposite-theme color, used bySectionCardheader strip andDashboardCardfill so cards read consistently on any surface.Other:
- DashboardCard radius reduced to
rounded-xland padding tightened topx-4 py-3for better grid density. - SectionCard wrapper no longer fills its background — header strip + border carry definition.
- Dark
surface2/surface3darkened slightly (16.84% → 16%, 19.13% → 18%) so the main frame reads as a distinct surface. - Dark
border1/border2alphas bumped (6% → 7%, 10% → 11%) for closer dark/light parity. - Removed deprecated
--section-card-*tokens and their@utilityblocks.
- DashboardCard radius reduced to
Patch Changes
-
Added a
destructivevariant onDropdownMenu.Itemto highlight dangerous actions like delete. (#16791)<DropdownMenu.Item variant="destructive">Delete project</DropdownMenu.Item>
-
PopoverContentno longer forwards the underlying library's auto-focus event handlers (onOpenAutoFocus,onCloseAutoFocus). To control focus when the popover opens or closes, useinitialFocusandfinalFocus. (#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 "
MenuPortalmust be used withinMenu". The submenu content now uses the design system'sDropdownMenu.SubContentinstead 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 —asChildonAlertDialog.Trigger, andAlertDialog.ActionandAlertDialog.Cancel, all work exactly as before. (Internally it now builds on Base UI primitives.) (#16824) -
Moved the
Collapsiblecomponent to Base UI, with a smoother height-based expand and collapse animation. The public API is unchanged —asChildonCollapsibleTriggerstill works. (#16825) -
Upgraded
@base-ui/reactto 1.5, making popups noticeably faster — components built on Base UI such asTooltip,Popover,DropdownMenuandContextMenunow 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
useTracesreturn fieldsisRefetching— 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.recentlyAddedKeys—Set<string>oftraceId:spanIdfor rows that just arrived via delta polling. Drives the temporary highlight inTracesListView.
New polling config
Every timing in the hook is tunable per-instance via a new
pollingoption: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 whosetraceId:spanIdis in the set get theanimate-row-highlightclass — a brief fade-out to transparent, added toindex.css.Compatibility
Requires
@mastra/serverand@mastra/client-jsat 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
alignandstackvariants toPageLayout.Row. Usestack="responsive"for top bars that should collapse to a vertical stack on narrow viewports, andalign="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 viagetByRole('tooltip')(Base UI does not add this role automatically). Existing<TooltipTrigger asChild>usage continues to work unchanged, and Base UI's nativerenderprop is now also supported onTooltipTriggerso consumers wrapping anchors, custom router links, or icons can pass the element directly without anasChildadapter: (#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-sliderwith@base-ui/react/slideras 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.Controland an invisible hit area on the thumb so it is easier to grab - Added
cursor-pointeron the control andcursor-not-allowedwhen disabled - Removed the now unused
@radix-ui/react-sliderand@radix-ui/react-tabsdependencies
API compatibility
The public API is preserved.
onValueChangeandonValueCommittedare wrapped so consumers always receivenumber[], even though base-ui returnsnumber | number[]internally. Existing call sites like<Slider value={[temperature]} onValueChange={value => setTemperature(value[0])} />continue to work without changes. - Replaced
-
Moved the
Dialogcomponent to Base UI. The public API is unchanged —asChildonDialogTriggerandDialogClosestill works the same way, and open/close animations behave as before. (#16821)
@mastra/react@0.4.0
Minor Changes
-
Added
clientToolsoption touseChat'sgenerate/streamcalls 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()andagent.stream()calls. -
Narrowed
AgentSignalContentsfromBaseMessageListInputtostring | (TextPart | FilePart)[]. (#16622)Fixed two signal-content bugs:
user-messagesignal attributes now reach the LLM- multimodal non-
user-messagesignals no longer lose file parts
Callers that previously passed wrapped message shapes to
agent.sendSignalshould 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
providerOptionsfield toagent.sendSignalthat flows through to the resulting prompt turn (asproviderOptionson the LLM message) and is persisted on the stored signal message (ascontent.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
paginationanddeltaCursorwhen delta polling is supported. Delta-mode responses includedeltaand do not includepagination.If you read these responses directly in typed code, note that
paginationis 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
favoriteCountand the caller'sisFavoritedflag.PUT/DELETE /stored/{agents|skills}/:id/favoritetoggle the favorite for the caller. - Avatar validation. Stored-agent/skill metadata avatars are validated through a new
validateMetadataAvatarUrlhelper (rejects payloads over the size limit or with malformed base64). - Model-policy enforcement. Stored-agent create/update routes invoke
assertModelAllowedvia the newresolveBuilderModelPolicyhelper. Disallowed models map to HTTP 422 with a structured body —{ code, attempted, offendingLabel, allowed }— viahandleError's newModelNotAllowedErrormapping. - Builder introspection.
GET /editor/builder/settingsreturns feature flags, configuration, picker visibility, and model policy.GET /editor/builder/infrastructurereports browser-provider and sandbox status. Both default to{ enabled: false }when noMastraEditoris 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/corepeer 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
AgentSignalContentsfromBaseMessageListInputtostring | (TextPart | FilePart)[]. (#16622)Fixed two signal-content bugs:
user-messagesignal attributes now reach the LLM- multimodal non-
user-messagesignals no longer lose file parts
Callers that previously passed wrapped message shapes to
agent.sendSignalshould 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
providerOptionsfield toagent.sendSignalthat flows through to the resulting prompt turn (asproviderOptionson the LLM message) and is persisted on the stored signal message (ascontent.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 cwhen an array is used.New endpoint
GET /api/auth/roles/:roleId/permissionsreturns 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)
coreAuthMiddlewarenow 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 readrequestContext.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) andpublish/activate/restoreaction suffixes on stored-resource routes. Return type widened tostring | string[] | nullto 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.scopenow 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.getToolsetto support async lookup. Existing synchronous implementations continue to work — the type now acceptsMastraBrowser | undefinedorPromise<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/resolveAuthorFilterwithresource: 'agents'andresource: 'skills', but the routes are gated bystored-agents:*/stored-skills:*permissions. An admin grantedstored-agents:*(orstored-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 usestored-agentsandstored-skillsas 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 +sortDirectionpair 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-stringifiedorderByare unaffected. -
Bumped the
@mastra/corepeer dependency floor from>=1.32.0-0to>=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
chatto^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/corepeer dependency floor from>=1.32.0-0to>=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:
- @mastra/agent-builder@1.0.37
- @mastra/arize@1.1.1
- @mastra/arthur@0.2.11
- @mastra/braintrust@1.1.1
- @mastra/laminar@1.1.1
- @mastra/langfuse@1.3.1
- @mastra/langsmith@1.2.1
- @mastra/longmemeval@1.0.42
- @mastra/mcp-docs-server@1.1.39
- @mastra/opencode@0.0.39
- @mastra/otel-bridge@1.1.1
- @mastra/otel-exporter@1.1.1
- @mastra/posthog@1.0.25
- @mastra/sentry@1.0.24