Skip to content

mcp.servers.<name>.env field is silently dropped at spawn time #63394

@tmote

Description

@tmote

Bug: mcp.servers.<name>.env field is silently dropped at spawn time

Summary

OpenClaw's config schema accepts an env field on mcp.servers.<name> entries (openclaw config validate passes when the field is present and correctly typed), but the MCP launcher does NOT propagate those environment variables to the spawned subprocess. The subprocess inherits only the gateway service's own environment (HOME, USER, PATH, etc.) — none of the keys from the configured env block are present.

This breaks any MCP server that relies on env-var configuration, which is the typical pattern for credentials, file paths, and toggles.

Environment

  • OpenClaw 2026.4.5 (3e72c03)
  • Ubuntu 24.04.4 LTS, Node v24.14.1 via nvm
  • Gateway runs as a system-systemd unit, User=pleresadmin, with the standard Environment=HOME=/home/pleresadmin directive
  • MCP server under test: @softeria/ms-365-mcp-server v0.73.1

Steps to reproduce

  1. Configure an MCP server with an env block in ~/.openclaw/openclaw.json:

    {
      "mcp": {
        "servers": {
          "ms365": {
            "command": "npx",
            "args": ["-y", "@softeria/ms-365-mcp-server", "--org-mode"],
            "env": {
              "MS365_MCP_TOKEN_CACHE_PATH": "/home/pleresadmin/.config/ms365-mcp/token-cache.json",
              "MS365_MCP_SELECTED_ACCOUNT_PATH": "/home/pleresadmin/.config/ms365-mcp/selected-account.json"
            }
          }
        }
      }
    }
  2. openclaw config validate → passes. The env field is accepted by the schema.

  3. To verify what env the spawned process actually sees, replace the command with a wrapper that logs its env to a file before exec'ing the real command:

    cat > /home/pleresadmin/.local/bin/ms365-mcp-wrapper <<'EOF'
    #!/bin/bash
    {
      echo "=== spawn at $(date -u +%Y-%m-%dT%H:%M:%S.%3NZ) ==="
      echo "  pid: $$ uid: $(id -u)"
      env
      echo "  args: $*"
    } >> /tmp/ms365-spawn-env.log
    exec /home/pleresadmin/.nvm/versions/node/v24.14.1/bin/npx -y @softeria/ms-365-mcp-server "$@"
    EOF
    chmod +x /home/pleresadmin/.local/bin/ms365-mcp-wrapper

    Update the command in mcp.servers.ms365 to point at the wrapper. Restart the gateway. Trigger an MCP tool call from any agent. Inspect /tmp/ms365-spawn-env.log.

  4. Observed: the spawn log contains HOME, USER, PATH (gateway service env) but NOT MS365_MCP_TOKEN_CACHE_PATH or MS365_MCP_SELECTED_ACCOUNT_PATH despite both being in the configured env block.

Expected behavior

The values in mcp.servers.<name>.env should be merged into the spawned subprocess's environment, taking precedence over any inherited gateway env vars on key collision. This is the standard MCP client pattern documented in the MCP spec and used by Claude Desktop, Cursor, and most other MCP hosts.

Actual behavior

The env field is accepted by the schema but silently ignored at spawn time. Subprocess receives only the gateway service's own environment.

Severity

High for any deployment where MCP servers need configuration via env vars (which is most of them, since many MCP servers expect API keys, credentials paths, or feature toggles in env). Particularly painful when:

  • The MCP server stores secrets in a path that depends on $HOME or a custom env var (e.g., MSAL token caches)
  • The user does interactive setup in their shell where env vars can be set, then expects the gateway-spawned subprocess to see the same env

Workaround

Use a wrapper shell script that exports the required env vars itself before exec'ing the real command:

#!/bin/bash
export MS365_MCP_TOKEN_CACHE_PATH=/home/pleresadmin/.config/ms365-mcp/token-cache.json
export MS365_MCP_SELECTED_ACCOUNT_PATH=/home/pleresadmin/.config/ms365-mcp/selected-account.json
exec /home/pleresadmin/.nvm/versions/node/v24.14.1/bin/npx -y @softeria/ms-365-mcp-server "$@"

Then point mcp.servers.<name>.command at the wrapper. This bypasses OpenClaw's broken env propagation entirely. Verified working — the subprocess sees the env vars and the MS365 server finds the persistent token cache.

Suggested fix paths

  1. Honor the env field at spawn: in the MCP launcher code path, merge mcp.servers.<name>.env into the subprocess env (gateway service env + configured env, with configured env taking precedence on key collision). This is a one-line change in the spawn call.
  2. Validate the env block at startup: log a warning if non-string values appear in the env block, since some MCP-host implementations crash on non-string env values.
  3. Document the precedence rules in the OpenClaw docs page for mcp.servers so operators know what to expect when there are key collisions with gateway service env.

Where this bit us

Synap deployment, 2026-04-08. Wiring @softeria/ms-365-mcp-server to give the pleres and family agents access to Microsoft 365 calendar/email/SharePoint. The package supports persistent token cache via MS365_MCP_TOKEN_CACHE_PATH (the README explicitly documents this for "hosted/sandboxed environments"), and we configured it correctly in mcp.servers.ms365.env. The standalone smoke test (running the same npx command from a shell with the env vars set) returned both logged-in accounts successfully. But when the gateway spawned the MCP server during an agent run, the package returned Failed to acquire token for account 'ty.mote@pleres.group' because it couldn't find the token cache — the env vars weren't being passed through.

We confirmed the bug by replacing the command with a logging wrapper as described above, and the captured spawn env had no MS365_* keys.

Related upstream issues

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions