Skip to main content
Glama

Law Scrapper MCP

A comprehensive Model Context Protocol (MCP) server for accessing and analyzing Polish legal acts from the Sejm API, enabling AI-powered legal research and document analysis.

Python version License Version

Features

  • Comprehensive legal act access - Full access to Polish legal acts from Dziennik Ustaw (DU) and Monitor Polski (MP)

  • Advanced search and filtering - Multi-criteria search by date, type, keywords, publisher, and status

  • Result Store with chained filtering - Store search results and filter with regex, type/status/year match, date ranges, sorting

  • Document Store pattern - Load acts into memory for efficient section-level navigation and search

  • Detailed document analysis - Metadata, structure, references, and content retrieval

  • Content processing - Automatic PDF-to-text and HTML-to-Markdown conversion

  • Date calculations - Specialized date utilities for legal document analysis

  • System metadata - Keywords, statuses, document types, and institution data

  • Official MCP SDK - Built with mcp[cli]==2.2.0 and MCPServer; STDIO and stateless Streamable HTTP transports

  • Async HTTP client - Efficient httpx client with retry logic and connection pooling

  • TTL caching - Intelligent response caching with configurable TTL

  • Structured logging - JSON and text log formats for easy debugging

  • Docker support - Containerized deployment with docker-compose

  • Comprehensive documentation - Examples and clear parameter descriptions

Related MCP server: Polish Law MCP Server

Requirements

  • Python: 3.13 or higher

  • Package manager: uv (recommended) or pip

  • Internet connection: Required for accessing Sejm API endpoints

  • MCP-compatible tool: Cursor IDE, Claude Code, or other MCP clients

Installation

# Clone the repository
git clone https://github.com/numikel/law-scrapper-mcp.git
cd law-scrapper-mcp

# Install dependencies
uv sync

# Install with dev dependencies
uv sync --extra dev

Using pip

# Clone the repository
git clone https://github.com/numikel/law-scrapper-mcp.git
cd law-scrapper-mcp

# Install dependencies
pip install -e .

From a GitHub Release (core and source modules)

Each release attaches the core and every source module as separate wheels, plus checksums.txt. A module requires the core of exactly its own version, so install both from the same release (replace X.Y.Z with the release version):

RELEASE=https://github.com/numikel/law-scrapper-mcp/releases/download/vX.Y.Z
pip install "$RELEASE/law_scrapper_mcp-X.Y.Z-py3-none-any.whl" \
            "$RELEASE/law_scrapper_mcp_voivodeship_journals-X.Y.Z-py3-none-any.whl"
# or: uv pip install with the same two URLs

An installed module is active; see Source modules to switch it off.

Using uvx (no installation required)

For quick testing without cloning the repository:

# Run the server directly from GitHub
uvx --from git+https://github.com/numikel/law-scrapper-mcp law-scrapper

Quick start

STDIO transport (default)

STDIO is the default transport for MCP communication. Start the server and connect from your MCP client:

# Run the server
uv run python -m law_scrapper_mcp

# Or use the installed script
law-scrapper

Configure in your MCP client (e.g., Cursor .cursor/mcp.json):

{
  "mcpServers": {
    "law-scrapper-mcp": {
      "command": "law-scrapper"
    }
  }
}

For Claude Code:

claude mcp add law-scrapper "uvx --from git+https://github.com/numikel/law-scrapper-mcp law-scrapper"

HTTP transport (streamable-http)

Run the server on HTTP with streamable-http transport:

# Run with HTTP transport on port 7683
LAW_MCP_TRANSPORT=streamable-http uv run python -m law_scrapper_mcp

# Or specify a custom port (the host stays on loopback unless you configure
# an authentication mode — see "Authenticated remote deployment" below)
LAW_MCP_TRANSPORT=streamable-http LAW_MCP_PORT=8080 uv run python -m law_scrapper_mcp

Configure in your MCP client:

{
  "mcpServers": {
    "law-scrapper-mcp": {
      "url": "http://localhost:7683/mcp",
      "transport": "streamable-http"
    }
  }
}

Note: The URL must include the /mcp path. The server exposes stateless Streamable HTTP at /mcp, not at the root. Using http://localhost:7683 without /mcp results in 404 (Not Found).

Docker

Build and run with Docker:

# Build the image
docker build -t law-scrapper-mcp .

# Run with STDIO transport (default)
docker run -it law-scrapper-mcp

# Run with HTTP transport on port 7683
docker run -it -p 7683:7683 -e LAW_MCP_TRANSPORT=streamable-http law-scrapper-mcp

Or use docker-compose:

# Run with STDIO transport
docker compose up

# Run with HTTP transport (set TRANSPORT=streamable-http in docker-compose.yml)
docker compose -f docker-compose.yml up

CI builds this image and smoke-tests both transports (/health over streamable-http, one initialize over STDIO) on every code change, so a Dockerfile that no longer builds fails the pull request rather than a downstream registry.

The image ships every released source module installed and switched off (LAW_MCP_SOURCE_MODULES=-*). Enable one with -*,<name>. Never use a bare <name>: it replaces -* and turns every other installed module on as well.

docker run -it -p 7683:7683 \
  -e LAW_MCP_HOST=0.0.0.0 \
  -e LAW_MCP_AUTH_MODE=bearer \
  -e LAW_MCP_AUTH_TOKEN="$(openssl rand -base64 32)" \
  -e 'LAW_MCP_SOURCE_MODULES=-*,voivodeship-journals' \
  law-scrapper-mcp

Configuration

All settings are configured via environment variables with the LAW_MCP_ prefix:

The list-valued settings — LAW_MCP_ALLOWED_HOSTS, LAW_MCP_ALLOWED_ORIGINS, LAW_MCP_TRUSTED_PROXIES, LAW_MCP_AUTH_REQUIRED_SCOPES and LAW_MCP_AUTH_ALGORITHMS — accept either a comma-separated value (a:*, b:*, the form used throughout this document) or a JSON array (["a:*", "b:*"]). Both are equivalent; surrounding whitespace is trimmed.

Variable

Default

Description

LAW_MCP_TRANSPORT

stdio

Transport: stdio or streamable-http

LAW_MCP_HOST

127.0.0.1

HTTP server host (when using streamable-http). Binding beyond loopback requires LAW_MCP_AUTH_MODE — see "Authenticated remote deployment"

LAW_MCP_PORT

7683

HTTP server port (when using streamable-http)

LAW_MCP_SHUTDOWN_GRACE

15

Graceful shutdown window in whole seconds for the HTTP server (a fractional value is rejected at startup rather than silently rounded). Keep stop_grace_period in docker-compose.yml at or above twice this value — nothing in the code enforces the relation.

LAW_MCP_API_TIMEOUT

30.0

HTTP request timeout in seconds

LAW_MCP_API_MAX_CONCURRENT

8

Concurrent light API requests (JSON metadata and search)

LAW_MCP_API_MAX_CONCURRENT_CONTENT

2

Concurrent heavy API requests (act HTML and PDF downloads)

LAW_MCP_API_RATE_PER_SECOND

5.0

Sustained outbound request rate towards the Sejm API; accepted range 0.1–100, non-finite values rejected

LAW_MCP_API_RATE_BURST

10

Requests allowed back-to-back before the rate applies; accepted range 1–1000

LAW_MCP_API_MAX_SERVER_PAUSE

60.0

Longest pause, in seconds, that a Retry-After sent by the Sejm API may hold all outbound traffic; a larger header value is clamped to this and logged. Accepted range above 0 up to 600

LAW_MCP_API_MAX_ATTEMPTS

3

Attempts per operation, retries included; accepted range 1–20

LAW_MCP_API_RETRY_BUDGET

45.0

Seconds the retry sequence of one operation may plan to wait

LAW_MCP_CACHE_METADATA_TTL

86400

Metadata cache TTL (24 hours)

LAW_MCP_CACHE_SEARCH_TTL

600

Search results cache TTL (10 minutes)

LAW_MCP_CACHE_BROWSE_TTL

3600

Browse results cache TTL (1 hour)

LAW_MCP_CACHE_DETAILS_TTL

3600

Act details cache TTL (1 hour)

LAW_MCP_CACHE_CHANGES_TTL

300

Changes tracking cache TTL (5 minutes)

LAW_MCP_CACHE_MAX_ENTRIES

1000

Maximum cache entries

LAW_MCP_DOC_STORE_MAX_DOCUMENTS

10

Maximum documents in Document Store

LAW_MCP_DOC_STORE_MAX_SIZE_BYTES

5242880

Maximum Document Store size (5 MB). Also the conversion threshold: content whose HTML or PDF payload exceeds it is refused with an error naming the source PDF URL — the download is aborted as soon as the body passes the limit (or earlier, from Content-Length), so the server never holds a larger body in memory.

LAW_MCP_DOC_STORE_TTL

7200

Document Store TTL (2 hours)

LAW_MCP_CIRCUIT_BREAKER_THRESHOLD

5

Failures before circuit breaker opens

LAW_MCP_CIRCUIT_BREAKER_RECOVERY_TIMEOUT

60.0

Seconds before trying recovery

LAW_MCP_CIRCUIT_BREAKER_HALF_OPEN_MAX_CALLS

3

Test calls in half-open state

LAW_MCP_MAX_PATTERN_LENGTH

512

Max filter_results pattern length, clamped to 64-4096

LAW_MCP_FILTER_MAX_RECORDS

100

Max records filter_results processes per call; floor 1, no ceiling (very high values lengthen the synchronous but linear scan)

LAW_MCP_ERROR_MESSAGE_MAX_CHARS

500

Cap on error-message length for categories whose message text is built from an exception's own string (validation, not_found, precondition, content_too_large, unavailable), which can be of unbounded length; accepted range 80-10000. Truncation is announced in the message rather than silent; a message that ends in a URL (the source PDF of an oversized act) keeps that URL whole when the rest of the text can absorb the cut, otherwise the plain cut applies so the cap always holds

LAW_MCP_ALLOWED_HOSTS

127.0.0.1:*, localhost:*, [::1]:*

Host header allowlist for streamable-http (DNS-rebinding protection). Widening beyond loopback requires an auth mode — see "Authenticated remote deployment"

LAW_MCP_ALLOWED_ORIGINS

http://127.0.0.1:*, http://localhost:*, http://[::1]:*

Origin header allowlist for streamable-http. Same auth-mode requirement as LAW_MCP_ALLOWED_HOSTS

LAW_MCP_AUTH_JWKS_URI

unset

Override the JWKS URI discovered from LAW_MCP_AUTH_ISSUER's OIDC discovery document; needed only when a provider's discovery document omits or misreports it

LAW_MCP_AUTH_REQUIRED_SCOPES

[] (none)

Scopes a presented token must carry, checked by RequireAuthMiddleware. Meaningful in oauth mode only: under bearer the static verifier would grant exactly these scopes to every holder of the token, so the check could never restrict anything — a non-empty value with LAW_MCP_AUTH_MODE=bearer is therefore rejected at startup

LAW_MCP_AUTH_ALGORITHMS

RS256, ES256

JWT signature algorithm allowlist passed to the decoder; never read from the token header

LAW_MCP_AUTH_JWKS_CACHE_TTL

3600

Seconds a fetched JWKS key set is cached before re-fetching

LAW_MCP_RATE_LIMIT_ENABLED

true

Whether the per-client rate limiter wraps the HTTP app. /health is exempt only when the caller is a loopback peer (container healthchecks); any other peer is metered like every other request

LAW_MCP_RATE_LIMIT_REQUESTS

60

Requests allowed per LAW_MCP_RATE_LIMIT_WINDOW before throttling

LAW_MCP_RATE_LIMIT_WINDOW

60.0

Rate limit window in seconds

LAW_MCP_RATE_LIMIT_BURST

10

Token bucket capacity — how many requests can arrive back-to-back before 429

LAW_MCP_LOG_LEVEL

INFO

Log level: DEBUG, INFO, WARNING, ERROR or CRITICAL (case-insensitive). Aliases such as WARN are rejected at startup on both transports instead of working on one and crashing the other. httpx's own request lines, which carry the full query URL, never go below WARNING regardless of this setting

LAW_MCP_LOG_FORMAT

text

Log format: text or json

LAW_MCP_CONTACT

https://github.com/numikel/law-scrapper-mcp

Contact written into the User-Agent of every request to an upstream host: an http(s):// URL or an e-mail address, in ASCII without spaces or parentheses, at most 200 characters. A URL needs a host and may not carry a user name or password (the value is sent as written to every upstream host). An e-mail is sent as mailto:<address>. An invalid or empty value stops startup; unset the variable to keep the default. See "Identity towards upstream hosts"

Logs go to stderr (stdout carries the MCP protocol on the STDIO transport). Every record carries a request_id correlating it with a single tool call — lifespan for records emitted outside one:

2026-08-24 12:00:00,123 - law_scrapper_mcp.tools.search - [a1b2c3d4] - INFO - Stored result set rs_1: 20 results (total 137)
{"timestamp": "2026-08-24T10:00:00.123456+00:00", "level": "INFO", "logger": "law_scrapper_mcp.tools.search", "request_id": "a1b2c3d4", "message": "Stored result set rs_1: 20 results (total 137)"}

Set LAW_MCP_LOG_LEVEL=DEBUG to recover the detail deliberately kept off INFO: search query text and the exception messages behind validation and upstream failures.

Example environment configuration:

export LAW_MCP_TRANSPORT=streamable-http
export LAW_MCP_PORT=7683
export LAW_MCP_LOG_LEVEL=DEBUG
export LAW_MCP_CACHE_METADATA_TTL=86400

Identity towards upstream hosts

Every request the server sends to an upstream host (the Sejm API and any host a source module talks to) carries the same identity header, so that a host's administrator can tell what is calling and reach the person responsible instead of blocking the address:

User-Agent: law-scrapper-mcp/<version> (+https://ops.example.org/contact)
User-Agent: law-scrapper-mcp/<version> (+mailto:ops@example.org)

LAW_MCP_CONTACT changes only the contact; the server name and version are the ones the server reports. The contact is yours to set, either as an http(s):// URL or as an e-mail address (the address is written into the header as mailto:<address>). A source module adds its own module/version token after the contact, so a host also sees which module generates its traffic.

Unset, the contact is this project's repository. That is fine for your own machine, but behind a shared or public deployment it sends complaints about your traffic to the project's authors. When the server listens beyond the loopback over HTTP and still sends the default contact, it logs one warning at startup asking you to set your own. A value that is not a plain URL or e-mail stops startup with a message naming the variable; the rejected value is not echoed.

export LAW_MCP_CONTACT=ops@example.org

Authenticated remote deployment

The HTTP transport binds 127.0.0.1 by default and refuses to start on any other address unless an authentication mode is configured. Two modes exist and neither falls back to the other.

Bearer token — local and simple deployments:

export LAW_MCP_AUTH_TOKEN=$(openssl rand -base64 32)   # min. 32 bytes
LAW_MCP_TRANSPORT=streamable-http \
LAW_MCP_HOST=0.0.0.0 \
LAW_MCP_AUTH_MODE=bearer \
  law-scrapper

In production prefer LAW_MCP_AUTH_TOKEN_FILE=/run/secrets/law_mcp_token — an environment variable is visible in docker inspect and /proc/<pid>/environ. Setting both sources is a startup error, not a precedence rule.

OAuth 2.1 / OIDC — corporate deployments. Works with any provider publishing OIDC discovery and JWKS:

Provider

LAW_MCP_AUTH_ISSUER

Microsoft Entra ID

https://login.microsoftonline.com/<tenant>/v2.0

Google

https://accounts.google.com

AWS Cognito

https://cognito-idp.<region>.amazonaws.com/<pool-id>

Okta

https://<org>.okta.com/oauth2/<server-id>

Auth0

https://<tenant>.auth0.com/

LAW_MCP_AUTH_MODE=oauth \
LAW_MCP_AUTH_ISSUER=https://login.microsoftonline.com/<tenant>/v2.0 \
LAW_MCP_AUTH_AUDIENCE=api://law-scrapper \
LAW_MCP_AUTH_RESOURCE_SERVER_URL=https://mcp.example.com/mcp \
  law-scrapper

Reachable through a reverse proxy — LAW_MCP_ALLOWED_HOSTS and LAW_MCP_ALLOWED_ORIGINS default to loopback-only (F18): a request whose Host or Origin header doesn't match gets 421/403 from the SDK's DNS-rebinding protection, even with a valid token. This default is deliberate — widening the allowlist is only permitted once an auth mode is configured (enforced at startup, D6). A reverse proxy that preserves the original Host (nginx's proxy_set_header Host $host, Caddy's default) needs both variables set to the public name, for either auth mode above:

LAW_MCP_ALLOWED_HOSTS='mcp.example.com:*' \
LAW_MCP_ALLOWED_ORIGINS='https://mcp.example.com' \
  law-scrapper

Providers issuing opaque tokens (GitHub) are not supported — they would require RFC 7662 introspection. TLS termination stays with the reverse proxy. /health is intentionally unauthenticated so container healthchecks work; it exposes the server version and circuit-breaker state. It is exempt from the rate limiter only for loopback peers — a probe arriving from another host is metered like any other request.

Rate limiting is always on for HTTP: 60 requests per 60 s, burst 10. Behind a proxy, set LAW_MCP_TRUSTED_PROXIES (addresses or CIDRs) — otherwise X-Forwarded-For is ignored and every client shares one bucket.

Source modules

The 13 tools below are the core. The server can also load source modules: separately installed packages that add tools for further legal-information sources. Every installed module is active unless LAW_MCP_SOURCE_MODULES switches it off. The variable is a comma-separated list read from left to right, where the last token about a module wins: -name disables a module, name enables it, -* disables all. To run only woj, write LAW_MCP_SOURCE_MODULES=-*,woj; a bare woj leaves the other installed modules on. A disabled module is never imported, and a module that fails to load stops the server with a message naming it.

With no module installed nothing changes. Module tools are described by each module and are not part of the tool table below. A module can also serve further publisher codes through the tools below instead of adding tools; modules/voivodeship-journals does this for the voivodeship official journals (POL_WOJ_*, for example POL_WOJ_PM/2025/5051), see its README. Conventions, the full grammar and the gates: docs/source-modules.md.

Tools reference

Law Scrapper MCP provides 13 tools for legal research and analysis:

Every tool is read-only (readOnlyHint=true, destructiveHint=false, idempotentHint=true), and openWorldHint=true means the tool calls api.sejm.gov.pl, while false means it never does: it works on data already held in the server's memory or computes its answer locally.

Tool

Title

readOnlyHint

openWorldHint

get_system_metadata

Metadane systemu ELI

true

true

search_legal_acts

Wyszukiwanie aktów prawnych

true

true

browse_acts

Przeglądanie aktów z rocznika

true

true

filter_results

Filtrowanie wyników wyszukiwania

true

false

get_act_details

Szczegóły aktu prawnego

true

true

read_act_content

Czytanie treści aktu

true

false

search_in_act

Wyszukiwanie w treści aktu

true

false

analyze_act_relationships

Powiązania aktu prawnego

true

true

track_legal_changes

Śledzenie zmian w prawie

true

true

calculate_legal_date

Obliczanie terminu prawnego

true

false

compare_acts

Porównanie metadanych dwóch aktów

true

true

list_result_sets

Zestawy wyników w pamięci

true

false

list_loaded_documents

Dokumenty załadowane do pamięci

true

false

1. get_system_metadata(category)

Retrieve system metadata for filtering and searching legal acts.

Parameters:

  • category (string, default: "all") - Metadata category: "keywords", "publishers", "statuses", "types", "institutions", "coverage", or "all"

Returns: Keywords, publishers, document types, statuses, and institutions available in the system; with "coverage", what each active source covers (newest document, count, years)

Examples:

- Get all available search keywords
- Check what each source covers (newest document, years) - category "coverage"
- Retrieve all legal document types
- List all publishers (DU, MP)
- Get all document statuses
- Get complete system metadata

Search for legal acts with advanced filtering options.

Parameters:

  • publisher (string) - Publisher code: "DU" (Dziennik Ustaw) or "MP" (Monitor Polski)

  • year (integer) - Publication year (e.g., 2024)

  • keywords (string) - Search keywords (AND logic - use multiple searches for OR)

  • detail_level (string, default: "standard") - Response detail: "minimal", "standard", or "full"

  • status (string, optional) - Document status filter

  • type (string, optional) - Document type filter

Returns: List of matching legal acts with metadata

Search note: Multiple keywords use AND logic. Search one keyword at a time for OR behavior.

Examples:

- Search DU 2024 for "environment protection" acts
- Find all MP 2023 acts with status "active"
- Search for COVID-19 related legislation
- Find acts by specific type (e.g., "regulation")
- Get minimal detail results for quick scanning

3. browse_acts(publisher, year, detail_level)

Browse all legal acts published in a specific year by publisher.

Parameters:

  • publisher (string) - Publisher code: "DU" or "MP"

  • year (integer) - Publication year

  • detail_level (string, default: "standard") - Response detail: "minimal", "standard", or "full"

Returns: Complete list of acts published in the specified year

Examples:

- Browse all DU acts from 2024
- Get minimal details of all MP acts from 2023
- Browse full details of DU 2022 legislation
- Get an overview of acts by publisher and year
- Track legislation published in a specific year

4. filter_results(result_set_id, pattern, field, type_equals, ...)

Filter and narrow down previously retrieved search/browse/changes results.

Parameters:

  • result_set_id (string) - Result set ID from a previous search/browse/changes call (e.g., "rs_1")

  • pattern (string, optional) - Regex pattern for text search (supports OR: "podatek|VAT|akcyza")

  • field (string, default: "title") - Field to search: "title", "eli", "status", "type", "publisher"

  • type_equals (string, optional) - Exact match on document type (e.g., "Ustawa", "Rozporządzenie")

  • status_equals (string, optional) - Exact match on status (e.g., "akt obowiązujący", "akt uchylony")

  • year_equals (integer, optional) - Exact match on publication year

  • date_field (string, optional) - Date field for range filter: "promulgation_date" or "effective_date"

  • date_from / date_to (string, optional) - Date range (YYYY-MM-DD)

  • sort_by (string, optional) - Sort field: "title", "year", "pos", "promulgation_date", etc.

  • sort_desc (boolean, default: false) - Sort descending

  • limit (integer, optional) - Maximum results to return

Returns: Filtered results with a new result_set_id for chained filtering

Examples:

- Filter search results to only "Rozporządzenie" type
- Search titles with regex "zdrow|apteka|lekar"
- Filter by date range and sort by promulgation date
- Chain filters: first by type, then by regex pattern
- Get top 10 most recent results

Result-set scope. Every stored result set declares whether it is the whole answer (complete) or a window cut from a larger corpus (page), in the result_set_scope field of the response and in list_result_sets. The distinction matters because filter_results narrows the set, not the query: an empty filter over a page set means "no match among these twenty records", not "no such act exists". Responses say so explicitly — a filter that matches nothing over a window sets no_match_is_inconclusive. track_legal_changes is the one tool whose set is always complete, because it stores the whole fetched range.

5. get_act_details(eli, load_content, detail_level)

Retrieve detailed information about a specific legal act and optionally load its content.

Parameters:

  • eli (string) - Act identifier in format "PUBLISHER/YEAR/NUMBER" (e.g., "DU/2024/1")

  • load_content (boolean, default: false) - Load act content into Document Store for section reading

  • detail_level (string, default: "standard") - Response detail: "minimal", "standard", or "full"

Returns: Act metadata (title, publication date, status, type, etc.), table of contents if load_content=true

content_status: Outcome of the content-loading half of this call, one of:

  • not_requested - load_content was not set and the act was not already in the Document Store

  • loaded - content is in the Document Store, ready for read_act_content / search_in_act

  • unavailable - the act permanently has no readable text (no HTML/PDF source, a 404 on either text fetch, or an empty extraction from either format); retrying will not change that, and the server remembers the absence for the metadata cache TTL rather than asking the API again

A transient upstream failure during loading (an open circuit breaker, a timeout, an HTTP 5xx from api.sejm.gov.pl) is not a content_status value — it fails the call itself with isError=true, distinct from content_status="unavailable". Retrying a transient failure may succeed; retrying an unavailable act will not.

Examples:

- Get metadata for act DU/2024/1
- Load act content for section-level reading
- Get full details including table of contents
- Retrieve act status and publication information
- Load multiple acts for comparison

6. read_act_content(eli, section)

Read content from a specific section of a loaded legal act.

Parameters:

  • eli (string) - Act identifier (must be loaded first via get_act_details with load_content=true)

  • section (string) - Section to read (e.g., "Art. 1", "Chapter 2", "Preamble")

Returns: Content of the requested section

Workflow note: Must call get_act_details(eli="...", load_content=true) first, then use this tool.

Examples:

- Read Article 1 from loaded act
- Get Chapter 2 content
- Read the Preamble section
- Access specific numbered articles
- Navigate act by chapters

7. search_in_act(eli, query)

Search for specific terms within a loaded legal act.

Parameters:

  • eli (string) - Act identifier (must be loaded first via get_act_details with load_content=true)

  • query (string) - Search term or phrase

Returns: Matching sections with context and location

Examples:

- Find all mentions of "penalty" in loaded act
- Search for specific legal terms
- Locate articles containing "fine" or "punishment"
- Find definitional sections
- Search for specific references

8. analyze_act_relationships(eli, relationship_type)

Analyze legal relationships and references of an act (amendments, references, etc.).

Parameters:

  • eli (string) - Act identifier

  • relationship_type (string, default: "all") - Type: "amends", "amended_by", "references", "referenced_by", or "all"

Returns: List of related acts and their relationships

Examples:

- Find which acts amend this legislation
- See what acts this legislation amends
- Get all legal references in the act
- Find acts that reference this legislation
- Analyze complete act relationship network

Track legal changes and new acts within a date range.

Parameters:

  • date_from (string) - Start date (YYYY-MM-DD format)

  • date_to (string) - End date (YYYY-MM-DD format)

  • publisher (string, optional) - Filter by publisher: "DU" or "MP"

  • keywords (string, optional) - Filter by keywords

Returns: Legal acts published in the date range

Examples:

- Track changes from 2024-01-01 to 2024-12-31
- Find new DU acts from last month
- Get changes published in past 7 days
- Track legislation on specific topics over time
- Monitor legal changes by publisher and date range

Calculate legal dates with intuitive sign convention.

Parameters:

  • days (integer, default: 0) - Days offset (+future, -past)

  • months (integer, default: 0) - Months offset (+future, -past)

  • years (integer, default: 0) - Years offset (+future, -past)

  • base_date (string, optional) - Base date (YYYY, YYYY-MM, or YYYY-MM-DD format, defaults to today)

Returns: Calculated date and relative description

Sign convention: Positive = future, Negative = past

Examples:

- Get current date (call with no parameters)
- Calculate date 30 days in the future (+30)
- Calculate date 6 months in the past (-6 months)
- Calculate date 1 year from a specific date
- Calculate legal deadlines and periods

11. compare_acts(eli_a, eli_b)

Compare metadata of two legal acts.

Parameters:

  • eli_a (string) - ELI identifier of the first act (e.g., "DU/2024/1692")

  • eli_b (string) - ELI identifier of the second act (e.g., "DU/2024/1716")

Returns: Comparison of titles, types, statuses, dates, keywords overlap and differences

Examples:

- Compare two acts from the same year
- Compare old and new versions of legislation
- Identify metadata differences between related acts

12. list_result_sets()

Display active result sets stored in memory.

Returns: List of result sets with IDs, query summaries, counts, and creation times

13. list_loaded_documents()

Display documents loaded into the Document Store.

Returns: List of loaded documents with ELIs, sizes, section counts, and timestamps

Document Store workflow

The Document Store pattern enables efficient content navigation and search within legal acts:

Workflow steps

  1. Load an act - Call get_act_details(eli="DU/2024/1", load_content=true) to load the act into the Document Store

  2. Read sections - Use read_act_content(eli="DU/2024/1", section="Art. 1") to read specific sections

  3. Search within act - Use search_in_act(eli="DU/2024/1", query="penalty") to find terms

Benefits

  • Efficient memory usage (configurable max documents and TTL)

  • Fast section-level navigation without refetching

  • Search within loaded acts without API calls

  • Automatic content processing (PDF→text, HTML→Markdown)

Configuration

  • LAW_MCP_DOC_STORE_MAX_DOCUMENTS - How many acts to keep in memory (default: 10)

  • LAW_MCP_DOC_STORE_MAX_SIZE_BYTES - Maximum memory usage (default: 5 MB)

  • LAW_MCP_DOC_STORE_TTL - How long to keep acts in memory (default: 2 hours)

Project structure

law-scrapper-mcp/
├── src/law_scrapper_mcp/
│   ├── __init__.py
│   ├── __main__.py              # Entry point for python -m
│   ├── server.py                # MCPServer, lifespan, transport config
│   ├── config.py                # Pydantic settings (env vars)
│   ├── logging_config.py        # Structured logging setup
│   ├── models/                  # Pydantic models
│   │   ├── enums.py            # Enumerations
│   │   ├── api_responses.py    # Sejm API response models
│   │   ├── tool_inputs.py      # Tool input models
│   │   └── tool_outputs.py     # Tool output models
│   ├── client/                  # HTTP client
│   │   ├── sejm_client.py      # AsyncClient with retry and circuit breaker
│   │   ├── cache.py            # Async TTL cache implementation
│   │   ├── circuit_breaker.py  # Circuit breaker for API protection
│   │   └── exceptions.py       # Custom exceptions (Polish messages)
│   ├── services/                # Business logic
│   │   ├── metadata_service.py    # Metadata retrieval
│   │   ├── search_service.py      # Search and browse
│   │   ├── act_service.py         # Act details and content
│   │   ├── changes_service.py     # Change tracking
│   │   ├── document_store.py      # In-memory act storage
│   │   ├── result_store.py        # Search result persistence and filtering
│   │   ├── content_processor.py   # PDF/HTML processing
│   │   └── response_enrichment.py # Response hints
│   └── tools/                   # MCP tool definitions
│       ├── annotations.py       # Shared ToolAnnotations presets (READ_ONLY_REMOTE / READ_ONLY_LOCAL)
│       ├── metadata.py          # get_system_metadata
│       ├── search.py            # search_legal_acts
│       ├── browse.py            # browse_acts
│       ├── act_details.py       # get_act_details
│       ├── act_content.py       # read_act_content
│       ├── act_search.py        # search_in_act
│       ├── relationships.py     # analyze_act_relationships
│       ├── filter_results.py    # filter_results, list_result_sets
│       ├── changes.py           # track_legal_changes
│       ├── compare.py           # compare_acts
│       ├── dates.py             # calculate_legal_date
│       └── error_handling.py    # Centralized @handle_tool_errors decorator
├── tests/
│   ├── unit/                    # Unit tests
│   └── integration/             # Integration tests with Sejm API
├── Dockerfile                   # Container image definition
├── docker-compose.yml           # Multi-service setup
├── pyproject.toml              # Project metadata and dependencies
├── uv.lock                      # Reproducible dependency lock
└── README.md                    # This file

Docker

Security and deployment

The server binds 127.0.0.1 by default; binding beyond loopback (as Docker port publishing requires) fails at startup unless an authentication mode is configured — see "Authenticated remote deployment" above. When exposing the HTTP transport (streamable-http) to a network, place the server behind a reverse proxy (nginx, Caddy, Traefik) with TLS termination — this project verifies bearer tokens and OAuth 2.1/OIDC access tokens, but does not terminate TLS itself. The /health endpoint is unauthenticated and intended for container healthchecks only — do not expose it publicly without access controls. Dependency versions are pinned in uv.lock with security overrides in pyproject.toml (cryptography, urllib3, idna, werkzeug, requests).

Host/Origin allowlist (DNS-rebinding protection): the official MCP SDK only auto-enables Host/Origin validation when the server binds to a literal loopback address (127.0.0.1, localhost, ::1). docker-compose.yml sets LAW_MCP_HOST=0.0.0.0 so Docker can publish the port, which would otherwise leave that validation disabled. server.py passes transport_security explicitly (build_transport_security()) so requests are still validated against the configured allowlist — Host outside it gets 421, Origin outside it gets 403 — independent of the bind address. The allowlist defaults to loopback only (127.0.0.1:* / localhost:* / [::1]:* and the matching http:// origins) and is widened through LAW_MCP_ALLOWED_HOSTS / LAW_MCP_ALLOWED_ORIGINS, which startup validation permits only once an authentication mode is configured. This restores the pre-3.0.0 FastMCP posture for /mcp. It is a defense-in-depth layer, not a substitute for the authentication mode required to bind beyond loopback in the first place.

One deliberate difference from the pre-3.0.0 server: the SDK applies this validation inside the Streamable HTTP app, not as whole-app middleware, so /health is not covered by the allowlist and answers any Host. That is what keeps container healthchecks working when they connect by container name or bridge IP, but it also means /health discloses the server name and version — and, since it now also reports the circuit breaker's circuit_state and failure_count, the health of the Sejm API integration — to anything that can reach the published port. FastMCP guarded /health too. Restrict the published port, or front it with a proxy, if that disclosure matters to you.

Health contract: the response body carries an upstream object. upstream.circuit_state is closed, open, half_open, or unknown before the lifespan has started (or after it has been torn down) — in that unknown case upstream.failure_count is omitted entirely; otherwise it sits alongside circuit_state. /health still answers 200 while the breaker is open: restarting the container cannot repair an outage of api.sejm.gov.pl, and under restart: unless-stopped a 503 would turn someone else's outage into a restart loop.

Dockerfile

The included Dockerfile builds a containerized Law Scrapper MCP server:

FROM python:3.13-slim
WORKDIR /app
COPY . .
RUN pip install -e .
EXPOSE 7683
CMD ["law-scrapper"]

Build and run:

# Build the image
docker build -t law-scrapper-mcp .

# Run with STDIO transport
docker run -it law-scrapper-mcp

# Run with HTTP transport, published on the host — the image no longer
# defaults LAW_MCP_HOST to 0.0.0.0, so publishing the port beyond loopback
# requires setting the host explicitly together with an authentication mode
docker run -it -p 7683:7683 \
  -e LAW_MCP_TRANSPORT=streamable-http \
  -e LAW_MCP_HOST=0.0.0.0 \
  -e LAW_MCP_AUTH_MODE=bearer \
  -e LAW_MCP_AUTH_TOKEN="$(openssl rand -base64 32)" \
  -e LAW_MCP_LOG_LEVEL=DEBUG \
  law-scrapper-mcp

docker-compose.yml

Deployment with docker-compose:

# LAW_MCP_AUTH_TOKEN is required — docker-compose.yml fails fast without it
export LAW_MCP_AUTH_TOKEN=$(openssl rand -base64 32)

# Start service
docker compose up -d

# View logs
docker compose logs -f

# Stop service
docker compose down

Migration guide (v1 to v2)

If upgrading from v1.0.2, note these breaking changes:

v1.0.2 (old)

v2.0.0 (new)

Notes

get_current_date

calculate_legal_date()

Call with no parameters for current date

calculate_date_offset

calculate_legal_date(days/months/years)

Use intuitive +future/-past sign convention

get_legal_keywords

get_system_metadata(category="keywords")

Consolidated into one tool

get_legal_publishers

get_system_metadata(category="publishers")

Consolidated into one tool

get_legal_statuses

get_system_metadata(category="statuses")

Consolidated into one tool

get_legal_types

get_system_metadata(category="types")

Consolidated into one tool

get_legal_institutions

get_system_metadata(category="institutions")

Consolidated into one tool

get_publisher_details

N/A

Use get_system_metadata(category="publishers")

search_legal_acts

search_legal_acts

Enhanced with detail_level parameter

get_publisher_year_acts

browse_acts

Renamed for clarity

get_act_comprehensive_details

get_act_details

Added load_content and detail_level

get_act_content

read_act_content

Requires pre-loading with get_act_details

get_act_table_of_contents

get_act_details

TOC included in details response

get_act_relationships

analyze_act_relationships

Renamed for clarity

ELI format

Single string "DU/2024/1"

Changed from separate parameters

Legacy event-stream transport

STDIO (default)

STDIO is default, HTTP via streamable-http

Port 7683

Port 7683

Same default HTTP port

What's new in v3.0.0

  • Official Python MCP SDK — Replaced the prior third-party framework with mcp[cli]==2.0.0 and MCPServer[AppContext]

  • Native structured responses — Tools return EnrichedResponse with outputSchema and object structuredContent (no JSON strings)

  • Protocol-visible errors — Tool failures surface as isError=true instead of in-body error fields

  • Stateless Streamable HTTP — /mcp endpoint only; legacy event-stream transport removed

  • Pagination — PageInfo model exposed as the page_info field with limit/offset on search, browse, metadata, changes, TOC, sections, and in-act search

  • Typed AppContext — Lifespan resources accessed via ctx.request_context.lifespan_context

  • Domain services — ComparisonService, RelationshipService, and DateService extracted from tool adapters

  • Transport tests — Real STDIO subprocess, loopback HTTP, and MCP conformance in CI

  • Explicit Host/Origin allowlist on HTTP — transport_security is passed explicitly so DNS-rebinding protection applies regardless of the configured bind host; see Security and deployment

Migration guide (v2 to v3)

v2.x (old)

v3.0.0 (new)

Notes

result.content JSON string

structuredContent object

Parse native MCP structured payload

EnrichedResponse.error in success body

isError=true

Check result.is_error before reading content

v2.x in-process client

Official mcp.Client

In-memory and transport tests use SDK client

v2.x direct lifespan access

ctx.request_context.lifespan_context

Typed AppContext dataclass

Legacy event-stream transport

Removed

STDIO or stateless Streamable HTTP only

What's new in v2.4.0

  • Security hardening — Framework upgrade and dependency overrides close 51 Dependabot alerts (cryptography, urllib3, pillow, starlette, and others)

  • Framework 3.x — Lifespan context API, app.run() for HTTP transport, @custom_route for /health

  • Integration tests — In-memory client tests for core tools (metadata, search, dates, act details)

  • CI and Dependabot — Automated quality gates and weekly dependency updates

What's new in v2.3.1

  • uvx / tool registration fix — Fixed NameError: name 'Annotated' is not defined when running via uvx --from "git+https://github.com/numikel/law-scrapper-mcp" law-scrapper. Removed from __future__ import annotations from compare.py so parameter type hints resolve correctly during tool registration.

What's new in v2.3.0

  • 3 new tools — compare_acts, list_result_sets, list_loaded_documents (total: 13 tools)

  • Circuit breaker — Protects against cascading failures when Sejm API is unavailable

  • Centralized error handling — @handle_tool_errors decorator with error classification and full tracebacks

  • asyncio.Lock migration — All stores use asyncio.Lock for proper async compatibility

  • Default search limit — Search/browse return max 20 results by default to limit token usage

  • Health endpoint — /health for Docker deployments with streamable-http transport

  • Polish error messages — All exception messages in Polish for consistent user experience

  • Decision tree docstrings — "When to use" / "When NOT to use" for all tools

Development

Setup

# Install dependencies
uv sync

# Install with dev dependencies
uv sync --extra dev

Running tests

# Run unit tests
uv run pytest tests/unit/ -v

# Run integration tests (mocked Sejm API via respx; no live network required)
uv run pytest tests/integration/ -v -m integration

# Run all tests with coverage
uv run pytest --cov=law_scrapper_mcp --cov-report=term-missing

# Run with timeout for slow tests
uv run pytest --timeout=10 -v

# Audit the locked dependencies for known vulnerabilities (same check as the CI audit job)
uv run pip-audit

MCP integration testing

Law Scrapper MCP uses three automated test layers to verify all 13 tools and their MCP protocol interaction:

Layer 1 — unit tests (tests/unit/):

uv run pytest tests/unit/ -v

Layer 2 — in-memory integration (official mcp.Client, mocked Sejm API via respx):

uv run pytest tests/integration/test_tools_e2e.py \
  tests/integration/test_content_pagination.py \
  tests/integration/test_result_pagination.py -v -m integration

Layer 3 — transport integration (real STDIO subprocess and loopback Streamable HTTP):

uv run pytest tests/integration/test_stdio_transport.py \
  tests/integration/test_http_transport.py -v -m integration

Full integration suite:

uv run pytest tests/integration/ -v -m integration

What the tests verify:

  • All 13 tools registered with preserved public arguments

  • Native structuredContent payloads with outputSchema (not JSON strings)

  • isError=true on tool execution failures

  • page_info (PageInfo) pagination metadata on paginated outputs

  • Stateful workflows: search → filter → load act → read sections → search in content

  • Real STDIO and HTTP transports against a running server process

Architecture:

  • Test framework: pytest with asyncio (unit) and anyio (MCP client tests)

  • HTTP mocking: respx intercepts httpx calls in in-memory integration tests

  • MCP protocol: Official mcp.Client from the Python MCP SDK

  • Helpers: tests/mcp_helpers.py (parse_tool_result for structuredContent assertions)

  • CI: Legacy dependency gate, protocol transport tests, and MCP conformance against /mcp

See tests/TEST_SUITE_SUMMARY.md for the full file layout and patterns.

Code quality

The project follows MCP SDK best practices:

  • Modular architecture - Separated concerns (models, client, services, tools)

  • Type hints - Full type annotation with Pydantic models

  • Async throughout - Async/await for all I/O operations

  • Comprehensive examples - Minimum 5 examples per tool

  • Tagged tools - Organized by category for easy discovery

  • Annotated parameters - Clear descriptions for all inputs

  • Structured logging - Configurable JSON/text formats

Running the server

# STDIO transport (default)
uv run python -m law_scrapper_mcp

# HTTP transport
LAW_MCP_TRANSPORT=streamable-http uv run python -m law_scrapper_mcp

# With debug logging
LAW_MCP_LOG_LEVEL=DEBUG uv run python -m law_scrapper_mcp

Contributing

  1. Fork the repository

  2. Create your feature branch (git checkout -b feature/amazing-feature)

  3. Commit your changes using Conventional Commits format

  4. Add tests for new functionality

  5. Ensure all tests pass and coverage is maintained

  6. Push to the branch (git push origin feature/amazing-feature)

  7. Open a Pull Request

Development guidelines

  • Follow MCP SDK patterns for tool definitions and structured output

  • Include comprehensive examples and parameter descriptions

  • Add appropriate tags for tool categorization

  • Write async code throughout

  • Add tests for all new functionality

  • Update CHANGELOG.md with your changes

  • Use English for all code comments and documentation

License

This project is licensed under the MIT License. See the LICENSE file for details.

Author

@numikel

Developed with help from:

Cursor Claude Code

And with models:

Claude Opus 4.6 Claude Opus 4.5 Claude Sonnet 4.5 Claude Haiku 4.5


Legal disclaimer: This tool provides access to Polish legal documents for research purposes. Always consult with qualified legal professionals for legal advice and interpretation of laws.

Available Tools

13 tools
analyze_act_relationshipsPowiązania aktu prawnegoA
Read-onlyIdempotent

Przeanalizuj powiązania i referencje między aktami prawnymi.

Zwraca informacje o aktach zmienionych, zmieniających, uchylonych, podstawie prawnej i tekstach jednolitych.

Przykłady:

  • analyze_act_relationships(eli="DU/2024/1716") - Wszystkie powiązania

  • analyze_act_relationships(eli="DU/2024/1716", relationship_type="Akty zmienione") - Jakie akty zmienił

  • analyze_act_relationships(eli="DU/2024/1716", relationship_type="Podstawa prawna") - Na jakiej podstawie powstał

  • analyze_act_relationships(eli="DU/2024/1716", relationship_type="Akty zmieniające") - Co go zmienia

  • analyze_act_relationships(eli="DU/2024/1", relationship_type="Akty uznane za uchylone") - Uchylone akty

Inputs: eli (required), relationship_type. Returns: data, hints

browse_actsPrzeglądanie aktów z rocznikaA
Read-onlyIdempotent

Przeglądaj wszystkie akty prawne wydane przez wydawcę w danym roku.

Kiedy użyć: Gdy chcesz przeglądać cały rocznik wydawcy bez filtrowania. Kiedy NIE używać: Gdy szukasz po słowach kluczowych lub tytule → użyj search_legal_acts.

Zwraca listę aktów (domyślnie max 20). Wyniki zapisywane są do result_set_id — użyj filter_results aby je zawęzić.

Przykłady:

  • browse_acts(publisher="DU", year=2024) - Wszystkie akty DU z 2024

  • browse_acts(publisher="MP", year=2024) - Wszystkie akty MP z 2024

  • browse_acts(publisher="DU", year=2024, detail_level="full") - Ze szczegółami

  • browse_acts(publisher="DU", year=2024, detail_level="minimal") - Tylko podstawowe info

  • browse_acts(publisher="DU", year=2000) - Akty z roku 2000

  • browse_acts(publisher="DU", year=2024, limit=20, offset=20) - Druga strona rocznika

Inputs: year (required), publisher (required), limit, offset, +1 more. Returns: data, hints

compare_actsPorównanie metadanych dwóch aktówA
Read-onlyIdempotent

Porównaj metadane dwóch aktów prawnych.

Porównuje tytuły, typy, statusy, daty (ogłoszenia, promulgacji, wejścia w życie), słowa kluczowe dwóch aktów. Identyfikuje różnice i wspólne elementy. Nie wymaga wcześniejszego załadowania treści (load_content).

Kiedy użyć: Aby porównać metadane dwóch aktów (np. wersji, powiązanych aktów). Kiedy NIE używać: Do porównania treści → załaduj oba akty i użyj read_act_content.

Przykłady:

  • compare_acts(eli_a="DU/2024/1692", eli_b="DU/2024/1716") - Porównaj dwa akty z 2024

  • compare_acts(eli_a="DU/2020/1444", eli_b="DU/2024/1692") - Porównaj starą i nową wersję

  • compare_acts(eli_a="DU/2024/1", eli_b="DU/2023/2000") - Porównaj akty z różnych lat

  • compare_acts(eli_a="DU/2024/100", eli_b="MP/2024/200") - Porównaj DU vs MP

  • compare_acts(eli_a="DU/2021/1500", eli_b="DU/2021/1600") - Porównaj podobne akty

Inputs: eli_a (required), eli_b (required). Returns: data, hints

filter_resultsFiltrowanie wyników wyszukiwaniaA
Read-onlyIdempotent

Filtruj i zawężaj wyniki wcześniejszego wyszukiwania aktów prawnych lub orzeczeń.

Działa jak grep/filtr na wynikach z search_legal_acts, browse_acts lub track_legal_changes oraz na zestawach orzeczeń z narzędzi wyszukiwania orzeczeń modułów źródeł. Wymaga result_set_id zwróconego przez te narzędzia. Przefiltrowane wyniki zapisywane są jako nowy zestaw (nowe result_set_id), który można filtrować dalej.

Zestaw orzeczeń: wyniki są w polu judgments (pole results jest puste, document_kind = 'judgment'), a wzorzec można dopasować do pól 'signature', 'court', 'division', 'division_code', 'excerpt'. Filtr status_equals dotyczy tylko aktów. Orzeczenia wydziałów pracy sądów powszechnych zawęzisz wzorcem na kodzie wydziału: pattern="(15|18|21)$", field="division_code" (wzorzec "(15|21)$" pomija wydziały wyłącznie ubezpieczeń społecznych; nazwy wydziałów: field="division").

Limit rozmiaru wejścia: pojedyncze wywołanie przetwarza maksymalnie 100 rekordów (wartość domyślna, konfigurowalna przez operatora). Większy zestaw kończy się błędem, a nie wynikiem częściowym — dzięki temu brak dopasowania zawsze oznacza przeszukanie całego zestawu. Zawęź wyszukiwanie przed filtrowaniem.

Kiedy użyć: Po search_legal_acts/browse_acts/track_legal_changes aby zawęzić wyniki. Kiedy użyć: Po wyszukiwaniu orzeczeń, aby zawęzić zestaw orzeczeń (wydział, sąd, rodzaj, data). Kiedy NIE używać: Gdy potrzebujesz nowych wyników z API → użyj search_legal_acts. Kiedy NIE używać: Do dowodzenia, że akt nie istnieje, dopóki pole source_scope odpowiedzi ma wartość 'page' — filtrowane jest wtedy okno, a nie cały zbiór, więc pusty wynik nie rozstrzyga. Odpowiedź sygnalizuje to polem no_match_is_inconclusive.

Przykłady:

  • filter_results(result_set_id="rs_1", type_equals="Rozporządzenie") - Tylko rozporządzenia

  • filter_results(result_set_id="rs_1", pattern="zdrow|apteka|lekar") - Akty związane ze zdrowiem

  • filter_results(result_set_id="rs_1", pattern="podatek|VAT", type_equals="Ustawa") - Ustawy podatkowe

  • filter_results(result_set_id="rs_1", date_field="promulgation_date", date_from="2024-01-01", date_to="2024-06-30") - Ogłoszone w I połowie 2024

  • filter_results(result_set_id="rs_1", sort_by="promulgation_date", sort_desc=True, limit=10) - 10 najnowszych na pierwszej stronie

  • filter_results(result_set_id="rs_1", pattern="\p{L}+ o ochronie") - Wzorzec z klasą unikodową

  • filter_results(result_set_id="rs_2", pattern="(15|18|21)$", field="division_code") - Orzeczenia wydziałów pracy (zestaw orzeczeń)

Inputs: result_set_id (required), field, limit, offset, +9 more. Returns: data, hints

get_act_detailsSzczegóły aktu prawnegoA
Read-onlyIdempotent

Pobierz szczegółowe informacje o akcie prawnym.

Zwraca metadane: tytuł, status, daty (ogłoszenia, wejścia w życie, uchylenia), słowa kluczowe, spis treści i dostępne formaty (HTML/PDF).

Ustaw load_content=True aby załadować treść — to wymagane przed użyciem read_act_content (czytanie sekcji) i search_in_act (wyszukiwanie w treści).

Kiedy użyć: Aby zobaczyć metadane aktu (status, daty, spis treści). Kiedy NIE używać: Aby czytać treść aktu → użyj read_act_content (po załadowaniu).

Przykłady:

  • get_act_details(eli="DU/2024/1692") - Metadane i spis treści

  • get_act_details(eli="DU/2024/1692", load_content=True) - Załaduj treść do analizy

  • get_act_details(eli="DU/2024/1716") - Sprawdź status, daty i referencje

  • get_act_details(eli="MP/2024/100") - Akt z Monitora Polskiego

  • get_act_details(eli="DU/2021/1500") - Sprawdź status i daty obowiązywania

Inputs: eli (required), load_content. Returns: data, hints

get_system_metadataMetadane systemu ELIA
Read-onlyIdempotent

Pobierz metadane systemu aktów prawnych.

Zwraca dostępne słowa kluczowe, wydawców, typy dokumentów, statusy i instytucje, które można wykorzystać do filtrowania i wyszukiwania aktów prawnych. Kategoria 'coverage' mówi, co każde aktywne źródło faktycznie obejmuje (najnowszy dokument, liczba pozycji, dostępne roczniki), więc brak wyniku można odróżnić od luki w danych.

Kiedy użyć:

  • Potrzebujesz słów kluczowych, typów, statusów, instytucji lub listy wydawców do filtrów.

  • Wyszukiwanie zwróciło mało lub zero wyników i chcesz sprawdzić, czy źródło w ogóle obejmuje dany rocznik lub okres → category="coverage".

  • Chcesz poznać świeżość źródła przed poleganiem na nim (data i odnośnik najnowszego dokumentu). Uwaga: pierwsze zapytanie danego dnia o 'coverage' może potrwać dłużej, bo mierzy zasięg u źródła (wynik jest pamiętany przez dobę). Pierwsze 'all' danego dnia kontaktuje hosty wszystkich aktywnych źródeł (do ok. 44 żądań), więc gdy chodzi tylko o jedną kategorię, podaj ją wprost zamiast 'all'.

Kiedy NIE używać:

  • Szukasz konkretnego aktu → użyj search_legal_acts.

  • Chcesz przeczytać treść aktu → użyj get_act_details i read_act_content.

Przykłady:

  • get_system_metadata(category="keywords") - Dostępne słowa kluczowe do wyszukiwania

  • get_system_metadata(category="types") - Typy dokumentów (Ustawa, Rozporządzenie itp.)

  • get_system_metadata(category="publishers") - Wydawcy (DU, MP i dzienniki z aktywnych modułów źródeł)

  • get_system_metadata(category="statuses") - Statusy aktów (obowiązujący, uchylony itp.)

  • get_system_metadata(category="coverage") - Zasięg źródeł (najnowszy dokument, roczniki)

  • get_system_metadata(category="all") - Wszystkie kategorie metadanych

Inputs: limit, offset, category. Returns: data, hints

list_loaded_documentsDokumenty załadowane do pamięciA
Read-onlyIdempotent

Wyświetl dokumenty załadowane do pamięci (Document Store).

Dokumenty ładowane są przez get_act_details(eli=..., load_content=True), a orzeczenia przez get_saos_judgment_details(source_id=..., load_content=true) (moduł SAOS). Każdy dokument ma TTL 2h — po tym czasie wymaga ponownego załadowania.

Zwraca jedną stronę listy wraz z metadanymi paginacji (page_info). Każdy wpis ma pola document_id i document_kind ('act' albo 'judgment').

Kiedy użyć: Aby sprawdzić jakie akty i orzeczenia są załadowane i dostępne do czytania/wyszukiwania. Kiedy NIE używać: Do wyszukiwania aktów → użyj search_legal_acts. Kiedy NIE używać: Do wyszukiwania orzeczeń → użyj search_saos_judgments.

Przykłady:

  • list_loaded_documents() - Wyświetl pierwszą stronę załadowanych dokumentów

  • list_loaded_documents(limit=5) - Pierwsze pięć dokumentów

  • list_loaded_documents(limit=5, offset=5) - Kolejna strona

  • list_loaded_documents(limit=100) - Cała zawartość Document Store

  • list_loaded_documents(offset=0) - Jawne przesunięcie od początku

Inputs: limit, offset. Returns: data, hints

list_result_setsZestawy wyników w pamięciA
Read-onlyIdempotent

Wyświetl aktywne zestawy wyników przechowywane w pamięci.

Każde wyszukiwanie (search_legal_acts, browse_acts, track_legal_changes) oraz filtrowanie (filter_results) tworzy zestaw wyników z unikalnym result_set_id. To narzędzie pokazuje aktywne zestawy (TTL: 1h) wraz z metadanymi paginacji.

Kiedy użyć: Aby sprawdzić jakie result_set_id są dostępne do filtrowania. Kiedy NIE używać: Do wyszukiwania nowych aktów → użyj search_legal_acts.

Przykłady:

  • list_result_sets() - Pierwsza strona aktywnych zestawów

  • list_result_sets(limit=5) - Pięć najnowszych wpisów strony

  • list_result_sets(limit=5, offset=5) - Kolejna strona

  • list_result_sets(limit=100) - Wszystkie aktywne zestawy

  • list_result_sets(offset=0) - Jawne przesunięcie od początku

Inputs: limit, offset. Returns: data, hints

read_act_contentCzytanie treści aktuA
Read-onlyIdempotent

Czytaj treść załadowanego aktu prawnego lub orzeczenia po sekcjach.

WYMAGANIE: Akt musi być wcześniej załadowany za pomocą get_act_details(eli=..., load_content=True), a orzeczenie za pomocą get_saos_judgment_details(source_id=..., load_content=true) (moduł SAOS).

Bez parametru section zwraca spis treści (listę sekcji). Z parametrem section zwraca treść wybranej sekcji. Odpowiedź ma pola document_id i document_kind ('act' albo 'judgment'). Dla orzeczenia eli powtarza identyfikator dokumentu (np. SAOS/554697) i nie jest identyfikatorem ELI.

Kiedy użyć: Aby czytać treść konkretnej sekcji załadowanego aktu lub orzeczenia. Kiedy użyć: Aby przeczytać uzasadnienie wczytanego orzeczenia (sekcja z uzasadnieniem w spisie treści). Kiedy NIE używać: Aby zobaczyć metadane aktu → użyj get_act_details. Kiedy NIE używać: Aby zobaczyć metadane orzeczenia → użyj get_saos_judgment_details.

Przykłady:

  • read_act_content(eli="DU/2024/1692") - Spis treści (lista sekcji)

  • read_act_content(eli="DU/2024/1692", section="Art. 1") - Treść artykułu 1

  • read_act_content(eli="DU/2024/1692", section="Rozdział 1") - Treść rozdziału 1

  • read_act_content(eli="DU/2024/1692", section="Dział II") - Treść działu II

  • read_act_content(eli="MP/2024/100") - Spis treści aktu z MP

  • read_act_content(eli="SAOS/554697") - Spis treści wczytanego orzeczenia z SAOS

Inputs: eli (required), limit, offset, section. Returns: data, hints

search_in_actWyszukiwanie w treści aktuA
Read-onlyIdempotent

Wyszukaj termin w treści załadowanego aktu prawnego lub orzeczenia.

WYMAGANIE: Akt musi być wcześniej załadowany za pomocą get_act_details(eli=..., load_content=True), a orzeczenie za pomocą get_saos_judgment_details(source_id=..., load_content=true) (moduł SAOS).

Zwraca jedną stronę trafień z kontekstem, metadanymi paginacji i informacją o sekcji. Odpowiedź ma pola document_id i document_kind ('act' albo 'judgment'). Dla orzeczenia eli powtarza identyfikator dokumentu (np. SAOS/554697) i nie jest identyfikatorem ELI.

Kiedy użyć: Szukasz terminu lub przepisu w treści załadowanego aktu albo orzeczenia. Kiedy użyć: Chcesz znaleźć cytat w uzasadnieniu wczytanego orzeczenia. Kiedy NIE używać: Szukasz aktu lub orzeczenia, które nie jest załadowane → użyj search_legal_acts albo search_saos_judgments, a potem załaduj dokument. Kiedy NIE używać: Chcesz przeczytać całą sekcję → użyj read_act_content.

Przykłady:

  • search_in_act(eli="DU/2024/1692", query="straż") - Znajdź "straż" w akcie

  • search_in_act(eli="DU/2024/1692", query="obowiązek", context_chars=200) - Z krótszym kontekstem

  • search_in_act(eli="DU/2024/1692", query="art. 5", limit=10, offset=0) - Pierwsza strona trafień

  • search_in_act(eli="DU/2024/1692", query="kara", limit=5, offset=5) - Kolejna strona trafień

  • search_in_act(eli="DU/2024/1692", query="termin") - Domyślna strona (do 20 trafień)

  • search_in_act(eli="SAOS/554697", query="odszkodowanie") - Szukaj w wczytanym orzeczeniu z SAOS

Inputs: eli (required), query (required), limit, offset, +1 more. Returns: data, hints

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4.4/5.0

    Scored across 13 tools

    Disambiguation4/5

    Most tools have sharply distinct roles, reinforced by explicit "Kiedy użyć / Kiedy NIE używać" blocks and cross-references, and the resource/session tools (list_result_sets, list_loaded_documents) are clearly separated. The one soft spot is the retrieval trio search_legal_acts / browse_acts / track_legal_changes: search_legal_acts also accepts date ranges, so its boundary with track_legal_changes is partly a matter of convention rather than capability.

    Naming Consistency5/5

    Every tool follows a consistent snake_case verb_noun pattern (get_system_metadata, search_legal_acts, filter_results, read_act_content, analyze_act_relationships, calculate_legal_date). Verbs are used consistently by intent: get/list/read for retrieval, search/browse/track for discovery, compare/analyze/filter/calculate for processing.

    Tool Count5/5

    Thirteen tools is well within the sweet spot for a legal-research server, and each one earns its place: discovery, inspection, in-document search, processing/filtering, analysis, plus two memory-management helpers. Nothing looks like filler or an accidental duplicate entry point.

    Completeness4/5

    The act lifecycle is covered end to end: metadata/coverage introspection, search, browse, change tracking, details, content loading, section reading, in-document search, filtering/result-set management, comparison, relationship analysis and date math. The main gap is that read_act_content and search_in_act explicitly require search_saos_judgments / get_saos_judgment_details for judgments, which are not part of this tool set, and there is no export/save or result-set cleanup operation.

    Maintenance

    ActivityNo data
    ResponsivenessOutdated data

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      A
      quality
      D
      maintenance
      Enables searching Polish parliamentary prints and retrieving the full legislative process history, including links to final legal acts, via the official Sejm API.
      3
      MIT
    • A
      license
      A
      quality
      B
      maintenance
      Enables AI assistants to query official Polish Sejm data and the ELI legal acts database for deputies, clubs, committees, votes, interpellations, bills, proceedings, transcripts, and legal acts, with source links.
      29
      51 npm
      2
      Apache 2.0
    • F
      license
      Not graded
      quality
      C
      maintenance
      Enables AI clients to search, retrieve and cite over 600 Polish statutes and regulations from the Journal of Laws in full text, including the wording of a provision as of any date, in-force status checked against the ELI register, related provisions, annexes and tables. Access is read-only via a hosted endpoint with e-mail sign-in, optional free trial queries, and paid plans.
      -