Request Context
Agents, tools, and workflows can all accept RequestContext as a parameter, making request-specific values available to the underlying primitives.
When to use RequestContextDirect link to when-to-use-requestcontext
Use RequestContext when a primitive's behavior should change based on runtime conditions. For example, you might switch models or storage backends based on user attributes, or adjust instructions and tool selection based on language.
Note: RequestContext is primarily used for passing data into specific
requests. It's distinct from agent memory, which handles conversation
history and state persistence across multiple calls.
Setting valuesDirect link to Setting values
Pass requestContext into an agent, network, workflow, or tool call to make values available to all underlying primitives during execution. Use .set() to define values before making the call.
The .set() method takes two arguments:
- key: The name used to identify the value.
- value: The data to associate with that key.
import { RequestContext } from "@mastra/core/request-context";
export type UserTier = {
"user-tier": "enterprise" | "pro";
};
const requestContext = new RequestContext<UserTier>();
requestContext.set("user-tier", "enterprise");
const agent = mastra.getAgent("weatherAgent");
await agent.generate("What's the weather in London?", {
requestContext,
});
const routingAgent = mastra.getAgent("routingAgent");
routingAgent.network("What's the weather in London?", {
requestContext,
});
const run = await mastra.getWorkflow("weatherWorkflow").createRun();
await run.start({
inputData: {
location: "London",
},
requestContext,
});
await run.resume({
resumeData: {
city: "New York",
},
requestContext,
});
await weatherTool.execute(
{ location: "London" },
{ requestContext },
);
Setting values based on request headersDirect link to Setting values based on request headers
You can populate requestContext dynamically in server middleware by extracting information from the request. In this example, the temperature-unit is set based on the Cloudflare CF-IPCountry header to ensure responses match the user's locale.
import { Mastra } from "@mastra/core";
import { RequestContext } from "@mastra/core/request-context";
import { testWeatherAgent } from "./agents/test-weather-agent";
export const mastra = new Mastra({
agents: { testWeatherAgent },
server: {
middleware: [
async (context, next) => {
const country = context.req.header("CF-IPCountry");
const requestContext = context.get("requestContext");
requestContext.set(
"temperature-unit",
country === "US" ? "fahrenheit" : "celsius",
);
await next();
},
],
},
});
Visit Middleware for how to use server middleware.
Accessing values with agentsDirect link to Accessing values with agents
You can access the requestContext argument from any supported configuration options in agents. These functions can be sync or async. Use the .get() method to read values from requestContext.
export type UserTier = {
"user-tier": "enterprise" | "pro";
};
export const weatherAgent = new Agent({
id: "weather-agent",
name: "Weather Agent",
instructions: async ({ requestContext }) => {
const userTier = requestContext.get("user-tier") as UserTier["user-tier"];
if (userTier === "enterprise") {}
},
model: ({ requestContext }) => {},
tools: ({ requestContext }) => {},
memory: ({ requestContext }) => {},
});
You can also use requestContext with other options like agents, workflows, scorers, inputProcessors, and outputProcessors.
Dynamic instructionsDirect link to Dynamic instructions
Agent instructions can be provided as an async function, enabling you to resolve prompts dynamically at runtime. Combined with requestContext, this enables patterns like:
- Personalization: Tailor instructions based on user attributes, preferences, or tier
- Localization: Adjust tone, language, or behavior based on locale
- A/B testing: Serve different prompt variants for experimentation
- External prompt management: Fetch prompts from registry services without redeploying
import { Agent } from "@mastra/core/agent";
export const dynamicAgent = new Agent({
id: "dynamic-agent",
name: "Dynamic Agent",
instructions: async ({ requestContext }) => {
const userTier = requestContext?.get("user-tier");
const locale = requestContext?.get("locale");
// Personalize based on user tier
const basePrompt = userTier === "enterprise"
? "You are a premium support agent. Provide detailed, thorough responses with technical depth."
: "You are a helpful assistant. Be concise and friendly.";
// Localize behavior
const localeInstructions = locale === "ja"
? "Respond in Japanese using formal keigo."
: "";
return `${basePrompt} ${localeInstructions}`.trim();
},
model: "openai/gpt-5.1",
});
Fetching from a prompt registryDirect link to Fetching from a prompt registry
If your organization uses a prompt registry service for central prompt management, you can fetch instructions at runtime. This allows you to update prompts without redeploying, run experiments with variants, and track prompt usage across your agents.
import { Agent } from "@mastra/core/agent";
// Your prompt registry client
import { promptRegistry } from "../lib/prompt-registry";
export const registryAgent = new Agent({
id: "registry-agent",
name: "Registry Agent",
instructions: async ({ requestContext }) => {
const prompt = await promptRegistry.getPrompt({
promptId: "customer-support-agent",
// Pass context for variant selection or tracking
variant: requestContext?.get("experiment-variant"),
userId: requestContext?.get("user-id"),
});
return prompt.content;
},
model: "openai/gpt-5.1",
});
Visit Agent for a full list of configuration options.
Accessing values from workflow stepsDirect link to Accessing values from workflow steps
You can access the requestContext argument from a workflow step's execute function. This function can be sync or async. Use the .get() method to read values from requestContext.
export type UserTier = {
"user-tier": "enterprise" | "pro";
};
const stepOne = createStep({
id: "step-one",
execute: async ({ requestContext }) => {
const userTier = requestContext.get("user-tier") as UserTier["user-tier"];
if (userTier === "enterprise") {}
},
});
Visit createStep() for a full list of configuration options.
Accessing values with toolsDirect link to Accessing values with tools
You can access the requestContext argument from a tool's execute function. This function is async. Use the .get() method to read values from requestContext.
export type UserTier = {
"user-tier": "enterprise" | "pro";
};
export const weatherTool = createTool({
id: "weather-tool",
execute: async (inputData, context) => {
const userTier = context?.requestContext?.get("user-tier") as UserTier["user-tier"] | undefined;
if (userTier === "enterprise") {}
},
});
Visit createTool() for a full list of configuration options.
Reserved keysDirect link to Reserved keys
Mastra reserves special context keys for security purposes. When set by middleware, these keys take precedence over client-provided values. The server automatically validates ownership and returns 403 errors when users attempt to access resources they don't own.
import {
MASTRA_RESOURCE_ID_KEY,
MASTRA_THREAD_ID_KEY,
} from "@mastra/core/request-context";
// In middleware: force memory operations to use authenticated user's ID
requestContext.set(MASTRA_RESOURCE_ID_KEY, user.id);
// In middleware: set validated thread ID
requestContext.set(MASTRA_THREAD_ID_KEY, threadId);
| Key | Purpose |
|---|---|
MASTRA_RESOURCE_ID_KEY | Forces all memory operations to use this resource ID. The server validates that accessed threads belong to this resource and returns 403 if not. |
MASTRA_THREAD_ID_KEY | Forces thread operations to use this thread ID, overriding client-provided values |
These keys are used to implement user isolation in multi-tenant applications. See Authorization middleware for usage examples.
TypeScript supportDirect link to TypeScript support
When you provide a type parameter to RequestContext, all methods are fully typed:
import { RequestContext } from "@mastra/core/request-context";
type MyContext = {
userId: string;
maxTokens: number;
isPremium: boolean;
};
const ctx = new RequestContext<MyContext>();
// set() enforces correct value types
ctx.set("userId", "user-123"); // ✓ valid
ctx.set("maxTokens", 4096); // ✓ valid
ctx.set("maxTokens", "wrong"); // ✗ TypeScript error: expected number
// get() returns the correct type automatically
const tokens = ctx.get("maxTokens"); // inferred as number
const id = ctx.get("userId"); // inferred as string
// keys() returns typed keys
for (const key of ctx.keys()) {
// key is "userId" | "maxTokens" | "isPremium"
}
// entries() supports type narrowing
for (const [key, value] of ctx.entries()) {
if (key === "maxTokens") {
// TypeScript knows value is number here
console.log(value.toFixed(2));
}
if (key === "userId") {
// TypeScript knows value is string here
console.log(value.toUpperCase());
}
}
Schema validationDirect link to Schema validation
Use requestContextSchema to define a Zod schema that validates request context values at runtime. This catches missing or invalid context values early, provides clear error messages, and gives you type inference within your component.
Agent schema validationDirect link to Agent schema validation
When you define requestContextSchema on an agent, the context is validated at the start of generate() or stream(). If validation fails, the agent throws a MastraError before any LLM calls are made.
import { Agent } from "@mastra/core/agent";
import { z } from "zod";
export const validatedAgent = new Agent({
id: "validated-agent",
name: "Validated Agent",
requestContextSchema: z.object({
userId: z.string(),
apiKey: z.string(),
}),
instructions: ({ requestContext }) => {
// Access all values as a typed object
const { userId, apiKey } = requestContext.all;
// { userId: string; apiKey: string }
// Or retrieve individual values with .get()
const id = requestContext.get("userId");
// string
return `You are helping user ${userId}`;
},
model: "openai/gpt-4o",
});
When validation fails, the error includes the agent ID and details about which fields failed:
Request context validation failed for agent 'validated-agent':
- apiKey: Required
Tool schema validationDirect link to Tool schema validation
When you define requestContextSchema on a tool, the context is validated before execute() runs. Unlike agents, tools return a validation error object instead of throwing:
import { createTool } from "@mastra/core/tools";
import { z } from "zod";
export const validatedTool = createTool({
id: "validated-tool",
description: "A tool that requires authenticated context",
inputSchema: z.object({
query: z.string(),
}),
requestContextSchema: z.object({
userId: z.string(),
}),
execute: async (inputData, context) => {
// Access all values as a typed object
const { userId } = context.requestContext?.all ?? {};
// { userId: string }
// Or retrieve individual values with .get()
const id = context.requestContext?.get("userId");
// string | undefined
return { result: `Processed for ${userId}` };
},
});
When validation fails, the tool returns an error object instead of throwing:
{
"error": true,
"message": "Request context validation failed for validated-tool. Please fix the following errors and try again:\n- userId: Required\n\nProvided context: {}"
}
Workflow schema validationDirect link to Workflow schema validation
When you define requestContextSchema on a workflow, the context is validated at the start of run.start(). If validation fails, the workflow throws an error before any steps execute.
import { createWorkflow, createStep } from "@mastra/core/workflows";
import { z } from "zod";
// Define schema once and share between workflow and steps
const workflowContextSchema = z.object({
tenantId: z.string(),
});
const step1 = createStep({
id: "step-1",
inputSchema: z.object({ message: z.string() }),
outputSchema: z.object({ result: z.string() }),
// Add schema to step for type inference
requestContextSchema: workflowContextSchema,
execute: async ({ inputData, requestContext }) => {
// Access all values as a typed object
const { tenantId } = requestContext.all;
// { tenantId: string }
// Or retrieve individual values with .get()
const id = requestContext.get("tenantId");
// string
return { result: `Processed for tenant ${tenantId}` };
},
});
export const validatedWorkflow = createWorkflow({
id: "validated-workflow",
inputSchema: z.object({ message: z.string() }),
outputSchema: z.object({ result: z.string() }),
requestContextSchema: workflowContextSchema,
})
.then(step1)
.commit();
When validation fails, the workflow throws an error:
Request context validation failed for workflow 'validated-workflow':
- tenantId: Required
Steps can also define their own requestContextSchema for step-level validation. Step validation runs before the step's execute() function.
Validation behaviorDirect link to Validation behavior
| Component | Property | Validation timing | On failure |
|---|---|---|---|
| Agent | requestContextSchema | Start of generate() / stream() | Throws MastraError |
| Tool | requestContextSchema | Before execute() | Returns error object |
| Workflow | requestContextSchema | Start of run.start() | Throws Error |
| Step | requestContextSchema | Before step execute() | Step fails with error |
Best practicesDirect link to Best practices
Match your middleware: Define the same required fields in your schema that your middleware sets. This ensures the contract between middleware and components is explicit and validated.
// Middleware sets these fields
requestContext.set("userId", user.id);
requestContext.set("tenantId", tenant.id);
// Schema validates they exist
requestContextSchema: z.object({
userId: z.string(),
tenantId: z.string(),
})
Use optional fields for conditional context: Use .optional() for values that may not always be present.
requestContextSchema: z.object({
userId: z.string(), // Always required
experimentVariant: z.string().optional(), // May not be set
})
Handle tool validation errors: Since tools return error objects instead of throwing, check for errors in your agent or workflow logic when tool execution is critical.
Testing with Studio presetsDirect link to Testing with Studio presets
When developing locally, you can define named presets in a JSON file and load them into Studio with the --request-context-presets CLI flag. This adds a dropdown to the request context editor in Studio so you can quickly switch between configurations without manually editing JSON each time.
mastra dev --request-context-presets ./presets.json
{
"development": { "userId": "dev-user", "env": "development" },
"production": { "userId": "prod-user", "env": "production" }
}
When you select a preset from the dropdown, the JSON editor populates with that preset's values. Editing the JSON manually switches the dropdown back to "Custom".