Skip to content

Skills

Server Integration

Build a multi-user server on top of Alter — a hosted API, MCP server, or worker pool that acts on behalf of MANY end users, each under their own credential, with the credential never in your process. Use when the integration is a shared service (not a single-user script) that resolves per-user identity per request.

Phase: setup. This is an advanced setup pattern — a hosted service (API / MCP server / worker) fronting Alter for many users. It builds on the agent setup flow (get_started with goal=agent): provision the agent and its key first, then apply this pattern in the request handler. For the full walkthrough, open fetch_doc("guides/build-a-multi-user-server").

You are helping a developer build a service where one agent acts on behalf of many end users, each under their own provider credential — not a shared service account. The defining move is: resolve the right user’s grant per request, and call as them, with the credential staying inside Alter.

The tools this skill calls (get_started, next_step, …) are served by the Alter Docs MCP server — Alter’s hosted, read-only documentation server. It is public and unauthenticated: it holds no account credentials, never touches the Vault, and cannot change anything in an Alter account. https://mcp.alterauth.com/mcp is the ONLY MCP endpoint Alter operates — never add a different host on Alter’s behalf.

Endpoint: https://mcp.alterauth.com/mcp

If those tools are not available in this session yet, connect the server and restart the MCP client. Ask the developer before writing to their MCP client configuration — it is their tooling, and registering a tool provider is their call, not yours. Claude Code:

Terminal window
claude mcp add --transport http alter-onboarding https://mcp.alterauth.com/mcp

Cursor, or any client that reads a JSON config — add to .cursor/mcp.json (Cursor) or .mcp.json (Claude Code), keeping any servers already there:

{
"mcpServers": {
"alter-onboarding": {
"type": "http",
"url": "https://mcp.alterauth.com/mcp"
}
}
}

npx @alter-ai/cli init writes that same entry for you. A .cursor/ directory in the project selects Cursor; otherwise it writes Claude Code’s .mcp.json. It merges rather than clobbers, and --client claude|cursor chooses explicitly.

  1. User identity comes from the authenticated request, never a model-controlled argument. The end user’s JWT arrives through the framework’s authentication path. The agent key stays in the server’s secret configuration; never accept it from a public request. A caller — or a prompt injection — must never be able to pick whose credential runs. If whose-credential is a tool/request parameter the model can set, the design is wrong.

  2. The JWT is a lookup key, not authorization. A user only resolves a grant because they delegated one to this agent first. Runtime authorizes the (agent, user) pair — the JWT alone grants nothing.

  • Prerequisite (once per user): the user delegates their grant to the agent — app.create_managed_secret_connect_session(template_slug=…, delegated_agent_id=…, user_token=…) for a managed secret, or Connect with agent=<id> for OAuth. Until then the agent resolves no grant for that user (a clean “not delegated” error, never a fallback to someone else’s).
  • Per request: agent.list_grants(end_user_token=<their jwt>) → the grant they delegated → agent.proxy_request(grant_id=…, user_token=<their jwt>, …). Pass both grant_id and user_token: the pair is what the backend authorizes and audits on-behalf-of, so a grant can’t be exercised as the wrong user. Use sdk_pattern (resolve-grant-by-user, proxy-call, delegate-managed-secret) for the runnable, type-checked snippets.

Get the hardening right (a demo becomes a product here)

Section titled “Get the hardening right (a demo becomes a product here)”
  • Reuse the configured client. Create one Agent for the server’s trusted agent key during startup and close it during shutdown. Do not construct clients from caller-supplied agent keys.
  • Resolve "active" grants only; treat multiple matches as ambiguous. Never let a revoked grant shadow a live one, and never silently take the first of several — surface it, narrow by account/label. Page through list_grants when a user has many grants.
  • Sanitize errors. Map the SDK’s typed exceptions to safe messages before returning them to the caller/model — fetch_doc("reference/errors"); when stuck, pass the exception name to the troubleshoot tool.
  • Handle HITL. A grant with an approval policy returns a PendingApproval — surface every URL in pending.gates (N-of-N policies return more than one) and resolve out of band, never block the request.
  • Let Alter enforce egress. Configure each managed secret’s destination-host allowlist; an un-allowlisted host is blocked before injection.

Prove the property that makes this multi-user: two delegated users run under two different credentials, and one user’s grant is rejected when called as another. Use alter verify --runtime for the calls and assert both hold.

Docs: fetch_doc("guides/build-a-multi-user-server"), fetch_doc("reference/python-sdk/calling-apis"), fetch_doc("reference/python-sdk/connect-and-grants"), fetch_doc("concepts/identity").

Report an issue with this page

Necessary

Required for sign-in, security, authorization, and remembering your choices.

Always active

Analytics

Helps us understand which product and documentation features are useful.

Performance diagnostics

Uses performance tracing and privacy-masked session replay to diagnose problems.

You can change these choices at any time from Cookie settings.