openlex-mcp
This MCP server provides AI-native access to the full legal collection of Canton Zurich (~970 cantonal laws) with full-text search, article extraction, and live metadata. Capabilities include:
Full-text search: Search across all laws using FTS5/BM25 ranking, with boolean operators (AND, OR, NOT). Filter by legal area (e.g., 412 education, 331 tax, 700 building) and active status.
Retrieve laws: Look up a specific law by LS number (e.g., 412.100) or abbreviation (e.g., VSG), optionally including full text.
List laws: Browse laws by legal area prefix with pagination and active/inactive filtering.
Education law search: Dedicated search within the LS 412.x series for precise education-related queries.
Article extraction: Extract a specific article by number (e.g., Art. 28, 28a) from a law, showing title, paragraphs, and content. Search for terms within all articles of a given law.
Live metadata: Fetch current metadata (validity, PDF links, last change) from zh.ch via live HTTP request.
Cache management: Refresh the local SQLite/FTS5 database from HuggingFace, with option to force reload.
Access: Works via stdio (for Claude Desktop) or Streamable HTTP (cloud/browser). No API keys needed; all operations are read-only.
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., "@openlex-mcpShow me Art. 55 of the Volksschulgesetz"
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.
π¨π Part of the Swiss Public Data MCP Portfolio
βοΈ openlex-mcp
MCP Server for Canton Zurich legislation (ZH-Lex) β full-text search, article extraction, and education law tools for ~970 cantonal laws
Overview
openlex-mcp provides AI-native access to the entire legal collection of Canton Zurich (ZΓΌrcher Gesetzessammlung). It combines full-text data from HuggingFace with live metadata from the official zh.ch website, storing everything in a local SQLite database with FTS5 full-text indexing for sub-50ms search performance.
Source | Data | Access |
HuggingFace | 974 ZH laws β full text (PDF extracts) | Cached locally as SQLite + FTS5 |
zh.ch ZH-Lex | Current metadata, PDF links, validity status | Live HTTP requests |
Built for the Schulamt (school department) of the City of Zurich, but covers all areas of cantonal law β from tax law to building regulations.
Anchor demo query: "What does the Volksschulgesetz say about parental involvement? Show me Art. 55 VSG and find all articles that mention 'Elternrat'."
Related MCP server: swiss-courts-mcp
Features
βοΈ 8 tools covering search, retrieval, article extraction, and cache management
π FTS5 full-text search across ~970 cantonal laws with BM25 ranking
π Article extraction β parse individual articles (Art. / Β§) with paragraph detection
π« Education law shortcuts β specialized search for LS 412.x series (Volksschulgesetz, Lehrpersonalverordnung, etc.)
π Live metadata from zh.ch for current validity status and PDF links
πΎ Hybrid architecture β cached full-text (HuggingFace) + live metadata (zh.ch)
π No API key required β all data under open licenses (CC-BY-SA 4.0)
βοΈ Dual transport β stdio (Claude Desktop) + Streamable HTTP (cloud)
Development Phase
Current phase: Phase 1 β Read-Only. All tools are read-only (readOnlyHint: true); no writes to external systems. See ROADMAP.md for the phase plan and transition gates before any write or multi-agent capability is added.
Prerequisites
Python 3.11+
uv (recommended) or pip
Internet connection (for initial data download and live metadata)
Installation
# Clone the repository
git clone https://github.com/malkreide/openlex-mcp.git
cd openlex-mcp
# Install
pip install -e .
# or with uv:
uv pip install -e .Quickstart
# stdio (for Claude Desktop)
python -m openlex_mcp.server
# Streamable HTTP β binds to 127.0.0.1:8000 by default (localhost only)
python -m openlex_mcp.server --http --port 8000Network binding
By default the HTTP transport binds to 127.0.0.1 (localhost only). The host
and port are configurable via the MCP_HOST / MCP_PORT environment variables
(or the --host / --port CLI flags, which take precedence).
Never bind to 0.0.0.0 outside a container β it exposes the server to your
local network (NeighborJack risk). For containerized/cloud deployments set
MCP_HOST=0.0.0.0 explicitly; when that happens outside a detected container the
server logs a warning.
Try it immediately in Claude Desktop:
"What is the Volksschulgesetz (VSG)?" "Find all Zurich laws about data protection" "Show me Art. 1 of the Volksschulgesetz" "Which education laws mention 'Schulleitung'?"
Configuration
Claude Desktop
Edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"openlex": {
"command": "python",
"args": ["-m", "openlex_mcp.server"]
}
}
}Or with the installed entry point:
{
"mcpServers": {
"openlex": {
"command": "openlex-mcp"
}
}
}Config file locations:
macOS:
~/Library/Application Support/Claude/claude_desktop_config.jsonWindows:
%APPDATA%\Claude\claude_desktop_config.json
Cloud Deployment (SSE for browser access)
For use via claude.ai in the browser (e.g. on managed workstations without local software):
Render.com (recommended):
Push/fork the repository to GitHub
On render.com: New Web Service β connect GitHub repo
Set start command:
python -m openlex_mcp.server --http --port 8000Set environment variable
MCP_HOST=0.0.0.0so the container is reachable (the code default is127.0.0.1; Render sets theRENDERenv var, so no NeighborJack warning is logged)Set
MCP_CORS_ORIGINS=https://claude.aiso the browser can read theMcp-Session-Idheader (comma-separated list; no wildcard β defaults to empty, i.e. no cross-origin access)In claude.ai under Settings β MCP Servers, add:
https://your-app.onrender.com/sse
π‘ "stdio for the developer laptop, SSE for the browser."
Available Tools
Search & Browse
Tool | Description |
| Full-text search across all ~970 ZH laws (FTS5 + BM25 ranking) |
| Retrieve a law by LS number (e.g. |
| List and filter laws by legal area prefix |
| Specialized search in education law (LS 412.x series) |
Article Extraction
Tool | Description |
| Extract a specific article from a law (e.g. Art. 28 VSG) |
| Search within all articles of a specific law |
Metadata & Cache
Tool | Description |
| Get live metadata from zh.ch (PDF links, validity status) |
| Refresh the local data cache from HuggingFace |
Key Legal Area Prefixes (LS Numbers)
Prefix | Legal Area | Example |
| Constitution and popular rights | Kantonsverfassung |
| Administrative procedure | Datenschutzgesetz |
| Tax law | Steuergesetz |
| Education and schools | Volksschulgesetz (VSG) |
| Spatial planning and building | Planungs- und Baugesetz |
| Health | Gesundheitsgesetz |
Example Use Cases
Query | Tool |
"What is the Volksschulgesetz?" |
|
"Find laws about data protection" |
|
"Show me Art. 55 VSG" |
|
"Which education laws mention Schulleitung?" |
|
"Find all articles about Elternrat in the VSG" |
|
"Is LS 412.100 still in force?" |
|
Architecture
βββββββββββββββββββ ββββββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββ
β Claude / AI ββββββΆβ OpenLex MCP ββββββΆβ HuggingFace β
β (MCP Host) βββββββ (MCP Server) βββββββ rcds/swiss_legislation β
βββββββββββββββββββ β β β (974 ZH laws, cached) β
β 8 Tools β ββββββββββββββββββββββββββββ€
β SQLite + FTS5 Cache ββββββΆβ zh.ch ZH-Lex β
β Stdio | HTTP βββββββ (live metadata + PDFs) β
β β ββββββββββββββββββββββββββββ€
β No authentication required β β LexFind.ch β
ββββββββββββββββββββββββββββββββ β (links only) β
ββββββββββββββββββββββββββββData Source Characteristics
Source | Protocol | Coverage | Auth | License |
HuggingFace | Datasets API | 974 ZH laws (full text) | None | CC-BY-SA 4.0 |
zh.ch ZH-Lex | HTTP/HTML | Current metadata, PDFs | None | Public |
LexFind.ch | HTTP | Cross-cantonal links | None | Public |
Design Decision: Tools-only (no MCP Resources)
All 8 endpoints are exposed as Tools rather than MCP Resources. Rationale:
Every lookup is parametric β queries, abbreviations, article numbers vary per call. Static Resources (one URI per document) don't capture this naturally.
The corpus is 974 laws Γ many articles β registering each as a Resource URI would create an impractically large resource list.
MCP Resource templates (
zhlex://laws/{sr_number}) are a future consideration for Phase 2 if clients benefit from resource-level caching or subscriptions.
Scaling Constraints
The Streamable-HTTP transport keeps session state in-process (FastMCP default). This has two implications:
Single-instance only β horizontal scaling (multiple replicas) breaks active sessions because there is no shared session store (Redis, Durable Objects, etc.).
No sticky-session LB needed today β a single-replica Render deployment naturally routes all requests to one process.
Before scaling beyond one instance: either add a shared session store or configure your edge load balancer to route on the Mcp-Session-Id header with a stick-table and an appropriate TTL.
MCP Protocol Version
Item | Value |
Served via the |
|
Served via the per-request envelope |
|
Who picks | The client's first request, once per connection. A request carrying the |
SDK |
|
Cache hints |
|
Pinned in |
|
Update policy
When
mcpis upgraded (via Dependabot PR), verify the protocol version in the SDK release notes.If the protocol version changes, update
MCP_PROTOCOL_VERSIONinserver.py, regeneratedocs/tool-hashes.json(PYTHONPATH=src python scripts/gen_tool_hashes.py --write), and note the change inCHANGELOG.md.Run
pytest tests/ -m "not live"to confirm compatibility before merging.
Project Structure
openlex-mcp/
βββ src/openlex_mcp/
β βββ __init__.py # Package
β βββ __main__.py # Entry point for python -m
β βββ server.py # 8 MCP tool definitions (FastMCP) + Settings
β βββ responses.py # Typed structured response envelopes (SDK-002)
β βββ logging_config.py # structlog JSON logging setup (OBS-003)
β βββ net.py # SSRF/egress-hardened outbound HTTP
β βββ api_client.py # zh.ch HTTP client + metadata extraction
β βββ data_cache.py # SQLite + FTS5 cache management
β βββ law_parser.py # Article extraction from law texts
βββ tests/ # 89 unit tests (parser, cache, net, toolsβ¦)
βββ scripts/gen_tool_hashes.py # Tool-definition hash snapshot (SEC-022)
βββ docs/ # network-egress, secret-management, tool-hashes
βββ .github/workflows/ci.yml # GitHub Actions (Python 3.11/3.12/3.13)
βββ .github/dependabot.yml # Weekly dependency PRs (ARCH-012)
βββ Dockerfile # Hardened multi-stage build (SEC-007/SCALE-004)
βββ compose.yml # Resource limits for local testing (SCALE-006)
βββ pyproject.toml
βββ claude_desktop_config.json # Example config for Claude Desktop
βββ CHANGELOG.md
βββ ROADMAP.md # Phase plan + accepted-risk register
βββ CONTRIBUTING.md # Contribution guide (English)
βββ CONTRIBUTING.de.md # Contribution guide (German)
βββ SECURITY.md # Security policy (English)
βββ SECURITY.de.md # Security policy (German)
βββ LICENSE
βββ README.md # This file (English)
βββ README.de.md # German versionTool output format
All tools return a structured response envelope (not Markdown text), so MCP
clients receive structuredContent they can parse directly:
{
"source": "Kanton ZΓΌrich Rechtssammlung β HuggingFace β¦ & zh.ch",
"provenance": "cache", // cache | live | parser | cache+parser | none
"result_type": "law_summaries", // law_summaries | law_detail | articles | metadata | cache_status
"count": 2,
"message": null, // human-readable guidance for empty/edge results
"results": [ /* typed items */ ]
}Known Limitations
HuggingFace dataset: The
html_contentfield is unreliable (cross-contaminated between laws); the server usespdf_contentinstead, which is correct but has PDF extraction artefacts (hyphenation, layout artefacts)Article parser: PDF text extraction sometimes merges article boundaries; complex nested articles may not parse perfectly
Initial load: First start requires ~25s to download and index 974 laws from HuggingFace (~38 MB SQLite database)
zh.ch metadata: No official API; metadata extraction relies on HTML patterns that may change
Offline mode: Full-text search works offline after initial load; live metadata requires internet
The corpus is frozen at 2023-01-01. This is the limitation that matters most for a legal server, and it was the one not stated. The newest version in the entire dataset carries
version_active_since = 2023-01-01; the HuggingFace dataset itself was last touched 2024-10-10. The 24-hour cache TTL andprovenance="cache"describe where an answer came from, not how old the laws in it are. Every response now carriescorpus_as_ofandcorpus_notealongsideprovenance.Repeals after the cut-off are invisible. All 974 entries carry
is_active = Trueand not one has aversion_inactive_since. That is not a server bug β the source lists only the statutes in force at snapshot time. The consequence is what matters: a law repealed since then still appears to be in force. Consult the ZH-Lex permalink for the operative text.zhlaw_update_cachedoes not make the laws newer. It re-downloads the same frozen dataset. Its docstring previously read "only call when law search results seem outdated", which suggested exactly the effect it does not have.
Safety & Limits
Aspect | Details |
Access | Read-only ( |
Personal data | No personal data β all sources are aggregated, public legal texts |
Rate limits | Built-in per-query caps (max 50 search results, 5000 chars content preview) |
Timeout | 30 seconds per HTTP call to zh.ch |
Egress | Outbound requests are restricted to an allow-list ( |
Authentication | No API keys required β HuggingFace dataset is public, zh.ch is open |
Security posture (Lethal Trifecta) | Score 1 / 3: public data only (no private/sensitive data) β Β· GET-only egress to |
Session handling |
|
Secrets | No secrets held β all data sources are public. See docs/secret-management.md. |
Licenses | Law data: CC-BY-SA 4.0 (rcds/swiss_legislation); zh.ch metadata: public |
Terms of Service | Subject to ToS of HuggingFace and Canton Zurich |
Disclaimer | This server provides legal texts for informational purposes only β it does not constitute legal advice |
To report a vulnerability, see the Security Policy.
Testing
# Unit + contract tests (no network) β this is what CI runs
PYTHONPATH=src pytest tests/ -m "not live"
# Live tests against zh.ch and HuggingFace
PYTHONPATH=src pytest tests/ -m "live"
# Re-measure the corpus date and the live hosts
PYTHONPATH=src python scripts/record_fixtures.py150 tests β 142 offline, 8 live. Eight tools, eight live tests: the best
coverage in this portfolio, which is why the finding here is not about
mechanics but about a confusion between two questions. provenance="cache"
answers where an answer came from; corpus_as_of answers how old the laws
in it are. Only the first was ever answered, and the second is the one a user
means when they ask "is this current?".
A measurement limit, deliberately not resolved by editing a test
test_live_get_law_metadata fails in the recording environment: zhlex.zh.ch
is not reachable from it. Nothing follows from that. Public DNS resolves
the host (NOERROR, 194.247.8.174) and an NXDOMAIN control shows the query
discriminates β so the limit is the environment's, not the source's.
The test was therefore left untouched. A test you see red because your own
network cannot get out is not a test to rewrite; rewriting it would leave you
measuring your own environment instead of the source. PROVENANCE.md records
this as open.
Changelog
See CHANGELOG.md
Roadmap
See ROADMAP.md
Contributing
See CONTRIBUTING.md
Security
See SECURITY.md
License
MIT License β see LICENSE
Author
Hayal Oezkan Β· malkreide
Credits & Related Projects
Data: rcds/swiss_legislation β HuggingFace dataset (CC-BY-SA 4.0)
ZH-Lex: zh.ch Gesetzessammlung β Official Canton Zurich legal collection
LexFind: lexfind.ch β Cross-cantonal legislation database
Protocol: Model Context Protocol β Anthropic / Linux Foundation
Related: swiss-courts-mcp β Law text + case law = complete legal research
Related: zurich-opendata-mcp β Law text + city council decisions = full context
Portfolio: Swiss Public Data MCP Portfolio
Installation
Run via uv's uvx β no clone or manual install needed. Add to your MCP client config (mcpServers for Claude Desktop, Cursor and Windsurf; use a top-level servers key for VS Code in .vscode/mcp.json):
{
"mcpServers": {
"openlex-mcp": {
"command": "uvx",
"args": [
"openlex-mcp"
]
}
}
}Available Tools
8 toolsopenlex__zhlaw_find_education_lawsARead-onlyIdempotent
Sucht gezielt im ZΓΌrcher Bildungsrecht (Ordnungsnummern 412.x).
Bevorzuge dieses Tool gegenΓΌber openlex__zhlaw_search_laws wenn die Anfrage klar im Bildungsbereich liegt (Schule, Lehrpersonen, Kindergarten, SonderpΓ€dagogik, Tagesstrukturen). Schneller und prΓ€ziser als die allgemeine Suche, da nur die 412.x-Serie (VSG, VSV, LPG, LPVO, VSM u.a.) durchsucht wird.
Sucht ausschliesslich in aktiven 412.x-Gesetzen. Bei keinen Treffern: automatischer Fallback auf alle Rechtsgebiete mit Hinweis im message- Feld. FΓΌr Artikel innerhalb eines gefundenen Gesetzes: openlex__zhlaw_search_articles. Synergie: Fundstelle + swiss-courts-mcp β Rechtsprechung finden.
query='Elternrat', limit=10
| Name | Required | Description | Default |
|---|---|---|---|
| params | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| count | No | |
| source | No | |
| message | No | |
| results | No | |
| provenance | Yes | |
| result_type | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already mark the tool as readOnlyHint, idempotentHint, and not destructive. The description adds valuable context: it searches only active 412.x laws and if no results, automatically falls back to all legal areas with a note in the message field. There is no contradiction with annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-structured with clear sections (<use_case>, <important_notes>, <example>), front-loads the main purpose, and uses concise language. Every sentence adds value, and the example illustrates usage efficiently.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity (search with fallback) and the presence of an output schema (though not shown), the description covers key aspects: scope, fallback behavior, related tools, and synergy with external MCP. It is comprehensive for a search tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema has a detailed description for the query parameter, but the tool description does not repeat it. However, the description provides an example (query='Elternrat', limit=10) and context for typical queries (e.g., 'Kindergarten', 'Schulleitungen'), which adds meaning beyond the schema. Given low schema description coverage (0% per signals), the description compensates adequately but could elaborate more.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states it searches specifically in Zurich education law (Ordnungsnummern 412.x) and distinguishes itself from sibling tools like openlex__zhlaw_search_laws by being faster and more precise for education-related queries. It also lists specific laws covered (VSG, VSV, LPG, etc.), making the purpose unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The <use_case> tag explicitly states when to prefer this tool over sibling openlex__zhlaw_search_laws (e.g., when query is education-related). It also provides conditions for use (school, teachers, kindergarten, etc.) and mentions fallback behavior to all legal areas with a message hint. Additionally, it advises using other tools for articles and synergy with swiss-courts-mcp.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
openlex__zhlaw_get_articleARead-onlyIdempotent
Extrahiert einen einzelnen Artikel aus einem ZΓΌrcher Gesetz.
Wenn der genaue Wortlaut eines bestimmten Artikels benΓΆtigt wird. Voraussetzung: Gesetz und Artikelnummer sind bekannt. Um zuerst relevante Artikel zu finden: openlex__zhlaw_search_articles nutzen.
UnterstΓΌtzt Standard-Artikelnummern (28), Buchstaben-Artikel (28a) und Bis-Artikel (28bis). Liefert Titel, alle AbsΓ€tze und Volltext des Artikels. Gibt count=0 zurΓΌck wenn der Artikel nicht existiert β Artikelnummer ohne 'Art.' angeben (nur die Zahl).
law_identifier='VSG', article_number='28'
| Name | Required | Description | Default |
|---|---|---|---|
| params | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| count | No | |
| source | No | |
| message | No | |
| results | No | |
| provenance | Yes | |
| result_type | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations (readOnlyHint=true, idempotentHint=true, destructiveHint=false) already indicate safety. Description adds specifics: supports standard/letter/bis article numbers, returns title/paragraphs/full text, returns count=0 if not found, and format requirements like omitting 'Art.' prefix.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Structured with XML tags (use_case, important_notes, example) that organize information efficiently. Every sentence is necessary, no fluff, and front-loaded with the core purpose.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple two-parameter retrieval tool, the description covers all needed context: what it retrieves, error behavior (count=0), and format requirements. Output schema exists, so return value details are not required.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema descriptions already cover parameter formats and examples. Tool description adds a concrete example but no new semantic information beyond the schema, which already specifies 'Ohne 'Art.' Prefix' and examples.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states 'Extrahiert einen einzelnen Artikel aus einem ZΓΌrcher Gesetz' with a use case that clarifies when to use it and distinguishes it from sibling tool openlex__zhlaw_search_articles for searching.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly includes a <use_case> block describing when to use (exact wording needed, known identifiers) and directs to openlex__zhlaw_search_articles for finding articles first.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
openlex__zhlaw_get_lawARead-onlyIdempotent
Ruft ein ZΓΌrcher Gesetz anhand der Ordnungsnummer oder AbkΓΌrzung ab.
Wenn die Ordnungsnummer oder AbkΓΌrzung eines Gesetzes bereits bekannt ist und Metadaten oder Volltext abgerufen werden sollen. Um ein Gesetz erst zu finden, openlex__zhlaw_search_laws oder openlex__zhlaw_find_education_laws vorschalten.
UnterstΓΌtzt LS-Nummern ('412.100') und AbkΓΌrzungen ('VSG'). include_content=True liefert den Volltext (bis 5000 Zeichen, dann truncated=True). Bei Truncation: openlex__zhlaw_get_article fΓΌr einzelne Artikel verwenden. Wichtige Gesetze: VSG, LPG, PBG, StG, KV.
identifier='VSG', include_content=False
| Name | Required | Description | Default |
|---|---|---|---|
| params | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| count | No | |
| source | No | |
| message | No | |
| results | No | |
| provenance | Yes | |
| result_type | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Adds behavioral details beyond annotations: include_content returns up to 5000 characters with truncation flag; lists important laws. Annotations already indicate readOnly and idempotent, no contradiction.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Well-structured with clear sections (use_case, important_notes, example). Every sentence adds value, no fluff. Purpose stated in first line.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's simplicity (1 required param, no nested objects, output schema exists), the description covers all necessary aspects: input format, behavior of include_content, truncation handling, and related tools.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema descriptions already cover both parameters (identifier and include_content). Description adds practical examples and known abbreviations, enhancing usability beyond schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool retrieves a Zurich law by identifier (Ordnungsnummer or Abbreviation), and distinguishes it from sibling tools like openlex__zhlaw_search_laws for finding laws.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit use case: when identifier is known. Provides when-not-to-use (use search_laws or find_education_laws to find) and alternatives for truncated content (openlex__zhlaw_get_article).
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
openlex__zhlaw_get_law_metadataARead-onlyIdempotent
Ruft aktuelle Metadaten eines Gesetzes live von zh.ch ab.
Wenn der aktuelle Stand eines Gesetzes auf zh.ch geprΓΌft werden soll β z.B. ob es kΓΌrzlich geΓ€ndert wurde, welche PDF-Version aktuell gilt oder welche ZH-Lex URL direkt verlinkt werden kann. Einziges Tool mit Live-HTTP-Aufruf; alle anderen Tools lesen den lokalen Cache.
Macht einen echten HTTP-Request an www.zh.ch (ca. 1β3 s). Erfordert Netzwerkzugang. Liefert: Seitentitel, PDF-Links, Γnderungsdatum, ZH-Lex URL. Bei 404 oder Timeout: found=False mit Fehlerdetail im error-Feld. Nur Ordnungsnummern akzeptiert (kein AbkΓΌrzungs-Lookup).
sr_number='412.100'
| Name | Required | Description | Default |
|---|---|---|---|
| params | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| count | No | |
| source | No | |
| message | No | |
| results | No | |
| provenance | Yes | |
| result_type | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already provide readOnlyHint, openWorldHint, idempotentHint, destructiveHint=false. Description adds details: real HTTP request (1-3s), required network access, return fields, error handling (404/Timeout). No contradictions. Slightly above baseline because description adds performance and error context not in annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Description is well-structured with clear sections (use_case, important_notes, example). Every sentence adds value. No fluff. Two short paragraphs with XML-like tags make it easy to scan.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with one parameter and an output schema, the description covers purpose, usage, behavioral details, parameter format, error handling, and constraints. Complete and self-contained. Sibling tools are diverse, and this description clearly differentiates.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0% per context, but the schema itself includes a description for the single parameter sr_number with example and format. The tool description includes an additional example. Since schema already provides parameter meaning, description adds minimal extra value. Baseline 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Description clearly states it retrieves current metadata of a law live from zh.ch. It distinguishes from siblings by noting it's the only tool with a live HTTP call, while others use local cache. Verb+resource is specific and unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Use_case section explicitly describes when to use: checking current status, recent changes, PDF version, ZH-Lex URL. It states it's the only live-HTTP tool, implying alternatives. Important_notes specify input constraints (only Ordnungsnummer, no abbreviation lookup).
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
openlex__zhlaw_list_lawsARead-onlyIdempotent
Listet ZΓΌrcher Gesetze auf mit optionalem Filter nach Rechtsgebiet.
Wenn eine strukturierte Γbersicht aller Gesetze eines Rechtsgebiets benΓΆtigt wird (z.B. alle aktiven Bildungsgesetze). FΓΌr gezielte Textsuche ist openlex__zhlaw_search_laws besser; fΓΌr reines Bildungsrecht openlex__zhlaw_find_education_laws.
sr_prefix filtert nach Ordnungsnummer-Prefix (412=Bildung, 331=Steuern, 700=Bau, 131=Verfassung, 810=Gesundheit). active_only=True blendet aufgehobene Gesetze aus. UnterstΓΌtzt Paginierung via offset. Gibt Titel, AbkΓΌrzung, SR-Nummer und Status zurΓΌck β keinen Volltext.
sr_prefix='412', active_only=True, limit=50
| Name | Required | Description | Default |
|---|---|---|---|
| params | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| count | No | |
| source | No | |
| message | No | |
| results | No | |
| provenance | Yes | |
| result_type | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already provide readOnlyHint, idempotentHint, destructiveHint. The description adds context about sr_prefix examples, active_only filtering, pagination via offset, and return fields (no full text), which is valuable beyond annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is structured with use_case, important_notes, and example tags. It is concise, front-loaded with the main purpose, and every sentence adds value.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given complexity (4 sub-params), output schema exists, and annotations are present, the description covers when to use, parameter details, return fields, and sibling differentiation. It is complete for an AI agent.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The description adds concrete examples for sr_prefix (412=Bildung, etc.) and explains active_only and pagination. The schema itself has decent descriptions, but the description enhances understanding with real-world usage.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool lists Zurich laws with optional filter, using specific verb 'auflisten' and resource 'ZΓΌrcher Gesetze'. It distinguishes itself from siblings by referencing use cases for alternative tools.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The use_case tag explicitly states when to use (structured overview) and when not (targeted text search or pure education law), naming alternative tools search_laws and find_education_laws.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
openlex__zhlaw_search_articlesARead-onlyIdempotent
Durchsucht alle Artikel eines bestimmten Gesetzes nach einem Begriff.
Wenn bekannt ist, in welchem Gesetz gesucht werden soll, aber nicht welcher Artikel relevant ist. Liefert alle Treffer-Artikel mit Inhalt. FΓΌr einen einzelnen Artikel mit bekannter Nummer: openlex__zhlaw_get_article.
Parst das Gesetz in einzelne Artikel und durchsucht Titel und Inhalt. Suche ist case-insensitive, kein FTS5 (einfaches Substring-Match). Gibt count=0 wenn kein Artikel den Begriff enthΓ€lt. BenΓΆtigt Volltext im Cache β bei fehlendem Inhalt: Hinweis im message-Feld.
law_identifier='VSG', query='Elternrat'
| Name | Required | Description | Default |
|---|---|---|---|
| params | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| count | No | |
| source | No | |
| message | No | |
| results | No | |
| provenance | Yes | |
| result_type | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description discloses important behavioral traits such as case-insensitive substring matching, no FTS5, parsing law into articles, and cache requirements. These add significant value beyond the annotations which only indicate read-only and idempotent hints.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is concise and well-structured with clear sections (<use_case>, <important_notes>, <example>). Every sentence adds value without unnecessary verbosity.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the presence of an output schema and the comprehensive description covering purpose, usage, behavior, and an example, the description is complete enough for an AI agent to understand and invoke the tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema already provides descriptions for both parameters. The description adds an example usage and reinforces the meaning, but does not introduce new semantics beyond what is in the schema. However, the example is helpful for clarifying the intended use.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states that the tool searches all articles of a specific law for a term, using a specific verb and resource. It distinguishes itself from sibling tools by noting when to use this tool vs. openlex__zhlaw_get_article.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly provides a use case ('when the law is known but the article is not') and an alternative ('for a specific article number, use openlex__zhlaw_get_article'). This gives clear guidance on when to use this tool.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
openlex__zhlaw_search_lawsARead-onlyIdempotent
Volltextsuche in allen ZΓΌrcher Gesetzen mit FTS5-Ranking.
Allgemeine Suche nach einem Rechtsbegriff ΓΌber alle ~970 kantonalen Gesetze. WΓ€hle dieses Tool wenn das Rechtsgebiet unbekannt ist. FΓΌr das Bildungsrecht (412.x) ist openlex__zhlaw_find_education_laws schneller und prΓ€ziser. Um Artikel innerhalb eines bekannten Gesetzes zu finden, nutze openlex__zhlaw_search_articles.
FTS5-Syntax: AND, OR, NOT, Phrasensuche "...". Ergebnisse nach BM25-Relevanz sortiert. Max 50 Treffer pro Aufruf (limit-Parameter). sr_prefix filtert nach Rechtsgebiet (412=Bildung, 331=Steuern, 700=Bau). Sucht im lokalen Cache β AktualitΓ€t der Metadaten via openlex__zhlaw_get_law_metadata prΓΌfen.
query='Elternrat OR Elternmitwirkung', sr_prefix='412', limit=10
| Name | Required | Description | Default |
|---|---|---|---|
| params | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| count | No | |
| source | No | |
| message | No | |
| results | No | |
| provenance | Yes | |
| result_type | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the annotations (readOnlyHint, idempotentHint, destructiveHint), the description adds crucial behavioral details: FTS5 syntax (AND, OR, NOT, phrase search), BM25 relevance sorting, max 50 results, local caching, and a note to check metadata freshness via openlex__zhlaw_get_law_metadata. No contradictions.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is succinct and well-structured using <use_case>, <important_notes>, and <example> tags. Each section adds necessary information without redundancy. The first sentence immediately conveys the core purpose.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity (4 parameters, an output schema, annotations), the description covers use cases, important notes, and an example. It explains caching behavior and when to check metadata freshness. The output schema is present, so return values need not be described.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
With schema coverage at 0%, the description meaningfully explains the query, limit, and sr_prefix parameters, including FTS5 syntax examples and how to filter by legal area. It does not mention the active_only parameter, but the other parameters are well-covered with practical guidance.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool does a full-text search across all ~970 Zurich cantonal laws using FTS5 ranking. It explicitly distinguishes itself from sibling tools: openlex__zhlaw_find_education_laws for the education law area and openlex__zhlaw_search_articles for finding articles within a known law.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The <use_case> tag explicitly states when to use this tool (when the legal area is unknown) and when to use alternatives (for education law or within a known law). It names specific sibling tools and provides clear context.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
openlex__zhlaw_update_cacheAIdempotent
Aktualisiert den lokalen Cache der ZΓΌrcher Gesetzesdaten.
Nur aufrufen wenn Gesetzes-Suchergebnisse veraltet wirken oder der Cache explizit neu geladen werden soll. Der Cache wird automatisch beim Start befΓΌllt und ist 24 Stunden gΓΌltig β manuelles Update ist selten nΓΆtig.
LΓ€dt ~970 Gesetze von HuggingFace (rcds/swiss_legislation) in die lokale SQLite-DB mit FTS5-Index (~25 s erster Lauf). force=False ΓΌberspringt den Download wenn Cache <24h alt (gibt status='cache_fresh' zurΓΌck). force=True erzwingt Neudownload. Erfordert Internetzugang zu HuggingFace.
force=False
| Name | Required | Description | Default |
|---|---|---|---|
| params | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| count | No | |
| source | No | |
| message | No | |
| results | No | |
| provenance | Yes | |
| result_type | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond annotations (idempotent, not destructive), the description adds key behaviors: downloads ~970 laws from HuggingFace, takes ~25s first run, force=False skips if cache fresh returning 'cache_fresh', force=True forces download, requires internet. This fully informs the agent of side effects and state outcomes.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-structured: short introductory sentence, then use_case, important_notes (with timings and behavior), and a clear example. Every sentence serves a purpose, and it's front-loaded with the core action.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple one-parameter update action, the description covers usage, behavior, and output snippet. An output schema exists, so detailed return structure is not needed. Minor gap: no mention of error handling or internet failure, but still complete enough for typical use.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The single boolean parameter 'force' is described in the schema as 'Cache auch wenn aktuell (<24h) erzwingen.' The tool description adds explicit return value behavior (status='cache_fresh' when skipped) and the time context. This adds meaningful nuance beyond the schema's brief description.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states it updates the local cache of Zurich law data. The use_case explicitly tells when to call: when search results seem outdated or cache needs reloading. Sibling tools are all query tools, so this stands out as a cache management action.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides explicit context: cache auto-refreshes every 24h, manual update rarely needed, and condition for calling (outdated results). However, it does not name specific alternative tools for when the cache should not be called, though siblings are clearly different in purpose.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
8 tool updates
v0.2.3- First observed
openlex__zhlaw_find_education_laws - First observed
openlex__zhlaw_get_article - First observed
openlex__zhlaw_get_law - First observed
openlex__zhlaw_get_law_metadata - First observed
openlex__zhlaw_list_laws - First observed
openlex__zhlaw_search_articles - First observed
openlex__zhlaw_search_laws - First observed
openlex__zhlaw_update_cache
TDQS
Scored across 8 tools
Each tool has a clearly distinct purpose: full-text search across all laws, retrieval by number/abbreviation, article extraction, listing, specialized education search, article search within a law, live metadata, and cache update. The use case guidance in descriptions eliminates ambiguity.
All tool names follow a consistent pattern: openlex__zhlaw_<verb>_<noun> (e.g., search_laws, get_law, get_article). The verb-noun structure is uniform across all tools, using standard verbs like search, get, list, find, update.
8 tools is well-scoped for Zurich cantonal law: covers search, retrieval, listing, specialized search, metadata, and cache management. Neither too few nor too many, each tool earns its place.
The tool set covers the full lifecycle of legal retrieval: searching across laws, finding specific articles, retrieving full text, listing laws by area, getting live metadata, and managing cache. No obvious gaps for a read-only legal database.
Maintenance
Related MCP Connectors
opendata.swiss MCP β Switzerland's federal open-data portal (CKAN catalogue).
Swiss federal law (Fedlex) and political data (LINDAS) for agents, every answer with sources
MCP for CanLII: Canadian case law and legislation metadata (federal, provincial, territorial).
TED MCP Server: Real-time EU public tenders access. https://www.lexsocket.ai/
Related MCP Servers
- AlicenseAqualityAmaintenanceMCP Server for Swiss federal law β search the SR, monitor legal changes, and query BBl/treaties via Claude Desktop or Claude.ai.125MIT
- AlicenseAqualityAmaintenanceMCP server for searching Swiss court decisions from federal and cantonal courts via entscheidsuche.ch. Enables full-text search, law reference lookup, and filtering by canton, court level, and date without API keys.81MIT

conformi-searchofficial
AlicenseAqualityAmaintenanceInstallable MCP server for EU legal research with verifiable CELEX citations from the EUR-Lex corpus (DE/EN/FR).21MIT- AlicenseAqualityAmaintenanceMCP server for Swiss federal legislation metadata via Fedlex, enabling search and retrieval of act details with ELI URIs, SR numbers, and multilingual support.3Apache 2.0