For the complete documentation index, see llms.txt. Markdown versions of all docs pages are available by appending .md to any docs URL.
Descope
Protect MCP servers with Descope as the authorization server.
Descope is an authentication and user management platform. Agentgateway includes a native descope MCP authentication provider so that you can use a Descope MCP Server as the authorization server for your MCP servers.
In this guide, you create a project and an MCP Server in Descope, protect a sample MCP server with the descope provider, and verify that agentgateway rejects unauthenticated requests and admits tokens that Descope issues.
Why the Descope provider is needed
Descope publishes signing keys at the project level rather than under the agentic issuer that your MCP Server exposes, and its Dynamic Client Registration (DCR) endpoint sits on a separate management path.
When you set provider.descope, agentgateway bridges these gaps as follows:
- Rewrites an agentic issuer of the form
https://api.descope.com/v1/apps/agentic/<project-id>/<server-id>to the project-level JWKS URLhttps://api.descope.com/<project-id>/.well-known/jwks.json. - Serves authorization server metadata from Descope’s OpenID Connect discovery document, because Descope does not support the RFC 8414 path-based issuer format.
- Proxies Dynamic Client Registration through the gateway, deriving the management endpoint from the agentic issuer. You verify this in Step 3.
Descope supports RFC 8707 resource indicators, so no audience workaround is needed.
For the underlying mcpAuthentication fields, see MCP authentication.
Before you begin
- Install the agentgateway binary.
- Install Node.js so that
npxcan run the sample MCP server. - Make sure that you have access to the Descope Console and permission to create an MCP Server and a Client.
Step 1: Set up Descope
Create a project and an MCP Server in Descope, then collect the values that agentgateway needs.
Create a project in the Descope Console, and note your Project ID from Project Settings. The project ID appears in both your issuer URL and your JWKS URL.
Create an MCP Server to represent your MCP gateway. Set the MCP Server URL to the public URL that agentgateway exposes, typically ending with
/mcp. For this guide, usehttp://localhost:3000/mcp. Define the scopes that your server enforces, such asread:all.The MCP Server includes a built-in User Consent Flow for interactive, user-delegated access. Customize it under Server Settings if you need a different login or consent experience.
Copy the Issuer URL and the Token Endpoint from the MCP Server’s Connection Information section.
Create a Client and note the generated Client ID. Enable the Client Credentials grant type on it and note the Client Secret. You use this Client to request a token from the command line in Step 4, which keeps the verification steps scriptable.
Assign the
Tenant Adminrole to the Client. You use this role in Step 5.Save the values that the rest of this guide uses.
export DESCOPE_PROJECT_ID='<your-project-id>' export DESCOPE_SERVER_ID='<your-mcp-server-id>' export DESCOPE_ISSUER="https://api.descope.com/v1/apps/agentic/${DESCOPE_PROJECT_ID}/${DESCOPE_SERVER_ID}" export DESCOPE_TOKEN_ENDPOINT='<your-token-endpoint>' export DESCOPE_CLIENT_ID='<your-client-id>' export DESCOPE_CLIENT_SECRET='<your-client-secret>' export DESCOPE_MCP_URL='http://localhost:3000/mcp'Variable Where to find it DESCOPE_PROJECT_IDProject Settings in the Descope Console. DESCOPE_SERVER_IDThe last path segment of the Issuer URL in Connection Information. DESCOPE_ISSUERDerived from the two IDs. It must match the Issuer URL shown in Connection Information. DESCOPE_TOKEN_ENDPOINTConnection Information on your MCP Server. DESCOPE_CLIENT_IDandDESCOPE_CLIENT_SECRETThe Client that you created in step 4. DESCOPE_MCP_URLYour MCP Server URL. Tokens carry it in the audclaim, so it must match what you set in step 2.Agentgateway expands
${...}references when it loads a configuration file, so the same variables also fill in theconfig.yamlthat you create next. If a variable is unset, agentgateway exits withenvironment variable not foundrather than starting with a broken configuration.
Step 2: Configure and start agentgateway
Create a
config.yamlfile that exposes a sample MCP server on port 3000 and protects it with thedescopeprovider.Review the following table to understand this configuration.# yaml-language-server: $schema=https://agentgateway.dev/schema/config mcp: port: 3000 policies: cors: allowOrigins: ["*"] allowHeaders: ["*"] exposeHeaders: ["Mcp-Session-Id"] mcpAuthentication: mode: strict issuer: ${DESCOPE_ISSUER} audiences: - ${DESCOPE_MCP_URL} provider: descope: {} clientId: ${DESCOPE_CLIENT_ID} resourceMetadata: resource: ${DESCOPE_MCP_URL} scopesSupported: - read:all bearerMethodsSupported: - header targets: - name: everything stdio: cmd: npx args: ["@modelcontextprotocol/server-everything"]Setting Description issuerThe full issuer URL from the Connection Information section of your MCP Server. Descope agentic issuers take the form https://api.descope.com/v1/apps/agentic/<project-id>/<server-id>.audiencesYour MCP server’s public URL. This value must match the audclaim in Descope-issued tokens, which equals your MCP server’s resource URL.provider.descopeEnables the Descope-specific behavior described in Why the Descope provider is needed. Takes no fields. clientIdA pre-registered Client ID. Descope’s Dynamic Client Registration endpoint requires a management key that MCP clients do not have, so agentgateway answers registration requests with this pre-registered client instead. To let clients register dynamically through Descope, omit clientIdand use CIMD instead.resourceMetadataThe protected resource metadata that agentgateway serves to MCP clients, which you inspect in Step 3. jwksOptional. Because provider.descopeis set, agentgateway rewrites the agentic issuer to the project-level JWKS URL. To fetch keys from somewhere else, setjwksexplicitly to override the derived URL.Start agentgateway.
agentgateway -f config.yamlExample output:
info state_manager loaded config from File("config.yaml") info app serving UI at http://localhost:15000/ui info proxy::gateway started bind bind="bind/3000"
Step 3: Verify that unauthenticated requests are rejected
Agentgateway runs in the foreground, so run the following commands in another terminal.
Send an MCP
initializerequest without a token.curl -i -X POST http://localhost:3000/mcp \ -H 'content-type: application/json' \ -H 'accept: application/json, text/event-stream' \ -d '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}},"id":1}'Agentgateway returns
401with aWWW-Authenticateheader that points MCP clients at the protected resource metadata.HTTP/1.1 401 Unauthorized www-authenticate: Bearer resource_metadata="http://localhost:3000/.well-known/oauth-protected-resource/mcp" {"error":"unauthorized","error_description":"JWT token required"}Follow that pointer to see the metadata that the gateway serves.
curl -s http://localhost:3000/.well-known/oauth-protected-resource/mcpExample output:
{"resource":"http://localhost:3000/mcp","authorization_servers":["http://localhost:3000/mcp"],"mcp_protocol_version":"2025-06-18","resource_type":"mcp-server","bearer_methods_supported":["header"],"scopes_supported":["read:all"]}Confirm that agentgateway advertises its own registration endpoint rather than Descope’s management path.
curl -s http://localhost:3000/.well-known/oauth-authorization-serverThe
registration_endpointpoints back at the gateway, while the other endpoints point at Descope.... "registration_endpoint": "http://localhost:3000/.well-known/oauth-authorization-server/client-registration", "token_endpoint": "https://api.descope.com/oauth2/v1/apps/token",Register a client through that endpoint. Because
clientIdis set, agentgateway answers with your pre-registered Client instead of calling Descope’s management API, which would require a management key that MCP clients do not have.curl -s -X POST http://localhost:3000/.well-known/oauth-authorization-server/client-registration \ -H 'content-type: application/json' \ -d '{"client_name":"mcp-inspector","redirect_uris":["http://localhost:6274/oauth/callback"],"grant_types":["authorization_code"],"response_types":["code"],"token_endpoint_auth_method":"none"}'The response carries the
clientIdfrom your configuration.... "client_id":"<your-client-id>","token_endpoint_auth_method":"none"
Step 4: Call the MCP server with a token
How a client gets a token depends on whether it acts for a user or on its own behalf.
Interactive MCP clients such as Claude or Cursor handle this automatically with the OAuth 2.1 authorization code flow and PKCE. The client discovers your MCP Server’s OAuth endpoints, registers through CIMD or DCR, and redirects the user through Descope’s User Consent Flow to approve scopes. No manual token request is needed.
For backend agents, scripts, or testing without an interactive client, exchange Client credentials directly for a token with the client credentials flow.
Request a token. The
resourceparameter is Descope’s RFC 8707 resource indicator, which targets the token’saudclaim at your MCP server.export TOKEN="$(curl -s -X POST "${DESCOPE_TOKEN_ENDPOINT}" \ -H 'content-type: application/x-www-form-urlencoded' \ -d grant_type=client_credentials \ -d "client_id=${DESCOPE_CLIENT_ID}" \ -d "client_secret=${DESCOPE_CLIENT_SECRET}" \ -d "scope=openid read:all" \ -d "resource=${DESCOPE_MCP_URL}" \ | jq -r .access_token)"Send the token as a bearer token.
curl -i -X POST http://localhost:3000/mcp \ -H "authorization: Bearer ${TOKEN}" \ -H 'content-type: application/json' \ -H 'accept: application/json, text/event-stream' \ -d '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}},"id":1}'Agentgateway resolves the project-level JWKS URL from your agentic issuer, validates the token, and returns the MCP server’s response.
HTTP/1.1 200 OK content-type: text/event-stream mcp-session-id: 0511047b-3f97-4dcf-9fec-4457b4c3c229 event: message data: {"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05", ... ,"serverInfo":{"name":"mcp-servers/everything","title":"Everything Reference Server","version":"2.0.0"}}}
Step 5: Restrict access by role
Because MCP authentication runs at the route level, you can use claims from the validated Descope token in an authorization policy.
Add an
authorizationpolicy alongsidemcpAuthenticationin yourconfig.yamlthat requires theTenant Adminrole.policies: mcpAuthentication: mode: strict issuer: ${DESCOPE_ISSUER} audiences: - ${DESCOPE_MCP_URL} provider: descope: {} clientId: ${DESCOPE_CLIENT_ID} resourceMetadata: resource: ${DESCOPE_MCP_URL} scopesSupported: - read:all bearerMethodsSupported: - header authorization: rules: # Check for a specific Descope role - '"Tenant Admin" in jwt.roles'Note
Where the roles claim appears depends on your Authorization Claims Configuration. With the default Descope JWT, roles are in
jwt.tenants["<YOUR TENANT ID>"].roles. With the No Tenant Reference claim format, roles are injwt.roles, which is what this rule uses.Restart agentgateway to apply the policy. Because the Client holds the
Tenant Adminrole, the request from Step 4 still succeeds.agentgateway -f config.yamlTo confirm that the rule is enforced, create another Client in Descope without the
Tenant Adminrole. Save its credentials.export DESCOPE_UNAUTHORIZED_CLIENT_ID='<second-client-id>' export DESCOPE_UNAUTHORIZED_CLIENT_SECRET='<second-client-secret>'Request a token with that Client and repeat the request.
export NO_ROLE_TOKEN="$(curl -s -X POST "${DESCOPE_TOKEN_ENDPOINT}" \ -H 'content-type: application/x-www-form-urlencoded' \ -d grant_type=client_credentials \ -d "client_id=${DESCOPE_UNAUTHORIZED_CLIENT_ID}" \ -d "client_secret=${DESCOPE_UNAUTHORIZED_CLIENT_SECRET}" \ -d "scope=openid read:all" \ -d "resource=${DESCOPE_MCP_URL}" \ | jq -r .access_token)" curl -s -o /dev/null -w '%{http_code}\n' -X POST http://localhost:3000/mcp \ -H "authorization: Bearer ${NO_ROLE_TOKEN}" \ -H 'content-type: application/json' \ -H 'accept: application/json, text/event-stream' \ -d '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}},"id":1}'The token is valid, so authentication succeeds, but the authorization rule denies the request with
403.403
Connect an MCP client
Point your MCP client at the gateway’s MCP endpoint, http://localhost:3000/mcp. The client discovers the authorization server through the gateway, registers against the pre-registered client in clientId as you verified in Step 3, and redirects the user to Descope to log in and consent.