prawo-pl-mcp
One MCP server that aggregates 10 independent Polish and EU legal-data connectors (case law, legislation, company register, tax rulings, procurement, DPA decisions, EU law) behind 5 unified tools.
pl_list_sources— browse the full source catalog without spawning anything, filter bygroup(pl/eu), or inspect one connector's live tool schemas plusnative_paramsmappings.pl_search— search any source with normalized parameters (query,date_from/date_toas YYYY-MM-DD, 1-basedpage,limit), passing source-specific native filters viaextra.pl_get_document— fetch a full judgment, act text, or register extract by source-specific identifier (numeric id, hex doc_id, ELI, KRS number, signature...), paginated at ~5000 chars/page withcontent,page,total_pages,has_more.pl_call— escape hatch to any native connector tool not covered by the unified ones (citator, DPA statistics, board composition, regulation comparison, category dictionaries), with parameter names validated against the connector's own schema.pl_coverage— see what each connector covers, when families were captured, what is explicitly not covered, and the fallback for every gap.Sources include Polish case law (SAOS, NSA/CBOSA), legislation (ISAP, 96k+ acts), company register (KRS), tax rulings (EUREKA, 550k+), procurement (KIO), GDPR enforcement (UODO), plus EU extras (Cellar SPARQL for EUR-Lex/CJEU, 14 offline EU regulations, and legalize's 32 versioned jurisdictions).
Consistent conventions everywhere: dates always
date_from/date_to, pages always 1-based (page_numbera deprecated alias),argsaliased toarguments.Deployable as stdio via
uvx prawo-pl-mcpor as remote Streamable HTTP with optional API-key auth; connectors are lazily spawned vianpx/uvx, can be individually overridden or timed out, and every call is logged to a JSONL audit trail.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@prawo-pl-mcp Find NSA rulings on environmental permits from 2023"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
prawo-pl-mcp
One MCP server for Polish legal data.
Ten independent connectors - case law, legislation, the company register, tax rulings, public procurement, data-protection decisions, plus EU extras - behind five tools. Each connector stays a separate package with its own repository; this server spawns them on demand and translates between their conventions.
"Prawo" is Polish for "law"; the name follows yargi-mcp's move of naming a country server in its own language.
Why an aggregator
The connectors cover Polish legal data well, but as separate MCP servers, one per database - ten installs and ten config entries before the first question. yargi-mcp showed the fix for Turkish law: 16 institutions, one server. prawo-pl-mcp does the same for Poland:
Five tools instead of 42.
pl_list_sources,pl_search,pl_get_document,pl_callandpl_coverage- fewer tool schemas means less context spent before the LLM starts working.One convention. The connectors grew organically:
dateFromhere,date_fromthere, pages counted from 0 or from 1 depending on the repo. Herepageis always 1-based and dates are alwaysdate_from/date_to(YYYY-MM-DD); the translation happens inside. One name per concept applies to the aggregator's own tools too:pageis the page parameter of bothpl_searchandpl_get_document(page_numberstill works as a deprecated alias), andpl_calltakesargumentsor its aliasargs.Native names on the table. Every catalog entry carries
native_params- the native parameter name each unified one maps to for that source, soarticle_number(eu-compliance) is visible without spawning the connector or reading an upstream error.pl_callchecks the names it was given against the connector's own schema and says which one it meant.Nothing preinstalled. Connectors run as subprocesses via
npx -y/uvx, downloaded on the first call to a given source and kept alive afterwards. A source that cannot start reports a readable error while the rest keep working.Paginated documents. Full judgment and act texts are chunked at ~5000 characters per page (yargi-mcp's pattern), so a 200-page ruling does not flood the context window. Sources that paginate server-side (
isap) get thepageforwarded natively and are reported aspagination: "native"- the aggregator does not re-cut an already-cut page and does not invent atotal_pagesit cannot know.
Related MCP server: mcp-nsa
Coverage
Source id | What | Institution / database | Connector | Runtime |
| Case law: common courts, Supreme Court, Constitutional Tribunal + citator | npx | ||
| Case law: administrative courts (NSA + 16 WSA) | npx | ||
| Legislation: Dziennik Ustaw + Monitor Polski, 96k+ acts | npx | ||
| Company register: extracts, history, board composition | npx | ||
| Tax rulings: 550k+ individual interpretations | npx | ||
| Public procurement: National Appeal Chamber rulings | uvx | ||
| GDPR enforcement: Polish DPA decisions, fines, stats | uvx | ||
| EU law: EUR-Lex by CELEX, CJEU by ECLI, GDPRhub | npx | ||
| 14 EU regulations offline (GDPR, AI Act, DORA, NIS2...) | Local SQLite corpus | npx | |
| Law-as-git: 32 jurisdictions, versioned by commit | uvx |
Polish sources are tagged group: pl, EU extras group: eu - filter with
pl_list_sources(group="pl").
Tools
Tool | What it does |
| Catalog of sources (no subprocess spawned). With |
| Search any source with normalized parameters; source-specific filters via |
| Full document by identifier (judgment id, ELI, KRS number, signature...), paginated. |
| Any native tool of any connector: citator, DPA statistics, board composition, regulation comparison... |
| Declare what this connector covers, when each family was captured, and - explicitly - what it does NOT cover. Every gap carries a fallback. |
The typical flow an LLM follows (spelled out in the server's instructions):
pl_list_sources → pl_search → pl_get_document, with pl_call for the
specialized tools each connector brings.
Install
Requires Python ≥ 3.11 plus the runtimes of the sources you use: Node.js ≥ 18
for npx sources, uv for uvx sources. If a
runtime is missing, its sources report source_unavailable and the rest work.
Claude Code
claude mcp add pl-legal -- uvx prawo-pl-mcpClaude Desktop / any MCP client (stdio)
{
"mcpServers": {
"pl-legal": {
"command": "uvx",
"args": ["prawo-pl-mcp"]
}
}
}Remote (Streamable HTTP)
uvicorn prawo_pl_mcp.asgi:app --host 0.0.0.0 --port 8000Open by default (public, read-only data). Set PRAWO_PL_MCP_API_KEY to require
X-API-Key: <key> or Authorization: Bearer <key> on every request.
Configuration
Env | Default | Meaning |
| - | Override the spawn command for a source, e.g. |
|
| Seconds allowed for a connector's first start (includes package download). |
|
| Seconds per tool call after startup. |
|
| Where the JSONL audit log goes. |
| - | ASGI mode only: require this API key (dual-channel). |
Architecture
The aggregator is a thin proxy (ADR 0001). Connectors are not imported, vendored or forked; the aggregator speaks MCP to them over stdio the same way any client would. The whole layer is a source registry (one dataclass entry per connector), a lazy subprocess pool, a parameter translator and a paginator. Adding a source means adding a registry entry.
LLM client ──MCP──▶ prawo-pl-mcp ──MCP/stdio──▶ npx @matematicsolutions/mcp-saos
│ ──MCP/stdio──▶ uvx kio-orzeczenia-mcp
│ ──MCP/stdio──▶ ... (spawned on first use)
└─ registry + param mapping + 5000-char paginationEvery call lands in a JSONL audit log (timestamp, tool, source, parameter hash, latency - never document content).
Development
git clone https://github.com/matematicsolutions/prawo-pl-mcp && cd prawo-pl-mcp
uv sync --extra dev
uv run pytest # offline tests: registry, dispatch mapping, pagination, drift
uv run prawo-pl-mcp # stdio serverLicense
Apache-2.0. Individual connectors carry their own licenses (MIT or Apache-2.0), their own rate limits and their own terms toward upstream databases - the aggregator adds no caching and no transformation beyond pagination, so each connector's constraints apply unchanged.
Available Tools
4 toolspl_callARead-onlyIdempotent
Escape hatch: call any native tool of any registered source.
For operations the unified tools do not cover: citator (saos_cite_check),
DPA statistics (uodo_stats), procurement-article search (kio_by_pzp_article),
board composition (get_board), regulation comparison (eu_compare), tax
category dictionary (list_categories)... arguments (alias: args - both
accepted, pass only one) is an object keyed by the NATIVE parameter names of
that tool, passed verbatim. The native names of the common ones are in
native_params from pl_list_sources; for everything else read the live
schema via pl_list_sources(source_id=...). Wrong parameter names are caught
here against the connector's schema, before the call leaves the aggregator.
Errors: unknown_source, unknown_tool (not exposed by that source),
invalid_arg (missing required or misspelled parameter name),
source_unavailable, upstream_error (connector's own error code in message).
Inputs: tool (required), source (required), args, arguments. Returns: result
pl_get_documentARead-onlyIdempotent
Fetch the full document (judgment, act text, register extract...) from a source.
document_id format depends on the source - numeric id (saos), hex doc_id
(nsa), ELI (isap), KRS number (krs), signature (kio, uodo)... - see the
document_id field in pl_list_sources. Long documents are paginated at
~5000 characters per page: response is JSON with content, page,
total_pages, has_more - iterate page while has_more (page is
1-based, exactly as in pl_search; page_number is a deprecated alias kept
for backward compatibility, and is echoed in the response for the same
reason). Sources that paginate the document themselves (isap) report
pagination: "native" and carry their own page footer inside content.
Some sources need extra (e.g. eu-compliance: {"regulation": "GDPR"}).
Errors: unknown_source, invalid_arg (missing required extra, page < 1,
conflicting page/page_number), source_unavailable, upstream_error
(e.g. connector's not_found - check the identifier or search first).
Inputs: source (required), document_id (required), page, extra, +1 more. Returns: result
pl_list_sourcesARead-onlyIdempotent
List available Polish/EU legal data sources, or inspect one source's live tool schemas.
Without arguments: full catalog (id, coverage, capabilities, native tool names,
and native_params - the NATIVE parameter name each unified parameter maps to,
e.g. eu-compliance pl_get_document -> {"tool": "eu_article",
"document_id": "article_number"}) - reads a local registry, spawns nothing.
With source_id: connects to that connector (lazy spawn on first use) and
returns its live tools/list with input schemas - needed for native tools that
the unified tools do not cover. Optional group filter: 'pl' or 'eu'.
Errors: unknown_source (bad source_id), source_unavailable (connector
could not start).
Inputs: group, source_id. Returns: result
pl_searchARead-onlyIdempotent
Unified search across any registered source (SAOS, NSA, ISAP, EUREKA, KIO, UODO...).
Common parameters are translated to the source's native names (dateFrom vs
date_from, 0- vs 1-based pages - handled here; page is ALWAYS 1-based, and
is the same parameter name in pl_get_document). date_from/date_to:
YYYY-MM-DD. limit: results per page (clamped to the source maximum).
extra: source-specific native filters merged verbatim (e.g.
{"courtType": "SUPREME"} for saos, {"country": "es"} for legalize - the
native names are listed under native_params in pl_list_sources).
page_number is a deprecated alias of page; prefer page.
Errors: unknown_source, invalid_arg (source has no search tool, e.g. krs;
missing required extra; page < 1), source_unavailable, upstream_error
(connector's own error code preserved in message).
Inputs: source (required), page, extra, limit, +4 more. Returns: result
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 4 tools
Each tool occupies a distinct layer: discovery (pl_list_sources), search (pl_search), retrieval (pl_get_document), and native access (pl_call). No overlap; pl_call is explicitly for operations not covered by the unified tools.
All tools share a consistent 'pl_' prefix with clear verb-based names (list_sources, search, get_document, call). The pattern is uniform and predictable, with no mixing of conventions.
Four tools is well-scoped for an aggregator server: it provides the essential operations plus an escape hatch for source-specific features, without bloat.
The tool set covers the full lifecycle of legal data access: discover sources, search, fetch documents, and call any native tool for specialized operations. The escape hatch ensures no dead ends.
Maintenance
Related MCP Connectors
Polish and EU law: statutes, rulings, UODO/UOKiK/KNF, EU regulations, citation checks. Free tier.
Brazilian legal stack in one MCP: lawsuits, court publications, case law, tenders, certificates.
Slovak court decisions as MCP tools. 12,000+ decisions, GDPR-compliant, pseudonymized, SLA-backed.
Brazilian legal search and jurimetria: 115M+ court decisions from 90+ courts across 14 MCP tools.
Related MCP Servers
- AlicenseAqualityDmaintenanceMCP server that provides access to Polish individual tax interpretations (KIS) via the official EUREKA API, enabling search and retrieval of rulings with signatures, theses, and full text.45MIT
- AlicenseAqualityCmaintenanceMCP server for Polish administrative case-law (NSA + regional administrative courts, CBOSA) with ECLI and verifiable citations.3111 npmMIT
- AlicenseNot gradedqualityDmaintenanceEnables semantic search over Polish court judgments and legislative acts via MCP. Allows LLMs to retrieve legal documents using natural language queries.Apache 2.0
- AlicenseAqualityCmaintenanceMCP server for searching and retrieving over 550,000 Polish tax documents from the EUREKA system, providing verifiable citations with signatures and URLs.4103 npm5MIT