MCP tool reference

What an AI assistant connected to First can actually do — every tool it can call, and what each one changes.

The First MCP server: connect an AI assistant to First to research a talent market, run the full role lifecycle (create, configure, edit the application process, publish, pause, close, archive), source candidates from the organisation's own candidate database and from the public web, and report on hiring.

Point your assistant at the First MCP server and sign in. Assistants that support it will sign you in with your First account; for tools that need a static credential instead, generate a personal API key in First under Profile, then Personal access tokens.

{
  "mcpServers": {
    "first": {
      "url": "https://api.firststage.co/mcp",
      "headers": { "Authorization": "Bearer fst_..." }
    }
  }
}

Every tool below is authorised as you: it can only reach the organisations your account belongs to, roles you are allowed to see are the only ones it can read, and changing a role needs the same admin rights it would in the app. Tools marked CHANGES DATA alter or withdraw something people can see — a good assistant will ask you before running one.

How your assistant is told to use these tools▼

The server hands every assistant these instructions when it connects. They are worth reading: they are what makes an assistant source against agreed criteria rather than guessing, and they explain the sourcing workspace that ties a run of searches together.

First is a recruitment platform. These tools research the talent market, manage roles, find candidates in the organisation's own database and on the public web, add them to roles, and report on hiring.

Every tool but one takes an organisationId. Never ask the user for it — call list_organisations and use what comes back. Most people belong to exactly one organisation, so use it without checking; ask which they mean only when there are several.

The hiring workflow, when someone is hiring rather than asking a one-off question. Offer it, do not force it — and never walk someone through it who only wanted a number or a database search:

1. RESEARCH the role and market — research_talent_market for demand, pay and who else is hiring. Then suggest a role, with their go-ahead before you create anything.
2. CAPTURE the intake — ask for the hiring manager's brief in whatever form it exists: notes, an interview transcript, a job description. There is no form to fill in, so take it as it comes.
3. CREATE the role — create_role, then generate_suggested_criteria from the brief and update_role_process with them. These criteria are what every candidate added is assessed against.
4. SOURCE a long list — see below — and show it to the user before adding anyone.
5. ADD and PUBLISH — add the people the user chose, then get_role to check readiness and publish_role. Added candidates are assessed against the role's criteria once it is published.

Sourcing tools:

- search_existing_candidates — people already in the organisation's database: they applied before, so First holds their CV and history. Fast and free.
- search_web_for_candidates — people on the public web who have not applied, with a Google X-Ray query. Thin results, and nothing is saved.
- save_web_candidates — saves the web results worth keeping to a saved list, looking each person up (paid). Then add_saved_candidates_to_role adds them; get_saved_candidates reads a list and clear_saved_candidates empties it.
- add_candidate_to_role — adds existing candidates by candidateId. On organisations on the newer web search, it also takes people on the web by their LinkedIn URL, skipping the save.

You do the judging; there is no separate matching step. Read what comes back, keep the people who fit the brief, and let the role's assessment score them once they are added.

To source well, first time:

- Check existing candidates first, and say so: "I'll check your existing candidates first, then look on the web." Re-engaging someone who applied before beats cold-sourcing a stranger. Go to the web when the database is thin, or straight away when the user asks for new people, the web or LinkedIn. Never make the user pick before you start.
- Before searching the web, work out the equivalent job titles (a principal engineer is also a staff, lead or senior staff engineer) and the companies that employ people like this — competitors, companies the user names, companies in the same sector and place. OR the titles together in one query, and run a search per company: a wider net brings back a longer, better list.
- Put the place, and any skill that must appear on a profile, into the query text.
- Show the user a short reason for each person you suggest, then add the ones they choose. Adding someone already on the role finds their application rather than making another.

Searching the database near a place: latitude, longitude and radiusKm need coordinates, and country needs an ISO code, so resolve the name first with geocode_place or geocode_country. If either returns found: false, search without a place rather than guessing coordinates.

Before creating a role, gather what it needs: a title, a job description and at least one location are required to publish. Employment type, workplace type and compensation are not required but make the listing usable. Ask the user for anything missing rather than guessing.
1 toolOrganisations

The organisations a credential may act on — where the organisationId every other tool needs comes from.

list_organisations

List organisations

The organisations you can act on, with their ids. Every other tool needs an organisationId — call this first to find it rather than asking the user for one. Returns exactly one organisation for most people: use it without asking.

This tool takes no arguments.

16 toolsRoles

Role lifecycle: create, configure, edit the application process, publish, pause, close, archive.

list_roles

List roles

List an organisation's roles, optionally filtered by status. Only returns roles the token owner is allowed to see. Use status 'imported' to find ATS-synced roles awaiting adoption.

FieldTypeDescription
organisationId*string
statusesarray of one of: imported, draft, open, paused, closed, archived
get_role

Get a role

A role's current state: its details, criteria and questions, and readiness — which required details are still missing before it can be published.

FieldTypeDescription
organisationId*string
roleId*string
create_role

Create a role

Create a new draft role. The token owner becomes the hiring manager. Location and category names that don't exist in the organisation are created automatically.

FieldTypeDescription
organisationId*string
name*stringThe role title
internalNamestring
shortDescriptionstring
descriptionstring
workplaceTypeone of: on-site, hybrid, remote, remote-first, flexible
employmentTypeone of: full-time, part-time, contractor, internship, volunteer
roleWebsiteUrlone of 2 shapes
compensationobject
locationNamesarray of string
categoryNamesarray of string
templateIdsarray of stringRole template ids to seed criteria and questions from
duplicate_role

Duplicate a role

Duplicate an existing role into a new draft, copying its details, settings, criteria and questions. Returns the new role's id.

FieldTypeDescription
organisationId*string
roleId*stringThe role to copy
import_role

Adopt an imported role

Adopt a role synced from the ATS: moves it from 'imported' to a draft the team can configure and publish. Use list_roles with status 'imported' to find adoptable roles.

FieldTypeDescription
organisationId*string
roleId*string
update_role_details

Update a role's details

Update a role's details. Provide only the fields to change; everything else keeps its current value. Location and category names replace the existing sets. The response lists any fields locked to a connected ATS which kept their synced values.

FieldTypeDescription
organisationId*string
roleId*string
namestring
internalNamestring
shortDescriptionstring
descriptionstring
workplaceTypeone of: on-site, hybrid, remote, remote-first, flexible
employmentTypeone of: full-time, part-time, contractor, internship, volunteer
roleWebsiteUrlone of 2 shapes
compensationobject
locationNamesarray of string
categoryNamesarray of string
publish_role

Publish a role

Publish a role: opens it for applications, putting it live on the job board and apply page and approving any criteria still awaiting review. Also resumes a paused role and reopens a closed one, restarting progress towards any pause goal. Fails with the missing fields when the role's details are incomplete.

FieldTypeDescription
organisationId*string
roleId*string
pause_role

Pause a role

Pause an open role: candidates can still apply, but new applications wait to be assessed until the role is resumed with publish_role. Fails when the role is not open.

FieldTypeDescription
organisationId*string
roleId*string
close_roleCHANGES DATA

Close a role

Close an open or paused role: candidates can no longer apply. Reopen it with publish_role. Fails when the role is not open or paused.

FieldTypeDescription
organisationId*string
roleId*string
archive_roleCHANGES DATA

Archive a role

Archive a role: hides it from active lists and stops new applications. Archived roles can be restored in First. Fails when the role is already archived.

FieldTypeDescription
organisationId*string
roleId*string
update_role_processCHANGES DATA

Update a role's application process

Apply changes to a role's application process: its criteria and questions. Each change is one item of the shared process-change contract, applied in order. Criteria changed on a live role may await review and are approved when the role is next published. Use get_role first to see the current criteria and questions.

FieldTypeDescription
organisationId*string
roleId*string
changes*array of one of 19 shapesThe process changes to apply, in order
remove_role_process_componentCHANGES DATA

Remove a criterion or question from a role

Remove a criterion or question from a role's application process by process component type and subtype. For stage criteria (type 'assess-stage'), pass the specific criterionId — otherwise every stage criterion on the role would be removed.

FieldTypeDescription
organisationId*string
roleId*string
type*one of: collect-basic-details, check-requirements, check-skills-and-experience, test-knowledge-and-behaviour, ask-video-question, ask-for-document, survey, assess-stage, legacy-criterion, legacy-question
itemType*one of 8 shapes
questionIdstring
criterionIdstringRequired for stage criteria (type 'assess-stage'): without it, every stage criterion on the role would be removed
get_stage_topicsREAD ONLY

Get a stage's interview topics

A role's pipeline stages (id, name, kind) and, for the stage given, its interview topics in order: each with its guidance for the interviewer, its questions and follow-ups, and the criteria scored from the answers (id, title, what good looks like, priority, scale). Call it without pipelineStageId to find the stage ids; topics go on interview stages. Read a stage's topics before changing them, so existing topics are changed rather than duplicated.

FieldTypeDescription
organisationId*string
roleId*string
pipelineStageIdstring
save_stage_topicsCHANGES DATA

Save interview topics on a stage

Add or change interview topics on a role's interview stage, each saved with its questions and criteria in one go - typically set up from a client's scorecard or interview template. A topic with a topicId from get_stage_topics is changed; one without is added at the end of its stage (use reorder_stage_topics to move it). Criteria left out of a changed topic are kept, not removed. A section with questions but nothing scored, such as an introduction, is a topic with no criteria. Confirm the topics with the user first.

FieldTypeDescription
organisationId*string
roleId*string
topics*array of object
reorder_stage_topics

Put a stage's interview topics in order

Put a stage's interview topics in the order the interviewer covers them - for example an introduction first, or the order a scorecard lists them in. Pass every topicId from get_stage_topics in the new order; a topic left out keeps its place after the ones given.

FieldTypeDescription
organisationId*string
roleId*string
pipelineStageId*string
topicIds*array of string
remove_stage_topicsCHANGES DATA

Remove interview topics from a stage

Remove interview topics from a role, by topicId from get_stage_topics. Each topic goes with its questions and the criteria scored under it, and any scores already given for them - the same as removing the topic in First. Always confirm with the user, naming the topics, before calling this.

FieldTypeDescription
organisationId*string
roleId*string
topicIds*array of string
2 toolsApplications

Search the organisation's applications.

search_applications

Search applications

Search the organisation's applications by name or email across every role. Only returns applications for a role the token owner is allowed to see. Use this to find a specific person by name or email address. Omit `query` to list rather than search: `list: "hired"` with no query lists hires, which is how you answer "who have we hired recently" — there is no search term that returns them all. Each row carries `hiredAt`, `shortlistedAt` and `rejectedAt`, so filter to a date window on those rather than on `createdAt`, which is when the application arrived rather than when the outcome happened. **Read `truncated` before quoting any total.** When it is true this is a partial answer — `found` is a lower bound and paging to the end will not reach the rest, so say the count is at least that rather than stating it as the number. Only when `truncated` is false is `found` the whole answer.

FieldTypeDescription
organisationId*string
querystringName or email to search for. Omit to list the whole of `list` instead — there is no term that returns every hire.
fieldone of: email, fullNameRestrict matching to one field; omit to match either
listone of: inbox, shortlisted, rejected, hired, all
pageinteger
get_applicationREAD ONLY

Get an application

One application's record: which stage it has reached, when it was hired, shortlisted or rejected, where the candidate came from, and its assessment score with the criteria version behind it. Search finds the person; this says what happened to them. Returns not found for a role you are not allowed to see. Their work and education history comes back too, taken from the CV already held against the application — use this rather than the web-sourcing tools for anyone who has applied before: it costs nothing and it is their own CV, not a third-party guess at it. Pass includeProfile: false to leave it out.

FieldTypeDescription
organisationId*string
applicationId*stringThe application's id, as returned by search_applications.
includeProfilebooleanSet false to leave out the candidate's CV history. Included by default.
11 toolsSourcing

Find candidates in the organisation's own database and on the public web, and add them to a role as applications.

search_web_for_candidatesREAD ONLY

Search the web for candidates

Search the public web for new candidates — people not in the organisation's database — with a Google X-Ray query (e.g. site:linkedin.com/in with OR-grouped job titles, target employers and must-have skills). It runs on Google, or on Exa for organisations on the newer web search; the query and the results are the same shape either way: a name, a title, a link and a snippet. It saves nothing: pass the ones worth keeping to save_web_candidates. There is no location parameter — put the location into the query text. Cast a wide net: OR together every equivalent job title, and run a search per target employer.

FieldTypeDescription
organisationId*string
query*stringA Google X-Ray query, e.g. site:linkedin.com/in with OR-grouped job titles and must-have skills. There is no location parameter — put the location scope into the query text.
numintegerHow many results to return, 1-10. Defaults to 3.
startintegerThe 1-based index of the first result, for paging. Defaults to 1; step by `num` to page.
dateRestrictstringGoogle only; ignored where the organisation's web search runs over public profile pages.
save_web_candidates

Save web search results

Save up to 10 search_web_for_candidates results to a saved list, ready for add_saved_candidates_to_role. Each is looked up as it is saved: through a paid enrichment where the search ran on Google, from each person's own page where it ran on Exa. Pass the `reference` from an earlier save to keep everything in one list, or omit it to start one and reuse the reference that comes back.

FieldTypeDescription
organisationId*string
contacts*array of objectThe profiles worth keeping, built from a prior web search. Send only the ones the caller has decided on: where the search was Google's each is enriched and billed on the way in; where it ran over public profile pages each is built from its page and nothing is billed.
referencestringThe saved list to save into. Pass the `reference` from an earlier match or save to keep everything together; omit to start a new one, whose reference comes back in the response.
add_candidate_to_role

Add candidates to a role

Add people to a role as applications, wherever they were found. Pass existing candidates as `candidateIds`, from search_existing_candidates, list_candidates or get_candidate: each is added as a copy of their newest application. Pass people on the web as `urls`, their LinkedIn profile URLs — from search_web_for_candidates, or the address of a profile page you are looking at — on organisations on the newer web search; elsewhere, save them with save_web_candidates and add them with add_saved_candidates_to_role. First looks each one up itself, builds a CV from their page and looks up their email and phone (paid lookups) so they can be contacted; nothing else about them is taken from you. Each becomes an application like any other: it is assessed against the role's criteria once the role is published, and synced to the organisation's ATS where that is on. Adding someone already on the role finds their application rather than making another. The response lists who was added, with their application ids, and who could not be.

FieldTypeDescription
organisationId*string
roleId*stringThe role to add them to.
candidateIdsarray of stringPeople already in the organisation's candidate database, by the `candidateId` search_existing_candidates, list_candidates or get_candidate returned. Each is added as a copy of their newest application.
urlsarray of stringPeople on the public web, by their LinkedIn profile URL: a `url` the web search returned, or the address of a profile page you are looking at. Only on organisations on the newer web search; elsewhere, save them to a saved list and add them from there. First looks each person up itself; nothing else about them is taken from the caller.
get_saved_candidates

List a saved list

List every candidate saved under a `reference` — before deciding who to add to a role, or after losing track of what was saved. A role's id lists the role's own list, which First's app sources into. An empty or unknown reference returns an empty list.

FieldTypeDescription
organisationId*string
reference*stringThe saved list to show.
clear_saved_candidatesCHANGES DATA

Empty a saved list

Remove every candidate saved under a `reference`. Starting a fresh reference achieves the same without discarding anything, so prefer that unless the user asks to start this one over.

FieldTypeDescription
organisationId*string
reference*stringThe saved list to empty.
add_saved_candidates_to_role

Add saved candidates to a role

Add people from a saved list to a role as applications, where they are assessed against its criteria. The contactIds are the ids save_web_candidates or get_saved_candidates returned; pass the list's `reference` too, or omit it for the role's own list. The response lists the ids that were added; ids that no longer resolve to a saved candidate are simply absent.

FieldTypeDescription
organisationId*string
roleId*string
contactIds*array of stringThe ids of the saved candidates to add to the role as applications — the `id` values a save or a saved list returned.
referencestringThe saved list the candidates are on. Pass the `reference` a save returned; omit for the role's own list, which First's app sources into.
list_candidates

List the candidate pool

List the organisation's candidate pool, most recently applied first: one row per person, however many times they applied. Narrow it by part of a name or email (`text`; a whole address is looked up directly), `discipline`, `rightToWork` or `appliedSince`. The first page says how many match out of the whole pool; pass `nextCursor` back as `cursor` for the next page. For people who fit a role, use search_existing_candidates instead.

FieldTypeDescription
organisationId*string
textstringPart of a name or email address. A whole address is looked up directly.
disciplinestringOnly people in this discipline: an id from the `disciplines` the first page of list_candidates returns, or 'none' for people without one.
rightToWorkstringOnly people who said they have the right to work in this country, as an ISO 3166-1 alpha-2 code, e.g. 'GB'.
roleIdstringOnly people who applied to this role.
stageone of: new, uploading, applied, sourced, invited, in progress, complete, stalled, duplicate, archived, withdrawn, shortlisted, pipeline, hired, rejectedOnly people with an application at this stage, e.g. 'shortlisted' or 'hired'.
countrystringOnly people who applied from this country, as an ISO 3166-1 alpha-2 code (geocode_country turns a name into one).
latitudenumberWith longitude and radiusKm: only people who applied from near here.
longitudenumber
radiusKmnumberHow far from latitude/longitude, in km (geocode_place gives the point).
appliedSincestringOnly people who last applied on or after this date, YYYY-MM-DD.
cursorstringThe nextCursor from the previous page, to continue from.
search_existing_candidates

Search existing candidates

Find the people in the organisation's own candidate database most like a description, a job title or another candidate, best match first, up to 50. Pass exactly one of `text` (e.g. 'forklift operator', 'React and TypeScript') or `likeCandidateId`. Similarity only: judge the list yourself, then add the people worth it with add_candidate_to_role, where the role's assessment scores them.

FieldTypeDescription
organisationId*string
textstringWhat to look for: skills, a job title or a short description of the person, e.g. 'forklift operator' or 'React and TypeScript'.
likeCandidateIdstringFind people like this candidate instead of matching text.
disciplinestringOnly people in this discipline: an id from the `disciplines` the first page of list_candidates returns, or 'none' for people without one.
rightToWorkstringOnly people who said they have the right to work in this country, as an ISO 3166-1 alpha-2 code, e.g. 'GB'.
roleIdstringOnly people who applied to this role.
stageone of: new, uploading, applied, sourced, invited, in progress, complete, stalled, duplicate, archived, withdrawn, shortlisted, pipeline, hired, rejectedOnly people with an application at this stage, e.g. 'shortlisted' or 'hired'.
countrystringOnly people who applied from this country, as an ISO 3166-1 alpha-2 code (geocode_country turns a name into one).
latitudenumberWith longitude and radiusKm: only people who applied from near here.
longitudenumber
radiusKmnumberHow far from latitude/longitude, in km (geocode_place gives the point).
get_candidate

Get a candidate and their applications

One person in the candidate pool and what they have applied to, newest first, by the candidateId list_candidates or search_existing_candidates returned. Applications on roles you can't see are left out. Use get_application for an application's full detail.

FieldTypeDescription
organisationId*string
candidateId*string
geocode_place

Resolve a place to coordinates

Turn a city or place name into the coordinates a 'radius' search needs. Call this before sourcing near somewhere, then pass the coordinates as `location`. Returns found: false when the name is not an actual city (a whole country, or unrecognisable) — use geocode_country for a country, or fall back to searchType 'everywhere'.

FieldTypeDescription
organisationId*string
name*stringThe place to resolve, e.g. 'London' or 'Leeds, UK'.
geocode_country

Resolve a country to its ISO code

Turn a country a recruiter named — 'United Kingdom', 'UK' or 'GB' — into the ISO 3166-1 alpha-2 code a 'country' search needs. Call this before sourcing country-wide, then pass the code as `country`. Returns found: false when the value is not a recognisable country.

FieldTypeDescription
organisationId*string
name*stringThe country to resolve, e.g. 'United Kingdom'.
1 toolCriteria

Generate a role's assessment criteria from its title and job description.

generate_suggested_criteria

Generate suggested role criteria

Generate a role's assessment criteria from its title and job description. Returns process changes ready to apply to a role (update_role_process) — the criteria every candidate added to it is assessed against. Nothing is created or changed: show the criteria to the user, adjust if they want, then apply them. `requirementsPreview` summarises what they ask for, which also tells you who to look for when sourcing. ALWAYS pass `roleId` when the role already exists — when you are working on a role rather than drafting one. That is what pulls in its intake call transcripts and note-taker summaries, which say what the hiring manager actually asked for; a job description is often boilerplate. Omitting it silently generates from the description alone. Omit it only while the role is still a draft.

FieldTypeDescription
organisationId*string
title*stringThe role title.
jobDescriptionstringThe role's job description. The richer this is, the better the criteria.
keywordsarray of stringSkills the hiring manager called out, when there is no job description yet.
roleIdstringAn existing role to draw intake material from. Its saved intake call transcripts and note-taker summaries are read in alongside the job description. Omit when the role does not exist yet.
1 toolResearch

Research the talent market for a role — pool depth, pay and who else is hiring. The only tools here that reach the web.

research_talent_market

Research a talent market

Research the talent market for a role — how deep the pool is, what it pays, and who else is hiring for it. Use it before committing to a hire, to sanity-check a salary, or to tell the user whether a location is realistic. Searches the web, not the organisation's candidate database (search_existing_candidates does that). Nothing is created or changed. `location` is a place as the recruiter would say it — 'London', 'the UK' — not coordinates or an ISO code. A market it finds nothing for returns found: false; say so rather than retrying.

FieldTypeDescription
organisationId*string
roleTitle*stringThe role to research, e.g. 'Software Engineer'.
location*stringWhere to research it, as the recruiter would say it — 'London', 'the UK', 'Berlin'. Not a coordinate or ISO code.
seniorityLevelstringThe seniority to narrow to, e.g. 'senior', 'lead'.
additionalContextstringAnything else that shapes the market, e.g. an industry or a required technology.
3 toolsReporting

Monthly usage reporting.

get_monthly_reporting

Get monthly recruitment reporting

Monthly reporting for an organisation: one row per role with application volumes (in period, total, last 7/30 days, rolling daily averages), hires and time to hire (average/median days), completion funnel percentages (started, complete, shortlisted), stalled and aged application counts, role status and whether the role uses AI assessment. Defaults to the latest available month. Use this to compare roles; use get_reporting_summary for org-wide trends and get_stage_reporting for pipeline bottlenecks.

FieldTypeDescription
organisationId*stringThe organisation to report on.
monthstringReport month in YYYY-MM format. Omit for the latest available month.
get_stage_reporting

Get pipeline stage reporting

Pipeline funnel reporting for an organisation: one row per role per pipeline stage, in pipeline order, with how many applications reached each stage and the average/median days spent in it. Use this to find where candidates drop off or wait the longest (pass-through rates, bottlenecks). Defaults to the latest available month. Months before stage metrics existed return no rows.

FieldTypeDescription
organisationId*stringThe organisation to report on.
monthstringReport month in YYYY-MM format. Omit for the latest available month.
get_reporting_summary

Get organisation reporting summary

Organisation-level trend reporting: one row per month with totals across every role — application volumes, hires and time to hire (average/median days), completion funnel percentages, aged applications and the share of roles using AI assessment. Use this first for topline questions and month-on-month trends. from/to are inclusive YYYY-MM bounds (at most 24 months); omit both for the trailing 12 months. Months without a report are omitted.

FieldTypeDescription
organisationId*stringThe organisation to report on.
fromstringFirst month in YYYY-MM format, inclusive.
tostringLast month in YYYY-MM format, inclusive.

The same operations are available over HTTP — see the REST API reference.

First.cx logo
Copyright © OpenDigital Limited 2026

We use cookies to analyse site traffic and improve your experience. By clicking "Accept", you consent to our use of analytics cookies. See our Privacy Policy for details.