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:
Deployment scripts
The scripts below deploy and lockmanaged-settings.json, then write the org MCP connectors for both Claude Code and Claude Desktop. On every run the script fetches a fresh gateway token for the logged-in user and writes it into ANTHROPIC_CUSTOM_HEADERS — no PAT or manual credentials needed. Schedule the script to run hourly so the token stays fresh; the browser device flow only appears on first run or after the refresh token at ~/.tf/refresh-token expires (30 days by default).
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).
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) and does six things on every execution:
-
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. -
Detects the logged-in user and fetches a fresh auth token — The binary identifies who is currently logged into the machine, then silently refreshes the token from
~/.tf/refresh-token. That refresh token is valid for 30 days. Each successful run rotates it and writes a new 30-day token, so developers do not re-login as long as the script runs at least once in that window. The browser device-authorization flow only appears the very first time, or after 30 days without a refresh. Every other run completes silently in the background. -
Writes and locks
managed-settings.json— The binary builds the JSON with the AI Gateway URL, model IDs, and the freshly fetched token, 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
--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.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
X-TFY-API-KEY token injected by the MDM script at the time of each request — no stored credential is needed or desired.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
tfy-local-ai-setup binary fetches a fresh OAuth token on every MDM run and writes it into ANTHROPIC_CUSTOM_HEADERS as X-TFY-API-KEY. TrueFoundry’s gateway forwards this header to Anthropic’s API to authenticate each request against the developer’s Claude Enterprise subscription. No long-lived credentials are stored anywhere on the managed machine.- 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
X-TFY-API-KEY) is issued by Anthropic for a specific Claude Enterprise account. If multiple targets were Claude Enterprise providers, all of them would receive the same token — which is only valid for one account, causing the others to fail. Non-Claude-Enterprise targets (e.g. a fallback to a different provider) can coexist without issue.
Troubleshooting: 401 / 403 errors
If Claude Code returns a 401 Unauthorized or 403 Forbidden error, the session token has expired or was never initialized. The developer needs to re-authenticate manually:
- Open Claude Code and run
/login - When prompted to choose an authentication method, select Claude Enterprise (the first option)
- Complete the browser login flow
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?
tfy-local-ai-setup binary and runs it on a schedule (hourly) to fetch a fresh gateway token scoped to the user’s TrueFoundry identity. The token is written into ANTHROPIC_CUSTOM_HEADERS as X-TFY-API-KEY and refreshed automatically — no personal or long-lived API keys on developer machines. The refresh token cached at ~/.tf/refresh-token is valid for 30 days; hourly runs rotate it so developers are not prompted to log in again.How long is the refresh token at ~/.tf/refresh-token valid?
How long is the refresh token at ~/.tf/refresh-token valid?
tfy-local-ai-setup uses the TrueFoundry Auth Server refresh token rather than setting its own expiry. Each successful MDM run rotates the token and writes a new 30-day value to ~/.tf/refresh-token. If the script runs at least once within those 30 days, no re-login is required. After 30 days without a refresh, the next run opens the browser device-authorization flow. Contact TrueFoundry support if you need a different lifetime.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?
- macOS
- Linux
- Windows
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?
~/.tf/refresh-token has expired (30 days without a successful refresh). Once the developer completes the login, subsequent MDM runs silently refresh the token without any browser prompt. If the window keeps appearing on every run, the refresh token is not being saved — run the setup manually (see the FAQ above) to re-authenticate and reset the token.