Serveur MCP RecrutAuto
Le Model Context Protocol (MCP) est un standard ouvert qui permet aux assistants IA comme Claude d'accéder à vos outils et données en toute sécurité. Recrut'Auto expose un serveur MCP qui donne à votre assistant IA l'accès à vos campagnes, candidats, messages et templates.
En pratique : vous parlez en langage naturel à Claude, et il exécute les actions dans Recrut'Auto pour vous.
Pourquoi utiliser le MCP ?
- Rapidité — « Liste les candidats qui ont répondu cette semaine sur la campagne Dev Python » plutôt que filtrer dans l'UI.
- Analyse — Claude peut croiser plusieurs campagnes, identifier des patterns, proposer des priorités.
- Rédaction — génération de messages de relance personnalisés à partir du profil candidat.
- Création rapide — créez une campagne en décrivant votre besoin en une phrase.
Endpoints disponibles
| Environnement | URL SSE | Usage |
|---|---|---|
| Dev cluster | https://mcp.dev.recrutauto.fr/sse | Recommandé pour le jour-le-jour |
| Production | https://mcp.recrutauto.fr/sse | Données live — utiliser avec prudence |
| Local (docker-compose) | http://localhost:3001/sse | Offline, isolé, nécessite docker compose up |
Tous les endpoints exigent un header Authorization: Bearer ra_<token> sauf le local (cf. section dédiée).
Prérequis
- Un Personal Access Token valide au format
ra_…— voir Tokens d'accès. - L'un des clients suivants installé : Claude Code CLI, Claude Desktop, Cursor ou VS Code (extension Claude).
Configuration par client
Claude Code (CLI)
Syntaxe correcte (Claude Code ≥ 2.1) — l'URL est un argument positionnel, pas un flag --url :
# Dev (recommandé)
claude mcp add recrutauto \
--transport sse \
https://mcp.dev.recrutauto.fr/sse \
--header "Authorization: Bearer ra_VOTRE_TOKEN" \
--scope user
# Production (données live)
claude mcp add recrutauto-prod \
--transport sse \
https://mcp.recrutauto.fr/sse \
--header "Authorization: Bearer ra_VOTRE_TOKEN" \
--scope user
Le flag --scope user écrit la config dans ~/.claude.json → disponible dans tous vos projets. Les autres valeurs de scope sont project (commit dans ./.claude/settings.json — à éviter pour un token) et local (par défaut, limité au répertoire courant).
Vérification :
claude mcp list
# → recrutauto: https://mcp.dev.recrutauto.fr/sse (SSE) - ✓ Connected
Dans une session Claude Code, les outils apparaissent sous mcp__recrutauto__* (ex : mcp__recrutauto__list_campaigns).
Les anciennes versions de la doc mentionnaient --url <URL> — c'est incorrect. La CLI renverra error: unknown option '--url'. Utilisez l'URL en argument positionnel.
Claude Desktop (Mac / Windows)
Claude Desktop ne parle que stdio. On passe par le bridge mcp-remote pour accéder à un serveur SSE distant.
- Ouvrez Settings > Developer > Edit Config.
- Ajoutez dans
claude_desktop_config.json:
{
"mcpServers": {
"recrutauto": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://mcp.dev.recrutauto.fr/sse",
"--header",
"Authorization: Bearer ${RECRUTAUTO_MCP_TOKEN}"
],
"env": {
"RECRUTAUTO_MCP_TOKEN": "ra_VOTRE_TOKEN"
}
}
}
}
- Quittez complètement Claude Desktop (menu → Quit, pas juste Cmd+W) puis relancez.
Cursor
Settings → MCP Servers → Add → type SSE :
| Champ | Valeur |
|---|---|
| Name | recrutauto |
| URL | https://mcp.dev.recrutauto.fr/sse |
| Header | Authorization: Bearer ra_VOTRE_TOKEN |
VS Code (extension Claude)
Même procédure que Cursor via la section MCP de l'extension.
Développement local (docker-compose)
Pour itérer sur le code du serveur MCP lui-même ou travailler offline, utilisez le conteneur docker-compose. Aucun token requis : le conteneur utilise le fallback legacy MCP_SUBSCRIPTION_ID=1 (à ne JAMAIS utiliser en production).
Via .mcp.json (auto-chargé par Claude Code dans ce projet)
Le fichier .mcp.json du monorepo déclare déjà recrutauto en mode docker exec :
{
"mcpServers": {
"recrutauto": {
"command": "docker",
"args": [
"exec", "-i",
"-e", "MCP_SUBSCRIPTION_ID=1",
"recrutauto-back-1",
"python", "/app/src/mcp_server.py"
]
}
}
}
Prérequis : docker compose up -d doit tourner et recrutauto-back-1 doit être Up. Claude Code lance alors un process Python stdio dans le conteneur existant — aucune dépendance à installer sur l'hôte.
Via SSE sur localhost:3001
Le conteneur recrutauto-mcp-1 expose aussi un endpoint SSE sur http://localhost:3001/sse. Utile pour tester avec Cursor / Desktop sans déployer :
claude mcp add recrutauto-local \
--transport sse \
http://localhost:3001/sse \
--header "Authorization: Bearer ra_LOCAL_TOKEN" \
--scope user
Nécessite que vous ayez créé un PAT local via l'UI ou l'API et que vous le passiez. Sans token valide → 401.
Le mode legacy MCP_SUBSCRIPTION_ID court-circuite complètement la validation du token. Il est acceptable uniquement en local isolé — jamais en dev cluster ni en prod. La présence du fichier .mcp.json garantit que ce mode reste cantonné à docker-compose up local.
Outils disponibles
Le serveur MCP expose 16 outils et 4 prompts, regroupés par domaine.
Recherche
| Outil | Description |
|---|---|
search_candidates | Rechercher des candidats par nom, titre, compétence ou headline (Elasticsearch full-text). |
search_companies | Rechercher des entreprises par nom. Requiert l'authentification (catalogue global). |
Candidats
| Outil | Description |
|---|---|
get_candidate | Profil complet d'un candidat avec expériences, formations, compétences, et statut dans les campagnes. |
get_messages | Historique complet des messages LinkedIn avec un candidat. |
get_conversations | Conversations récentes (dernier message par candidat), triées par date. |
Campagnes
| Outil | Description |
|---|---|
list_campaigns | Lister les campagnes avec le nombre de candidats et la répartition par étape du workflow. Filtre status optionnel. |
get_campaign | Détails complets d'une campagne (filtres, tags, configuration, offre). |
create_campaign | Créer une nouvelle campagne (statut draft par défaut — jamais d'exécution n8n automatique). |
update_campaign | Modifier les paramètres d'une campagne existante (merge partiel). |
Pipeline
| Outil | Description |
|---|---|
get_campaign_candidates | Candidats d'une campagne avec statut workflow, score et notes. Paginé. |
get_pipeline_stats | Statistiques : taux de connexion, de réponse, de conversion, répartition par étape. |
add_candidate | Ajouter un candidat à une campagne par son URL LinkedIn (unicité par abonnement). |
update_candidate_notes | Mettre à jour les notes et tags d'un candidat dans une campagne. |
evaluate_candidates | Lancer l'évaluation et le scoring (11 filtres + score 0-10). Paramètre preview: bool — si true, calcule les changements projetés et les reverte sans persister. Utile pour visualiser l'impact avant d'appliquer. |
Templates
| Outil | Description |
|---|---|
list_message_templates | Lister les templates de messages d'une campagne. |
update_message_template | Modifier le contenu ou la clé d'un template (syntaxe Jinja2). |
- Les campagnes créées via MCP sont en statut draft : activez-les manuellement depuis l'interface pour lancer l'exécution n8n.
- Un candidat ne peut être que dans une seule campagne par abonnement.
- Chaque écriture émet un événement d'audit structuré (
recrutauto.mcp.audit.*) avecsubscription_id, outil invoqué, et champs modifiés. - Les templates utilisent Jinja2 avec les variables :
{{candidate.firstname}},{{candidate.lastname}},{{candidate.title}},{{candidate.company}},{{recruiter.firstname}},{{offer.title}},{{booking_url}},{{politeness}},{{period}}.
Prompts pré-configurés
4 prompts MCP prêts à l'emploi — invoquez-les via / dans Claude Desktop ou Cursor, ou demandez-les explicitement dans Claude Code.
campaign_summary
Résumé complet d'une campagne avec stats et candidats clés. Chaîne get_campaign + get_pipeline_stats + get_campaign_candidates.
Paramètre : campaign_id
candidate_profile
Analyse du profil complet d'un candidat et de son historique d'interactions. Chaîne get_candidate + get_messages.
Paramètre : candidate_id
pipeline_review
Revue globale de toutes les campagnes actives et identification des actions prioritaires. Chaîne list_campaigns + get_pipeline_stats pour chaque.
Aucun paramètre.
draft_message
Rédaction d'un message personnalisé pour un candidat. Chaîne get_candidate + get_messages + rédaction contextuelle.
Paramètres :
candidate_idmessage_type—reconnect(relance),hunt(première approche),meet(proposition RDV),not_interested(réponse polie à un refus).
Exemples de conversation
Vous : « Liste mes 3 dernières campagnes actives et donne-moi leurs taux de réponse »
Claude appelle list_campaigns(status="en cours") puis get_pipeline_stats(campaign_id=X) pour chaque.
Vous : « Crée une campagne Dev Go Senior, 5 à 10 ans d'expérience, sur Paris ou Remote, avec tutoiement »
Claude appelle :
create_campaign(
name="Dev Go Senior",
tags="Go,Backend,Senior",
experience_min=5,
experience_max=10,
locations="Paris,Remote",
politeness="cool"
)
Vous : « Simule le scoring de la campagne 42 sans rien modifier pour voir l'impact »
Claude appelle evaluate_candidates(campaign_id=42, preview=True) — retourne un diff before/after par candidat sans toucher la base.
Vous : « Pour le candidat 42, rédige un message de relance qui fait référence à son expérience chez Datadog »
Claude appelle get_candidate(42) puis get_messages(42), puis rédige un message personnalisé.
Validation rapide
Après configuration, trois tests pour confirmer que tout marche :
1. Connectivité réseau (doit renvoyer 401 avec un JSON d'erreur propre)
curl -s -o - -w "\nHTTP %{http_code}\n" https://mcp.dev.recrutauto.fr/sse
# → {"error":"unauthorized","detail":"missing Bearer token"}
# HTTP 401
2. Authentification (stream SSE ouvert, Ctrl+C pour couper)
curl -N -H "Authorization: Bearer ra_VOTRE_TOKEN" https://mcp.dev.recrutauto.fr/sse
# → event: endpoint
# data: /messages/?session_id=...
Si vous voyez event: endpoint, votre token est valide et le stream est ouvert.
3. Découverte depuis Claude Code
claude mcp list
# → recrutauto: ... - ✓ Connected
Puis dans une session : « liste mes campagnes » → Claude doit invoquer mcp__recrutauto__list_campaigns.
Sécurité
Modèle d'authentification
Le serveur expose le protocole MCP sur un transport SSE (Server-Sent Events). Deux routes HTTP sont impliquées : GET /sse ouvre le flux de notifications, et chaque appel d'outil transite par un POST /messages/?session_id=... dédié.
Sur chaque requête entrante, un middleware Starlette (mcp_server.py) :
- lit l'en-tête
Authorization: Bearer ra_…; - valide le token contre la table
personal_access_tokens(hash SHA-256, vérification d'expiration et de l'état actif) ; - met à jour
last_used_at; - lie le
subscription_idrésolu à la Task asyncio de la requête via unContextVar; - délègue à FastMCP, qui invoque le handler de l'outil dans la même Task — le handler lit donc automatiquement le bon
subscription_id.
Toute requête sans en-tête Authorization valide est rejetée avec un HTTP 401 avant même d'atteindre le moteur MCP. Il n'existe pas de mode « sans auth » ni de token global côté serveur : l'instance est strictement multi-locataire.
Garanties
- Isolation par abonnement — votre token n'accède qu'à vos campagnes, candidats et messages. Chaque service filtre explicitement sur
subscription_id: impossible d'accéder aux données d'un autre utilisateur, même en forgeant l'identifiant d'un objet. - Audit trail — chaque écriture (création/modification de campagne, ajout/mise à jour de candidat, évaluation, mise à jour de template) émet un événement structuré sous le logger
recrutauto.mcp.auditavecsubscription_id, nom d'outil, et champs modifiés. Le modepreview=Truesurevaluate_candidatesémet un événement distinct (mcp.evaluate_candidates.preview). - Révocation instantanée — révoquer un token coupe immédiatement l'accès du client MCP correspondant. Un flux
GET /ssedéjà ouvert reste connecté mais ne peut plus invoquer d'outils. - Rate-limit — 30 req/s par IP au niveau ingress Nginx (
policy-rate-limit-mcp). - Chiffrement en transit — l'ingress termine TLS via cert-manager ; le transport interne au cluster reste en HTTP.
- Dry-run disponible —
evaluate_candidates(preview=True)renvoie la projection sans persister aucune modification.
WAF et ModSecurity
Contrairement aux autres ingresses (back, front, admin, backoffice), le VirtualServer mcp-ingress n'active pas ModSecurity / OWASP CRS. Ce choix est assumé :
- Les payloads FastMCP sont du JSON-RPC 2.0 (
method,params,id) dont les chaînes déclenchent systématiquement les règles CRS 942xxx (SQLi) et 921xxx (HTTP argument injection) — faux positifs bloquant les appels d'outils légitimes par un403servi directement par nginx, sans jamais atteindre le pod. - La défense-in-depth est portée par les couches applicatives :
BearerAuthMiddleware(validation PAT SHA-256) rejette toute requête non-authentifiée avec un401avant tout handler.- Rate-limit ingress (30 r/s par IP) maintenu — il reste attaché au VirtualServer
mcp-ingressvia la policyrate-limit-mcp. - Audit log structuré sur chaque mutation (
recrutauto.mcp.audit). TransportSecuritySettingscôté FastMCP limite lesHostacceptés (MCP_ALLOWED_HOSTS) — anti DNS-rebinding.
Cette posture est documentée dans devops/CLAUDE.md et matérialisée par un commentaire explicite dans devops/app/templates/mcp-ingress.yaml.
Mode stdio (exécution locale)
Le même binaire peut être lancé en mode stdio (sans flag --sse). Le processus est alors mono-locataire et le token est résolu une seule fois au démarrage depuis la variable d'environnement MCP_API_TOKEN.
La variable MCP_SUBSCRIPTION_ID reste acceptée en mode stdio pour compatibilité avec d'anciens setups locaux, mais elle court-circuite complètement la validation du token. Ne l'utilisez JAMAIS en production ni en dev partagé — uniquement en docker-compose local isolé, où le risque est contenu au poste du développeur. Préférez toujours MCP_API_TOKEN.
Troubleshooting
| Symptôme | Cause probable | Remède |
|---|---|---|
error: unknown option '--url' | Syntaxe CLI obsolète | L'URL est un argument positionnel : claude mcp add <name> --transport sse <URL> --header "..." |
HTTP 401 invalid or expired token | Token révoqué, expiré ou mal collé | Regénérer via console.recrutauto.fr → Mon Compte → Tokens d'accès |
HTTP 401 missing Bearer token | Header mal formaté | Vérifier la casse de Bearer et l'espace unique après |
Timeout ou HTTP 000 sur curl | DNS ou firewall sortant bloqué | dig mcp.dev.recrutauto.fr + vérifier proxy entreprise |
HTTP 502 / 503 sur dev | Pod MCP down | kubectl -n recrutauto-dev get pods | grep mcp |
claude mcp list ne montre pas recrutauto | Mauvais scope ou config non rechargée | claude mcp list --scope user / --scope project ; relancer Claude Code |
recrutauto: ... ✗ Failed to connect | En local : docker compose pas démarré | docker compose up -d ; vérifier docker ps | grep recrutauto-back |
| Outils répondent mais tout est vide | Abonnement sans données | Votre token est sur le bon subscription_id ? Voir l'UI /mon-compte |
| Claude ne propose pas les outils MCP | Permissions pas accordées | claude mcp approve recrutauto ou redémarrer la session |
| Token leaké dans une conversation | Incident sécurité | Révoquez immédiatement (Mon Compte → Tokens → corbeille) et régénérez |
Commandes de diagnostic
# Version CLI Claude Code
claude --version # doit être >= 2.1
# Config effective (liste tous scopes)
claude mcp list
# Supprimer une entrée problématique
claude mcp remove recrutauto --scope user
# Tester la connectivité brute
curl -i https://mcp.dev.recrutauto.fr/sse # 401 attendu
# Tester avec token
curl -N -H "Authorization: Bearer ra_xxx" https://mcp.dev.recrutauto.fr/sse
# En local : vérifier que le conteneur écoute
docker ps | grep recrutauto-mcp
curl -i http://localhost:3001/sse # 401 attendu
# Inspecter les logs du MCP local
docker logs recrutauto-mcp-1 --tail 30
Ressources externes
- Spécification MCP — le standard ouvert.
- Anthropic MCP documentation — guide officiel côté client.
mcp-remote— bridge stdio ↔ SSE/HTTP pour Claude Desktop.