openlex-mcp 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openlex_mcp-0.1.0/.github/workflows/ci.yml +33 -0
- openlex_mcp-0.1.0/.github/workflows/publish.yml +50 -0
- openlex_mcp-0.1.0/.gitignore +27 -0
- openlex_mcp-0.1.0/CHANGELOG.md +22 -0
- openlex_mcp-0.1.0/CONTRIBUTING.md +54 -0
- openlex_mcp-0.1.0/LICENSE +21 -0
- openlex_mcp-0.1.0/PKG-INFO +338 -0
- openlex_mcp-0.1.0/README.de.md +288 -0
- openlex_mcp-0.1.0/README.md +288 -0
- openlex_mcp-0.1.0/claude_desktop_config.json +8 -0
- openlex_mcp-0.1.0/pyproject.toml +70 -0
- openlex_mcp-0.1.0/src/openlex_mcp/__init__.py +1 -0
- openlex_mcp-0.1.0/src/openlex_mcp/__main__.py +4 -0
- openlex_mcp-0.1.0/src/openlex_mcp/api_client.py +219 -0
- openlex_mcp-0.1.0/src/openlex_mcp/data_cache.py +467 -0
- openlex_mcp-0.1.0/src/openlex_mcp/law_parser.py +270 -0
- openlex_mcp-0.1.0/src/openlex_mcp/server.py +848 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, master]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main, master]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v5
|
|
18
|
+
|
|
19
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
20
|
+
uses: actions/setup-python@v6
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python-version }}
|
|
23
|
+
|
|
24
|
+
- name: Install dependencies
|
|
25
|
+
run: |
|
|
26
|
+
pip install -e ".[dev]"
|
|
27
|
+
|
|
28
|
+
- name: Run tests
|
|
29
|
+
run: |
|
|
30
|
+
PYTHONPATH=src pytest tests/ -m "not live"
|
|
31
|
+
|
|
32
|
+
- name: Lint
|
|
33
|
+
run: ruff check src/ tests/
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
build:
|
|
9
|
+
name: Build distribution
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v5
|
|
14
|
+
|
|
15
|
+
- name: Set up Python
|
|
16
|
+
uses: actions/setup-python@v6
|
|
17
|
+
with:
|
|
18
|
+
python-version: "3.11"
|
|
19
|
+
|
|
20
|
+
- name: Install build tools
|
|
21
|
+
run: pip install build
|
|
22
|
+
|
|
23
|
+
- name: Build package
|
|
24
|
+
run: python -m build
|
|
25
|
+
|
|
26
|
+
- name: Upload build artifact
|
|
27
|
+
uses: actions/upload-artifact@v4
|
|
28
|
+
with:
|
|
29
|
+
name: dist
|
|
30
|
+
path: dist/
|
|
31
|
+
|
|
32
|
+
publish:
|
|
33
|
+
name: Publish to PyPI
|
|
34
|
+
needs: build
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
environment:
|
|
37
|
+
name: pypi
|
|
38
|
+
url: https://pypi.org/project/openlex-mcp/
|
|
39
|
+
permissions:
|
|
40
|
+
id-token: write # Required for Trusted Publisher (OIDC)
|
|
41
|
+
|
|
42
|
+
steps:
|
|
43
|
+
- name: Download build artifact
|
|
44
|
+
uses: actions/download-artifact@v4
|
|
45
|
+
with:
|
|
46
|
+
name: dist
|
|
47
|
+
path: dist/
|
|
48
|
+
|
|
49
|
+
- name: Publish to PyPI
|
|
50
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Data cache
|
|
2
|
+
data/*.db
|
|
3
|
+
data/*.sqlite
|
|
4
|
+
|
|
5
|
+
# Python
|
|
6
|
+
__pycache__/
|
|
7
|
+
*.py[cod]
|
|
8
|
+
*.egg-info/
|
|
9
|
+
dist/
|
|
10
|
+
build/
|
|
11
|
+
.eggs/
|
|
12
|
+
|
|
13
|
+
# Virtual environments
|
|
14
|
+
.venv/
|
|
15
|
+
venv/
|
|
16
|
+
env/
|
|
17
|
+
|
|
18
|
+
# IDE
|
|
19
|
+
.vscode/
|
|
20
|
+
.idea/
|
|
21
|
+
|
|
22
|
+
# OS
|
|
23
|
+
.DS_Store
|
|
24
|
+
Thumbs.db
|
|
25
|
+
|
|
26
|
+
# HuggingFace cache
|
|
27
|
+
data/hf_cache/
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0] - 2026-04-12
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Initial release with 8 MCP tools for Canton Zurich legislation
|
|
14
|
+
- **Search tools**: `zhlaw_search_laws`, `zhlaw_list_laws`, `zhlaw_find_education_laws`
|
|
15
|
+
- **Retrieval tools**: `zhlaw_get_law`, `zhlaw_get_law_metadata`
|
|
16
|
+
- **Article tools**: `zhlaw_get_article`, `zhlaw_search_articles`
|
|
17
|
+
- **Cache tools**: `zhlaw_update_cache`
|
|
18
|
+
- Local SQLite + FTS5 cache with automatic HuggingFace data loading (974 ZH laws)
|
|
19
|
+
- Article parser supporting Art./§ notation and superscript paragraph digits
|
|
20
|
+
- Hybrid architecture: cached full-text (HuggingFace) + live metadata (zh.ch)
|
|
21
|
+
- Dual transport: stdio (Claude Desktop) + Streamable HTTP (cloud)
|
|
22
|
+
- Bilingual documentation (EN/DE)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Contributing to openlex-mcp
|
|
2
|
+
|
|
3
|
+
Thank you for your interest in contributing! This server is part of the [Swiss Public Data MCP Portfolio](https://github.com/malkreide).
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Reporting Issues
|
|
8
|
+
|
|
9
|
+
Use [GitHub Issues](https://github.com/malkreide/openlex-mcp/issues) to report bugs or request features.
|
|
10
|
+
|
|
11
|
+
Please include:
|
|
12
|
+
- Python version and OS
|
|
13
|
+
- Full error message or description of unexpected behaviour
|
|
14
|
+
- Steps to reproduce
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Pull Requests
|
|
19
|
+
|
|
20
|
+
1. Fork the repository
|
|
21
|
+
2. Create a feature branch: `git checkout -b feat/your-feature`
|
|
22
|
+
3. Make your changes and add tests
|
|
23
|
+
4. Ensure all tests pass: `PYTHONPATH=src pytest tests/ -m "not live"`
|
|
24
|
+
5. Commit using [Conventional Commits](https://www.conventionalcommits.org/): `feat: add new tool`
|
|
25
|
+
6. Push and open a Pull Request against `main`
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Code Style
|
|
30
|
+
|
|
31
|
+
- Python 3.11+
|
|
32
|
+
- [Ruff](https://github.com/astral-sh/ruff) for linting and formatting
|
|
33
|
+
- Type hints required for all public functions
|
|
34
|
+
- Tests required for new tools
|
|
35
|
+
- Follow the existing FastMCP / Pydantic v2 patterns in `server.py`
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Data Sources
|
|
40
|
+
|
|
41
|
+
This server uses two open data sources — all without authentication:
|
|
42
|
+
|
|
43
|
+
| Source | Documentation |
|
|
44
|
+
|--------|--------------|
|
|
45
|
+
| HuggingFace `rcds/swiss_legislation` | [huggingface.co/datasets/rcds/swiss_legislation](https://huggingface.co/datasets/rcds/swiss_legislation) |
|
|
46
|
+
| zh.ch ZH-Lex | [zh.ch Gesetzessammlung](https://www.zh.ch/de/politik-staat/gesetze-beschluesse/gesetzessammlung.html) |
|
|
47
|
+
|
|
48
|
+
When adding new data sources, follow the **No-Auth-First** principle: use only open, authentication-free endpoints.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## License
|
|
53
|
+
|
|
54
|
+
By contributing, you agree that your contributions will be licensed under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Hayal Oezkan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openlex-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP Server for Canton Zurich legislation (ZH-Lex) — full-text search, article extraction, and education law tools
|
|
5
|
+
Project-URL: Homepage, https://github.com/malkreide/openlex-mcp
|
|
6
|
+
Project-URL: Repository, https://github.com/malkreide/openlex-mcp
|
|
7
|
+
Project-URL: Issues, https://github.com/malkreide/openlex-mcp/issues
|
|
8
|
+
License: MIT License
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2025 Hayal Oezkan
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
+
SOFTWARE.
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Keywords: bildungsrecht,gesetzessammlung,mcp,model-context-protocol,swiss-open-data,volksschulgesetz,zh-lex,zurich-law
|
|
31
|
+
Classifier: Development Status :: 3 - Alpha
|
|
32
|
+
Classifier: Intended Audience :: Developers
|
|
33
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
34
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
37
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
38
|
+
Requires-Python: >=3.11
|
|
39
|
+
Requires-Dist: datasets>=2.14.0
|
|
40
|
+
Requires-Dist: httpx>=0.27.0
|
|
41
|
+
Requires-Dist: mcp[cli]>=1.3.0
|
|
42
|
+
Requires-Dist: pydantic>=2.0.0
|
|
43
|
+
Provides-Extra: dev
|
|
44
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
|
|
45
|
+
Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
|
|
46
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
47
|
+
Requires-Dist: respx>=0.21.0; extra == 'dev'
|
|
48
|
+
Requires-Dist: ruff>=0.4.0; extra == 'dev'
|
|
49
|
+
Description-Content-Type: text/markdown
|
|
50
|
+
|
|
51
|
+
> 🇨🇭 **Part of the [Swiss Public Data MCP Portfolio](https://github.com/malkreide)**
|
|
52
|
+
|
|
53
|
+
# ⚖️ openlex-mcp
|
|
54
|
+
|
|
55
|
+

|
|
56
|
+
[](https://opensource.org/licenses/MIT)
|
|
57
|
+
[](https://www.python.org/downloads/)
|
|
58
|
+
[](https://modelcontextprotocol.io/)
|
|
59
|
+
[](https://github.com/malkreide/openlex-mcp)
|
|
60
|
+
|
|
61
|
+
> MCP Server for Canton Zurich legislation (ZH-Lex) — full-text search, article extraction, and education law tools for ~970 cantonal laws
|
|
62
|
+
|
|
63
|
+
[🇩🇪 Deutsche Version](README.de.md)
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Overview
|
|
68
|
+
|
|
69
|
+
`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.
|
|
70
|
+
|
|
71
|
+
| Source | Data | Access |
|
|
72
|
+
|--------|------|--------|
|
|
73
|
+
| **HuggingFace** | 974 ZH laws — full text (PDF extracts) | Cached locally as SQLite + FTS5 |
|
|
74
|
+
| **zh.ch ZH-Lex** | Current metadata, PDF links, validity status | Live HTTP requests |
|
|
75
|
+
|
|
76
|
+
Built for the Schulamt (school department) of the City of Zurich, but covers all areas of cantonal law — from tax law to building regulations.
|
|
77
|
+
|
|
78
|
+
**Anchor demo query:** *"What does the Volksschulgesetz say about parental involvement? Show me Art. 55 VSG and find all articles that mention 'Elternrat'."*
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Features
|
|
83
|
+
|
|
84
|
+
- ⚖️ **8 tools** covering search, retrieval, article extraction, and cache management
|
|
85
|
+
- 🔍 **FTS5 full-text search** across ~970 cantonal laws with BM25 ranking
|
|
86
|
+
- 📑 **Article extraction** — parse individual articles (Art. / §) with paragraph detection
|
|
87
|
+
- 🏫 **Education law shortcuts** — specialized search for LS 412.x series (Volksschulgesetz, Lehrpersonalverordnung, etc.)
|
|
88
|
+
- 🌐 **Live metadata** from zh.ch for current validity status and PDF links
|
|
89
|
+
- 💾 **Hybrid architecture** — cached full-text (HuggingFace) + live metadata (zh.ch)
|
|
90
|
+
- 🔓 **No API key required** — all data under open licenses (CC-BY-SA 4.0)
|
|
91
|
+
- ☁️ **Dual transport** — stdio (Claude Desktop) + Streamable HTTP (cloud)
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Prerequisites
|
|
96
|
+
|
|
97
|
+
- Python 3.11+
|
|
98
|
+
- [uv](https://github.com/astral-sh/uv) (recommended) or pip
|
|
99
|
+
- Internet connection (for initial data download and live metadata)
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Installation
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
# Clone the repository
|
|
107
|
+
git clone https://github.com/malkreide/openlex-mcp.git
|
|
108
|
+
cd openlex-mcp
|
|
109
|
+
|
|
110
|
+
# Install
|
|
111
|
+
pip install -e .
|
|
112
|
+
# or with uv:
|
|
113
|
+
uv pip install -e .
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Quickstart
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
# stdio (for Claude Desktop)
|
|
122
|
+
python -m openlex_mcp.server
|
|
123
|
+
|
|
124
|
+
# Streamable HTTP (port 8000)
|
|
125
|
+
python -m openlex_mcp.server --http --port 8000
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Try it immediately in Claude Desktop:
|
|
129
|
+
|
|
130
|
+
> *"What is the Volksschulgesetz (VSG)?"*
|
|
131
|
+
> *"Find all Zurich laws about data protection"*
|
|
132
|
+
> *"Show me Art. 1 of the Volksschulgesetz"*
|
|
133
|
+
> *"Which education laws mention 'Schulleitung'?"*
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Configuration
|
|
138
|
+
|
|
139
|
+
### Claude Desktop
|
|
140
|
+
|
|
141
|
+
Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
|
|
142
|
+
|
|
143
|
+
```json
|
|
144
|
+
{
|
|
145
|
+
"mcpServers": {
|
|
146
|
+
"openlex": {
|
|
147
|
+
"command": "python",
|
|
148
|
+
"args": ["-m", "openlex_mcp.server"]
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Or with the installed entry point:
|
|
155
|
+
|
|
156
|
+
```json
|
|
157
|
+
{
|
|
158
|
+
"mcpServers": {
|
|
159
|
+
"openlex": {
|
|
160
|
+
"command": "openlex-mcp"
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Config file locations:**
|
|
167
|
+
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
168
|
+
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
169
|
+
|
|
170
|
+
### Cloud Deployment (SSE for browser access)
|
|
171
|
+
|
|
172
|
+
For use via **claude.ai in the browser** (e.g. on managed workstations without local software):
|
|
173
|
+
|
|
174
|
+
**Render.com (recommended):**
|
|
175
|
+
1. Push/fork the repository to GitHub
|
|
176
|
+
2. On [render.com](https://render.com): New Web Service → connect GitHub repo
|
|
177
|
+
3. Set start command: `python -m openlex_mcp.server --http --port 8000`
|
|
178
|
+
4. In claude.ai under Settings → MCP Servers, add: `https://your-app.onrender.com/sse`
|
|
179
|
+
|
|
180
|
+
> 💡 *"stdio for the developer laptop, SSE for the browser."*
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Available Tools
|
|
185
|
+
|
|
186
|
+
### Search & Browse
|
|
187
|
+
|
|
188
|
+
| Tool | Description |
|
|
189
|
+
|------|-------------|
|
|
190
|
+
| `zhlaw_search_laws` | Full-text search across all ~970 ZH laws (FTS5 + BM25 ranking) |
|
|
191
|
+
| `zhlaw_get_law` | Retrieve a law by LS number (e.g. `412.100`) or abbreviation (e.g. `VSG`) |
|
|
192
|
+
| `zhlaw_list_laws` | List and filter laws by legal area prefix |
|
|
193
|
+
| `zhlaw_find_education_laws` | Specialized search in education law (LS 412.x series) |
|
|
194
|
+
|
|
195
|
+
### Article Extraction
|
|
196
|
+
|
|
197
|
+
| Tool | Description |
|
|
198
|
+
|------|-------------|
|
|
199
|
+
| `zhlaw_get_article` | Extract a specific article from a law (e.g. Art. 28 VSG) |
|
|
200
|
+
| `zhlaw_search_articles` | Search within all articles of a specific law |
|
|
201
|
+
|
|
202
|
+
### Metadata & Cache
|
|
203
|
+
|
|
204
|
+
| Tool | Description |
|
|
205
|
+
|------|-------------|
|
|
206
|
+
| `zhlaw_get_law_metadata` | Get live metadata from zh.ch (PDF links, validity status) |
|
|
207
|
+
| `zhlaw_update_cache` | Refresh the local data cache from HuggingFace |
|
|
208
|
+
|
|
209
|
+
### Key Legal Area Prefixes (LS Numbers)
|
|
210
|
+
|
|
211
|
+
| Prefix | Legal Area | Example |
|
|
212
|
+
|--------|-----------|---------|
|
|
213
|
+
| `131` | Constitution and popular rights | Kantonsverfassung |
|
|
214
|
+
| `170` | Administrative procedure | Datenschutzgesetz |
|
|
215
|
+
| `331` | Tax law | Steuergesetz |
|
|
216
|
+
| `412` | Education and schools | Volksschulgesetz (VSG) |
|
|
217
|
+
| `700` | Spatial planning and building | Planungs- und Baugesetz |
|
|
218
|
+
| `810` | Health | Gesundheitsgesetz |
|
|
219
|
+
|
|
220
|
+
### Example Use Cases
|
|
221
|
+
|
|
222
|
+
| Query | Tool |
|
|
223
|
+
|-------|------|
|
|
224
|
+
| *"What is the Volksschulgesetz?"* | `zhlaw_get_law` |
|
|
225
|
+
| *"Find laws about data protection"* | `zhlaw_search_laws` |
|
|
226
|
+
| *"Show me Art. 55 VSG"* | `zhlaw_get_article` |
|
|
227
|
+
| *"Which education laws mention Schulleitung?"* | `zhlaw_find_education_laws` |
|
|
228
|
+
| *"Find all articles about Elternrat in the VSG"* | `zhlaw_search_articles` |
|
|
229
|
+
| *"Is LS 412.100 still in force?"* | `zhlaw_get_law_metadata` |
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Architecture
|
|
234
|
+
|
|
235
|
+
```
|
|
236
|
+
┌─────────────────┐ ┌──────────────────────────────┐ ┌──────────────────────────┐
|
|
237
|
+
│ Claude / AI │────▶│ OpenLex MCP │────▶│ HuggingFace │
|
|
238
|
+
│ (MCP Host) │◀────│ (MCP Server) │◀────│ rcds/swiss_legislation │
|
|
239
|
+
└─────────────────┘ │ │ │ (974 ZH laws, cached) │
|
|
240
|
+
│ 8 Tools │ ├──────────────────────────┤
|
|
241
|
+
│ SQLite + FTS5 Cache │────▶│ zh.ch ZH-Lex │
|
|
242
|
+
│ Stdio | HTTP │◀────│ (live metadata + PDFs) │
|
|
243
|
+
│ │ ├──────────────────────────┤
|
|
244
|
+
│ No authentication required │ │ LexFind.ch │
|
|
245
|
+
└──────────────────────────────┘ │ (links only) │
|
|
246
|
+
└──────────────────────────┘
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Data Source Characteristics
|
|
250
|
+
|
|
251
|
+
| Source | Protocol | Coverage | Auth | License |
|
|
252
|
+
|--------|----------|----------|------|---------|
|
|
253
|
+
| HuggingFace `rcds/swiss_legislation` | Datasets API | 974 ZH laws (full text) | None | CC-BY-SA 4.0 |
|
|
254
|
+
| zh.ch ZH-Lex | HTTP/HTML | Current metadata, PDFs | None | Public |
|
|
255
|
+
| LexFind.ch | HTTP | Cross-cantonal links | None | Public |
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## Project Structure
|
|
260
|
+
|
|
261
|
+
```
|
|
262
|
+
openlex-mcp/
|
|
263
|
+
├── src/openlex_mcp/
|
|
264
|
+
│ ├── __init__.py # Package
|
|
265
|
+
│ ├── __main__.py # Entry point for python -m
|
|
266
|
+
│ ├── server.py # 8 MCP tool definitions (FastMCP)
|
|
267
|
+
│ ├── api_client.py # zh.ch HTTP client + metadata extraction
|
|
268
|
+
│ ├── data_cache.py # SQLite + FTS5 cache management
|
|
269
|
+
│ └── law_parser.py # Article extraction from law texts
|
|
270
|
+
├── tests/
|
|
271
|
+
│ └── test_server.py # Unit + integration tests
|
|
272
|
+
├── .github/workflows/ci.yml # GitHub Actions (Python 3.11/3.12/3.13)
|
|
273
|
+
├── pyproject.toml
|
|
274
|
+
├── claude_desktop_config.json # Example config for Claude Desktop
|
|
275
|
+
├── CHANGELOG.md
|
|
276
|
+
├── CONTRIBUTING.md
|
|
277
|
+
├── LICENSE
|
|
278
|
+
├── README.md # This file (English)
|
|
279
|
+
└── README.de.md # German version
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## Known Limitations
|
|
285
|
+
|
|
286
|
+
- **HuggingFace dataset:** The `html_content` field is unreliable (cross-contaminated between laws); the server uses `pdf_content` instead, which is correct but has PDF extraction artefacts (hyphenation, layout artefacts)
|
|
287
|
+
- **Article parser:** PDF text extraction sometimes merges article boundaries; complex nested articles may not parse perfectly
|
|
288
|
+
- **Initial load:** First start requires ~25s to download and index 974 laws from HuggingFace (~38 MB SQLite database)
|
|
289
|
+
- **zh.ch metadata:** No official API; metadata extraction relies on HTML patterns that may change
|
|
290
|
+
- **Offline mode:** Full-text search works offline after initial load; live metadata requires internet
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## Testing
|
|
295
|
+
|
|
296
|
+
```bash
|
|
297
|
+
# Unit tests (no API key required)
|
|
298
|
+
PYTHONPATH=src pytest tests/ -m "not live"
|
|
299
|
+
|
|
300
|
+
# Integration tests (live API calls)
|
|
301
|
+
pytest tests/ -m "live"
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## Changelog
|
|
307
|
+
|
|
308
|
+
See [CHANGELOG.md](CHANGELOG.md)
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## Contributing
|
|
313
|
+
|
|
314
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md)
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## License
|
|
319
|
+
|
|
320
|
+
MIT License — see [LICENSE](LICENSE)
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## Author
|
|
325
|
+
|
|
326
|
+
Hayal Oezkan · [malkreide](https://github.com/malkreide)
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## Credits & Related Projects
|
|
331
|
+
|
|
332
|
+
- **Data:** [rcds/swiss_legislation](https://huggingface.co/datasets/rcds/swiss_legislation) — HuggingFace dataset (CC-BY-SA 4.0)
|
|
333
|
+
- **ZH-Lex:** [zh.ch Gesetzessammlung](https://www.zh.ch/de/politik-staat/gesetze-beschluesse/gesetzessammlung.html) — Official Canton Zurich legal collection
|
|
334
|
+
- **LexFind:** [lexfind.ch](https://www.lexfind.ch/) — Cross-cantonal legislation database
|
|
335
|
+
- **Protocol:** [Model Context Protocol](https://modelcontextprotocol.io/) — Anthropic / Linux Foundation
|
|
336
|
+
- **Related:** [swiss-courts-mcp](https://github.com/malkreide/swiss-courts-mcp) — Law text + case law = complete legal research
|
|
337
|
+
- **Related:** [zurich-opendata-mcp](https://github.com/malkreide/zurich-opendata-mcp) — Law text + city council decisions = full context
|
|
338
|
+
- **Portfolio:** [Swiss Public Data MCP Portfolio](https://github.com/malkreide)
|