Skip to main content
Glama
handelsregister-mcp

handelsregister-mcp

by lexdoudkin

handelsregister-mcp

An open-source Model Context Protocol server for the German Commercial Register — handelsregister.de.

Give your AI agents first-class access to official German company data: search the register, read register extracts with management and capital, list filed documents, and pull the shareholder list (Gesellschafterliste) — all returned as structured data and tables, not raw HTML.

CI License: Apache 2.0 Python 3.10+ MCP Built in


„Das Internet ist für uns alle Neuland.“

In 2013, a German Federal Chancellor stood next to the US President and described the internet as Neuland — uncharted new territory. The line became a national meme.

More than a decade later, the official Handelsregister is a living monument to that Neuland era: a JavaServer Faces application that threads a server-side ViewState through every click, a lazy-loaded PrimeFaces document tree, a strict 60-requests-per-hour limit baked into its terms of use, and no public API whatsoever. Since 2022 the data is free and public by law — yet it stays locked behind a portal that no machine was ever meant to talk to.

That gap is exactly why MCP servers need to exist. An LLM agent can reason brilliantly about a company — if something hands it the company's data. This project is that something: a small, deterministic bridge from a relic of Neuland to the agents of today.


Features

  • 🔎 Company search — by name/keywords, with exact, all-keyword, and phonetic matching, plus fuzzy resolution that returns ranked suggestions when the name isn't exact.

  • 📄 Register extracts (AD / CD / HD) — parsed into structured fields: name, register number, seat, address, capital, business purpose, dates, and a management table (Geschäftsführer / Vorstand with birthdates).

  • 🧬 Structured XJustiz data (SI) — the machine-readable register payload, parsed.

  • 👥 Shareholders as a table — finds and downloads the filed Gesellschafterliste and extracts {shareholder, type, city, register, date_of_birth, shares, nominal_total_eur, percent}. Handles complex, multi-page, bilingual cap tables.

  • 🗂️ Filed-document register — list everything on file (shareholder lists, articles of association, annual accounts, …) and download any of it.

  • 🧾 OCR fallback — scanned/image-only PDFs are run through Tesseract so text still comes out.

  • ⏱️ Polite by design — a shared 60 req/hour limiter, on-disk caching, descriptive User-Agent.

  • 🧱 Fully deterministic — no LLM lives inside the server (see Design).

Related MCP server: brreg-mcp-server

How it works

handelsregister.de exposes no REST API — only a JSF/PrimeFaces web form. Every request must carry a session cookie and a server-generated ViewState through a multi-step submission. This server reproduces that browser flow with mechanize and turns the rendered HTML/PDF/XML into clean structured data.

Since 1 August 2022 (the DiRUG law), both searches and document downloads are free of charge.

The Handelsregister is public, but not a free-for-all to scrape:

  • Its Nutzungsordnung (terms of use, per §9 HGB) forbids more than 60 retrievals per hour, and the portal FAQ warns that automated mass querying may be treated as a criminal offence (§§303a, b StGB).

  • This server enforces a shared 60 requests/hour limit by default. Do not raise it to abuse the portal. For high-volume or commercial use, use a licensed data provider (e.g. OpenRegister, handelsregister.ai) instead of scraping.

  • Register data contains personal data (managing directors, shareholders, birthdates). Handle it under the GDPR and use it only for the informational purposes the register is intended for.

This is a tool for legitimate, measured lookups — diligence, research, journalism, compliance — not bulk harvesting.

Install

git clone https://github.com/lexdoudkin/handelsregister-mcp.git
cd handelsregister-mcp
python -m venv .venv && source .venv/bin/activate
pip install -e .

# optional: OCR for scanned documents
pip install -e ".[ocr]"
brew install tesseract tesseract-lang   # macOS; provides the Tesseract binary + German pack

License note on the OCR extra: [ocr] pulls in PyMuPDF, which is AGPL-3.0 / commercial dual-licensed. The core package (without [ocr]) is fully permissive. If you redistribute a product built on the OCR path, mind AGPL's terms or obtain a commercial PyMuPDF license.

Connect it to an MCP client

Claude Code

claude mcp add handelsregister -- handelsregister-mcp

Claude Desktop — add to your MCP config (see examples/claude_desktop_config.json):

{
  "mcpServers": {
    "handelsregister": {
      "command": "handelsregister-mcp",
      "env": { "HANDELSREGISTER_MAX_PER_HOUR": "60" }
    }
  }
}

Then just ask your agent things like "Who are the shareholders of Trade Republic Bank GmbH?" or "Get the register details and managing directors of GASAG AG."

Tools

Every tool returns structured data — search rows, parsed company fields, and shareholder/management tables — plus a ready-to-render markdown table for document tools.

Tool

What it does

search_company(keywords, match="all", similar=False, max_results=20)

Search by name/keywords. match ∈ all | min | exact. similar=True enables phonetic matching. * and ? wildcards.

get_company(name)

Name lookup. Exact name → the record; inexact/ambiguous → found: false + ranked suggestions.

get_shareholders(company, which="latest")

Shareholders as a table — resolves the name, downloads the filed Gesellschafterliste, and extracts structured rows.

list_filed_documents(company)

List everything filed in the DK register, grouped by category, with dates.

fetch_filed_document(company, category, which="latest")

Download any filed document by category (+ shareholder table for share lists).

fetch_document(keywords, document_type="AD", ...)

Download a register extract. AD/CD/HD → company fields + management table; SI → XJustiz data.

rate_limit_status()

Remaining requests in the current hour.

Document types: AD current extract · CD chronological extract · HD historical extract · SI structured XML (XJustiz) · VÖ announcements · UT holder data. (DK, the filed-documents register, is reached via list_filed_documents / get_shareholders / fetch_filed_document.)

Example: get_shareholders

// get_shareholders("Trade Republic Bank GmbH")
{
  "company": { "name": "Trade Republic Bank GmbH", "register_number": "HRB 244347" },
  "source_document": { "label": "List of shareholders … on 15/06/2026", "date": "2026-06-15" },
  "method": "pdfplumber",
  "confidence": "high",
  "stammkapital_eur": 96293600.0,
  "shareholders": [
    { "shareholder": "Accel Holdings-TR LLC", "type": "company", "city": "Palo Alto, USA",
      "nominal_total_eur": 13008200.0, "percent": "12,7936%" },
    { "shareholder": "Creandum V, L.P.", "type": "company", "city": "St. Peter Port",
      "nominal_total_eur": 12936200.0, "percent": "12,7228%" }
    // … 74 shareholders total
  ]
}

Fuzzy name resolution

get_company and get_shareholders don't need the exact registered name. Exact match → they proceed; otherwise they fall back to keyword + phonetic search, rank the candidates, and return suggestions — e.g. get_shareholders("Trade Republic") → ["Trade Republic Bank GmbH", "Trade Republic Service GmbH", …].

Shareholder extraction (layered & deterministic)

The Gesellschafterliste has no standard layout, so extraction is layered — strongest engine first:

  1. Coordinate-aware table parsing (pdfplumber) — rebuilds columns from the PDF's ruling lines / text positions. Handles complex and bilingual cap tables (e.g. Trade Republic's 74-shareholder German/English list) that flat text extraction garbles.

  2. Text heuristic — the standard single-language notarial template.

The method field tells you which engine produced the table.

Shareholders are not in the register extract for a GmbH/UG — they exist only in this separately filed list, which get_shareholders downloads and parses.

Design: a deterministic tool

An MCP server is consumed by an LLM agent — so it would be redundant (and non-deterministic) to call another LLM inside it. This server never calls an LLM. When neither parser is confident (confidence: "low"), it doesn't guess: it returns the raw_text and the downloaded PDF path, and the calling agent — which is already an LLM — reads those and extracts the table itself. Intelligence lives in the caller; the tool just fetches data, faithfully.

OCR is the one heavyweight step, and it's deterministic too: scanned PDFs are rasterised and run through Tesseract (German) so text still comes out.

Configuration

Env var

Default

Purpose

HANDELSREGISTER_MAX_PER_HOUR

60

Hourly request cap. Do not exceed the portal limit.

HANDELSREGISTER_DOWNLOAD_DIR

system temp dir

Where downloaded documents are written.

HANDELSREGISTER_OCR

auto

OCR for scanned PDFs: auto (only when the text layer is empty), always, or off.

HANDELSREGISTER_OCR_LANG

deu

Tesseract language(s), e.g. deu+eng.

Use as a library

from handelsregister_mcp import HandelsregisterClient

client = HandelsregisterClient()
hits = client.search("GASAG AG", match="exact")
doc = client.fetch_document(hits[0]["row_index"], "AD")
print(doc["structured"]["management"])

Project layout

src/handelsregister_mcp/
├── server.py      # MCP tools (FastMCP)
├── client.py      # the JSF/PrimeFaces portal client (search + documents)
├── parsers.py     # register extract, XJustiz SI, shareholder-list parsing
├── ocr.py         # optional Tesseract OCR fallback
└── ratelimit.py   # shared 60/hour limiter

Known limitations

  • Scanned tables: OCR recovers text but not table structure — those parse low-confidence and return raw_text for the caller. Coordinate-based OCR table reconstruction (Tesseract TSV) is a possible future improvement.

  • Pagination: fuzzy suggestions are drawn from the first results page; a very common partial query may miss a match that sits on a later page.

  • Portal markup drift: the document-register flow depends on the live HTML; if the portal changes, parsing may need a tweak. PRs welcome.

Contributing

Issues and pull requests are welcome — especially for parser robustness across the many notarial Gesellschafterliste templates, and for additional register document types. Please keep changes deterministic and respectful of the portal's rate limits.

Credits

The portal search flow is adapted from the excellent bundesAPI/handelsregister / deutschland project (Apache-2.0), part of the bund.dev effort to document and open up Germany's public APIs.

License

Apache-2.0.

Disclaimer

Not affiliated with the Handelsregister, the German Federal States, or any official body. Use it lawfully and within the portal's terms of use. The maintainers accept no liability for how the data is obtained or used.

Available Tools

7 tools
fetch_documentA

Retrieve a register document/extract for a company and return its text.

Runs a search, picks result_index from the hits, then downloads the requested document type from that same portal session. Document types: AD - current extract CD - chronological extract HD - historical extract DK - filed documents register SI - structured XML data VÖ - announcements UT - holder data

Returns the local file path, content_type, size_bytes, and (for PDF/XML) extracted text. Document retrieval is the most fragile part of the portal flow; if it fails, the error explains what happened.

Inputs: keywords (required), document_type, match, result_index.

fetch_filed_documentA

Download a filed document of a given category from the DK document register.

category is matched case-insensitively as a substring against the categories from list_filed_documents (e.g. "shareholders", "articles", "annual"). Returns the local path and extracted text; for shareholder lists it also parses the table.

Inputs: company (required), category (required), which, match.

get_companyA

Look up a company by name and return the best match — or suggestions.

Tries an exact match first, then falls back to fuzzy + phonetic search. If the name is precise enough it returns the company; if it's ambiguous or only close, it returns found: false plus ranked suggestions so the caller can pick one.

Inputs: name (required).

get_shareholdersA

Retrieve a company's shareholders (Gesellschafterliste) as a structured table.

Resolves the company name (exact → fuzzy → phonetic; returns suggestions if ambiguous), locates the filed shareholder list (newest by default, or which="oldest"), downloads it, and extracts rows of {shareholder, type, city, register, date_of_birth, shares, nominal_total_eur, percent}.

Extraction is deterministic and layered: a coordinate-aware table parser (handles complex/bilingual cap tables), then a text heuristic. method reports which engine produced the result. On confidence: "low" the server does not guess — it returns raw_text and the PDF path so the calling agent can extract the table itself.

Shareholders are NOT in the register extract for a GmbH/UG — this filed list is the authoritative source.

Inputs: company (required), which.

list_filed_documentsB

List the documents filed for a company in the DK document register.

Returns the filed documents grouped by category — e.g. "List of shareholders", "Articles of Association / Rules / Statute", "Annual accounts / balance sheet" — each with the available dated entries. Use the category + fetch_filed_document (or get_shareholders) to download a specific one.

Inputs: company (required), match, result_index.

rate_limit_statusA

Report the remaining handelsregister.de request budget for this hour.

Inputs: no named parameters.

search_companyA

Search the German commercial register (Handelsregister) for companies.

Args: keywords: Company name or search terms. Wildcards * and ? are supported. match: How keywords are matched — "all" (contains every keyword, default), "min" (contains at least one), or "exact" (exact company name). similar: Enable the portal's phonetic ("ähnlich lautende") matching to tolerate typos and spelling variants. max_results: Cap on returned rows (the portal page holds up to ~100).

Returns a dict with the query echo, a result count, the remaining hourly request budget, and results: a list of companies with name, court, register_number, state, status, historical names, and available_documents (the document types that can be passed to fetch_document).

Inputs: keywords (required), match, similar, max_results.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4/5.0

    Scored across 7 tools

    Disambiguation5/5

    Each tool has a distinct purpose: search_company for searching, get_company for best-match lookup, fetch_document for general extracts, fetch_filed_document for specific filed documents, list_filed_documents for categories, get_shareholders for structured shareholder data, and rate_limit_status for budget info. No two tools do the same thing.

    Naming Consistency4/5

    Most tools follow a verb_noun pattern (fetch_, get_, list_, search_). The naming is all lowercase with underscores, but rate_limit_status is a noun_noun_noun exception. Overall consistent and readable.

    Tool Count5/5

    7 tools cover the German commercial register domain well: search, company details, multiple document retrieval types, filed document listing and download, and rate limit monitoring. The count feels well-scoped without unnecessary tools.

    Completeness5/5

    The tool set covers all major read operations: searching for companies, retrieving current/historical extracts, filed documents (including shareholder lists with structured output). No obvious gaps like missing document types or inability to fetch by register number.

    Maintenance

    ActivityInactive
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      B
      quality
      D
      maintenance
      Enables AI assistants to search the Swiss Central Business Name Index (Zefix) for companies by name or UID, with optional filters, and retrieve full company details including address, legal form, history, and representatives.
      1
      30 npm
      5
      MIT
    • A
      license
      A
      quality
      D
      maintenance
      Enables AI agents to search and retrieve Norwegian company data from the Brønnøysund Register Centre's open API, including company details, roles, and subunits.
      5
      10 npm
      MIT
    • A
      license
      A
      quality
      D
      maintenance
      Enables AI assistants to search and retrieve UK Companies House data including company profiles, officers, and filing history via the official API.
      4
      196 npm
      1
      MIT
    • A
      license
      A
      quality
      D
      maintenance
      Enables AI agents to verify and search business entities across US state and international company registries, providing real-time confirmation of legal existence, status, and filings.
      9
      MIT