Skip to content

Commit 7526ac8

Browse files
authored
Merge pull request #82 from sandialabs/copilot/allow-env-values-for-mcps
Add environment variable support for stdio MCP servers
2 parents 0137d83 + 6aca4df commit 7526ac8

File tree

11 files changed

+776
-5
lines changed

11 files changed

+776
-5
lines changed

backend/mcp/env-demo/README.md

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
# Environment Variable Demo MCP Server
2+
3+
This MCP server demonstrates the environment variable passing capability added to Atlas UI 3. It shows how to configure and use environment variables for MCP servers through the `mcp.json` configuration file.
4+
5+
## Purpose
6+
7+
This server provides tools to:
8+
- Retrieve specific environment variables
9+
- List all configured environment variables
10+
- Demonstrate practical usage patterns for environment variables
11+
12+
## Configuration
13+
14+
The server is configured in `config/overrides/mcp.json` (or `config/defaults/mcp.json`) with the `env` field:
15+
16+
```json
17+
{
18+
"env-demo": {
19+
"command": ["python", "mcp/env-demo/main.py"],
20+
"cwd": "backend",
21+
"env": {
22+
"CLOUD_PROFILE": "demo-profile",
23+
"CLOUD_REGION": "us-west-2",
24+
"DEBUG_MODE": "true",
25+
"ENVIRONMENT": "development",
26+
"API_KEY": "${DEMO_API_KEY}"
27+
},
28+
"groups": ["users"],
29+
"description": "Demonstrates environment variable passing to MCP servers",
30+
"compliance_level": "Public"
31+
}
32+
}
33+
```
34+
35+
## Environment Variable Features
36+
37+
### Literal Values
38+
Set environment variables with literal string values:
39+
```json
40+
"env": {
41+
"CLOUD_REGION": "us-west-2",
42+
"DEBUG_MODE": "true"
43+
}
44+
```
45+
46+
### Variable Substitution
47+
Reference system environment variables using `${VAR_NAME}` syntax:
48+
```json
49+
"env": {
50+
"API_KEY": "${DEMO_API_KEY}"
51+
}
52+
```
53+
54+
Before starting Atlas UI, set the system environment variable:
55+
```bash
56+
export DEMO_API_KEY="your-secret-key"
57+
```
58+
59+
## Available Tools
60+
61+
### 1. `get_env_var`
62+
Retrieves the value of a specific environment variable.
63+
64+
**Input:**
65+
- `var_name` (string): Name of the environment variable
66+
67+
**Example Usage:**
68+
```
69+
Get the value of CLOUD_REGION
70+
```
71+
72+
### 2. `list_configured_env_vars`
73+
Lists all configured environment variables that are commonly expected.
74+
75+
**Example Usage:**
76+
```
77+
Show me the configured environment variables
78+
```
79+
80+
### 3. `demonstrate_env_usage`
81+
Shows practical examples of using environment variables.
82+
83+
**Input:**
84+
- `operation` (string): Type of demonstration
85+
- `"info"`: General information about env var configuration
86+
- `"config"`: Demonstrates configuration usage (profile, region)
87+
- `"credentials"`: Demonstrates secure credential handling
88+
89+
**Example Usage:**
90+
```
91+
Demonstrate how to use environment variables for configuration
92+
```
93+
94+
## Use Cases
95+
96+
### Cloud Configuration
97+
```json
98+
"env": {
99+
"CLOUD_PROFILE": "production-profile",
100+
"CLOUD_REGION": "us-east-1",
101+
"AVAILABILITY_ZONE": "us-east-1a"
102+
}
103+
```
104+
105+
### API Credentials
106+
```json
107+
"env": {
108+
"API_KEY": "${MY_SERVICE_API_KEY}",
109+
"API_ENDPOINT": "https://api.example.com"
110+
}
111+
```
112+
113+
### Feature Flags
114+
```json
115+
"env": {
116+
"DEBUG_MODE": "false",
117+
"ENABLE_CACHING": "true",
118+
"MAX_RETRIES": "3"
119+
}
120+
```
121+
122+
## Security Best Practices
123+
124+
1. **Never commit secrets**: Use `${VAR_NAME}` substitution for sensitive values
125+
2. **Set system env vars**: Configure sensitive values at the system level
126+
3. **Use appropriate compliance levels**: Mark servers with sensitive access appropriately
127+
4. **Document required variables**: Clearly document which env vars are needed
128+
129+
## Testing
130+
131+
To test this server:
132+
133+
1. Set any optional environment variables:
134+
```bash
135+
export DEMO_API_KEY="test-key-123"
136+
```
137+
138+
2. Start Atlas UI (the server will automatically load with the configured env vars)
139+
140+
3. In the chat interface, try:
141+
```
142+
List all configured environment variables for the env-demo server
143+
```
144+
145+
```
146+
Get the value of CLOUD_REGION
147+
```
148+
149+
```
150+
Demonstrate how environment variables are used for credentials
151+
```
152+
153+
## Notes
154+
155+
- Environment variables are only passed to stdio servers (not HTTP/SSE servers)
156+
- If a `${VAR_NAME}` reference cannot be resolved, server initialization will fail with a clear error message
157+
- Empty `env: {}` is valid and will set no environment variables
158+
- The `env` field is optional; servers work without it as before

backend/mcp/env-demo/main.py

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Environment Variable Demo MCP Server using FastMCP
4+
5+
This server demonstrates the environment variable passing capability.
6+
It reads environment variables that are configured in mcp.json and
7+
exposes them through MCP tools.
8+
"""
9+
10+
import os
11+
import time
12+
from typing import Any, Dict
13+
14+
from fastmcp import FastMCP
15+
16+
# Initialize the MCP server
17+
mcp = FastMCP("Environment Variable Demo")
18+
19+
20+
@mcp.tool
21+
def get_env_var(var_name: str) -> Dict[str, Any]:
22+
"""Get the value of a specific environment variable.
23+
24+
This tool demonstrates how environment variables configured in mcp.json
25+
are passed to the MCP server process.
26+
27+
Args:
28+
var_name: Name of the environment variable to retrieve
29+
30+
Returns:
31+
MCP contract shape with the environment variable value:
32+
{
33+
"results": {
34+
"var_name": str,
35+
"var_value": str or None,
36+
"is_set": bool
37+
},
38+
"meta_data": {
39+
"elapsed_ms": float
40+
}
41+
}
42+
"""
43+
start = time.perf_counter()
44+
45+
var_value = os.environ.get(var_name)
46+
is_set = var_name in os.environ
47+
48+
elapsed_ms = round((time.perf_counter() - start) * 1000, 3)
49+
50+
return {
51+
"results": {
52+
"var_name": var_name,
53+
"var_value": var_value,
54+
"is_set": is_set
55+
},
56+
"meta_data": {
57+
"elapsed_ms": elapsed_ms
58+
}
59+
}
60+
61+
62+
@mcp.tool
63+
def list_configured_env_vars() -> Dict[str, Any]:
64+
"""List all environment variables that were configured in mcp.json.
65+
66+
This tool shows which environment variables from the mcp.json configuration
67+
are available to this server. It returns commonly expected configuration
68+
variables that might be set.
69+
70+
Returns:
71+
MCP contract shape with environment variables:
72+
{
73+
"results": {
74+
"configured_vars": dict of var_name -> var_value,
75+
"total_count": int
76+
},
77+
"meta_data": {
78+
"elapsed_ms": float
79+
}
80+
}
81+
"""
82+
start = time.perf_counter()
83+
84+
# List of common configuration environment variables
85+
# This demonstrates what might be passed from mcp.json
86+
common_config_vars = [
87+
"CLOUD_PROFILE",
88+
"CLOUD_REGION",
89+
"API_KEY",
90+
"DEBUG_MODE",
91+
"MAX_RETRIES",
92+
"TIMEOUT_SECONDS",
93+
"ENVIRONMENT",
94+
"SERVICE_URL"
95+
]
96+
97+
configured_vars = {}
98+
for var_name in common_config_vars:
99+
if var_name in os.environ:
100+
configured_vars[var_name] = os.environ[var_name]
101+
102+
elapsed_ms = round((time.perf_counter() - start) * 1000, 3)
103+
104+
return {
105+
"results": {
106+
"configured_vars": configured_vars,
107+
"total_count": len(configured_vars)
108+
},
109+
"meta_data": {
110+
"elapsed_ms": elapsed_ms
111+
}
112+
}
113+
114+
115+
@mcp.tool
116+
def demonstrate_env_usage(operation: str = "info") -> Dict[str, Any]:
117+
"""Demonstrate how environment variables can be used in MCP server operations.
118+
119+
This tool shows practical examples of using environment variables for:
120+
- Configuration (e.g., region, profile)
121+
- Feature flags (e.g., debug mode)
122+
- API credentials (e.g., API keys)
123+
124+
Args:
125+
operation: Type of demonstration ("info", "config", "credentials")
126+
127+
Returns:
128+
MCP contract shape with demonstration results:
129+
{
130+
"results": {
131+
"operation": str,
132+
"example": str,
133+
"details": dict
134+
},
135+
"meta_data": {
136+
"elapsed_ms": float
137+
}
138+
}
139+
"""
140+
start = time.perf_counter()
141+
142+
if operation == "config":
143+
# Demonstrate configuration from environment
144+
cloud_profile = os.environ.get("CLOUD_PROFILE", "default")
145+
cloud_region = os.environ.get("CLOUD_REGION", "us-east-1")
146+
147+
example = f"Using cloud profile '{cloud_profile}' in region '{cloud_region}'"
148+
details = {
149+
"profile": cloud_profile,
150+
"region": cloud_region,
151+
"source": "environment variables from mcp.json"
152+
}
153+
154+
elif operation == "credentials":
155+
# Demonstrate secure credential handling
156+
api_key = os.environ.get("API_KEY")
157+
has_key = api_key is not None
158+
159+
example = f"API key is {'configured' if has_key else 'not configured'}"
160+
details = {
161+
"has_api_key": has_key,
162+
"key_length": len(api_key) if api_key else 0,
163+
"masked_key": f"{api_key[:4]}...{api_key[-4:]}" if api_key and len(api_key) > 8 else None,
164+
"source": "environment variable ${API_KEY} from mcp.json"
165+
}
166+
167+
else: # info
168+
example = "Environment variables can be configured in mcp.json"
169+
details = {
170+
"usage": "Set env dict in mcp.json server configuration",
171+
"syntax": {
172+
"literal": "KEY: 'literal-value'",
173+
"substitution": "KEY: '${SYSTEM_ENV_VAR}'"
174+
},
175+
"example_config": {
176+
"env": {
177+
"CLOUD_PROFILE": "my-profile-9",
178+
"CLOUD_REGION": "us-east-7",
179+
"API_KEY": "${MY_API_KEY}"
180+
}
181+
}
182+
}
183+
184+
elapsed_ms = round((time.perf_counter() - start) * 1000, 3)
185+
186+
return {
187+
"results": {
188+
"operation": operation,
189+
"example": example,
190+
"details": details
191+
},
192+
"meta_data": {
193+
"elapsed_ms": elapsed_ms
194+
}
195+
}
196+
197+
198+
if __name__ == "__main__":
199+
mcp.run()

backend/modules/config/config_manager.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ class MCPServerConfig(BaseModel):
101101
enabled: bool = True
102102
command: Optional[List[str]] = None # Command to run server (for stdio servers)
103103
cwd: Optional[str] = None # Working directory for command
104+
env: Optional[Dict[str, str]] = None # Environment variables for stdio servers
104105
url: Optional[str] = None # URL for HTTP servers
105106
type: str = "stdio" # Server type: "stdio" or "http" (deprecated, use transport)
106107
transport: Optional[str] = None # Explicit transport: "stdio", "http", "sse" - takes priority over auto-detection

0 commit comments

Comments
 (0)