Un serveur Model Context Protocol (MCP) qui expose Ashby comme surface d’outils pour Claude — permettant aux recruteurs et aux équipes recruiting-ops d’interroger la base de données de candidats, de parcourir le pipeline d’un poste, de faire remonter les candidatures en attente, et de documenter du contexte sur un candidat sans quitter la conversation. Le bundle d’artefacts dans apps/web/public/artifacts/mcp-server-ashby-recruiting/ livre un scaffold fonctionnel (README.md, pyproject.toml, src/ashby_recruiting_mcp/__init__.py, src/ashby_recruiting_mcp/server.py) qui enregistre onze outils couvrant les lectures, la recherche, les helpers de recrutement, et deux écritures à périmètre limité. Il est conçu pour être principalement en lecture : tout changement équivalent à un changement de statut continue de passer par l’interface Ashby où la piste d’audit et le workflow d’approbation existent déjà.
Quand l’utiliser
Choisissez cette solution quand l’équipe de recrutement est déjà productive dans Claude pour des travaux adjacents — rédiger des messages d’approche, résumer des scorecards, préparer des mises à jour pour les hiring managers — et que la friction est le changement de contexte constant vers Ashby pour chercher “à quel stage est ce candidat”, “quelles candidatures n’ont pas bougé cette semaine”, “quel est le temps moyen à l’on-site pour ce rôle”. Un serveur MCP effondre cette boucle. Le recruteur reste dans Claude, pose une question, obtient les données Ashby en temps réel intégrées à la conversation, et continue. La population idéale pour cela est les équipes recruiting-ops et recruiting-engineering d’au moins trois recruteurs ; en dessous de ce seuil, le coût d’installation et de maintenance dépasse les économies par question.
Évitez si l’équipe de recrutement est une seule personne, ou si le workspace contient des pipelines confidentiels au niveau du poste (executive search, staffing M&A, réorganisations non annoncées). Les clés API Ashby agissent avec une portée admin, donc une fois le serveur MCP câblé, chaque conversation a un accès potentiel en lecture à chaque candidat. Il n’y a pas d’ACL par recruteur sur l’API — le TODO ASHBY_ALLOWED_JOB_IDS du scaffold est la mitigation la plus proche, et c’est un TODO. Si le workspace ne peut pas tolérer cette exposition, soit gardez l’installation MCP sur une machine dédiée recruiting-ops, soit ne le déployez pas.
Évitez aussi si l’équipe est sur un stack réglementé qui n’a pas encore autorisé le routage des données de candidats via l’API Anthropic. Les candidats EU sont des données RGPD ; les candidats californiens sont des données CCPA ; faire remonter des notes via Claude route des données réglementées via un tiers. Faites d’abord signer la politique AI, puis déployez.
Et évitez si le mouvement de recrutement est piloté par le volume (>500 candidatures/semaine par recruteur) — à cette échelle, la latence par question d’un appel MCP (une à deux secondes, parfois plus pour pipeline_velocity) s’accumule mal, et l’équipe est mieux servie par un tableau de bord qui batcherise les mêmes questions en avance.
Setup
Les instructions complètes se trouvent dans apps/web/public/artifacts/mcp-server-ashby-recruiting/README.md. La version courte : clonez le bundle, pip install -e ., générez une clé API Ashby avec candidatesRead, candidatesWrite, applicationsRead, jobsRead, openingsRead, et interviewsRead, définissez ASHBY_API_KEY plus les trois variables d’environnement de réglage, et enregistrez le serveur dans claude_desktop_config.json de Claude Desktop. Prévoyez quatre-vingt-dix minutes pour l’installation initiale plus trente autres pour la vérification des outils sur le workspace en production ; la section Limits and TODOs de l’artefact liste les travaux à faire avant que ce soit production-ready.
Ce qu’il expose
Onze outils répartis en quatre buckets, tous définis dans src/ashby_recruiting_mcp/server.py :
Recherche : search_candidates(query, limit?), list_applications(job_id, status?), list_jobs(status?) — paginés par curseur, plafonnés à 20 pages par le helper.
Helpers de recrutement : stale_candidates(days_inactive=30, job_id?) retourne les candidatures actives sans activité depuis N jours, groupées par stage actuel. pipeline_velocity(job_id) calcule le nombre moyen de jours par stage sur la fenêtre de lookback configurée (90 jours par défaut), mettant en évidence où le funnel est bloqué.
Écritures légères : add_note(candidate_id, body) ajoute une note au fil d’activité du candidat ; add_tag(candidate_id, tag) applique un tag descriptif. Pas d’avancement de stage, pas d’archivage de candidature, pas de création d’offre, pas de suppression de candidat.
Choix d’ingénierie
Principalement en lecture, jamais d’écriture de statut. Les écritures légères sont délibérément limitées à des opérations additives à faible impact. Les notes et tags sont descriptifs — ils ne font pas avancer le candidat dans le pipeline, ne déclenchent pas d’automation en aval, et sont réversibles par le recruteur en deux clics. Les changements de stage, les archivages, et les routes de création d’offre ont été considérés et rejetés : ce sont des opérations à fort impact que l’interface Ashby protège par des flows de confirmation explicites, et un outil MCP n’a pas de garde équivalent. Mieux vaut les garder dans l’interface et garder l’histoire d’audit propre.
Un client HTTP par appel. Le scaffold ouvre et ferme un httpx.AsyncClient par requête plutôt que de réutiliser une session. C’est sous-optimal pour le débit brut mais contourne tous les bugs d’état partagé que le runtime MCP a historiquement fait remonter quand des clients de longue durée survivent à leur event loop. Pour la production, passez à un client singleton derrière un verrou async et ajoutez le middleware de retry mentionné dans les TODOs du README.
Pagination plafonnée à 20 pages.ashby_post_paginated vide jusqu’à 20 pages de curseur avant de s’arrêter. Avec la taille de page par défaut d’Ashby de 100, cela représente 2 000 enregistrements — suffisant pour toute requête raisonnable, assez petit pour qu’un appel d’outil incontrôlé ne puisse pas monopoliser le budget de rate-limit du workspace pendant plusieurs minutes. Ajustez le plafond si le workspace a genuinement besoin de remonter plus, mais la meilleure réponse est généralement un filtre plus strict.
Les noms de stages sont relus à chaque appel.pipeline_velocity relit le pipeline depuis application.interviewStageChanges à chaque invocation plutôt que de le mettre en cache. Les noms de stages dérivent entre les pipelines (un renommage trimestriel de “Phone Screen” en “Initial Call” est normal), et un cache périmé retourne des labels déroutants. Le coût est un aller-retour supplémentaire ; le bénéfice est que le recruteur peut faire confiance aux labels.
La posture d’audit est “ajout explicite uniquement”. Chaque écriture passe par un outil avec add_ dans son nom. Il n’y a pas d’update_, pas de set_, pas de delete_. Cela rend le filtrage du journal d’audit trivial : cherchez dans les logs du serveur MCP add_note et add_tag, et c’est l’inventaire complet des changements que l’IA a effectués sur le workspace.
Réalité des coûts
Trois postes, aucun dramatique, mais valant la peine d’être budgétés :
Abonnement Claude. Claude Pro à 20 $/recruteur/mois pour les individuels ; Claude Team à 25 $/recruteur/mois pour les workspaces partagés. Pour une équipe de six recruteurs, cela représente 150 $/mois. MCP lui-même n’ajoute rien à cette facture.
Hébergement du serveur. Le scaffold tourne comme un processus stdio dans Claude Desktop — coût d’hébergement nul. Si l’équipe passe à un endpoint MCP hébergé (multi-recruteurs, installation partagée unique), le coût réaliste est un conteneur Fly.io ou Render à 5 $/mois plus ce que l’équipe ajoute en observabilité. Comptez 10-30 $/mois tout compris.
Quota API Ashby. L’API d’Ashby est limitée par workspace (la guidance publiée est “gardez-le raisonnable” ; le plafond pratique est dans les quelques centaines d’appels par minute). Un power user déclenchant une question MCP par minute sur une journée de 8 heures représente ~480 appels — bien en dessous du plafond. pipeline_velocity est le plus coûteux : il émet un appel application.list plus un appel interviewStageChanges par candidature, donc un poste avec 200 candidatures est une opération de 201 appels. Ne le bouclez pas sur tous les postes du workspace à la fois.
Total, pour une équipe de six recruteurs utilisant cela sérieusement : moins de 200 $/mois.
Le gain principal est le temps des recruteurs. Un back-of-envelope d’équipes ayant câblé cela : ~15 minutes/recruteur/jour récupérées à ne plus “switcher vers Ashby pour chercher X”. Sur six recruteurs, c’est ~30 heures/mois — au coût plein du recruteur (disons 100 $/heure), cela représente 3 000 $/mois de capacité restituée. Les économies en dollars dominent le coût en dollars d’un ordre de grandeur. La raison pour laquelle les équipes sous-déploient encore est la friction d’installation, pas le ROI.
À quoi ressemble le succès
Trois métriques, suivies chaque semaine pendant le premier mois :
Appels d’outils MCP par recruteur par jour. Cible : 10-30. En dessous de 10 signifie que les recruteurs ne l’utilisent pas vraiment (ont probablement oublié qu’il était là, ou ont rencontré un bug tôt et abandonné). Au-dessus de 50 signifie qu’un workflow tourne qui devrait être un job planifié, pas un outil interactif.
Réduction de stale_candidates. Suivez le nombre de candidatures actives inactives depuis plus de 30 jours, chaque semaine. Dans le mois suivant le déploiement, ce chiffre devrait baisser de 30-50 % — le helper rend le travail visible, et le travail visible est fait.
NPS des recruteurs sur “Claude est-il utile pour le travail Ashby”. Sondage à la semaine 1 et à la semaine 4. S’il n’est pas solidement positif à la semaine 4, l’installation est cassée ou les mauvais outils ont été livrés — revenez aux recruteurs et demandez quels deux outils ils utilisent et quels neuf ils ignorent.
Comparaison avec les alternatives
Prompts Claude.ai personnalisés collant des exports Ashby. C’est le statu quo pour la plupart des équipes : exporter un CSV depuis Ashby, le coller dans une conversation Claude, poser la question. Ça marche et ne coûte rien de plus, mais les données sont périmées dès le moment du collage, le recruteur fait le travail d’export à chaque fois, et il n’y a pas de chemin pour documenter du contexte en retour dans Ashby. MCP gagne parce que les données sont en temps réel et la boucle est aller-retour — Claude peut lire et (de manière limitée) écrire en retour.
Les fonctionnalités AI natives d’Ashby. Ashby livre une recherche de candidats alimentée par l’IA, des résumés et du matching. Elles sont utiles mais elles sont à l’intérieur d’Ashby — elles n’aident pas quand le recruteur est dans Claude à faire autre chose. Elles n’aident pas non plus avec la synthèse multi-outils (Ashby + Linear + Slack), qui est la frontière la plus intéressante. MCP est le bon choix quand le recruteur veut Claude comme substrat ; l’IA native Ashby est le bon choix quand le recruteur veut Ashby comme substrat. Beaucoup d’équipes veulent les deux.
Glue Zapier. Un Zap peut se déclencher sur des événements Ashby et poster dans Slack ou notifier Claude.ai, mais les flows pilotés par Zap sont unidirectionnels et en forme d’événement. Ils ne peuvent pas répondre à des questions ad hoc comme “montrez-moi les candidats en attente pour le rôle backend senior”. MCP est le bon choix quand la forme de la question est interactive ; Zap est le bon choix quand la forme de la question est “dites-moi quand X se produit”.
Points de vigilance
Trois modes d’échec nommés, chacun associé à un garde-fou :
La clé API agit avec une portée admin. Quiconque a accès à l’installation Claude avec ce MCP câblé peut lire chaque candidat, y compris les pipelines de direction. Garde-fou : limitez l’installation MCP aux machines de l’équipe de recrutement uniquement, documentez l’exposition avec la sécurité par écrit, faites tourner la clé trimestriellement, et traitez l’installation Claude comme un endpoint privilégié (pas de logins partagés, pas de claude_desktop_config.json commité).
Les écritures légères contournent les workflows d’approbation Ashby.add_note et add_tag écrivent directement via l’API — ils ne déclenchent aucune approbation qui se déclencherait normalement dans l’interface Ashby. Garde-fou : n’utilisez pas les outils d’écriture légère pour des tags équivalents à un statut (hired, offer-extended, do-not-hire) ; la description de l’outil add_tag du scaffold le signale explicitement, et le responsable recruiting-ops devrait le renforcer lors de l’onboarding.
pipeline_velocity est le plus gourmand en rate-limit. Il émet un appel par candidature dans le pipeline du poste. Un poste avec 500 candidatures est une opération de 501 appels qui peut saturer le budget de rate-limit du workspace pendant quelques minutes — visible par d’autres automatisations comme des 429. Garde-fou : limitez les utilisations concurrentes (un recruteur à la fois par poste), et le TODO du README d’ajouter un backoff exponentiel sur les 429 est non-optionnel avant tout déploiement à l’échelle de l’équipe.
Stack
Ashby (ATS, la source de données). Claude Desktop ou Claude Code (le host MCP). Runtime Python 3.11+ pour le serveur. Le SDK Python mcp (mcp>=1.2.0), httpx pour le client HTTP async, pydantic pour la validation. Pas de base de données, pas de queue, pas de broker — le serveur est sans état et relit tout à chaque appel.
# mcp-server-ashby-recruiting
An MCP server tuned for recruiting teams using Ashby. Exposes candidates, applications, jobs, and openings as Claude tools, plus two recruiting-specific helpers (`stale_candidates`, `pipeline_velocity`) and two light-write tools (`add_note`, `add_tag`) that recruiters can drive from a Claude conversation.
> **STATUS: scaffold — not runtime-tested.** The code below is structurally
> complete and follows the official `mcp` Python SDK conventions, but it
> has not been executed against a live Ashby workspace. Treat it as a
> starting point you adapt to your team's pipeline conventions, not as a
> deployable binary. Custom field IDs, stage names, source labels, and
> pipeline structure vary by workspace — re-test every helper against the
> Ashby UI before relying on it.
## What it exposes
### Object-read tools (read-only)
- `get_candidate(candidate_id)` — full candidate properties, contact info, and current applications
- `get_application(application_id)` — application record with current stage, source, and history
- `get_job(job_id)` — job record with hiring team, status, and pipeline reference
- `get_opening(opening_id)` — opening (req) record with target start and headcount
### Search tools (read-only)
- `search_candidates(query, limit?)` — name / email / company substring search across the candidate database
- `list_applications(job_id, status?)` — applications for a job, optionally filtered by status (`Active`, `Hired`, `Archived`)
- `list_jobs(status?)` — jobs in the workspace, optionally filtered by status (`Open`, `Closed`, `Draft`)
### Recruiting-specific helpers (read-only)
- `stale_candidates(days_inactive=30)` — active candidates with no application activity (stage change, note, interview event) for N days; output grouped by current stage
- `pipeline_velocity(job_id)` — average days-in-stage per stage for a job's pipeline, computed across the last 90 days of stage changes; surfaces where the funnel is stuck
### Light-write tools (recruiter-driven)
- `add_note(candidate_id, body)` — append a note to a candidate's record (visible in the Ashby UI activity feed)
- `add_tag(candidate_id, tag)` — apply a tag to a candidate (e.g. `phone-screen-passed`, `do-not-pursue-2026`)
The server **does not** expose stage advances, application archives, offer creation, or candidate deletes. The principle: Claude can ask, summarize, and document; the recruiter drives every candidate-facing change in the Ashby UI where the audit trail and approval workflow already live.
## Setup
### 1. Install
```bash
git clone <wherever you put this>
cd mcp-server-ashby-recruiting
python -m venv .venv
source .venv/bin/activate # or .venv\Scripts\activate on Windows
pip install -e .
```
### 2. Create an Ashby API key
In Ashby: Admin → API Keys → Create API Key. Grant scopes:
- `candidatesRead` and `candidatesWrite` (write only for `add_note`, `add_tag`)
- `applicationsRead`
- `jobsRead`
- `openingsRead`
- `interviewsRead` (used by `stale_candidates` to detect interview activity)
Copy the generated key. Ashby keys are workspace-scoped and act with the privileges of an admin role — treat them like a production secret.
### 3. Configure environment
```bash
export ASHBY_API_KEY="ashby_live_..."
export ASHBY_WORKSPACE_NAME="acme" # used in tool descriptions and logs
export ASHBY_STALE_DEFAULT_DAYS="30" # default for stale_candidates
export ASHBY_VELOCITY_LOOKBACK_DAYS="90" # window for pipeline_velocity
```
#### `ASHBY_API_KEY`
The generated key from Admin → API Keys. Use HTTP Basic auth: the key is the username, password is blank. The server handles this automatically.
#### `ASHBY_WORKSPACE_NAME`
A short label (no spaces) used in tool descriptions so recruiters know which workspace they are querying when multiple workspaces are wired into one Claude install. Optional — defaults to `default`.
#### `ASHBY_STALE_DEFAULT_DAYS`
Default value for the `days_inactive` parameter when the recruiter does not specify one. 30 is the sane starting point for engineering pipelines; 14 for high-volume sales pipelines.
#### `ASHBY_VELOCITY_LOOKBACK_DAYS`
Window over which `pipeline_velocity` averages stage durations. 90 days smooths out single-candidate noise without going stale across hiring-plan changes.
### 4. Register with Claude Desktop
Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
```json
{
"mcpServers": {
"ashby-recruiting": {
"command": "python",
"args": ["-m", "ashby_recruiting_mcp.server"],
"env": {
"ASHBY_API_KEY": "ashby_live_...",
"ASHBY_WORKSPACE_NAME": "acme",
"ASHBY_STALE_DEFAULT_DAYS": "30",
"ASHBY_VELOCITY_LOOKBACK_DAYS": "90"
}
}
}
}
```
Restart Claude Desktop. You should see ~11 tools registered under `ashby-recruiting`.
### 5. Sanity-check
Ask Claude: "Find candidates whose name starts with `Smith`." Then: "Show me stale candidates in the senior backend engineer pipeline." Compare the output against the equivalent filtered view in Ashby. Tune `ASHBY_STALE_DEFAULT_DAYS` until the result matches the recruiter's intuition for "should have heard back by now".
## Watch-outs
- **API keys bypass per-recruiter permissions.** An Ashby API key acts with full admin scope. Anyone with access to the MCP client sees every candidate the workspace contains, including senior-leadership pipelines and rejected candidates with sensitive notes. Guard: scope the MCP install to recruiting-team machines only, document the exposure with security, rotate quarterly.
- **Stage names drift across pipelines.** `pipeline_velocity` reads the pipeline definition fresh on every call so renames don't break the helper, but year-over-year comparisons get noisy when the pipeline shape changes. Guard: snapshot pipeline definitions quarterly if you care about historical trend lines.
- **Light-write tools bypass Ashby approval workflows.** `add_note` and `add_tag` write directly through the API — they do not trigger any approval that would normally fire in the UI. Guard: do not use light-write tools for status-change-equivalent tags (`hired`, `offer-extended`); reserve for descriptive tags only.
- **Rate limits are workspace-shared.** Ashby's API is rate-limited per workspace, not per key. A chatty MCP session can crowd out the candidate-engagement automation that depends on the same workspace. Guard: cap concurrent calls (the scaffold uses one client per call) and watch for 429s in the logs during heavy use.
- **Candidate data is regulated.** EU candidates fall under GDPR; California candidates under CCPA. Surfacing candidate notes through Claude potentially routes regulated data through the Anthropic API. Guard: confirm the workspace's AI policy explicitly authorizes this.
## Limits and TODOs (before production use)
- [ ] Add request-level retries with exponential backoff on 429 and 5xx (Ashby returns 429 readily under sustained load).
- [ ] Replace `str(data)` response stringification with structured JSON serialization that strips PII fields the recruiter did not ask for.
- [ ] Write integration tests against an Ashby sandbox workspace.
- [ ] Add structured logging via `python-json-logger` with candidate IDs and tool names; never log raw note bodies.
- [ ] Wire optional Sentry / OpenTelemetry export for production telemetry.
- [ ] Validate stage IDs returned by `pipeline_velocity` against the live pipeline shape on first call per session, fail loud if cached IDs no longer exist.
- [ ] Add an allow-list env var (`ASHBY_ALLOWED_JOB_IDS`) to scope MCP visibility to a subset of jobs for confidentiality-sensitive roles.
- [ ] Audit-log every light-write tool call to a separate file the recruiting-ops lead reviews weekly.