Requires Claude Desktop 1.10270.0 or later. Earlier builds ignore the
bootstrapUrl keys.bootstrapUrl, optional bootstrapOidc, and the bootstrapEnabled opt-out), and Claude Desktop does not consult MDM for any key the bootstrap server is permitted to set. A bootstrap-settable key that your response omits is treated as unset, not inherited from MDM, so return every key you want applied.
The trust anchor itself can arrive via MDM or via the in-app configuration window. If your organization does not use MDM, distribute a small JSON file containing only bootstrapUrl and bootstrapOidc and have users load it from Developer → Configure third-party inference → Import configuration; the bootstrap server supplies everything else after sign-in. See Installation for both paths.
How it works
- Your managed configuration (MDM or imported) sets
bootstrapUrl(andbootstrapOidcif you use a separate identity provider). - At launch, the app authenticates the user via one of two modes and sends
GET <bootstrapUrl>withAuthorization: Bearer <token>. - Your server validates the token, authorizes the caller against your directory or entitlement source, and returns a JSON object whose keys are the same managed-configuration key names documented in the configuration reference.
- The app validates each key against the response schema, drops anything it doesn’t recognize or that fails validation, and applies the result as the effective configuration.
- The response is cached in memory and refetched when it expires (your
expiresAt, or 1 hour by default).
Availability
The cached response is held in memory only. If your bootstrap server is unreachable when Claude Desktop launches, the user is in the degraded sign-in state until the server recovers; there is no on-disk fallback to a previous session’s response. Run the endpoint across multiple replicas or regions behind a load balancer; do not rely on response caching for availability, since responses are per-user and carry credentials (see theCache-Control: no-store guidance under Server responsibilities). If your configuration data lives in a database, a read replica of that store improves availability without caching responses. A failed refetch during a running session keeps the in-memory response and retries, so an outage that starts mid-session does not disrupt active users until they relaunch.
When a refetch returns different values, main-process consumers (the inference client, egress allowlist, MCP server set) pick them up on their next operation. Renderer state such as the model picker and the organization banner reflects the new values after the next app relaunch.
Server responsibilities
Your bootstrap endpoint is a security boundary. The response can carry inference credentials, so an unauthenticated or under-authorized endpoint leaks those credentials to anyone who can reach the URL. Host it on your private network (VPC, corporate intranet, or behind your zero-trust access proxy) rather than the public internet; reachability from managed devices is sufficient. Authenticate. Verify the bearer token’s signature against your identity provider’s JWKS, and checkiss, aud, and exp. Reject anything else with 401.
Authorize. Verifying the token proves who the caller is, not that they’re entitled to a configuration. Check the caller’s identity claim against your directory before returning a response:
| Identity provider | Stable per-user claim | Group/role claim |
|---|---|---|
| Microsoft Entra ID | oid (directory object ID) | roles (app roles) or groups |
| Okta | uid or sub | groups (via a custom claim rule) |
| Generic OIDC | sub | provider-specific |
403 when the token is valid but the caller is not entitled. Do not authorize on email or preferred_username alone; those claims are mutable and may be absent for guest or external-identity users.
Key the response on the caller when configuration needs to differ. A single default profile returned to every entitled user is valid; vary by user or group only where you need per-user credentials, model allowlists, or telemetry attribution.
Mapping groups to profiles
The common pattern is one profile per directory group or app role. For Entra, define an app role on the registration (for examplecowork-power-user), assign it to a group via Enterprise applications → Users and groups, and select the profile from the token’s roles claim. For Okta, the equivalent is a groups claim on your custom authorization server; match on payload.groups. Moving a user between groups in your directory changes their configuration on the next refetch with no profile re-push to devices.
A reference Node.js handler showing token validation, role-based authorization, and profile selection:
Cache-Control: no-store on the response. Without it, a reverse proxy or CDN between the app and your endpoint may cache one user’s credentials and serve them to the next.
Authentication
The bootstrap request always carries a bearer token; there is no unauthenticated mode. Two ways to obtain that token, chosen by whether you setbootstrapOidc in MDM:
| Mode | When to use it | MDM keys |
|---|---|---|
| Separate identity provider (PKCE) | Users sign in through your existing OIDC provider (Microsoft Entra ID, Okta, Ping, or any compliant provider). The app runs an OAuth authorization-code grant with PKCE in the system browser. | bootstrapUrl and bootstrapOidc |
| Bootstrap server as authorization server (device code) | Your bootstrap server (or the gateway it fronts) implements RFC 8414 discovery and the RFC 8628 device-code grant. One sign-in covers both the configuration fetch and inference when they share an origin. | bootstrapUrl only |
Separate identity provider (PKCE)
Register a public client in your identity provider
Register a native or public application with a loopback redirect URI and no client secret. The registration is identical to the one used for gateway single sign-on; if you already have that, reuse it. See the provider notes below for redirect-URI specifics.For Microsoft Entra ID, also set an Application ID URI on the registration (App registration → Expose an API → Set; accept the default
api://CLIENT_ID). The CLIENT_ID/.default scope in the next step does not resolve without it.Choose the scope your server will validate
The app sends the OAuth access token as the bearer. Your server validates that token’s
Include
aud, so the scope you request must produce a token whose audience your server accepts. This is provider-specific:| Provider | Scope to request | Resulting aud |
|---|---|---|
| Microsoft Entra ID | openid offline_access CLIENT_ID/.default | your client ID |
| Okta (custom authorization server) | openid offline_access YOUR_API_SCOPE | your authorization server’s audience |
| Generic OIDC | openid offline_access plus your API’s resource scope | provider-specific |
offline_access so the app receives a refresh token and can renew silently between launches.Validate the token in your server
See Server responsibilities. What the token’s
Entra token version. A new Entra app registration emits v1-format access tokens by default, with
iss and aud look like depends on your provider:| Provider | iss to expect | aud to expect | JWKS URL |
|---|---|---|---|
Microsoft Entra ID (token version 2) | https://login.microsoftonline.com/TENANT/v2.0 | your client ID | https://login.microsoftonline.com/TENANT/discovery/v2.0/keys |
| Okta (custom authorization server) | https://YOUR_DOMAIN.okta.com/oauth2/AUTH_SERVER_ID | the audience configured on that authorization server | <issuer>/v1/keys |
iss = https://sts.windows.net/TENANT/ and aud = api://CLIENT_ID. Set the accepted-token-version field in the registration’s Manifest to 2 so tokens match the table above. The portal shows this field as either accessTokenAcceptedVersion or api.requestedAccessTokenVersion depending on the manifest view; set whichever you see. If you cannot change it, your server must accept both the v1 and v2 forms.Group and role claims. Entra does not emit groups or roles in access tokens by default. Enable the groups claim under App registration → Token configuration, or define App roles and assign users via Enterprise applications. The oid claim is always present. For Okta, add a groups claim on your custom authorization server with a group filter.Configure and export from Claude Desktop
Install Claude Desktop on an admin workstation (see Installation). From the menu bar, open Developer → Configure third-party inference. In the Source section, fill in the Bootstrap config URL card:
Click Sign in to test against your typed values. Once authenticated, the card shows the keys your server supplied. Click Export and choose the template format your MDM expects (
| Field | Value |
|---|---|
| Bootstrap config URL | https://YOUR_BOOTSTRAP_HOST/user/bootstrap |
| Bootstrap OIDC parameters → Client ID | YOUR_CLIENT_ID |
| Bootstrap OIDC parameters → Issuer URL | https://login.microsoftonline.com/YOUR_TENANT_ID/v2.0 |
| Bootstrap OIDC parameters → Scopes | openid offline_access YOUR_CLIENT_ID/.default |
| Bootstrap OIDC parameters → Redirect port | leave empty for Entra; set for Okta |
.mobileconfig, ADMX, Intune OMA-URI JSON, or .reg). See Deploy the configuration for per-platform instructions.Provider notes
| Provider | Redirect URI to register | Redirect port field | Additional setup |
|---|---|---|---|
| Microsoft Entra ID | http://127.0.0.1/callback under Mobile and desktop applications | Leave empty (any local port allowed) | Manifest: set the accepted-token-version field to 2. Expose an API: set the Application ID URI. Token configuration: add the groups claim if your server authorizes on groups. |
| Okta | http://127.0.0.1:53180/callback (any fixed port) on a Native application | Set to the registered port | Create a custom authorization server with an audience your bootstrap server validates. |
| Other OIDC | http://127.0.0.1/callback | Set only if exact-port match is enforced | None |
127.0.0.1, not localhost.
The bootstrap sign-in is the only sign-in this page is concerned with. Any further authentication for inference depends on what your response provisions and is independent of bootstrap; see the relevant provider page (gateway SSO, Vertex, Bedrock, Foundry).
Bootstrap server as authorization server (device code)
Set onlybootstrapUrl in MDM. The app discovers your authorization endpoints via RFC 8414 and runs an RFC 8628 device-code grant. The bearer is reused for inference when inferenceGatewayBaseUrl shares the bootstrapUrl origin and inferenceGatewayAuthScheme is sso, so the user signs in once for both.
Publish RFC 8414 discovery metadata
Serve a metadata document under the Every endpoint URL must share the
bootstrapUrl path. If bootstrapUrl ends in /bootstrap or /user/bootstrap, that suffix is stripped to form the issuer base.bootstrapUrl origin. Metadata that points off-origin is rejected.Implement the device-code grant
POST to device_authorization_endpoint returns:verification_uri and verification_uri_complete must share the bootstrapUrl origin; federate behind your own pages rather than returning an upstream provider’s URL directly. The app opens the verification URL in the user’s browser and shows the user code.The app polls token_endpoint with grant_type=urn:ietf:params:oauth:grant-type:device_code and the device_code. Return {"error":"authorization_pending"} until the user approves, then:Serve the configuration endpoint
On
GET <bootstrapUrl> with a valid bearer, look up the user from the token claims and return their configuration (see the HTTP contract).The HTTP contract
Request
bootstrapUrl; there is no required path. Redirects are not followed: a 3xx is treated as an error so a same-origin open redirect cannot exfiltrate the bearer. The request times out after 30 seconds.
Response
Return200 OK with Content-Type: application/json and a JSON object whose keys are a subset of the published response schema. Keys use the exact managed-configuration key names. Unknown keys, keys that fail validation, and keys outside that schema are silently dropped; one bad key never invalidates the rest.
| Status | App behavior |
|---|---|
200 | Parse and apply. |
304 | Re-serve the cached response (the app sends If-None-Match when it has one). |
401, 403 | Discard the cached token and prompt the user to sign in again. A 401 on a background refresh keeps the running session and retries without prompting. Return 401 when the token is missing, expired, or the wrong audience; return 403 when the token is valid but the caller is not entitled. |
Other non-2xx, or 3xx | Fetch error. Falls back to the last good response from this session if one exists; otherwise the app stays in the degraded sign-in state. |
Response schema
The full set of bootstrap-settable keys is published as a machine-readable JSON Schema at/cowork/3p/schemas/bootstrap-config-v1.schema.json. It is generated from the same source as the configuration reference and updates with each release. Reference it with "$schema" in your response template, or with # yaml-language-server: $schema=… in YAML, for autocomplete and validation.
The response can supply any key in that schema, including inference credentials, model allowlists, MCP servers, the egress allowlist, telemetry endpoints, and the organization banner.
Organization plugins and skills are not yet delivered via bootstrap. They are distributed through the filesystem
org-plugins/ directory described in Connectors and extensions. Network delivery via a bootstrap-supplied organizationPluginsUrl is planned.bootstrapUrl,bootstrapOidc,bootstrapEnabled: the trust anchor cannot redirect itself.inferenceCredentialHelperand other keys whose value is a local executable path: a network response cannot nominate code to run.stdio-transport entries inmanagedMcpServersare dropped for the same reason.- Loopback hosts (
127.0.0.1,localhost,[::1]) in any URL-valued key, regardless of scheme.
Caching and expiresAt
| Value | Meaning |
|---|---|
| Omitted | Cache for 1 hour. |
| Number ≥ 1012 | Unix epoch milliseconds. |
| Number < 1012 | Unix epoch seconds. |
Origin pinning
When the bootstrap server is its own authorization server (nobootstrapOidc), the response is fenced: inferenceGatewayBaseUrl, inferenceVertexBaseUrl, inferenceBedrockBaseUrl, and organizationPluginsUrl must share the bootstrapUrl origin or the field is dropped. A compromised configuration response cannot redirect inference to an attacker-controlled host because the only host it can name is the one the user already authenticated to.
When you supply bootstrapOidc, your configuration server and gateway are independent hosts you control, so origin pinning is disabled and the response can name any HTTPS host. In this mode the bootstrap server’s integrity is the only control on where inference and MCP traffic are sent.
MDM configuration keys
| Setting | Required | Description |
|---|---|---|
Bootstrap config URLbootstrapUrl | Yes | HTTPS endpoint of your bootstrap server. Setting this enables bootstrap. |
Use bootstrap configbootstrapEnabled | No | Defaults to true. Set to false to keep bootstrapUrl in your profile but skip the fetch, for example to pause a misbehaving server without re-pushing the profile. |
Bootstrap OIDC parametersbootstrapOidc | For PKCE mode | Identity-provider settings for the PKCE mode. Omit for device-code mode. |
→ Client IDbootstrapOidc.clientId | Yes | Your registered public client ID. |
→ Issuer URLbootstrapOidc.issuer | This, or both URL fields | OIDC issuer. The app fetches <issuer>/.well-known/openid-configuration. |
→ Authorization URLbootstrapOidc.authorizationUrl | With tokenUrl if issuer unset | Explicit authorize endpoint. |
→ Token URLbootstrapOidc.tokenUrl | With authorizationUrl if issuer unset | Explicit token endpoint. |
→ ScopesbootstrapOidc.scopes | Yes | Space-separated scopes for the authorize request. Must include a scope that produces an access token whose aud your server validates. See the PKCE setup steps. |
→ Redirect portbootstrapOidc.redirectPort | No | Pin the loopback callback port (1024–65535) when your provider requires an exact registered redirect URI. |
inferenceProvider is needed in the MDM profile when using bootstrap; the response supplies it.
Troubleshooting
| Symptom | Likely cause |
|---|---|
Identity provider shows AADSTS900144 (Entra) or invalid_request: scope | bootstrapOidc.scopes is empty. It is required. |
Server logs unexpected "iss" or unexpected "aud" for a valid Entra token | The app registration’s accepted-token-version is at its default. Set it to 2 in the Manifest, or accept both v1 (sts.windows.net / api://CLIENT_ID) and v2 forms in your server. |
| Sign-in succeeds in the browser but the app immediately re-prompts | Your server returned 401 or 403. For 401, check the aud match: the requested scope must produce a token whose audience your server validates. For 403, the user authenticated but is not in the entitled group or role. |
Entra returns AADSTS500011 (“resource principal not found”) | The app registration has no Application ID URI. Set one under Expose an API. |
Silent refresh fails after ~1 hour with AADSTS90009 | scopes uses the api://CLIENT_ID/.default form. Use the bare-GUID CLIENT_ID/.default form. |
| Some keys you returned are not applied | They failed schema validation, are structurally excluded, or were dropped by origin pinning. The desktop log (~/Library/Logs/Claude-3p/main.log on macOS, %LOCALAPPDATA%\Claude-3p\logs\main.log on Windows) records which keys were dropped and why. |
| Browser opens to your identity provider’s device page instead of yours | In device-code mode, verification_uri must share the bootstrapUrl origin. Federate behind your own page. |