Law Scrapper MCP
This MCP server lets an AI client research Polish legal acts (Dziennik Ustaw, Monitor Polski, plus optional source-module sources like court judgments) by searching, browsing, loading, reading and filtering them.
Search and browse acts —
search_legal_acts(keywords/AND, title, type, in-force, publisher, year, publication and effective date ranges) andbrowse_acts(whole year by publisher), each returning aresult_set_id.Filter results incrementally —
filter_resultsnarrows stored result sets with an RE2 regex pattern, exact type/status/year match, date ranges and sorting; chains into new sets and flags when an empty match is inconclusive because only a page was searched.Inspect act metadata —
get_act_detailsreturns titles, status, dates, keywords, TOC, formats and optionalload_content;compare_actsdiffs metadata of two acts;analyze_act_relationshipsreports amendments, repeals, legal bases and consolidated texts.Read and search document text —
read_act_contentreads a loaded act/judgment by section or returns its TOC;search_in_actfinds terms within loaded content with context and pagination.Track legal changes —
track_legal_changeslists acts published/changed in a date range, filterable by publisher and keywords.Discover system metadata —
get_system_metadatalists keywords, publishers, statuses, types, institutions and per-source coverage (newest document, years) for building filters and judging data gaps.Do date arithmetic —
calculate_legal_dateadds/subtracts days, months and years from a base date for deadlines and vacatio legis.Manage in-memory stores —
list_result_setsandlist_loaded_documentsshow cached result sets and loaded documents (with TTLs) available for further filtering or reading.
Utilizes Git version control for repository management and development workflow
Hosted on GitHub for source code management, distribution, and collaborative development
Built using Python programming language with FastMCP framework for MCP server implementation
Displays project status badges for Python version, license, and version information
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., "@Law Scrapper MCP Find recent regulations about data protection from the last 2 years"
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.
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.
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.0andMCPServer; STDIO and stateless Streamable HTTP transportsAsync 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
Using uv (recommended)
# 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 devUsing 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 URLsAn 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-scrapperQuick 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-scrapperConfigure 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_mcpConfigure 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-mcpOr 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 upCI 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-mcpConfiguration
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 |
|
| Transport: |
|
| HTTP server host (when using streamable-http). Binding beyond loopback requires |
|
| HTTP server port (when using streamable-http) |
|
| Graceful shutdown window in whole seconds for the HTTP server (a fractional value is rejected at startup rather than silently rounded). Keep |
|
| HTTP request timeout in seconds |
|
| Concurrent light API requests (JSON metadata and search) |
|
| Concurrent heavy API requests (act HTML and PDF downloads) |
|
| Sustained outbound request rate towards the Sejm API; accepted range |
|
| Requests allowed back-to-back before the rate applies; accepted range |
|
| Longest pause, in seconds, that a |
|
| Attempts per operation, retries included; accepted range |
|
| Seconds the retry sequence of one operation may plan to wait |
|
| Metadata cache TTL (24 hours) |
|
| Search results cache TTL (10 minutes) |
|
| Browse results cache TTL (1 hour) |
|
| Act details cache TTL (1 hour) |
|
| Changes tracking cache TTL (5 minutes) |
|
| Maximum cache entries |
|
| Maximum documents in Document Store |
|
| 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 |
|
| Document Store TTL (2 hours) |
|
| Failures before circuit breaker opens |
|
| Seconds before trying recovery |
|
| Test calls in half-open state |
|
| Max |
|
| Max records |
|
| Cap on error-message length for categories whose message text is built from an exception's own string ( |
|
|
|
|
|
|
| unset | Override the JWKS URI discovered from |
|
| Scopes a presented token must carry, checked by |
|
| JWT signature algorithm allowlist passed to the decoder; never read from the token header |
|
| Seconds a fetched JWKS key set is cached before re-fetching |
|
| Whether the per-client rate limiter wraps the HTTP app. |
|
| Requests allowed per |
|
| Rate limit window in seconds |
|
| Token bucket capacity — how many requests can arrive back-to-back before |
|
| Log level: |
|
| Log format: |
|
| Contact written into the |
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=86400Identity 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.orgAuthenticated 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-scrapperIn 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 |
|
Microsoft Entra ID |
|
| |
AWS Cognito |
|
Okta |
|
Auth0 |
|
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-scrapperReachable 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-scrapperProviders 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 |
| Metadane systemu ELI | true | true |
| Wyszukiwanie aktów prawnych | true | true |
| Przeglądanie aktów z rocznika | true | true |
| Filtrowanie wyników wyszukiwania | true | false |
| Szczegóły aktu prawnego | true | true |
| Czytanie treści aktu | true | false |
| Wyszukiwanie w treści aktu | true | false |
| Powiązania aktu prawnego | true | true |
| Śledzenie zmian w prawie | true | true |
| Obliczanie terminu prawnego | true | false |
| Porównanie metadanych dwóch aktów | true | true |
| Zestawy wyników w pamięci | true | false |
| 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 metadata2. search_legal_acts(publisher, year, keywords, detail_level, status, type)
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 filtertype(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 scanning3. 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 yeardetail_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 year4. 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 yeardate_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 descendinglimit(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 resultsResult-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 readingdetail_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_contentwas not set and the act was not already in the Document Storeloaded- content is in the Document Store, ready forread_act_content/search_in_actunavailable- 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 comparison6. 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 chapters7. 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 references8. analyze_act_relationships(eli, relationship_type)
Analyze legal relationships and references of an act (amendments, references, etc.).
Parameters:
eli(string) - Act identifierrelationship_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 network9. track_legal_changes(date_from, date_to, publisher, keywords)
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 range10. calculate_legal_date(days, months, years, base_date)
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 periods11. 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 acts12. 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
Load an act - Call
get_act_details(eli="DU/2024/1", load_content=true)to load the act into the Document StoreRead sections - Use
read_act_content(eli="DU/2024/1", section="Art. 1")to read specific sectionsSearch 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 fileDocker
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-mcpdocker-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 downMigration guide (v1 to v2)
If upgrading from v1.0.2, note these breaking changes:
v1.0.2 (old) | v2.0.0 (new) | Notes |
|
| Call with no parameters for current date |
|
| Use intuitive +future/-past sign convention |
|
| Consolidated into one tool |
|
| Consolidated into one tool |
|
| Consolidated into one tool |
|
| Consolidated into one tool |
|
| Consolidated into one tool |
| N/A | Use |
|
| Enhanced with |
|
| Renamed for clarity |
|
| Added |
|
| Requires pre-loading with |
|
| TOC included in details response |
|
| 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.0andMCPServer[AppContext]Native structured responses — Tools return
EnrichedResponsewithoutputSchemaand objectstructuredContent(no JSON strings)Protocol-visible errors — Tool failures surface as
isError=trueinstead of in-bodyerrorfieldsStateless Streamable HTTP —
/mcpendpoint only; legacy event-stream transport removedPagination —
PageInfomodel exposed as thepage_infofield withlimit/offseton search, browse, metadata, changes, TOC, sections, and in-act searchTyped
AppContext— Lifespan resources accessed viactx.request_context.lifespan_contextDomain services —
ComparisonService,RelationshipService, andDateServiceextracted from tool adaptersTransport tests — Real STDIO subprocess, loopback HTTP, and MCP conformance in CI
Explicit Host/Origin allowlist on HTTP —
transport_securityis 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 |
|
| Parse native MCP structured payload |
|
| Check |
v2.x in-process client | Official | In-memory and transport tests use SDK client |
v2.x direct lifespan access |
| Typed |
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_routefor/healthIntegration 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 definedwhen running viauvx --from "git+https://github.com/numikel/law-scrapper-mcp" law-scrapper. Removedfrom __future__ import annotationsfromcompare.pyso 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_errorsdecorator with error classification and full tracebacksasyncio.Lock migration — All stores use
asyncio.Lockfor proper async compatibilityDefault search limit — Search/browse return max 20 results by default to limit token usage
Health endpoint —
/healthfor Docker deployments with streamable-http transportPolish 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 devRunning 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-auditMCP 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/ -vLayer 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 integrationLayer 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 integrationFull integration suite:
uv run pytest tests/integration/ -v -m integrationWhat the tests verify:
All 13 tools registered with preserved public arguments
Native
structuredContentpayloads withoutputSchema(not JSON strings)isError=trueon tool execution failurespage_info(PageInfo) pagination metadata on paginated outputsStateful 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.Clientfrom the Python MCP SDKHelpers:
tests/mcp_helpers.py(parse_tool_resultforstructuredContentassertions)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_mcpContributing
Fork the repository
Create your feature branch (
git checkout -b feature/amazing-feature)Commit your changes using Conventional Commits format
Add tests for new functionality
Ensure all tests pass and coverage is maintained
Push to the branch (
git push origin feature/amazing-feature)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
Developed with help from:
And with models:
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 toolsanalyze_act_relationshipsPowiązania aktu prawnegoARead-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 rocznikaARead-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
calculate_legal_dateObliczanie terminu prawnegoARead-onlyIdempotent
Oblicz daty na potrzeby prawne (np. terminy, daty wejścia w życie, vacatio legis).
Dodaje lub odejmuje dni, miesiące i lata od daty bazowej (lub dzisiejszej). Wartości dodatnie = przyszłość, ujemne = przeszłość.
Przykłady:
calculate_legal_date(days=14) - Termin 14 dni od dziś (np. termin na odwołanie)
calculate_legal_date(days=30, base_date="2024-06-01") - 30 dni od 1 czerwca 2024
calculate_legal_date(years=1, base_date="2024-01-01") - Rok po 1 stycznia 2024
calculate_legal_date(days=-90) - 90 dni wstecz od dziś
calculate_legal_date(months=6, days=15) - 6 miesięcy i 15 dni od dziś
calculate_legal_date(years=-5, base_date="2024") - 5 lat przed 1 stycznia 2024
Inputs: days, years, months, base_date. Returns: data, hints
compare_actsPorównanie metadanych dwóch aktówARead-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 wyszukiwaniaARead-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 prawnegoARead-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 ELIARead-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ęciARead-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ęciARead-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 aktuARead-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 aktuARead-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
search_legal_actsWyszukiwanie aktów prawnychARead-onlyIdempotent
Wyszukaj polskie akty prawne z Dziennika Ustaw (DU) i Monitora Polskiego (MP).
UWAGA: Wszystkie słowa kluczowe muszą wystąpić jednocześnie (logika AND). Szukaj jednego słowa na raz dla logiki OR.
Kiedy użyć: Gdy znasz słowa kluczowe, tytuł, typ lub kryteria wyszukiwania. Kiedy NIE używać: Gdy chcesz przeglądać cały rocznik → użyj browse_acts.
Po wyszukiwaniu możesz użyć filter_results(result_set_id=...) aby dalej zawęzić wyniki (np. regex na tytułach, filtr po typie dokumentu).
Przykłady:
search_legal_acts(keywords=["podatek"], year=2024) - Akty podatkowe z 2024
search_legal_acts(act_type="Ustawa", title="zdrowotny") - Ustawy o zdrowiu
search_legal_acts(act_type="Rozporządzenie", year=2024, in_force=True) - Obowiązujące rozporządzenia z 2024
search_legal_acts(publisher="MP", year=2024, limit=10) - 10 aktów z MP z 2024
search_legal_acts(date_from="2024-01-01", date_to="2024-06-30") - Akty wchodzące w życie I poł. 2024
search_legal_acts(pub_date_from="2024-03-01", pub_date_to="2024-03-31") - Ogłoszone w marcu 2024
search_legal_acts(title="budżet", year=2024) - Akty budżetowe z 2024
Inputs: year, limit, title, offset, +9 more. Returns: data, hints
track_legal_changesŚledzenie zmian w prawieARead-onlyIdempotent
Śledź zmiany prawne i nowe publikacje w zakresie dat.
Zwraca akty prawne opublikowane lub zmienione w podanym zakresie dat. Wyniki zapisywane są do result_set_id — użyj filter_results aby je zawęzić (np. po typie dokumentu lub wzorcem regex w tytule).
Przykłady:
track_legal_changes(date_from="2025-01-01") - Zmiany od początku 2025
track_legal_changes(date_from="2024-01-01", date_to="2024-12-31") - Zmiany w 2024
track_legal_changes(date_from="2024-01-01", keywords=["podatek"]) - Zmiany podatkowe w 2024
track_legal_changes(date_from="2024-06-01", publisher="MP") - Zmiany w MP od czerwca 2024
track_legal_changes(date_from="2024-01-01", keywords=["zdrowotny"]) - Zmiany zdrowotne
Inputs: date_from (required), limit, offset, date_to, +2 more. Returns: data, hints
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 13 tools
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.
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.
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.
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
Related MCP Connectors
Polish statutes (Dziennik Ustaw, ELI) and court rulings (SAOS) for AI agents. Runs on Apify.
Semantic search over Polish law and case law, citing the exact in-force article.
Polish and EU law: statutes, rulings, UODO/UOKiK/KNF, EU regulations, citation checks. Free tier.
Verified Polish open data for AI agents: debt, budget, 460 MPs, votings, judiciary search, RAG.
Related MCP Servers
- AlicenseAqualityDmaintenanceEnables searching Polish parliamentary prints and retrieving the full legislative process history, including links to final legal acts, via the official Sejm API.3MIT
- AlicenseNot gradedqualityCmaintenanceEnables AI assistants to search and retrieve Polish statutes, provisions, and EU law cross-references with verified citations, supporting legal research and compliance checks.50 npmApache 2.0
- AlicenseAqualityBmaintenanceEnables 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.2951 npm2Apache 2.0
- FlicenseNot gradedqualityCmaintenanceEnables 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.-