fix(mcp-config-validator): stop flagging auto-injected + POSIX env vars

Clears false positives on valid .mcp.json (gap matrix, Batch 1):
- ${CLAUDE_PROJECT_DIR} is auto-injected at runtime (CC 2.1.139) and never
  needs an env block — now allowlisted.
- POSIX expansions like ${VAR%pattern} / ${VAR:-default} are resolved by
  Claude Code (CC 2.1.142); the env-var regex now matches only bare
  ${IDENTIFIER}, so operator expressions are skipped.

Genuine bare unreferenced vars are still flagged (broken-project regression
intact). The MCP `trust` field is untouched — it is verify-first (point 4),
not part of Batch 1.

Tests: hermetic runtime temp-fixture; 23/23 MCP green, both directions covered.

Ref: docs/cc-2.1.x-gap-matrix.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ter3E2JSi1Khgmuf2kady8
This commit is contained in:
Kjell Tore Guttormsen 2026-06-18 12:03:21 +02:00
commit 4b94da0f11
2 changed files with 64 additions and 3 deletions

View file

@ -18,7 +18,13 @@ const VALID_SERVER_FIELDS = new Set([
'type', 'command', 'args', 'env', 'url', 'headers', 'timeout', 'trust',
]);
const ENV_VAR_PATTERN = /\$\{([^}]+)\}/g;
// Match only bare ${IDENTIFIER} references. POSIX expansions like ${VAR%pattern}
// or ${VAR:-default} contain operators and are skipped — Claude Code resolves
// them at launch (CC 2.1.142), so they are not config-defined env references.
const ENV_VAR_PATTERN = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
// Auto-injected by Claude Code at runtime — never require an env block (CC 2.1.139).
const AUTO_INJECTED_ENV_VARS = new Set(['CLAUDE_PROJECT_DIR']);
/**
* Scan all .mcp.json files discovered.
@ -116,6 +122,7 @@ export async function scan(targetPath, discovery) {
ENV_VAR_PATTERN.lastIndex = 0;
while ((match = ENV_VAR_PATTERN.exec(arg)) !== null) {
const varName = match[1];
if (AUTO_INJECTED_ENV_VARS.has(varName)) continue;
const hasEnvBlock = config.env && typeof config.env === 'object' && varName in config.env;
if (!hasEnvBlock) {
findings.push(finding({