Skip to main content
Glama
decroche-mcp

decroche-mcp

by Casius999

decroche-mcp

MCP 360° pour décrocher un emploi — bat l'ATS et le screener LLM honnêtement (Phase 1 : cœur anti-rejet CV).

CI OpenSSF Scorecard Release License: MIT

Table of Contents

Related MCP server: cv-mirror-mcp

Overview

decroche-mcp is a deterministic Python/FastMCP MCP server for the complete 360° job-landing pipeline. It exposes pure, testable, zero-LLM tools — the host Claude (guided by the decroche skill) does all the reasoning and rewriting.

Phase 1 (this release): CV anti-rejection core — parse a CV (PDF/DOCX/MD/TXT) into a validated JSON Resume, detect sections, score parse confidence, and expose FR/US market profiles. No magic, no hallucinated metrics, no hidden-text tricks.

Honesty guarantees (hard-coded):

  • keyword_gap marks each gap as addable_honestly (real skill, not yet phrased) or genuinely_missing — never invents credentials.

  • cv.xyz_scaffold signals missing metrics (y_present: false) and asks for real numbers.

  • ats.redflag_scan detects prompt-injection / hidden-text tactics and reports them; never produces them.

Architecture

flowchart LR
    CV["CV\n(PDF/DOCX/MD/TXT)"]
    Offer["Offre\n(texte/URL)"]

    subgraph MCP["decroche-mcp (FastMCP — stdio)"]
        parse["cv.parse\nJSON Resume + sections\n+ confiance"]
        market["market.set\nprofil FR/US/UK…"]
        atssim["ats.parse_sim\nscore parsabilité\n+ casses"]
        matchscore["match.score\n+ keyword_gap"]
        redflag["ats.redflag_scan"]
        brief["ats.screener_brief\n→ kit simulation"]
        render["cv.render\n.docx ATS-safe\n+ PDF stylé"]
        report["ats.score_report\navant/après"]
    end

    Claude["Claude (hôte)\npiloté par skill decroche\nréécriture XYZ honnête\nscreener LLM simulation"]

    CV --> parse
    Offer --> matchscore
    parse --> market
    parse --> atssim
    parse --> matchscore
    parse --> redflag
    parse --> brief
    brief --> Claude
    matchscore --> Claude
    Claude --> render
    render --> atssim
    atssim --> report
    redflag --> report

Install

# Run as MCP server via uvx (recommended — no install needed):
uvx decroche-mcp

# Or install in a project:
uv add decroche-mcp

# From source:
git clone https://github.com/Casius999/decroche-mcp.git
cd decroche-mcp
uv venv && uv sync --extra dev

MCP Client Config

Add to your Claude Desktop / MCP client config:

{
  "mcpServers": {
    "decroche-mcp": {
      "command": "uvx",
      "args": ["decroche-mcp"]
    }
  }
}

Features — Phase 1

Tool

Description

cv_parse

Parse PDF/DOCX/MD/TXT → JSON Resume + sections + confidence score + warnings

market_get

Get active market profile (FR by default)

market_set

Set active market profile (fr, us, uk, ca-en, ca-fr)

market_available

List available market profile ids

Phase 2+ tools (ATS simulation, match scoring, XYZ scaffold, render, apply queue) come in subsequent tranches. See CHANGELOG.md.

Development

uv venv
uv sync --extra dev

# Lint
uv run ruff check .
uv run ruff format --check .

# Tests with coverage
uv run pytest --cov --cov-report=term-missing --cov-fail-under=80

# Run the server (stdio — attach an MCP client)
uv run decroche-mcp

Contributing

Contributions are welcome! Please read CONTRIBUTING.md and our Code of Conduct. Commits follow Conventional Commits and must be signed.

Security

Found a vulnerability? Please follow our Security Policy and report privately — do not open a public issue.

License

Licensed under the MIT license. © 2026 Julien Compain.

Available Tools

75 tools
analytics_channel_roi_toolB

Compute interview and offer rates by source_channel.

Inputs: apps (required). Returns: object

analytics_funnelA

Compute conversion funnel statistics over a list of Applications.

Inputs: apps (required). Returns: Conversion funnel statistics across all tracked applications.

analytics_list_appsB

Return all tracked Applications, optionally filtered by stage.

Inputs: db_path (required), stage. Returns: result

analytics_salary_delta_toolC

Compare an offer amount to benchmark P50 and P75.

Inputs: offer (required), benchmark (required). Returns: object

analytics_story_coverage_toolB

Report which competencies have at least one story and which are gaps.

Inputs: stories (required), target_competencies (required). Returns: object

analytics_trackC

Track (insert or update) an Application in the CRM.

Inputs: app (required), db_path (required). Returns: A job application tracked in the CRM.

analytics_update_stageA

Transition an Application to a new stage.

Appends a history entry with timestamp (and optional note).

Inputs: app_id (required), new_stage (required), db_path (required), note. Returns: A job application tracked in the CRM.

apply_actA

Preview or perform ONE browser step on an employer ATS form.

SAFETY RULES enforced in code (non-negotiable):

  • classify_sensitive_field() is called BEFORE any fill().

  • is_payment_url() blocks the step if the URL is a payment page.

  • is_login_context() blocks the step if a login wall is detected.

  • confirm=False (default) returns a preview with requires_confirm=True. Only confirm=True actually performs the step.

  • NEVER auto-fills passwords, card numbers, CVC/CVV, IBAN, SSN, 2FA.

apply-at-source: use only on employer ATS pages, never on LinkedIn/Indeed.

Inputs: intent (required), params (required), confirm. Returns: Preview or result of a single gated browser step (apply.act). When ``blocked=Tr…

apply_answer_screeningA

Answer a screening question factually from the CV, or flag needs_human.

HONESTY rules (non-negotiable):

  • Work authorization / visa / sponsorship → needs_human=True, answer=None.

  • Salary expectations → needs_human=True, answer=None.

  • Relocation / notice / availability / start date → needs_human=True.

  • "Why this company/role" → needs_human=True.

  • Unknown questions → needs_human=True. Only factual, derivable answers are returned with source="derived_from_cv".

No network calls. Deterministic.

Inputs: question (required), cv_path (required). Returns: Deterministic answer to a screening question. ``source`` is one of: - "derived_…

apply_cover_letterA

Build an honest cover-letter scaffold from a CV file and a job posting.

HONESTY: why_me bullets come ONLY from the candidate's real CV. why_them is a clearly-marked [à compléter: …] placeholder — the host LLM fills it using actual company research. Nothing is invented.

No network calls. Deterministic.

Inputs: cv_path (required), job_json (required), lang. Returns: Honest scaffold for a cover letter. The host LLM personalises prose by filling…

apply_followupA

Draft a polite follow-up message scaffold for an application.

Sending is always human-confirmed (Phase 4b). No network calls.

Inputs: app (required), lang. Returns: result

apply_prefillA

Build a PrefillPlan by mapping resume data to common ATS form fields.

Sensitive fields (password, card, SSN, IBAN, DOB) are NEVER in fields. They are listed in excluded_sensitive for auditability.

Inputs: job (required), json_resume (required), cover_letter. Returns: A plan to pre-fill an ATS application form from a resume.

apply_queue_addA

Add or replace a QueueItem in the batch-apply queue.

If an item with the same job_id already exists it is replaced.

Inputs: item (required), path (required). Returns: result

apply_queue_approveB

Approve a batch of queue items (prepared → approved).

Inputs: job_ids (required), path (required). Returns: result

apply_queue_reviewB

Return all items in the batch-apply queue.

Inputs: path (required). Returns: result

apply_resolve_sourceB

Resolve the best apply URL and channel for a job posting.

Implements the apply-at-source principle: candidates apply at the employer's own ATS, never via LinkedIn/Indeed/Glassdoor aggregator pages.

No network calls are made.

Inputs: job (required). Returns: object

apply_send_approvedA

Submit ATS applications for approved queue items via the user's own Chrome.

SAFETY RULES enforced in code (non-negotiable):

  • ONLY items with status=="approved" are attempted (queue_approve first).

  • is_payment_url() → item stopped, never submitted.

  • is_login_context() → item stopped as "needs_manual_login".

  • classify_sensitive_field() on any prefill field → item skipped.

  • confirm_send=False (default) → dry-run, nothing submitted. Only confirm_send=True actually submits.

Inputs: queue_path (required), confirm_send. Returns: Result of apply.send_approved. ``dry_run=True`` means ``confirm_send=False`` wa…

ats_detect_adversarialA

Detect adversarial tactics embedded in a submitted CV (spec §2).

Scans the CV file for three classes of adversarial content:

  1. prompt_injection (CRITICAL): screener-directed imperatives in the text (EN + FR) designed to manipulate AI screeners.

  2. hidden_text (CRITICAL): near-invisible text via white/near-white colour or sub-4pt font size (PDF and DOCX).

  3. keyword_stuffing (HIGH): anomalous token or line repetition designed to game keyword-match scoring.

This tool ONLY detects adversarial content — it never produces it.

Inputs: cv_path (required). Returns: result

ats_parse_simA

Simulate how an ATS parses the CV file.

Analyses the raw file structure (columns, tables, header/footer contact, file size, text density) and applies the target ATS's quirks rules to compute a parsability score (0–100) and a list of breakages with fixes.

Inputs: path (required), ats_id (required), fmt. Returns: ats_id, fmt, parsability_score, fields_extracted, +2 more

ats_redflag_scanA

Scan a parsed CV for content red flags.

Implements all red-flag checks from data/redflags.yaml: passive voice, duty bullets without metric/strong verb, employment gaps, job-hopping, banned buzzwords, year-only dates, unprofessional email, wrong photo for market, AI-generic phrasing, no quantification.

Inputs: json_resume (required), raw_text (required), market_id, has_photo. Returns: result

ats_score_reportA

Generate a before/after score report.

Combines the parsability score, optional match score, and redflag count into a ScoreReport with screener_readiness tier and optional delta.

Inputs: before (required), after, match, redflag_count. Returns: parsability, match, screener_readiness, redflag_count, +1 more

ats_screener_briefA

Build a screener simulation kit for Claude.

Produces the plain text the machine sees after ATS parsing, a fixed scoring rubric, and deterministic keyword requirements extracted from the offer. Claude then plays the AI screener on this exact text.

Inputs: json_resume (required), offer_text (required), ats_id (required). Returns: machine_view_text, rubric, requirements, ats_id

cv_parseA

Parse a CV file (PDF/DOCX/MD/TXT) into a validated JSON Resume, detected sections, a parse-confidence score, and warnings.

Inputs: path (required). Returns: json_resume, raw_text, sections, parse_confidence, +1 more

cv_renderA

Parse a CV file then render ATS-safe export artifacts.

Produces:

  • ats_docx: ATS-safe single-column DOCX (proven via parse_sim round-trip)

  • styled_html: Self-contained styled HTML (no external assets)

  • pdf: PDF rendered from styled HTML (best-effort; skipped if weasyprint missing)

  • json_resume: Structured JSON Resume (.json)

  • plain_text: Flat plain-text version (.txt)

Returns a Render model with:

  • files: list of RenderFile(kind, path) — all absolute paths

  • ats_safe_proof: dict of {ats_id → parsability_score} from live round-trip

  • warnings: any non-fatal issues (e.g. pdf_skipped)

The ats_safe_proof proves the exported DOCX is machine-readable. All artifacts are written to out_dir (or a system temp dir if omitted).

Inputs: cv_path (required), market_id, out_dir. Returns: Result of cv.render: produced files + ATS round-trip proof + warnings.

cv_verify_claimsA

Flag quantified achievements, leadership claims, named project outcomes, certifications, and awards that should be backed by a verifiable artefact.

Returns only actionable claims (needs_evidence=True). Suggests the artefact type for each (dashboard link, repo URL, credential ID, reference contact, etc.). The host LLM asks the candidate to supply the actual link — never fabricated.

Inputs: cv_path (required). Returns: result

cv_xyz_scaffoldA

Decompose every highlight bullet in a CV into an XYZ scaffold.

For each highlight returns: detected verb, achievement object (X), metric-present flag (Y), method clause (Z), a fill-in template, and a prompt to request a real metric when Y is absent.

NEVER fabricates metrics — only asks the candidate for real data.

Inputs: cv_path (required). Returns: result

interview_company_briefB

Build a structured research scaffold for interview preparation.

PURE — no network, no LLM. Returns a CompanyBrief with [TO_RESEARCH] placeholders and a research checklist to fill before the interview.

Inputs: company (required), notes, jobs. Returns: Research scaffold for company interview preparation.

interview_debriefB

Generate a post-interview debrief Markdown template.

Inputs: role (required), lang. Returns: result

interview_mock_evaluateA

Evaluate a mock interview answer deterministically.

Checks STAR structure, quantification, I/we ratio, duration (~130 wpm), and returns a scored MockEval with feedback.

Inputs: answer_text (required). Returns: Deterministic evaluation of a mock interview answer.

interview_question_bankA

Return interview questions for a role family and kind.

Inputs: role_family (required), kind. Returns: result

interview_story_addA

Add a STAR+E story to the local JSON story bank.

Validates STAR structure (situation, task, action, result must be non-empty). Raises ValueError if validation fails.

Inputs: story (required), store_path (required). Returns: A STAR+E story for interview preparation.

interview_story_suggestB

Return stories that cover a given competency (partial/case-insensitive match).

Inputs: competency (required), store_path (required). Returns: result

interview_thank_youB

Generate a post-interview thank-you message scaffold.

Inputs: interviewer (required), role (required), lang. Returns: result

market_availableA

List available market profile ids.

Inputs: no named parameters. Returns: result

market_getA

Return the currently active market profile.

Inputs: no named parameters. Returns: id, photo, personal_info_ok, hobbies_common, +7 more

market_setA

Set the active market profile (e.g. 'fr', 'us') and return it.

Inputs: market_id (required). Returns: id, photo, personal_info_ok, hobbies_common, +7 more

match_company_intelA

Derive company intelligence from job postings + produce research checklist.

Only asserts facts derivable from the provided postings (open_roles_count, locations, remote_ratio, tech_tags). Everything else (Glassdoor rating, funding, layoff signals, visa sponsorship) is placed in a research_checklist with status "to_research" — never fabricated.

Inputs: company (required), jobs. Returns: Synthesised company intelligence derived from job postings + research checklist.

match_dedupeA

Remove duplicate job postings from a list.

Uses SHA-256 blocking on normalised company|city|title, then within each block merges postings where token_set_ratio(title) ≥ 85 AND dates are within ±14 calendar days (or absent). Keeps the most-complete posting.

Inputs: jobs (required). Returns: result

match_keyword_gapA

Return the top-N offer keywords not covered by the CV, ranked by salience.

Each gap is classified as:

  • "addable_honestly": term or synonym appears anywhere in CV text.

  • "genuinely_missing": no trace in the CV at all.

Inputs: cv_path (required), offer_text (required), n. Returns: result

match_scoreB

Compute a skill-coverage match score between a CV file and a job offer.

Inputs: cv_path (required), offer_text (required). Returns: score_0_100, requirement_coverage, seniority_fit, missing_must

match_success_probabilityB

Estimate application success probability deterministically.

Combines fit_score with recency, competition proxy, optional network proximity, and optional applicant count into a single 0–100 score with per-factor breakdown.

Unknown signals default to neutral and are flagged in notes — never fabricated.

Inputs: job (required), fit_score (required), network_proximity, applicants. Returns: Deterministic estimate of application success probability.

negotiate_benchmark_rangeA

Return a sourced salary benchmark for a role/seniority/region.

Uses the bundled salary_benchmarks.yaml dataset. Returns the closest match with approximate=True and a note if an exact match is absent.

Inputs: role_family (required), seniority (required), region (required). Returns: Sourced salary benchmark for a role/seniority/region combination.

negotiate_competing_offer_scriptC

Generate a competing-offer negotiation script.

Inputs: company (required), competitor (required), competing_amount (required), competing_role (required), +2 more. Returns: result

negotiate_counter_offer_templateB

Build a counter-offer message scaffold (FR or EN register).

Inputs: offer (required), target (required), market_id. Returns: A counter-offer message scaffold.

negotiate_total_compB

Compute annualised total compensation breakdown.

Inputs: base (required), variable_pct, signing, equity_total, +2 more. Returns: Total compensation breakdown.

network_draft_intro_requestA

Draft an introduction request scaffold.

French drafts include a mandatory opt-out line.

Inputs: path (required), context, lang. Returns: A drafted introduction / outreach message scaffold.

network_find_warm_pathA

Find warm introduction paths to a target company.

Operates ONLY on the user-provided connections list. NO LinkedIn traversal, NO external lookups.

Inputs: target_company (required), connections (required). Returns: result

network_score_intro_valueB

Score the intro value of a NetworkPath (0–1).

Inputs: path (required). Returns: result

recruiter_draft_messageB

Draft an outreach message scaffold for a recruiter.

French drafts include a mandatory RGPD opt-out line.

Inputs: recruiter (required), candidate_summary (required), offer_title (required), lang. Returns: A drafted introduction / outreach message scaffold.

recruiter_find_contactA

Find or guess a recruiter's email.

If DROPCONTACT_KEY env var is set, calls Dropcontact API (CNIL-audited). Otherwise generates a pattern guess (status="guessed_unverified"). Status "verified" is ONLY set when Dropcontact confirms.

Inputs: name (required), company, domain. Returns: An email contact record, with honest provenance and verification status.

recruiter_identifyA

Parse a user-pasted text block to identify a recruiter.

Operates ONLY on the provided text — never fetches any URL. Accepts LinkedIn profile text copy-pasted by the user, email signatures, or company "team" page text.

Inputs: text (required), target_company. Returns: A recruiter identified from user-pasted text (profile, signature, team page).

recruiter_qualifyB

Score a recruiter's fit against a target job/company context.

Inputs: recruiter (required), target (required). Returns: Fit score + reasons for a recruiter relative to a target job/company.

recruiter_storeA

Persist a recruiter+contact record to a local JSON store.

Records are always tagged pii=true with retention_max_years=3 (CNIL).

Inputs: recruiter (required), contact (required), out_path (required). Returns: object

source_adzunaB

Fetch jobs from Adzuna (19 countries).

Requires env: ADZUNA_APP_ID, ADZUNA_APP_KEY

Inputs: query (required), country. Returns: Aggregated result from a single source-provider tool call.

source_arbeitnowA

Fetch all jobs from the Arbeitnow public job board (no auth required).

Returns: SourceResult with all normalised job postings.

Inputs: no named parameters. Returns: Aggregated result from a single source-provider tool call.

source_ashbyA

Fetch all listed jobs from an Ashby job board (no auth required).

Inputs: job_board_name (required). Returns: Aggregated result from a single source-provider tool call.

source_careerjetB

Fetch jobs from Careerjet public API (international job aggregator).

Optional env: CAREERJET_AFFID — affiliate ID for higher rate limits. Works without it in some locales (reduced results possible).

Inputs: keywords (required), location, locale. Returns: Aggregated result from a single source-provider tool call.

source_france_travailB

Fetch jobs from France Travail (Pôle Emploi) API v2.

Requires env: FRANCE_TRAVAIL_ID, FRANCE_TRAVAIL_SECRET

Inputs: query (required), location. Returns: Aggregated result from a single source-provider tool call.

source_greenhouseA

Fetch all live jobs from a Greenhouse job board (no auth required).

Inputs: board_token (required). Returns: Aggregated result from a single source-provider tool call.

source_joobleC

Fetch jobs from Jooble international aggregator.

Requires env: JOOBLE_KEY

Inputs: query (required), location. Returns: Aggregated result from a single source-provider tool call.

source_jsearchB

Fetch jobs from JSearch (RapidAPI) — aggregates LinkedIn/Indeed/Glassdoor legally.

Requires env: JSEARCH_RAPIDAPI_KEY

Inputs: query (required). Returns: Aggregated result from a single source-provider tool call.

source_labonneboiteA

Fetch companies likely to hire from La Bonne Boîte (hidden job market).

La Bonne Boîte surfaces companies likely to recruit in a given ROME code and commune, even when they have published no formal job offer. Each result is a JobPosting with a (marché caché) title prefix.

Requires env: FRANCE_TRAVAIL_ID, FRANCE_TRAVAIL_SECRET Scope: api_labonneboitev1 (partner must subscribe in the FT developer portal)

NOTE: scope acceptance requires partner subscription to the La Bonne Boîte product. Live verification of the endpoint and scope is needed before production use — see project report.

Inputs: rome (required), commune (required), distance. Returns: Aggregated result from a single source-provider tool call.

source_leverA

Fetch all published postings from a Lever company page (no auth required).

Inputs: company (required). Returns: Aggregated result from a single source-provider tool call.

source_monitor_diffA

Detect new job postings since a previous snapshot.

Fetches current postings for the provider+key and compares source_ids against the previously saved snapshot at prev_path.

Inputs: provider (required), key (required), prev_path (required). Returns: Result of comparing a current provider fetch against a stored snapshot.

source_monitor_snapshotA

Take a snapshot of current job postings for a keyless provider.

Writes a JSON file containing the list of current source_ids to out_path. Use monitor_diff later to detect new postings.

Inputs: provider (required), key (required), out_path (required). Returns: object

source_recruiteeA

Fetch all open offers from a Recruitee company page (no auth required).

Inputs: company (required). Returns: Aggregated result from a single source-provider tool call.

source_reedB

Fetch UK jobs from Reed.co.uk.

Requires env: REED_KEY

Inputs: query (required). Returns: Aggregated result from a single source-provider tool call.

source_remoteokA

Fetch all remote jobs from RemoteOK API (no auth required; attribution: remoteok.com).

Returns: SourceResult with all normalised job postings.

Inputs: no named parameters. Returns: Aggregated result from a single source-provider tool call.

source_remotiveA

Fetch remote jobs from Remotive API, optionally filtered by search query.

Inputs: search. Returns: Aggregated result from a single source-provider tool call.

source_search_allA

Fetch and aggregate jobs from multiple keyless providers concurrently.

Network failures per provider are silently dropped (not raised).

Returns: Flat list of all collected JobPosting objects across all providers.

Inputs: greenhouse_tokens, lever_companies, ashby_boards, recruitee_companies, +5 more. Returns: result

source_smartrecruitersA

Fetch all public postings from SmartRecruiters Posting API (no auth required).

Inputs: company_id (required). Returns: Aggregated result from a single source-provider tool call.

source_source_search_marketA

Search across the maximum number of job sources for maximum breadth.

Fans out across all boards in the built-in known_boards.yaml registry (greenhouse/lever/ashby/recruitee tokens) plus optionally any keyed providers whose env vars are set (france_travail/adzuna/jsearch).

Results are deduplicated, query-filtered, and sorted by date descending.

Inputs: query (required), region, use_keyed, per_provider_limit. Returns: Aggregated result from a single source-provider tool call.

source_themuseA

Fetch jobs from The Muse.

Optional env: THEMUSE_KEY (works without key but rate-limited).

Inputs: category. Returns: Aggregated result from a single source-provider tool call.

source_usajobsA

Fetch US federal jobs from USAJobs.gov.

Requires env: USAJOBS_KEY, USAJOBS_EMAIL

Inputs: query (required). Returns: Aggregated result from a single source-provider tool call.

source_workableA

Fetch all published jobs from a Workable account (no auth required for public listings).

Inputs: account (required). Returns: Aggregated result from a single source-provider tool call.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    B3.3/5.0

    Scored across 75 tools

    Disambiguation4/5

    Tools are grouped by clear prefixes (analytics_, apply_, ats_, cv_, etc.), each with distinct purposes. Some source_* tools could be confused if an agent doesn't require a specific job board, but descriptions clarify. Overall, ambiguity is minimal given the large set.

    Naming Consistency4/5

    Most tool names follow a consistent snake_case prefix+verb_noun pattern. Minor inconsistencies exist, such as 'source_source_search_market' (double prefix) and a few others, but the overall pattern is predictable.

    Tool Count2/5

    With 75 tools, the count is well above the 25+ threshold. While the broad scope (sourcing, application, CV, interviews, etc.) justifies many tools, the sheer number may overwhelm agents and users, making navigation challenging.

    Completeness4/5

    The tool set covers the job-seeking process comprehensively: sourcing, matching, CV parsing/rendering, application, interviews, negotiation, networking, and analytics. Minor gaps exist (e.g., reference management, interview scheduling), but core workflows are well-supported.

    Maintenance

    ActivityInactive
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers