Skip to main content
Tutorial7 min read

MCP credential rotation: automate it before you leak a key

MCP env blocks collect GitHub PATs, database URLs and API keys. A concrete rotation strategy with code, schedules, and blast-radius controls.

MCP `env` blocks quietly collect GitHub PATs, database URLs, Stripe keys and Anthropic tokens. Each one is a stale supply-chain incident waiting to happen. Rotation is the cheapest control you own — here is a working strategy, a schedule, and the code.

Why MCP makes rotation urgent

Three reasons rotation matters more for MCP than for classical backends:

  • Config is on developer laptops, which are lost, imaged, and shared more than prod servers.
  • MCP servers run with the full credential — no token exchange, no least-privilege scoping by default.
  • Credential exposure is often invisible — a compromised MCP package keeps working, so the breach goes undetected for months.

Four credential categories by risk

Category Example Rotation cadence
Critical Production DB, payment API Every 30 days
High GitHub org admin PAT, cloud admin Every 60 days
Medium SaaS read-only tokens Every 90 days
Low Public API keys with rate limits Every 180 days or on event

Three patterns, in order of preference

1. Secret manager reference

Best pattern: the MCP config references a secret manager path, not the literal value. Rotation happens in the manager, zero config changes on developer machines.

{
  "env": {
    "GITHUB_TOKEN": "op://Dev/github-mcp/token"
  }
}

A tiny wrapper resolves the reference before launch. Compatible with 1Password CLI, Doppler, AWS Secrets Manager, and Vault.

2. Workload identity

Second best: the MCP server receives a short-lived token minted on demand, no long-term secret at all. Supported natively by cloud MCP servers (AWS MCP, GCP MCP) and by any server that can call an OIDC exchange.

3. Plain env, rotated on schedule

Fallback for servers that do not support the above. A cron job replaces the value in the config file and restarts the host.

Automating rotation in 80 lines

// rotate.ts
import { readFile, writeFile } from 'node:fs/promises';
import { execSync } from 'node:child_process';

const configPath = process.env.MCP_CONFIG_PATH!;
const rotations = [
  {
    name: 'github',
    envKey: 'GITHUB_TOKEN',
    mint: () => execSync('gh auth token --refresh').toString().trim(),
  },
  {
    name: 'postgres',
    envKey: 'DATABASE_URL',
    mint: () => mintPgCredentials(),
  },
];

const raw = await readFile(configPath, 'utf-8');
const cfg = JSON.parse(raw);
for (const r of rotations) {
  for (const server of Object.values(cfg.mcpServers) as Array<Record<string, unknown>>) {
    const env = server.env as Record<string, string> | undefined;
    if (env?.[r.envKey]) {
      env[r.envKey] = r.mint();
    }
  }
}
await writeFile(configPath, JSON.stringify(cfg, null, 2));
console.log('Rotated', rotations.length, 'credentials.');

Rolling credentials without downtime

Naive rotation races: old token revoked before new token reaches every host. The three-step dance:

  1. Create the new token, keep the old one live.
  2. Deploy the new token to every host and verify first use.
  3. Revoke the old token only after the deploy settles.

When you suspect a leak

  1. Revoke the suspect token at the source (GitHub, Stripe, your DB) immediately.
  2. Check audit logs on the source for activity outside your IPs or hours.
  3. Rotate anything that shared a machine or config bundle with the compromised token.
  4. Run the malicious MCP checklist on every installed server.
  5. File a public advisory if the compromise affected customers.

What good looks like

The target state has no human ever pasting a raw token into a config file. Every value is a reference. Every reference resolves at launch from a short-lived secret. The secret manager records access per use. If that sounds like prod-infrastructure hygiene, it is — MCP configs deserve the same treatment.

Loadout

Build your AI agent loadout

Directory
Contact
© 2026 Loadout. Built on Angular 21 SSR.