Per-User MCP Access
advanced20 minper-user-mcp.ts
One support agent serves many customers. Each must only see and change their own orders, and nobody may read another’s chat.
What it shows
- Authenticate MCP requests with an
MCPAuthFunction; tools readcontext.userId - Clone the agent per user with that user’s MCP client
- Get
THREAD_ACCESS_DENIEDwhen a user opens someone else’s thread
The code
per-user-mcp.ts
import { Agent, Cogitator, CogitatorError, ErrorCode, tool } from '@cogitator-ai/core';
import { MCPClient, MCPServer, type MCPAuthFunction } from '@cogitator-ai/mcp';
import { z } from 'zod';
const apiKey = process.env.GOOGLE_API_KEY;
if (!apiKey) throw new Error('Set GOOGLE_API_KEY');
interface Order {
id: string;
item: string;
status: 'processing' | 'shipped' | 'cancelled';
}
const accessTokens: Record<string, string> = { 'tok-alice-7f3a': 'alice', 'tok-bob-91c2': 'bob' };
const ordersByUser: Record<string, Order[]> = {
alice: [{ id: 'A-1001', item: 'standing desk', status: 'shipped' }],
bob: [{ id: 'B-2001', item: 'mechanical keyboard', status: 'processing' }],
};
const bearerAuth: MCPAuthFunction = (request) => {
const token = request.headers.authorization?.replace(/^Bearer /, '');
const userId = token ? accessTokens[token] : undefined;
return userId ? { userId } : undefined;
};
const listMyOrders = tool({
name: 'list_my_orders',
description: "List the calling customer's orders with their status.",
parameters: z.object({}),
execute: async (_args, context) => ordersByUser[context.userId ?? ''] ?? [],
});
const cancelOrder = tool({
name: 'cancel_order',
description: "Cancel one of the calling customer's orders that has not shipped yet.",
parameters: z.object({ orderId: z.string() }),
execute: async ({ orderId }, context) => {
const order = ordersByUser[context.userId ?? '']?.find((o) => o.id === orderId);
if (!order) throw new Error(`No order ${orderId} for this customer`);
if (order.status !== 'processing') throw new Error(`Order ${orderId} has already shipped`);
order.status = 'cancelled';
return order;
},
});
const shop = new MCPServer({
name: 'shop',
version: '1.0.0',
transport: 'http',
host: '127.0.0.1',
port: 0,
auth: bearerAuth,
});
shop.registerTools([listMyOrders, cancelOrder]);
await shop.start();
const shopUrl = `http://127.0.0.1:${shop.getPort()}/mcp`;
const cog = new Cogitator({
llm: { providers: { google: { apiKey } } },
memory: { adapter: 'memory' },
});
const support = new Agent({
name: 'support',
model: 'google/gemini-3.5-flash-lite',
instructions: "You are a shop's support agent. Use the tools to look up and change orders. Be brief.",
temperature: 0.2,
});
const clients: MCPClient[] = [];
/** The support agent with the shop's tools, acting with this user's own token. */
async function supportFor(token: string): Promise<Agent> {
const client = await MCPClient.connect({
transport: 'http',
url: shopUrl,
headers: { Authorization: `Bearer ${token}` },
});
clients.push(client);
return support.clone({ id: support.id, tools: await client.getTools() });
}
try {
const forAlice = await supportFor('tok-alice-7f3a');
const forBob = await supportFor('tok-bob-91c2');
const alice = await cog.run(forAlice, { input: 'What did I order?', userId: 'alice', threadId: 'chat-alice' });
console.log('Alice:', alice.output);
const cancel = await cog.run(forBob, {
input: 'Please cancel the order that has not shipped yet.',
userId: 'bob',
threadId: 'chat-bob',
});
console.log('Bob:', cancel.output, `(B-2001 is now ${ordersByUser.bob[0].status})`);
try {
await cog.run(forBob, { input: 'What did we talk about?', userId: 'bob', threadId: 'chat-alice' });
} catch (error) {
if (!(error instanceof CogitatorError && error.code === ErrorCode.THREAD_ACCESS_DENIED)) throw error;
console.log(`Refused: ${error.message} (HTTP ${error.statusCode})`);
}
} finally {
await Promise.all(clients.map((client) => client.close()));
await shop.stop();
await cog.close();
}Run it
install · bash
pnpm add @cogitator-ai/core @cogitator-ai/mcp zodEnvironment
GOOGLE_API_KEYrun · bash
GOOGLE_API_KEY=your-key npx tsx per-user-mcp.tsOr run the full example from a clone of the repository (keys in .env):
repository · bash
npx tsx examples/mcp/03-per-user-mcp.ts