Skip to content

XSUAA OAuth for MCP-Native Clients

This guide sets up BTP XSUAA authentication so MCP-native clients (Claude Desktop, Cursor, VS Code, MCP Inspector) can authenticate via OAuth when connecting to ARC-1.

Overview

MCP-native clients use RFC 8414 OAuth discovery to find authorization endpoints at the MCP server's URL. ARC-1 proxies the OAuth flow to XSUAA using the MCP SDK's ProxyOAuthServerProvider.

Auth flow: 1. Client discovers OAuth via /.well-known/oauth-authorization-server 2. Client redirects user to ARC-1's /authorize endpoint 3. ARC-1 proxies to XSUAA's login page 4. After login, XSUAA returns authorization code 5. Client exchanges code for token via ARC-1's /token endpoint 6. Client sends Bearer token with MCP requests

Coexistence: XSUAA OAuth coexists with API key and Entra ID OIDC auth. All three methods work on the same /mcp endpoint via a chained token verifier.

Prerequisites

  • SAP BTP Cloud Foundry account with XSUAA entitlement
  • CF CLI installed and logged in
  • ARC-1 deployed on BTP CF (see BTP Cloud Foundry deployment)

Step 1: Create XSUAA Service Instance

The xs-security.json file defines scopes, roles, and OAuth configuration:

cf create-service xsuaa application arc1-xsuaa -c xs-security.json

The included xs-security.json defines:

Scope Description Tools
read Read SAP objects SAPRead, SAPSearch, SAPNavigate, SAPContext, SAPLint, SAPDiagnose
write Write SAP objects SAPWrite, SAPActivate, SAPManage, SAPTransport
data Preview named table contents SAPQuery (table preview)
sql Execute freestyle SQL queries SAPQuery (freestyle SQL)
admin Administrative access System management
Role Collection Scopes Use Case
ARC-1 Viewer read Read-only SAP access
ARC-1 Developer read, write Development access
ARC-1 Data Viewer read, data Read-only with table preview
ARC-1 Developer + Data read, write, data Development + table preview
ARC-1 Developer + SQL read, write, data, sql Development + freestyle SQL
ARC-1 Admin read, write, data, sql, admin Full administrative access

Step 2: Bind Service and Configure

# Bind XSUAA to your app
cf bind-service arc1-mcp-server arc1-xsuaa

# Enable XSUAA auth
cf set-env arc1-mcp-server SAP_XSUAA_AUTH true

# Restage to pick up changes
cf restage arc1-mcp-server

Verify XSUAA is active in the logs:

cf logs arc1-mcp-server --recent | grep XSUAA
# Should show:
# INFO: XSUAA credentials loaded {"xsappname":"arc1-mcp!t..."}
# INFO: XSUAA OAuth proxy enabled {"xsappname":"arc1-mcp!t..."}
# INFO: ARC-1 HTTP server started {"auth":"XSUAA OAuth proxy"}

Step 3: Assign Role Collections

  1. Open BTP CockpitSecurityRole Collections
  2. Find "ARC-1 Viewer" / "ARC-1 Editor" / "ARC-1 Admin"
  3. Click the role collection → EditUsers tab
  4. Add your BTP user (email address)
  5. Save

Step 4: Verify OAuth Discovery

curl -s https://arc1-mcp-server.cfapps.us10-001.hana.ondemand.com/.well-known/oauth-authorization-server | jq .

Expected response:

{
  "issuer": "https://arc1-mcp-server.cfapps.us10-001.hana.ondemand.com/",
  "authorization_endpoint": "https://arc1-mcp-server.cfapps.us10-001.hana.ondemand.com/authorize",
  "token_endpoint": "https://arc1-mcp-server.cfapps.us10-001.hana.ondemand.com/token",
  "scopes_supported": ["read", "write", "admin"],
  "response_types_supported": ["code"],
  "code_challenge_methods_supported": ["S256"],
  "grant_types_supported": ["authorization_code", "refresh_token"]
}

Step 5: Configure MCP Clients

Claude Desktop

Add to claude_desktop_config.json:

{
  "mcpServers": {
    "arc1-sap": {
      "url": "https://arc1-mcp-server.cfapps.us10-001.hana.ondemand.com/mcp"
    }
  }
}

Claude Desktop will automatically discover OAuth via /.well-known/oauth-authorization-server and prompt for login.

Cursor

In Cursor settings → MCP Servers, add:

{
  "arc1-sap": {
    "url": "https://arc1-mcp-server.cfapps.us10-001.hana.ondemand.com/mcp"
  }
}

MCP Inspector

Connect to:

https://arc1-mcp-server.cfapps.us10-001.hana.ondemand.com/mcp

The inspector will perform OAuth discovery and redirect to XSUAA login.

Note: MCP Inspector may use http://127.0.0.1:6274 as its callback URL. ARC-1 automatically rewrites this to http://localhost:6274 because XSUAA only allows http://localhost for redirect URIs, never http://127.0.0.1.

Copilot Studio does not re-register via DCR after server restarts, so use Manual OAuth mode instead of Dynamic Discovery.

  1. In Copilot Studio, add an MCP server connection
  2. Select Manual OAuth type
  3. Fill in:
  4. Client ID: XSUAA clientid from cf env <app-name> (e.g. sb-arc1-mcp!t627062)
  5. Client secret: XSUAA clientsecret from cf env <app-name>
  6. Authorization URL: https://<app-route>/authorize
  7. Token URL template: https://<app-route>/token
  8. Refresh URL: https://<app-route>/token
  9. Scopes: read write (ARC-1 auto-qualifies these with the XSUAA xsappname prefix)
  10. Save — Copilot Studio generates a redirect URL
  11. ARC-1 automatically accepts the redirect URL (dynamic redirect URI registration for the XSUAA client)

Why Manual mode: Dynamic Discovery uses DCR (Dynamic Client Registration) with in-memory storage. Every deploy/restart clears registrations, breaking Copilot Studio connections. Manual mode uses the permanent XSUAA service binding credentials.

Redirect URI: Copilot Studio uses https://global.consent.azure-apim.net/redirect/* — this pattern is already in xs-security.json. ARC-1's dynamic redirect URI registration handles the MCP SDK's exact-match requirement automatically.

Updating xs-security.json

If you need to add redirect URIs or change scopes:

# Edit xs-security.json
# Then update the service:
cf update-service arc1-xsuaa -c xs-security.json

# Restage the app to pick up changes:
cf restage arc1-mcp-server

Configuration Reference

Variable Description Default
SAP_XSUAA_AUTH Enable XSUAA OAuth proxy false

XSUAA credentials are automatically loaded from VCAP_SERVICES when the service is bound. No manual credential configuration is needed.

How Auth Coexistence Works

When XSUAA auth is enabled, the chained token verifier tries three methods in order:

  1. XSUAA JWT — validated by @sap/xssec against XSUAA JWKS (offline, cached)
  2. Entra ID JWT — validated by jose against OIDC issuer JWKS (if SAP_OIDC_ISSUER is set)
  3. API Key — simple string match against ARC1_API_KEY

The first successful validation wins. This means: - MCP-native clients (Claude Desktop, Cursor, MCP Inspector) use XSUAA OAuth via auto-discovery - Copilot Studio uses XSUAA OAuth via Manual mode (or Entra ID OIDC if configured separately) - API key auth continues to work for testing and Joule Studio

Troubleshooting

"AADSTS50011: Redirect URI mismatch"

The redirect URI used by the MCP client isn't in xs-security.json. Add the URI pattern:

"redirect-uris": [
  "http://localhost:*/**",
  "https://*.cfapps.us10-001.hana.ondemand.com/**"
]
Then run cf update-service arc1-xsuaa -c xs-security.json.

"Token has no expiration time"

API key tokens now include a synthetic expiration (1 year). If you see this error, ensure you're running the latest version of ARC-1.

"XSUAA credentials not found"

Ensure the XSUAA service is bound: cf services should show arc1-xsuaa bound to your app. If not: cf bind-service arc1-mcp-server arc1-xsuaa && cf restage arc1-mcp-server.

"Insufficient scope" / "invalid_scope"

The user doesn't have the required role collection assigned. Go to BTP Cockpit → Security → Role Collections and assign the appropriate collection to the user.

IdP matters: If the subaccount has a custom IAS tenant (trust configuration shows sap.custom), role collections must be assigned with the correct IdP origin. Assigning via sap.default when the user logs in via sap.custom will result in invalid_scope.

"Invalid client_id" (Copilot Studio)

DCR registrations are in-memory and lost on restart. Switch to Manual OAuth mode (see above) to avoid this.

OAuth flow hangs or returns 400

Check that the XSUAA client ID matches. Run cf env <app-name> and look for the clientid in the XSUAA binding credentials.

"Authorization Request Error" / XSUAA login fails

If using MCP Inspector with http://127.0.0.1:6274, XSUAA rejects the redirect URI (only http://localhost is allowed). ARC-1 handles this automatically by rewriting 127.0.0.1localhost.

Architecture

MCP Client (Claude Desktop, Cursor, MCP Inspector)
  ├── GET /.well-known/oauth-authorization-server  ──→  OAuth metadata
  ├── GET /authorize?client_id=...&redirect_uri=... ──→  Proxied to XSUAA login
  ├── POST /token (authorization_code exchange)     ──→  Proxied to XSUAA token endpoint
  └── POST /mcp (Bearer token)
        ├── requireBearerAuth middleware
        │     └── Chained verifier: XSUAA → OIDC → API key
        └── MCP Server (per-request)
              └── ADT Client → SAP System