Headcount Labs
by headcountlabs.com · remote · no sign-in to list tools · v1.2.0 · published by com.headcountlabs
Job postings search: find, count and read open jobs from employer career sites, with pay and links.
9 tools: 2 change things and 7 only read.
Scanned Oct 21, 2026
- Deletes or cancels0Claude Code: deny
- Can't be undone0Claude Code: ask
- Changes things2Claude Code: ask: see these tools
- Only reads7Claude Code: allow: see these tools
6 checks matched something in the tool definitions. No finding is not a safety guarantee. How the checks work.
claude mcp add --transport http jobs https://api.headcountlabs.com/mcp/publicFindings
Matches of fixed checks, with the text they matched.
- Warnsearch_jobs
The input schema at properties.filters.properties.title_advanced.anyOf[0].$ref contains a 40-character base64 string.
What this check looks forMatched text
inputSchema.properties.filters.properties.title_advanced.anyOf[0].$ref: "#/properties/filters/properties/q/anyOf/0"
- Warnsearch_jobs
The input schema at properties.filters.properties.title_advanced.anyOf[1].$ref contains a 40-character base64 string.
What this check looks forMatched text
inputSchema.properties.filters.properties.title_advanced.anyOf[1].$ref: "#/properties/filters/properties/q/anyOf/1"
- Warnsearch_jobs
The input schema at properties.filters.properties.title_advanced.anyOf[2].$ref contains a 40-character base64 string.
What this check looks forMatched text
inputSchema.properties.filters.properties.title_advanced.anyOf[2].$ref: "#/properties/filters/properties/q/anyOf/2"
- Warncount_jobs
The input schema at properties.filters.properties.title_advanced.anyOf[0].$ref contains a 40-character base64 string.
What this check looks forMatched text
inputSchema.properties.filters.properties.title_advanced.anyOf[0].$ref: "#/properties/filters/properties/q/anyOf/0"
- Warncount_jobs
The input schema at properties.filters.properties.title_advanced.anyOf[1].$ref contains a 40-character base64 string.
What this check looks forMatched text
inputSchema.properties.filters.properties.title_advanced.anyOf[1].$ref: "#/properties/filters/properties/q/anyOf/1"
- Warncount_jobs
The input schema at properties.filters.properties.title_advanced.anyOf[2].$ref contains a 40-character base64 string.
What this check looks forMatched text
inputSchema.properties.filters.properties.title_advanced.anyOf[2].$ref: "#/properties/filters/properties/q/anyOf/2"
Tools
Open a tool for its reasons and full description. Descriptions are the publisher's own words, shown as they are.
9 tools
Changes things 2Claude Code: ask
market_breakdownRank the values of one dimension (by: title, state, metro, city, company, seniority, employment_type or remote) by jobs, pay or another figure, over all jobs or inside one filter, …No findings
Why it is in this group
- Nothing in its name or description says what it does, so it is counted as a write.
- The server marks it read-only (readOnlyHint: true), but an annotation never lowers a class.
Description, as the server wrote it Rank the values of one dimension (by: title, state, metro, city, company, seniority, employment_type or remote) by jobs, pay or another figure, over all jobs or inside one filter, such as which states pay Software Engineers the most (by=state, title=Software Engineer, sort=pay_median). Answers come from a daily snapshot of open US jobs and give their as_of date; for current job counts or the employers hiring right now, count_jobs is the live source. Cells with fewer than 30 jobs are left out. No account needed.
Annotations the server declares
- readOnlyHint: true
- destructiveHint: false
- idempotentHint: true
- openWorldHint: false
salary_rangeWhat salary to ask for: for a title, a place (default US) and an optional seniority, the 25th, 50th, 75th and 90th percentile of posted pay, the sample size and a plain recommendat…No findings
Why it is in this group
- Nothing in its name or description says what it does, so it is counted as a write.
- The server marks it read-only (readOnlyHint: true), but an annotation never lowers a class.
Description, as the server wrote it What salary to ask for: for a title, a place (default US) and an optional seniority, the 25th, 50th, 75th and 90th percentile of posted pay, the sample size and a plain recommendation. Answers come from a daily snapshot of open US jobs and give their as_of date; for current job counts or the employers hiring right now, count_jobs is the live source. Cells with fewer than 30 jobs are left out. No account needed.
Annotations the server declares
- readOnlyHint: true
- destructiveHint: false
- idempotentHint: true
- openWorldHint: false
Only reads 7Claude Code: allow
count_jobsCount the current jobs matching the filters, without listing them.3 findings
Why it is in this group
- The name "count_jobs" says "count": a word for reading.
Findings
- Warn
The input schema at properties.filters.properties.title_advanced.anyOf[0].$ref contains a 40-character base64 string.
What this check looks forMatched text
inputSchema.properties.filters.properties.title_advanced.anyOf[0].$ref: "#/properties/filters/properties/q/anyOf/0"
- Warn
The input schema at properties.filters.properties.title_advanced.anyOf[1].$ref contains a 40-character base64 string.
What this check looks forMatched text
inputSchema.properties.filters.properties.title_advanced.anyOf[1].$ref: "#/properties/filters/properties/q/anyOf/1"
- Warn
The input schema at properties.filters.properties.title_advanced.anyOf[2].$ref contains a 40-character base64 string.
What this check looks forMatched text
inputSchema.properties.filters.properties.title_advanced.anyOf[2].$ref: "#/properties/filters/properties/q/anyOf/2"
Description, as the server wrote it Count the current jobs matching the filters, without listing them. This is the live count: it reads the job database as it is now, so use it for "how many jobs" and "which employers are hiring" questions, and prefer its numbers over market_lookup and market_breakdown, which read a daily snapshot. No account needed. To answer "which companies are hiring for X" or "how are these jobs split by ...", pass group_by: employer (company works too), work_arrangement (remote, hybrid or in_person), title, state (US), city (metro works too), country or employment_type. The answer then also has group_by, groups (the top 25 values by number of matching jobs, each with value and jobs), groups_from_jobs (how many matching jobs were grouped, at most 1,000, taken in the default list order), groups_sampled (true when more jobs matched than were grouped, so the counts are from a sample) and groups_unknown_jobs (grouped jobs with no known value). A job with several locations or postings can count under more than one value. group_by employer keeps its own keys: employers (each with employer_name, company_id and jobs), employers_from_jobs, employers_sampled and employers_unknown_jobs. When a filter is too broad to count exactly in time, count is an estimate: the answer then has count_is_estimate: true, count_margin (the 95% range either side of count) and count_note.
Annotations the server declares
- readOnlyHint: true
- destructiveHint: false
- idempotentHint: true
- openWorldHint: false
get_jobGet a job by its job_id.No findings
Why it is in this group
- The name "get_job" says "get": a word for reading.
Description, as the server wrote it Get a job by its job_id. No account needed. By default the answer has the same compact shape search_jobs returns (rank 1, job_id, job_ad, company_id, locations, work_arrangement, pay, posted_at, first_seen_at, posting_url, apply_url) with the whole description in job_ad.description, currentness, stale and last_checked_at (is it still open, and when was it last checked), and every linked posting in postings (status, last_fetched_at, application_check). Pass view: "full" for every field as the REST API returns it. Use fields to select top-level job properties; job_id is always included.
Annotations the server declares
- readOnlyHint: true
- destructiveHint: false
- idempotentHint: true
- openWorldHint: false
list_fieldsFind the search_jobs filters and the job fields that match some keywords, with each one's type and a short description.No findings
Why it is in this group
- The name "list_fields" says "list": a word for reading.
Description, as the server wrote it Find the search_jobs filters and the job fields that match some keywords, with each one's type and a short description. Pass words such as ["pay", "remote"]; no keywords lists everything. kind is filter (pass it in search_jobs filters) or field (a property of each returned job; postings[].x is a property of each linked posting). A field state is an object whose state says whether the source published the value (known), Atlas derived it, the source omitted it, or extraction failed.
Annotations the server declares
- readOnlyHint: true
- destructiveHint: false
- idempotentHint: true
- openWorldHint: false
market_dimensionsList what market_lookup, market_breakdown and salary_range accept: the dimensions, how many values each has and the metrics; with dimension (and optionally q) the values of that di…No findings
Why it is in this group
- The description's first verb is "list": a word for reading.
Description, as the server wrote it List what market_lookup, market_breakdown and salary_range accept: the dimensions, how many values each has and the metrics; with dimension (and optionally q) the values of that dimension, such as the metro names. No account needed.
Annotations the server declares
- readOnlyHint: true
- destructiveHint: false
- idempotentHint: true
- openWorldHint: false
market_lookupJob count, pay percentiles, share of jobs showing pay, remote/hybrid/in-person shares, posting age, new postings or top employers for up to two of title, place, company, seniority,…No findings
Why it is in this group
- The name "market_lookup" says "lookup": a word for reading.
Description, as the server wrote it Job count, pay percentiles, share of jobs showing pay, remote/hybrid/in-person shares, posting age, new postings or top employers for up to two of title, place, company, seniority, employment_type and remote, such as the pay range for Registered Nurses in Texas. Leave metric out for every figure. Answers come from a daily snapshot of open US jobs and give their as_of date; for current job counts or the employers hiring right now, count_jobs is the live source. Cells with fewer than 30 jobs are left out. No account needed.
Annotations the server declares
- readOnlyHint: true
- destructiveHint: false
- idempotentHint: true
- openWorldHint: false
search_docsAsk a question about Headcount Labs (filters, limits, MCP setup, errors) and get the best-matching sections of the headcountlabs.com docs, each with its URL.Reads private dataReads text others writeNo findings
Why it is in this group
- The name "search_docs" says "search": a word for reading.
What it can reach
- private-dataThe name "search_docs" says "search" and "doc": it reads documents.
- private-dataThe description's first sentence says "doc": it reads documents.
- untrusted-inputThe description's first sentence says "url": it reads web pages, which anyone can write.
Description, as the server wrote it Ask a question about Headcount Labs (filters, limits, MCP setup, errors) and get the best-matching sections of the headcountlabs.com docs, each with its URL. Works without an account.
Annotations the server declares
- readOnlyHint: true
- destructiveHint: false
- idempotentHint: true
- openWorldHint: false
search_jobsSearch current job postings by keyword, title, company, place, pay, date and more.3 findings
Why it is in this group
- The name "search_jobs" says "search": a word for reading.
Findings
- Warn
The input schema at properties.filters.properties.title_advanced.anyOf[0].$ref contains a 40-character base64 string.
What this check looks forMatched text
inputSchema.properties.filters.properties.title_advanced.anyOf[0].$ref: "#/properties/filters/properties/q/anyOf/0"
- Warn
The input schema at properties.filters.properties.title_advanced.anyOf[1].$ref contains a 40-character base64 string.
What this check looks forMatched text
inputSchema.properties.filters.properties.title_advanced.anyOf[1].$ref: "#/properties/filters/properties/q/anyOf/1"
- Warn
The input schema at properties.filters.properties.title_advanced.anyOf[2].$ref contains a 40-character base64 string.
What this check looks forMatched text
inputSchema.properties.filters.properties.title_advanced.anyOf[2].$ref: "#/properties/filters/properties/q/anyOf/2"
Description, as the server wrote it Search current job postings by keyword, title, company, place, pay, date and more. No account needed. Call list_fields to find the filter names. Country and employment_type accept comma-separated values or arrays and match any listed value. Pages return up to 50 jobs (limit, default 10), or up to 10 jobs when descriptions is "full"; a larger limit is refused. Pass next_cursor as cursor to see the next page. One search shows up to 250 jobs in total (the last page is shortened to fit), so narrow it with filters to find the right ones; count_jobs tells you how many jobs match without listing them. sort is relevance (best match) or updated (most recently updated). By default each job is a compact summary: rank, job_id, job_ad (the employer-written title, employer and a 200-character snippet of the description), company_id, locations, work_arrangement, pay (min, max, currency, period), posted_at, first_seen_at, posting_url and apply_url. For every field of each job pass view: "full" (descriptions cut to 400 characters, or descriptions: "full" for the whole text), name the top-level properties you need in fields (job_id is always included), or call get_job for one job. You choose how much description each job gets: descriptions: "snippet" (the default) gives a short preview and allows up to 50 jobs per page; descriptions: "full" gives the whole text but allows only 10 jobs per page. To read the full text of just a few jobs, search with snippets, then call get_job on the ones you want. Leave description out of fields to drop it entirely. For job titles, title matches the words as one phrase in that order, so "senior software engineer" misses "Software Engineer, Senior"; title_advanced matches words in any order with AND, OR and NOT, for example senior AND ("software engineer" OR "software developer"), and usually finds more.
Annotations the server declares
- readOnlyHint: true
- destructiveHint: false
- idempotentHint: true
- openWorldHint: false
This is not a safety rating, and no finding is not a safety guarantee. The groups and findings come from fixed checks of the tool definitions the server returned on the date shown, not from its code or what it does when called. How the checks work.
Exfil surface
Which tools read private data, which read text someone else can write, and which can send data out. One of each is a path a prompt injection can use.
No private-data, untrusted-input, outbound path in this server alone: no tool is tagged outbound.
- Reads private data1
- Reads files, mail, messages, databases, secrets, calendars or repositories.
- Reads text others write1
- Brings in text someone else can write: web pages, inbound email, messages, issues and comments.
- Can send data out0
- Can carry data to people or systems: it sends, posts or publishes, or takes a recipient, channel or URL.
Other servers in the same agent add their own tools to these paths. Audit your whole setup to see them together.
Remote posture
What anyone on the internet sees at the remote endpoint: an anonymous initialize and tools/list, and the public OAuth metadata.
Looked at Oct 22, 2026, 14:33 UTC, unchanged since Oct 22, 2026. No credentials were sent and no tool was called. What is looked at.
- Reachable
- yes
- HTTP status of an anonymous initialize
- 200
- tools/list served without credentials
- yes
- Tools that change things, listed without credentials
- 2
- Protocol version
- 2025-06-18
- WWW-Authenticate on 401
- not observed
- resource_metadata in WWW-Authenticate
- none
- RFC 9728 protected resource metadata
- none
- Authorization servers
- none
- Legacy HTTP+SSE transport
- no
- Dynamic client registration
- not observed
- Client ID metadata documents
- not observed
- WarnThe whole server
The endpoint served tools/list without credentials, and the list has 2 tools that change things (2 write). Whether a call also works without credentials was not tested: tools are never called.
What this check looks forMatched text
https://api.headcountlabs.com/mcp/public: tools/list returned 9 tools to an anonymous client, including market_breakdown (write), salary_range (write)
Install
Pinned to version 1.2.0. Values in angle brackets are yours to fill in.
claude mcp add --transport http jobs https://api.headcountlabs.com/mcp/publicRun in a terminal.
Claude Code permissions
Allow the reads, ask before a change or a way out, deny the deletes. Paste into .claude/settings.json.
{
"permissions": {
"allow": [
"mcp__jobs__search_docs",
"mcp__jobs__search_jobs",
"mcp__jobs__get_job",
"mcp__jobs__count_jobs",
"mcp__jobs__market_lookup",
"mcp__jobs__market_dimensions",
"mcp__jobs__list_fields"
],
"ask": [
"mcp__jobs__market_breakdown",
"mcp__jobs__salary_range"
],
"deny": []
}
}Tools that only read are allowed, tools that change things ask first, tools that delete or cancel are denied. A read that can send data out (it takes a URL, say) asks first too. It assumes the server is installed as jobs. Review it before you use it.
Capability tags behind 1 rule
- mcp__jobs__search_docs private-data, untrusted-input