Skip to main content
Glama

mcp-ashby

npm CI license MCP Registry Glama M8ven LobeHub Install in Cursor Install in VS Code

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.

Version française


Install

One-click install

Install in Cursor Install in VS Code

Claude Code

claude mcp add ashby -- npx -y mcp-ashby

Claude 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.1.0"]
    }
  }
}

-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.1.0.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: io.github.groundtruthtools/ats-jobs-mcp

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

resolve_board

Turns a company name into the Ashby board token.

search_jobs

Searches the postings of the companies you name.

get_job

Reads one posting in full, advert included.

list_filter_values

Lists the words one board uses, with how many postings carry each.

compare_compensation

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

name

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

companies

array of 1 to 10 strings

yes

Company names or board tokens.

query

string

no

Words to look for.

search_in

title or title_and_description, default title

no

Where query is looked for.

department

one string or a list of up to 10

no

Departments as the board spells them.

team

one string or a list of up to 10

no

Teams as the board spells them.

employment_type

one string or a list of up to 6

no

Employment types.

workplace_type

one string or a list of up to 4

no

Workplace types.

is_remote

boolean

no

Keep the postings marked remote.

country

one country or a list of up to 10

no

Countries as the board spells them.

location_contains

string

no

Part of a location line.

published_after

an ISO 8601 date

no

How recent a posting has to be.

has_compensation

boolean

no

Keep the postings whose company publishes a pay range.

salary_min

number, 0 or more

no

A floor for the salary component.

currency

three-letter code

no

The currency the floor is written in.

salary_interval

string, default 1 YEAR

no

The period the floor belongs to.

sort

published_desc, published_asc or title, default published_desc

no

How the rows are ordered.

limit

integer, 1 to 100, default 20

no

Postings to serve.

offset

integer, 0 to 10000, default 0

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

board

string

yes

A company name, or an Ashby board token.

job_id

string

yes

The identifier a search row carries.

description

plain, html or none, default plain

no

How to serve the advert.

include_compensation

boolean, default true

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

board

string

yes

A company name, or a board token.

facet

departments, teams, locations, countries, employment_types, workplace_types or all, default all

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

board

string

yes

A company name, or a board token.

job_ids

array of up to 50 strings

no

The postings to compare.

department

one string or a list of up to 10

no

Compare a department instead.

team

one string or a list of up to 10

no

Compare a team instead.

query

string

no

Words to look for in the titles.

component

Salary, EquityCashValue, EquityPercentage, Commission or Bonus, default Salary

no

Which component to compare.

interval

string, default 1 YEAR

no

The period compared.

limit

integer, 1 to 100, default 25

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

not_found

Ashby answered, and holds no such board or posting.

Check the token with resolve_board.

invalid_input

The arguments were refused before any request went out.

Read the message, which names the argument and what it takes.

rate_limited

Ashby asked this client to slow down.

Wait, then call again with the same arguments. The posting is still on the board.

parse_failure

Ashby answered in a shape this client cannot read.

Report it at the issue tracker.

network_error

The request did not complete.

Try again shortly.

timeout

The request passed its deadline.

Ask for fewer companies, or a smaller limit.

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 check

Tests 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)

English version

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

Install in Cursor Install in VS Code

Claude Code

claude mcp add ashby -- npx -y mcp-ashby

Claude 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.1.0"]
    }
  }
}

-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.1.0.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

resolve_board

Traduit un nom d'entreprise en jeton de site Ashby.

search_jobs

Cherche dans les offres des entreprises nommées.

get_job

Lit une offre en entier, annonce comprise.

list_filter_values

Liste les mots qu'un site emploie, et combien d'offres les portent.

compare_compensation

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

name

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

companies

tableau de 1 à 10 chaînes

oui

Noms d'entreprises ou jetons.

query

chaîne

non

Les mots à chercher.

search_in

title ou title_and_description, défaut title

non

Où query est cherché.

department

une chaîne ou une liste jusqu'à 10

non

Départements comme le site les écrit.

team

une chaîne ou une liste jusqu'à 10

non

Équipes comme le site les écrit.

employment_type

une chaîne ou une liste jusqu'à 6

non

Types de contrat.

workplace_type

une chaîne ou une liste jusqu'à 4

non

Modes de travail.

is_remote

booléen

non

Ne garder que les offres en télétravail.

country

un pays ou une liste jusqu'à 10

non

Pays comme le site les écrit.

location_contains

chaîne

non

Une partie d'une ligne de lieu.

published_after

une date ISO 8601

non

L'ancienneté maximale d'une offre.

has_compensation

booléen

non

Ne garder que les offres dont l'entreprise publie une fourchette.

salary_min

nombre, 0 ou plus

non

Un plancher pour la composante salaire.

currency

code à trois lettres

non

La devise du plancher.

salary_interval

chaîne, défaut 1 YEAR

non

La période à laquelle le plancher se rapporte.

sort

published_desc, published_asc ou title, défaut published_desc

non

L'ordre des lignes.

limit

entier, 1 à 100, défaut 20

non

Offres à servir.

offset

entier, 0 à 10000, défaut 0

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

board

chaîne

oui

Un nom d'entreprise, ou un jeton Ashby.

job_id

chaîne

oui

L'identifiant que porte une ligne.

description

plain, html ou none, défaut plain

non

Comment servir l'annonce.

include_compensation

booléen, défaut true

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

board

chaîne

oui

Un nom d'entreprise, ou un jeton.

facet

departments, teams, locations, countries, employment_types, workplace_types ou all, défaut all

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

board

chaîne

oui

Un nom d'entreprise, ou un jeton.

job_ids

tableau jusqu'à 50 chaînes

non

Les offres à comparer.

department

une chaîne ou une liste jusqu'à 10

non

Comparer un département.

team

une chaîne ou une liste jusqu'à 10

non

Comparer une équipe.

query

chaîne

non

Les mots à chercher dans les intitulés.

component

Salary, EquityCashValue, EquityPercentage, Commission ou Bonus, défaut Salary

non

La composante comparée.

interval

chaîne, défaut 1 YEAR

non

La période comparée.

limit

entier, 1 à 100, défaut 25

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

not_found

Ashby a répondu, et n'a ni ce site ni cette offre.

Vérifiez le jeton avec resolve_board.

invalid_input

Les arguments ont été refusés avant toute requête.

Lisez le message, qui nomme l'argument et ce qu'il prend.

rate_limited

Ashby demande à ce client de ralentir.

Attendez, puis rappelez avec les mêmes arguments. L'offre est toujours en ligne.

parse_failure

Ashby a répondu dans une forme que ce client ne lit pas.

Signalez-le sur le suivi d'incidents.

network_error

La requête n'a pas abouti.

Réessayez sous peu.

timeout

La requête a dépassé son délai.

Demandez moins d'entreprises, ou un limit plus petit.

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 check

Les 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 tools
compare_compensationCompare published pay on one boardA
Read-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.

NameRequiredDescriptionDefault
teamNo
boardYesA company name, or an Ashby board token.
limitNoHow many postings to compare.
queryNoWords to look for in the titles.
job_idsNoA list of posting identifiers search_jobs returned, one to 50 of them.
intervalNoThe period compared, 1 YEAR unless named. Postings on another period are listed apart, unconverted.1 YEAR
componentNoOne component at a time: a share of capital and a salary do not add up.Salary
departmentNo

Output Schema

NameRequiredDescription
rowsYes
boardYes
notesYes
intervalYes
componentYes
not_publishedYesPostings whose company publishes no range, named rather than dropped.
other_intervalsYesPostings on another period, left unconverted.
currencies_presentYesNamed, because rows in different currencies do not rank against each other.

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover the safety profile (readOnly, idempotent, non-destructive), so the description rightly spends its words elsewhere and does so usefully: no currency conversion, no summing across components, no averaging, and roughly a third of postings have no range but are named rather than dropped. That last point is genuinely non-obvious output behavior. Auth and rate-limit details are absent, keeping it from a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, purpose front-loaded, second sentence carrying the behavioral caveats. No filler and no restatement of the title or schema.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need not be described; the description fills the remaining gaps an agent would care about — no cross-currency or cross-component math, and missing-range postings are still listed. For an 8-parameter read tool at 75% schema coverage, this is sufficient to call correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 75%, so the baseline is 3, and the description earns an extra point by explaining the semantics behind two key parameters: component is one-at-a-time because a share of capital and a salary do not add up, and interval defaults to 1 YEAR with off-period postings listed apart and unconverted. team/department/query/limit semantics are left entirely to the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

Names a specific verb (compare, 'put side by side') and a specific resource (pay ranges on one Ashby board), with the scoping qualifiers 'one component at a time and one period at a time'. It is clearly distinguishable from search/lookup siblings, though it never names them explicitly.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The use case (comparing published pay across postings on a single board) is implied by the purpose statement, and the 'one component / one period at a time' constraint implicitly steers how to call it. However, there is no explicit 'use this when…' framing, no exclusion rules, and no routing to search_jobs or get_job.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_jobRead one postingA
Read-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.

NameRequiredDescriptionDefault
boardYesA company name, or an Ashby board token.
job_idYesThe identifier search_jobs returned for this posting.
descriptionNoThe description runs to thousands of characters. html is the company's own markup, unrewritten.plain
include_compensationNo

Output Schema

NameRequiredDescription
idYes
teamYes
boardYes
notesYes
titleYes
sourceYes
job_urlYes
locationYes
apply_urlYes
is_listedYesFalse marks a posting Ashby serves by direct link alone rather than on the board.
is_remoteYes
departmentYes
descriptionYesNull when the caller asked for no description.
compensationYesNull when the caller asked for no compensation, which states nothing about the company.
published_atYesISO 8601 with the offset Ashby publishes.
workplace_typeYesNull when the company records none, which is never the same as on site.
employment_typeYes
secondary_locationsYes
compensation_summaryYesNull when the company withholds its pay ranges, which is never zero.

TDQS

A4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint, destructiveHint=false, and openWorldHint, so the safety profile is covered. The description adds that the response is the complete posting including compensation, which is modest extra context but says nothing about payload size, error behavior, or missing/unpublished compensation cases. Adequate but not rich beyond the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, no filler. The scope of returned content is front-loaded before the addressing requirement, matching how an agent reads: what it gives me, then what it needs.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present, return values need not be explained, and the annotations cover safety. The description covers purpose, source of identifiers, and payload scope. The only gap is the include_compensation parameter, which neither the schema nor the description explains.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 75%, so the schema does most of the work, including documenting job_id's provenance. The description reinforces the addressing model (board plus identifier from search_jobs) but adds no detail on the description format or include_compensation flag, which remain undocumented in the schema. Marginal value over structured fields.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ("Read one Ashby posting in full") and enumerates the payload it returns: locations, description, and published pay ranges. It also implicitly contrasts with the discovery sibling by naming search_jobs as the source of addressing values, so an agent can tell it apart from a search tool.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The second sentence establishes the retrieval workflow: you need board and identifier, and search_jobs supplies both. That is clear guidance on when this tool is the right call (single-posting fetch after a search). It does not name explicit exclusions such as when to prefer compare_compensation, so it falls short of a 5.

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 wordingsA
Read-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.

NameRequiredDescriptionDefault
boardYesA company name, or an Ashby board token.
facetNoall

Output Schema

NameRequiredDescription
boardYes
notesYes
facetsYes
total_jobsYes
undeclaredYesPostings a criterion could say nothing about, counted per field. A posting that records no workplace is not a posting on site.
sibling_spellingsYesSpellings on this board that name what looks like one place, so a caller asks for both rather than picking one.

TDQS

A4.1/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters2/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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 boardsA
Read-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.

NameRequiredDescriptionDefault
nameYesA company name, or an Ashby board token you already know.

Output Schema

NameRequiredDescription
foundYes
inputYes
notesYes
triedYesThe forms actually sent, in order.

TDQS

A4.5/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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 companiesA
Read-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.

NameRequiredDescriptionDefault
sortNopublished_desc
teamNo
limitNoHow many postings to return.
queryNoWords to look for.
offsetNoHow many postings to skip.
countryNo
currencyNoA three-letter currency code, as in EUR or USD.
companiesYesCompany 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_remoteNoKeeps only postings that declare it. Postings recording nothing are counted in undeclared rather than treated as on site.
search_inNoWhere query is looked for. Descriptions run to thousands of characters each.title
departmentNo
salary_minNoA floor for the salary component.
workplace_typeNo
employment_typeNo
published_afterNoAn ISO 8601 date.
salary_intervalNoThe period salary_min belongs to, 1 YEAR unless named. An hourly amount is never weighed against a yearly floor.1 YEAR
has_compensationNoKeeps only postings whose company publishes a pay range.
location_containsNoPart of a location line.

Output Schema

NameRequiredDescription
jobsYesNo descriptions, at any limit.
notesYes
returnedYesPostings in this answer, after limit and offset.
undeclaredYesPostings a criterion could say nothing about, counted per field. A posting that records no workplace is not a posting on site.
per_companyYes
total_matchedYesPostings the criteria kept.
total_on_boardYesPostings the boards read hold, all of them.
filters_appliedYes

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover the safety profile (readOnly, idempotent, non-destructive, open world), and the description adds behavior the annotations cannot: filters are evaluated locally rather than at the source, the response counts postings that declared nothing for a filtered field, and result rows omit descriptions. Those are exactly the non-obvious traits that affect how an agent interprets results.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Four short sentences, front-loaded with the core action and the required-input constraint, then the filter/undeclared caveat, then the hand-off to get_job. Nothing is repeated from the schema or annotations and no sentence is filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present and 18 documented parameters, the description covers the remaining gaps an agent needs: required input, local filtering semantics, null accounting, and where to go for full text. Cost/latency of multi-company searches and pagination behavior are left to the schema, which is acceptable but slightly reduces completeness for an open-world multi-request tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 67% and many parameters already carry useful notes (undeclared handling for is_remote, board-cost for companies, salary interval matching), so the schema does most of the work. The description adds the general rule that filtering happens locally and that missing values are counted rather than excluded, but it does not explain specific parameters such as search_in, has_compensation, or the sort options.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

"Search the open postings of named companies on Ashby" gives a specific verb and resource, and the sentence about rows carrying no description explicitly separates this tool from get_job. The scope constraint (companies required, no cross-customer index) further pins down what this tool is and is not.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It states the mandatory precondition (companies required) and routes the agent to get_job when a full description is needed, which is a genuine use-vs-use-else split. It does not mention the other relevant siblings (resolve_board for board tokens, list_filter_values for valid filter values), so a couple of routing decisions are left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

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

  1. 3 tool updatesv2.1.0
    • Changedcompare_compensation8 fields changed
      • removedOutput schema / properties / rows / items / properties / currency / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / rows / items / properties / currency / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / rows / items / properties / max / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / rows / items / properties / max / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / properties / rows / items / properties / min / anyOf
        Removed value: -[
        -  {
        -    "type": "number"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / rows / items / properties / min / type
        Added value: +[
        +  "number",
        +  "null"
        +]
      • removedOutput schema / properties / rows / items / properties / tier_title / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / rows / items / properties / tier_title / type
        Added value: +[
        +  "string",
        +  "null"
        +]
    • Changedget_job19 fields changed
      • changedOutput schema / properties / compensation / anyOf
        Previous value: -[
        -  {
        -    "anyOf": [
        -      {
        -        "additionalProperties": false,
        -        "properties": {
        -          "published": {
        -            "const": true,
        -            "type": "boolean"
        -          },
        -          "summary": {
        -            "anyOf": [
        -              {
        -                "type": "string"
        -              },
        -              {
        -                "type": "null"
        -              }
        -            ]
        -          },
        -          "tiers": {
        -            "items": {
        -              "additionalProperties": false,
        -              "properties": {
        -                "additional_information": {
        -                  "anyOf": [
        -                    {
        -                      "type": "string"
        -                    },
        -                    {
        -                      "type": "null"
        -                    }
        -                  ]
        -                },
        -                "components": {
        -                  "items": {
        -                    "additionalProperties": false,
        -                    "properties": {
        -                      "currency": {
        -                        "anyOf": [
        -                          {
        -                            "type": "string"
        -                          },
        -                          {
        -                            "type": "null"
        -                          }
        -                        ],
        -                        "description": "Null on a percentage of equity, which has no currency. Never converted."
        -                      },
        -                      "interval": {
        -                        "description": "The period the amounts belong to, as Ashby writes it: 1 YEAR, 1 MONTH, 1 HOUR, NONE. Never annualised.",
        -                        "type": "string"
        -                      },
        -                      "max": {
        -                        "anyOf": [
        -                          {
        -                            "type": "number"
        -                          },
        -                          {
        -                            "type": "null"
        -                          }
        -                        ]
        -                      },
        -                      "min": {
        -                        "anyOf": [
        -                          {
        -                            "type": "number"
        -                          },
        -                          {
        -                            "type": "null"
        -                          }
        -                        ],
        -                        "description": "Null when the company promises the component without naming an amount."
        -                      },
        -                      "summary": {
        -                        "description": "The line the company wrote for this component.",
        -                        "type": "string"
        -                      },
        -                      "type": {
        -                        "description": "Salary, EquityCashValue, EquityPercentage, Commission, Bonus, or a value beyond them.",
        -                        "type": "string"
        -                      }
        -                    },
        -                    "required": [
        -                      "type",
        -                      "min",
        -                      "max",
        -                      "currency",
        -                      "interval",
        -                      "summary"
        -                    ],
        -                    "type": "object"
        -                  },
        -                  "type": "array"
        -                },
        -                "summary": {
        -                  "type": "string"
        -                },
        -                "title": {
        -                  "anyOf": [
        -                    {
        -                      "type": "string"
        -                    },
        -                    {
        -                      "type": "null"
        -                    }
        -                  ],
        -                  "description": "Names a zone or a level, and is null on most tiers."
        -                }
        -              },
        -              "required": [
        -                "title",
        -                "summary",
        -                "additional_information",
        -                "components"
        -              ],
        -              "type": "object"
        -            },
        -            "type": "array"
        -          }
        -        },
        -        "required": [
        -          "published",
        -          "summary",
        -          "tiers"
        -        ],
        -        "type": "object"
        -      },
        -      {
        -        "additionalProperties": false,
        -        "properties": {
        -          "published": {
        -            "const": false,
        -            "type": "boolean"
        -          },
        -          "summary": {
        -            "type": "null"
        -          },
        -          "tiers": {
        -            "items": [],
        -            "type": "array"
        -          }
        -        },
        -        "required": [
        -          "published",
        -          "summary",
        -          "tiers"
        -        ],
        -        "type": "object"
        -      }
        -    ]
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "anyOf": [
        +      {
        +        "additionalProperties": false,
        +        "properties": {
        +          "published": {
        +            "const": true,
        +            "type": "boolean"
        +          },
        +          "summary": {
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "tiers": {
        +            "items": {
        +              "additionalProperties": false,
        +              "properties": {
        +                "additional_information": {
        +                  "type": [
        +                    "string",
        +                    "null"
        +                  ]
        +                },
        +                "components": {
        +                  "items": {
        +                    "additionalProperties": false,
        +                    "properties": {
        +                      "currency": {
        +                        "description": "Null on a percentage of equity, which has no currency. Never converted.",
        +                        "type": [
        +                          "string",
        +                          "null"
        +                        ]
        +                      },
        +                      "interval": {
        +                        "description": "The period the amounts belong to, as Ashby writes it: 1 YEAR, 1 MONTH, 1 HOUR, NONE. Never annualised.",
        +                        "type": "string"
        +                      },
        +                      "max": {
        +                        "type": [
        +                          "number",
        +                          "null"
        +                        ]
        +                      },
        +                      "min": {
        +                        "description": "Null when the company promises the component without naming an amount.",
        +                        "type": [
        +                          "number",
        +                          "null"
        +                        ]
        +                      },
        +                      "summary": {
        +                        "description": "The line the company wrote for this component.",
        +                        "type": "string"
        +                      },
        +                      "type": {
        +                        "description": "Salary, EquityCashValue, EquityPercentage, Commission, Bonus, or a value beyond them.",
        +                        "type": "string"
        +                      }
        +                    },
        +                    "required": [
        +                      "type",
        +                      "min",
        +                      "max",
        +                      "currency",
        +                      "interval",
        +                      "summary"
        +                    ],
        +                    "type": "object"
        +                  },
        +                  "type": "array"
        +                },
        +                "summary": {
        +                  "type": "string"
        +                },
        +                "title": {
        +                  "description": "Names a zone or a level, and is null on most tiers.",
        +                  "type": [
        +                    "string",
        +                    "null"
        +                  ]
        +                }
        +              },
        +              "required": [
        +                "title",
        +                "summary",
        +                "additional_information",
        +                "components"
        +              ],
        +              "type": "object"
        +            },
        +            "type": "array"
        +          }
        +        },
        +        "required": [
        +          "published",
        +          "summary",
        +          "tiers"
        +        ],
        +        "type": "object"
        +      },
        +      {
        +        "additionalProperties": false,
        +        "properties": {
        +          "published": {
        +            "const": false,
        +            "type": "boolean"
        +          },
        +          "summary": {
        +            "type": "null"
        +          },
        +          "tiers": {
        +            "additionalItems": false,
        +            "items": [],
        +            "maxItems": 0,
        +            "type": "array"
        +          }
        +        },
        +        "required": [
        +          "published",
        +          "summary",
        +          "tiers"
        +        ],
        +        "type": "object"
        +      }
        +    ]
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • removedOutput schema / properties / compensation_summary / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / compensation_summary / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / is_remote / anyOf
        Removed value: -[
        -  {
        -    "type": "boolean"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / is_remote / type
        Added value: +[
        +  "boolean",
        +  "null"
        +]
      • removedOutput schema / properties / location / properties / country / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / location / properties / country / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / location / properties / locality / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / location / properties / locality / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / location / properties / region / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / location / properties / region / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / secondary_locations / items / properties / country / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / secondary_locations / items / properties / country / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / secondary_locations / items / properties / locality / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / secondary_locations / items / properties / locality / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / secondary_locations / items / properties / region / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / secondary_locations / items / properties / region / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / workplace_type / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / workplace_type / type
        Added value: +[
        +  "string",
        +  "null"
        +]
    • Changedsearch_jobs10 fields changed
      • removedOutput schema / properties / jobs / items / properties / compensation_summary / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / jobs / items / properties / compensation_summary / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / jobs / items / properties / country / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / jobs / items / properties / country / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / jobs / items / properties / is_remote / anyOf
        Removed value: -[
        -  {
        -    "type": "boolean"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / jobs / items / properties / is_remote / type
        Added value: +[
        +  "boolean",
        +  "null"
        +]
      • removedOutput schema / properties / jobs / items / properties / workplace_type / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / jobs / items / properties / workplace_type / type
        Added value: +[
        +  "string",
        +  "null"
        +]
      • removedOutput schema / properties / per_company / items / properties / board / anyOf
        Removed value: -[
        -  {
        -    "type": "string"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]
      • addedOutput schema / properties / per_company / items / properties / board / type
        Added value: +[
        +  "string",
        +  "null"
        +]
  2. 5 tool updatesv1.0.0
    • First observedcompare_compensation
    • First observedget_job
    • First observedlist_filter_values
    • First observedresolve_board
    • First observedsearch_jobs

TDQS

A4.2/5.0

Scored across 5 tools

Disambiguation4/5

Each tool has a mostly distinct role: search, resolve board, read one posting, compare compensation, and list filter vocabulary. The main overlap is resolve_board versus search_jobs' internal board resolution, but the description explicitly frames resolve_board as a diagnostic fallback when a name fails. get_job and compare_compensation also both involve pay data, though one is per-posting detail and the other is board-level comparison.

Naming Consistency5/5

All five tools follow a clean snake_case verb_noun pattern: search_jobs, resolve_board, get_job, compare_compensation, list_filter_values. There are no mixed conventions or vague standalone verbs.

Tool Count5/5

Five tools is well-scoped for an Ashby job-search server, with each tool covering a clear part of the workflow. Search, board resolution, detail retrieval, compensation comparison, and filter vocabulary all earn their place without redundancy.

Completeness4/5

The surface covers the core read-only job research workflow: finding postings, resolving company boards, reading full postings, comparing compensation, and discovering valid filters. Minor gaps remain around direct posting lookup by URL or broader company discovery, but Ashby's lack of a cross-customer index makes the current shape reasonable.

Maintenance

ActivityActive
ResponsivenessUnresponsive

Related MCP Connectors

Related MCP Servers

  • F
    license
    A
    quality
    C
    maintenance
    Search and browse any company's public Ashby job board from Claude Code.
    9
    -
  • A
    license
    A
    quality
    B
    maintenance
    Enables 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.
    3
    1
    MIT
  • A
    license
    A
    quality
    C
    maintenance
    Enables real-time job search across thousands of companies' open roles from Greenhouse, Lever, Ashby, and SmartRecruiters, with full-text filtering and company-specific queries, no API key required.
    2
    MIT
  • A
    license
    A
    quality
    C
    maintenance
    Enables querying job boards for a company's open roles, pulling from Greenhouse, Workday, and an opt-in LinkedIn source and normalizing the results into one shape. It discovers boards, searches one or many companies at once, and returns full postings with title-based AI/ML and junior filtering.
    7
    MIT