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:

  1. Signs your agent in to Descope and gets a Descope token.
  2. 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-auth
npm install @descope/agent-auth

The 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…MethodToken
Call your own API or MCP server, protected by Descope as the authorization serverresources.get_tokenResource token, minted on demand via token-exchange
Call a third-party OAuth provider (GitHub, Slack, Google)connections.get_tokenprovider OAuth token from the Connections vault
Call a third-party or internal API with a stored API keyconnections.get_tokenAPI 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 yet
import { 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.

ProviderUse when
ClientCredentialsProviderAutonomous agent, no user (M2M)
AccessTokenProviderYou already hold a user's Descope token from your app's login
DeviceCodeProviderHeadless or CLI agent with no browser
CibaProviderOut-of-band user approval (push), and the approval gate for sensitive calls
JwtBearerProviderExchange a signed JWT from a trusted issuer (RFC 7523)
ManagementKeyProviderPrivileged 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-shared
import { 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-shared

Acting 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).

ErrorMeaning
ConnectionAuthorizationRequiredThe user hasn't connected this account. Carries connect_url / connectUrl, connection, and identifier.
PolicyDeniedThe credential lacks policy permission for the requested token.
ApprovalDenied / ApprovalTimeoutA CIBA approval gate was rejected or timed out.
CredentialAcquisitionFailedThe agent could not sign in to Descope.
TokenExchangeFailedAny 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.

Was this helpful?

On this page