Releases: numikel/law-scrapper-mcp
Release list
v4.3.1
Changelog - v4.3.1
All notable changes to the law-scrapper-mcp project for version v4.3.1 will be documented in this file.
The format is based on Keep a Changelog 1.1.0,
and this project adheres to Semantic Versioning 2.0.0.
[4.3.1] - 2026-09-06
Two error-message findings deferred from the v4.3.0 review (issues #60 and #61). Both
change what a caller reads on failure, neither changes a contract: the accepted range of
LAW_MCP_ERROR_MESSAGE_MAX_CHARS is unchanged, no error category was added or renamed, and
no tool schema moved.
Fixed
- A truncated
content_too_largemessage no longer cuts the source PDF URL in half. Under a
lowLAW_MCP_ERROR_MESSAGE_MAX_CHARSthe cut used to land inside the URL — the one token the
caller can act on — while the appended guidance still said to fetch the content from "the
given address". The cut now lands in the descriptive prefix and the trailing URL is kept whole
whenever it fits within the cap; when even the URL plus the truncation announcement would
exceed the cap, the plain cut still applies, so the cap always holds. That fallback is pinned
by a regression test as a deliberate trade-off (#60). - A transport failure toward
api.sejm.gov.pl(connection refused, read timeout, protocol
error) no longer copies the rawhttpxtext — host, URL, TLS or errno detail — into the tool
error or the ERROR log. The message names the failure class and the endpoint
(Błąd połączenia z API Sejmu (ConnectError) podczas żądania GET acts/DU/2024/1); the
httpxexception is kept as the chained cause and logged at DEBUG only, the same ERROR/DEBUG
split thevalidationandupstreamcategories already had. With this, everyunavailable
message is project-authored at its raise site (#61). law_scrapper_mcp.__version__is read from the installed distribution instead of a hardcoded
string that sat outside the release script's synced locations and had stayed at3.0.0since
v3.0.0. A checkout that was never installed reports0.0.0+unknown.
v4.3.0
Changelog - v4.3.0
All notable changes to the law-scrapper-mcp project for version v4.3.0 will be documented in this file.
The format is based on Keep a Changelog 1.1.0,
and this project adheres to Semantic Versioning 2.0.0.
[4.3.0] - 2026-09-06
Failed content loading now looks like a failure: a transient upstream problem during
get_act_details(load_content=True) used to surface as a successful response with no readable
text, and now fails the call itself. A new regression test also pins the guarantee, introduced
when this project migrated to the official MCP SDK, that any domain exception surfaces as a
protocol error (isError=true) rather than a success body with an embedded error field. Every
error message built from caller-sourced or exception text is now length-bounded.
BREAKING — content loading failures are protocol errors
get_act_details(eli=..., load_content=True) used to return a successful response with
is_loaded=false whenever loading failed — an open circuit breaker, a timeout, an HTTP 5xx
from api.sejm.gov.pl, or a conversion error. The only record of the failure was a server log
line, and the response even hinted at retrying the call that had just failed.
Such calls now fail with isError=true. Clients that treated is_loaded=false as "this act has
no text" must distinguish two cases:
content_status="unavailable"in a successful response — the act permanently has no
readable text; retrying will not change that.isError=true— the upstream is unreachable; retrying later may succeed.
This mirrors the change v3.1.1 made for oversized acts, which shipped as a PATCH; it is a
signalling fix, since the contract always meant "success = content loaded".
Added
content_statusfield onget_act_detailsoutput, with valuesnot_requested,loadedand
unavailable. The field is part of the tool'soutputSchemaand itsstructuredContent.
loadedreflects the final in-memory state rather than this call'sload_contentflag, so an
act loaded by an earlier call reportsloadedeven on a metadata-only call that never asked to
load anything.LAW_MCP_ERROR_MESSAGE_MAX_CHARS(default500, range 80-10000) caps the length of error
messages built from an exception's own text, which can quote caller input of unbounded length.
Truncation is announced in the message rather than silent.- Every tool error message now ends with a fixed one-sentence remediation hint, so a caller can
tell "retry shortly" from "this identifier does not exist" without parsing category names.
Seven categories carry a hint:not_found,validation,precondition,content_too_large,
unavailable,upstream,internal.content_too_large(a refusal because the act is too
large to convert) is split out ofpreconditionrather than sharing its "do a step first"
wording — there is no prior step for an oversized act, and the one actionable remedy (fetch the
source file) is already the last sentence of the body.
Changed
ActService._load_contentno longer swallows exceptions. Transient failures propagate; only a
permanent absence of readable text is handled internally, asContentNotAvailableError— no
HTML or PDF URL at all, a 404 on thetext.htmlortext.pdffetch, or an empty extraction
from either format.- An act with neither an HTML nor a PDF text URL no longer triggers a request that its metadata
already proves pointless. - A permanent absence is remembered for as long as the act's metadata stays cached
(LAW_MCP_CACHE_DETAILS_TTL, default one hour): a repeatedload_content=truecall on such an
act answerscontent_status="unavailable"from memory instead of re-fetchingtext.html/
text.pdf. The removed placeholder document used to play that role by accident; without a
replacement every retry would have costapi.sejm.gov.plone or two requests for an answer that
cannot change before the metadata it was derived from expires. - Hints for an act with no readable content no longer point at
read_act_content,search_in_act
or anotherload_content=truecall; one hint names the source PDF URL when the act has one,
alongside the unrelatedanalyze_act_relationshipshint everyget_act_detailsresponse
already carries. An act with no PDF either gets the same explanatory hint without a URL to
offer, rather than one pointing at a file the server itself declined to fetch. - A message built from an exception's own text now ends in a sentence terminator before the
remediation sentence is appended, unless it already ends in one or ends in a URL — so the two
sentences don't run together, and aContentTooLargeErrormessage can still end on a bare,
unpunctuated PDF URL without a stray period being glued onto it. - The set of error categories treated as caller-sourced — and therefore subject to the length
cap — is nowvalidation,not_found,precondition,content_too_largeandunavailable.
unavailableis included becauseApiUnavailableError's text can embed a rawhttpx
exception, which is not authored by this project even though the category itself is not new.
Removed
- The placeholder documents
*No readable content available for {eli}…*and
*Content extraction failed…*. They were stored in the document store as if they were the act,
madeis_loaded=trueuntrue, and were matched bysearch_in_act.
Fixed
- A failure to load content is no longer indistinguishable from an act without text: a transient
upstream failure now propagates asisError=trueinstead of silently reporting
is_loaded=false. validation,not_found,precondition,content_too_largeandunavailableerror messages
are now length-bounded, so text quoting unbounded caller input or upstream detail cannot fill
the caller's whole context window.
v4.2.0
Changelog - v4.2.0
All notable changes to the law-scrapper-mcp project for version v4.2.0 will be documented in this file.
The format is based on Keep a Changelog 1.1.0,
and this project adheres to Semantic Versioning 2.0.0.
[4.2.0] - 2026-09-02
Sweep of the deferred review findings from clusters 2–9 (GitHub issues #14, #16, #18–#21, #27,
#31, #32, #34, #38–#41, #45–#47, #49, #50, #52, #54, #55). No MCP tool changes its name,
parameter names or response shape; several fields and validations now behave as documented.
Added
LAW_MCP_API_MAX_SERVER_PAUSE(default60, at most600): the cap on how long a
server-sentRetry-Aftermay hold all outbound traffic, previously a fixed constant. The
clamp now lives in the rate limiter itself, so every caller ofpause_for()inherits it (#46,
#47).- Document downloads are budgeted while streaming: the client aborts a HTML or PDF body as
soon as it passesLAW_MCP_DOC_STORE_MAX_SIZE_BYTES(or earlier, fromContent-Length),
raising the same PolishContentTooLargeErrorthe conversion step already used. The whole
body is no longer materialised before the size check (#19). search_in_actbuilds only the requested page of match positions (DocumentStore.scan_page)
instead of every match in the document; memory is bounded bylimit,total_countstays
exact (#16).- CI builds the Docker image and smoke-tests both transports (
/healthover streamable-http, an
initializeround-trip over STDIO), so the image cannot silently stop building again. - CI runs the MCP conformance suite for real again, on
@modelcontextprotocol/conformance
0.2.0-alpha.11— the first line that accepts the SDK's protocol era2026-07-28— with the
baseline inconformance-baseline.yml(#14).
Changed
search_legal_actsvalidateslimitandoffsetlike every other listing tool: a malformed
or non-positivelimit, or a malformed or negativeoffset, is a tool error with a Polish
message instead of silently yielding page one.limitstill has no upper clamp; its
description now says why (#18, #19).track_legal_changessendslimitandoffsetupstream and readstotalCount, so a date
range wider than one API page is stored as apage-scoped set with a truthful
corpus_countinstead of being labelledcompleteafter silent upstream truncation (#54).DocumentStore.loadrefuses a document over the size limit instead of truncating it (the
branch was unreachable from production since 4.0.0, and its character slice against a byte
budget was wrong for Polish text) (#32, #21).LAW_MCP_LOG_LEVELaccepts exactlyDEBUG,INFO,WARNING,ERROR,CRITICAL
(case-insensitive);WARN-style aliases that used to work on STDIO and crash on
streamable-http are rejected at startup on both.LAW_MCP_SHUTDOWN_GRACEis an integer
(#31).LAW_MCP_AUTH_REQUIRED_SCOPESwithLAW_MCP_AUTH_MODE=beareris rejected at startup: a
shared secret has no scope semantics, so the setting could never restrict anything. Remove
the variable or switch tooauth(#38).LAW_MCP_API_RATE_PER_SECONDis bounded to0.1–100and rejects non-finite values;
LAW_MCP_API_RATE_BURSTto1–1000;LAW_MCP_API_MAX_ATTEMPTSto1–20(#47, #27)./healthis exempt from the per-client rate limiter only for loopback peers; a probe from
another host is metered like any other request (#39).- JWKS discovery no longer runs while holding the verifier's lock, so concurrent requests
during a slow identity-provider response are not serialised behind it (#39). build_http_app()derives the auth settings and token verifier from the livesettings
object, the same place it already reads host and rate-limit settings (#41).uvicornis a declared dependency rather than a transitive one (#31).- Removed two internal methods without production callers:
DocumentStore.search()and
MetadataService.get_metadata();ContentTooLargeErroris exported from the client
facade (#20, #31).
Fixed
- A bracketed IPv6 allowlist entry with a trailing slash (
http://[::1]:8080/) was classified as
remote after the[::1].evil.comtightening, so a value 4.1.0 accepted refused startup under
auth_mode=none; the host parser now drops any path suffix before classifying, for every form. - A cache entry for
acts/searchis shared bysearch_legal_acts,browse_actsand
track_legal_changeswhenever their parameters coincide, and the first writer's TTL used to
decide freshness for all three. Each read is now bounded by its own caller's TTL, so a call
documented as 300 s is never served a 600 s entry. - The Docker image did not build: the builder stage ran
uv sync --no-editable, which builds
the project wheel, withoutREADME.mdin the build context, and hatchling refuses to build
a package whose declared readme is missing. Broken since thereadmefield was added on
2026-08-10; surfaced by the Glama indexer. A test now pins that the builder stage copies
every file the package metadata names. effective_dateinsearch_legal_acts,browse_actsandtrack_legal_changeswas always
null: the code read adateEffectkey that neither list endpoint returns. It now reads
entryIntoForce, the field the API does return, so theeffective_datefilter and sort in
filter_resultsoperate on data (#52).- A negative
offsetpassed tosearch_legal_actsreachedapi.sejm.gov.plverbatim (#18). httpx's request log line carried the full search URL — keywords and title included — at
INFO, the path the F13 audit finding never covered. Thehttpxandhttpcoreloggers are now
held atWARNINGor above (#34).[::1].evil.cominLAW_MCP_ALLOWED_HOSTSwas classified as loopback because the bracket
parser ignored everything after]; trailing content other than a port or:*now disables
the loopback match (#38).backoff()raisedOverflowErrorfor a large attempt number and returned sub-basedelays
for attempts below 1; the exponent is now bounded and computed in floating point (#27).LAW_MCP_API_RATE_PER_SECOND=infwas accepted and silently disabled pacing (#47).- The pacing deadline and the rate limiter measured time on different clocks when a limiter
was injected, which made the deadline bound fail open; both now share the limiter's clock
(#45). StaticTokenVerifieraccepts no empty secret, so an emptyAuthorizationvalue can never
match one (#38).- A section with
end_pos == 0was treated as open-ended bysection_for_position;
overlapping sections are now rejected when a document is loaded (#20).
v4.1.0
Changelog - v4.1.0
All notable changes to the law-scrapper-mcp project for version v4.1.0 will be documented in this file.
The format is based on Keep a Changelog 1.1.0,
and this project adheres to Semantic Versioning 2.0.0.
[4.1.0] - 2026-09-02
Result-set scope as first-class contract — a single new field disambiguates complete result sets from windows into larger match sets, closing two categories of false inference over stored results.
Added
- Result-set scope: A new
set_scopefield on every tool response indicates whether the result set iscomplete(the full set of matches for the query) orpage(a window into a larger match set). This directly addresses misinterpretations where a tool returned twenty results from a corpus of thousands but the response implied it was the full set. The scope is stored alongside results inResultStoreand propagates tofilter_results, so filtering and paginating both carry the scope forward. Clients that do not useResultStorecan read the scope fromresult_set_info.set_scope. - Correct
total_countinsearch_legal_acts: Previously,search_legal_actsreported the current page size astotal_count(e.g., 20 when querying a corpus of 1984 matches). The API response carries bothcount(page size) andtotalCount(corpus size) — now the tool readstotalCount, so pagination hints and scope are computed against the actual match count. This reverses a long-standing misreport in the pagination field:was_truncatednow correctly reflects whether there are more matches beyond the current page.
Changed
- Pagination hints are now scope-aware: A hint to paginate is only generated when
set_scope="page". Whenset_scope="complete", results cannot be paginated further (the full set fits), so hints encourage filtering instead. This eliminates the prior pattern of suggesting pagination to a tool that was always returning the complete set.
v4.0.2
Changelog - v4.0.2
All notable changes to the law-scrapper-mcp project for version v4.0.2 will be documented in this file.
The format is based on Keep a Changelog 1.1.0,
and this project adheres to Semantic Versioning 2.0.0.
[4.0.2] - 2026-09-01
Outbound politeness towards api.sejm.gov.pl — egress rate limiting and concurrent-request budgeting to respect the public API. No MCP tool changes signature or response shape.
Added
- Egress rate limiting — A token bucket now bounds how fast requests leave for the Sejm API, on top of the existing concurrency bound. Configurable with
LAW_MCP_API_RATE_PER_SECOND(default5.0) andLAW_MCP_API_RATE_BURST(default10). ARetry-Aftersent by the API now pauses the whole client rather than each failing request separately, so honouring it no longer ends in a synchronised retry burst. The pause is capped at 60 seconds regardless of the header's requested duration, so a large or misconfiguredRetry-Aftercannot silently wedge every in-flight tool call. Pacing is also bounded byLAW_MCP_API_RETRY_BUDGET: a call that cannot be paced within its own time budget fails immediately with a message naming the pause, instead of waiting out a window it was never going to survive and returning a bare client-side timeout. Retry-Afteris now read in both forms RFC 9110 allows. Only the delta-seconds form was understood before; a date-form header — which the WAF in front of the API may send even though the API itself does not — was discarded, which silently left the client-wide pause switched off. Repeated headers are resolved to the longest wait.- Separate concurrency budget for content downloads —
LAW_MCP_API_MAX_CONCURRENT_CONTENT(default2) governs act HTML and PDF downloads, so a run of document fetches can no longer occupy every slot and stall concurrent searches.
Changed
LAW_MCP_API_MAX_CONCURRENTdefault lowered from10to8, and its meaning narrowed to light JSON requests. Together with the newLAW_MCP_API_MAX_CONCURRENT_CONTENTof2the peak concurrency the API sees is unchanged at ten. Deployments that relied on the default now get eight light slots plus two heavy ones; set both variables explicitly to restore any other split.browse_actsfetches one page instead of a whole year. It now queriesacts/searchwithlimitandoffsetrather thanacts/{publisher}/{year}, which ignores both and returns the full year every time — 1 093 224 B and 1984 records forDU/2024, of which a default page kept twenty. Results, ordering and response fields are unchanged. Abrowse_actscall with a non-numericyearnow returns a clean tool error instead of silently queryingyear=0.limitis now clamped to the same maximum of 100 items every other list tool applies, because it reaches the API and decides the page width where the year endpoint used to ignore it; a negativelimitoroffsetreturns a clean tool error instead of being silently dropped.search_legal_actsandbrowse_actsnow query the sameacts/searchendpoint, which makes a pre-existing asymmetry newly visible:search_legal_actsstill reportstotal_countas the size of the current page, whilebrowse_actscorrectly reports the size of the whole year.
v4.0.1
Changelog - v4.0.1
All notable changes to the law-scrapper-mcp project for version v4.0.1 will be documented in this file.
The format is based on Keep a Changelog 1.1.0,
and this project adheres to Semantic Versioning 2.0.0.
[4.0.1] - 2026-08-31
Cleanup release — hardening and test coverage from Klaster 7 review findings, no behavior change for existing correct configurations.
Fixed
trusted_proxiesCIDR validation moved to startup — InvalidLAW_MCP_TRUSTED_PROXIESentries now fail fast at configuration time instead of on the first proxied request.- Binary auth token files no longer crash with
UnicodeDecodeError—LAW_MCP_AUTH_TOKEN_FILEpointing at a non-text file now produces a clean, Polish-language configuration error. - OAuth JWKS discovery rejects non-
https://URIs — A discovered JWKS URI that isn'thttps://is refused instead of being fetched, closing a downgrade path. LAW_MCP_AUTH_ISSUER/LAW_MCP_AUTH_JWKS_URInow must behttps://— The discovery-time check above only covered a JWKS URI discovered from the issuer; a directly configuredauth_jwks_uriskipped discovery entirely, and the discovery request itself could still go out over plain HTTP. Both are now rejected at startup.- JWKS/IdP communication failures now log at WARNING instead of INFO — Includes a fix for an except-clause ordering bug that had silently suppressed the intended WARNING level for these failures.
Changed
- Test coverage strengthened — Added a 31-byte token boundary test, a rate-limit-zero rejection test, a
compare_digesttiming-safety spy, and fixedcaplogscoping so auth-related log assertions can't leak between tests. Also restores the wildcard-bind regression guard (test_config_contains_no_wildcard_bind) dropped whenconfig.py's security-boundary logic moved out, now scanning all three modules. httpx2declared as an explicit dev dependency — It was previously an undeclared transitive dependency; its test client is now properly closed after use.bearer_apptest fixture no longer mutates global module state — Removed a use ofimportlib.reloadthat could leak state between tests.- Security-boundary validation extracted from
config.py— Moved into a dedicatedconfig_validation.pymodule, alongside a genuinely dependency-freeconfig_primitives.pyfor shared leaf helpers. No behavior change; internal organization only.
v4.0.0
Changelog - v4.0.0
All notable changes to the law-scrapper-mcp project for version v4.0.0 will be documented in this file.
The format is based on Keep a Changelog 1.1.0,
and this project adheres to Semantic Versioning 2.0.0.
[v4.0.0] - 2026-08-31
BREAKING CHANGES
- Default HTTP bind moved from
0.0.0.0to127.0.0.1— A deployment relying on the implicit wildcard bind becomes unreachable untilLAW_MCP_HOSTis set explicitly. - Binding beyond loopback now requires authentication — Setting
LAW_MCP_HOSTto a non-loopback address requiresLAW_MCP_AUTH_MODEset tobeareroroauth. A container configured with0.0.0.0and no token refuses to start instead of exposing an unauthenticated MCP endpoint.
Added
- Static bearer token authentication —
LAW_MCP_AUTH_MODE=bearervalidates incoming requests againstLAW_MCP_AUTH_TOKENorLAW_MCP_AUTH_TOKEN_FILE; correctly handles bracketed IPv6 hosts in the self-named issuer URL and never echoes the token value in configuration validation errors - OAuth resource server verification —
LAW_MCP_AUTH_MODE=oauthverifies bearer tokens against an OAuth issuer viaLAW_MCP_AUTH_ISSUER,LAW_MCP_AUTH_AUDIENCE, andLAW_MCP_AUTH_RESOURCE_SERVER_URL, tolerating malformed JWKS/discovery documents without crashing - Per-client HTTP rate limiting — Bounds request throughput per client on the Streamable HTTP transport. Enabled by default: 60 requests per 60s window, burst of 10; tune via
LAW_MCP_RATE_LIMIT_ENABLED,LAW_MCP_RATE_LIMIT_REQUESTS,LAW_MCP_RATE_LIMIT_WINDOW,LAW_MCP_RATE_LIMIT_BURST, andLAW_MCP_TRUSTED_PROXIESfor client identification behind a proxy - Network and auth settings surfaced in configuration —
LAW_MCP_HOSTand the auth settings above are validated at startup with Polish-language errors for misconfiguration;LAW_MCP_ALLOWED_HOSTSandLAW_MCP_ALLOWED_ORIGINS(recognizing bare IPv6 loopback) are enforced per request by the MCP SDK's transport-security layer, protecting against DNS-rebinding and cross-origin access - Authenticated deployment documentation — Deployment docs cover bearer and OAuth setup for non-loopback binds
v3.1.2
Changelog - v3.1.2
All notable changes to the law-scrapper-mcp project for version v3.1.2 will be documented in this file.
The format is based on Keep a Changelog 1.1.0,
and this project adheres to Semantic Versioning 2.0.0.
[3.1.2] - 2026-08-25
Changed
- Log output contract — Every log record now carries a
request_idfield:lifespanfor records emitted outside a tool call, an eight-character hexadecimal id for records emitted inside one. JSON logs keep Polish diacritics literal instead of escaping them to\uXXXX, and the JSONtimestampfield now ends in an explicit+00:00instead of being a naive UTC value. Log aggregation pipelines parsing this output absorb one field-shape change, not three.
Fixed
- Query text no longer reaches INFO through the result store or the tool error path —
ResultStore.store()logs the result set id and counts at INFO and moves the query summary to DEBUG; the tool error decorator logs validation failures at ERROR without the caller-supplied message, which moves to DEBUG. In the legal domain a query can be sensitive even when every act it matches is public. Tool responses are unchanged.
v3.1.1
Changelog - v3.1.1
All notable changes to the law-scrapper-mcp project for version v3.1.1 will be documented in this file.
The format is based on Keep a Changelog 1.1.0,
and this project adheres to Semantic Versioning 2.0.0.
[v3.1.1] - 2026-08-24
Added
- Configurable graceful shutdown window —
LAW_MCP_SHUTDOWN_GRACE(default 15s) reaches uvicorn astimeout_graceful_shutdown. The HTTP branch builds its ownuvicorn.Serveron the SDK's publicstreamable_http_app(), enabling clean teardown in containerized deployments withstop_grace_period: 30sin Docker Compose. - Upstream state in
/health— The health endpoint response carries anupstreamobject withcircuit_stateandfailure_countonce the lifespan has started (circuit_stateisunknownandfailure_countis omitted before startup or after teardown). The endpoint consistently returns HTTP 200 while the process is alive.
Changed
- Document conversion moved off the event loop —
html_to_markdown,pdf_to_text, andindex_sectionsrun throughasyncio.to_thread, keeping/healthand concurrent requests responsive during large act processing. - Pre- and post-conversion size limits — Content exceeding
LAW_MCP_DOC_STORE_MAX_SIZE_BYTESis refused before conversion with an error citing the source PDF URL, and re-verified post-conversion to guard against uncompressed text expansion. Refusing oversized documents prevents silent mid-clause truncation. get_act_details(load_content=True)error handling — Calls attempting to load oversized acts returnis_error=truewith the refusal details and source URL instead of reporting successful loading of truncated text. Metadata for oversized acts remains accessible viaget_act_detailswithoutload_content.
v3.1.0
Changelog - v3.1.0
All notable changes to the law-scrapper-mcp project for version v3.1.0 will be documented in this file.
The format is based on Keep a Changelog 1.1.0,
and this project adheres to Semantic Versioning 2.0.0.
[v3.1.0] - 2026-08-22
Added
- Listing and result set pagination —
list_loaded_documentsandlist_result_setsacceptlimit/offsetand returnpage_info. - Search and browse pagination metadata —
search_legal_actsandbrowse_actsreturnpage_info;browse_actsacceptsoffset. - Failed metadata category tracking —
MetadataOutput.failed_categoriesprovides an explicit list of metadata categories that could not be fetched. - Context character ceiling visibility —
SearchInActOutput.context_chars_requested/context_chars_applied, plus an informational hint when context was trimmed. - API retry loop configuration —
LAW_MCP_API_MAX_ATTEMPTS(default 3) andLAW_MCP_API_RETRY_BUDGET(default 45.0 s) settings for the API client retry loop.
Changed
- CI Action Runtime — CI and release workflows bump pinned GitHub Actions to the Node 24 runtime (
actions/checkout7.0.1,astral-sh/setup-uv10.0.1,actions/upload-artifact7.0.1,actions/download-artifact8.0.1,softprops/action-gh-release3.0.2). - API Client Layering & Resilience —
SejmApiClientsplits request handling into three layers (_send/_execute_with_resilience/_request); exception translation occurs outside the retry loop. - Circuit Breaker Admission Contract — The circuit breaker gains an explicit
try_acquire/release_success/release_failure/release_probecontract; the probe counter increments on admission rather than completion. - Specific Exception Classification — HTTP 500 and 504 raise
ApiUnavailableErrorinstead of genericSejmApiError, aligning with the circuit breaker classification while remaining backward-compatible via inheritance. - Search In Act Pagination & Binary Search —
search_in_actbuilds context and resolves sections only for hits on the requested page; section lookup uses binary search. - Concurrent Metadata Retrieval —
get_system_metadata(category="all")fetches categories concurrently within client semaphore limits instead of sequentially. - Search In Act Input Schema Documentation — The
context_charsdescription insearch_in_actinputSchemaclearly states the 2000-character ceiling and trimming behavior.
Fixed
- Metadata count integrity — A failure when fetching an individual metadata category no longer silently understates
total_count. - Search count discrepancy handling —
SearchOutput.total_countis raised to the actual number of returned records when the Sejm API reports acountlower than its response payload. - 5xx error retries — Fixed exception translation so HTTP 5xx errors properly reach the retry policy instead of being intercepted prematurely.
- Transport error handling — Transport errors other than timeouts (
ConnectError,ReadError,RemoteProtocolError) are translated to domain exceptions rather than leaking rawhttpxexceptions to the services layer. - Circuit breaker failure counting — A failed operation increments the breaker failure count by 1 rather than by the number of retry attempts; retry loops obey the configured time budget and abort if the circuit opens.
- Half-open state transition — Confirmed failures in a retry sequence are booked even when subsequent attempts are rejected by the circuit breaker, preventing stuck half-open states.
- Local protocol error exclusion —
httpx.LocalProtocolError(malformed client requests) is excluded from retry loops and circuit breaker failure tallies. - Retry setting validation —
LAW_MCP_API_MAX_ATTEMPTSandLAW_MCP_API_RETRY_BUDGETare validated (ge=1/gt=0) to prevent silent zero-iteration loops. Retry-Afterheader validation — Rejects non-finite, NaN, and negativeRetry-Aftervalues to prevent scheduling anomalies.- Accurate User-Agent reporting — The
User-Agentheader reports the actual server version and contact address instead of a hardcoded string.
Removed
tenacitydependency — Replaced with an explicit, resilient retry loop inclient/sejm_client.py.LAW_MCP_API_MAX_RETRIESsetting — Removed deprecated setting in favor ofLAW_MCP_API_MAX_ATTEMPTS.