AnthropicClaude Certified Architect – Foundations2026-102.4 Integrate MCP servers into Claude Code and agent workflows
Expand credentials without committing their values
Completed by the knowledge check
Learning objective
Use environment variable expansion for credentials rather than committing secrets into MCP configuration.
Core idea
Commit a credential reference, not the credential it names. A project-scoped .mcp.json can describe the same MCP integration for the whole team while each developer supplies an authentication value through the environment that launches Claude Code. The server definition remains reviewable; the secret value stays outside the shared file.
Expansion is a configuration mechanism, not a guarantee that authentication succeeds. An unset variable, a protected credential name, or a token rejected by the server can all leave the definition present while the connection fails. Diagnose those cases through the documented expansion rules and connection status rather than replacing the reference with a literal secret.
See 2.4.1 for where the file belongs. This lesson concerns how its values become executable configuration.
How it works
Put references in supported fields
Claude Code supports ${VAR} for an environment variable and ${VAR:-default} for a fallback when the variable is unset. Within .mcp.json, expansion applies to command, args, env, url, and headers. These fields cover executable paths, process arguments, values supplied to a stdio server, and HTTP connection configuration.
For an HTTP server that accepts a bearer token, an illustrative shared configuration is:
{
"mcpServers": {
"team-issues": {
"type": "http",
"url": "https://issues.example.com/mcp",
"headers": {
"Authorization": "Bearer ${ISSUES_MCP_TOKEN}"
}
}
}
}Replace the example URL with your approved endpoint. ISSUES_MCP_TOKEN is an application-chosen environment variable containing the token issued for that integration; it is not an Anthropic setting. The JSON deliberately contains the reference. Claude Code resolves it from its environment when constructing the server configuration.
For a stdio server, put a reference in the server entry’s env object, such as "API_KEY": "${ISSUES_MCP_TOKEN}", when that server expects API_KEY. The left-hand name is the server’s input contract; the right-hand name is the variable your launch environment supplies. Verify both against that server’s documentation instead of assuming every server uses bearer headers.
Distinguish defaults from credentials
A fallback can make a nonsecret path or service base URL portable. For example, ${API_BASE_URL:-https://api.example.com} selects a documented default base URL when the variable is unset. A default is a real value sent onward, not a reminder to configure the integration.
Do not place a working credential inside :-default. That would commit the credential in the same configuration you intended to share safely. An empty or illustrative fallback also does not establish valid authentication. For a credential, supply the intended variable and examine whether the server accepts it.
If a referenced variable is unset and has no default, Claude Code still loads the configuration. It reports a missing-variable warning for that server in claude mcp list and uses the unexpanded ${VAR} text as-is. This is not a fail-closed parser that refuses every incomplete definition. A literal placeholder can reach the server and trigger an authentication failure.
The remedy is to set the variable in Claude Code’s launch environment or, for a suitable nonsecret field, provide a fallback. Do not assume that a variable present in a separate terminal or unrelated process is available to the running Claude Code process.
Recognize protected credential names
In a remote server’s url and headers, Claude Code treats certain credential variables as empty. The documentation names Claude Code credentials such as ANTHROPIC_API_KEY and ANTHROPIC_AUTH_TOKEN, cloud credentials such as AWS_BEARER_TOKEN_BEDROCK, and other protected values such as HTTPS_PROXY and NPM_TOKEN.
For example, "Authorization": "Bearer ${ANTHROPIC_AUTH_TOKEN}" produces Bearer rather than exporting that token to the configured remote server. The server usually rejects it with a 401, and Claude Code reports a failed connection. A :-default fallback on a protected name is ignored, whether or not the variable is set. This differs from the missing-variable warning path.
Use a separately named variable for a credential intentionally issued to the MCP integration. A name outside the protected set, such as API_KEY, expands normally. The decision should begin with which credential the server is authorized to receive, rather than with how to bypass the protection around an unrelated Claude or cloud credential.
Verify without replacing references with values
claude mcp list, claude mcp get, and the /mcp detail view show ${VAR} references by name rather than as resolved values for local, project, and user servers. The /mcp detail behavior requires Claude Code v2.1.268 or later. Seeing the placeholder there is therefore not proof that expansion failed.
Inspect the connection status and missing-variable warning instead. When a protected reference is set, claude --debug-file /tmp/claude-debug.log can reveal a line containing never expanded toward a remote server. A connection failure and a displayed reference need different interpretations: one concerns execution, the other concerns how configuration is presented.
If the Agent SDK configures servers directly in code, use that language’s environment mechanism. Anthropic’s TypeScript examples use process.env.API_TOKEN in mcpServers; Python examples use os.environ["API_TOKEN"] in mcp_servers. A literal ${VAR} in an arbitrary programming-language string is not the .mcp.json expansion mechanism.
Key distinctions and trade-offs
Reference versus value: The reference is safe to review as configuration; the secret value must be supplied separately. A reference does not make the destination trustworthy.
Unset versus protected: The same reference resolves differently depending on the variable and the field:
Reference in .mcp.json | What Claude Code uses | What you see |
|---|---|---|
${ISSUES_MCP_TOKEN}, variable set | The variable’s value | A working or rejected connection |
${API_BASE_URL:-https://api.example.com}, unset | The fallback | The default endpoint |
${ISSUES_MCP_TOKEN}, unset, no fallback | The literal ${ISSUES_MCP_TOKEN} text | A missing-variable warning |
${ANTHROPIC_AUTH_TOKEN} in remote url/headers | An empty value; any fallback is ignored | No warning; usually a 401 from the server |
Only the first row delivers the integration’s credential, and only the second suits a committed fallback.
Configuration display versus connection evidence: A displayed ${VAR} can be intentional masking. Status and server responses tell you whether the connection worked.
Architecture / decision scenario
A team commits the issue-server example for a code-review workflow. Each engineer supplies ISSUES_MCP_TOKEN from their own launch environment. The CI process supplies a token intended for that same integration.
One developer forgets the variable. claude mcp list reports the missing reference, and the team repairs the environment rather than committing a token. Another substitutes ANTHROPIC_AUTH_TOKEN, believing any bearer token works. That protected reference becomes empty in the remote header; the team restores the integration-specific variable and credential.
The resulting review checks both the committed reference and a successful connection. It does not demand that /mcp display a secret as proof. If the workflow moves to direct SDK configuration, the team uses the language-specific environment expression rather than copying JSON interpolation into an unevaluated string.
Common exam traps
- Committing a secret as the fallback in
${VAR:-default}. - Assuming an unset variable prevents the configuration from loading.
- Treating a displayed reference as an unresolved runtime value.
- Reusing Claude Code’s own authentication token for an unrelated server.
- Expecting direct SDK strings to perform
.mcp.jsoninterpolation.
What to remember
Use environment references in the fields Claude Code expands, and supply integration-specific credentials separately. Diagnose ordinary missing variables, protected names, and server authentication as distinct cases. Keep the shared file free of credential values while verifying that the intended server actually connects.
Knowledge check
A team commits .mcp.json for a remote issue server that authenticates each engineer with their own bearer token. The file must stay in version control, and no token may be stored in it. How should the Authorization header be configured?
Bearer ${ISSUES_MCP_TOKEN} , with each engineer setting the variable for Claude CodeBearer ${ISSUES_MCP_TOKEN:-<team token>} , with the team token used when the variable is unsetBearer <team token> , with read access to the repository limited to the engineering teamBearer ${ANTHROPIC_AUTH_TOKEN} , with each engineer’s existing Claude Code token reused${VAR} reference in headers is expanded from the environment that launches Claude Code, so the shared file holds the reference and each engineer supplies the value. A fallback after :- is a real value committed with the file, a literal token is a committed secret whatever the repository’s access rules, and the server needs a credential issued for its own integration.After a fresh clone, a developer’s issue server fails with a 401. claude mcp list shows a warning for that server naming ISSUES_MCP_TOKEN. The header is Bearer ${ISSUES_MCP_TOKEN} with no fallback. What happened, and what is the fix?
.mcp.jsonclaude mcp list , naming the variable, and sends the unexpanded ${ISSUES_MCP_TOKEN} text, which the server rejects. Set the named variable in the environment that launches Claude Code, then verify the connection rather than replacing the reference in the shared file.A developer changes a remote server’s header to Bearer ${ANTHROPIC_AUTH_TOKEN} because that variable is already set in their shell. The server now returns 401, and claude mcp list shows no missing-variable warning. Why does authentication fail?
url but not in its headersheaders only when it declares a :-default fallbackenv object of a stdio server’s entryheadersANTHROPIC_AUTH_TOKEN , and other protected names as empty in a remote server’s url and headers , whether or not they are set, so the server receives Bearer and rejects it. This keeps a project’s configuration from sending Claude Code’s credentials to a server it names. The fix is a separately named variable holding a token issued for this integration; headers are otherwise expanded normally.A connected issue server is working. During review, a teammate runs claude mcp get team-issues, sees Bearer ${ISSUES_MCP_TOKEN} in the output, and concludes that expansion is broken. What is the correct reading?
${ISSUES_MCP_TOKEN} text/mcp , so the command line shows the raw fileclaude mcp list , claude mcp get and the /mcp detail view show a ${VAR} reference by name rather than its resolved value, so a displayed placeholder is masking, not a failure. Whether expansion worked is answered by the connection status, a missing-variable warning, and the server’s response.References
- Anthropic, Connect Claude Code to tools via MCP
- Anthropic, Connect to external tools with MCP