Skip to content

BTP Destination Setup Guide

How to configure SAP BTP Destinations for ARC-1, covering Basic Authentication (shared service account), Principal Propagation (on-premise per-user SAP identity), and OAuth2UserTokenExchange (BTP ABAP Environment per-user SAP identity).


Authentication Modes Overview

ARC-1 supports these destination-related ways to authenticate to SAP:

Mode Who acts in SAP Config Use Case
Hardcoded credentials Single user (SAP_USER/SAP_PASSWORD) Env vars only, no BTP Local dev, direct connection
BTP Destination (Basic) Single service account BTP Destination Service Cloud deployment, shared user
BTP Destination (PP, on-premise) Each MCP user as their own SAP user BTP Destination + Connectivity + Cloud Connector PP Enterprise, per-user audit trail for on-premise SAP
BTP Destination (OAuth2UserTokenExchange) Each MCP user as their own BTP ABAP user BTP Destination, ProxyType=Internet BTP ABAP Environment, no Cloud Connector

All modes can coexist with any MCP client authentication (API key, OIDC, XSUAA), but per-user destinations need a real JWT. XSUAA is the BTP-native path.

OAuth2UserTokenExchange requires ARC-1 and the BTP ABAP Environment to be in the same subaccount (it's an XSUAA→XSUAA exchange within one identity zone). For ARC-1 and the ABAP env in different subaccounts, use OAuth2SAMLBearerAssertion + trust instead. See BTP ABAP Environment → Cross-subaccount principal propagation.


Mode 1: Hardcoded Credentials (No BTP)

The simplest mode. SAP credentials are set directly via environment variables or CLI flags:

# Via env vars
SAP_URL=http://sap-host:50000 SAP_USER=DEVELOPER SAP_PASSWORD=secret npx arc-1

# Via CLI flags
npx arc-1 --url http://sap-host:50000 --user DEVELOPER --password secret

This works for: - Local development with stdio transport - Direct network access to SAP (no Cloud Connector needed) - Testing and demos

Important: With SAP_PP_STRICT=false (default) hardcoded credentials or the destination's service account act as a fallback when PP fails. Set SAP_PP_STRICT=true to disable fallback and surface PP failures as errors. Per-user sessions never inherit shared Basic/cookie credentials — cookies combined with SAP_PP_ENABLED=true fail fast at startup unless the SAP_PP_ALLOW_SHARED_COOKIES=true escape hatch is set (SEC-09). See Coexistence Matrix.


Mode 2: BTP Destination with Basic Authentication

A BTP Destination stores SAP connection details (URL, user, password) centrally. ARC-1 reads them at startup via the Destination Service API.

Step 1: Create the BTP Destination

In the BTP Cockpit, go to Connectivity > Destinations and create:

Property Value
Name SAP_TRIAL (or any name)
Type HTTP
URL http://a4h-abap:50000 (Cloud Connector virtual host)
Proxy Type OnPremise
Authentication BasicAuthentication
User DEVELOPER (SAP technical user)
Password <password>

Add additional properties:

Property Value
sap-client 001
HTML5.DynamicDestination true

Step 2: Configure ARC-1

Set the environment variable pointing to the destination name:

# In manifest.yml or via cf set-env
SAP_BTP_DESTINATION=SAP_TRIAL

ARC-1 resolves the destination at startup and uses the credentials for all requests. The SAP_URL, SAP_USER, and SAP_PASSWORD env vars are overridden by the destination values.

Step 3: Bind Services

ARC-1 needs the Destination Service and Connectivity Service bindings:

cf create-service destination lite arc1-destination
cf create-service connectivity lite arc1-connectivity
cf bind-service arc1-mcp-server arc1-destination
cf bind-service arc1-mcp-server arc1-connectivity

Or in manifest.yml:

services:
  - arc1-destination    # or your existing destination service instance
  - arc1-connectivity   # or your existing connectivity service instance

Mode 3: BTP Destination with Principal Propagation (On-Premise)

Each authenticated MCP user gets their own SAP identity. SAP enforces S_DEVELOP authorization per user and the audit log shows who did what.

How it works

MCP Client → XSUAA OAuth → ARC-1 → Destination Service (X-User-Token: <jwt>)
                                         ↓
                                   SAML assertion with user identity
                                         ↓
                              ADT Client → SAP-Connectivity-Authentication header
                                         ↓
                              Connectivity Proxy → Cloud Connector
                                         ↓
                              X.509 cert (CN=SAP_USERNAME) → CERTRULE → SAP user

Step 1: Create a Dual-Destination Setup

The recommended approach uses two destinations — one for the shared service account and one for per-user PP:

Destination 1: SAP_TRIAL (BasicAuth — shared client)

Property Value
Name SAP_TRIAL
Type HTTP
URL http://a4h-abap:50000 (CC virtual host, HTTP)
Proxy Type OnPremise
Authentication BasicAuthentication
User DEVELOPER
Password <password>
sap-client 001

This destination is resolved at startup and used as the fallback for API key auth and when PP fails.

Destination 2: SAP_TRIAL_PP (PrincipalPropagation — per-user)

Property Value
Name SAP_TRIAL_PP
Type HTTP
URL http://a4h-abap:50001 (CC virtual host, HTTPS port)
Proxy Type OnPremise
Authentication PrincipalPropagation
User (leave empty)
Password (leave empty)
sap-client 001

This destination is used per-request when an authenticated user's JWT is available.

Why two destinations? A PrincipalPropagation destination has no User/Password. At startup, there is no user JWT — the SAP Cloud SDK's getDestination() would fail for PP destinations. The BasicAuth destination provides a fallback for system-level operations (feature probing, cache warmup) and API key users.

Why port 50001 for PP? The Cloud Connector needs an HTTPS system mapping with X509_GENERAL auth mode for PP. Port 50001 is the SAP HTTPS port. The HTTP mapping (50000) uses NONE_RESTRICTED auth which doesn't support PP.

Cloud Connector Location ID

If you have multiple Cloud Connectors connected to the same BTP subaccount (each with a different Location ID), add the CloudConnectorLocationId property to your destinations:

Property Value
CloudConnectorLocationId LOC1 (must match the Location ID configured in the Cloud Connector)

ARC-1 propagates this as the SAP-Connectivity-SCC-Location_ID header to route requests to the correct Cloud Connector instance. If you only have one Cloud Connector, leave this empty.

Important: Each destination in a dual-destination setup can have a different Location ID. ARC-1 correctly uses the PP destination's Location ID for per-user requests and the startup destination's Location ID for system requests.

Step 2: Configure Cloud Connector

These steps were validated on SAP Cloud Connector 2.x:

2a. Generate System Certificate

Cloud Connector Admin UI → Configuration → On-Premises tab:

  1. Under System Certificate, click "Create and use a self-signed certificate" icon
  2. Fill in: CN=a4h-cloudconnector, OU=ARC1, O=MZ, C=DE (or your org details)
  3. Click Create

Or via CC REST API:

curl -sk -u Administrator:<password> -X POST \
  -H "Content-Type: application/json" \
  -d '{"type":"selfsigned","subjectDN":"CN=a4h-cloudconnector, OU=ARC1, O=MZ, C=DE","keySize":2048}' \
  https://localhost:8443/api/v1/configuration/connector/onPremise/systemCertificate

2b. Generate CA Certificate

Cloud Connector Admin UI → Configuration → On-Premises tab:

  1. Scroll to CA Certificate section
  2. Click "Create and use a self-signed certificate" icon
  3. Fill in: CN=SCC-CA-a4h, OU=ARC1, O=MZ, C=DE
  4. Key size: 4096 bits (recommended)
  5. Click Create

This CA will sign the short-lived X.509 certificates for each propagated user.

2c. Export the CA Certificate

  1. In the CA Certificate section, click the Download icon
  2. Save as ca_cert.der — you'll import this into SAP's STRUST

2d. Add HTTPS System Mapping

Cloud Connector Admin UI → Cloud to On-Premise → Access Control:

  1. Add a new system mapping:
  2. Virtual Host: a4h-abap
  3. Virtual Port: 50001
  4. Internal Host: localhost
  5. Internal Port: 50001
  6. Protocol: HTTPS
  7. Back-end Type: ABAP System
  8. Authentication Mode: X509_GENERAL
  9. Add resources (see URL path reference below for the full list). For a quick start, add / with Path and all sub-paths. For production, use fine-grained paths.

Or via CC REST API:

curl -sk -u Administrator:<password> -X POST \
  -H "Content-Type: application/json" \
  -d '{"virtualHost":"a4h-abap","virtualPort":50001,"localHost":"localhost","localPort":50001,"protocol":"HTTPS","backendType":"abapSys","authenticationMode":"X509_GENERAL","sid":"A4H","hostInHeader":"INTERNAL"}' \
  "https://localhost:8443/api/v1/configuration/subaccounts/<region>/<subaccount>/systemMappings"

2e. Configure Subject Pattern

Cloud Connector Admin UI → Configuration → On-Premises → scroll to Principal Propagation → Subject Pattern Rules:

  • Add rule: CN=${name} (maps the user's login name to the cert CN)

2f. Backend Trust Store

Set "Determining Trust Through Allowlist" to OFF (trusts all backend certs). This is acceptable when your SAP system uses self-signed certificates.

Step 3: Configure SAP Backend

3a. Import CC CA Certificate into STRUST

The Cloud Connector's CA certificate must be imported into SAP's SSL Server PSE so SAP trusts the short-lived PP certificates.

Via CLI (recommended — no SAP GUI needed):

# Convert DER to PEM
openssl x509 -inform DER -in ca_cert.der -out ca_cert.pem

# Import into SAPSSLS.pse
sapgenpse maintain_pk -p /usr/sap/<SID>/<INSTANCE>/sec/SAPSSLS.pse -a ca_cert.pem

Example for SID=A4H, instance=D00:

su - a4hadm -c "sapgenpse maintain_pk -p /usr/sap/A4H/D00/sec/SAPSSLS.pse -a /tmp/ca_cert.pem"

Verify with:

su - a4hadm -c "sapgenpse maintain_pk -p /usr/sap/A4H/D00/sec/SAPSSLS.pse -l"

Via SAP GUI (alternative): 1. Transaction STRUST 2. Expand SSL Server Standard → double-click your instance 3. Click Import (📥), browse to ca_cert.der 4. Click Add to Certificate List 5. Click Save

3b. Verify ICM Profile Parameters

These must be set in the SAP instance profile (DEFAULT.PFL or instance profile):

icm/HTTPS/verify_client = 1          # Request client certificates
login/certificate_mapping_rulebased = 1   # Enable rule-based cert mapping
login/certificate = 1                    # Enable certificate login
login/certificate_mapping = 1            # Enable cert-to-user mapping

Check current values:

grep -E "certificate|icm/HTTPS" /sapmnt/<SID>/profile/DEFAULT.PFL

3c. Create Certificate-to-User Mapping (CERTRULE)

Transaction SM30, view VUSREXTID:

  1. Click New Entries
  2. External ID type: leave empty (default DN)
  3. External ID: CN=DEVELOPER (must match the Subject Pattern — CN=${name} generates CN=<username>)
  4. Seq. No.: 000
  5. User: DEVELOPER
  6. Activated: checked ✅
  7. Save

Important: The External ID must match exactly what the Cloud Connector generates. With Subject Pattern CN=${name}, the cert subject is just CN=<username> — NOT CN=<username>, OU=ARC1, O=MZ, C=DE. The OU/O/C are in the issuer (CA cert), not the subject.

Known issue: Transaction CERTRULE may dump with STRING_OFFSET_TOO_LARGE (CX_SY_RANGE_OUT_OF_BOUNDS in SAPLSUSR_CERTRULE). Use SM30 with view VUSREXTID as a workaround.

Repeat for each SAP user that will be used via PP. Create one entry per user.

3d. Restart ICM

After all changes, restart ICM to pick up the updated certificates:

# Via sapcontrol (soft restart, no full SAP restart needed)
su - <sid>adm -c "sapcontrol -nr <instance_nr> -function RestartService"

Or via SAP GUI: Transaction SMICM → Administration → ICM → Soft Restart.

Step 4: Enable PP in ARC-1

# Set the dual-destination config
cf set-env arc1-mcp-server SAP_BTP_DESTINATION SAP_TRIAL        # BasicAuth (shared)
cf set-env arc1-mcp-server SAP_BTP_PP_DESTINATION SAP_TRIAL_PP  # PP (per-user)
cf set-env arc1-mcp-server SAP_PP_ENABLED true
cf set-env arc1-mcp-server SAP_PP_STRICT true
cf set-env arc1-mcp-server SAP_XSUAA_AUTH true
cf restage arc1-mcp-server

Or in manifest.yml:

env:
  SAP_BTP_DESTINATION: "SAP_TRIAL"
  SAP_BTP_PP_DESTINATION: "SAP_TRIAL_PP"
  SAP_PP_ENABLED: "true"
  SAP_PP_STRICT: "true"
  SAP_XSUAA_AUTH: "true"

Step 5: Graceful Fallback

When SAP_PP_ENABLED=true: - If the user has a valid JWT (XSUAA/OIDC, 3 dot-separated parts) → per-user ADT client via SAP_BTP_PP_DESTINATION - If PP fails (destination error, missing user mapping, etc.) → falls back to shared service account via SAP_BTP_DESTINATION - If no JWT available (API key auth, stdio) → uses shared service account - API key tokens are detected as non-JWT and skip PP entirely (no wasted API calls)

This means you can enable PP without breaking existing API key users.

The PP fallback is a privilege-escalation path — set SAP_PP_STRICT=true in production

With SAP_PP_STRICT=false (default), any PP failure silently routes the call through the shared service account in SAP_BTP_DESTINATION. The action then executes with the technical user's authorizations and is audited in SAP as that technical user, not the real caller — so an attacker who can force PP failure escalates to the shared account. Keep the fallback only for deployments that intentionally mix API-key and per-user access; for a pure per-user instance set SAP_PP_STRICT=true.

How ARC-1 Resolves PP Destinations

ARC-1 uses the SAP Cloud SDK getDestination() for per-user destination resolution. The SDK handles:

  1. Service token acquisition — obtains a client_credentials token for the Destination Service
  2. X-User-Token header — passes the user's JWT to the Destination Service
  3. Per-user caching — caches resolved destinations with tenant-user isolation when the JWT carries user_id / user_uuid; otherwise SDK defaults can fall back to tenant-level isolation
  4. Auth token extraction — returns authTokens array with PP tokens or Bearer tokens

The startup path (SAP_BTP_DESTINATION) uses direct REST API calls instead of the SDK, because no user JWT is available at startup.

Per-user isolation depends on the token carrying a user id

The SDK's per-user destination/token cache isolates correctly only when the user JWT carries user_id / user_uuid — XSUAA-issued tokens do. A generic OIDC / Entra bearer token that lacks those claims can collapse to tenant-wide caching, where one user's exchanged token may be reused for another. Prefer XSUAA-issued tokens for principal propagation, or verify per-user isolation end-to-end before trusting external OIDC here.

Principal Propagation: Option 1 vs Option 2

SAP documents two ways to propagate user identity through the Cloud Connector (reference):

Option 1 (Recommended) Option 2 (Backward compat)
Headers 1 header: Proxy-Authorization: Bearer <exchanged-token> 2 headers: SAP-Connectivity-Authentication: Bearer <user-JWT> + Proxy-Authorization: Bearer <client-credentials-token>
Token in Proxy-Authorization jwt-bearer exchanged token (contains user identity) Client credentials token (no user identity)
SAP-Connectivity-Authentication Not used Original user JWT
How CC extracts user From the exchanged token in Proxy-Authorization From the original JWT in SAP-Connectivity-Authentication

ARC-1's behavior:

  1. First, ARC-1 tries to get auth tokens from the SDK response (the Destination Service returns a SAP-Connectivity-Authentication header value for PP destinations).
  2. If the Destination Service returns no auth tokens (a known issue — the service sometimes omits them), ARC-1 falls back to a jwt-bearer token exchange with the Connectivity Service XSUAA, then uses Option 2: the original user JWT is sent as SAP-Connectivity-Authentication.

This fallback is documented in the code at src/adt/btp.ts with detailed comments explaining why Option 2 was chosen over Option 1 (the Cloud Connector couldn't extract the principal from the exchanged token in testing).


Mode 4: BTP Destination with OAuth2UserTokenExchange (BTP ABAP Environment)

Use this when ARC-1 runs on BTP Cloud Foundry and connects to a BTP ABAP Environment. The ABAP Environment is Internet-facing, so no Connectivity service or Cloud Connector is needed.

Destination properties

Create an HTTP destination in BTP Cockpit or through the Destination service:

Property Value
Name ABAP_PP (or any name)
Type HTTP
URL ABAP Environment URL from the service key
Proxy Type Internet
Authentication OAuth2UserTokenExchange
Token Service URL <service-key uaa.url>/oauth/token
Client ID / Secret uaa.clientid / uaa.clientsecret from the ABAP service key

ARC-1 configuration

cf set-env arc1-mcp-server SAP_SYSTEM_TYPE btp
cf set-env arc1-mcp-server SAP_BTP_DESTINATION ABAP_PP
cf set-env arc1-mcp-server SAP_PP_ENABLED true
cf set-env arc1-mcp-server SAP_PP_STRICT true
cf set-env arc1-mcp-server SAP_XSUAA_AUTH true
cf restage arc1-mcp-server

At request time, ARC-1 validates the MCP user's XSUAA JWT, asks the Destination service to resolve ABAP_PP with that JWT, extracts the returned ABAP bearer token, and sends it to ADT as Authorization: Bearer <token>. SAP sees the end user. Do not set SAP_BTP_SERVICE_KEY on the ARC-1 CF app; the service key belongs in the destination configuration only.


Using Per-User Destinations from MCP Clients

Prerequisites

  • ARC-1 deployed on BTP CF with SAP_XSUAA_AUTH=true and SAP_PP_ENABLED=true
  • XSUAA service instance with xs-security.json (see XSUAA Setup)
  • For on-premise SAP: BTP Destination set to PrincipalPropagation, plus Cloud Connector and SAP configured for PP (Steps 2-3 above)
  • For BTP ABAP Environment: BTP Destination set to OAuth2UserTokenExchange (Mode 4)

Claude Desktop / Claude Code

Add to your Claude Desktop claude_desktop_config.json or Claude Code settings:

{
  "mcpServers": {
    "arc1-sap": {
      "url": "https://arc1-mcp-<space>.cfapps.us10-001.hana.ondemand.com/mcp",
      "transport": "streamable-http"
    }
  }
}

Claude will auto-discover OAuth via /.well-known/oauth-authorization-server and prompt you to log in via XSUAA. After authentication, every SAP call runs as your user.

Cursor

In Cursor settings, add MCP server:

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

Cursor supports MCP OAuth discovery natively. It will redirect you to the XSUAA login page.

VS Code (with MCP extension)

If using an MCP extension that supports HTTP Streamable transport:

{
  "mcp.servers": {
    "arc1-sap": {
      "url": "https://arc1-mcp-<space>.cfapps.us10-001.hana.ondemand.com/mcp"
    }
  }
}

Copilot Studio (Power Platform)

Prefer XSUAA Manual OAuth for BTP-hosted ARC-1, as described in XSUAA Setup. In that setup the user may authenticate through SAP Cloud Identity Services, SAP ID service, or a corporate IdP federated into the BTP subaccount; ARC-1 still receives and validates an XSUAA token.

Generic external OIDC, such as Entra ID (SAP_OIDC_ISSUER), is supported for MCP authentication, but it does not automatically make the token valid for BTP Destination principal propagation. If you choose external OIDC directly, test the Destination/Connectivity exchange end to end and ensure the BTP trust configuration accepts that issuer. In many productive setups, ARC-1 can validate the external JWT for Layer A while the Destination service still rejects it for Layer B propagation.

MCP Inspector (Testing)

# Start MCP Inspector pointing to your server
npx @modelcontextprotocol/inspector https://arc1-mcp-<space>.cfapps.us10-001.hana.ondemand.com/mcp

Inspector supports OAuth discovery. It will open a browser for XSUAA login.


Verifying Per-User SAP Identity

Check ARC-1 logs

cf logs arc1-mcp-server --recent | grep -E "per-user|Principal|PP"

You should see:

INFO: Principal propagation enabled {"destination":"SAP_TRIAL","hasBtpConfig":true}
INFO: BTP destination resolved (per-user) {"name":"SAP_TRIAL","auth":"PrincipalPropagation","hasConnectivityAuth":true}
DEBUG: Per-user ADT client created {"user":"john.doe@company.com"}

For BTP ABAP OAuth2UserTokenExchange, the important ARC-1 signal is that the per-user destination returns a Bearer token and ARC-1 uses it for ADT:

DEBUG: PP: using destination-exchanged Bearer token (OAuth2UserTokenExchange) {"destination":"ABAP_PP"}

Check SAP audit log

On on-premise SAP systems, run transaction SM20 (Security Audit Log): - Filter by the time of your MCP request - You should see the individual SAP user (e.g., JDOE) — not the technical service account - The action should match what the MCP tool did (e.g., read program source)

For BTP ABAP Environment, verify the corresponding ABAP security/audit traces or request logs show the end user from the exchanged token.

Check SAP user determination (SM30 / VUSREXTID)

For on-premise PP, if the Cloud Connector certificate mapping is not resolving to the correct SAP user: 1. Check the CERTRULE table via SM30, view VUSREXTID 2. Verify the certificate subject (CN) matches what the Cloud Connector sends 3. Use transaction SU01 to verify the target SAP user exists


Cloud Connector URL Path Reference

ARC-1 uses two URL path prefixes. Add both as resources with Path and all sub-paths in the Cloud Connector:

Resource Sub-Paths Purpose
/sap/bc/adt/ Yes All ADT operations (source code, search, write, activate, tests, diagnostics, transports)
/sap/opu/odata/UI5/ABAP_REPOSITORY_SRV/ Yes UI5 ABAP Repository OData Service — query deployed BSP/UI5 app metadata

Note: /sap/opu/odata/UI5/ABAP_REPOSITORY_SRV/ is the only path outside /sap/bc/adt/. It uses the same OData V2 service as SAP Business Application Studio and @sap-ux/deploy-tooling.

If you use a dual-destination setup (HTTP + HTTPS for PP), both system mappings need the same resource paths.


Troubleshooting

Symptom Cause Fix
Destination Service (per-user) returned no destination SDK couldn't resolve destination Check destination name, VCAP_SERVICES binding, and user JWT validity
auth token error: User token validation failed BTP doesn't trust the IdP that issued the JWT Add IdP to BTP Trust Configuration
SAP returns 403 on ADT call SAP user exists but lacks S_DEVELOP authorization Grant via PFCG role assignment
CERTRULE mapping not found Cloud Connector sends cert but SAP can't map CN to user Check SM30 view VUSREXTID
On-prem PP falls back to shared client Destination auth type is still BasicAuthentication Change to PrincipalPropagation in BTP Cockpit
SAP_PP_ENABLED is true but btpConfig is null VCAP_SERVICES not available Ensure the Destination service is bound. Connectivity service is required only for on-premise Cloud Connector PP.
PP requests hit wrong SAP system CloudConnectorLocationId mismatch between startup and PP destination Set correct CloudConnectorLocationId on each destination in BTP Cockpit
jwt-bearer exchange: failed with 401 Connectivity Service doesn't trust the user's JWT issuer Ensure IdP trust is configured in BTP subaccount
Destination Service returned no authTokens (warn) Known Destination Service behavior for PP destinations ARC-1 handles this automatically via jwt-bearer fallback — no action needed

Configuration Reference

Env Var / Flag Description Default
SAP_BTP_DESTINATION BTP Destination name. For BasicAuth, this is the shared startup destination. For BTP ABAP OAuth2UserTokenExchange, this is usually the per-user destination used with SAP_PP_ENABLED=true. (none)
SAP_BTP_PP_DESTINATION Optional per-user destination name. Use for on-prem PP when the shared startup destination and PP destination differ. For BTP ABAP OAuth2UserTokenExchange, SAP_BTP_DESTINATION alone is usually the per-user destination. Falls back to SAP_BTP_DESTINATION
SAP_PP_ENABLED / --pp-enabled Enable ARC-1's per-user destination path: Cloud Connector PP for on-premise SAP, or OAuth2UserTokenExchange for BTP ABAP Environment. false
SAP_XSUAA_AUTH / --xsuaa-auth Enable XSUAA OAuth proxy false
SAP_URL / --url Direct SAP URL (overridden by destination) (none)
SAP_USER / --user Direct SAP user (overridden by destination/PP) (none)
SAP_PASSWORD / --password Direct SAP password (overridden by destination/PP) (none)

Priority: PP per-user > BTP Destination > env vars.

SAP Documentation References