Developing a MCP server almost always requires authentication credentials. Whether you are connecting to a remote SQL database, integrating with the GitHub API, or using a proprietary SaaS platform, your server needs access to secrets like API keys, connection strings, and tokens. Hardcoding these credentials directly into your Python or TypeScript source code is a significant security vulnerability and prevents the code from being shared or version-controlled safely.
In standard web development, we often rely on .env files located in the project root. However, the architectural nature of MCP introduces a specific constraint: the server is a subprocess spawned by the client application (such as Claude Desktop). The working directory of this subprocess may not match your development directory, making relative file paths to .env files unreliable. Consequently, the Model Context Protocol relies on the host application to inject environment variables into the server's process at runtime.
When an MCP client initiates a connection, it creates a new process for your server. During this instantiation, the client can pass a dictionary of environment variables that become available to the standard input/output (stdio) stream or the execution context of that process. This ensures that secrets are held in the configuration layer of the host system, keeping your server implementation generic and stateless regarding authentication.
The data flow for environment variable injection follows a strict hierarchy:
The propagation of configuration data from the host JSON file to the server runtime.
For Claude Desktop and similar MCP-compliant clients, environment variables are defined in the configuration file (typically claude_desktop_config.json on macOS or %APPDATA%\Claude\claude_desktop_config.json on Windows).
The configuration schema includes an env object within the server definition. This object maps variable names to their string values. The client reads this mapping before executing the command specified in args and ensures these values are present in the spawned process's environment block.
Consider a scenario where your server connects to a Postgres database and the weather API. Your configuration would look like this:
{
"mcpServers": {
"data-service": {
"command": "uv",
"args": [
"--directory",
"/absolute/path/to/server",
"run",
"mcp-server-data"
],
"env": {
"POSTGRES_CONNECTION_STRING": "postgresql://user:pass@localhost:5432/db",
"WEATHER_API_KEY": "sk_live_1234567890",
"LOG_LEVEL": "DEBUG"
}
}
}
}
In this configuration, the env block is explicitly separate from the args. The args define how to run the executable, while env defines the context in which it runs.
Once injected, these variables are accessed using standard library methods provided by your programming language. There is no MCP-specific SDK method required to retrieve them; they function exactly like system-level environment variables.
In Python, the os module is the standard interface. However, for production-grade MCP servers, it is advisable to perform validation. If a required variable is missing, the server should ideally fail fast with a descriptive error in the logs, rather than crashing mid-operation.
Using os.environ:
import os
import logging
# Retrieve the variable or return None
db_url = os.environ.get("POSTGRES_CONNECTION_STRING")
# Retrieve or raise generic KeyError if missing
api_key = os.environ["WEATHER_API_KEY"]
if not db_url:
logging.error("POSTGRES_CONNECTION_STRING is missing from environment.")
# Depending on logic, you might raise an exception here
raise ValueError("Database configuration required")
A more pattern, often seen in intermediate to advanced implementations, involves using Pydantic's BaseSettings (as discussed in Chapter 3 regarding input validation). This allows you to define a strictly typed configuration schema that validates the environment on startup.
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
postgres_connection_string: str
weather_api_key: str
log_level: str = "INFO" # Default value if not provided
try:
config = Settings()
except Exception as e:
# This ensures the server logs clearly why it failed to start
print(f"Configuration Error: {e}")
In TypeScript or JavaScript (Node.js), these values reside in process.env.
const API_KEY = process.env.WEATHER_API_KEY;
if (!API_KEY) {
console.error("Error: WEATHER_API_KEY not set in client configuration");
process.exit(1);
}
// Using the in a tool handler
const response = await fetch(`https://api.weather.com/v1?key=${API_KEY}`);
When managing environment variables in the context of MCP, consider the isolation of the runtime environment.
Avoid Dotenv in Production Logic
While libraries like python-dotenv or dotenv are excellent for local development, relying on them within an installed MCP server can be problematic. If the client spawns the server from a different directory than where the .env file resides, the file will not be loaded. It is safer to rely entirely on the injection from the host configuration file, which acts as the source of truth.
Protecting the Configuration File
The claude_desktop_config.json file contains sensitive plaintext credentials. Ensure this file has appropriate file system permissions (e.g., readable only by your user account). If you are distributing your MCP server code to other developers or team members, provide a template configuration but never commit the actual credentials to your version control system.
Variable Scope Variables defined in the configuration file are scoped exclusively to that specific server process. If you have multiple MCP servers registered (e.g., one for filesystem access and another for database access), they do not share environment variables. This isolation is a security feature, ensuring that a compromised or buggy tool in one server cannot access the API keys intended for another.
A common issue during integration is the "variable not found" error, even when the configuration file appears correct. This usually results from three causes:
env block or fail to parse the entire server definition.API_KEY) and the code access (e.g., os.environ["API_KEY"]).By centralizing configuration in the host application, you decouple your server's logic from its credentials, adhering to the 12-factor app methodology and ensuring your MCP server is portable across different environments and clients.
Was this section helpful?
os - Miscellaneous operating system interfaces (Environment variables), Python Software Foundation, 2024 (Python Software Foundation) - Official documentation for accessing and manipulating environment variables in Python using os.environ.pydantic-settings to load and validate environment variables.process object (Environment Variables), Node.js Foundation, 2024 - Official documentation for accessing environment variables in Node.js applications via process.env.© 2026 ApX Machine LearningEngineered with