prawo-pl-mcp
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-mcpFind 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 four 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:
Four tools instead of 42.
pl_list_sources,pl_search,pl_get_document,pl_call- 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).
| Name | Required | Description | Default |
|---|---|---|---|
| args | No | ||
| tool | Yes | ||
| source | Yes | ||
| arguments | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already include readOnlyHint, idempotentHint, and destructiveHint=false. The description adds meaningful context: parameters are passed verbatim, validation happens at the aggregator before forwarding, and specific error codes are enumerated. This enriches the behavioral model without contradicting annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-structured: a concise opening, practical examples, parameter details, and a list of errors. Slightly long for an escape hatch but every sentence contributes necessary information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a generic escape-hatch tool, the description is highly complete. It covers use cases, parameter semantics, how to discover native names, validation behavior, and error handling. The output schema is present, so return-value details are not needed.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description compensates well. It explains the 'arguments'/'args' alias and clarifies that either can be used (not both), and that keys must be native parameter names, directing to pl_list_sources for discovering them. Source and tool are self-explanatory given the context.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's function: 'call any native tool of any registered source.' It distinguishes itself from sibling unified tools by positioning as an 'escape hatch' for operations not covered, with concrete examples (citator, DPA statistics, etc.).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly tells when to use the tool: 'For operations the unified tools do not cover.' It also provides alternatives for finding native parameter names (pl_list_sources) and lists possible error cases, fully guiding the agent.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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).
| Name | Required | Description | Default |
|---|---|---|---|
| page | No | ||
| extra | No | ||
| source | Yes | ||
| document_id | Yes | ||
| page_number | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already mark the tool as read-only, idempotent, and non-destructive. The description adds substantial behavioral detail: pagination at ~5000 characters, the exact response fields (content, page, total_pages, has_more), the deprecated page_number alias, native pagination for isap, and a concrete extra example for eu-compliance. It also enumerates error types, enriching the transparency beyond the annotation baseline. No contradiction with annotations exists.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single dense paragraph, but every sentence conveys necessary information: pagination, native pagination, extra per source, and error codes. No filler or redundancy. While not broken into bullet points, its length is justified by the tool's complexity. It is front-loaded with the main purpose.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's high complexity (source-specific IDs, optional extra, pagination modes, error taxonomy), the description covers all functional aspects necessary for correct invocation. It includes edge cases like page_number conflicts, missing required extra, and upstream_error with guidance to search first. An output schema exists, so return-value details are not needed, and the description's pagination overview complements it.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description bears full responsibility for parameter meaning. It does this thoroughly: document_id is explained with source-specific formats, page is clarified as 1-based, extra is given a concrete example, and page_number is identified as a deprecated alias. Source is implicitly understood from pl_list_sources. This fully compensates for the empty schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The opening phrase 'Fetch the full document' uses a specific verb and resource, clearly distinguishing it from sibling tools like pl_search (search) and pl_list_sources (list sources). It also delineates scope by enumerating document types and referencing per-source ID formats.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage context is clear: it explains how to fetch documents and how pagination works, and it references pl_list_sources for document_id format and pl_search for pagination comparability. However, it does not explicitly state when to use this tool versus alternatives or provide 'when not to use' exclusions, though it hints at searching first before fetching.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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).
| Name | Required | Description | Default |
|---|---|---|---|
| group | No | ||
| source_id | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare readOnly, idempotent, and non-destructive hints, and the description adds valuable context: it explicitly states that the no-arg mode 'reads a local registry, spawns nothing' and that the source_id mode 'connects to that connector (lazy spawn on first use)'. It also discloses possible errors, adding transparency beyond the annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is dense and well-structured, using a first-sentence summary followed by mode-specific details and error notes. It is longer than necessary but every sentence provides useful information, making it efficient rather than verbose.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description covers both execution modes, optional filters, return values (catalog fields, live schemas), side effects (lazy spawn), and error cases. Given the tool's two-mode complexity and the presence of an output schema, this is a complete and self-sufficient description.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Despite 0% schema description coverage, the description thoroughly explains both parameters: source_id triggers connector inspection, group filters by 'pl' or 'eu', and no arguments yields the full catalog. It even illustrates the native_params mapping with an example, fully compensating for the schema's lack of descriptions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's function: 'List available Polish/EU legal data sources, or inspect one source's live tool schemas.' It specifies both a verb and resource, and distinguishes it from sibling tools like pl_search and pl_get_document by focusing on discovery and schema inspection.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explains when to use each mode: 'Without arguments' for the full catalog, 'With source_id' for live schemas, and 'Optional group filter' for narrowing by 'pl' or 'eu'. It also notes the use case for native tools not covered by unified tools, but does not explicitly name alternative tools or when-not-to-use scenarios.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
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).
| Name | Required | Description | Default |
|---|---|---|---|
| page | No | ||
| extra | No | ||
| limit | No | ||
| query | No | ||
| source | Yes | ||
| date_to | No | ||
| date_from | No | ||
| page_number | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Despite annotations already declaring readOnly/openWorld/idempotent behavior, the description adds substantial context: parameter normalization (0- vs 1-based pages), clamping of limit, verbatim merging of extra filters, and a comprehensive list of error codes. It discloses deprecated page_number alias and source-specific behaviors beyond the structured hints.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with the purpose sentence, then systematically covers parameter behavior in a compact block, followed by an error list. Every sentence carries operational value, and the length is appropriate for the tool's complexity.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description covers parameter semantics, error handling, source adapters, and cross-references to sibling tools (pl_list_sources and pl_get_document) for additional context. An output schema exists, so return-value details are not needed in the description; the provided information is complete for a tool of this complexity.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
With 0% parameter documentation in the schema, the description compensates fully by explaining date format, page numbering, limit clamping, extra native filters, and the deprecated page_number alias. Even though query and source are not individually defined, their meanings are self-evident from the schema and the first sentence.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with 'Unified search across any registered source (SAOS, NSA, ISAP, EUREKA, KIO, UODO...),' using a specific verb and resource scope. It clearly distinguishes itself from siblings like pl_get_document and pl_list_sources by focusing on search across sources.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description establishes when to use the tool ('Unified search across any registered source') and cross-references pl_list_sources for native parameter names, but it does not explicitly state when to avoid this tool or name direct alternatives beyond the sibling tool names. This is clear context without explicit exclusions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
4 tool updates
v0.1.3- First observed
pl_call - First observed
pl_get_document - First observed
pl_list_sources - First observed
pl_search
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
MCP server for 3M+ Polish companies — KRS & CEIDG financials, ownership, and industry search.
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.
Polish law: search statutes (ISAP), court rulings, verify citations. Free tier + paid plans + x402.
Related MCP Servers
- AlicenseAqualityBmaintenanceMCP 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.44MIT
- AlicenseAqualityCmaintenanceMCP server for Polish administrative case-law (NSA + regional administrative courts, CBOSA) with ECLI and verifiable citations.393 npmMIT
- AlicenseNot gradedqualityCmaintenanceEnables semantic search over Polish court judgments and legislative acts via MCP. Allows LLMs to retrieve legal documents using natural language queries.Apache 2.0
- AlicenseAqualityBmaintenanceMCP server for searching and retrieving over 550,000 Polish tax documents from the EUREKA system, providing verifiable citations with signatures and URLs.434 npmMIT