ARES Explorer
by duzidavid
README.md
# ARES Explorer π¨πΏ β MCP App
> **What is this?** An [MCP App](https://modelcontextprotocol.io/extensions/apps/overview) that turns the Czech business register (ARES) into an **interactive relationship graph**. Ask Claude about a company and a force-directed graph of its management and ownership ties renders right in the chat β then **click any company node to expand it live** and watch the network grow. Built with the MCP Apps SDK, a serverless MCP endpoint (`mcp-handler` on Vercel), and D3.
  
---
## How to use it in Claude
> **This is not a website you click on β it's an MCP App connector.** You don't open it in a browser; you add it to Claude as a custom connector and then talk to it in chat.
**Connector endpoint:**
```
https://ares-explorer-mcp.vercel.app/api/mcp
```
Mind the **`/api/mcp`** path β that exact path *is* the MCP endpoint (Streamable HTTP). Opening the URL in a browser shows nothing useful; it's an MCP server, not a web page. The bare domain or any other path won't work as a connector.
**Connect it (one-time):**
1. In **Claude on web or desktop**, go to **Connectors** (now under the **Customize** section β it used to live under **Settings**).
2. Click **Add custom connector**.
3. Paste `https://ares-explorer-mcp.vercel.app/api/mcp` and confirm.
> **Requires a paid Claude plan** (Pro / Max / Team). Custom connectors are not available on the free plan.
> **Harmless sign-in warning.** While adding the connector, Claude may show an OAuth / sign-in message such as *"Couldn't register with sign-in service."* It's safe to ignore β just dismiss it. The server is public and needs no login, and the tools load regardless.
**Try it.** Once connected, ask in chat:
- *βUkaΕΎ graf vazeb firmy s IΔO 24130222."* (Show the relationship graph for the company with IΔO 24130222.) β then **click company nodes** and the graph grows live.
- *βVykresli vlastnickou strukturu firmy s IΔO 27604977."* (Draw the ownership structure of the company with IΔO 27604977.)
- *βNajdi firmu Seznam.cz a ukaΕΎ jejΓ vazby."* (Find the company Seznam.cz and show its ties.)
---
## What it is
**ARES Explorer** is an MCP App that takes the open data of the Czech [ARES](https://ares.gov.cz) register and builds an **interactive graph of a company's ties** β its statutory body, members/owners, and connected companies β rendering it directly inside Claude. It isn't just a static picture: **company nodes are clickable** and the graph grows live as the app calls back to the server (the "bidirectional loop" of MCP Apps).
Type something like *βUkaΕΎ mi vazby firmy s IΔO 24130222."* (Show me the relationships of the company with IΔO 24130222.) and you get a canvas where you can explore the ownership and personnel structure by clicking.
### Why it's interesting
- **An MCP App, not just a tool.** Most MCP servers return text. Here a full UI runs in a sandbox and initiates further tool calls on its own based on what the user does. Click a node β `app.callServerTool("expand-node")` β new data is merged into the graph.
- **Real open data.** No mocks β the server talks live to the public ARES REST API.
- **Tolerant parser.** The VR (*veΕejnΓ½ rejstΕΓk*, public register) JSON is deeply nested and varies by legal form. The parser therefore walks the record recursively and pulls out anything that _looks like_ a person (`jmeno` + `prijmeni`) or a connected company (`ico` + `obchodniJmeno`), inferring the role (jednatel, spoleΔnΓk, β¦) from context. Resilient to schema changes.
---
## What it looks like

*The expanded graph (55 nodes Β· 59 ties). The inspector lists the statutory-body members (Δlen statutΓ‘rnΓho orgΓ‘nu) of the selected company, Corporate Consulting a.s., each with a deep link out to ARES.*

*Click a person node for its detail: RenΓ© Sommer, born 1966, sits on the supervisory board (Δlen dozorΔΓ rady) of Kofola ΔeskoSlovensko a.s.*
- **Companies** = amber rounded square, **people** = teal circle (distinguished by both shape and color).
- A node with **`+`** can be expanded. The side inspector shows detail, ties, and a link out to ARES.
- The toolbar lets you paste any **IΔO** to attach another company to the graph.
---
## Architecture
```
βββββββββββββββββββββββββββββ MCP host (Claude) βββββββββββββββββββββββββββββ
β β
β company-graph(ico) sandboxed iframe (ui://) β
β β tool result (GraphData) ββββββββββββββββββββββββββββββββ β
β βΌ ββββββββββββββββββββββββββββΆ β D3 force graph β β
β βββββββββββββββ β node click ββββ β β
β β /api/mcp β βββ expand-node(ico) ββββ βββββββββββββββ callServerToolβ
β β (serverless)β ββββ GraphData βββββββββΆβ merge β graph grows β β
β ββββββ¬βββββββββ ββββββββββββββββββββββββββββββββ β
β β fetch β
ββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
βΌ
ARES REST API (ares.gov.cz)
β’ /ekonomicke-subjekty/{ico} β base record
β’ /ekonomicke-subjekty-vr/{ico} β public register (ties)
β’ /ekonomicke-subjekty/vyhledat β search by name
```
**Server tools**
| Tool | UI? | What it does |
|------|-----|--------------|
| `ares-search` | no | Najde firmy podle nΓ‘zvu, vrΓ‘tΓ IΔO. |
| `company-graph` | **yes** | OtevΕe interaktivnΓ graf vazeb (IΔO nebo nΓ‘zev). |
| `expand-node` | no | VrΓ‘tΓ podgraf jednΓ© firmy β volΓ‘ appka pΕi rozkliknutΓ uzlu. |
> Tool titles and descriptions are intentionally kept in Czech β the app and its data are Czech, so this is how they read to both the user and the model.
The **data contract** (`src/types.ts`) is shared by the server and the UI, keeping both sides in sync. The UI never talks to ARES itself β it only renders `GraphData` and asks the server to expand nodes.
---
## Architecture decision: from Express to serverless
The first version ran as a long-lived Express process exposing the MCP server over Streamable HTTP. It was rewritten as a single Vercel serverless function via `mcp-handler`'s `createMcpHandler` (`app/api/mcp/route.ts`). Streamable HTTP is the recommended MCP transport, so a stateless request/response function maps onto it cleanly β there's no socket to keep alive, and the platform autoscales with load. Vercel's Fluid compute keeps instances warm and reuses them across invocations, avoiding the cold-start lag of sleeping free-tier processes while still scaling down when idle. The one build artifact the function depends on β the app's UI HTML β is inlined into the bundle at build time, so there's no runtime filesystem dependency in the serverless environment.
---
## Tech stack
- **TypeScript** (server and UI), **strict** mode
- [`@modelcontextprotocol/ext-apps`](https://github.com/modelcontextprotocol/ext-apps) β MCP Apps SDK (server helpers + client `App` class)
- [`@modelcontextprotocol/sdk`](https://github.com/modelcontextprotocol/typescript-sdk) β MCP server + Streamable HTTP transport
- [`mcp-handler`](https://github.com/vercel/mcp-handler) + **Next.js 15** App Router β MCP over a single serverless function (`app/api/mcp/route.ts`), no long-lived process. Next.js is used purely as the routing + build wrapper for that one handler β no pages, no `next/*` imports in code. `react` / `react-dom` are pulled in only as Next's required peers, not used by the app (the UI is plain D3 in inlined HTML)
- **zod** β tool input validation
- **D3** (force-directed graph, zoom/pan, drag)
- **Vite** + `vite-plugin-singlefile` β the UI is bundled into one HTML file that the build inlines directly into the serverless function (no external origins β simple CSP)
---
## Local development
You need **Node.js 20+**.
```bash
npm install # .npmrc sets legacy-peer-deps (see "Deploy to Vercel")
npm run dev # vite build β inline UI into the function β next dev
```
The MCP endpoint then runs at **`http://localhost:3000/api/mcp`** (Streamable HTTP, POST). As with production, the **`/api/mcp`** path *is* the endpoint β `http://localhost:3000` on its own serves nothing useful (this is an API-only Next.js app).
Other scripts:
```bash
npm run build # production build: vite + inline + next build
npm run typecheck # tsc --noEmit
npm test # unit tests for the ties parser (no network)
```
### The inlined-HTML step
The route handler imports the UI from a **build-generated, gitignored** module β `app/api/mcp/route.ts` does `import { ARES_EXPLORER_HTML } from "./ares-explorer-html"`, and that file (`app/api/mcp/ares-explorer-html.ts`) only exists after the build emits it. Both `npm run dev` and `npm run build` regenerate it for you: they bundle the UI with Vite into `dist/ares-explorer.html`, then `scripts/inline-html.mjs` embeds it as a string into the module. The serverless function therefore has no runtime filesystem dependency β the HTML is part of the bundle.
> **Always start the dev server with `npm run dev`, not a bare `next dev`.** The npm script runs `build:app` + `inline` first; running `next dev` directly on a clean checkout skips those steps and Next will fail to compile on the missing `./ares-explorer-html` import. If you ever hit that, run `npm run dev` (or just `npm run build` once) to generate the module.
### Testing the server without Claude β MCP Inspector
To exercise the endpoint without wiring it into Claude, point the [MCP Inspector](https://github.com/modelcontextprotocol/inspector) at your local server:
```bash
npx @modelcontextprotocol/inspector
```
Connect it to `http://localhost:3000/api/mcp` (transport: Streamable HTTP) and you can drive the protocol directly β `initialize`, `tools/list`, `resources/list` β and call the tools, getting their results back as JSON. This is the fastest way to confirm the server is up and the tools are wired correctly.
> The Inspector only shows **raw JSON** β `company-graph` returns `GraphData`, not the rendered graph. The interactive D3 UI only paints inside an MCP host (Claude), so for the full visual experience use the tunnel flow below.
### Testing the full UI in Claude β via a tunnel
Because the graph renders only inside the MCP host's sandbox, to see the real UI you need to expose your local server over a public URL and add it to Claude as a second custom connector. A quick tunnel:
```bash
cloudflared tunnel --url http://localhost:3000
```
This prints a public `https://<random>.trycloudflare.com` URL. Add **`https://<random>.trycloudflare.com/api/mcp`** as an *additional* custom connector in Claude (same steps as [How to use it in Claude](#how-to-use-it-in-claude), just your tunnel URL) β keep the production connector too β and you can dogfood your local changes with the full clickable graph before opening a PR.
> `cloudflared` is installed separately (e.g. `brew install cloudflared`); there's intentionally no npm script for it, since tunnelling is an ad-hoc debugging step, not part of the build.
For host-free local debugging you can also drive the app with [`basic-host`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-host) from the ext-apps repo.
### Recommended workflow
1. **Iterate locally** with `npm run dev` + the MCP Inspector for fast JSON-level checks.
2. **Verify the real UI in Claude** through the cloudflared tunnel once the behaviour looks right.
3. **Open a PR** β only then does it go through CI and review. Production is reached strictly via merge to `main`, as described in [Who can deploy, and how](#who-can-deploy-and-how-contribution-process): PR β CI (typecheck + tests + build) β maintainer review β merge to `main` β production deploy. Nothing ships any other way.
---
## Deploy to Vercel
The project is an API-only **Next.js** app β the only route is the MCP endpoint `app/api/mcp/route.ts`, which serves Streamable HTTP via [`mcp-handler`](https://github.com/vercel/mcp-handler). No long-lived process, no state between requests.
**Via the dashboard:** import the repo at [vercel.com/new](https://vercel.com/new). The framework (Next.js) and build are detected automatically from `vercel.json`; nothing needs to be configured by hand. After deploy, the endpoint lives at `https://<project>.vercel.app/api/mcp`.
**Via the CLI:**
```bash
npm i -g vercel
vercel # preview deploy
vercel --prod # production deploy
```
What's wired up for deployment:
- **`vercel.json`** β `framework: nextjs`, `buildCommand: npm run build` (runs vite build β inline HTML β next build), and `maxDuration: 60 s` for the `/api/mcp` function.
- **`.npmrc`** with `legacy-peer-deps=true` β `mcp-handler@1.1.0` pins its `@modelcontextprotocol/sdk` peer to exactly `1.26.0`, whereas `ext-apps` requires `^1.29.0`. The APIs in use (`McpServer` + Streamable HTTP transport) are stable across those versions, so we stay on `1.29.x`. Vercel reads this file at install time too, so the same resolution applies in CI.
- **Inlined HTML** β the app's UI is embedded straight into the function at build time (see above), so the serverless environment never needs to read `dist/` from disk.
After deploying, add `https://<project>.vercel.app/api/mcp` as a custom connector in Claude (see [How to use it in Claude](#how-to-use-it-in-claude)).
### Who can deploy, and how (contribution process)
Production deploys are **gated** β they don't happen on a whim, and not everyone can trigger one.
- **`main` is protected.** No direct pushes for contributors. Every change lands through a pull request that must pass CI (`.github/workflows/ci.yml` β typecheck + tests + build) and get a maintainer's approving review before it can merge.
- **Production = a merge to `main`.** Vercel deploys `main` to production. Since only reviewed, CI-green PRs (or the maintainer's own pushes) reach `main`, nothing ships to production without the maintainer's sign-off.
- **Fork previews are not automatic.** Vercel's Git fork protection is on, so a PR from a fork won't build a preview until a maintainer authorizes it β outside code never builds in this project unprompted.
- **CLI production deploys (`vercel --prod`) are restricted** to the maintainer via Vercel team membership. This is the one path that bypasses GitHub, so team access is kept tight on purpose.
So a new contributor's flow is: **fork β branch β open a PR β CI runs β maintainer reviews and approves β maintainer merges β production deploys.** No task gets to production any other way.
---
## Project structure
```
ares-explorer-mcp/
βββ app/
β βββ api/mcp/
β βββ route.ts # MCP endpoint: createMcpHandler β tools + UI resource
β βββ ares-explorer-html.ts # build-generated (gitignored): inlined UI HTML
βββ scripts/
β βββ inline-html.mjs # embeds dist/ares-explorer.html into the function as a string
βββ ares-explorer.html # app entry HTML (inline styles)
βββ src/
β βββ types.ts # shared data contract (GraphData β¦)
β βββ ares.ts # ARES REST client + tolerant ties parser
β βββ mcp-app.ts # UI: D3 force graph, node expansion
βββ test/
β βββ graph.test.ts # parser unit tests (fixtures, no network)
βββ vercel.json # Vercel: framework, build, maxDuration
βββ next.config.mjs
βββ vite.config.ts
βββ tsconfig.json
βββ package.json
```
---
## Notes & limits
- ARES data is **for information only** and has no character of an official document (see the [ARES terms](https://ares.gov.cz)). The API is rate-limited to **500 requests/min**.
- The parser covers the most common forms (s.r.o., a.s., spolek). For exotic structures some ties may be missing β it deliberately omits rather than guesses. The logic is covered by unit tests.
- Subjects with no VR record (e.g. some sole traders / OSVΔ) show up as a standalone node with no ties.
- Graph depth is capped (~80 ties per record) to stay readable; deeper levels are filled in by expanding nodes.
## License
MIT β see [`LICENSE`](./LICENSE).
Data: Β© ARES / Ministry of Finance of the Czech Republic, provided as open data.
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues