finlex-mcp
README.md
# finlex-mcp
**An MCP server that gives AI assistants direct, citable access to Finnish legislation.**
Ask Claude *"Mitä kuluttajansuojalaki sanoo etämyynnin peruuttamisesta?"* and instead of answering from memory (and possibly inventing a section number), it calls this server and answers with the actual, current text of KSL 6 luku 14 § — with the formal citation, the Finlex consolidation date, and a link to verify.

> **This is a retrieval tool, not legal advice.** It returns statute text with citations; it never interprets law. Always verify against [finlex.fi](https://www.finlex.fi).
## Demo
*"Mitä kuluttajansuojalaki sanoo etämyynnin peruuttamisesta?"* → Claude calls `search_statutes`, then `get_section` → answers with the real section text + citation.
<!-- TODO: docs/demo.gif — recorded in Claude Desktop -->
## What it demonstrates
A complete public-data pipeline, end to end:
| Stage | Implementation |
|---|---|
| **Acquisition** | [Finlex open data REST API](https://www.finlex.fi/en/open-data) — resolves and downloads the newest consolidated (ajantasainen) version of each statute ([`src/scripts/fetch.ts`](src/scripts/fetch.ts)) |
| **Structuring** | Akoma Ntoso (OASIS LegalDocML) XML parsed into a SQLite schema: statute → chapter → section, with amendment labels and full-text search ([`src/parse.ts`](src/parse.ts), [`src/scripts/ingest.ts`](src/scripts/ingest.ts)) |
| **Implementation** | A [Model Context Protocol](https://modelcontextprotocol.io) server (official TypeScript SDK) exposing three tools to any MCP host ([`src/server.ts`](src/server.ts)) |
| **Verification** | 15 scripted lookups through the real MCP interface, cross-checked against the live finlex.fi rendering — **[current accuracy: 15/15](VERIFICATION.md)**, regenerated by `npm run verify`, enforced in CI |
Zero runtime API costs: the server is pure local retrieval. No keys, no external calls, no LLM spend.
## Tools
| Tool | What it does |
|---|---|
| `search_statutes(query)` | Full-text search over the covered statutes; returns sections with citations and snippets. Handles Finnish inflection with stem-prefix matching. |
| `get_section(statute, chapter, section)` | Exact text of one pykälä, with formal citation, amendment label ("sellaisena kuin se on laissa 1211/2013"), and any confirmed-but-not-yet-in-force wording explicitly flagged. |
| `list_statute_structure(statute?)` | Without arguments: the exact dataset coverage. With a statute: its full table of contents. |
Every response ends with the source line: consolidation date, data-fetch date, and the finlex.fi link — so a downstream AI can always disclose how fresh its law is.
### Accuracy discipline (the interesting part)
- **Scope honesty.** v1 covers exactly one statute: **Kuluttajansuojalaki (38/1978)**. The tool descriptions state this coverage as exhaustive, and out-of-scope requests are refused with a pointer to finlex.fi instead of a guess (verified: [check #14](VERIFICATION.md)).
- **Pending amendments handled correctly.** Finlex consolidations include confirmed future changes (e.g. L 31/2026 rewrites KSL 7:10 effective 20.11.2026). The server returns the wording in force **today** as primary, and flags the upcoming wording as `EI VIELÄ VOIMASSA` (verified: [check #9](VERIFICATION.md)).
- **No hand-written accuracy claims.** [VERIFICATION.md](VERIFICATION.md) is generated by the verification script, which exits non-zero on any failure — CI fails if accuracy drops.
## Connect to Claude Desktop
Add to `claude_desktop_config.json` (Settings → Developer → Edit Config):
```json
{
"mcpServers": {
"finlex": {
"command": "node",
"args": ["/ABSOLUTE/PATH/TO/finlex-mcp/dist/server.js"]
}
}
}
```
Or run the container (no local Node needed):
```json
{
"mcpServers": {
"finlex": {
"command": "docker",
"args": ["run", "-i", "--rm", "finlex-mcp"]
}
}
}
```
Any other MCP host works the same way — the server speaks standard MCP over stdio.
## Build from source
```bash
npm ci
npm run fetch # download newest consolidations from Finlex open data
npm run ingest # parse XML -> SQLite (data/finlex.db)
npm run verify # regenerate VERIFICATION.md against live finlex.fi
npm start # serve over stdio
```
Or: `docker build -t finlex-mcp .` — the image ships a ready-built database from the committed XML snapshot.
Adding a statute is one line in [`src/config.ts`](src/config.ts) plus `npm run fetch && npm run ingest`.
## Data source & license
- Statute data: **[Finlex open data](https://www.finlex.fi/en/open-data)** (Ministry of Justice / Legal Register Centre), used under **[CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)** as published on [avoindata.fi](https://avoindata.suomi.fi/data/fi/dataset/finlex-laki-ja-oikeus-avoimena-linkitettyna-datana). Finnish statute text itself is exempt from copyright (tekijänoikeuslaki 404/1961 § 9).
- The fetch pipeline identifies itself with a User-Agent (required by the API) and paces requests politely.
- Finlex's consolidations are informational, not the authoritative source of law — the XML itself declares `FRBRauthoritative="false"`. Hence: verify against finlex.fi, and nothing this server returns is legal advice.
- Code: MIT © Anna Sebedach
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues