Calling External APIs from MCP Tools
When an MCP tool needs to call an authenticated downstream API — whether that's a third-party service like Google or Salesforce, or an internal API not directly exposed as a Descope Resource — your MCP server needs credentials scoped to that service, not the inbound token it received from the MCP client.
This page explains how to set that up.
The Dual Role Problem
Your MCP server is already defined as a Resource in Descope. That handles the inbound side: the MCP client's access token carries your MCP server's URL in the aud claim, and your server validates it.
But fetching downstream credentials requires a second role. To exchange the inbound user token for a credential scoped to a downstream service, your MCP server needs to authenticate to the Descope STS as a Client — with its own client_id and client_secret.
This is not redundant. The client ID serves a specific purpose: it tells the STS which system is making the exchange, so that:
- Policy can be evaluated against the full context — the original user, the MCP client, and your MCP server
- The audit log records the complete delegation chain: user → MCP client → MCP server → downstream service
- Policies can target the MCP server specifically using
client.tagsorclient.name
Without it, the STS has no way to distinguish a legitimate server-side exchange from a client trying to fetch credentials directly.
The MCP server plays two roles in one request. As a Resource, it validates the inbound token from the MCP client. As a Client, it authenticates to Descope STS using its own client credentials to exchange that token for a downstream credential.
Policy is evaluated against the full context at exchange time: the original user, the MCP client that initiated the request, and the MCP server making the exchange. All three appear in the audit log.
Setup
1. Register a Client for Your MCP Server
In the Descope Console, go to Clients and create a new client to represent your MCP server.
- Enable the Client Credentials grant type
- Disable any grant types the MCP server will not use
- Copy the generated Client ID and Client Secret
This client is separate from the MCP clients (Claude, Cursor, your users' agents) that connect to your server. It represents the server itself acting as an OAuth client toward the STS.
2. Configure Your MCP Server with the Credentials
Store the client ID and secret securely in your MCP server's environment. These are the credentials it will use to authenticate token exchange requests.
3. Define What the MCP Server Can Reach
Depending on which downstream services your tools call, configure either:
- A Descope Resource for internal APIs that can validate Descope tokens directly — the STS will issue a resource-scoped token
- A Connection for third-party services (Google, Slack, GitHub, etc.) or any service that requires its own OAuth token or API key — the STS will look up and return the stored credential
4. Create a Policy (if needed)
If you want to restrict which users or MCP clients can trigger downstream credential access, create a Policy targeting the relevant Connection or Resource.
The policy is evaluated at exchange time against the user associated with the original inbound token and the client (MCP Server) context.
Making the Exchange
At tool execution time, your MCP server calls the token endpoint with:
POST /oauth2/v1/token
Authorization: Basic {base64(client_id:client_secret)}
Content-Type: application/x-www-form-urlencoded
grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&subject_token={inbound_access_token}
&subject_token_type=urn:ietf:params:oauth:token-type:access_token
&resource={resource_url_or_connection_id}Descope validates the server's credentials, evaluates policy against the inbound token's claims, and returns either a resource-scoped Descope JWT or a stored credential from Connections.
The Descope Python MCP SDK handles this exchange automatically when you use the connection token retrieval helpers.
Which Pattern to Use
| Downstream service | Pattern | What the STS returns |
|---|---|---|
| Internal API defined as a Descope Resource | STS → Resource | A Descope JWT scoped to that resource |
| Third-party OAuth service (Google, Slack, GitHub) | STS → Connection | The stored OAuth access token for that user or tenant |
| API key-based service | STS → Connection | The stored API key for that user or tenant |
Both patterns use the same token exchange request. The resource parameter determines which path the STS takes.
What Shows Up in Audit Logs
Every exchange produces an audit event that records:
- The original user identity from the inbound token
- The MCP client that initiated the tool call
- The MCP server client ID that made the exchange request
- The downstream resource or connection accessed
- The policy decision applied
This gives you a complete, traceable record of every downstream credential access — from the user who authorized it, through the agent that requested it, to the server that fetched it.