Deploy with a bootstrap server changedthird-party/claude-desktop/bootstrap
Nearest release: v2.1.283, published under an hour after upstream edited the page. Shown because the two are within 24 hours of each other. Nothing here says the release caused the edit.
Upstream edited this page at 25 Sep 2026 17:46 UTC, give or take a minute or two: the time comes from Anthropic’s own sitemap rather than from a commit. This site recorded the change at 28 Sep 2026 22:07 UTC.
Upstream edited
Recorded here
Lines+66added
Lines−66removed
From line
63
where the diff opens
First seen
14 Aug 2026
this site's first read of the page
Recorded edits20to this page, all time
The whole hunk
from line 63, old and new numbered
/
from line 63
6363
6464**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:
6565
66| Identity provider | Stable per-user claim | Group/role claim |
67| ------------------ | --------------------------- | ---------------------------------- |
68| Microsoft Entra ID | `oid` (directory object ID) | `roles` (app roles) or `groups` |
69| Okta | `uid` or `sub` | `groups` (via a custom claim rule) |
70| Generic OIDC | `sub` | provider-specific |
66| Identity provider | Stable per-user claim | Group/role claim |
67| - | - | - |
68| Microsoft Entra ID | `oid` (directory object ID) | `roles` (app roles) or `groups` |
69| Okta | `uid` or `sub` | `groups` (via a custom claim rule) |
70| Generic OIDC | `sub` | provider-specific |
7171
7272Return `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.
7373
from line 135
135135
136136The bootstrap request is always authenticated: either each user signs in and the app sends their bearer token, or the device sends request headers you configure. The mode is chosen by which keys you set alongside `bootstrapUrl`:
137137
138| Mode | When to use it | MDM keys |
139| ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
140| **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` |
141| **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 |
142| **Request headers (no per-user sign-in)** | The endpoint authenticates the device or a service account rather than the user: a static `Authorization: Basic …` or API-key header, or a short-lived token a script on the device fetches from your secrets manager. No browser step; the response cannot vary by signed-in user unless your headers identify one. | `bootstrapUrl` and `bootstrapHeaders` and/or `bootstrapHeadersHelper` (1.32885.1 or later) |
138| Mode | When to use it | MDM keys |
139| - | - | - |
140| **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` |
141| **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 |
142| **Request headers (no per-user sign-in)** | The endpoint authenticates the device or a service account rather than the user: a static `Authorization: Basic …` or API-key header, or a short-lived token a script on the device fetches from your secrets manager. No browser step; the response cannot vary by signed-in user unless your headers identify one. | `bootstrapUrl` and `bootstrapHeaders` and/or `bootstrapHeadersHelper` (1.32885.1 or later) |
143143
144144### Separate identity provider (PKCE)
145145
from line 153
153153 <Step title="Choose the scope your server will validate">
154154 The app sends the OAuth **access token** as the bearer. Your server validates that token's `aud`, so the scope you request must produce a token whose audience your server accepts. This is provider-specific:
155155
156 | Provider | Scope to request | Resulting `aud` |
157 | ---------------------------------- | ------------------------------------------------------ | ------------------------------------ |
158 | Microsoft Entra ID | `openid offline_access CLIENT_ID/.default` | your client ID |
159 | Okta (custom authorization server) | `openid offline_access YOUR_API_SCOPE` | your authorization server's audience |
160 | Generic OIDC | `openid offline_access` plus your API's resource scope | provider-specific |
156 | Provider | Scope to request | Resulting `aud` |
157 | - | - | - |
158 | Microsoft Entra ID | `openid offline_access CLIENT_ID/.default` | your client ID |
159 | Okta (custom authorization server) | `openid offline_access YOUR_API_SCOPE` | your authorization server's audience |
160 | Generic OIDC | `openid offline_access` plus your API's resource scope | provider-specific |
161161
162162 Include `offline_access` so the app receives a refresh token and can renew silently between launches.
163163
from line 169
169169 <Step title="Validate the token in your server">
170170 See [Server responsibilities](#server-responsibilities). What the token's `iss` and `aud` look like depends on your provider:
171171
172 | Provider | `iss` to expect | `aud` to expect | JWKS URL |
173 | -------------------------------------- | ---------------------------------------------------- | ---------------------------------------------------- | -------------------------------------------------------------- |
174 | 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` |
175 | Okta (custom authorization server) | `https://YOUR_DOMAIN.okta.com/oauth2/AUTH_SERVER_ID` | the audience configured on that authorization server | `<issuer>/v1/keys` |
172 | Provider | `iss` to expect | `aud` to expect | JWKS URL |
173 | - | - | - | - |
174 | 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` |
175 | Okta (custom authorization server) | `https://YOUR_DOMAIN.okta.com/oauth2/AUTH_SERVER_ID` | the audience configured on that authorization server | `<issuer>/v1/keys` |
176176
177177 **Entra token version.** A new Entra app registration emits v1-format access tokens by default, with `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.
178178
from line 182
182182 <Step title="Configure and export from Claude Desktop">
183183 Install Claude Desktop on an admin workstation (see [Installation](/docs/third-party/claude-desktop/installation)). From the menu bar, open **Developer → Configure Third-Party Inference…**. In the **Source** section, fill in the **Bootstrap config URL** card:
184184
185 | Field | Value |
186 | ----------------------------------------- | ------------------------------------------------------- |
187 | Bootstrap config URL | `https://YOUR_BOOTSTRAP_HOST/user/bootstrap` |
188 | Bootstrap OIDC parameters → Client ID | `YOUR_CLIENT_ID` |
189 | Bootstrap OIDC parameters → Issuer URL | `https://login.microsoftonline.com/YOUR_TENANT_ID/v2.0` |
190 | Bootstrap OIDC parameters → Scopes | `openid offline_access YOUR_CLIENT_ID/.default` |
191 | Bootstrap OIDC parameters → Redirect port | leave empty for Entra; set for Okta |
185 | Field | Value |
186 | - | - |
187 | Bootstrap config URL | `https://YOUR_BOOTSTRAP_HOST/user/bootstrap` |
188 | Bootstrap OIDC parameters → Client ID | `YOUR_CLIENT_ID` |
189 | Bootstrap OIDC parameters → Issuer URL | `https://login.microsoftonline.com/YOUR_TENANT_ID/v2.0` |
190 | Bootstrap OIDC parameters → Scopes | `openid offline_access YOUR_CLIENT_ID/.default` |
191 | Bootstrap OIDC parameters → Redirect port | leave empty for Entra; set for Okta |
192192
193193 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 (`.mobileconfig`, ADMX, Intune OMA-URI JSON, or `.reg`). See [Deploy the configuration](/docs/third-party/claude-desktop/mdm#4-deploy-the-configuration) for per-platform instructions.
194194 </Step>
from line 196
196196
197197#### Provider notes
198198
199| Provider | Redirect URI to register | Redirect port field | Additional setup |
200| ------------------ | ------------------------------------------------------------------------------ | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
201| 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. |
202| 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. |
203| Other OIDC | `http://127.0.0.1/callback` | Set only if exact-port match is enforced | None |
199| Provider | Redirect URI to register | Redirect port field | Additional setup |
200| - | - | - | - |
201| 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. |
202| 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. |
203| Other OIDC | `http://127.0.0.1/callback` | Set only if exact-port match is enforced | None |
204204
205205Register the redirect URI with `127.0.0.1` rather than `localhost`, because the app sends `http://127.0.0.1:<port>/callback` by default. If your identity provider accepts only `localhost` in a registered redirect URI, set the `redirectHost` field of [`bootstrapOidc`](/docs/third-party/claude-desktop/configuration#bootstrapoidc) to `localhost` and register `http://localhost/callback` instead, or `http://localhost:<port>/callback` when you set a redirect port.
206206
from line 297
297297}
298298```
299299
300| Status | App behavior |
301| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
302| `200` | Parse and apply. |
303| `304` | Re-serve the cached response (the app sends `If-None-Match` when it has one). |
304| `401`, `403` | Discard the cached token and prompt the user to sign in again. A `401` on a background refresh keeps the running configuration. When the same sign-in also serves inference, the app treats it as an ended session and asks the user to sign in again (1.34493.0 and later); otherwise it retries at the next check 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. |
305| 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. |
300| Status | App behavior |
301| - | - |
302| `200` | Parse and apply. |
303| `304` | Re-serve the cached response (the app sends `If-None-Match` when it has one). |
304| `401`, `403` | Discard the cached token and prompt the user to sign in again. A `401` on a background refresh keeps the running configuration. When the same sign-in also serves inference, the app treats it as an ended session and asks the user to sign in again (1.34493.0 and later); otherwise it retries at the next check 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. |
305| 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. |
306306
307307<Warning>
308308 A `200` that is not a JSON object (an empty body, an HTML page from a captive portal or load balancer, or a JSON array) is a parse error. Make sure intermediate proxies do not rewrite the response.
from line 354
354354
355355### Caching and `expiresAt`
356356
357| Field | Type | Description |
358| ---------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
357| Field | Type | Description |
358| - | - | - |
359359| `$schemaVersion` | `integer` | Wire-format marker: `2` for the nested format (bootstrap-config-v2, what the app's own JSON export writes), `1` for the flat format (bootstrap-config-v1). Optional: the client infers the format from the document shape; set it to state the format explicitly. |
360| `expiresAt` | `number` | Unix epoch (seconds or milliseconds) after which the client should re-fetch this document. Optional; when absent the client uses its default refresh interval. |
360| `expiresAt` | `number` | Unix epoch (seconds or milliseconds) after which the client should re-fetch this document. Optional; when absent the client uses its default refresh interval. |
361361
362362<AccordionGroup>
363363 <Accordion title="$schemaVersion details">
from line 379
379379
380380## MDM configuration keys
381381
382| Setting | Type | Availability | Default | Description |
383| ---------------------------------------------------------------------------------------------------- | --------- | -------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
384| <span id="bootstrapenabled" />Use bootstrap config<br />`bootstrapEnabled` | `boolean` | MDM only<br />Added in 1.10628.0 | `true` | Fetch and apply the URL above at launch. Turn off to keep the URL saved but skip the fetch. Defaults to `true`. |
385| <span id="bootstrapurl" />Bootstrap config URL<br />`bootstrapUrl` | `string` | MDM only<br />Added in 1.10628.0 | — | HTTPS endpoint that returns a per-user JSON config overlay. Values from the response override local settings and become read-only. |
386| <span id="bootstrapoidc" />Bootstrap OIDC parameters<br />`bootstrapOidc` | `object` | MDM only<br />Added in 1.10628.0 | — | When set, the bootstrap request sends a Bearer token from a browser sign-in (authorization-code-with-PKCE). |
387| <span id="bootstrapheaders" />Bootstrap request headers<br />`bootstrapHeaders` | `object` | MDM only<br />Added in 1.32885.1 | — | HTTP headers sent on every bootstrap config fetch. Use this instead of embedding user:pass@ in the URL. Deprecated: `bootstrapHeaders as a "Name=value,…" string or a ["Name: value", …] list` (accepted until October 7, 2026); use a JSON object such as \{"Name": "value"}. If it is still present after that, a string or list value will be rejected as malformed and no bootstrap request headers will be sent (the fetch may then fail to authenticate). |
388| <span id="bootstrapheadershelper" />Bootstrap headers helper script<br />`bootstrapHeadersHelper` | `string` | MDM only<br />Added in 1.32885.1 | — | Absolute path to an executable that prints a JSON object of bootstrap request headers. Merged over the static headers; the helper wins. |
382| Setting | Type | Availability | Default | Description |
383| - | - | - | - | - |
384| <span id="bootstrapenabled" />Use bootstrap config<br />`bootstrapEnabled` | `boolean` | MDM only<br />Added in 1.10628.0 | `true` | Fetch and apply the URL above at launch. Turn off to keep the URL saved but skip the fetch. Defaults to `true`. |
385| <span id="bootstrapurl" />Bootstrap config URL<br />`bootstrapUrl` | `string` | MDM only<br />Added in 1.10628.0 | — | HTTPS endpoint that returns a per-user JSON config overlay. Values from the response override local settings and become read-only. |
386| <span id="bootstrapoidc" />Bootstrap OIDC parameters<br />`bootstrapOidc` | `object` | MDM only<br />Added in 1.10628.0 | — | When set, the bootstrap request sends a Bearer token from a browser sign-in (authorization-code-with-PKCE). |
387| <span id="bootstrapheaders" />Bootstrap request headers<br />`bootstrapHeaders` | `object` | MDM only<br />Added in 1.32885.1 | — | HTTP headers sent on every bootstrap config fetch. Use this instead of embedding user:pass@ in the URL. Deprecated: `bootstrapHeaders as a "Name=value,…" string or a ["Name: value", …] list` (accepted until October 7, 2026); use a JSON object such as \{"Name": "value"}. If it is still present after that, a string or list value will be rejected as malformed and no bootstrap request headers will be sent (the fetch may then fail to authenticate). |
388| <span id="bootstrapheadershelper" />Bootstrap headers helper script<br />`bootstrapHeadersHelper` | `string` | MDM only<br />Added in 1.32885.1 | — | Absolute path to an executable that prints a JSON object of bootstrap request headers. Merged over the static headers; the helper wins. |
389389| <span id="trustbootstrapdelivery" />Trust bootstrap-delivered settings<br />`trustBootstrapDelivery` | `boolean` | MDM only<br />Added in 1.26832.0 | `false` | Skip the per-user consent prompt for sign-in targets, inference endpoints, helper scripts, and connectors the bootstrap server delivers. Defaults to `false`. Previously named `trustBootstrapLocalExec` (the old name is accepted until October 7, 2026). If it is still present after that, the key will read as false (its fail-closed value): each user will be asked to consent to bootstrap-delivered sign-in targets, endpoints, helper scripts and connectors, even when the bootstrap URL came from a device-managed profile. |
390390
391391<AccordionGroup>
from line 394
394394
395395 This is an **object-typed key** — in an MDM profile it is a single JSON-string value, not separate keys with dotted names like `bootstrapOidc.clientId`. Writing the sub-fields as separate registry values causes the app to silently fall through to device-code mode.
396396
397 | Field | Type | Default | Description |
398 | --------------------------------- | --------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
399 | `clientId` | `string` | — | OAuth client ID of the desktop app registration at your identity provider (public client, PKCE). |
400 | `issuer` | `string` | — | HTTPS issuer with OIDC discovery. Set this, or set the authorization and token URLs instead. |
401 | `authorizationUrl` | `string` | — | HTTPS authorization endpoint. Used with the token URL when no issuer is set. |
402 | `tokenUrl` | `string` | — | HTTPS token endpoint. Used with the authorization URL when no issuer is set. |
403 | `scopes` | `string` | — | Space-separated; the token’s audience must match what your bootstrap server validates. |
404 | `redirectPort` | `integer` | — | Fixed loopback port for the sign-in redirect. Leave unset to use a free port each time. |
405 | `redirectHost` | `enum` | — | Use localhost only if your IdP’s registered redirect URI specifies it. One of: `127.0.0.1`, `localhost`. |
406 | `additionalRedirectReferrerHosts` | `string` | — | Space-separated hostnames also accepted as the referrer of the sign-in callback. Only needed when the IdP completes sign-in from a different host. |
397 | Field | Type | Default | Description |
398 | - | - | - | - |
399 | `clientId` | `string` | — | OAuth client ID of the desktop app registration at your identity provider (public client, PKCE). |
400 | `issuer` | `string` | — | HTTPS issuer with OIDC discovery. Set this, or set the authorization and token URLs instead. |
401 | `authorizationUrl` | `string` | — | HTTPS authorization endpoint. Used with the token URL when no issuer is set. |
402 | `tokenUrl` | `string` | — | HTTPS token endpoint. Used with the authorization URL when no issuer is set. |
403 | `scopes` | `string` | — | Space-separated; the token’s audience must match what your bootstrap server validates. |
404 | `redirectPort` | `integer` | — | Fixed loopback port for the sign-in redirect. Leave unset to use a free port each time. |
405 | `redirectHost` | `enum` | — | Use localhost only if your IdP’s registered redirect URI specifies it. One of: `127.0.0.1`, `localhost`. |
406 | `additionalRedirectReferrerHosts` | `string` | — | Space-separated hostnames also accepted as the referrer of the sign-in callback. Only needed when the IdP completes sign-in from a different host. |
407407 </Accordion>
408408
409409 <Accordion title="bootstrapHeaders details">
from line 419
419419
420420## Troubleshooting
421421
422| Symptom | Likely cause |
423| ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
424| Identity provider shows `AADSTS900144` (Entra) or `invalid_request: scope` | `bootstrapOidc.scopes` is empty. It is required. |
425| 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. |
426| 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. |
427| Entra returns `AADSTS500011` ("resource principal not found") | The app registration has no Application ID URI. Set one under **Expose an API**. |
428| Silent refresh fails after \~1 hour with `AADSTS90009` | `scopes` uses the `api://CLIENT_ID/.default` form. Use the bare-GUID `CLIENT_ID/.default` form. |
429| Some keys you returned are not applied | They failed schema validation, are structurally excluded, or were dropped by origin pinning. If the app instead shows an **Apply settings from your organization?** dialog, the whole response is waiting for [user consent](#keys-that-require-user-consent) and none of it has been applied yet. 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. |
430| 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. |
422| Symptom | Likely cause |
423| - | - |
424| Identity provider shows `AADSTS900144` (Entra) or `invalid_request: scope` | `bootstrapOidc.scopes` is empty. It is required. |
425| 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. |
426| 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. |
427| Entra returns `AADSTS500011` ("resource principal not found") | The app registration has no Application ID URI. Set one under **Expose an API**. |
428| Silent refresh fails after \~1 hour with `AADSTS90009` | `scopes` uses the `api://CLIENT_ID/.default` form. Use the bare-GUID `CLIENT_ID/.default` form. |
429| Some keys you returned are not applied | They failed schema validation, are structurally excluded, or were dropped by origin pinning. If the app instead shows an **Apply settings from your organization?** dialog, the whole response is waiting for [user consent](#keys-that-require-user-consent) and none of it has been applied yet. 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. |
430| 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. |
431431
No line in this hunk matches that.