job-search-mcp
Enables AI assistants to evaluate job postings under a UK visa constraint, including sponsor-licence checks with confidence grades, screening for seniority and hard requirements, and role searches that exclude recruitment agencies. It also tracks application history to help avoid duplicate applications.
README
job-search-mcp
An MCP server for job hunting under a visa constraint.
If you need sponsorship, most job search tooling answers the wrong question. It tells you a role exists. It does not tell you whether the employer can legally hire you, or whether the "mid-level" title is hiding a senior brief, or that the company posting the role is a recruitment agency that holds no licence at all.
This server answers those questions. It exposes four tools over the Model Context Protocol so an AI assistant can check them itself instead of guessing.
Tools
| Tool | Answers |
|---|---|
check_sponsor |
Does this employer hold a UK Skilled Worker licence, and how much can I trust the match? |
screen_posting |
Are there hard bars here? Is the title telling the truth about seniority? |
search_roles |
What is live right now, minus the agencies? |
application_history |
Have I already applied here? |
Why confidence grading exists
The obvious way to check a sponsor licence is to search the register for the company name. That is how this started, and it is wrong often enough to be dangerous.
Company names collide. To match Pimberly against its registered name Pimberly Software Development Limited, you have to strip words like "Software" and "Limited". That same normalisation turns Minerva Defence into minerva, which matches thirteen unrelated companies: a furnishing business, a credit agency, a supported-living provider.
So check_sponsor returns a confidence grade rather than a boolean:
high— the distinctive part of the name matched exactly, once.verify— something matched, but it could be coincidence. Confirm the employer's registered legal entity, then look that name up.none— nothing matched. This is not proof they cannot sponsor. Many licensed employers trade under a name unlike their registered one.
Real cases that shaped this:
| Searched | Matched | What was true |
|---|---|---|
| Minerva Defence | 13 unrelated "Minerva" rows | MINERVA DEFENCE LTD is not on the register, nor its former name PARABELLUM TECHNOLOGIES LTD |
| Prevail | Prevail Technology Limited (Poole) | The employer is Prevail Partners Ltd, which is absent. Same town, different company |
| MAGIC | MAGIC SOFTWARE SERVICES LTD | Right answer, wrong evidence. The employer is MAGIC TECH LTD, also licensed |
| TransPerfect | PERFECT DIGITAL LTD | Matched on the word "perfect" |
| eFinancialCareers | eFinancialCareers Ltd | Correct and useless: licensed, but a job board. Roles under its name belong to unnamed third parties |
Why screen_posting reads the body, not the title
A posting titled "Software Engineer" opened with "As a Senior Software Engineer you will" and asked for someone to join "the founding team". Another titled "Software Engineer (Java Mid)" named a senior grade three paragraphs down. Title-based filtering misses both.
screen_posting also catches hard bars that no amount of tailoring survives:
You must be a UK citizen and have lived in the UK for the past 10 years.
You must already hold high-level UK security clearance.
and the one that matters most when you need sponsorship, from a company that holds an A-rated licence:
We are unable to offer visa sponsorship for this role. Candidates who need visa
sponsorship now or will need it in the future will not be considered.
A licence means a company can sponsor. It does not mean it will.
Why search_roles returns a funnel
UK job boards are dominated by recruitment agencies, and an agency does not hold the sponsor licence for a role it advertises. Filtering them out is essential and brutal: a typical search drops from seven results to one.
One result with no explanation looks like a bug. So the tool returns what each stage removed:
{
"totalFromSource": 7,
"funnel": { "fromSource": 7, "afterAgencyFilter": 2, "afterSalaryAndAge": 1 },
"notes": ["5 of 7 results were recruitment agencies or job boards. ..."]
}
Unpublished salaries are kept, never filtered out. Silence about pay is not evidence of low pay.
Install
npm install && npm run build
Download the current register (about 11 MB, updated regularly):
https://www.gov.uk/government/publications/register-of-licensed-sponsors-workers
Save it as data/register.csv, or point SPONSOR_REGISTER_PATH at it.
Claude Desktop / Claude Code
{
"mcpServers": {
"job-search": {
"command": "node",
"args": ["/absolute/path/to/job-search-mcp/dist/index.js"],
"env": {
"SPONSOR_REGISTER_PATH": "/absolute/path/to/data/register.csv",
"REED_API_KEY_FILE": "/absolute/path/to/.reed-api-key"
}
}
}
}
| Variable | Required | Purpose |
|---|---|---|
SPONSOR_REGISTER_PATH |
no | Register CSV. Defaults to ./data/register.csv |
REED_API_KEY_FILE |
for search_roles |
Path to a file containing the key. Preferred: the secret lives in one place and the client config holds only a path |
REED_API_KEY |
alternative | The key inline. Simpler, but copies the secret into your MCP config |
APPLICATION_LEDGER_PATH |
no | NDJSON application history |
No credential is ever read from a file inside the repo, and the register is gitignored.
Or with the Claude Code CLI:
claude mcp add job-search --scope user \
--env SPONSOR_REGISTER_PATH=/path/to/data/register.csv \
--env REED_API_KEY_FILE=/path/to/.reed-api-key \
-- node /path/to/job-search-mcp/dist/index.js
Tests
npm test
54 tests. Every fixture is a real posting or a real register entry that defeated an
earlier version of this code. The Reed tests mock fetch, so the suite runs offline
and costs no API quota.
Two bugs the suite caught while it was being written:
£40,000-85,000parsed as a flat £40,000, because the second figure omits the currency symbol. Against a salary threshold, that is the difference between "clears it" and "does not".- An agency filter written as
\brecruit\bnever matched Recruitment or Consultancy. Roughly fifty agencies passed straight through.
Design notes
core() deliberately destroys information. Stripping descriptors is what makes
brand-to-legal-name matching work, and it is exactly what causes false positives. The
confidence grade prices that trade-off instead of hiding it.
Absence is not a negative. none carries a caveat saying so. The costliest error in
this domain is concluding that an unlisted employer cannot sponsor.
Read-only. The server reads the application ledger; it never writes to it. Recording an outcome is a decision a person should make.
Licence
MIT
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。