[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$f8dvu5mn2fols":3},{"_id":4,"slug":5,"title":6,"subtitle":7,"kind":8,"cards":9,"tags":59,"categories":61,"source":63,"lang":66,"author":67,"audioState":70,"stats":71,"publishedAt":74,"renderer":75},"6ab9d748ca21c797c7e97248","six-var-forms-five-mcpjson-fields-two-tiny-servers-what-clau-3c41ce8e","Six ${VAR} forms, five .mcp.json fields, two tiny servers: what Claude Code MCP expansion actually produced","I put six environment-variable forms into the args, env and command fields of one project .mcp.json (plus url and headers on a second, HTTP server) and read back what the server process actually received.","news",[10,14,19,24,29,34,39,44,49,54],{"headline":11,"body":12,"imageUrl":13,"sourceImageUrl":13},"Six ${VAR} forms, five .mcp.json fields, two tiny servers: what Claude Code MCP…","I put six environment-variable forms into the args, env and command fields of one project .mcp.json (plus url and headers on a second, HTTP server) and read back what the server process actually received. On Claude Code 2.1.278, ${VAR} and ${VAR:-default} expanded everywhere, $VAR never expanded anywhere, an unset ${VAR} arrived as the literal text, and a nested ${A:-${B}} expanded only in headers because that field is expanded twice.","https:\u002F\u002Fmedia2.dev.to\u002Fdynamic\u002Fimage\u002Fwidth=1200,height=627,fit=cover,gravity=auto,format=auto\u002Fhttps%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh8eibzc2cdm1phezm2w6.png",{"headline":15,"body":16,"imageUrl":17,"images":18},"The MCP page in the Claude Code docs","The MCP page in the Claude Code docs says two syntaxes are supported and five fields are expanded. That leaves a lot unsaid: what happens to a bare $VAR, to a nested default, to a variable that is unset, and whether all five fields behave the same. Rather than guess, I wrote a dependency-free stdio MCP server that reports its own process.argv and process.env, wired it into a .mcp.json several times over, and let Claude Code launch it. Everything below is what the logs and tool results said, not what I expected.","\u002Fapi\u002Fmedia\u002Fposts\u002Fsix-var-forms-five-mcpjson-fields-two-tiny-servers-what-clau-3c41ce8e\u002F1.webp",{"local":17},{"headline":20,"body":21,"imageUrl":22,"images":23},"I fetched https:\u002F\u002Fcode.claude.com\u002Fdocs\u002Fen\u002Fmcp on 2026-09-22 with trafilatura -u","I fetched https:\u002F\u002Fcode.claude.com\u002Fdocs\u002Fen\u002Fmcp on 2026-09-22 with trafilatura -u. The section \"Environment variable expansion in .mcp.json\" lists exactly two forms: ${VAR}: expands to the value of environment variable VAR ${VAR:-default}: expands to VAR if set, otherwise uses default ${VAR}: expands to the value of environment variable VAR ${VAR:-default}: expands to VAR if set, otherwise uses default and five locations: command, args, env, url and headers. For the unset case it says:","\u002Fapi\u002Fmedia\u002Fposts\u002Fsix-var-forms-five-mcpjson-fields-two-tiny-servers-what-clau-3c41ce8e\u002F2.webp",{"local":22},{"headline":25,"body":26,"imageUrl":27,"images":28},"If a referenced environment variable isn't set and","If a referenced environment variable isn't set and has no default value, the config still loads: Claude Code reports a missing-variable warning for that server in claude mcp list output and uses the unexpanded ${VAR} text as-is.","\u002Fapi\u002Fmedia\u002Fposts\u002Fsix-var-forms-five-mcpjson-fields-two-tiny-servers-what-clau-3c41ce8e\u002F3.webp",{"local":27},{"headline":30,"body":31,"imageUrl":32,"images":33},"There is no mention of $VAR without braces","There is no mention of $VAR without braces, and no mention of nesting. Those two were the gaps I most wanted to measure. The rerun, in under ten minutes","\u002Fapi\u002Fmedia\u002Fposts\u002Fsix-var-forms-five-mcpjson-fields-two-tiny-servers-what-clau-3c41ce8e\u002F4.webp",{"local":32},{"headline":35,"body":36,"imageUrl":37,"images":38},"Everything ran in a directory created with mktemp","Everything ran in a directory created with mktemp -d, so no project instructions or memory files were in play. The whole setup is three files.","\u002Fapi\u002Fmedia\u002Fposts\u002Fsix-var-forms-five-mcpjson-fields-two-tiny-servers-what-clau-3c41ce8e\u002F5.webp",{"local":37},{"headline":40,"body":41,"imageUrl":42,"images":43},"The stdio server, server.js, has no dependencies. It","The stdio server, server.js, has no dependencies. It speaks newline-delimited JSON-RPC over stdin\u002Fstdout, answers initialize, ping, tools\u002Flist and tools\u002Fcall, and exposes one tool, echo_env. On startup it also appends a snapshot to spawn-log.jsonl, which matters: the log gives ground truth even when no model turn happens.","\u002Fapi\u002Fmedia\u002Fposts\u002Fsix-var-forms-five-mcpjson-fields-two-tiny-servers-what-clau-3c41ce8e\u002F6.webp",{"local":42},{"headline":45,"body":46,"imageUrl":47,"images":48},"The .mcp.json registers that same script seven times","The .mcp.json registers that same script seven times. One entry, probe-args-env, carries all six forms in args and again as six keys in env. Six more entries differ only in the command field, because a command is a single string and can hold one form at a time.","\u002Fapi\u002Fmedia\u002Fposts\u002Fsix-var-forms-five-mcpjson-fields-two-tiny-servers-what-clau-3c41ce8e\u002F7.webp",{"local":47},{"headline":50,"body":51,"imageUrl":52,"images":53},"A .claude\u002Fsettings.local.json containing { \"enableAllProjectMcpServers\": true }…","A .claude\u002Fsettings.local.json containing { \"enableAllProjectMcpServers\": true } approves the project servers for non-interactive runs. Then, from the shell:","\u002Fapi\u002Fmedia\u002Fposts\u002Fsix-var-forms-five-mcpjson-fields-two-tiny-servers-what-clau-3c41ce8e\u002F8.webp",{"local":52},{"headline":55,"body":56,"imageUrl":57,"images":58},"One trap I fell into: with the prompt","One trap I fell into: with the prompt placed after --allowedTools, claude -p consumed the prompt as another tool pattern and exited with \"Input must be provided either through stdin or as a prompt argument\". Put the prompt directly after -p. Two of my six invocations were lost to that mistake and made no model call. What arrived in args and env","\u002Fapi\u002Fmedia\u002Fposts\u002Fsix-var-forms-five-mcpjson-fields-two-tiny-servers-what-clau-3c41ce8e\u002F9.webp",{"local":57},[60],"dev",[62],"Technology",{"name":64,"url":65},"Dev.to","https:\u002F\u002Fdev.to\u002Frulestack\u002Fsix-var-forms-five-mcpjson-fields-two-tiny-servers-what-claude-code-mcp-expansion-actually-5a1f","en",{"handle":68,"displayName":69},"spots","Spots","queued",{"views":72,"likes":73,"saves":73,"shares":73,"completions":73,"opens":73,"skips":73,"depthSum":73},5,0,"2026-09-28T02:56:08.144Z","local"]