Configuration mistakes are one of the fastest ways to turn a working MCP demo into a security incident.
Rules of thumb
- Code reads configuration from the environment
- Secrets never live in git, images, client config committed to repos, or tool results
- Document required keys in README /
.env.example(empty values) - Prefer least privilege upstream credentials (scope minimization)
Local development
| Practice | Detail |
|---|---|
.env gitignored | Real values only on the machine |
.env.example committed | Names + dummy placeholders |
Host env map | Claude/Cursor stdio configs often pass env — don’t commit those files with secrets |
Official local install security: show full commands and consent before one-click local server execution (local MCP compromise).
Stdio logging
From Build an MCP server and stdio transport:
- MUST NOT write non-MCP messages to stdout
- Log to stderr or files
- TypeScript:
console.error, notconsole.log - Python:
print(..., file=sys.stderr)or logging to stderr
Remote / production
| Kind | Examples | Storage |
|---|---|---|
| Non-secret config | LOG_LEVEL, NODE_ENV | Env vars OK |
| Secrets | API tokens, DB passwords | Secret manager / platform secrets |
| Client auth material | Deploy API keys, OAuth | Issued per user/team; rotatable |
On MCPLambda: set env and secrets in dashboard, CLI (-e), or API; secrets overwrite plain env with the same key when configured that way (see deploy/API docs). Do not put secret values in mcplambda.yaml.
Auth tokens vs app secrets
| Token type | Purpose |
|---|---|
| MCP client → server auth | Who may call your MCP endpoint (authorization) |
| Server → upstream credentials | How tools call Slack/DB/SaaS |
Do not pass client tokens through to upstream APIs (no token passthrough — security best practices).
Tool outputs
From tools security: servers MUST sanitize tool outputs. Never return raw secret material in content.
Naming conventions
- Prefer
SERVICE_API_KEYoverKEY - Fail fast at startup if required vars missing
- Separate
*_STAGINGvs prod names if both exist in one process (prefer separate deploys)
Anti-patterns
- Baking tokens into Docker layers
- Printing full env on startup
- One prod secret across all preview deploys
- Sharing PATs in chat instead of issued remote credentials (team-shared remote)
Checklist
-
.envignored; example committed - Secrets only via platform/secret store in prod
- Stdio logs to stderr
- Tool results scrubbed
- Rotation path documented
- Offboarding revokes client + upstream credentials
Worked scenario: The committed .env that escaped
A developer commits .env with a prod GitHub PAT so “onboarding is easier.” The token is scraped within days. Every stdio config in the team still embeds the same PAT.
You rotate the token, move secrets to the platform secret store, add .env to gitignore with a committed .env.example, and issue per-user remote credentials for the shared server. mcplambda.yaml stays free of secrets—only build/run/transport.
You also ban startup logs that dump os.environ, and you verify tool results never echo credentials.
Checklist for this topic
- gitignore real env files; commit .env.example only
- Runtime secret injection for prod
- No secrets in mcplambda.yaml or images
- Separate MCP client auth from upstream API secrets
- Never log secret values (stdio → stderr for app logs)
- Rotate on offboarding the same day
Topic-specific failure modes
| Failure | Likely cause | Fix |
|---|---|---|
| Leaked PAT | Secrets in git/chat | Rotate; use secret store |
| Works for author only | Laptop-absolute paths | Env-based config only |
| Token passthrough | Forwarding client tokens upstream | Mint server-side upstream creds |
| Preview hits prod data | Shared prod secrets | Env-scoped credentials |
Related guides
Securing remote · Team-shared remote · Auth patterns
In practice: secret classes
Label every secret:
- Bootstrap — needed to start process (fatal if missing)
- Upstream — third-party API keys used inside tools
- Edge auth — validates MCP clients
Different rotation and access policies apply. Mixing them in one “SECRETS” bag is how preview environments accidentally share prod upstream keys.
Example README env table
| Name | Required | Secret? | Description |
|---|---|---|---|
UPSTREAM_API_BASE | yes | no | API base URL |
UPSTREAM_API_KEY | yes | yes | Upstream key |
LOG_LEVEL | no | no | default info |
Agents and humans should be able to configure staging from this table alone.
Takeaways
For Environment Variables and Secrets for MCP Servers, remember three things:
- Be specific to this problem — the worked scenario “The committed .env that escaped” is the failure mode you should design against, not a generic outage narrative.
- Secrets live in the platform secret store, not in git, images, or chat transcripts.
- Rotate credentials after any leak path; treat
.envcommits as incidents.
If you only remember one habit: never put long-lived API keys in MCP tool results or error messages.
Sources
- Security Best Practices
- Tools — security considerations
- Transports — stdio
- Build an MCP server
- Authorization — 2025-11-25
- MCPLambda
mcplambda.yaml