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_startedwith goal=agent): provision the agent and its key first, then apply this pattern in the request handler. For the full walkthrough, openfetch_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.
Connect this server first
Section titled “Connect this server first”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:
claude mcp add --transport http alter-onboarding https://mcp.alterauth.com/mcpCursor, 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.
The two rules that make it safe
Section titled “The two rules that make it safe”-
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.
-
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.
The shape
Section titled “The shape”- 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 withagent=<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 bothgrant_idanduser_token: the pair is what the backend authorizes and audits on-behalf-of, so a grant can’t be exercised as the wrong user. Usesdk_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
Agentfor 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 byaccount/label. Page throughlist_grantswhen 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 thetroubleshoottool. - Handle HITL. A grant with an approval policy returns a
PendingApproval— surface every URL inpending.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.
Verify per-user isolation
Section titled “Verify per-user isolation”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").