How settings are enforced
Claude Code reads settings in priority order, with the system-levelmanaged-settings.json taking precedence and being un-overridable by developers:
/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:
Sign-in
Each client picks one method in the script’s Config section. The three can differ.OIDC
OIDC needstfy-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.Create a Personal Access Token
Create the OIDC client
<tenant_name> and <PAT Token>, then run: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/callbackis what Claude Code and Codex use. If port 8585 is in use, the binary tries 8586 and then 8587. Anativeclient matches a loopback redirect on any port, so those two fallback ports do not need their own entries.http://127.0.0.1/callbackis what Claude Desktop uses.
{ "data": { ... } }.data.idis the client ID (cli_…). Put it inOIDC_CLIENT_ID.data.manifest.nameisclaude-mdm.nameis not a top-level field.- Sending this request again with the same
manifest.nameupdates the client and keeps the samedata.id. client_typecannot be changed after creation.
Set the issuer and client ID in the script
/<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.
- macOS and Linux
- Windows
Codex
The same binary configures Codex. What OIDC writes into the managed TOML, including Windowsrequirements.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.
Pass --oidc-bearer-token-type=access_token (the scripts below do this). The binary then also writes scopes (default openid profile email offline_access).
- macOS
- Windows
How OIDC tokens refresh
How OIDC tokens refresh
- 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-tokenand 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_ttlis 7 days andrefresh_token_max_usesis 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 toapi-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:
~/.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 lockmanaged-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.
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).
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.
-
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). -
Installs or updates
tfy-local-ai-setup. Downloads the binary from thetfy-local-ai-setupGitHub releases if it is not present or if the installed version does not matchRELEASE_TAG. On subsequent runs where the version already matches, this step is skipped entirely. -
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. -
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:icaclsACL). -
Writes the org MCP connectors. From one
CONNECTORSlist the script derives both payloads and writes each to its own surface:managed-mcp.jsonfor Claude Code, andmanagedMcpServersin the managed preferences for Claude Desktop (macOS plist, WindowsHKLM\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. -
Reads everything back. Both a
plutil -replaceagainst 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(-VerifyOnlyon 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.- macOS
- Linux
- Windows
- OIDC
- API key
/usr/local/bin/tfy-local-ai-setup.- Claude Code starts that binary through
apiKeyHelperand 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, andinferenceCredentialKindisinteractive. There is no token in the file. - The first Claude Code or Codex call opens a browser. Later calls refresh
~/.tf/oidc-refresh-tokenin that user’s home directory.
OIDC_ISSUER and OIDC_CLIENT_ID from Set up OIDC.--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.Check that the managed settings are locked
- macOS
- Linux
- Windows
--verify (-VerifyOnly on Windows).Check the sign-in block
- OIDC
- API key
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).Send a test prompt
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.
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:
"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.
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.
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.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:
.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.
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-betatoapi.anthropic.comunfiltered and does not validate/v1/messagescontent 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-betathrough a per-provider allowlist and silently drop any value not configured for them. Iftool-search-tool-2025-10-19isn’t on that provider’s list, the beta is stripped while thetool_referenceblocks still go up, and the upstream rejects the request with HTTP 400.
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.Create an Anthropic provider on TrueFoundry
apiKeyHelper. With the API key method the MDM script writes it as X-TFY-API-KEY.Copy the model IDs
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.Update the MDM script config
- macOS and Linux
- Windows
- OIDC
- API key
- macOS and Linux
- Windows
managed-settings.json:/login.
- OIDC
- API key
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.- 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
/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.
- 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. - 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.
- 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
Why route Claude Code through TrueFoundry instead of straight to Anthropic?
Why route Claude Code through TrueFoundry instead of straight to Anthropic?
How does authentication work without distributing API keys?
How does authentication work without distributing API keys?
- 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;openidmust stay in scopes so sign-in still receives an ID token). The refresh token is~/.tf/oidc-refresh-token. Its lifetime is the client’srefresh_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.
How long do the refresh tokens last?
How long do the refresh tokens last?
- OIDC. The file is
~/.tf/oidc-refresh-token. It lasts forrefresh_token_ttlon the OIDC client. In the client created above that is 7 days. Each refresh replaces it, becauserefresh_token_max_usesis 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-setupuses 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.
What does the TrueFoundry MCP Gateway add over connecting MCP servers directly?
What does the TrueFoundry MCP Gateway add over connecting MCP servers directly?
I accidentally closed the browser login window, do I have to wait for the next MDM sync?
I accidentally closed the browser login window, do I have to wait for the next MDM sync?
- OIDC
- API key
tfy-local-ai-setup by hand.Can developers override these settings locally?
Can developers override these settings locally?
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.A browser login window opens every time the MDM script runs, is this expected?
A browser login window opens every time the MDM script runs, is this expected?
- 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-tokenhas 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-tokenhas 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.