Les configurations du serveur MCP stockent les clés API dans des fichiers JSON bruts comme .mcp.json ou claude_desktop_config.json. Lorsqu'une clé expire, est compromise ou atteint ses limites de débit, tous les agents utilisant ce serveur MCP tombent en panne. Modifier manuellement les fichiers de configuration sur plusieurs machines est source d'erreurs et lent. Ce tutoriel construit un utilitaire Python qui lit les configurations MCP, fait tourner les clés API à partir d'un stockage sécurisé, valide la nouvelle clé auprès de l'API cible, et écrit la configuration mise à jour de manière atomique. L'approche fonctionne pour tout serveur MCP qui transmet les identifiants via des variables d'environnement ou des arguments de ligne de commande.
Prérequis
- Python 3.8 ou version ultérieure installé
- bibliothèque requests installée
- Un fichier de configuration MCP existant (.mcp.json ou équivalent)
- Une clé API Scavio pour les tests de validation
Parcours
Étape 1: Lire la configuration MCP actuelle
Charger le fichier de configuration MCP et identifier tous les serveurs qui utilisent des variables d'environnement de clé API. Le script recherche les clés d'environnement contenant API_KEY ou TOKEN.
import json
import os
def load_mcp_config(path: str) -> dict:
with open(path, "r") as f:
return json.load(f)
config_path = os.path.expanduser("~/.mcp.json")
config = load_mcp_config(config_path)
for name, server in config.get("mcpServers", {}).items():
env = server.get("env", {})
key_vars = [k for k in env if "API_KEY" in k or "TOKEN" in k]
print(f"{name}: {key_vars}")Étape 2: Récupérer la clé tournée depuis votre stockage de secrets
Extraire la nouvelle clé API d'une variable d'environnement, d'un gestionnaire de secrets ou d'un fichier local chiffré. Cet exemple lit à partir de variables d'environnement avec une convention de suffixe _NEW.
def get_rotated_key(var_name: str) -> str | None:
new_var = f"{var_name}_NEW"
rotated = os.environ.get(new_var)
if rotated:
print(f"Rotation candidate found: {new_var}")
return rotatedÉtape 3: Valider la nouvelle clé avant l'écriture
Effectuer un appel API léger avec la nouvelle clé pour confirmer qu'elle fonctionne. Pour les clés Scavio, POST une requête de recherche minimale. Si la validation échoue, sauter la rotation pour cette clé.
import requests
def validate_scavio_key(api_key: str) -> bool:
try:
r = requests.post(
"https://api.scavio.dev/api/v1/search",
headers={"x-api-key": api_key},
json={"query": "test", "country_code": "us"},
timeout=10
)
return r.status_code == 200
except Exception:
return False
new_key = get_rotated_key("SCAVIO_API_KEY")
if new_key and validate_scavio_key(new_key):
print("New key validated successfully")
else:
print("Validation failed, skipping rotation")Étape 4: Écrire la configuration mise à jour de manière atomique
Écrire d'abord dans un fichier temporaire, puis le renommer par-dessus l'original. Cela évite que des écritures partielles n'endommagent la configuration si le processus est interrompu.
import tempfile
import shutil
def rotate_config(config_path: str, config: dict, server_name: str, var_name: str, new_key: str) -> None:
config["mcpServers"][server_name]["env"][var_name] = new_key
tmp = tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False)
json.dump(config, tmp, indent=2)
tmp.close()
shutil.move(tmp.name, config_path)
print(f"Rotated {var_name} for {server_name}")Exemple Python
import json
import os
import tempfile
import shutil
import requests
def load_config(path: str) -> dict:
with open(path, "r") as f:
return json.load(f)
def validate_key(api_key: str) -> bool:
try:
r = requests.post(
"https://api.scavio.dev/api/v1/search",
headers={"x-api-key": api_key},
json={"query": "test", "country_code": "us"},
timeout=10
)
return r.status_code == 200
except Exception:
return False
def atomic_write(path: str, data: dict) -> None:
tmp = tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False)
json.dump(data, tmp, indent=2)
tmp.close()
shutil.move(tmp.name, path)
def rotate_all(config_path: str) -> None:
config = load_config(config_path)
rotated = 0
for name, server in config.get("mcpServers", {}).items():
env = server.get("env", {})
for var in list(env.keys()):
if "API_KEY" not in var and "TOKEN" not in var:
continue
new_key = os.environ.get(f"{var}_NEW")
if not new_key:
continue
if validate_key(new_key):
env[var] = new_key
rotated += 1
print(f"Rotated {var} for {name}")
else:
print(f"Validation failed for {var}, skipping")
if rotated > 0:
atomic_write(config_path, config)
print(f"Config updated with {rotated} rotated keys")
else:
print("No keys rotated")
if __name__ == "__main__":
rotate_all(os.path.expanduser("~/.mcp.json"))Exemple JavaScript
const fs = require("fs");
const os = require("os");
const path = require("path");
async function validateKey(apiKey) {
try {
const res = await fetch("https://api.scavio.dev/api/v1/search", {
method: "POST",
headers: { "x-api-key": apiKey, "Content-Type": "application/json" },
body: JSON.stringify({ query: "test", country_code: "us" })
});
return res.ok;
} catch {
return false;
}
}
async function rotateAll(configPath) {
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
let rotated = 0;
for (const [name, server] of Object.entries(config.mcpServers || {})) {
const env = server.env || {};
for (const varName of Object.keys(env)) {
if (!varName.includes("API_KEY") && !varName.includes("TOKEN")) continue;
const newKey = process.env[`${varName}_NEW`];
if (!newKey) continue;
if (await validateKey(newKey)) {
env[varName] = newKey;
rotated++;
console.log(`Rotated ${varName} for ${name}`);
} else {
console.log(`Validation failed for ${varName}, skipping`);
}
}
}
if (rotated > 0) {
const tmp = path.join(os.tmpdir(), `mcp-${Date.now()}.json`);
fs.writeFileSync(tmp, JSON.stringify(config, null, 2));
fs.renameSync(tmp, configPath);
console.log(`Config updated with ${rotated} rotated keys`);
}
}
rotateAll(path.join(os.homedir(), ".mcp.json")).catch(console.error);Sortie attendue
SCAVIO_API_KEY: rotation candidate found
New key validated successfully
Rotated SCAVIO_API_KEY for scavio-search
Config updated with 1 rotated keys