Skip to content

MCP server

mcp.igregulator.io exposes iGregulator as a Model Context Protocol server. Compatible clients (Claude Desktop, Cursor, Windsurf, Cline) can discover and call iGregulator tools without your users writing any HTTP code.

Two ways in, one plan: sign in with your iGregulator account (claude.ai, ChatGPT and Claude Code connectors — no key to copy), or send the same API key that works against https://api.igregulator.io. Either way tool calls are counted against your account’s normal quota (shared by all your keys and connected apps) — there’s no separate MCP plan.

Direct APIMCP
Where it runsYour application codeThe agent runtime (Claude Desktop, Cursor, etc.)
AuthYour code stores + sends Bearer headerConfigured once, agent reuses across sessions
Tool discoveryRead OpenAPI, write wrappersAgent auto-introspects
Best forServer-to-server, batch jobsEnd-user agent flows, KYB / compliance assistants

Use direct API when you build the integration. Use MCP when an agent on the user’s machine needs to talk to iGregulator.

Mapped to the public API surface. Tool output is lean — a compact verdict, not the full REST payload — so it stays cheap in your context; fetch get_operator when you need the full dossier.

ToolBacks ontoUse case
check_domainGET /v1/check”Is bet365.com licensed?” (supports as_of)
check_domain_batchPOST /v1/check/batchA KYB sweep — up to 100 domains in one call
search_operatorsGET /v1/operators/searchBrand → registered legal entity
get_operatorGET /v1/operators/:slugFull record incl. licenses + domains (supports as_of). Long domain lists are capped — 50 by default, listed links first; domains_total says how many there are and domains_limit (up to 1,000) asks for more
get_operator_regulatory_actionsGET /v1/operators/:slug/regulatory-actionsEnforcement history (fines, suspensions). An empty list means none is linked to that operator — not a clean record
check_coverageGET /v1/health/coverageData freshness per jurisdiction
list_jurisdictionsGET /v1/jurisdictionsCoverage overview
get_jurisdictionGET /v1/jurisdictions/:codeOne regulator’s metadata
get_licenseGET /v1/licenses/:license_idSpecific license detail
get_license_historyGET /v1/licenses/:license_id/historyStatus-change audit trail

check_domain and each check_domain_batch row lead with the API’s verdict and verdict_detail (see Reading the answer). On a no-match, they return match_absence_reason + checked_jurisdictions — so an agent says “not found in the jurisdictions we cover”, never an unqualified “unlicensed”. On a match, check_domain also passes through verification_url (the regulator’s own per-domain verification page — Curaçao certificate / Tobique seal — the citation to surface next to the verdict) and, for dual-licensed domains, a compact jurisdictions[] array so the answer never flattens a multi-register brand to one jurisdiction.

Branch on verdict; quote verdict_detail. Only licensed and licensed_provisional mean licensed now — licensed_provisional is in force provisionally (a Curaçao licence under the CGA’s final assessment), so say so and suggest a re-check. Everything else — licence_not_active, domain_not_listed, related_host_listed, name_match_only, not_found, generic_term, or a value added later — is “not confirmed as licensed by the registers we cover”, never “unlicensed”. The rules behind each value are in confidence scoring.

verdict_detail already names the status in its own words. When you need to say more about a status, the values are not interchangeable, and the server’s own instructions tell the model so on connect:

statusWhat it meansWhat an agent should say
revoked, suspendedThe regulator published an enforcement decision and we read itThat, citing the source
surrenderedThe operator gave the licence upNot licensed now — not enforcement
expiredIt ran outNot licensed now
not_in_registerThe register stopped listing it; no reason was published”No longer listed” — never “revoked”
unknownListed, with wording we could not classifyEscalate

check_domain, check_domain_batch and get_operator return a lean result (they land in the model’s context window). A check carries the verdict, status, status_qualifier, domain_status and — as the API sends them — regulator_name, status_source_url (the page that published this status, often not the register itself), status_observed_at, license_id, and related_hosts[] when the host isn’t listed but others on its registrable domain are (never that host’s licence — see which host answers). Next to a verification_url (a check, or a get_operator domain) comes verification_page_status: what the regulator’s own page printed about the licence at our latest read, verbatim, with verification_page_read_at. If that word does not say “in force”, the page is not proof (get_operator’s summary names such domains); don’t cite the link as confirmation. get_operator gives each licence its license_id, status, status_qualifier and license_reference_is_ours (true for Kahnawake, Tobique and the Isle of Man: license_number is then our reference, not the regulator’s). Pass a license_id to get_license for the full REST record — status_source_url, status_observed_at, not_listed_since, last_listed_at — and to get_license_history for every event with its note and the evidence hash (snapshot_sha256). An event with corrected_at set was later withdrawn: never quote it as fact. A correction_note without corrected_at is a reworded note on an event that stands, not a withdrawal.

get_operator_regulatory_actions says what its list means in the result itself (note, sources_read): which regulator publications we read, and that not every published action is matched to an operator — an empty list is not a clean record.

Webhook + watchlist management (creating endpoints, rotating secrets, etc.) is intentionally not exposed via MCP — those belong in the dashboard at app.igregulator.io. MCP is read-only by design.

Connect from claude.ai, ChatGPT or Claude Code (sign in — no key)

Section titled “Connect from claude.ai, ChatGPT or Claude Code (sign in — no key)”

Paste this URL as a custom connector, then sign in with your iGregulator account when asked:

https://mcp.igregulator.io/mcp/account

No account yet? The sign-in page has a sign up link — free, no card — and you come straight back to the connection afterwards.

  • claude.ai (and Claude Desktop, mobile): Settings → Connectors → Add custom connector, paste the URL, Add, then Connect. Leave the advanced OAuth client fields empty: Claude registers itself.

  • ChatGPT: turn on developer mode (Settings → Security and login → Developer mode; availability depends on your plan and workspace), add a new app/connector with the URL above, and choose OAuth if asked.

  • Claude Code:

    Terminal window
    claude mcp add --transport http igregulator https://mcp.igregulator.io/mcp/account

    then run /mcp in a session and pick igregulator → Authenticate; a browser opens for the sign-in.

You will see a consent screen on app.igregulator.io naming the app, the site it returns you to, and what it gets: read-only licence lookups — all ten tools — on your plan, counted against your monthly quota, until you revoke it. It never sees your password or API keys and cannot change your account. Every app you connect is listed under API keys → Connected apps; Revoke cuts it off at once (its tokens stop working on the next request).

How it works, for the curious: mcp.igregulator.io/mcp/account is an OAuth 2.1 protected resource (RFC 9728 metadata); app.igregulator.io is its authorization server (RFC 8414 metadata) — dynamic client registration, PKCE (S256), one scope (mcp:read), one-hour access tokens and rotating refresh tokens, all per the MCP authorization spec. The same endpoint also accepts an API key (Authorization: Bearer igk_…).

No account needed to see what the server does. With no Authorization header, check_domain, search_operators (top 3), list_jurisdictions and check_coverage work, 10 requests per hour per IP; the other six tools answer with a pointer to a free key.

Terminal window
claude mcp add --transport http igregulator https://mcp.igregulator.io/mcp

Create a free account and get an API key at app.igregulator.io/api-keys. Founding members get the full Starter plan free — all tools, no card.

Native HTTP MCP support. One command:

Terminal window
claude mcp add --transport http igregulator https://mcp.igregulator.io/mcp \
--header "Authorization: Bearer igk_..."

Replace igk_... with your real key. Verify with claude mcp list — igregulator should appear with status connected. Restart any active Claude Code session and the tools become available immediately.

Section titled “Claude Desktop (recommended path: mcp-remote shim)”

Claude Desktop’s stdio-based MCP support is universal; HTTP support is rolling out. The most reliable config today uses mcp-remote to bridge stdio → streamable HTTP. Open ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows) and add:

{
"mcpServers": {
"igregulator": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://mcp.igregulator.io/mcp",
"--header",
"Authorization: Bearer igk_..."
]
}
}
}

Replace igk_... with your real key. Restart Claude Desktop. Verify with: “What iGregulator tools do you have?”

Claude Desktop (native HTTP — newer builds only)

Section titled “Claude Desktop (native HTTP — newer builds only)”

If your Claude Desktop build supports remote MCP servers natively, you can drop the mcp-remote shim:

{
"mcpServers": {
"igregulator": {
"url": "https://mcp.igregulator.io/mcp",
"transport": "streamable-http",
"headers": {
"Authorization": "Bearer igk_..."
}
}
}
}

If this gives you an “unknown transport” error, your build is too old — fall back to the mcp-remote shim above.

Cursor → Settings → Features → Model Context Protocol → Add new MCP server. Use the same mcp-remote shim:

Terminal window
npx -y mcp-remote https://mcp.igregulator.io/mcp --header "Authorization: Bearer igk_..."

Windsurf → Settings → Cascade → Manage MCP Servers. Same mcp-remote shim pattern as Cursor; Windsurf documents per-version specifics in their MCP guide.

Anything that speaks MCP can connect. Refer to your client’s docs for HTTP/SSE transport configuration; the connect URL is https://mcp.igregulator.io/mcp and auth is a bearer header named Authorization.

Check whether bet365.com is licensed.

→ Agent calls check_domain with domain="bet365.com", returns UKGC license details.

Find every operator named "Flutter" and show me their licenses.

→ search_operators(q="flutter") followed by get_operator(slug=...) for each result.

What's the regulatory action history for license <UUID>?

→ get_license_history(license_id=...) returns the full status-change timeline.

Compare UKGC and MGA — what license types do they each issue?

→ get_jurisdiction(code="UKGC") + get_jurisdiction(code="MGA").

Here are 40 merchant domains — which are licensed?

→ one check_domain_batch(domains=[...]) call, not 40 separate checks.

Was virginbet.com licensed on 2026-03-01?

→ check_domain(domain="virginbet.com", as_of="2026-03-01") — reads as_of.knowledge (won’t assert a status from before tracking began).

Any enforcement actions against Flutter?

→ get_operator_regulatory_actions(slug="flutter-uk-limited").

Two credentials work:

  • Signing in (OAuth 2.1, at https://mcp.igregulator.io/mcp/account): what claude.ai, ChatGPT and Claude Code connectors do. The MCP server checks each access token with app.igregulator.io on every request — it must be active, issued for this endpoint, and carry mcp:read — then calls the API for you with its own internal credential; your token is never passed on. A revoked connection (Connected apps → Revoke) or a canceled plan stops working on the next request. Without a token the endpoint answers 401 with WWW-Authenticate: Bearer resource_metadata="…", which is what starts the sign-in.
  • An API key, on either endpoint:

API keys are bearer tokens. The same key works against the direct API and the MCP server. Rotate or revoke at app.igregulator.io/api-keys; changes take effect within seconds across both surfaces.

The MCP gateway does not store your key. It checks that the header is shaped Bearer <key> and forwards the key on each tool call to the API, which runs the same requireApiKey middleware that direct-HTTP callers go through — an unknown or revoked key is refused there, on the first tool call.

A key is optional. Without one, the gateway calls only what the REST API serves without a key (/v1/check, /v1/operators/search, /v1/jurisdictions, /v1/health/coverage) and the API applies its public limit to your IP, not the gateway’s. A header that is present but malformed is still rejected with a 401.

Without a key: 10 requests per hour per IP — the REST API’s own counters, kept per route, not a separate MCP allowance. A keyless tool call counts against the route it calls, from your IP: check_domain spends the same /v1/check counter as a direct request (ten keyless check_domain calls from one IP in an hour use up that IP’s /v1/check allowance for direct requests too), search_operators spends /v1/operators/search’s, list_jurisdictions /v1/jurisdictions’s and check_coverage /v1/health/coverage’s. With a key, tool calls count against your account’s existing quota, one call per tool call. HTTP headers don’t cross the MCP boundary: when a limit trips, the tool result is an error whose text is { "http_status": 429, …the API's error body… } — code, details.reason, and details.reset_at where the API sets it — plus, for a keyless caller, a how_to_fix hint.

Founding (Starter) keys reach every tool. Only legacy trial-tier keys are scoped — to the four tools that also work without a key (check_domain, search_operators, list_jurisdictions, check_coverage; 1,000 calls/day) — and return 402 payment_required with details.reason=endpoint_requires_paid_plan on the other tools.

This server publishes a manifest at three URLs (community convention; not a canonical MCP spec field — canonical discovery is the user typing the URL into their client):

All three return the same JSON: server URL, transport, auth flow (key, or OAuth at /mcp/account), key-request URL, docs URL.

OAuth discovery for the signed-in endpoint follows the MCP spec: Protected Resource Metadata at /.well-known/oauth-protected-resource/mcp/account (also at the root /.well-known/oauth-protected-resource), naming the authorization server https://app.igregulator.io/api/auth, whose metadata is at /.well-known/oauth-authorization-server/api/auth.

“Tool not found” — usually the agent never connected. Check that your client logs show iGregulator listed under MCP servers. If it’s missing, the bearer header is malformed or the URL is unreachable.

“Connection failed” or DNS errors — curl -I https://mcp.igregulator.io/health should return 200 OK. If not, the issue is upstream of MCP.

Connector says it can’t connect, or asks to sign in again — the sign-in was revoked under Connected apps, or went unused for 30 days (refresh tokens expire after 30 days without use). Connect again; you won’t be asked to approve an app you already approved unless you revoked it.

“402 / plan_inactive” from a signed-in connector — the account has no active plan; signed-in connections are refused exactly as its keys would be.

“401 / api_key_missing / api_key_invalid” — your key isn’t being passed. With mcp-remote, double-check the --header arg includes Bearer (with the space). Quoting matters in some shells; if your config has issues, restart the client.

“402 / endpoint_requires_paid_plan” — a legacy trial-tier key hit a paid endpoint. Free founding (Starter) accounts don’t see this; create one at app.igregulator.io/signup, or ask the agent to stay on the four keyless tools.

“429 / rate limited” — same quota as direct API. Rate-limit headers are not passed through; the API’s error body is (http_status, code, details.reason, and details.reset_at where set). Branch on details.reason as in the errors reference: per_second_limit_exceeded clears in a second, monthly_request_limit_reached at details.reset_at, and the keyless quota_exceeded at the top of the next UTC hour.

If a tool description or argument schema needs work, write to founder@igregulator.io — agent-voice quality is something we iterate on.