Skip to main content
Glama
recruitee-mcp

recruitee-mcp

by jnot807

Recruitee MCP

Work your Recruitee / Tellent pipeline from inside Claude. Look up a role, read a candidate and everything already recorded about them, add someone you sourced, and write your interview evaluation — without leaving the conversation.

It runs on your own machine under your own Recruitee API token, so everything it writes is filed in your name, exactly as if you had clicked it yourself.


What it can do

Fourteen tools. Nine read, five write, and every write shows you exactly what it is about to do before it does it.

Reading

Tool

What you get

rt_list_offers

Your roles with their ids, status and candidate counts. Optionally filtered by title.

rt_get_stages

One role's pipeline stages, with a live count in each.

rt_offer_candidates

Everyone on one role — their stage, whether they were disqualified, and any ratings. Genuinely scoped to that role, not the whole company.

rt_get_candidate

A full record: contact details, tags, every role they sit on, and their application answers.

rt_search_candidates

Find one person by name.

rt_source_candidates

Search your whole database, CV text included — see below.

rt_get_rating_scale

The rating scale your account is configured for, so a verdict is never guessed at.

rt_get_evaluations

Every evaluation on a candidate — rating, note, stage, reviewer and date — flattened into one list.

rt_get_notes

Notes already on a candidate, newest first.

Application answers are worth a word: salary expectations and the like are returned per role, because someone who applied to three jobs answered the question three times, and a flat list cannot tell those answers apart.

Writing

Tool

What it does

rt_create_candidate

Creates a person and places them on a role in one step. Takes email, phone, links, tags, a cover-letter block, where they came from, and a file to attach. Lands them in Sourced by default.

rt_submit_evaluation

Writes the thumbs rating and your reasoning onto a candidate for one role — the Evaluation tab of their profile.

rt_set_stage

Moves a candidate to another stage on one of their offers. Refuses a disqualified placement, so it cannot requalify anybody.

rt_attach_file

Attaches a local file to an existing candidate, optionally making it their CV.

rt_add_note

Adds a note, public or private. For context that is not a verdict — a call recap, sourcing rationale, a summary.

How the writes behave

They take names, not ids. "Dana Whitfield", "Regional Sales Manager". If a name matches two people it stops and lists them rather than picking one — filing a verdict on the wrong person is the failure that actually matters here.

Every one previews first. The first call returns exactly what would be written and writes nothing. Only after you approve does anything land. For a new candidate the preview also runs a duplicate check and tells you which details are missing, so you find out before the record exists rather than after.

Evaluations file against the candidate's real current stage, which is what an evaluation means. You can override it deliberately, but you never have to work it out.

Your paragraphs survive. Recruitee's note field takes plain text but its interface renders that text as HTML, so a note written in paragraphs would otherwise arrive as one run-on block. The line breaks are converted on the way across, and the text is escaped first so a stray < in your writing cannot be swallowed or rendered.

Ratings are checked, not rounded. Valid values depend on your configured scale — a 4-point thumbs scale has no "neutral", a 5-point one does. A value the scale does not have is rejected rather than quietly turned into a neighbour.


Related MCP server: zoho-recruit

Sourcing from your own database

rt_source_candidates runs the same search the Candidates screen runs, which is a different thing from rt_search_candidates: that one matches names, this one matches everything, CV text included, with boolean operators.

query: "renewals AND churn"
query: "(SaaS OR B2B) AND \"net revenue retention\" NOT \"vice president\""

That matters because job titles are inconsistent between companies, and what somebody actually did is written in their CV. Searching for the evidence beats searching for the title.

Filters combine: offer, excludeOffer, jobStatus, stage, status, tags, sources. excludeOffer is the one that makes it a sourcing tool rather than a search box — it keeps the people already on a role out of the results when you are topping it up.

Every result carries why it matched — the actual sentences, with the HTML stripped — and every role the person is already on, with the stage and, where they were turned down, the reason. That last part is not decoration: most of an established ATS was rejected once. "Wrong location" two years ago may not apply today; "failed the assessment" still does. Nobody should be presented as a fresh find without it.

Why the filter building looks paranoid

/search/new/candidates silently ignores anything it does not recognise and returns an unfiltered result rather than an error. Four ways to get a plausible, badly wrong answer, all confirmed against a live account:

Mistake

What the API does

Unknown entity name

returns the entire database

nin instead of not_in

returns the entire database

Unknown sort

silently falls back to relevance

Two filter objects for the same entity

the second replaces the first

That last one is the nastiest: a role plus a job status sent as two objects returns everyone with that job status, and nothing anywhere says the role filter was dropped. So every constraint on an entity is merged into a single object, and no caller-supplied key ever reaches the API — names are mapped onto a vocabulary verified against the live API, and anything outside it throws.

A wrong value is safe by contrast: it returns zero, which is obviously wrong to whoever reads it. A zero result also comes back with your real stage names attached, so a mistyped stage is distinguishable from an empty one.

node sourcing-test.js checks all of it, including that the client refuses each of the four mistakes above.

Setup

Five minutes, once. You need Node 18 or newer (node -v to check) and Claude Code or the Claude desktop app.

1. Get the code and install

git clone https://github.com/jnot807/recruitee-mcp.git
cd recruitee-mcp
npm install

Every command from here runs from inside that folder.

2. Create your own API token

In Recruitee: Settings → Apps and plugins → API tokens, stay on the Personal API tokens tab, and click + Add token. It asks for your password, then shows the value once.

While you are on that screen, note your company from the Current company details panel at the top. Either the numeric ID or the subdomain works.

This has to be your token, not a shared one. A Recruitee token acts as the person who created it, so an evaluation written with your token shows up as yours — which is the point. Never paste it into a chat, an email or a ticket.

3. Store it

npm run set-token -- <paste-your-token-here> <your-company>

Rotating a token later is just npm run set-token -- <new-token> — the company is remembered.

It is written to session/token.json, readable only by you, and gitignored. RECRUITEE_API_TOKEN in the environment overrides the file if you would rather keep it in a password manager.

4. Prove it works

npm run check

You want authenticated: true and a few of your roles.

5. Connect it to Claude

Run this from inside this folder, then restart Claude:

claude mcp add recruitee -- node "$PWD/server.js"

Using the Claude desktop app instead? Open Settings → Developer → Edit Config and add this, with your real absolute path (pwd prints it):

{
  "mcpServers": {
    "recruitee": {
      "command": "node",
      "args": ["/absolute/path/to/recruitee-mcp/server.js"]
    }
  }
}

Then ask Claude: "list the open roles in Recruitee".


What it looks like in use

You: Who's in the pipeline for Regional Sales Manager?

You: Pull up Dana Whitfield — what did she put for salary, and what evaluations are already on her?

You: Write an evaluation for her on that role. A yes: strong on renewals and expansion, ran a team of nine, no PLG experience.

Claude shows you the rating, the note, the role and the stage, and writes nothing.

You: Yes, send it.


What it deliberately cannot do

A Recruitee API token carries exactly the permissions of the person who generated it — the documentation is explicit that it can "perform the same actions as in the web or mobile application in the name of that user". There is no read-only token to issue.

So the restraint lives in this code instead. Disqualifying, requalifying, deleting, concealing and anonymising are all real, documented endpoints that this server does not implement. Not hidden behind a flag, not commented out — absent, so no instruction, prompt or bug can reach them. Rejecting a candidate stays a decision you make in the UI.

Stage moves are the one thing that is allowed. rt_set_stage advances a candidate along one offer's pipeline, because that is bookkeeping rather than a judgement, and a pipeline you cannot advance from here drifts out of step with wherever else you track it. The line is drawn at disqualification and it is enforced, not just documented: the move refuses a placement that has already been disqualified, since changing its stage would requalify the person — reversing somebody's rejection as a side effect of a bookkeeping call.

npm run smoke asserts these properties on every run: that no destructive tool is exposed, that the stage mover refuses a disqualified placement and is scoped to one offer, and that every write advertises its confirm gate. That last check derives writes from the tool schemas rather than from a list of name patterns — the earlier version silently stopped covering new tools, and waved rt_set_stage through without testing it at all.


Things worth knowing

New candidates land in "Sourced". Recruitee's create endpoint always drops people in "Applied", which would file everyone you sourced among the genuine applicants, so they are moved immediately after creation and you are told if that did not take. Pass stage to override it — "Applied" for somebody who genuinely applied, or any later stage for someone already in process. To move them afterwards, use rt_set_stage.

Setting a CV replaces the one already there. Recruitee's set_as_cv does not add a CV, it swaps the slot and demotes the previous file to a plain attachment. rt_attach_file therefore refuses to set a CV on a candidate who already has one unless you pass replaceCv — a CV on file is somebody's decision, and the only trace of overwriting it is an extra row in the attachments list.

Evaluations are filed under you. They appear as "You evaluated", indistinguishable from one clicked by hand. Never write one for a conversation you did not have or have not read, and if the judgement came from a colleague, say so in the note.

Attribution is unreliable coming back. Anything written through any API token is attributed to that token's owner, so the reviewer on an evaluation someone synced may be whoever synced it rather than whoever ran the interview. The note usually names the real one.

Questionnaire scorecards are not supported. Only the plain rating card. The API documents the per-question answers in every response but never in a request body, so the write shape would have to be observed from a real submission first. It may not matter for your account either: if /results/scorecards comes back empty for people who have been through interview stages, plain rating cards are what is in use and there is nothing missing. Worth checking before anyone invests in the questionnaire path.


Where this works

This is a local stdio MCP server — Claude launches it as a process on your machine, and your token never leaves it.

  • Claude Code (terminal, desktop app, IDE extensions) ✅

  • Claude desktop app ✅

  • claude.ai in a browser ❌ — that connects only to remote MCP servers reachable over HTTPS, which would mean hosting this and storing everyone's Recruitee tokens on that host.


Configuration

Variable

Purpose

RECRUITEE_API_TOKEN

Use a token from the environment instead of the stored one

RECRUITEE_COMPANY_ID

Use a company from the environment instead of the stored one

Troubleshooting

What you see

What to do

"No Recruitee API token"

Step 3 did not run, or ran in a different folder. cd back here and try npm run check.

"authenticated": false

The token was mistyped or revoked. Generate a new one and redo step 3.

Claude does not see the tools

Restart Claude properly — quit, don't just close the window. Check step 5 ran from inside this folder.

"That name matches two candidates"

Working as intended. Open the person in Recruitee and give Claude the number from the end of the URL.

Anything else

npm run smoke, and send whatever it prints.

Development

npm run smoke     # self-check: tool list, no destructive tools, confirm gates, one live read
npm run sourcing  # 20 checks on the search filters, including the four silent-failure modes
npm run check     # prove the token
npm start         # run the server directly (it speaks JSON-RPC on stdin/stdout)

Two implementation notes, both found by probing rather than from the docs:

  • File upload is undocumented. The reference describes a JSON body carrying a server-side path it never explains how to obtain. A plain multipart POST works, with the file part named attachment[file] — a bare file returns 500, and passing the candidate id as a query parameter creates an attachment linked to nobody. Promoting a file to the CV slot replaces it with a new id and a generated filename, so uploads are verified against the candidate's CV URL rather than the id that was just uploaded.

  • /search/new/candidates ignores its own query parameter and returns every record in the company, so name search goes through /candidates?query= instead. Pipeline stages come from /offers/{id}/placements, grouped by stage, not from /offers/{id}/pipeline_templates, which lists templates available to a role without their stages.

Available Tools

14 tools
rt_add_noteA

Add a note to a candidate. Use for context that is not a verdict — an assessment summary, sourcing rationale, or a call recap. Accepts a name or an id. Two-call gate.

Visibility "public" means anyone with access to the candidate can read it, which is usually what you want for a shared hiring record.

Inputs: body (required), candidate (required), confirm, visibility.

rt_attach_fileA

Attach a local file to an existing candidate — a CV, or a summary document. Set asCv to make it their CV rather than a plain attachment; a record with no CV shows "No CV or resume yet" in Tellent.

Setting a file as the CV REPLACES any CV already on file, demoting theirs to a plain attachment — so that is refused unless you pass replaceCv.

Only ever attach a file the user named. Never go looking for one, and never substitute a different file if the named one is missing. Two-call gate: preview, then confirm.

The upload is verified against the candidate afterwards; if it comes back unlinked, say so rather than reporting it as attached.

Inputs: filePath (required), candidate (required), asCv, confirm, +1 more.

rt_create_candidateA

Create a candidate in Recruitee/Tellent AND place them on a role, in one call. Works entirely from what you are told here — nothing has to exist anywhere else first.

WHAT YOU NEED. Required: the role (its title is enough, e.g. "VP of Customer Success") and the person's full name. Strongly recommended, because a record without them is close to useless to a recruiter: email, and either a LinkedIn URL or a CV file. Optional: phone, tags, a coverLetter block for their background and why they are worth a look, and where they came from. If the user has not given you the recommended fields, ASK for them before creating — the preview call lists exactly what is missing.

TWO-CALL GATE, ALWAYS. The first call (confirm omitted or false) returns what would be created, what is missing, and any existing candidates with the same name — and writes nothing. Show that to the user. Only call again with confirm true once they have approved THIS person for THIS role. One person per confirmation.

THIS CREATES A REAL RECORD FOR A REAL PERSON, visible to the whole hiring team and counted in reporting. The preview always runs a duplicate check; if it finds a match, resolve that with the user rather than creating a second record that splits their history.

DO NOT INVENT DETAILS. An empty field beats a guessed one.

STAGE. Defaults to "Sourced", which is right for anyone we went looking for. Pass stage: "Applied" only for someone who genuinely applied. Recruitee's create endpoint has no stage parameter, so this moves them immediately after creation and tells you if that did not take.

Inputs: name (required), offer (required), asCv, tags, +8 more.

rt_get_candidateA

Full record for one candidate: contact details, tags, every placement with its stage, and their application answers. Salary answers live in groupedOpenQuestionAnswers and are PER OFFER — read the offer id on each, because someone who applied to several roles carries several answers and the flat list cannot tell them apart.

Inputs: candidateId (required).

rt_get_evaluationsA

Every evaluation on a candidate: rating, note, stage, reviewer and date, flattened into one list, newest last.

READ THIS BEFORE WRITING ONE, so you build on what other interviewers recorded instead of duplicating it.

IF YOU ARE COPYING THESE SOMEWHERE ELSE, carry the evaluation id across so a second sync updates the same row instead of duplicating it.

ATTRIBUTION IS UNRELIABLE COMING BACK. Recruitee files everything written through an API token under the token owner, so reviewer may be the token holder rather than whoever ran the interview. The note usually names the real one. Do not present the reviewer as fact when the note disagrees with it.

Inputs: candidateId (required), offerId.

rt_get_notesA

Notes already on a candidate, newest first.

Inputs: candidateId (required), limit.

rt_get_rating_scaleA

The rating scale this company is configured for. Read it BEFORE writing an evaluation: the valid rating values differ per tenant (a 4-point thumbs scale has no "neutral", a 5-point one does), and guessing files the wrong verdict on a real person.

Inputs: no named parameters.

rt_get_stagesA

The pipeline stages of one offer, with their ids. Needed before writing an evaluation, because an evaluation is filed against a stage.

Inputs: offerId (required).

rt_list_offersA

List the roles (offers) in Recruitee/Tellent with their ids, status and candidate counts. Start here — every other tool is keyed on an offer id.

Inputs: limit, query.

rt_offer_candidatesA

Everyone on ONE offer, with their stage, disqualification state and ratings — genuinely scoped to that offer rather than the whole company database.

Inputs: offerId (required), limit.

rt_search_candidatesB

Find candidates by name or keyword across the company.

Inputs: query (required), limit.

rt_set_stageA

Move an existing candidate into another pipeline stage on ONE of their roles — to advance them, or to mirror a move already made wherever your pipeline is tracked. Scoped to an offer because a candidate can sit on several pipelines at once.

Call rt_get_stages first if you do not know the offer's stage names; an unknown name is refused and the real ones are listed back. If you are mirroring a move from another system, do not assume the two name stages the same way — ask which stage is meant rather than picking the nearest word.

THIS IS NOT A REJECTION TOOL. It cannot disqualify anyone, and it refuses to move a candidate who has already been disqualified, because that would requalify them. Only move somebody because a person moved them or told you to — never because a meeting was booked, a score looked good, or the pipeline seemed stale.

Two-call gate: preview, then confirm. The move is verified by re-reading the candidate afterwards; if it did not land, that is reported rather than claimed as done.

Inputs: offer (required), stage (required), candidate (required), confirm.

rt_source_candidatesA

SEARCH THE WHOLE CANDIDATE DATABASE — every person who ever applied or was added, not just one role. This is the sourcing tool: use it to find people already in the ATS before going out to LinkedIn, because someone who applied to a similar role last year is the cheapest good candidate there is.

IT SEARCHES CV TEXT, not just names and titles. query takes boolean operators exactly as the Recruitee search bar does: "renewals AND churn", "(SaaS OR B2B) AND expansion". Every result carries whyMatched — the actual sentences that matched — so a coincidental hit can be dismissed without opening the profile.

SEARCH FOR THE EVIDENCE, NOT THE JOB TITLE. Titles are inconsistent between companies; what someone DID is written in their CV. Prefer "quota AND renewals" over "Account Manager", and run several narrow searches rather than one broad one.

MOST OF THIS DATABASE WAS REJECTED ONCE. Every result lists each role the person sits on with its stage and, where they were turned down, the reason. Read it before you suggest anybody: "wrong location" two years ago may not apply now, "failed the assessment" still does. Never present someone as a fresh find without saying they have been through the process before, and for which role.

excludeOffer keeps people already on a role out of the results, which is what you want when topping one up. Combine filters freely — they AND together.

Inputs: page, tags, limit, offer, +7 more.

rt_submit_evaluationA

Write an evaluation (a thumbs rating plus a note) onto a candidate for one role — the "Evaluation" tab of their profile.

WORKS BY NAME. Give the person's name and the role title and the text; ids are not needed. If a name matches more than one candidate it refuses and lists them rather than guessing — filing a verdict on the wrong person is the failure that matters here.

THE STAGE IS AUTOMATIC. It files against wherever the candidate actually sits on that role, which is what an evaluation means. Only pass stage to override that deliberately.

TWO-CALL GATE, ALWAYS. First call previews, second call with confirm true writes.

IT IS FILED IN THE TOKEN OWNER'S NAME. Confirmed in practice: it appears in Tellent as "You evaluated", indistinguishable from one clicked by hand. So never write one as though it were another interviewer's verdict, and never write one for a conversation the token owner did not have or has not read. If the judgement came from someone else, say so in the note.

CALL rt_get_rating_scale IF UNSURE. Valid ratings depend on the configured scale. This company is on a 4-point thumbs scale: strong_no, no, yes, strong_yes ("yes" scores 4/5, 75%). A value the scale does not have is rejected rather than rounded.

PUT THE REASONING IN ratingNote. A bare rating nobody can audit is not worth writing. Where the verdict came from a screen or interview, say which.

Structured questionnaire scorecards (per-question answers) are NOT supported — only the rating card. If a role uses a questionnaire template, say so rather than flattening a multi-question scorecard into a single rating.

Inputs: offer (required), rating (required), candidate (required), stage, +2 more.

Tool Schema Changelog

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

Loading tool schema changelog…

    TDQS

    A3.9/5.0

    Scored across 14 tools

    Disambiguation3/5

    rt_search_candidates and rt_source_candidates both search the candidate database with substantial overlap; source is a superset with CV-text boolean search and rejection history, which can cause confusion about which to use. Other read and write tools are clearly separated by resource and action, making the pair the main source of ambiguity.

    Naming Consistency4/5

    All tools share the rt_ prefix and most follow a consistent verb_noun pattern (list_offers, get_stages, create_candidate, submit_evaluation). Two deviations exist: rt_offer_candidates is a noun-noun phrase rather than an action, and rt_source_candidates uses the domain-specific verb 'source' instead of a generic search verb, but the overall pattern remains predictable.

    Tool Count5/5

    14 tools is well-scoped for an ATS server: candidate lifecycle, pipeline stages, evaluations, notes, attachments, offers, search, and rating scale are each covered. No tool feels redundant, and none is missing enough to make the count feel thin or bloated.

    Completeness4/5

    Core recruiting workflows are covered: sourcing, creating candidates, moving them through stages, filing evaluations, adding notes, attaching files, and reading offer/candidate data. Obvious gaps are the lack of a candidate profile update tool and no explicit rejection/disqualification operation, but the design deliberately scopes set_stage away from rejection, and the rest of the lifecycle is well supported.

    Related MCP Connectors

    Related MCP Servers

    • F
      license
      B
      quality
      Not graded
      maintenance
      Enables Claude to manage Zoho Recruit ATS operations including candidates, jobs, interviews, analytics, email, and AI-assist through natural language.
      20
      -
    • F
      license
      A
      quality
      D
      maintenance
      Connects Claude to the Pandapé recruitment API, enabling natural language management of vacancies, structured candidate review against custom criteria, pipeline stage updates, and CV downloads.
      14
      -
    • F
      license
      Not graded
      quality
      C
      maintenance
      Enables Claude to securely search, read, and update your ATS data — candidates, mandates, pipelines, tasks, and interviews — through natural language, with configurable write permissions and confirmation for actions like scheduling and emailing.
      -