mcp-ashby
Connects an MCP client to Ashby's public job boards to search, read, and compare job postings without an API key.
Resolve company names to Ashby board tokens (
resolve_board).Search open postings across up to 10 companies and filter by title/description, department, team, employment type, workplace/remoteness, country/location, recency, and compensation (
search_jobs).Read a full posting, including description (plain/html/none) and published compensation (
get_job).List board-specific vocabularies for filters, with counts and undeclared postings (
list_filter_values).Compare one compensation component (salary, equity, commission, bonus) across postings on a board, with non-published and different-period rows listed separately (
compare_compensation).
Provides tools to search and retrieve job postings from ElevenLabs' Ashby-hosted job board, including filtering by criteria, reading full job details, and comparing compensation data.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mcp-ashbysearch for engineering jobs at Eleven Labs"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mcp-ashby
Ashby is recruiting software, and every company using it gets a public job board that comes with it. Each board carries that company's open positions with their title, their department and team, the employment type, the locations and whether the work is remote, the full advert, and, where the company chose to publish it, the pay: a salary range, an equity share, a commission or a bonus, each with the period it is quoted over. Ashby holds one board per company and publishes no index across them.
This server connects a chat client to those boards. You name the companies you are interested in, and it turns each name into the token that addresses its board, searches their postings, filters them by department, team, location, country, employment type, remoteness, recency or pay, reads one posting in full, lists the words each board actually uses, and puts the pay of several postings side by side. It needs no API key and no account.
Install
One-click install
Claude Code
claude mcp add ashby -- npx -y mcp-ashbyClaude Desktop, Cursor, and any client using the standard config format
{
"mcpServers": {
"ashby": {
"command": "npx",
"args": ["-y", "mcp-ashby"]
}
}
}Node 24 or later is required, and no environment variable has to be set.
With Docker
{
"mcpServers": {
"ashby": {
"command": "docker",
"args": ["run", "-i", "--rm", "ghcr.io/smeet666/mcp-ashby:2.0.1"]
}
}
}-i keeps stdin open, which is where the protocol travels, and -t is left out
because a TTY rewrites the stream. The container needs outbound HTTPS to
api.ashbyhq.com, and nothing else: no volume, no port, no credential.
Bundle, without npm
Download mcp-ashby-2.0.1.mcpb from
the latest release and
open it. A client that supports MCP bundles installs it on its own, with no npm
and no configuration file to edit. The bundle carries its dependencies, so
nothing is fetched at install time.
Related MCP server: ashby-jobs
What you can ask
"Is Ramp hiring on Ashby?"
"Find me remote design roles at Ramp and Linear."
"Read me that posting in full."
"What departments does that board file its jobs under?"
"Put the salaries of those engineering postings side by side."
Every question starts from a company, since Ashby offers no search across boards.
search_jobs resolves the names you give it, so no preparation is needed:
resolve_board(["Ramp"]) -> ramp, publishing
search_jobs(["Ramp"], query: "designer", is_remote: true)
get_job("ramp", "b0c8…")Tools
Tool | What it does |
| Turns a company name into the Ashby board token. |
| Searches the postings of the companies you name. |
| Reads one posting in full, advert included. |
| Lists the words one board uses, with how many postings carry each. |
| Puts one pay component of several postings side by side. |
Every board keeps its own departments and teams, so a filter written from another
board's vocabulary narrows to nothing. list_filter_values publishes the words a
board actually uses.
resolve_board
Turns a company name into the token that addresses its Ashby board.
Argument | Type | Required | What it does |
| string | yes | A company name, or an Ashby board token you already know. |
In return: found, the boards that answered, and tried, the forms actually
sent in order. Four forms are tried per name, so nothing found is never proof
that a company is absent from Ashby.
search_jobs
Searches the postings of the companies named. Ashby serves a whole board at once, and every restriction below is applied to what was read.
Argument | Type | Required | What it does |
| array of 1 to 10 strings | yes | Company names or board tokens. |
| string | no | Words to look for. |
|
| no | Where |
| one string or a list of up to 10 | no | Departments as the board spells them. |
| one string or a list of up to 10 | no | Teams as the board spells them. |
| one string or a list of up to 6 | no | Employment types. |
| one string or a list of up to 4 | no | Workplace types. |
| boolean | no | Keep the postings marked remote. |
| one country or a list of up to 10 | no | Countries as the board spells them. |
| string | no | Part of a location line. |
| an ISO 8601 date | no | How recent a posting has to be. |
| boolean | no | Keep the postings whose company publishes a pay range. |
| number, 0 or more | no | A floor for the salary component. |
| three-letter code | no | The currency the floor is written in. |
| string, default | no | The period the floor belongs to. |
|
| no | How the rows are ordered. |
| integer, 1 to 100, default | no | Postings to serve. |
| integer, 0 to 10000, default | no | Postings to skip. |
In return: jobs, each carrying board and id, which get_job takes
together, plus title, department, team, employment_type, location,
country, secondary_location_count, workplace_type, is_remote,
published_at with the offset Ashby publishes, compensation_summary, job_url
and apply_url. The rows carry no advert text, at any limit.
total_on_board counts the postings the boards read hold, total_matched those
the criteria kept, and returned those in this answer: three different numbers.
per_company gives one outcome per company with its status, filters_applied
echoes what was applied, and undeclared counts the postings that declare
nothing on a field being filtered, so a restriction never silently swallows them.
get_job
Reads one posting in full.
Argument | Type | Required | What it does |
| string | yes | A company name, or an Ashby board token. |
| string | yes | The identifier a search row carries. |
|
| no | How to serve the advert. |
| boolean, default | no | Carry the pay the company published. |
The advert runs to thousands of characters, and html is the company's own
markup, unrewritten.
In return: the posting a search row carries, with its description, its locations and the pay components the company published.
list_filter_values
Lists the words one board actually uses, with how many postings carry each.
Argument | Type | Required | What it does |
| string | yes | A company name, or a board token. |
|
| no | Which vocabulary to read. |
In return: facets, each value with the number of postings carrying it, and
undeclared, the postings declaring nothing on that facet. sibling_spellings
names the wordings that differ only in case or spacing, which a filter would
otherwise treat as two different things.
compare_compensation
Puts one pay component of several postings side by side.
Argument | Type | Required | What it does |
| string | yes | A company name, or a board token. |
| array of up to 50 strings | no | The postings to compare. |
| one string or a list of up to 10 | no | Compare a department instead. |
| one string or a list of up to 10 | no | Compare a team instead. |
| string | no | Words to look for in the titles. |
|
| no | Which component to compare. |
| string, default | no | The period compared. |
| integer, 1 to 100, default | no | Postings to compare. |
One component at a time: a share of capital and a salary do not add up. Postings quoted over another period are listed apart, unconverted.
In return: rows, one per posting, with the component and the interval
they were compared on, currencies_present naming every currency in the answer,
and not_published listing the postings whose company published nothing, which
is never the same as zero.
What a pay figure means
A company publishes what it chooses. A posting without a range comes back with none, never with a zero. A range is reported in the currency and over the period Ashby carries it in, and it is never converted or annualised: comparing two postings quoted over different periods is left to whoever knows what the comparison is for.
Configuration
Nothing has to be configured. The server reads no environment variable, and the
mcpServers block above is complete as written.
The pacing, the timeout and the cache are settings of the client layer, which As a library shows how to pass. The interval between two requests can be widened there and never narrowed.
Errors
Every failure carries one of six codes, a message, and where it helps the values that would have been accepted.
Code | What happened | What to do |
| Ashby answered, and holds no such board or posting. | Check the token with |
| The arguments were refused before any request went out. | Read the message, which names the argument and what it takes. |
| Ashby asked this client to slow down. | Wait, then call again with the same arguments. The posting is still on the board. |
| Ashby answered in a shape this client cannot read. | Report it at the issue tracker. |
| The request did not complete. | Try again shortly. |
| The request passed its deadline. | Ask for fewer companies, or a smaller |
As a library
The layer reading Ashby is published on its own, with its pacing, its cache and its errors, and with no protocol attached.
import { Client } from "mcp-ashby/client";
const client = new Client({ minIntervalMs: 2000 });
const resolved = await client.resolveBoard("Ramp");
console.log(resolved.found);ClientOptions takes minIntervalMs, timeoutMs, cacheTtlMs and fetchImpl.
An interval below the published floor is ignored, so the floor holds here as
well.
Pacing and attribution
Requests go out one at a time with at least a second between them, and that floor
holds however the client is configured. Ashby serves a whole board in one
response, which can weigh megabytes, so a single question about one company costs
one request and this server holds the answer briefly rather than asking again.
The User-Agent carries the project and an address where a person can be
reached, and imitates no browser.
Every posting carries the address of its Ashby page and its apply URL. Credit the company and link that page when you show a posting.
This MCP server is an unofficial project, with no affiliation to Ashby or to the companies whose boards it reads.
Privacy
This server collects nothing about you and sends nothing to its author. It runs
on your machine, contacts api.ashbyhq.com and nothing else, holds its answers
in memory while it runs, and writes nothing to disk.
PRIVACY.md states what a request carries and which settings change
any of it.
Development
npm install
npm run build:fixtures
npm test
npm run checkTests run against generated fixtures and make no network request. The live suite,
npm run test:live, makes one request per route and runs nightly against the
service itself.
Contributing
Bugs, questions and ideas belong in the issue tracker. Pull requests are welcome; opening an issue first helps agree on the shape of the change. See CONTRIBUTING.md.
License
MIT, see LICENSE. The postings belong to the companies that published them.
mcp-ashby (français)
Ashby est un logiciel de recrutement, et chaque entreprise qui l'utilise reçoit avec lui un site d'offres public. Chaque site porte les postes ouverts de cette entreprise avec leur intitulé, leur département et leur équipe, le type de contrat, les lieux et le caractère distant du travail, l'annonce complète, et, quand l'entreprise a choisi de le publier, la rémunération : une fourchette de salaire, une part de capital, une commission ou une prime, chacune avec la période sur laquelle elle est exprimée. Ashby héberge un site par entreprise et ne publie aucun index les traversant.
Ce serveur relie un client de conversation à ces sites. Vous nommez les entreprises qui vous intéressent, et il traduit chaque nom en le jeton qui adresse son site, cherche dans leurs offres, les filtre par département, équipe, lieu, pays, type de contrat, télétravail, fraîcheur ou rémunération, lit une offre en entier, liste les mots que chaque site emploie réellement, et met les rémunérations de plusieurs offres côte à côte. Aucune clé d'API, aucun compte.
Installation
Installation en un clic
Claude Code
claude mcp add ashby -- npx -y mcp-ashbyClaude Desktop, Cursor, et tout client au format de configuration standard
{
"mcpServers": {
"ashby": {
"command": "npx",
"args": ["-y", "mcp-ashby"]
}
}
}Node 24 ou plus récent est nécessaire, et aucune variable d'environnement n'est à renseigner.
Avec Docker
{
"mcpServers": {
"ashby": {
"command": "docker",
"args": ["run", "-i", "--rm", "ghcr.io/smeet666/mcp-ashby:2.0.1"]
}
}
}-i garde l'entrée standard ouverte, qui est le canal du protocole, et -t est
omis parce qu'un TTY réécrit le flux. Le conteneur a besoin d'un accès HTTPS
sortant vers api.ashbyhq.com, et de rien d'autre : aucun volume, aucun port,
aucun identifiant.
Bundle, sans npm
Téléchargez mcp-ashby-2.0.1.mcpb depuis
la dernière publication
et ouvrez-le. Un client qui gère les bundles MCP l'installe seul, sans npm et
sans fichier de configuration à modifier. Le bundle emporte ses dépendances, donc
rien n'est téléchargé à l'installation.
Ce qu'on peut demander
« Est-ce que Ramp recrute sur Ashby ? »
« Trouve-moi des postes de design en télétravail chez Ramp et Linear. »
« Lis-moi cette offre en entier. »
« Sous quels départements ce site classe-t-il ses offres ? »
« Mets côte à côte les salaires de ces offres d'ingénierie. »
Chaque question part d'une entreprise, puisque Ashby n'offre aucune recherche
traversant les sites. search_jobs résout lui-même les noms qu'on lui donne,
donc rien n'est à préparer :
resolve_board(["Ramp"]) -> ramp, publie
search_jobs(["Ramp"], query: "designer", is_remote: true)
get_job("ramp", "b0c8…")Les outils
Outil | Ce qu'il fait |
| Traduit un nom d'entreprise en jeton de site Ashby. |
| Cherche dans les offres des entreprises nommées. |
| Lit une offre en entier, annonce comprise. |
| Liste les mots qu'un site emploie, et combien d'offres les portent. |
| Met une composante de rémunération de plusieurs offres côte à côte. |
Chaque site garde ses propres départements et équipes, donc un filtre écrit dans
le vocabulaire d'un autre site ne retient rien. list_filter_values publie les
mots qu'un site emploie réellement.
resolve_board
Traduit un nom d'entreprise en le jeton qui adresse son site Ashby.
Argument | Type | Requis | Ce qu'il fait |
| chaîne | oui | Un nom d'entreprise, ou un jeton Ashby déjà connu. |
En retour : found, les sites qui ont répondu, et tried, les formes
réellement envoyées dans l'ordre. Quatre formes sont essayées par nom, donc ne
rien trouver ne prouve jamais qu'une entreprise est absente d'Ashby.
search_jobs
Cherche dans les offres des entreprises nommées. Ashby sert un site entier d'un coup, et chaque restriction ci-dessous s'applique à ce qui a été lu.
Argument | Type | Requis | Ce qu'il fait |
| tableau de 1 à 10 chaînes | oui | Noms d'entreprises ou jetons. |
| chaîne | non | Les mots à chercher. |
|
| non | Où |
| une chaîne ou une liste jusqu'à 10 | non | Départements comme le site les écrit. |
| une chaîne ou une liste jusqu'à 10 | non | Équipes comme le site les écrit. |
| une chaîne ou une liste jusqu'à 6 | non | Types de contrat. |
| une chaîne ou une liste jusqu'à 4 | non | Modes de travail. |
| booléen | non | Ne garder que les offres en télétravail. |
| un pays ou une liste jusqu'à 10 | non | Pays comme le site les écrit. |
| chaîne | non | Une partie d'une ligne de lieu. |
| une date ISO 8601 | non | L'ancienneté maximale d'une offre. |
| booléen | non | Ne garder que les offres dont l'entreprise publie une fourchette. |
| nombre, 0 ou plus | non | Un plancher pour la composante salaire. |
| code à trois lettres | non | La devise du plancher. |
| chaîne, défaut | non | La période à laquelle le plancher se rapporte. |
|
| non | L'ordre des lignes. |
| entier, 1 à 100, défaut | non | Offres à servir. |
| entier, 0 à 10000, défaut | non | Offres à enjamber. |
En retour : jobs, chacune portant board et id, que get_job reprend
ensemble, plus title, department, team, employment_type, location,
country, secondary_location_count, workplace_type, is_remote,
published_at avec le décalage horaire qu'Ashby publie, compensation_summary,
job_url et apply_url. Les lignes ne portent pas l'annonce, quelle que soit
la limite. total_on_board compte les offres que contiennent les sites lus,
total_matched celles que les critères ont retenues, et returned celles de
cette réponse : trois nombres différents. per_company donne une issue par
entreprise avec son status, filters_applied redonne ce qui a été appliqué, et
undeclared compte les offres qui ne déclarent rien sur un champ filtré, pour
qu'une restriction ne les avale jamais en silence.
get_job
Lit une offre en entier.
Argument | Type | Requis | Ce qu'il fait |
| chaîne | oui | Un nom d'entreprise, ou un jeton Ashby. |
| chaîne | oui | L'identifiant que porte une ligne. |
|
| non | Comment servir l'annonce. |
| booléen, défaut | non | Porter la rémunération publiée. |
L'annonce fait des milliers de caractères, et html est le balisage de
l'entreprise, non réécrit.
En retour : l'offre que porte une ligne de recherche, avec sa description, ses lieux et les composantes de rémunération que l'entreprise a publiées.
list_filter_values
Liste les mots qu'un site emploie réellement, et combien d'offres portent chacun.
Argument | Type | Requis | Ce qu'il fait |
| chaîne | oui | Un nom d'entreprise, ou un jeton. |
|
| non | Le vocabulaire à lire. |
En retour : facets, chaque valeur avec le nombre d'offres qui la portent,
et undeclared, les offres qui ne déclarent rien sur cette facette.
sibling_spellings nomme les formulations qui ne diffèrent que par la casse ou
les espaces, qu'un filtre traiterait sinon comme deux choses distinctes.
compare_compensation
Met une composante de rémunération de plusieurs offres côte à côte.
Argument | Type | Requis | Ce qu'il fait |
| chaîne | oui | Un nom d'entreprise, ou un jeton. |
| tableau jusqu'à 50 chaînes | non | Les offres à comparer. |
| une chaîne ou une liste jusqu'à 10 | non | Comparer un département. |
| une chaîne ou une liste jusqu'à 10 | non | Comparer une équipe. |
| chaîne | non | Les mots à chercher dans les intitulés. |
|
| non | La composante comparée. |
| chaîne, défaut | non | La période comparée. |
| entier, 1 à 100, défaut | non | Offres à comparer. |
Une composante à la fois : une part de capital et un salaire ne s'additionnent pas. Les offres exprimées sur une autre période sont listées à part, sans conversion.
En retour : rows, une par offre, avec le component et l'interval sur
lesquels elles ont été comparées, currencies_present qui nomme chaque devise
présente dans la réponse, et not_published qui liste les offres dont
l'entreprise n'a rien publié, ce qui ne vaut jamais zéro.
Ce que dit un chiffre de rémunération
Une entreprise publie ce qu'elle veut. Une offre sans fourchette revient sans rien, jamais avec un zéro. Une fourchette est rendue dans la devise et sur la période où Ashby la porte, et elle n'est jamais convertie ni annualisée : comparer deux offres exprimées sur des périodes différentes est laissé à qui sait à quoi la comparaison doit servir.
Configuration
Il n'y a rien à configurer. Le serveur ne lit aucune variable d'environnement, et
le bloc mcpServers ci-dessus est complet tel quel.
Le rythme, le délai et le cache sont des réglages de la couche cliente, que Comme bibliothèque montre comment passer. L'écart entre deux requêtes peut y être élargi et jamais resserré.
Erreurs
Chaque échec porte un des six codes, un message, et quand cela aide les valeurs qui auraient été acceptées.
Code | Ce qui s'est passé | Que faire |
| Ashby a répondu, et n'a ni ce site ni cette offre. | Vérifiez le jeton avec |
| Les arguments ont été refusés avant toute requête. | Lisez le message, qui nomme l'argument et ce qu'il prend. |
| Ashby demande à ce client de ralentir. | Attendez, puis rappelez avec les mêmes arguments. L'offre est toujours en ligne. |
| Ashby a répondu dans une forme que ce client ne lit pas. | Signalez-le sur le suivi d'incidents. |
| La requête n'a pas abouti. | Réessayez sous peu. |
| La requête a dépassé son délai. | Demandez moins d'entreprises, ou un |
Comme bibliothèque
La couche qui lit Ashby est publiée seule, avec son rythme, son cache et ses erreurs, sans protocole attaché.
import { Client } from "mcp-ashby/client";
const client = new Client({ minIntervalMs: 2000 });
const resolved = await client.resolveBoard("Ramp");
console.log(resolved.found);ClientOptions prend minIntervalMs, timeoutMs, cacheTtlMs et fetchImpl.
Un écart sous le plancher publié est ignoré, donc le plancher tient également
ici.
Rythme et attribution
Les requêtes partent une à une avec au moins une seconde entre elles, et ce
plancher tient quelle que soit la configuration du client. Ashby sert un site
entier en une réponse, qui peut peser plusieurs mégaoctets, donc une question sur
une entreprise coûte une requête et ce serveur garde brièvement la réponse plutôt
que de redemander. Le User-Agent porte le projet et une adresse où joindre une
personne, et n'imite aucun navigateur.
Chaque offre porte l'adresse de sa page Ashby et son adresse de candidature. Créditez l'entreprise et renvoyez vers cette page quand vous montrez une offre.
Ce MCP est un projet non officiel, sans affiliation à Ashby ni aux entreprises dont il lit les sites.
Confidentialité
Ce serveur ne collecte rien sur vous et n'envoie rien à son auteur. Il tourne sur
votre machine, ne joint que api.ashbyhq.com, garde ses réponses en mémoire le
temps qu'il tourne, et n'écrit rien sur le disque. PRIVACY.md dit
ce qu'une requête emporte et quels réglages changent cela.
Développement
npm install
npm run build:fixtures
npm test
npm run checkLes tests s'exécutent sur des fixtures engendrées et n'émettent aucune requête.
La suite en direct, npm run test:live, émet une requête par route et tourne
chaque nuit contre le service lui-même.
Contribuer
Les anomalies, les questions et les idées ont leur place dans le suivi d'incidents. Les propositions de modification sont bienvenues ; ouvrir un ticket d'abord aide à s'accorder sur la forme du changement. Voir CONTRIBUTING.md.
Licence
MIT, voir LICENSE. Les offres appartiennent aux entreprises qui les ont publiées.
Available Tools
5 toolscompare_compensationCompare published pay on one boardARead-onlyIdempotent
Put the pay ranges of one Ashby board side by side, one component at a time and one period at a time. Nothing is converted between currencies, summed across components or averaged: a third of the postings publish no range at all, and they are named rather than dropped.
| Name | Required | Description | Default |
|---|---|---|---|
| team | No | ||
| board | Yes | A company name, or an Ashby board token. | |
| limit | No | How many postings to compare. | |
| query | No | Words to look for in the titles. | |
| job_ids | No | A list of posting identifiers search_jobs returned, one to 50 of them. | |
| interval | No | The period compared, 1 YEAR unless named. Postings on another period are listed apart, unconverted. | 1 YEAR |
| component | No | One component at a time: a share of capital and a salary do not add up. | Salary |
| department | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| rows | Yes | |
| board | Yes | |
| notes | Yes | |
| interval | Yes | |
| component | Yes | |
| not_published | Yes | Postings whose company publishes no range, named rather than dropped. |
| other_intervals | Yes | Postings on another period, left unconverted. |
| currencies_present | Yes | Named, because rows in different currencies do not rank against each other. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Despite having readOnlyHint, idempotentHint, and openWorldHint annotations, the description adds non-obvious behavior: 'Nothing is converted between currencies, summed across components or averaged' and 'a third of the postings publish no range at all, and they are named rather than dropped.' This is valuable context beyond what annotations provide.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two tight sentences, front-loaded with the core action, then crucial caveats. Every clause earns its place with zero waste.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present and strong annotations, the description sufficiently covers the tool's behavioral quirks. It doesn't mention filtering options like team or department, but those are documented in the schema, so for a comparison tool this is adequate.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema descriptions already cover the parameters well (~75% coverage), including explaining component and interval behavior. The description reinforces the 'one component/period at a time' rule but doesn't add new parameter-level details beyond what the schema already states.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb and resource ('Put the pay ranges of one Ashby board side by side') and clearly distinguishes this from sibling tools like search_jobs or get_job. It also provides scope constraints (one component, one period), making the tool's function unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies when to use this tool: to compare pay ranges across postings on a board, with explicit constraints on components and periods. It doesn't explicitly name alternatives or state when not to use, but the context is clear enough given the sibling tools are distinct.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_jobRead one postingARead-onlyIdempotent
Read one Ashby posting in full: its locations, its description and the pay ranges its company published. Ashby addresses a posting by board and identifier, both of which search_jobs returns.
| Name | Required | Description | Default |
|---|---|---|---|
| board | Yes | A company name, or an Ashby board token. | |
| job_id | Yes | The identifier search_jobs returned for this posting. | |
| description | No | The description runs to thousands of characters. html is the company's own markup, unrewritten. | plain |
| include_compensation | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| id | Yes | |
| team | Yes | |
| board | Yes | |
| notes | Yes | |
| title | Yes | |
| source | Yes | |
| job_url | Yes | |
| location | Yes | |
| apply_url | Yes | |
| is_listed | Yes | False marks a posting Ashby serves by direct link alone rather than on the board. |
| is_remote | Yes | |
| department | Yes | |
| description | Yes | Null when the caller asked for no description. |
| compensation | Yes | Null when the caller asked for no compensation, which states nothing about the company. |
| published_at | Yes | ISO 8601 with the offset Ashby publishes. |
| workplace_type | Yes | Null when the company records none, which is never the same as on site. |
| employment_type | Yes | |
| secondary_locations | Yes | |
| compensation_summary | Yes | Null when the company withholds its pay ranges, which is never zero. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly, idempotent, and non-destructive behavior, so the description does not need to repeat those. It adds useful context about what is read (locations, description, pay ranges) and how addresses work, but does not disclose deeper behavioral details like response size or optional parameter effects.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two sentences long, front-loaded with the primary purpose, and contains no fluff. It efficiently communicates the essential behavior and the source of required identifiers.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the presence of an output schema and rich annotations, the description is largely complete. It explains the core read intent and required identifiers, though it could more explicitly connect the optional include_compensation and description parameters to the overall behavior, but the schema covers these.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema covers most parameter descriptions (75% coverage), and the description adds the meaningful context that both board and job_id are returned by search_jobs. However, it does not explain the include_compensation parameter or the description format options beyond what the schema already states, so it adds limited value.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's function: reading one Ashby posting in full, including locations, description, and pay ranges. It distinguishes itself from the sibling search_jobs tool by emphasizing it reads a single posting rather than searching, and references how Ashby addresses postings.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explicitly states that both the board and identifier should come from search_jobs, giving clear prerequisite context. However, it does not explicitly mention when to avoid this tool or compare it with alternatives like compare_compensation, so it lacks explicit exclusions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_filter_valuesList a board's filter wordingsARead-onlyIdempotent
List the words one Ashby board actually uses, with how many postings carry each, and how many declare nothing. Every board keeps its own departments and teams, so a filter written from another board's vocabulary narrows to nothing.
| Name | Required | Description | Default |
|---|---|---|---|
| board | Yes | A company name, or an Ashby board token. | |
| facet | No | all |
Output Schema
| Name | Required | Description |
|---|---|---|
| board | Yes | |
| notes | Yes | |
| facets | Yes | |
| total_jobs | Yes | |
| undeclared | Yes | Postings a criterion could say nothing about, counted per field. A posting that records no workplace is not a posting on site. |
| sibling_spellings | Yes | Spellings on this board that name what looks like one place, so a caller asks for both rather than picking one. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, openWorldHint, idempotentHint, and destructiveHint=false, covering the safety profile. The description adds that output includes counts and a 'nothing' bucket, and stresses that values are board-specific—useful behavioral context beyond the annotations. It does not mention rate limits or auth, but that is not a major gap here.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two tight sentences: the first states the action and output, the second provides a crucial context warning. No wasted words.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present, the description need not detail return values. It covers the action, output composition, and a board-specific caveat. However, the unclarified facet parameter is a small gap, keeping it from a perfect score.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 50%. The 'board' parameter is already described in the schema, while the 'facet' parameter has only an enum without explanatory text. The description does not clarify the facet parameter's meaning or how it relates to the tool's purpose, so it adds no value beyond the schema for parameter semantics.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool lists the actual filter words used by an Ashby board, along with counts and a 'nothing' bucket. It uses a specific verb 'list' and identifies the resource (board's filter wordings), distinguishing it from siblings that search, get jobs, or compare compensation.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explains a key use case: since each board has its own vocabulary, writing filters with another board's terms yields empty results. This implies using the tool to obtain board-specific terms before filtering, offering clear context. It does not explicitly name alternatives or exclusions, so it does not earn a 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
resolve_boardResolve company names to Ashby boardsARead-onlyIdempotent
Turn a company name into the token that addresses its Ashby job board, and report every form that answered. search_jobs does this on its own, so call this when a name returns nothing and you want to see the spellings that were tried.
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | A company name, or an Ashby board token you already know. |
Output Schema
| Name | Required | Description |
|---|---|---|
| found | Yes | |
| input | Yes | |
| notes | Yes | |
| tried | Yes | The forms actually sent, in order. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare read-only, idempotent, non-destructive behavior. The description adds the behavioral detail that the tool 'reports every form that answered' and shows 'spellings that were tried', which goes beyond the safety profile and explains its observational nature.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is exactly two sentences, front-loaded with the core action, and the second sentence provides essential usage guidance. Every word earns its place with no redundancy or filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With a single parameter, an output schema, and comprehensive annotations, the description covers purpose, usage conditions, and key behavior. The tool is simple, and the description fully equips an agent to select and invoke it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% with a description for 'name' that already mentions it accepts a company name or an existing token. The tool description does not add new parameter information beyond the schema, so baseline 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb ('Turn') and resource ('company name into the token'), clearly stating the tool's function. It distinguishes itself from sibling search_jobs by noting that search_jobs already does this, making the purpose unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly states when to use: 'call this when a name returns nothing' and names the alternative (search_jobs). This provides clear context for deciding between tools, which is exactly what usage guidelines should do.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_jobsSearch postings at named companiesARead-onlyIdempotent
Search the open postings of named companies on Ashby. Ashby publishes no index across its customers, so companies are required. Every filter runs here rather than at the source, and the answer reports how many postings declared nothing about a field it filtered on. Rows carry no description: read one posting with get_job.
| Name | Required | Description | Default |
|---|---|---|---|
| sort | No | published_desc | |
| team | No | ||
| limit | No | How many postings to return. | |
| query | No | Words to look for. | |
| offset | No | How many postings to skip. | |
| country | No | ||
| currency | No | A three-letter currency code, as in EUR or USD. | |
| companies | Yes | Company names or board tokens, one to 10. Each costs a request and a whole board, which is why 5 at a time is the comfortable number. | |
| is_remote | No | Keeps only postings that declare it. Postings recording nothing are counted in undeclared rather than treated as on site. | |
| search_in | No | Where query is looked for. Descriptions run to thousands of characters each. | title |
| department | No | ||
| salary_min | No | A floor for the salary component. | |
| workplace_type | No | ||
| employment_type | No | ||
| published_after | No | An ISO 8601 date. | |
| salary_interval | No | The period salary_min belongs to, 1 YEAR unless named. An hourly amount is never weighed against a yearly floor. | 1 YEAR |
| has_compensation | No | Keeps only postings whose company publishes a pay range. | |
| location_contains | No | Part of a location line. |
Output Schema
| Name | Required | Description |
|---|---|---|
| jobs | Yes | No descriptions, at any limit. |
| notes | Yes | |
| returned | Yes | Postings in this answer, after limit and offset. |
| undeclared | Yes | Postings a criterion could say nothing about, counted per field. A posting that records no workplace is not a posting on site. |
| per_company | Yes | |
| total_matched | Yes | Postings the criteria kept. |
| total_on_board | Yes | Postings the boards read hold, all of them. |
| filters_applied | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, openWorldHint, idempotentHint, and destructiveHint='false', so the safety profile is covered. The description adds useful behavioral context about client-side filtering, the reporting of undeclared field counts, and the absence of descriptions in rows, which is valuable beyond annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is three sentences and front-loaded with the core purpose. Every sentence conveys essential information: the action, the constraint (companies required), and a behavioral note about filtering and output, plus a pointer to get_job. No wasted words.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool has 18 parameters and an output schema, the description doesn't need to explain every parameter or return value. It covers the critical context: company requirement, client-side filtering, undeclared counts, and no descriptions in rows. With an output schema present and rich annotations, this is sufficient, though it omits details about pagination that the schema does provide.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema has 67% description coverage with detailed parameter descriptions, so the description doesn't need to add much. The description does mention that companies are required, but that's already in the schema. It provides no additional parameter-level semantics, so a baseline score of 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool searches open postings at named companies on Ashby, with a specific verb and resource. It distinguishes itself from siblings like get_job (read one posting) and list_filter_values by indicating its scope and the requirement to specify companies.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explains that companies are required, which is a key usage constraint, and explicitly points to get_job for reading full descriptions. It also notes that filters run client-side, implying this tool is used for cross-company searches rather than source-level filtering. However, it doesn't contrast with all siblings (e.g., list_filter_values) explicitly.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
TDQS
Each tool serves a distinct role: resolving company names, searching postings, fetching full details, listing filter vocabularies, and comparing compensation. While resolve_board and search_jobs both touch board resolution, resolve_board is explicitly a fallback for when search returns nothing, preventing confusion.
All five tools follow a consistent verb_noun pattern with snake_case (resolve_board, search_jobs, get_job, list_filter_values, compare_compensation). No style mixing or vague verbs.
Five tools is an appropriate scope for a job-search read-only server, covering search, retrieval, filtering support, and compensation analysis without bloat.
The set covers the key workflows: searching postings, reading full details, understanding filter vocabulary, and comparing compensation. The only potential gap (searching without a company) is impossible due to Ashby's lack of an index, so the coverage is complete for the domain.
Maintenance
Related MCP Connectors
Ashby ATS job scraper pulls live postings via API with team filtering.
Ashby job boards with employer-published salary data, via an Apify Actor, hosted MCP.
Scrape job listings from Greenhouse, Workable, Reed, RemoteOK and other boards. Pay per row.
Live job postings from 30+ ATS feeds and job boards, one schema. Live results need a Bearer key.
Related MCP Servers
- FlicenseNot gradedqualityCmaintenanceEnables AI agents to pull live job listings from major ATS platforms (Greenhouse, Lever, Ashby, Workable), Hacker News hiring threads, and detect hiring signals on company career pages.
- FlicenseNot gradedqualityCmaintenanceSearch and browse any company's public Ashby job board from Claude Code.
- AlicenseAqualityBmaintenanceEnables querying open job postings directly from company applicant-tracking systems (Greenhouse, Ashby, Lever), finding a company's job board, listing and comparing roles, and accessing salary data, all without scraping or API keys.3MIT
- AlicenseNot gradedqualityBmaintenanceEnables natural-language job search and aggregation from multiple recruitment websites with zero configuration, providing filtered results and standardized output for AI assistants.69ISC
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/smeet666/mcp-ashby'
If you have feedback or need assistance with the MCP directory API, please join our Discord server