Guides and TutorialsModel Context Protocol (MCP)

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.tags or client.name

Without it, the STS has no way to distinguish a legitimate server-side exchange from a client trying to fetch credentials directly.

MCP ClientMCP ServerResource + ClientDescope STSExternal APIInbound — MCP server as Resourcetool call + access tokenvalidate token (aud, scope)Outbound — MCP server as Clienttoken exchange (RFC 8693)authenticates as client_idpolicy eval (user + client context)connection lookupdownstream credentialDownstream API callAPI request + credentialresponsetool result

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.

1 / 2

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 servicePatternWhat the STS returns
Internal API defined as a Descope ResourceSTS → ResourceA Descope JWT scoped to that resource
Third-party OAuth service (Google, Slack, GitHub)STS → ConnectionThe stored OAuth access token for that user or tenant
API key-based serviceSTS → ConnectionThe 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.

Was this helpful?

On this page