MCP Python SDKで環境変数が子に渡らない理由 #AI - Qiita
@hironakamura_ai(中村 啓|LLM Engineer)

Why Environment Variables Aren't Passed to the Child Process in the MCP Python SDK

  • Python
  • MCP
  • AI Agents
  • Generative AI
  • AI
0
Posted at 2026-11-11

While testing the MCP Python SDK's stdio connection locally, a dummy token placed in the parent process disappeared in the child process. When I launched the same Python as a regular subprocess, it was visible.

Environment variables passed to the MCP server should be explicitly specified by name. I want to implement it this way. If you pass the parent process's entire environment, it will also pass along secret information that isn't used.

Without using any communication with the authentication service or LLM calls, I narrowed the test down to whether environment variables reach the local child process.

Visible in the Parent Process, but Not Passed to the stdio Child Process

I ran it on macOS with CPython 3.12.13 and mcp==1.27.1. The dependencies were anyio==4.13.0 and pydantic==2.13.4. The target was mcp.client.stdio.stdio_client.

I set two dummy variables in the parent process. MCP_DEMO_TOKEN is a required setting, and MCP_DEMO_OTHER is an unnecessary setting. I did not use any actual API keys.

Save it as env_probe.py and run python env_probe.py in the environment above.

import asyncio import json import os import subprocess import sys from mcp import StdioServerParameters from mcp.client.stdio import stdio_clientos.environ["MCP_DEMO_TOKEN"] = "dummy" os.environ["MCP_DEMO_OTHER"] = "unrelated" child = '''import json, os params = {"token": "MCP_DEMO_TOKEN" in os.environ, "other": "MCP_DEMO_OTHER" in os.environ, "path": "PATH" in os.environ} print(json.dumps({"jsonrpc": "2.0", "method": "probe", "params": params}), flush=True) '''def show(name, values): print(name, " ".join(f"{k}={v}" for , v in values.items())) async def main(): raw = subprocess.check_output([sys.executable, "-c", child], text=True) show("subprocess", json.loads(raw)["params"]) cases = [ ("SDK default", None), ("SDK empty", {}), ("SDK explicit", {"MCP_DEMO_TOKEN": os.environ["MCP_DEMO_TOKEN]}), ("SDK all", dict(os.environ)), ] for name, env in cases: server = StdioServerParameters(command=sys.executable, args=["-c", child], env=env ) async with stdio_client(server) as (read, _): message = await read.receive() if isinstance(message, Exception): raise message show(name, message.message.root.params) asyncio.run(main())

The child process returns, in JSON-RPC format and on a single line, whether the environment variables are present. This is for verifying process startup and reading; MCP initialization and tool calls are omitted. The token value is not output.

The local output is as follows.

subprocess token=True other=True path=True SDK default token=False other=False path=True SDK empty token=False other=False path=True SDK explicit token=True other=False path=True SDK all token=True other=True path=True

With the normal subprocess, both arrived. If env is omitted in the SDK, neither arrives.

The first thing that caught my attention was the next one, path=True.

Even with env={}, the environment is not empty

Even when an empty dictionary is passed to the SDK's env, PATH remains.

I read mcp/client/stdio/__init__.py in the installed 1.27.1.In the source of the same version, stdio_client() adds server.env to the dictionary from get_default_environment() and launches the child process.

The names included among the default variables inherited in this macOS environment were HOME, LOGNAME, PATH, SHELL, TERM, and USER. It selects only variables that have values in the parent and excludes those whose values start with (). There is a separate list for Windows.

MCP_DEMO_TOKEN is not on the list, so merely setting it in the parent does not cause it to be inherited. env={} also means that nothing is added to the default dictionary. Putting the three approaches side by side gives the following.

env passed to the SDK How the dictionary passed to the child is constructed
Omitted or None Default variables selected by the SDK
Empty dictionary Use the default variables as-is
Specify names and values Add them to the default variables, overwriting any with the same name

There is no way to specify the bulk removal of default variables through the SDK's env. This is where it is easy to confuse it with env passed directly to the standard library.

sys.executable in the code was fixed so that the same Python would be used. Using only the name python adds the effort of investigating which virtual environment was being used.

Passing everything fixes it. What gets added then?

Unnecessary variables also arrived with SDK all. Even if dict(os.environ) resolves the connection error, the range of variables passed has not been narrowed.

I would list the required variable names in the launch configuration. SDK explicit has that form. Even when using the actual names, read them from the parent with os.environ[name]. If a setting is missing, it stops before launching the child process. This makes it easier to track missing settings than passing None to get() and getting a different error.

This limitation alone cannot isolate the child process.It runs with the permissions of the same OS user and may obtain different information from files it can read. other=False only indicates that no dummy variable was passed via the environment on this launch. The file permissions of the third-party server and the isolation of the execution environment must be determined separately.

Only the local startup was verified. Configuration handling for each AI app, HTTP connections, and authentication with actual services were out of scope.

When no configuration is found for the MCP server, in addition to checking whether it is visible in the parent shell, check the name the SDK passes to the child. By explicitly specifying the required variables one at a time, in this example the token could be passed without passing unnecessary variables. A small probe that does not print any values can be used to verify startup. We want to be able to review, on the same line, the changes that fix the connection and the changes that increase the information passed to the server.

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
Sign upLogin