-
fix(observability): start MODEL_STEP span at beginning of LLM execution (#11409)
The MODEL_STEP span was being created when the step-start chunk arrived (after the model API call completed), causing the span's startTime to be close to its endTime instead of accurately reflecting when the step began.
This fix ensures MODEL_STEP spans capture the full duration of each LLM execution step, including the API call latency, by starting the span at the beginning of the step execution rather than when the response starts streaming.
Fixes #11271
-
Fixed inline type narrowing for tool.execute() return type when using outputSchema. (#11420)
Problem: When calling tool.execute(), TypeScript couldn't narrow the ValidationError | OutputType union after checking 'error' in result && result.error, causing type errors when accessing output properties.
Solution:
- Added
{ error?: never } to the success type, enabling proper discriminated union narrowing
- Simplified
createTool generics so inputData is correctly typed based on inputSchema
Note: Tool output schemas should not use error as a field name since it's reserved for ValidationError discrimination. Use errorMessage or similar instead.
Usage:
const result = await myTool.execute({ firstName: 'Hans' });
if ('error' in result && result.error) {
console.error('Validation failed:', result.message);
return;
}
// ✅ TypeScript now correctly narrows result
return { fullName: result.fullName };
-
Add support for instructions field in MCPServer (#11421)
Implements the official MCP specification's instructions field, which allows MCP servers to provide system-wide prompts that are automatically sent to clients during initialization. This eliminates the need for per-project configuration files (like AGENTS.md) by centralizing the system prompt in the server definition.
What's New:
- Added
instructions optional field to MCPServerConfig type
- Instructions are passed to the underlying MCP SDK Server during initialization
- Instructions are sent to clients in the
InitializeResult response
- Fully compatible with all MCP clients (Cursor, Windsurf, Claude Desktop, etc.)
Example Usage:
const server = new MCPServer({
name: 'GitHub MCP Server',
version: '1.0.0',
instructions:
'Use the available tools to help users manage GitHub repositories, issues, and pull requests. Always search before creating to avoid duplicates.',
tools: { searchIssues, createIssue, listPRs },
});
-
Add storage composition to MastraStorage (#11401)
MastraStorage can now compose storage domains from different adapters. Use it when you need different databases for different purposes - for example, PostgreSQL for memory and workflows, but a different database for observability.
import { MastraStorage } from '@mastra/core/storage';
import { MemoryPG, WorkflowsPG, ScoresPG } from '@mastra/pg';
import { MemoryLibSQL } from '@mastra/libsql';
// Compose domains from different stores
const storage = new MastraStorage({
id: 'composite',
domains: {
memory: new MemoryLibSQL({ url: 'file:./local.db' }),
workflows: new WorkflowsPG({ connectionString: process.env.DATABASE_URL }),
scores: new ScoresPG({ connectionString: process.env.DATABASE_URL }),
},
});
Breaking changes:
storage.supports property no longer exists
StorageSupports type is no longer exported from @mastra/core/storage
All stores now support the same features. For domain availability, use getStore():
const store = await storage.getStore('memory');
if (store) {
// domain is available
}
-
Fix various places in core package where we were logging with console.error instead of the mastra logger. (#11425)
-
fix(workflows): ensure writer.custom() bubbles up from nested workflows and loops (#11422)
Previously, when using writer.custom() in steps within nested sub-workflows or loops (like dountil), the custom data events would not properly bubble up to the top-level workflow stream. This fix ensures that custom events are now correctly propagated through the nested workflow hierarchy without modification, allowing them to be consumed at the top level.
This brings workflows in line with the existing behavior for agents, where custom data chunks properly bubble up through sub-agent execution.
What changed:
- Modified the
nestedWatchCb function in workflow event handling to detect and preserve data-* custom events
- Custom events now bubble up directly without being wrapped or modified
- Regular workflow events continue to work as before with proper step ID prefixing
Example:
const subStep = createStep({
id: 'subStep',
execute: async ({ writer }) => {
await writer.custom({
type: 'custom-progress',
data: { status: 'processing' },
});
return { result: 'done' };
},
});
const subWorkflow = createWorkflow({ id: 'sub' }).then(subStep).commit();
const topWorkflow = createWorkflow({ id: 'top' }).then(subWorkflow).commit();
const run = await topWorkflow.createRun();
const stream = run.stream({ inputData: {} });
// Custom events from subStep now properly appear in the top-level stream
for await (const event of stream) {
if (event.type === 'custom-progress') {
console.log(event.data); // { status: 'processing' }
}
}