Search

Search the documentation

Protobox
Use cases

Per-user MCP sessions

Mint a scoped, revocable MCP endpoint for each of your end-users or conversations — one URL that serves exactly the tools that user may use, with their credentials.

Scenario: Acme's assistant runtime speaks MCP. Instead of wiring one MCP client per app per user, Acme mints one session per conversation: a single MCP URL scoped to the merged, curated tool surface for that user — Gmail, HubSpot, and Acme's own private connector alike — and dies when the conversation ends. (What tools/list literally serves on that URL depends on the preset — see below.)

This is also the shape for "bring your own agent": your power users paste a personal MCP URL into Claude Desktop or Cursor, scoped to exactly what they connected.

Mint a session

const session = await protobox.sessions.create({
  userId: 'user_123',        // your end-user id
  toolsetSlug: 'support',    // optional: restrict to one curated toolset
});

// session.token is returned ONCE — store it server-side, never log it.
// session.mcpUrl is the endpoint to hand to the agent.

Two serving presets decide what tools/list returns on the URL:

  • 'meta' (the default) — three meta-tools (search_actions, get_action_schema, execute_action) that search and execute across the scope. Scales past ~40 tools; the toolset still bounds what execute_action may run.
  • 'direct' — the toolset's tools, flat: what most MCP clients expect for a small curated surface. Requires SDK ≥ 2.3.0:
const session = await protobox.sessions.create({
  userId: 'user_123',
  toolsetSlug: 'support',
  preset: 'direct',          // tools/list serves the toolset flat
});

Wire any MCP-capable runtime to it:

{
  "mcpServers": {
    "acme": {
      "url": "<session.mcpUrl>",
      "headers": { "Authorization": "Bearer <session.token>" }
    }
  }
}

get and list never return the token again. If it's lost, revoke the session and mint a new one.

Scope with toolsets

toolsetSlug restricts the session to one curated selection — the right tools, your names, your policies. A session with no toolset serves the user's full connected surface. Curate first, scope always: an agent that can see 200 tools is slower and riskier than one that sees 8.

Sessions are live-rebindable — update the binding and the MCP endpoint picks it up on the next call, no re-mint:

await protobox.sessions.update(session.id, { toolset: 'support-tier-2' });

The same session, without an MCP client

Backend code that already has the session doesn't need to speak MCP — a session handle exposes the same scoped tools over plain method calls:

const handle = await protobox.sessions.use(session.id);

const tools = await handle.tools({ format: 'anthropic' }); // or openai/vercel/langchain/google-genai/raw
const result = await handle.execute('HUBSPOT_SEARCHCONTACTS', {
  arguments: { query: 'initech.com' },
});

execute addresses tools by their served (possibly overridden) names — the same names the model sees — and returns a structured result (completed, pending_approval, or failed). One session, two transports, identical tools, policies, and traces.

Connect-from-chat

When a tool fails because the user never connected the app, drive the fix from inside the conversation:

const { redirectUrl } = await handle.authorize('hubspot');
// send redirectUrl to the user: "Connect HubSpot to continue"

Lifecycle

MomentCall
Conversation startssessions.create({ userId, toolsetSlug })
User signs out / conversation endssessions.revoke(id) — kills the URL immediately
Audit "what did this agent do?"sessions.listEvents(id) — see below
Find a user's sessionssessions.list({ search: 'user_123' }) — never returns tokens
// Offboarding a user: revoke everything they hold
const sessions = await protobox.sessions.list({ search: 'user_123' });
await Promise.all(sessions.map((s) => protobox.sessions.revoke(s.id)));

Activity on the session

Every search, execution, and connection event is recorded for 90 days:

let cursor: string | undefined;
do {
  const page = await protobox.sessions.listEvents(session.id, { cursor, limit: 100 });
  for (const e of page.items) {
    console.log(e.createdAt, e.type, e.tool, e.status, e.latencyMs);
  }
  cursor = page.nextCursor;
} while (cursor);

sessions.get(id) additionally returns a server-computed summary — tool calls, errors, toolkits used — handy for a per-conversation debug panel.

Production notes

  • One session per conversation beats one long-lived session per user: revocation is scoped, traces map 1:1 to conversations, and a leaked token has a short blast radius.
  • Store the token next to your conversation record, encrypted at rest, and treat it like a password.
  • Sessions are for identified callers. For an endpoint your customers configure once in their own AI clients — durable, named, key- or OAuth-guarded — publish a server instead: Publish an MCP server.

On this page