Content
# SponsorFinder MCP Server
An authless, read-only remote [MCP](https://modelcontextprotocol.io) server that lets AI assistants (Claude, ChatGPT, Cursor, …) check whether a company holds a **UK** or **Netherlands** work-visa sponsorship licence. It proxies the public [SponsorFinder](https://sponsorfinder.io) API, shaping responses into clean verdicts, and keeps the upstream API key server-side so clients connect with no credentials.
[](./LICENSE)
[](https://nodejs.org)
[](https://modelcontextprotocol.io)
## What it does
SponsorFinder tracks two official government registers of licensed work-visa sponsors:
- **UK** — the [Home Office register of licensed sponsors](https://www.gov.uk/government/publications/register-of-licensed-sponsors-workers), rebuilt from the CSVs the Home Office publishes (checked daily). Carries routes (e.g. Skilled Worker), ratings (A/B) and locations.
- **Netherlands** — the [IND public register of recognised sponsors](https://ind.nl/en/public-register-recognised-sponsors), checked daily. Lists recognised sponsors and their sponsor type (WORK / EXCHANGE / STUDY / RESEARCH); much thinner than the UK data — no routes, ratings, or locations.
This MCP server exposes that data as four tools. It is **read-only** and **authless for clients**: the upstream `x-api-key` is a server-side secret that MCP clients never see. There is no OAuth, no per-user state, and no write tools.
## Tools
| Tool | Title | What it does | Key inputs |
| ----------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `check_sponsor_license` | Check Sponsorship Licence | Primary tool. Resolves a company by name (typo-tolerant) and returns a verdict — `licensed`, `formerly_licensed`, `ambiguous`, or `not_found` — with routes, ratings, locations and register dates. Refuses to guess on weak matches. | `company_name` (2–100 chars, typos OK), `country` (`uk` \| `nl` \| `both`, default `both`) |
| `search_sponsors` | Search Sponsor Register | Exploratory list search with optional filters. Returns a compact list plus a total count. For one specific company, prefer `check_sponsor_license`. | `query?`, `country` (`uk` \| `nl`, default `uk`), `city?` (UK), `route?` (UK), `sponsor_type?` (NL: `WORK` \| `EXCHANGE` \| `STUDY` \| `RESEARCH`), `limit` (1–20, default 10) |
| `get_sponsor_details` | Get Sponsor Details | Full record for one organization by id: routes/ratings/locations/dates (UK) or sponsor type + KvK number (NL). Optionally includes register change history. | `org_id` (int), `country` (`uk` \| `nl`, default `uk`), `include_history` (bool, default `false`) |
| `get_register_info` | About the Sponsor Registers | Register statistics, data freshness, a terminology glossary, and the legal disclaimer. Use it to explain what a licence, route, rating, or sponsor type means. | none |
Every tool is annotated `readOnlyHint: true` and returns both a human-readable text block and `structuredContent`, each stamped with the data-freshness date and a source note.
## Example prompts
Natural-language things you can ask an assistant once the server is connected:
- "Does Google hold a UK sponsorship licence?"
- "Is ASML a recognised sponsor in the Netherlands?"
- "Search UK Skilled Worker sponsors in Manchester."
- "List Dutch WORK-type sponsors matching 'shell'."
- "What does a B rating mean?"
**A note on name matching.** The fuzzy match tolerates typos (e.g. `googel uk`), but it needs a _reasonably complete_ name to resolve confidently — "Google UK" or "Google UK Limited" resolves cleanly, whereas a single bare word can match many companies. The server deliberately **refuses to guess on weak or tied matches**: instead of silently picking one, it returns an `ambiguous` verdict with the candidate list and asks you to disambiguate (or to call `get_sponsor_details` with the right id). Absence from a register is itself a meaningful answer: it means the company cannot currently sponsor that visa type.
## Use it (hosted)
The server is hosted at:
```
https://mcp.sponsorfinder.io/mcp
```
**Claude Code**
```bash
claude mcp add --transport http sponsorfinder https://mcp.sponsorfinder.io/mcp
```
**claude.ai** — Settings → Connectors → **Add custom connector** → paste the URL above. No authentication is required.
**ChatGPT** — Settings → Connectors (or a custom GPT's Actions) → **Add** a custom/remote MCP connector and paste the URL above.
Any MCP client that speaks Streamable HTTP can connect the same way — point it at `https://mcp.sponsorfinder.io/mcp`.
## Run locally
**Prerequisites:** Node.js 24 and pnpm 11.
```bash
pnpm install
cp .env.example .env
# then edit .env and fill in:
# SPONSORFINDER_API_BASE e.g. https://api.sponsorfinder.io
# SPONSORFINDER_API_KEY your upstream x-api-key (server-side secret)
```
Run one of the two transports in watch mode:
```bash
pnpm dev:stdio # stdio transport (Claude Desktop / Claude Code / MCP Inspector)
pnpm dev:http # Streamable HTTP on http://localhost:3001/mcp
```
**Add the local stdio build to Claude Code:**
```bash
pnpm build
claude mcp add sponsorfinder -- node dist/entry/stdio.js
```
**Inspect the tools interactively** with the MCP Inspector against either transport:
```bash
npx @modelcontextprotocol/inspector
```
## Configuration
All configuration is via environment variables (see `.env.example`):
| Name | Required | Default | Description |
| ------------------------ | -------- | ------- | ----------------------------------------------------------------------------------------------------------------------- |
| `SPONSORFINDER_API_BASE` | yes | — | Base URL of the upstream SponsorFinder API, no trailing slash (e.g. `https://api.sponsorfinder.io`). |
| `SPONSORFINDER_API_KEY` | yes | — | Upstream `x-api-key` header value. **Server-side secret — never exposed to MCP clients**, tool output, errors, or logs. |
| `PORT` | no | `3001` | HTTP port for the Node entry (`src/entry/node.ts`). Ignored by stdio and Cloudflare Workers. |
| `UPSTREAM_TIMEOUT_MS` | no | `10000` | Upstream request timeout in milliseconds (aborted via `AbortSignal.timeout()`). |
`SPONSORFINDER_API_KEY` is the one true secret. MCP clients connect authless; the key lives only on the server (a Docker env var or a Cloudflare Workers secret) and is never surfaced to clients.
## Self-host / deploy
The Web-standard core runs on both Node and Cloudflare Workers; only the entry file differs.
**Docker (primary)** — image `ghcr.io/alirezahamid/sponsor-finder-mcp`, config in `Dockerfile` and `docker-compose.yml`:
```bash
docker compose up -d
```
Put a reverse proxy (Caddy/nginx) in front of `mcp.sponsorfinder.io` and pass **POST, GET and DELETE** through to `/mcp`. Streamable HTTP needs response **buffering off** (`proxy_buffering off;` in nginx; Caddy's defaults are fine).
**Cloudflare Workers** — config in `wrangler.jsonc`:
```bash
wrangler secret put SPONSORFINDER_API_KEY
pnpm deploy:worker
```
## Development
| Script | What it does |
| ----------------- | --------------------------------------------------------------------- |
| `pnpm typecheck` | Type-check with `tsc --noEmit`. |
| `pnpm lint` | Lint with ESLint. |
| `pnpm test` | Run unit tests (excludes smoke tests). |
| `pnpm test:smoke` | Integration smoke tests against the real staging API (needs secrets). |
| `pnpm build` | Bundle to `dist/` with tsup. |
| `pnpm check` | `typecheck` + `lint` + `test` in one go. |
## Analytics (optional)
The server can report usage to **Google Analytics 4** via the server-side
[Measurement Protocol](https://developers.google.com/analytics/devguides/collection/protocol/ga4).
Because an MCP server has no browser, a GA/GTM JavaScript tag cannot run in it — the
server sends events over HTTP instead. It works on both Node and Cloudflare Workers.
Analytics are **off** unless both `GA_MEASUREMENT_ID` and `GA_API_SECRET` are set.
Each tool call emits one `mcp_tool_call` event with **categorical parameters** —
`tool`, `status`, `verdict`, `country`, `mcp_client`, `latency_bucket`, `error_kind`.
By default **no company names or free-text queries are sent to GA**; the searched name
is only included (as the `query` param) if you opt in with `CAPTURE_QUERY_NAMES` — see below.
Setup:
1. Create a GA4 property (free) and a Web data stream.
2. In **Admin → Data streams → your stream → Measurement Protocol API secrets**, create a
secret. Copy the stream's **Measurement ID** (`G-XXXXXXXXXX`) and the secret value.
3. Set `GA_MEASUREMENT_ID` and `GA_API_SECRET` (env / Docker / `wrangler secret`).
4. In GA4, register the event params above as **custom dimensions**
(Admin → Custom definitions) so they appear in reports. Use `GA_DEBUG=true` to send to
GA's validation endpoint while testing.
Unmet-demand analysis: setting `CAPTURE_QUERY_NAMES=true` records the searched company
name to the server's structured logs **and** sends it to GA4 as the `query` event param
(register a `query` custom dimension to see it). Off by default. Caveats: raw names are
high-cardinality in GA (bucketed as `(other)`) and person-named queries may count as PII
under GA's terms — a private log store is usually the better home for this, and you should
add a privacy-policy line before enabling it.
## How it works
The server is a thin, stateless proxy with response shaping. An MCP client connects over stdio or stateless Streamable HTTP; a small [Hono](https://hono.dev) app (with `@hono/mcp`) constructs an MCP server per request, calls the SponsorFinder API with the server-side key, validates every response with zod (a contract-drift guard), and shapes it into a compact verdict. Register stats and filter values are cached in-process with a short TTL. Because the core uses only Web-standard APIs (`fetch`, `URL`), the same code runs on Node and Cloudflare Workers — only `src/entry/*` differs.
```
MCP client (Claude / ChatGPT / Cursor)
│ stdio or stateless Streamable HTTP
▼
SponsorFinder MCP server (Hono + @hono/mcp)
• 4 read-only tools, zod-validated
• x-api-key added server-side
• cached /status + /filters
│ HTTPS (x-api-key)
▼
SponsorFinder API → UK Home Office register + Dutch IND register
```
## Data & disclaimer
The data comes from the official [UK gov.uk register of licensed sponsors](https://www.gov.uk/government/publications/register-of-licensed-sponsors-workers) and the [Dutch IND public register of recognised sponsors](https://ind.nl/en/public-register-recognised-sponsors), refreshed daily.
This tool is **informational only and is not legal advice**. Register data can lag official publications, and a licence does not guarantee a company will sponsor any given role. Always verify against the official sources before making decisions.
## Contributing
Issues and pull requests are welcome at [github.com/alirezahamid/sponsor-finder-mcp](https://github.com/alirezahamid/sponsor-finder-mcp). Please run `pnpm check` before opening a PR.
> **Publishing note:** `server.json` is the manifest for the official MCP registry. Before running `mcp-publisher publish`, confirm the exact `$schema` URL against the current [registry.modelcontextprotocol.io](https://registry.modelcontextprotocol.io) docs — the schema version pinned here may have moved on.
## License
[MIT](./LICENSE) © 2026 Alireza Hamid
MCP Config
Below is the configuration for this MCP Server. You can copy it directly to Cursor or other MCP clients.
mcp.json
Connection Info
You Might Also Like
No recommendations available