Skip to content

MCP env object from mcp_settings.json not passed to server subprocess #10808

@CurtisASmith

Description

@CurtisASmith

App Version

3.41.1

API Provider

Not Applicable / Other

Model Used

N/A

🔁 Steps to Reproduce

  1. Setup:

    • OS: Windows 10
    • Roo Code Extension Version: 3.41.1
    • Godot MCP Server (Coding-Solo/godot-mcp) configured in mcp_settings.json
  2. Configuration:

    {
      "mcpServers": {
        "godot": {
          "command": "node",
          "args": ["D:\\ML\\MCP-Servers\\godot-mcp\\build\\index.js"],
          "env": {
            "GODOT_PATH": "D:\\Tools\\Godot_v4.5.1-stable_win64.exe",
            "DEBUG": "true"
          },
          "disabled": false,
          "autoApprove": [...]
        }
      }
    }
  3. Expected Behavior:

    • The GODOT_PATH and DEBUG environment variables from mcp_settings.json should be passed to the Godot MCP server subprocess
    • The server should use the configured Godot executable path
  4. Actual Behavior:

    • The Godot MCP server fails to locate the Godot executable
    • Error shows default path: "C:\Program Files\Godot\Godot.exe" --version - The system cannot find the path specified.
    • Setting a system-wide GODOT_PATH environment variable also doesn't work
    • No debug output appears despite DEBUG: "true" being set

💥 Outcome Summary

The env object configured in mcp_settings.json is not being passed to MCP server subprocesses. This prevents users from configuring environment variables for their MCP servers, making it impossible to use servers that require custom environment configuration.

📄 Relevant Logs or Errors

Error: "C:\Program Files\Godot\Godot.exe" --version - The system cannot find the path specified.

Root Cause Analysis

The bug is in src/utils/config.ts in the injectVariables() function (lines 45-58):

export function injectVariables(
  config: string,
  variables: {
    env: Record<string, string>;
    workspaceFolder: string;
    cwd: string;
    workspaceFolderName: string;
  }
): string {
  const envRegex = /\$\{env:([^}]+)\}/g;
  
  return config.replace(envRegex, (match, variable) => {
    const [envKey, ...subKey] = variable.split(':');
    if (subKey) {
      return variables.env?.[envKey]?.[subKey] || '';
    }
    return variables.env?.[envKey] || '';
  });
}

The Problem:

  • The function only handles nested patterns like ${env:VAR_NAME} or ${env:VAR_NAME:subkey}
  • When injectVariables() is called from src/services/mcp/McpHub.ts (around line 575), it's passed with variables = { env: process.env, ... }
  • Since variables.env is the entire process.env object (not a string with nested patterns), the regex /\$\{env:([^}]+)\}/g never matches
  • Therefore, the config.env object from mcp_settings.json is never substituted
  • When StdioClientTransport is created (line 591-598), configInjected.env is undefined, so only getDefaultEnvironment() is used

Evidence:

  1. System environment variables (like PATH, USERPROFILE) ARE passed correctly
  2. Configured env object from mcp_settings.json is NOT passed
  3. Debug flag DEBUG: "true" doesn't produce output (because it's not passed)
  4. The Godot MCP server falls back to its default path detection

Suggested Fix

The injectVariables() function needs to handle the case where variables.env is a complete object (not just a source for nested substitutions). One approach:

export function injectVariables(
  config: string,
  variables: {
    env: Record<string, string>;
    workspaceFolder: string;
    cwd: string;
    workspaceFolderName: string;
  }
): string {
  // Handle nested patterns like ${env:VAR_NAME} or ${env:VAR_NAME:subkey}
  const envRegex = /\$\{env:([^}]+)\}/g;
  
  let result = config.replace(envRegex, (match, variable) => {
    const [envKey, ...subKey] = variable.split(':');
    if (subKey) {
      return variables.env?.[envKey]?.[subKey] || '';
    }
    return variables.env?.[envKey] || '';
  });

  // NEW: Handle top-level env object merging
  // If variables.env is an object, merge it into the result
  if (typeof variables.env === 'object' && variables.env !== null) {
    // Create a new env object that combines process.env with config.env
    const mergedEnv = { ...process.env, ...(variables.env as Record<string, string>) };
    
    // For each key in mergedEnv, replace ${KEY} patterns with the actual value
    for (const key of Object.keys(mergedEnv)) {
      const value = mergedEnv[key];
      if (typeof value === 'string') {
        result = result.replace(new RegExp(`\\$\\{${key}\\}`, 'g'), value);
      }
    }
    }
  }
  
  return result;
}

Additional Context

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions