Skip to main content
Pointing Claude Code or Claude Desktop at the gateway on one machine is a settings change a developer can undo. This guide makes it tamper-resistant across a fleet: the gateway URL, model mapping, MCP allowlist, and tool permissions go into system-level managed configuration that user and project settings cannot override, and a TrueFoundry binary deployed over MDM keeps the gateway credential fresh so no long-lived API key ever sits on a developer machine. It covers Claude Code (macOS · Linux · Windows) and Claude Desktop (macOS · Windows). Claude Web has no endpoint to enforce, see Claude Web for how it is governed instead.
Identity setup (SSO, domain capture) and admin-console settings are configured in Anthropic’s own console and documented in Claude’s docs. Enforce those first so access is tied to your IdP and employees can’t fall back to personal accounts; this guide covers only what is specific to a TrueFoundry deployment.

How settings are enforced

Claude Code reads settings in priority order, with the system-level managed-settings.json taking precedence and being un-overridable by developers:
System paths: macOS /Library/Application Support/ClaudeCode/managed-settings.json · Linux /etc/claude-code/managed-settings.json · Windows C:\Program Files\ClaudeCode\managed-settings.json. Claude Desktop has the equivalent in OS-native managed preferences under the com.anthropic.claudefordesktop domain, macOS /Library/Managed Preferences/<user>/com.anthropic.claudefordesktop.plist, Windows HKLM\SOFTWARE\Policies\Claude. The key reference is on Claude Desktop. One managed-settings.json enforces everything for Claude Code, gateway routing, model mapping, MCP allowlisting, tool permissions, and sandboxing. A recommended baseline:
Key controls (see Claude’s settings reference for the full list): That JSON has no credential. The binary adds one. See Sign-in.

Sign-in

Each client picks one method in the script’s Config section. The three can differ.
  1. OIDC (preferred)
  2. API key

OIDC

OIDC needs tfy-local-ai-setup v1.4.1 or later. The scripts below default every client to oidc. For Claude Code and Codex, the managed files hold the issuer and client ID, not a token. When the client needs a token it starts the binary, and the binary runs the OIDC flow. Claude Desktop signs in itself using the issuer and client ID in managed preferences. Codex’s ChatGPT subscription setup cannot use OIDC; use API key for that.

Set up OIDC

One public client serves Claude Code, Codex, and Claude Desktop. It has no client secret.
1

Create a Personal Access Token

Create a Personal Access Token. The user who owns it needs the Manage settings permission on the tenant.
2

Create the OIDC client

Replace <tenant_name> and <PAT Token>, then run:
On a self-hosted control plane, use your control plane origin in place of https://<tenant_name>.truefoundry.cloud. The path stays /api/svc/v1/oidc/clients.The two redirect URIs are the ones the clients send:
  • http://127.0.0.1:8585/callback is what Claude Code and Codex use. If port 8585 is in use, the binary tries 8586 and then 8587. A native client matches a loopback redirect on any port, so those two fallback ports do not need their own entries.
  • http://127.0.0.1/callback is what Claude Desktop uses.
The response body is { "data": { ... } }.
  • data.id is the client ID (cli_…). Put it in OIDC_CLIENT_ID.
  • data.manifest.name is claude-mdm. name is not a top-level field.
  • Sending this request again with the same manifest.name updates the client and keeps the same data.id.
  • client_type cannot be changed after creation.
3

Set the issuer and client ID in the script

In the script’s Config section, set:
The issuer is the tenant’s control plane origin plus /<tenant_name>. On TrueFoundry Cloud the origin is https://<tenant_name>.truefoundry.cloud. On a self-hosted control plane it is your control plane origin. Open <OIDC_ISSUER>/.well-known/openid-configuration and copy the issuer value. It must match exactly.

What OIDC writes

On each hourly run the binary writes this config. It does not fetch a token on that run. When Claude Code or Codex needs a token, it starts the binary, and the binary calls the OIDC flow. Claude Desktop signs in itself.
Claude Code
--claude-code-auth=oidc. apiKeyHelper replaces ANTHROPIC_CUSTOM_HEADERS. Claude Code starts the binary and reads the access token from its stdout. The binary calls the OIDC flow.
Codex
The same binary configures Codex. What OIDC writes into the managed TOML, including Windows requirements.toml, is on Codex Security with MDM.
Claude Desktop
--claude-desktop-auth=oidc. The app runs the browser sign-in. The binary writes no token. It sets inferenceCredentialKind to interactive. Claude Desktop sends whichever token bearerTokenType names as Authorization: Bearer on every gateway request.
TrueFoundry requires bearerTokenType: access_token. The AI Gateway validates the OAuth access token. With id_token (Claude Desktop’s default), the gateway rejects the request.Keep openid in scopes so the IdP still issues an ID token during sign-in. Without openid, Claude Desktop’s interactive login fails. What Desktop sends to the gateway on each request is still the access token.
Pass --oidc-bearer-token-type=access_token (the scripts below do this). The binary then also writes scopes (default openid profile email offline_access).
  • The first time Claude Code or Codex needs a token, the client starts the binary. The binary calls the OIDC flow and opens a browser.
  • The refresh token is saved at ~/.tf/oidc-refresh-token and the access token at ~/.tf/oidc-access-token, in that user’s home directory.
  • The next call refreshes from the refresh token and does not open a browser.
  • In the client above, refresh_token_ttl is 7 days and refresh_token_max_uses is 1, so each refresh replaces the saved refresh token.
  • If the refresh token expires, the next call opens the browser again.
  • These files are separate from the API key method’s ~/.tf/refresh-token.
  • Claude Desktop stores its own session and asks the user to sign in on first launch.

API key

Set that client’s variable to api-key. If every client is api-key, leave OIDC_ISSUER and OIDC_CLIENT_ID empty. The script does not pass them. The binary runs on the hourly schedule. On each run it signs in as the logged-in user. It tries ~/.tf/refresh-token first. If that file is missing or the refresh fails, it opens a browser for the device-login flow. It then writes the access token into the managed config: The refresh token is ~/.tf/refresh-token and the access token is ~/.tf/access-token, in the logged-in user’s home directory. The auth server issues that refresh token for 30 days, and each successful run rotates it. A run at least once in those 30 days does not open a browser. This is a different file and a different lifetime from the OIDC refresh token. Codex’s ChatGPT subscription setup requires this method.

Deployment scripts

The scripts below write and lock managed-settings.json, then write the org MCP connectors for Claude Code and Claude Desktop. On macOS, Linux, and Windows, pick OIDC or API key. Fill in OIDC_ISSUER and OIDC_CLIENT_ID from Set up OIDC before the first OIDC run. To use the API key method for one client inside an OIDC script, set that client’s variable to api-key. Schedule the script to run hourly.
  • OIDC. That run keeps the gateway URL, models, and permissions current. The client fetches its own token.
  • API key. That run also refreshes the token written into the managed files.
Write connectors after the binary, never before. tfy-local-ai-setup does not emit MCP keys, and on macOS it rewrites and re-locks the entire managed-preferences plist on every run, anything written before it is silently gone. On Windows the registry values are written individually, so a connector value survives, but the ordering rule is the same on both platforms.
The scripts download the tfy-local-ai-setup binary from the truefoundry/tfy-local-ai-setup GitHub repo. The README there documents all available flags, the default managed-settings.json config, and advanced usage (custom model IDs, settings templates, manual runs).
Replace <your-gateway-url>, <your-control-plane-url>, and <your-tenant-name> before the first run. While any client is oidc, also set OIDC_ISSUER and OIDC_CLIENT_ID. Those two are read from the script on every run. They are not written to the saved config file. The script exits if they are empty.
Prerequisites Before running the MDM script, verify one thing: no server-managed settings active. Check the Anthropic Admin Console under Settings → Policies. If any server-side policies are enabled, they will conflict with or override the file-based managed-settings.json. Disable server-managed settings before proceeding. How the script works The script runs as root on a schedule (recommended: hourly). Steps 1, 2, 4, 5, and 6 are the same for both sign-in methods. Step 3 is the one that changes. Each operating system has a full script for each sign-in method. Use the OIDC or API key tab below; the script is collapsed until you expand it.
  1. Saves and loads config. On first run, the three required values (GATEWAY_URL, CONTROL_PLANE_URL, TENANT_NAME) are read from the script’s Config section and written to a root-owned config file (macOS: /Library/Preferences/com.truefoundry.tfy-local-ai-setup.conf; Linux: /etc/tfy/tfy-local-ai-setup.conf; Windows: C:\ProgramData\TrueFoundry\tfy-local-ai-setup.conf). On subsequent runs the file is loaded automatically, no changes to the script are needed. A non-empty value in the Config section overrides the saved file (useful for one-off updates).
  2. Installs or updates tfy-local-ai-setup. Downloads the binary from the tfy-local-ai-setup GitHub releases if it is not present or if the installed version does not match RELEASE_TAG. On subsequent runs where the version already matches, this step is skipped entirely.
  3. Writes the sign-in config. The macOS, Linux, and Windows tabs below each spell this out for OIDC and for API key. OIDC. The hourly run writes the config in What OIDC writes. It does not fetch a token. When Claude Code or Codex needs a token, that client starts the binary, and the binary calls the OIDC flow. Claude Desktop signs in itself. API key. The hourly run signs in with device login and writes the gateway token as described in API key. The first run opens a browser. Later runs refresh ~/.tf/refresh-token.
  4. Writes and locks managed-settings.json. The binary builds the JSON with the AI Gateway URL, model IDs, and the sign-in config, then writes it to the system-level path and locks the file so developers cannot modify it (macOS: chflags schg; Linux: chattr +i; Windows: icacls ACL).
  5. Writes the org MCP connectors. From one CONNECTORS list the script derives both payloads and writes each to its own surface: managed-mcp.json for Claude Code, and managedMcpServers in the managed preferences for Claude Desktop (macOS plist, Windows HKLM\SOFTWARE\Policies\Claude). The two schemas differ, see the table below, so keep the list as the single source of truth and let the script build the JSON.
  6. Reads everything back. Both a plutil -replace against a still-locked file and a registry write into a redirected 32-bit view can report success while changing nothing, so the script re-reads every value it wrote and exits non-zero on a mismatch. Run it with --verify (-VerifyOnly on Windows) at any time to print the machine’s current state without writing.
oauth: true needs no clientId and no pre-registered redirect URI: Claude registers itself against the AI Gateway’s authorization server through dynamic client registration. Leave toolPolicy out unless you are naming real tool names, its keys are individual tools, and an undocumented {"*": "allow"} risks the whole entry being rejected by the schema.
Run via Jamf, Mosyle, Kandji, or any MDM supporting script execution. Must run as root. Detects Apple Silicon vs Intel automatically.
The hourly run writes the issuer and client ID. It does not fetch a token. The binary is /usr/local/bin/tfy-local-ai-setup.
  • Claude Code starts that binary through apiKeyHelper and reads the access token from stdout. The binary calls the OIDC flow.
  • Codex starts the same binary through [model_providers.truefoundry.auth]. The binary calls the OIDC flow.
  • Claude Desktop signs in itself. The managed plist holds inferenceGatewayOidc, and inferenceCredentialKind is interactive. There is no token in the file.
  • The first Claude Code or Codex call opens a browser. Later calls refresh ~/.tf/oidc-refresh-token in that user’s home directory.
Set OIDC_ISSUER and OIDC_CLIENT_ID from Set up OIDC.
Pass --claude-code, --codex, or --claude-desktop to target specific tools. From v1.3.6 an explicit flag configures that tool whether or not it is currently installed, so a client installed later is governed from its first launch, useful when you are rolling a tool out and the MDM job runs before the install lands. --codex covers both Codex surfaces, the CLI and the desktop app, which share the same managed config.With none of those flags the binary auto-detects what is installed and configures each one, and a machine with no AI client is a clean no-op, so the same script is safe to push fleet-wide.

Verify the deployment

After the first run, confirm the managed files are locked and the sign-in method you chose is present.
1

Check that the managed settings are locked

To re-check without writing, run the deployment script with --verify (-VerifyOnly on Windows).
2

Check the sign-in block

For Claude Code, look for apiKeyHelper pointing at tfy-local-ai-setup with --_print-oidc-token. There should be no ANTHROPIC_CUSTOM_HEADERS token. For Claude Desktop, look for inferenceCredentialKind=interactive and inferenceGatewayOidc with bearerTokenType of access_token and scopes that include openid (no inferenceGatewayApiKey).
3

Send a test prompt

Open Claude Code or Claude Desktop and send a short prompt. The request should appear in the AI Gateway’s request logs, attributed to the logged-in developer.
Prefer a server-side option? Claude’s Admin Console (Settings → Policies) can push tool permissions, MCP allowlists, and model restrictions to all members without deploying files, useful for BYOD. Note that server-managed settings require a direct connection to api.anthropic.com and are bypassed when you route through a gateway via ANTHROPIC_BASE_URL.

Let users bring their claude.ai history across (claudeAiImport)

Teams moving onto a gateway-backed Claude Desktop usually arrive with history on claude.ai. The Import from Claude wizard copies those conversations and projects into the local session store so that work continues against your AI Gateway, but it is off by default and not user-settable. It exists only as managed config, so MDM (or the Claude Admin Console) is the only way to turn it on.
The deployment scripts above do not write this key, they cover inference and MCP connectors only. Add claudeAiImport to the same managed-preferences store yourself, after the binary runs, for the same reason connectors go last: on macOS tfy-local-ai-setup rewrites and re-locks the whole plist on every run.
claudeAiImport is an object with four subfields, all off unless set. The posture below is the one we deploy for customers rolling Desktop out fleet-wide: Like the other object-valued keys, it is stored as a JSON-encoded string:
Keep the inner booleans as real JSON booleans, "enabled":"true" is a string and will not parse as on. MDM delivery of this key needs Claude Desktop 1.25927.0 or later; earlier builds accept it only from a bootstrap server, and an older build silently ignores it.
Two switches are needed and only one of them is yours. If the source history lives in a claude.ai Team or Enterprise workspace, a workspace owner must also enable Settings → Organization → Data and privacy → Allow members to export their own data. Personal claude.ai accounts can always export. No managed key fixes that side.
Also weigh the egress: the Sign in to claude.ai path has the app authorize against claude.ai and download the export itself, so devices need egress to claude.ai for that step. On a locked-down fleet the downloaded-.zip path needs no such egress and is the one that works.
Terminal Claude Code history is not covered. “Code sessions” throughout this wizard means Claude Desktop’s Code tab. There is no Claude-Code-specific import key, Claude Code rides inside claudeAiImport, and neither half of the feature reaches the CLI: the wizard only finds sessions left by an earlier Claude Desktop install, and the export explicitly excludes terminal Claude Code sessions. A developer’s own ~/.claude history (%USERPROFILE%\.claude on Windows) stays where it is, so don’t plan a CLI migration around this flag. The similarly named isClaudeCodeForDesktopEnabled gates whether the Code tab appears at all and has nothing to do with import.
Inference still comes from your gateway: the first message in an imported conversation raises a Resume imported session? prompt, and the reply is served by your configured model. The transcript is replayed as context on that turn, so pin models with enough context for what users bring over and expect those requests in AI Gateway → Analytics. Anthropic’s Import history from claude.ai.

Stop connector schemas from crowding the context window (toolSearchEnabled)

Every connector you push through managedMcpServers costs context. By default Claude Desktop inlines the full JSON schema of every MCP tool into each session up front, so a fleet-wide connector set can spend a large slice of the window before the user types anything. Tool search puts only tool names in context and has Claude fetch a tool’s full schema the first time it needs it. Like claudeAiImport, this is managed-only and off by default, so MDM is the only way to turn it on. Unlike it, the value is a plain boolean rather than a JSON-encoded string:
If you deploy a .mobileconfig or a Group Policy template instead of writing the store directly, the key is a first-class subkey of the com.anthropic.claudefordesktop payload and appears in Anthropic’s ADMX as a normal boolean policy, no custom payload needed. MDM delivery needs Claude Desktop 1.21459.0 or later; earlier builds ignore the key. The same ordering caveat as connectors applies: the deployment scripts above don’t write it, and on macOS tfy-local-ai-setup rewrites and re-locks the whole plist on every run, so it has to go in after the binary.
Setting ENABLE_TOOL_SEARCH yourself will not work, this key is the only switch. When Desktop runs against a gateway it launches every Code session with CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1 and strips ENABLE_TOOL_SEARCH out of the session environment, precisely because toolSearchEnabled is unset. A developer who adds ENABLE_TOOL_SEARCH to their own ~/.claude/settings.json will therefore see no change and report tool search as broken. Setting toolSearchEnabled makes the app lift that suppression on its own, depending on the bundled Claude Code version it either stops disabling the betas or passes ENABLE_TOOL_SEARCH=force down through Claude Code managed settings. Editing the rendered config under configLibrary on a single machine is not a workaround either: that file is regenerated from managed config on the next sync.
Enabling it changes the request shape your gateway receives, sessions add the tool-search-tool-2025-10-19 anthropic-beta value and send tool_reference content blocks. Whether that survives the hop depends on which provider backs the model:
  • Anthropic direct: works as-is. TrueFoundry forwards anthropic-beta to api.anthropic.com unfiltered and does not validate /v1/messages content blocks, so the beta and the new blocks pass straight through.
  • Bedrock, Vertex, Azure Foundry, Microsoft Foundry, Bedrock Mantle: check first. These providers run anthropic-beta through a per-provider allowlist and silently drop any value not configured for them. If tool-search-tool-2025-10-19 isn’t on that provider’s list, the beta is stripped while the tool_reference blocks still go up, and the upstream rejects the request with HTTP 400.
Either way, pilot it with one group before the fleet. Anthropic’s reference: MCP tool search.

Using Claude Code MDM with a Claude Enterprise Account

If your organization has a Claude Enterprise subscription (rather than a direct Anthropic API key), the MDM setup requires one additional configuration step on TrueFoundry: you create an Anthropic provider that acts as a pass-through, and the authentication is supplied at request time by each developer’s own Claude Enterprise account, not stored as a credential on TrueFoundry. This approach also gives you per-user usage attribution on both sides, TrueFoundry logs which user made each request, and Anthropic attributes usage to the individual Claude Enterprise seat. This works because the authorization token is user-specific: each developer obtains it by logging into their own Claude Enterprise account, so every request carries their identity end-to-end through the AI Gateway.
Two sign-ins, and they are not the same login. Each developer runs /login in Claude Code once and chooses Claude Enterprise. That session is their Anthropic credential.How the machine signs in to TrueFoundry is separate. Prefer OIDC: the binary runs when Claude Code needs a token, and the managed file holds the issuer and client ID. With API key, the first MDM run opens a browser, and later hourly runs refresh ~/.tf/refresh-token for 30 days.
1

Create an Anthropic provider on TrueFoundry

In the TrueFoundry platform, navigate to Integrations → Providers and create a new provider of type Anthropic.When prompted for authentication credentials, leave the API key field empty. Do not enter any API key or secret. Each request carries the developer’s TrueFoundry credential. With OIDC that credential comes from apiKeyHelper. With the API key method the MDM script writes it as X-TFY-API-KEY.
2

Copy the model IDs

After saving the provider, TrueFoundry assigns model IDs for each Claude model tier (for example, claude-enterprise/claude-opus-4-6, claude-enterprise/claude-sonnet-4-6, claude-enterprise/claude-haiku-4-5). Copy these model IDs from the provider detail page.
3

Update the MDM script config

Set the gateway and the model IDs in the script’s Config section. These lines are the same for both sign-in methods.
Then set sign-in. Prefer OIDC; use API key only if you need the device-login flow.
Either way the binary writes these model env vars into managed-settings.json:
The Anthropic provider on TrueFoundry must have an empty API key. If a stored API key is present, TrueFoundry will use it instead of the per-request token from the MDM script, which breaks the Claude Enterprise subscription flow and may route traffic under the wrong account.
What the binary writes for TrueFoundry sign-in depends on the method. The Claude Enterprise session still comes from /login.
The hourly run writes apiKeyHelper. It does not write ANTHROPIC_CUSTOM_HEADERS. When Claude Code needs a token it starts the binary, and the binary calls the OIDC flow. The managed file holds the issuer and client ID, not a token.
Using a virtual model with Claude Enterprise The MDM setup works with virtual models as well. Point the model IDs in the MDM script to a virtual model instead of a direct provider model, you get two immediate benefits:
  • Fallbacks: if one target is unavailable, traffic automatically falls through to the next
  • Model changes without touching the MDM script: update the virtual model’s routing on TrueFoundry and the change takes effect immediately across all managed machines, with no script redeployment
One important constraint: only one of the virtual model’s targets can be a Claude Enterprise model account. The Claude Enterprise session from /login belongs to one account. If several targets were Claude Enterprise providers, each would receive that same session and all but one would fail. This is true for OIDC and for the API key method. Targets that are not Claude Enterprise accounts can sit beside it. Troubleshooting: 401 / 403 errors If Claude Code returns a 401 Unauthorized or 403 Forbidden, check which login expired.
  1. Claude Enterprise session. Open Claude Code, run /login, choose Claude Enterprise, and finish the browser login. Do this for OIDC and for the API key method.
  2. TrueFoundry sign-in, OIDC. Use Claude Code again. Claude Code starts the binary, and the binary opens a new browser sign-in. Do not re-run the binary by hand.
  3. TrueFoundry sign-in, API key. The next hourly run refreshes ~/.tf/refresh-token. To do it now, run the API key commands in the browser-login FAQ.

Observability

Once Claude traffic flows through the gateway, every model request and MCP tool call is traced with user, model, server, and tool attribution, and metrics for models, MCP, and guardrails are available in AI Gateway → Analytics, no extra instrumentation. Traces export to any OTEL-compatible platform for your SIEM. See Analytics overview, Request logging, and Export OpenTelemetry data. Set spending guardrails per user and team with budget limits and rate limits. For local transcripts, transcriptRetentionDays in managed-settings.json (7-14 days recommended, set to 14 in the baseline above) auto-deletes Claude Code’s on-disk history. Anthropic-side retention, Zero Data Retention, and compliance posture are configured with Anthropic, see Claude’s data retention docs.

Frequently asked questions

A direct connection gives you no central place to enforce policy. Routing through the TrueFoundry AI Gateway lets you apply access control, rate and budget limits, multi-provider routing with failover, and full tracing, across web and CLI, from a single control point, without changing client config.
  • OIDC. For Claude Code and Codex, the binary runs when the client needs a token, and the binary calls the OIDC flow. The managed files hold the issuer and client ID, not a token. Claude Desktop signs in itself and sends the access token to the gateway (bearerTokenType: access_token; openid must stay in scopes so sign-in still receives an ID token). The refresh token is ~/.tf/oidc-refresh-token. Its lifetime is the client’s refresh_token_ttl (7 days in the example above).
  • API key. The binary runs on the hourly schedule. It signs in with device login and writes that user’s gateway token into the managed files. The hourly run refreshes it from ~/.tf/refresh-token.
See Sign-in.
  • OIDC. The file is ~/.tf/oidc-refresh-token. It lasts for refresh_token_ttl on the OIDC client. In the client created above that is 7 days. Each refresh replaces it, because refresh_token_max_uses is 1. Claude Desktop stores its own session and does not use this file.
  • API key. The file is ~/.tf/refresh-token. It lasts 30 days from when it was last issued or rotated. tfy-local-ai-setup uses the TrueFoundry auth server’s refresh token and does not set its own expiry. Each successful run rotates it. If the script runs at least once within those 30 days, no browser sign-in is required. After 30 days without a refresh, the next run opens the device-login flow.
It centralizes MCP governance: a registry of approved servers, unified outbound authentication, role-based access, tool-level controls, guardrails, and a full audit trail of every tool call. You allowlist only the AI Gateway URL, so developers can’t connect to unvetted servers.
No.Use the tab that matches the machine’s sign-in method. The API key commands would replace an OIDC config with a device-login token.
Use the client again. Claude Code or Codex starts the binary, and the binary opens a new browser sign-in. Claude Desktop asks again on its next launch. Do not re-run tfy-local-ai-setup by hand.
No. When managed-settings.json is deployed to the system path and locked (and allowManagedPermissionRulesOnly is set), it takes highest priority and cannot be overridden by project- or user-level settings.
  • OIDC. The hourly run does not fetch a token and does not open a browser. The browser opens the first time Claude Code or Codex needs a token, and again when ~/.tf/oidc-refresh-token has expired. Claude Desktop asks on first launch, then keeps its own session.
  • API key. The browser opens on the first run, or when ~/.tf/refresh-token has expired (30 days without a successful refresh). Later hourly runs refresh that file and do not open a browser. If the window opens on every run, the refresh token is not being saved. Run the API key commands in the FAQ above.