Aller au contenu principal

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

EnvironnementURL SSEUsage
Dev clusterhttps://mcp.dev.recrutauto.fr/sseRecommandé pour le jour-le-jour
Productionhttps://mcp.recrutauto.fr/sseDonnées live — utiliser avec prudence
Local (docker-compose)http://localhost:3001/sseOffline, 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

  1. Un Personal Access Token valide au format ra_… — voir Tokens d'accès.
  2. 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).

Syntaxe obsolète

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.

  1. Ouvrez Settings > Developer > Edit Config.
  2. 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"
}
}
}
}
  1. Quittez complètement Claude Desktop (menu → Quit, pas juste Cmd+W) puis relancez.

Cursor

Settings → MCP ServersAdd → type SSE :

ChampValeur
Namerecrutauto
URLhttps://mcp.dev.recrutauto.fr/sse
HeaderAuthorization: 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.

Local ≠ Production

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

OutilDescription
search_candidatesRechercher des candidats par nom, titre, compétence ou headline (Elasticsearch full-text).
search_companiesRechercher des entreprises par nom. Requiert l'authentification (catalogue global).

Candidats

OutilDescription
get_candidateProfil complet d'un candidat avec expériences, formations, compétences, et statut dans les campagnes.
get_messagesHistorique complet des messages LinkedIn avec un candidat.
get_conversationsConversations récentes (dernier message par candidat), triées par date.

Campagnes

OutilDescription
list_campaignsLister les campagnes avec le nombre de candidats et la répartition par étape du workflow. Filtre status optionnel.
get_campaignDétails complets d'une campagne (filtres, tags, configuration, offre).
create_campaignCréer une nouvelle campagne (statut draft par défaut — jamais d'exécution n8n automatique).
update_campaignModifier les paramètres d'une campagne existante (merge partiel).

Pipeline

OutilDescription
get_campaign_candidatesCandidats d'une campagne avec statut workflow, score et notes. Paginé.
get_pipeline_statsStatistiques : taux de connexion, de réponse, de conversion, répartition par étape.
add_candidateAjouter un candidat à une campagne par son URL LinkedIn (unicité par abonnement).
update_candidate_notesMettre à jour les notes et tags d'un candidat dans une campagne.
evaluate_candidatesLancer 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

OutilDescription
list_message_templatesLister les templates de messages d'une campagne.
update_message_templateModifier le contenu ou la clé d'un template (syntaxe Jinja2).
Règles importantes
  • 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.*) avec subscription_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_id
  • message_typereconnect (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) :

  1. lit l'en-tête Authorization: Bearer ra_… ;
  2. valide le token contre la table personal_access_tokens (hash SHA-256, vérification d'expiration et de l'état actif) ;
  3. met à jour last_used_at ;
  4. lie le subscription_id résolu à la Task asyncio de la requête via un ContextVar ;
  5. 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.audit avec subscription_id, nom d'outil, et champs modifiés. Le mode preview=True sur evaluate_candidates émet un événement distinct (mcp.evaluate_candidates.preview).
  • Révocation instantanéerévoquer un token coupe immédiatement l'accès du client MCP correspondant. Un flux GET /sse dé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 disponibleevaluate_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 un 403 servi 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 un 401 avant tout handler.
    • Rate-limit ingress (30 r/s par IP) maintenu — il reste attaché au VirtualServer mcp-ingress via la policy rate-limit-mcp.
    • Audit log structuré sur chaque mutation (recrutauto.mcp.audit).
    • TransportSecuritySettings côté FastMCP limite les Host accepté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.

Mode legacy dangereux

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ômeCause probableRemède
error: unknown option '--url'Syntaxe CLI obsolèteL'URL est un argument positionnel : claude mcp add <name> --transport sse <URL> --header "..."
HTTP 401 invalid or expired tokenToken révoqué, expiré ou mal colléRegénérer via console.recrutauto.fr → Mon Compte → Tokens d'accès
HTTP 401 missing Bearer tokenHeader mal formatéVérifier la casse de Bearer et l'espace unique après
Timeout ou HTTP 000 sur curlDNS ou firewall sortant bloquédig mcp.dev.recrutauto.fr + vérifier proxy entreprise
HTTP 502 / 503 sur devPod MCP downkubectl -n recrutauto-dev get pods | grep mcp
claude mcp list ne montre pas recrutautoMauvais scope ou config non rechargéeclaude mcp list --scope user / --scope project ; relancer Claude Code
recrutauto: ... ✗ Failed to connectEn local : docker compose pas démarrédocker compose up -d ; vérifier docker ps | grep recrutauto-back
Outils répondent mais tout est videAbonnement sans donnéesVotre token est sur le bon subscription_id ? Voir l'UI /mon-compte
Claude ne propose pas les outils MCPPermissions pas accordéesclaude mcp approve recrutauto ou redémarrer la session
Token leaké dans une conversationIncident 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