Agent Auth SDK
The Descope Agent Auth SDK is a client-side SDK, in Python and TypeScript, that does two things for a custom agent:
- Signs your agent in to Descope and gets a Descope token.
- Fetches the tokens it needs, a Connection token or a Resource token, whenever the agent calls an API or MCP server.
It is the identity layer under your agent's tool calls. You still write the tool code and API wrappers; the SDK gets each tool the token it needs, and Descope stores and refreshes those tokens so your code never holds a long-lived secret.
Building an MCP server instead?
This SDK is the OAuth client side, fetching the tokens an agent's tools need. To protect an MCP server you built (client registration, token validation, tool scopes), use Descope MCP Auth, covered in MCP Servers and the MCP SDKs.
Inside a server's tool handler, use this SDK to fetch the downstream token the tool needs.
Install
pip install descope-agent-authnpm install @descope/agent-authThe two packages are kept identical, so the mental model transfers. TypeScript names are the camelCase of the Python ones.
Two kinds of token
Everything the SDK fetches is one of two kinds. The distinction mirrors Resources vs. Connections in the Agentic Identity Hub.
| Your agent needs to⦠| Method | Token |
|---|---|---|
| Call your own API or MCP server, protected by Descope as the authorization server | resources.get_token | Resource token, minted on demand via token-exchange |
| Call a third-party OAuth provider (GitHub, Slack, Google) | connections.get_token | provider OAuth token from the Connections vault |
| Call a third-party or internal API with a stored API key | connections.get_token | API key from the Connections vault |
A Resource token needs no provisioning: it is minted on demand for the agent. A Connection token must already exist in the vault, put there either when the user connected the account (OAuth consent) or when your backend wrote an API key through the Management API.
Quickstart
Sign the agent in once, then fetch tokens whenever a tool runs. If the user has not connected the account yet, get_token raises ConnectionAuthorizationRequired, which carries a connect URL to send them through.
from descope_agent_auth import AgentAuthClient, AccessTokenProvider
from descope_agent_auth.errors import ConnectionAuthorizationRequired
client = AgentAuthClient(
project_id="P2...",
credential=AccessTokenProvider(access_token=user_jwt),
)
try:
github = client.connections.get_token(connection="github", identifier="user@example.com")
use(github.access_token) # a fresh, scoped GitHub token
except ConnectionAuthorizationRequired as e:
redirect_user_to(e.connect_url) # the user hasn't linked GitHub yetimport { AgentAuthClient, AccessTokenProvider, ConnectionAuthorizationRequired } from '@descope/agent-auth';
const client = new AgentAuthClient({
projectId: 'P2...',
credential: new AccessTokenProvider({ accessToken: userJwt }),
});
try {
const github = await client.connections.getToken({ connection: 'github', identifier: 'user@example.com' });
use(github.accessToken); // a fresh, scoped GitHub token
} catch (e) {
if (e instanceof ConnectionAuthorizationRequired) {
redirectUserTo(e.connectUrl); // the user hasn't linked GitHub yet
} else {
throw e;
}
}How your agent signs in
Pick how the agent authenticates to Descope, configured once at client init by passing a credential. When the agent signs in with OAuth client credentials it becomes a first-class identity in your Agent Directory, and policies govern what its token can obtain.
| Provider | Use when |
|---|---|
ClientCredentialsProvider | Autonomous agent, no user (M2M) |
AccessTokenProvider | You already hold a user's Descope token from your app's login |
DeviceCodeProvider | Headless or CLI agent with no browser |
CibaProvider | Out-of-band user approval (push), and the approval gate for sensitive calls |
JwtBearerProvider | Exchange a signed JWT from a trusted issuer (RFC 7523) |
ManagementKeyProvider | Privileged static key that bypasses policies; not recommended, requires explicit opt-in |
Note
A client-credentials (M2M) sign-in cannot read a user-level Connection token. To read a user's token, sign in with that user's access token (AccessTokenProvider, DeviceCodeProvider, or CibaProvider) or use a management key.
Autonomous vs. Acting for a User
Autonomous: acts as itself. With client credentials the agent mints Resource tokens scoped to itself and reads tenant-level Connection tokens for a tenant it belongs to.
from descope_agent_auth import AgentAuthClient, ClientCredentialsProvider
client = AgentAuthClient(
project_id="P2...",
credential=ClientCredentialsProvider(client_id="...", client_secret="..."),
)
res = client.resources.get_token(resource="urn:my-api", scopes=["read"]) # agent-scoped
slack = client.connections.get_tenant_token(connection="slack", tenant_id="acme") # org-sharedimport { AgentAuthClient, ClientCredentialsProvider } from '@descope/agent-auth';
const client = new AgentAuthClient({
projectId: 'P2...',
credential: new ClientCredentialsProvider({ clientId: '...', clientSecret: '...' }),
});
const res = await client.resources.getToken({ resource: 'urn:my-api', scopes: ['read'] }); // agent-scoped
const slack = await client.connections.getTenantToken({ connection: 'slack', tenantId: 'acme' }); // org-sharedActing for a user. Supply the user's access token to read that user's Connection tokens or mint a user-scoped Resource token. Bind it at init, or pass it per call with act_as_user_token on a shared client.
from descope_agent_auth import AgentAuthClient, AccessTokenProvider
client = AgentAuthClient(project_id="P2...", credential=AccessTokenProvider(access_token=user_jwt))
gh = client.connections.get_token(connection="github", identifier=user_id)
# or per call on a shared client:
gh = client.connections.get_token(connection="github", identifier=user_id, act_as_user_token=user_jwt)import { AgentAuthClient, AccessTokenProvider } from '@descope/agent-auth';
const client = new AgentAuthClient({ projectId: 'P2...', credential: new AccessTokenProvider({ accessToken: userJwt }) });
await client.connections.getToken({ connection: 'github', identifier: userId });
// or per call on a shared client:
await client.connections.getToken({ connection: 'github', identifier: userId, actAsUserToken: userJwt });Connect to a Resource
Use resources.get_token for an API or MCP server that you build and protect with Descope as the authorization server. The token is minted via token-exchange with no prior authorization step; what you signed in with sets the scope (a user token is user-scoped, client credentials are agent-scoped).
res = client.resources.get_token(
resource="urn:my-api", # RFC 8707 resource indicator
scopes=["orders.read"],
audience="https://api.example.com", # optional RFC 8693 audience
)
call_my_api(res.access_token)const res = await client.resources.getToken({
resource: 'urn:my-api', // RFC 8707 resource indicator
scopes: ['orders.read'],
audience: 'https://api.example.com', // optional RFC 8693 audience
});
callMyApi(res.accessToken);Connect to a Connection
Use connections.get_token (user-level) or connections.get_tenant_token (org-shared) for a credential in the Connections vault. The credential has to exist first, created when the user completes OAuth consent or when your backend writes an API key through the Management API.
When the user has not connected yet, catch ConnectionAuthorizationRequired and send them to its connect_url, or generate the URL proactively with get_connect_url and poll with wait_for_connection.
url = client.connections.get_connect_url(connection="github", identifier=user_id)
if url:
redirect_user_to(url)
github = client.connections.wait_for_connection(connection="github", identifier=user_id)const url = await client.connections.getConnectUrl({ connection: 'github', identifier: userId });
if (url) {
redirectUserTo(url);
const github = await client.connections.waitForConnection({ connection: 'github', identifier: userId });
}Errors
All errors extend AgentAuthError; match them with isinstance (Python) or instanceof (TypeScript).
| Error | Meaning |
|---|---|
ConnectionAuthorizationRequired | The user hasn't connected this account. Carries connect_url / connectUrl, connection, and identifier. |
PolicyDenied | The credential lacks policy permission for the requested token. |
ApprovalDenied / ApprovalTimeout | A CIBA approval gate was rejected or timed out. |
CredentialAcquisitionFailed | The agent could not sign in to Descope. |
TokenExchangeFailed | Any other token-fetch failure. |
Framework support
The SDK is framework-agnostic and fetches tokens for the tools you implement in any framework: LangChain, LangGraph, Google ADK, OpenAI, Vercel AI, Mastra, LlamaIndex, Cloudflare Agents, CrewAI, and the Anthropic SDK, among others. A with_connection (withConnection) tool wrapper injects a fresh scoped token into a tool call. Descope can also manage an agent's connection to a remote MCP server through the SDK's MCP auth adapter.
See docs/FRAMEWORKS.md in the repo for a copy-paste snippet per framework.
Related
- Descope Agent Auth SDK on GitHub: source, examples, and the full API reference
- Resources: the APIs and MCP servers a Resource token is minted for
- Connections: the credential vault a Connection token comes from
- Agentic Identity: the agent directory a client-credentials sign-in appears in
- Agent Authorization: the policies that gate which tokens an agent can obtain
Multi-Tenancy with Connections
Learn how user and tenant-scoped connection tokens work, including tenant-associated user tokens and tenant-level shared tokens.
Enterprise-Managed Authorization
Use Descope for enterprise-managed authorization (ID-JAG) to govern the agents you run or let customers manage access to MCP servers you sell.