ares-mcp
# ares-mcp
[](https://github.com/Tomi5037/ares-mcp/actions/workflows/ci.yml)
[](https://www.python.org/)
[](LICENSE)
An **MCP server** that gives an LLM agent grounded access to **ARES**, the Czech
public business register: look a company up by its registration number (IČO),
search the register by name, check VAT registration, and validate an IČO offline.
> **Why it exists.** Ask a model about a Czech company and it will happily invent
> an address and a VAT number. That is fine in a chat and unacceptable in a
> back-office workflow — onboarding a client, checking a counterparty, filling a
> contract header. This server replaces the guess with a citable record from the
> state register: every answer carries a `source_url` a human can open.
---
## Tools
| Tool | What it does | Network |
|---|---|---|
| `lookup_company(ico)` | Full company profile: name, seat, legal form, incorporation date, VAT ID, CZ-NACE activities, registers | ARES |
| `search_companies(name, limit=10)` | Full-text search by business name, capped at 50 hits | ARES |
| `check_vat_registration(ico)` | Whether the subject is an active VAT payer, plus its VAT ID | ARES |
| `validate_ico_number(ico)` | Modulo-11 checksum validation and normalisation | none — pure logic |
Every tool returns a flat, documented JSON object. Failures come back as
`{"error": "...", "message": "..."}` instead of an exception, so the agent can
recover rather than abort the turn.
## Quick start
```bash
git clone https://github.com/Tomi5037/ares-mcp.git
cd ares-mcp
python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e ".[dev]"
pytest
```
Run the server directly — it speaks MCP over stdio, so it waits for a client:
```bash
ares-mcp # stdio (default)
ares-mcp --transport streamable-http # run it as an HTTP service instead
```
Built on the official [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk) 2.x.
All four tools are annotated `read_only_hint`, so a client can auto-approve them
without prompting on every call.
### Use it from Claude Code
```bash
claude mcp add ares -- /absolute/path/to/.venv/bin/ares-mcp
```
### Use it from Claude Desktop
`claude_desktop_config.json`:
```json
{
"mcpServers": {
"ares": {
"command": "/absolute/path/to/.venv/bin/ares-mcp"
}
}
}
```
Then ask, in plain language:
> *"Check IČO 04084063 — who is it, are they VAT registered, and what do they do?"*
```json
{
"ico": "04084063",
"name": "CETIN a.s.",
"legal_form": "Joint-stock company (a.s.)",
"address": { "city": "Praha", "region": "Hlavní město Praha", "postal_code": "19000" },
"established_on": "2015-06-01",
"vat_number": "CZ04084063",
"vat_registered": true,
"is_active": true,
"activities": [{ "code": "6110", "section": "J", "section_label": "Information and communication" }],
"source_url": "https://ares.gov.cz/ekonomicke-subjekty?ico=04084063"
}
```
## How it works
```
LLM client (Claude Code / Desktop)
│ MCP over stdio
▼
server.py 4 tools, input validation, error contract
▼
client.py httpx async, timeouts, retry + backoff, TTL cache
▼
models.py raw ARES JSON -> flat pydantic schema
▼
ARES REST API v3 (public, no API key)
```
Three decisions worth calling out:
1. **Validate before you call.** An IČO is checked locally with its modulo-11
checksum. A hallucinated or mistyped number never becomes an HTTP request,
and the agent gets a specific error instead of a generic 404.
2. **Normalise the payload.** ARES answers with deeply nested Czech keys and the
same record repeated once per source register. `models.py` flattens that into
one documented schema — fewer tokens for the model, and one place to change
when the upstream API moves.
3. **Cache and retry.** A TTL cache keeps a repeated lookup off the state API,
and transient 5xx/timeout responses are retried with exponential backoff.
A public service you do not own is a dependency you should be polite to.
## Configuration
All optional — ARES is a public API and needs no key.
| Variable | Default | Meaning |
|---|---|---|
| `ARES_BASE_URL` | `https://ares.gov.cz/ekonomicke-subjekty-v-be/rest` | API root |
| `ARES_TIMEOUT_SECONDS` | `10` | Per-request timeout |
| `ARES_MAX_RETRIES` | `3` | Attempts before giving up |
| `ARES_CACHE_TTL_SECONDS` | `900` | Cache lifetime |
## Tests
```bash
pytest --cov=ares_mcp # 45 tests, no network access
ruff check . && mypy src # lint and strict typing
```
HTTP is mocked with `httpx.MockTransport` against payloads recorded from the
live API, so the suite is deterministic and runs offline in CI. Covered:
checksum edge cases, cache hits, retry on 503 and on timeout, 404 mapping,
missing upstream fields, and the exact JSON contract of every tool.
## Limitations
- ARES exposes public register data only; personal data of natural persons is
returned by the upstream API in a limited form and this server does not
enrich or store it.
- The insolvency register (ISIR) is reported only as a presence flag, not with
case detail.
- Data is as fresh as ARES itself (`data_updated_on` is passed through).
## Roadmap
- [x] Streamable HTTP transport in addition to stdio
- [ ] `check_insolvency` backed by ISIR
- [ ] Bulk lookup for a list of IČO with concurrency limits
- [ ] Optional Redis cache for multi-process deployments
## License
MIT — see [LICENSE](LICENSE).
Data comes from [ARES](https://ares.gov.cz/), operated by the Czech Ministry of
Finance. This project is not affiliated with the Ministry.
TDQS
Scored across 4 tools
The tools are mostly distinct: validate_ico_number handles offline checksum validation, search_companies finds by name, and lookup_company returns a full profile. There is minor overlap between lookup_company and check_vat_registration since both accept an IČO and lookup_company already returns a VAT ID, but the descriptions make the focused VAT-status purpose clear.
All tool names follow the same imperative verb_object pattern in snake_case: validate_, lookup_, search_, check_. This makes the tool set highly predictable and easy for an agent to reason about.
Four tools is a well-scoped size for an ARES-focused MCP server. Each tool covers a distinct, useful operation without redundancy or unnecessary bloat.
The domain is Czech company registry lookups, and the set covers offline validation, name search, full company profile retrieval, and VAT registration status. There are no important dead ends for common workflows involving finding and validating Czech companies.