Remote MCP server that mirrors the IND public register of recognised sponsors (Work) — the list of Dutch organisations that can sponsor work / highly-skilled-migrant residence permits — and makes it queryable by AI assistants.
IND publishes the register as a monthly-updated HTML page with no API. This Worker scrapes it daily (write-on-change), stores it in KV with monthly snapshots, and serves two MCP tools over Streamable HTTP:
search_sponsors— company name or 8-digit KvK number in, ranked candidate matches out (exact / base-name / substring / fuzzy). Deliberately returns candidates, not a yes/no verdict: the register lists registered legal names, queries use trade names, and the calling LLM is the right judge of whether "Adyen N.V." is the "Adyen" you meant. Seedocs/adr/0001.get_register_status— register date, scrape health, staleness flag.
Runs entirely on the Cloudflare free tier: Workers + KV + SQLite-backed Durable Objects (McpAgent) + cron + Email Routing for scrape-failure alerts.
Connect the server (no auth; rate limited to 30 req/min per IP):
Claude Code
claude mcp add --transport http ind-sponsors https://hsm.codealan.com/mcpclaude.ai / Claude Desktop — Settings → Connectors → Add custom connector →
https://hsm.codealan.com/mcp
Any MCP client (Cursor, etc.)
{ "mcpServers": { "ind-sponsors": { "url": "https://hsm.codealan.com/mcp" } } }Then just ask in plain language — the assistant calls the tools itself:
- "Is Adyen a recognised sponsor for highly skilled migrants?"
- "Check these five companies from my shortlist against the IND register."
- "KvK 56317441 — who is this and can they sponsor?"
- "How fresh is the sponsor data?" (hits
get_register_status)
Reading the answers: tools return ranked candidates, never a yes/no
verdict. A base_name/exact_name match at score ≥0.95 is a solid yes; a
KvK-number match is identity-grade; an empty result is not a "no" — the
register lists registered legal names while companies go by trade names, so try
the legal name (often ends in B.V./N.V.) or the KvK number. Always confirm
against the official register
before acting on it.
flowchart LR
client["AI client<br/>(Claude, any MCP client)"]
ind["ind.nl Work register<br/>(HTML page, updated ~monthly)"]
inbox["Owner's inbox"]
subgraph gh["GitHub Actions"]
ci["CI: push to main<br/>test → deploy"]
health["Health check (daily)<br/>GET /health, 503 = stale"]
end
subgraph cf["Cloudflare (free plan)"]
worker["Worker: routing +<br/>30 req/min per-IP rate limit"]
mcpdo["HsmMcp Durable Object<br/>McpAgent tools:<br/>search_sponsors · get_register_status<br/>(cached fuzzy-match index)"]
scrdo["Scraper Durable Object<br/>parse + sanity gates<br/>(DO dodges 10 ms Worker CPU cap)"]
kv[("KV<br/>register:current<br/>snapshot:YYYY-MM-DD<br/>status")]
cron["Cron 06:17 UTC daily"]
mail["Email Routing"]
end
client -- "MCP (streamable HTTP) /mcp" --> worker --> mcpdo
mcpdo -- "read + cache per version" --> kv
cron --> scrdo
worker -- "POST /admin/scrape (bearer)" --> scrdo
scrdo -- "fetch HTML" --> ind
scrdo -- "write-on-change<br/>+ monthly snapshot" --> kv
scrdo -- "3 consecutive failures" --> mail --> inbox
health -- "probe" --> worker
ci -- "wrangler deploy" --> worker
Key design points, in one breath: the scraper polls daily but writes only when IND's own "last updated" date changes; sanity gates (row count, KvK format, date parse) mean a broken scrape can never replace good data with garbage; searches run against an in-memory index rebuilt only when the register version changes; and every snapshot is kept forever so a future diff feature can answer "when did company X appear?".
| Path | What happens |
|---|---|
| Query | client → /mcp → HsmMcp DO → ranked candidates from cached index |
| Refresh | cron (or POST /admin/scrape) → Scraper DO → gates → KV |
| Monitoring | GitHub Action probes /health daily; 503 fails the run → GitHub emails the owner |
| Deploy | push to main → tests → wrangler deploy (config from repo secrets) |
Setup, file-by-file architecture notes, and deploy steps: see CLAUDE.md.
Unofficial project; data © IND, republished for easier checking. Always verify with the official register before acting on it.