registry-mcp — national company registries

io.github.foretakv0.4.2更新於 Oct 7, 2026

The company registry MCP: brreg orgnr, Companies House, Bolagsverket organisationsnummer.

已驗證Streamable HTTP可網頁執行Developer ToolsFinanceBusiness & Commerce

概覽

AI 產生的概覽

查詢英國、挪威與瑞典國家企業登記機關的公司紀錄,並回傳法定申報截止日期。

功能
提供五個登記工具:lookup_company 依國家識別碼回傳完整公司報告,search_company 依名稱檢索,company_deadlines 回傳下一批法定申報日期,validate_company_id 離線驗證識別碼,list_countries 列出支援的國家。另有 search 與 fetch 兩個連接器別名,為 ChatGPT 包裝相同資料。各國回應採用一致 JSON 結構,並帶有 cached、fetched_at、source 與 license 欄位;選用附件可補充 filings、charges、insolvency、financials、LEI、母公司及 Peppol 資訊。
適用情境
適合在引進供應商、客戶或交易對手前,讓助理核實公司登記資訊、法律形式、狀態或申報截止日期,或驗證 organisasjonsnummer、company number 等識別碼。也適合需要可引用、帶時間戳記之登記證據,而非一般網頁搜尋的工作流程。
執行需求
可使用託管遠端端點(不需金鑰或帳號),也可在本機執行:uvx registry-mcp(需 Python 3.12+ 及 uv 或 pipx)或 npx registry-mcp(內部會呼叫 uvx)。選用環境變數:REGISTRY_MCP_CONTACT_EMAIL、REGISTRY_MCP_CACHE_PATH(預設 ./data/cache.sqlite3)。自行架設時 GB 需要免費的 Companies House 金鑰,SE 需要 Bolagsverket 的 OAuth 2 用戶端組;挪威不需任何憑證。需要連線各國登記機關的網路。
安裝前請注意
此服務為唯讀,不會寫入任何登記機關。自行架設需要密鑰 COMPANIES_HOUSE_API_KEY 以及 BOLAGSVERKET_CLIENT_ID 與 BOLAGSVERKET_CLIENT_SECRET 組合;託管端點已設定好自身憑證。選用附件會向第三方發出請求:lei 與 parents 會存取 GLEIF(api.gleif.org),peppol 會存取 Peppol SML/SMP 或目錄。此服務不進行制裁、PEP 或負面新聞篩查,也不驗證銀行帳戶。瑞典個體經營者的公司編號可能是個人身分編號,應避免記錄。

安裝

在 SourceWeft 中

  1. 開啟 儀表板中的 registry-mcp — national company registries,將其新增到工作區。
  2. 為需要使用其工具的對話啟用該服務。

Web executable,透過 Streamable HTTP。 遠端服務在工作區中設定後即可從網頁執行環境執行。

其他 MCP 客戶端

把它新增到你客戶端的 mcpServers 設定中。

{
  "mcpServers": {
    "registry-mcp": {
      "type": "http",
      "url": "https://api.foretak.dev/mcp"
    }
  }
}

README

registry-mcp — the company registry MCP

[PyPI] [PyPI alias] [npm] [CI] [License: MIT] [Listed on mcpservers.org] [Rated A on Glama]

Company data for AI agents, any country. One MCP server and REST API, three national registers today: the United Kingdom's Companies House, looked up by company number; Norway's Enhetsregisteret / Brønnøysundregistrene (brreg), looked up by organisasjonsnummer (orgnr); and Sweden's Bolagsverket, looked up by organisationsnummer — one JSON shape whichever you ask.

bash
claude mcp add registry-mcp --transport http https://api.foretak.dev/mcp?src=readme

No install step, over stdio:

bash
claude mcp add registry-mcp -- uvx registry-mcp

What makes it worth a tool slot:

  • Deadlines that cite the rule, not just a date. company_deadlines gives the next filing date and names why in applies_because — a Norwegian legal form's statutory duty, or "Companies House publishes this date for the company itself" when the register states it rather than us computing it. Quote the reason, not just the number.
  • Never more than 24 hours stale, and it says so. Every response carries cached and fetched_at. OpenCorporates' own knowledge base tells users to "allow 30 days" for a correction to reach its site — a 30× freshness gap, stated by the incumbent about itself.
  • Seven tools, not fifty — five registry tools plus two ChatGPT connector aliases. Tool-selection accuracy degrades past 30-50 tools loaded into an agent's context, and some clients cap around 40. Seven tools is roughly 17% of that budget, next to competitors in this space shipping 23 to 78 tools for the same job.

Security. Read-only, always — nothing here writes to a register or anywhere else. No credentials are required from a caller; this deployment's own upstream credential (COMPANIES_HOUSE_API_KEY) is read from the environment and never logged or returned. The base lookup calls three named upstreams and nothing else: data.brreg.no, api.company-information.service.gov.uk and gw.api.bolagsverket.se. include=["lei"]/include=["parents"] additionally call api.gleif.org (GLEIF, CC0, keyless); include=["peppol"] additionally resolves a DNS record at the Peppol SML and calls whichever SMP host it names for that participant, falling back to the Peppol Directory (directory.peppol.eu) only when neither answers — see the attachments above. No personal data beyond what each national register already publishes about the entity itself — and because a Swedish sole trader's company number is their personnummer, the usage log stores no identifier at all for a country whose identifiers can be a natural person's (legal/privacy.md). This service does not perform sanctions, PEP or adverse-media screening, and it does not verify bank account details. Details: SECURITY.md.

One-click install, for a remote streamable-HTTP server:

[Install in VS Code] [Install in VS Code Insiders] [Install in Cursor]

Other clients (Claude Desktop, Cursor, VS Code, Cline, plain JSON configs): see docs/clients.md.

Add to ChatGPT

ChatGPT reaches an MCP server through a custom connector, and its deep research mode calls exactly two tools — search and fetch — which this server ships alongside the five registry tools. In ChatGPT, open Settings → Connectors, add a custom connector, and give it:

https://api.foretak.dev/mcp?src=readme

No authentication, no key, no account. If your ChatGPT plan does not show custom connectors under Settings → Connectors, turn on Settings → Security and login → Developer mode first, then add the URL from https://chatgpt.com/plugins.

search takes one free-text query — a company name, a national identifier, or a name plus a country ("Tesco United Kingdom") — and returns citable rows; fetch takes a row's id ("NO:923609016") and returns that company's register record and its statutory filing deadlines, with the full JSON of both in metadata.

Add to Claude Desktop

Claude Desktop takes the same URL as a custom connector: Settings → Connectors → Add custom connector, then https://api.foretak.dev/mcp?src=readme. No key. For a local stdio install instead, see Configuration.

Status: 0.4.2, live — GET /health returns {"version":"0.4.2","countries":["GB","NO","SE"]}. The five registry tools and their response shapes are frozen; two connector aliases (search, fetch) wrap them for ChatGPT and add no new shape. The hosted API at api.foretak.dev is live, and listed in the official MCP registry as io.github.foretak/registry-mcp. Countries: United Kingdom (Companies House), Norway (brreg), Sweden (Bolagsverket) — see below for each country's identifier format and example calls.

Add to Claude Code

The same two commands as above — Streamable HTTP or local stdio. To add it as a project-level .mcp.json file instead of the CLI, see Configuration.

What it returns

console
$ curl https://api.foretak.dev/v1/NO/company/923609016{  "country": "NO", "registry": "brreg",  "id": "923609016", "id_formatted": "923 609 016", "id_scheme": "organisasjonsnummer", "euid": null,  "name": "EQUINOR ASA",  "legal_form_code": "ASA", "legal_form": "Public limited company", "legal_form_local": "Allmennaksjeselskap",  "status": "active", "is_active": true, "registered_at": "1995-03-12",  "vat_registered": true, "vat_number": "NO923609016MVA", "vat_registered_at": "1989-07-01",  "employees": 21239, "share_capital": 5976872600.0, "share_capital_currency": "NOK",  "business_address": {"lines": ["Forusbeen 50"], "postal_code": "4035", "city": "STAVANGER"},  "advertising_protected": null,  "source": "Enhetsregisteret (Brønnøysundregistrene)", "license": "NLOD 2.0"}

Abridged — the full CompanyReport also carries previous_names, industry_codes, registers, purpose, parent_id, confidence, cached, fetched_at and notes. Every field is documented in llms-full.txt §5.

euid is the EU-wide identifier some member-state registers publish (Finland does; none of ours do yet) — never the LEI, never constructed from parts. advertising_protected is true/false/null: whether the register marks this entity as protected against direct-marketing use, null meaning the register publishes no such flag at all (Norway and the UK, today); where it is true (Sweden's reklamspärr, for one), a notes sentence states it and that marking must travel with any contact details you pass on.

The United Kingdom, same shape, same abridgement:

console
$ curl https://api.foretak.dev/v1/GB/company/00445790{  "country": "GB", "registry": "companies-house",  "id": "00445790", "id_formatted": null, "id_scheme": "company number", "euid": null,  "name": "TESCO PLC",  "legal_form_code": "plc", "legal_form": "Public limited company",  "status": "active", "is_active": true, "registered_at": "1947-11-27",  "vat_registered": null, "vat_number": null,  "employees": null, "employees_reported": false,  "registers": {"insolvency": false},  "industry_codes": [{"code": "47110", "description": null, "scheme": "SIC 2007", "rank": 1}],  "business_address": {"lines": ["Tesco House, Shire Park", "Kestrel Way"], "postal_code": "AL7 1GA", "city": "Welwyn Garden City"},  "advertising_protected": null,  "published_deadlines": [    {"kind": "annual_accounts", "due_date": "2027-08-26", "period_end": "2027-02-26", "overdue": false, "source": "accounts.next_accounts.due_on"},    {"kind": "confirmation_statement", "due_date": "2027-07-02", "period_end": "2027-06-18", "overdue": false, "source": "confirmation_statement.next_due"}  ],  "source": "Companies House (UK)", "license": "Crown copyright — Companies House public register, free to re-use"}

published_deadlines carries the dates the register publishes itself, with the upstream field each came from. It is [] for Norway and Sweden, which compute all of their own.

Sweden, added in 0.3.0, is where "one shape" starts to earn the claim:

console
$ curl https://api.foretak.dev/v1/SE/company/5560160680{  "country": "SE", "registry": "bolagsverket",  "id": "5560160680", "id_formatted": "556016-0680", "id_scheme": "organisationsnummer", "euid": null,  "name": "Telefonaktiebolaget LM Ericsson",  "legal_form_code": "AB", "legal_form": "Private or public limited company", "legal_form_local": "Aktiebolag",  "status": "active", "is_active": true, "registered_at": "1918-08-19",  "vat_registered": null, "vat_number": null,  "employees": null, "employees_reported": false,  "industry_codes": [{"code": "70100", "description": "Verksamheter som utövas av huvudkontor", "scheme": "SNI 2007", "rank": 1}],  "postal_address": {"postal_code": "16483", "city": "STOCKHOLM", "country_code": "SE"},  "advertising_protected": null,  "published_deadlines": [],  "source": "Bolagsverket (bolagsverket.se)",  "license": "Free re-use (Bolagsverket/SCB high-value datasets, EU Open Data Directive) — the publisher names no licence"}

Bolagsverket names no licence for this data, so neither do we: the string says what the permission is and says plainly that there is no licence name to quote, because a familiar name in that field would be a fabrication.

Sweden publishes no status field at all. status is derived from three independent signals — a strike-off date, an ongoing winding-up or restructuring procedure, and Statistics Sweden's "economically active" flag — and is_active therefore means on the register and not winding down, which is not the same as trading. Where any of that is unavailable the answer is unknown, never active.

Two dates are computed, and each carries the provision it comes from:

console
$ curl "https://api.foretak.dev/v1/SE/company/5560160680/deadlines?today=2026-09-07"{"kind": "general_meeting",  "due_date": "2027-06-30", "days_until": 296, "rolled_forward": false, "period_label": "2026"}{"kind": "annual_accounts",  "due_date": "2027-07-31", "days_until": 327, "rolled_forward": false, "period_label": "2026"}

Six months to the annual general meeting (aktiebolagslagen 7 kap. 10 §) and seven to the filing before a late fee bites (årsredovisningslagen 8 kap. 6 §). Neither date is rolled forward off a weekend, because no Swedish source says it moves. Both assume a financial year ending 31 December by default — a notes sentence says exactly that, including how to shift both dates if the year end is different — but Bolagsverket's document list does publish a filed annual report's own year end: pass include=["filings"] (company_deadlines accepts it on both surfaces) to read it and replace the assumption with the register's own figure. search_company answers not_implemented for Sweden: the free API has four operations and none of them accepts a name.

Note the nulls. Companies House publishes no VAT status, no employee count and no share capital for any company, so those fields are null rather than guessed — null means "this register does not say", never "no". That honesty is the point of one shape across countries.

And the deadlines, which is where the UK module earns its keep:

console
$ curl "https://api.foretak.dev/v1/GB/company/00445790/deadlines?today=2026-09-04"{  "company_name": "TESCO PLC", "today": "2026-09-04",  "deadlines": [    {"kind": "confirmation_statement", "local_name": "Confirmation statement (CS01)",     "due_date": "2027-07-02", "period_end": "2027-06-18", "days_until": 301,     "applies_because": "Companies House publishes this date for the company itself; it is the register's own figure, not a calculation."},    {"kind": "annual_accounts", "local_name": "Annual accounts",     "due_date": "2027-08-26", "period_end": "2027-02-26", "days_until": 356,     "applies_because": "Companies House publishes this date for the company itself; it is the register's own figure, not a calculation."}  ]}

Where Companies House publishes a date, it is quoted; where it does not, the date is computed from a cited statute and applies_because says so. UK deadlines never roll forward off a weekend or bank holiday, and days_until goes negative for a filing the register still shows as overdue.

Running it yourself? The hosted service at api.foretak.dev has every credential configured. A self-hosted copy needs a free Companies House key for GB and an OAuth 2 client pair from Bolagsverket (BOLAGSVERKET_CLIENT_ID, BOLAGSVERKET_CLIENT_SECRET) for SE; without them those two countries return upstream_error naming the variable, and every other country keeps answering. Norway needs nothing.

Tools

ToolWhat it does
lookup_company(id, country="NO")Full CompanyReport for one company by national identifier
search_company(name, country="NO", limit=10)SearchResult — candidates with identifiers, in the register's relevance order, each scored
company_deadlines(id, country="NO", today=None)DeadlineReport — the next occurrence of each statutory filing obligation
validate_company_id(id, country="NO")ValidationResult — validate and normalise an identifier with no network call
list_countries()Which national registries are supported right now

Connector aliases — for ChatGPT, which reaches an MCP server through exactly search and fetch (Add to ChatGPT); add no new response shape and have no REST twin.

ToolWhat it does
search(query)ChatGPT connector alias for search_company — one free-text query, {"results": [{"id", "title", "url"}]}
fetch(id)ChatGPT connector alias for lookup_company + company_deadlines — one "{COUNTRY}:{identifier}", a Markdown document with both reports in metadata

Plus the resource registry://rules/{country} (identifier rules, legal forms, deadline rules — read once instead of validating in a loop) and the prompt explain_company.

Attachments — a second, independent fetch alongside the base report, opt-in per name: include=["filings"] on MCP, ?include=filings on REST.

  • filings — what the entity has filed, and when (GB, NO, SE)
  • charges — registered charges against the entity (GB only)
  • insolvency — winding-up and administration proceedings (GB only)
  • financials — the register's own financial figures (turnover, profit, balance sheet, equity and liabilities) for the latest filed accounting period, answering whether a supplier looks solvent — Norway's own key-figures feed, Sweden's read out of the entity's own filed annual report (NO, SE)
  • lei — the Legal Entity Identifier GLEIF publishes for the entity, CC0-licensed and keyless (every country except SE)
  • parents — the direct and ultimate corporate parent GLEIF's Level 2 data discloses, or the entity's own stated reason — a category word such as NATURAL_PERSONS, never a name — when it discloses none (same countries as lei)
  • peppol — whether the entity can receive an e-invoice over the Peppol network, read live from the SML/SMP walk ahead of the 1 January 2027 e-invoicing duty (NO only)

A country's declared set is supported_includes on list_countries / GET /v1/countries; see llms-full.txt for the two-level nullability and the per-country scope of filings.

parent_id and in_group on a Norwegian CompanyReport describe Enhetsregisteret's own parent/sub-unit relation for that entity — nothing more. There is no group-walk tool: following a corporate group upward means calling lookup_company again on parent_id, repeatedly, and that walk answers "what does the register list as this entity's parent?", not "who beneficially owns or controls this company?" — a different question this service does not answer. include=["parents"] above is a different route to an adjacent fact — GLEIF's opinion of who consolidates this entity's accounts, not Enhetsregisteret's own hierarchy and not beneficial ownership either. See llms-full.txt §5.

Why an agent checks a company

Three rules make this a duty rather than a nicety. Finanstilsynet's Rundskriv 15/2019 § 4.4.1 accepts an oppslag against Enhetsregisteret no older than three months, one month where the check rests on company details the customer supplied, and asks for notoritet about the lookup: what was consulted, and when. From 1 January 2027 Norwegian bookkeeping-obliged businesses must invoice each other by e-invoice, and the receiver is resolved in ELMA as 0192: plus organisasjonsnummer, the identifier these tools already take. From 10 July 2027, AMLR Article 23(4) requires "valid proof of registration or a recently issued excerpt of the register" for every new business relationship with a legal entity.

That is what source_url, fetched_at, cached, license and applies_because are for: which record was consulted, when it was read, whether it came from the 24 h cache, the terms it travels under, and whether a deadline was quoted from the register or computed from a named rule.

The limits, stated rather than implied: no sanctions or PEP screening; no bank-account verification, and the commonest invoice fraud is payment redirection, where the supplier is real and only the account number is wrong; and no beneficial owners, which brreg releases on application only, to categories of applicant that do not include a product vendor. Fuller version in llms-full.txt §9.

Configuration

Claude Code — .mcp.json in the project root
json
{  "mcpServers": {    "registry-mcp": {      "command": "uvx",      "args": ["registry-mcp"],      "env": {        "REGISTRY_MCP_CONTACT_EMAIL": "[email protected]",        "COMPANIES_HOUSE_API_KEY": "your-companies-house-key"      }    }  }}

Drop COMPANIES_HOUSE_API_KEY if you only need Norway; every other country works without it.

Cursor — ~/.cursor/mcp.json (or .cursor/mcp.json in the project)
json
{  "mcpServers": {    "registry-mcp": {      "command": "uvx",      "args": ["registry-mcp"],      "env": { "REGISTRY_MCP_CONTACT_EMAIL": "[email protected]" }    }  }}
Claude Desktop — claude_desktop_config.json

macOS: ~/Library/Application Support/Claude/claude_desktop_config.json Windows: %APPDATA%\Claude\claude_desktop_config.json

json
{  "mcpServers": {    "registry-mcp": {      "command": "uvx",      "args": ["registry-mcp"],      "env": { "REGISTRY_MCP_CONTACT_EMAIL": "[email protected]" }    }  }}
npm instead of uvx — same server, Node launcher
json
{  "mcpServers": {    "registry-mcp": { "command": "npx", "args": ["-y", "registry-mcp"] }  }}

npx registry-mcp shells out to uvx registry-mcp (falling back to pipx run registry-mcp), so Python 3.12+ and one of uv or pipx must be present.

Hosted, no local install — Streamable HTTP
json
{  "mcpServers": {    "registry-mcp": { "type": "http", "url": "https://api.foretak.dev/mcp?src=readme" }  }}

Environment variables — all optional:

VariableMeaning
REGISTRY_MCP_CONTACT_EMAILContact address sent in the User-Agent to the national registry, as Brønnøysundregistrene asks of API clients. Unset means an anonymous client, which may be throttled or blocked upstream.
REGISTRY_MCP_CACHE_PATHPath to the local SQLite response cache (24 h TTL). Defaults to ./data/cache.sqlite3.
COMPANIES_HOUSE_API_KEYRequired for the United Kingdom (GB). A key is free and instant. Unset means GB lookups return upstream_error with a hint naming this variable — every other country keeps working.
BOLAGSVERKET_CLIENT_ID, BOLAGSVERKET_CLIENT_SECRETRequired for Sweden (SE). An OAuth 2 client pair Bolagsverket issues on request; the data itself is free under the EU high-value-datasets regulation. Unset means SE lookups return upstream_error naming both variables — every other country keeps working.

REST

Every tool has a REST twin returning the identical JSON document.

bash
# One company by organisasjonsnummercurl https://api.foretak.dev/v1/NO/company/923609016
# Search by namecurl "https://api.foretak.dev/v1/NO/search?q=equinor&limit=5"
# Statutory filing deadlines, from a date you choosecurl "https://api.foretak.dev/v1/NO/company/923609016/deadlines?today=2026-01-15"
# Checksum-validate an identifier — no upstream call, instantcurl https://api.foretak.dev/v1/NO/validate/923609016
# Which countries are live, and which need an API keycurl https://api.foretak.dev/v1/countries
# The same five routes for the United Kingdom — GB, never UKcurl https://api.foretak.dev/v1/GB/company/00445790curl "https://api.foretak.dev/v1/GB/search?q=tesco&limit=5"curl "https://api.foretak.dev/v1/GB/company/00445790/deadlines?today=2026-09-04"curl https://api.foretak.dev/v1/GB/validate/445790
# Sweden — four of the five. /search returns 501 not_implemented: the free# Bolagsverket API has no name index, and the error's hint says so.curl https://api.foretak.dev/v1/SE/company/5560160680curl "https://api.foretak.dev/v1/SE/company/5560160680/deadlines?today=2026-09-07"curl https://api.foretak.dev/v1/SE/validate/556016-0680

Machine-readable docs: /llms.txt, /llms-full.txt, /openapi.json.

Adding your country

Norway is one folder. So is the United Kingdom: registries/gb/ was added as four files and one import line, and GB appeared in list_countries, in every tool, in /openapi.json and in registry://rules/GB on its own. So is Sweden — registries/se/ shipped in 0.3.0 with no change to core/, including the parts of Sweden that fit the abstraction worst: a register that publishes no status field, an operation the upstream does not offer (search_company answers not_implemented), and an identifier that can be a natural person's national ID.

Copy src/registry_mcp/registries/xx/ to registries/<cc>/, implement four methods, add one import line — nothing in core/ changes, and both surfaces plus the manifests light up for the new country automatically.

→ CONTRIBUTING.md — "Add your country", and the new country issue template to claim one first.

Development

bash
uv sync --all-extrasuv run pytest          # `-m "not live"` to skip the tests that hit the real registryuv run mypy .uv run ruff check .

Layout:

src/registry_mcp/core/        country-neutral models, Registry ABC, rules, date helperssrc/registry_mcp/registries/  one folder per country — no/ (Norway), gb/ (UK), se/ (Sweden), xx/ (template)src/registry_mcp/api/         FastAPI REST surfacesrc/registry_mcp/mcp/         FastMCP server (stdio + Streamable HTTP at /mcp)

Documents

Data source and licence

Norwegian data comes from Enhetsregisteret (Brønnøysundregistrene), published under NLOD 2.0 — attribution required. UK data comes from the Companies House public register, Crown copyright, free to re-use with no attribution condition; we cite it anyway. Swedish data comes from Bolagsverket, with Statistics Sweden (SCB) as a second producer inside the same payload, free to re-use as a värdefull datamängd under the EU high-value-datasets regime — Bolagsverket's own words are "Det krävs inget avtal för att du ska få använda vårt API för värdefulla datamängder" and "Värdefulla datamängder är avgiftsfritt" — and Bolagsverket names no licence, so neither do we: the license string states the permission and states plainly that there is no licence name to quote. Every response carries source, source_url and license so the attribution travels with the data. This project's own code is MIT licensed. Not affiliated with or endorsed by Brønnøysundregistrene, Companies House or Bolagsverket.

來源:README.md,提交 1aba1ff

工具

0
工具後設資料尚未被收錄。

版本歷史

1
  1. v0.4.2最新Sep 16, 2026