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:
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¶
- Open BTP Cockpit → Security → Role Collections
- Find "ARC-1 Viewer" / "ARC-1 Editor" / "ARC-1 Admin"
- Click the role collection → Edit → Users tab
- Add your BTP user (email address)
- 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:
MCP Inspector¶
Connect to:
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 (Manual OAuth — recommended)¶
Copilot Studio does not re-register via DCR after server restarts, so use Manual OAuth mode instead of Dynamic Discovery.
- In Copilot Studio, add an MCP server connection
- Select Manual OAuth type
- Fill in:
- Client ID: XSUAA
clientidfromcf env <app-name>(e.g.sb-arc1-mcp!t627062) - Client secret: XSUAA
clientsecretfromcf env <app-name> - Authorization URL:
https://<app-route>/authorize - Token URL template:
https://<app-route>/token - Refresh URL:
https://<app-route>/token - Scopes:
read write(ARC-1 auto-qualifies these with the XSUAA xsappname prefix) - Save — Copilot Studio generates a redirect URL
- 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:
- XSUAA JWT — validated by
@sap/xssecagainst XSUAA JWKS (offline, cached) - Entra ID JWT — validated by
joseagainst OIDC issuer JWKS (ifSAP_OIDC_ISSUERis set) - 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:
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.1 → localhost.
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