What happened?
StepCode currently passes the complete StepCode process environment to every local stdio MCP subprocess.
On current main, resolveStepMcpEnvironment() starts by copying every entry from process.env, overlays the MCP server's declared env, and may additionally fill STEPFUN_API_KEY from the stored Step login credential:
export function resolveStepMcpEnvironment(
declared: Record<string, string> | undefined,
input: { env?: NodeJS.ProcessEnv; authPath?: string } = {},
): Record<string, string> {
const resolved: Record<string, string> = {};
for (const [key, value] of Object.entries(input.env ?? process.env)) {
if (value !== undefined) resolved[key] = value;
}
Object.assign(resolved, declared ?? {});
if (!resolved.STEPFUN_API_KEY?.trim()) {
const credential = readStoredCredential(
"step",
input.authPath ?? getStepAuthPath(),
);
if (
credential?.type === "oauth" &&
typeof credential.access === "string" &&
credential.access.trim()
) {
resolved.STEPFUN_API_KEY = credential.access;
}
if (
credential?.type === "api_key" &&
typeof credential.key === "string" &&
credential.key.trim()
) {
resolved.STEPFUN_API_KEY = credential.key;
}
}
return resolved;
}
That object is then explicitly supplied to the MCP SDK transport:
if (input.declaration.command) {
const env = resolveStepMcpEnvironment(input.declaration.env);
transport = new StdioClientTransport({
command: input.declaration.command,
args: input.declaration.args,
cwd: input.declaration.cwd,
env,
stderr: "pipe",
});
}
I reproduced this behavior locally with a synthetic sentinel environment variable: a stdio MCP subprocess could read a parent-process variable that had never been declared in that MCP's configuration.
This is notable because StepCode currently depends on @modelcontextprotocol/sdk@1.27.1, and that SDK already has a restricted default for stdio subprocess environments.
If env is not supplied, StdioClientTransport uses getDefaultEnvironment(). In v1.27.1, the Unix default is limited to:
HOME
LOGNAME
PATH
SHELL
TERM
USER
rather than copying arbitrary values from process.env.
SDK implementation:
https://github.com/modelcontextprotocol/typescript-sdk/blob/v1.27.1/src/client/stdio.ts
StepCode therefore replaces the SDK's restricted default by explicitly supplying its complete resolved process environment.
This report is specifically about local stdio MCP subprocesses. Remote url-based MCP servers use StreamableHTTPClientTransport and do not receive the complete process environment through this path.
Other MCP client behavior
For comparison, several other MCP clients also use restricted subprocess environments rather than forwarding the entire parent environment.
OpenAI Codex builds the MCP environment from a small default baseline plus variables explicitly configured for the MCP server:
https://github.com/openai/codex/blob/b15718acfc5e79dff8cc8999eada7c80df9c85cb/codex-rs/rmcp-client/src/utils.rs
OpenClaw normally starts from the MCP SDK's getDefaultEnvironment() and overlays server-specific environment configuration:
https://github.com/openclaw/openclaw/blob/2c9d4817f0f33cbbf3d5707b8e674333339808de/src/agents/mcp-stdio-transport.ts
Hermes Agent explicitly constructs a safe stdio MCP environment and applies server-declared values:
https://github.com/hermes-agent-org/hermes/blob/036cbdfa0a3158454a0a2a7a7388cf70353326b4/tools/mcp_tool.py
OpenCode currently takes the opposite approach and explicitly merges process.env into the local MCP environment:
https://github.com/anomalyco/opencode/blob/0f549842ee746e400b1f72516b0b2e292e267e2c/packages/opencode/src/mcp/index.ts
So restricted inheritance is not universal across MCP clients. The relevant point here is that the MCP SDK used by StepCode already provides a restricted default, while StepCode explicitly overrides it with the full process environment.
Steps to reproduce
Use a synthetic value so the reproduction does not involve any real credential.
Configure a temporary stdio MCP server:
[mcp_servers.env_probe]
command = "node"
args = [
"-e",
"require('fs').writeFileSync('/tmp/step-mcp-env-probe', process.env.STEP_MCP_TEST_VALUE ?? 'missing')"
]
Start StepCode with an unrelated parent-process environment variable:
STEP_MCP_TEST_VALUE=sentinel-value step
The probe process may exit immediately and MCP initialization may subsequently fail; that does not affect this reproduction because the environment is available as soon as the subprocess starts.
Inspect the result:
cat /tmp/step-mcp-env-probe
Observed:
STEP_MCP_TEST_VALUE was never declared in the MCP configuration, but the subprocess received it.
The same code path also means that when no STEPFUN_API_KEY is already present, resolveStepMcpEnvironment() can load the stored Step login credential and add it to the environment passed to a generic stdio MCP subprocess.
Expected behavior
A stdio MCP subprocess should receive:
- the SDK's minimal environment required for normal subprocess execution;
- environment variables explicitly declared for that MCP server;
- product-specific credentials only when that integration explicitly requires them.
Unrelated parent-process variables should not be forwarded implicitly.
A minimal implementation could be:
const env = {
...getDefaultEnvironment(),
...(input.declaration.env ?? {}),
};
with Step credential injection handled separately and only for integrations that explicitly require STEPFUN_API_KEY.
This would also preserve the MCP SDK's existing cross-platform handling of required environment variables.
Suggested regression coverage:
- an unrelated parent sentinel variable is not inherited;
PATH, HOME, and other SDK-default variables remain available;
- explicitly declared MCP
env values are preserved;
- explicit values override the default environment where appropriate;
- a generic stdio MCP does not implicitly receive a Step login credential;
- integrations explicitly requiring
STEPFUN_API_KEY continue to work.
I have isolated the affected code paths and the regression-test cases and can submit a focused PR if this behavior change matches the maintainers' intended policy.
Version
Reproduced against current main. @step-harness/coding-agent: 0.84.4
What happened?
StepCode currently passes the complete StepCode process environment to every local stdio MCP subprocess.
On current
main,resolveStepMcpEnvironment()starts by copying every entry fromprocess.env, overlays the MCP server's declaredenv, and may additionally fillSTEPFUN_API_KEYfrom the stored Step login credential:That object is then explicitly supplied to the MCP SDK transport:
I reproduced this behavior locally with a synthetic sentinel environment variable: a stdio MCP subprocess could read a parent-process variable that had never been declared in that MCP's configuration.
This is notable because StepCode currently depends on
@modelcontextprotocol/sdk@1.27.1, and that SDK already has a restricted default for stdio subprocess environments.If
envis not supplied,StdioClientTransportusesgetDefaultEnvironment(). In v1.27.1, the Unix default is limited to:rather than copying arbitrary values from
process.env.SDK implementation:
https://github.com/modelcontextprotocol/typescript-sdk/blob/v1.27.1/src/client/stdio.ts
StepCode therefore replaces the SDK's restricted default by explicitly supplying its complete resolved process environment.
This report is specifically about local stdio MCP subprocesses. Remote
url-based MCP servers useStreamableHTTPClientTransportand do not receive the complete process environment through this path.Other MCP client behavior
For comparison, several other MCP clients also use restricted subprocess environments rather than forwarding the entire parent environment.
OpenAI Codex builds the MCP environment from a small default baseline plus variables explicitly configured for the MCP server:
https://github.com/openai/codex/blob/b15718acfc5e79dff8cc8999eada7c80df9c85cb/codex-rs/rmcp-client/src/utils.rs
OpenClaw normally starts from the MCP SDK's
getDefaultEnvironment()and overlays server-specific environment configuration:https://github.com/openclaw/openclaw/blob/2c9d4817f0f33cbbf3d5707b8e674333339808de/src/agents/mcp-stdio-transport.ts
Hermes Agent explicitly constructs a safe stdio MCP environment and applies server-declared values:
https://github.com/hermes-agent-org/hermes/blob/036cbdfa0a3158454a0a2a7a7388cf70353326b4/tools/mcp_tool.py
OpenCode currently takes the opposite approach and explicitly merges
process.envinto the local MCP environment:https://github.com/anomalyco/opencode/blob/0f549842ee746e400b1f72516b0b2e292e267e2c/packages/opencode/src/mcp/index.ts
So restricted inheritance is not universal across MCP clients. The relevant point here is that the MCP SDK used by StepCode already provides a restricted default, while StepCode explicitly overrides it with the full process environment.
Steps to reproduce
Use a synthetic value so the reproduction does not involve any real credential.
Configure a temporary stdio MCP server:
Start StepCode with an unrelated parent-process environment variable:
The probe process may exit immediately and MCP initialization may subsequently fail; that does not affect this reproduction because the environment is available as soon as the subprocess starts.
Inspect the result:
Observed:
STEP_MCP_TEST_VALUEwas never declared in the MCP configuration, but the subprocess received it.The same code path also means that when no
STEPFUN_API_KEYis already present,resolveStepMcpEnvironment()can load the stored Step login credential and add it to the environment passed to a generic stdio MCP subprocess.Expected behavior
A stdio MCP subprocess should receive:
Unrelated parent-process variables should not be forwarded implicitly.
A minimal implementation could be:
with Step credential injection handled separately and only for integrations that explicitly require
STEPFUN_API_KEY.This would also preserve the MCP SDK's existing cross-platform handling of required environment variables.
Suggested regression coverage:
PATH,HOME, and other SDK-default variables remain available;envvalues are preserved;STEPFUN_API_KEYcontinue to work.I have isolated the affected code paths and the regression-test cases and can submit a focused PR if this behavior change matches the maintainers' intended policy.
Version
Reproduced against current
main.@step-harness/coding-agent:0.84.4