User Context (ctx.userId)
How to identify and personalize responses for each subscriber in your App using the stable ctx.userId identifier mctx provides to every handler.
Every tool, resource, and prompt handler on mctx receives a third parameter: ctx. The ctx.userId property gives you a stable identifier for the authenticated subscriber making the request.
ctx.userId is typed as string | undefined. In practice it is present for all authenticated requests — the mctx dispatch layer validates authentication before your server is called. The optional typing exists as a defensive safety measure: guard your access so your code compiles correctly under TypeScript strict mode and handles edge cases safely.
const getPreferences = async (args, ask, ctx) => {
if (!ctx.userId) return { theme: "light", language: "en" };
const prefs = await db.get(`prefs:${ctx.userId}`);
return prefs ?? { theme: "light", language: "en" };
};
getPreferences.description = "Fetch the current user's preferences";
getPreferences.input = {};
app.tool("get_preferences", getPreferences);You do not need to do anything special to receive ctx — it is always passed as the third argument when a subscriber calls your App.
What ctx.userId is
ctx.userId is typed as string | undefined. It is stable, opaque, and per-server isolated.
Stable means the same subscriber always gets the same ctx.userId on your server, regardless of which device they use, which session they are in, or when they connect. A subscriber who used your server six months ago has the same ctx.userId today.
Per-server isolated means the same subscriber gets a different ctx.userId on each mctx App they use. Your App cannot correlate users with other Apps, and other Apps cannot correlate users with yours. This is a privacy guarantee: subscribers' activity on one App stays isolated from every other App on the platform.
Opaque means you cannot reverse it to obtain any personal information about the subscriber. You can use it as a key to store and retrieve data, but it tells you nothing else.
Optional typing means the TypeScript type is string | undefined. The value is present for all authenticated requests — the dispatch layer validates authentication before your handler is called. Guard your access with an if check or nullish coalescing so your code compiles under strict mode and handles any unexpected edge cases.
Minimum required versions
ctx.userId is available starting with:
@mctx-ai/app>= 1.2.0@mctx-ai/dev>= 1.1.0
Practical example: per-user data storage
The most common use of ctx.userId is as a key for per-user data — preferences, history, state, or anything else you want to scope to a specific subscriber.
import { createServer, T } from "@mctx-ai/app";
const app = createServer();
// In-memory store for this example.
// In production, use a database, KV store, or external API.
const userPrefs = new Map();
const setPreference = async ({ key, value }, ask, ctx) => {
if (!ctx.userId) return "Unable to save preference: no user context.";
const existing = userPrefs.get(ctx.userId) ?? {};
userPrefs.set(ctx.userId, { ...existing, [key]: value });
return `Saved ${key} for your account.`;
};
setPreference.description = "Save a preference for the current user";
setPreference.input = {
key: T.string({ required: true, description: "Preference key" }),
value: T.string({ required: true, description: "Preference value" }),
};
const getPreference = ({ key }, ask, ctx) => {
if (!ctx.userId) return null;
const prefs = userPrefs.get(ctx.userId) ?? {};
return prefs[key] ?? null;
};
getPreference.description = "Retrieve a saved preference for the current user";
getPreference.input = {
key: T.string({ required: true, description: "Preference key" }),
};
app.tool("set_preference", setPreference);
app.tool("get_preference", getPreference);
export default { fetch: app.fetch };Two different subscribers calling get_preference with the same key get their own values back — their ctx.userId values are different, so their data is stored under separate keys.
Using ctx.userId in resources and prompts
ctx is also the third parameter for resource and prompt handlers.
// Resource: return the current user's profile
const myProfile = async (params, ask, ctx) => {
if (!ctx.userId) return JSON.stringify({});
const profile = await db.get(`profile:${ctx.userId}`);
return JSON.stringify(profile ?? { userId: ctx.userId });
};
myProfile.mimeType = "application/json";
app.resource("user://me/profile", myProfile);
// Prompt: personalize a welcome message
const welcomePrompt = async (args, ask, ctx) => {
if (!ctx.userId) return "Welcome! How can I help you today?";
const name = await db.get(`name:${ctx.userId}`);
return `Welcome back${name ? `, ${name}` : ""}! How can I help you today?`;
};
welcomePrompt.description = "Personalized welcome message for the current user";
app.prompt("welcome", welcomePrompt);Where ctx.userId comes from
When a subscriber connects to your App, mctx validates their identity. The dispatch layer derives ctx.userId from the verified subscriber identity and passes it to your handler on every request.
The value is derived, not the raw identity credential itself. This means:
- The same subscriber produces the same
ctx.userIdon your App every time, across all devices and sessions - The same subscriber produces a different
ctx.userIdon each App — your App and other Apps share no common identifier for the same subscriber - You cannot reconstruct the subscriber's underlying identity from it
What ctx does not contain
ctx.userId is the only field on ctx that carries subscriber information. It does not include email addresses, names, subscription details, or any other personal data. If you need to associate additional information with a subscriber, store it yourself using ctx.userId as the key.
Next steps
- Tools, Resources, and Prompts -- the three building blocks that make Apps powerful
- Environment Variables -- store API keys and database URLs your server needs
- Framework API Reference -- every export, type, and option in
@mctx-ai/app
See something wrong? Report it or suggest an improvement -- your feedback helps make these docs better.
Tools, Resources, and Prompts
The three building blocks of every App. Learn what tools, resources, and prompts are through practical examples you can copy and run.
Channel Events
Push real-time notifications into your subscribers' AI sessions using ctx.emit(). Apps can surface events proactively -- no tool call required.