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
-
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"
}
}
}
}
}
-
openclaw config validate → passes. The env field is accepted by the schema.
-
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.
-
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
- 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.
- 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.
- 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
Bug:
mcp.servers.<name>.envfield is silently dropped at spawn timeSummary
OpenClaw's config schema accepts an
envfield onmcp.servers.<name>entries (openclaw config validatepasses 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 configuredenvblock 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
2026.4.5 (3e72c03)User=pleresadmin, with the standardEnvironment=HOME=/home/pleresadmindirective@softeria/ms-365-mcp-serverv0.73.1Steps to reproduce
Configure an MCP server with an
envblock 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" } } } } }openclaw config validate→ passes. Theenvfield is accepted by the schema.To verify what env the spawned process actually sees, replace the
commandwith a wrapper that logs its env to a file before exec'ing the real command:Update the
commandinmcp.servers.ms365to point at the wrapper. Restart the gateway. Trigger an MCP tool call from any agent. Inspect/tmp/ms365-spawn-env.log.Observed: the spawn log contains
HOME,USER,PATH(gateway service env) but NOTMS365_MCP_TOKEN_CACHE_PATHorMS365_MCP_SELECTED_ACCOUNT_PATHdespite both being in the configuredenvblock.Expected behavior
The values in
mcp.servers.<name>.envshould 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
envfield 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:
$HOMEor a custom env var (e.g., MSAL token caches)Workaround
Use a wrapper shell script that exports the required env vars itself before exec'ing the real command:
Then point
mcp.servers.<name>.commandat 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
envfield at spawn: in the MCP launcher code path, mergemcp.servers.<name>.envinto 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.mcp.serversso 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-serverto give the pleres and family agents access to Microsoft 365 calendar/email/SharePoint. The package supports persistent token cache viaMS365_MCP_TOKEN_CACHE_PATH(the README explicitly documents this for "hosted/sandboxed environments"), and we configured it correctly inmcp.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 returnedFailed 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