bag-epl-mcp 0.2.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.
@@ -0,0 +1,16 @@
1
+ .git
2
+ .github
3
+ .venv
4
+ __pycache__
5
+ *.py[cod]
6
+ *.egg-info
7
+ dist
8
+ build
9
+ .pytest_cache
10
+ .ruff_cache
11
+ htmlcov
12
+ .coverage
13
+ tests
14
+ docs
15
+ *.md
16
+ !README.md
@@ -0,0 +1,20 @@
1
+ # ARCH-012: automatisierte monatliche Update-Vorschlaege fuer Abhaengigkeiten.
2
+ version: 2
3
+ updates:
4
+ - package-ecosystem: "pip"
5
+ directory: "/"
6
+ schedule:
7
+ interval: "monthly"
8
+ open-pull-requests-limit: 5
9
+ commit-message:
10
+ prefix: "deps"
11
+ groups:
12
+ python-deps:
13
+ patterns: ["*"]
14
+
15
+ - package-ecosystem: "github-actions"
16
+ directory: "/"
17
+ schedule:
18
+ interval: "monthly"
19
+ commit-message:
20
+ prefix: "ci"
@@ -0,0 +1,33 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
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@v6
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,65 @@
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@v6
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: Snapshot tool-definition hashes (SEC-022)
27
+ # NB: write outside dist/ — the publish step runs twine over every file
28
+ # in the downloaded dist/ artifact and rejects non-distribution files.
29
+ run: |
30
+ pip install -e .
31
+ mkdir -p tool-hashes
32
+ PYTHONPATH=src python scripts/snapshot_tool_hashes.py tool-hashes/tool-hashes.json
33
+ cat tool-hashes/tool-hashes.json
34
+
35
+ - name: Upload build artifact
36
+ uses: actions/upload-artifact@v7
37
+ with:
38
+ name: dist
39
+ path: dist/
40
+
41
+ - name: Upload tool-definition hashes
42
+ uses: actions/upload-artifact@v7
43
+ with:
44
+ name: tool-hashes
45
+ path: tool-hashes/tool-hashes.json
46
+
47
+ publish:
48
+ name: Publish to PyPI
49
+ needs: build
50
+ runs-on: ubuntu-latest
51
+ environment:
52
+ name: pypi
53
+ url: https://pypi.org/project/bag-epl-mcp/
54
+ permissions:
55
+ id-token: write
56
+
57
+ steps:
58
+ - name: Download build artifact
59
+ uses: actions/download-artifact@v8
60
+ with:
61
+ name: dist
62
+ path: dist/
63
+
64
+ - name: Publish to PyPI
65
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,8 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ dist/
5
+ build/
6
+ .env
7
+ .venv
8
+ *.egg
@@ -0,0 +1,99 @@
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.2.0] - 2026-06-01
11
+
12
+ ### Added
13
+ - **Env-based transport selection** (`MCP_TRANSPORT`, `MCP_HOST`, `MCP_PORT`):
14
+ the Streamable HTTP transport for cloud deployment is now actually
15
+ implemented (previously only documented). Default remains `stdio`.
16
+ - CORS middleware on the HTTP transport exposing `Mcp-Session-Id` for browser
17
+ clients (claude.ai), with an explicit origin allow-list (`MCP_CORS_ORIGINS`).
18
+ - Tool annotations (`readOnlyHint`, `openWorldHint`) on all six tools.
19
+ - Egress allow-list guard (`ALLOWED_HOSTS` + `_assert_safe_url`): HTTPS-only,
20
+ host allow-list, and resolved-IP blocklist (SSRF protection) before any
21
+ outbound request.
22
+ - `docs/SECURITY.md` — threat model (Lethal-Trifecta assessment, no-auth/
23
+ session rationale, egress and host-binding policy).
24
+ - `[http]` optional dependency group (`uvicorn`, `starlette`).
25
+ - **Multi-stage `Dockerfile`** (slim base, non-root UID 10001, `HEALTHCHECK`)
26
+ and `render.yaml` Blueprint with health check and resource plan.
27
+ - `/healthz` HTTP endpoint for load-balancer probes.
28
+ - **Lifespan-managed pooled `httpx.AsyncClient`** (connection reuse / keep-alive)
29
+ instead of a fresh client per request.
30
+ - **Structured JSON logging** via `structlog`, written to **stderr** (stdout
31
+ stays reserved for the stdio JSON-RPC stream); full error detail is logged
32
+ server-side while the model only sees a sanitized message.
33
+ - `$PORT` fallback for `MCP_PORT` (PaaS/Render compatibility).
34
+ - **Structured response envelope** for tool JSON output: `source`, `provenance`
35
+ (incl. `license`), `match_type`, `count`, `results` (SDK-002, ARCH-003).
36
+ - **OGD-CH attribution** (CC BY 4.0) in every tool response — JSON `provenance`
37
+ block and Markdown source/licence footer (CH-004).
38
+ - `<use_case>` tags in all tool docstrings (ARCH-002).
39
+ - `protocolVersion` constant + update policy; surfaced via `epl_server_info`
40
+ (ARCH-012); `.github/dependabot.yml` for monthly pip / actions updates.
41
+ - `docs/ROADMAP.md` with phase gates and sign-offs (OPS-003); data-flow
42
+ architecture diagram in both READMEs (OPS-002).
43
+ - Optional OpenTelemetry tracing (`[otel]` extra, `MCP_OTEL_ENABLED`) — OBS-006.
44
+ - `scripts/snapshot_tool_hashes.py` + release-workflow step recording SHA-256
45
+ hashes of tool definitions (SEC-022).
46
+ - Tests split into `tests/test_unit.py` (mocked) and `tests/test_live.py`
47
+ (opt-in, one per tool) with a shared `conftest.py` (OPS-001).
48
+ - `ctx: Context` injection in all tools + per-tool-call bound structured logging
49
+ context (`tool`, `correlation_id`, `request_id`/`client_id`) — SDK-003 / OBS-003.
50
+ - **Structured tool output (SDK-002):** all 6 tools now declare typed Pydantic
51
+ output schemas and return `structuredContent` alongside the curated Markdown
52
+ (`content`) — a hybrid `CallToolResult`, so machine consumers get a validated
53
+ schema with no loss of the human-readable output.
54
+ - **OpenTelemetry on by default (OBS-006):** `MCP_OTEL_ENABLED` now defaults to
55
+ on; a silent no-op when the `[otel]` extra isn't installed (base installs and
56
+ stdout are unaffected). `TracerProvider` + OTLP exporter +
57
+ Starlette/httpx auto-instrumentation; set `MCP_OTEL_ENABLED=0` to disable.
58
+ - **DNS-pinned HTTP transport** (`_PinnedNetworkBackend`): the host is resolved
59
+ exactly once, the resolved IP is validated and the TCP connection pinned to it,
60
+ while TLS SNI/cert verification still use the hostname — eliminates the
61
+ resolve/connect TOCTOU (SEC-005).
62
+ - `deploy/haproxy.cfg`: reference sticky-session (Mcp-Session-Id stick-table +
63
+ TTL) config for the horizontal-scaling path (SCALE-002/003).
64
+
65
+ ### Changed
66
+ - Console entrypoint is now `bag_epl_mcp.server:main` (transport-aware).
67
+ - Configuration via a `pydantic-settings` `ServerSettings` object instead of
68
+ module-level transport globals.
69
+ - Errors now surface as MCP `isError` results (raised `ToolError`); the generic
70
+ error path no longer echoes raw exception messages to the model.
71
+ - Corrected README/README.de deployment instructions to the real
72
+ env-var-based mechanism and the `/mcp` endpoint.
73
+ - Input models now use Pydantic `strict=True` (the `format` field stays lenient
74
+ so clients may pass the string `"json"`/`"markdown"`) — SEC-018.
75
+
76
+ ### Security
77
+ - Addresses audit findings SCALE-001, ARCH-009, SDK-004, SEC-004/005/021,
78
+ SEC-016, SEC-019, SEC-009, OBS-001/002 (Phase 1); SEC-007, SCALE-004,
79
+ SCALE-006, SDK-001, OBS-003 (Phase 2); and SEC-018, SEC-022, ARCH-002,
80
+ ARCH-012, SDK-002, ARCH-003, CH-004, OBS-006, OPS-001/002/003 (Phase 3);
81
+ and SDK-003, OBS-003, SEC-005, SDK-002, OBS-006 (post-re-audit follow-up). See
82
+ `docs/audit/2026-06-01/` and `docs/audit/2026-06-01-reaudit/`.
83
+
84
+ ## [0.1.0] - 2026-04-13
85
+
86
+ ### Added
87
+ - Initial release with Phase 1 implementation (no authentication required)
88
+ - **SL module**: `epl_sl_suche` — search the Spezialitaetenliste
89
+ - **GGSL module**: `epl_ggsl_abfrage` — check congenital disorder coverage
90
+ - **MiGeL module**: `epl_migel_suche` — search medical devices
91
+ - **Transparency**: `epl_gesuchseingaenge` — pending SL admission requests
92
+ - **Legal context**: `epl_rechtskontext` — WZW criteria, KVG/KLV/IVG references
93
+ - **Server info**: `epl_server_info` — status and phase information
94
+ - 2 Resources: `epl://uebersicht`, `epl://rechtsrahmen`
95
+ - 2 Prompts: `epl_kassenpflicht_check`, `epl_schulgesundheit_recherche`
96
+ - Dual transport: stdio (Claude Desktop) + Streamable HTTP (cloud/Render.com)
97
+ - GitHub Actions CI (Python 3.11, 3.12, 3.13)
98
+ - Bilingual documentation (DE/EN)
99
+ - Unit and integration tests (mocked HTTP via respx)
@@ -0,0 +1,59 @@
1
+ # Contributing to bag-epl-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/bag-epl-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 (`tests/test_server.py`)
35
+ - Follow the existing FastMCP / Pydantic v2 patterns in `server.py`
36
+
37
+ ---
38
+
39
+ ## Data Sources
40
+
41
+ This server accesses Swiss BAG (Federal Office of Public Health) data — all without authentication:
42
+
43
+ | Source | Documentation |
44
+ |--------|--------------|
45
+ | Spezialitaetenliste (SL) | [sl.bag.admin.ch](https://sl.bag.admin.ch) |
46
+ | GGSL | [BAG GGSL Info](https://www.bag.admin.ch) |
47
+ | MiGeL | [BAG MiGeL Info](https://www.bag.admin.ch) |
48
+
49
+ ### Phase 2: FHIR API
50
+
51
+ When the BAG publishes its FHIR/IDMP API for the ePL, contributions updating the tools to use it are very welcome. The architecture is designed to make this upgrade minimal — see the `FHIR_BASE_URL` constant and the `_sl_website_suche` function in `server.py`.
52
+
53
+ When adding new data sources, follow the **No-Auth-First** principle: Phase 1 uses only open, authentication-free endpoints. Authenticated APIs are introduced in later phases with graceful degradation.
54
+
55
+ ---
56
+
57
+ ## License
58
+
59
+ By contributing, you agree that your contributions will be licensed under the [MIT License](LICENSE).
@@ -0,0 +1,38 @@
1
+ # ── Multi-Stage-Build (SCALE-004) ───────────────────────────────────────────────
2
+ # Stage 1: Abhaengigkeiten in ein venv installieren.
3
+ FROM python:3.12-slim AS builder
4
+
5
+ WORKDIR /app
6
+ ENV PIP_NO_CACHE_DIR=1 PIP_DISABLE_PIP_VERSION_CHECK=1
7
+
8
+ # Nur die fuer den Build noetigen Dateien kopieren (siehe .dockerignore).
9
+ COPY pyproject.toml README.md LICENSE ./
10
+ COPY src ./src
11
+
12
+ RUN python -m venv /opt/venv \
13
+ && /opt/venv/bin/pip install --upgrade pip \
14
+ && /opt/venv/bin/pip install ".[http]"
15
+
16
+ # ── Stage 2: schlankes Runtime-Image ────────────────────────────────────────────
17
+ FROM python:3.12-slim AS runtime
18
+
19
+ # SEC-007: Non-Root-User (UID >= 10000), keine Root-Rechte zur Laufzeit.
20
+ RUN groupadd -r -g 10001 app && useradd -r -g app -u 10001 -d /home/app -m app
21
+
22
+ COPY --from=builder /opt/venv /opt/venv
23
+
24
+ ENV PATH="/opt/venv/bin:$PATH" \
25
+ PYTHONUNBUFFERED=1 \
26
+ PYTHONDONTWRITEBYTECODE=1 \
27
+ MCP_TRANSPORT=streamable-http \
28
+ MCP_HOST=0.0.0.0 \
29
+ MCP_PORT=8000
30
+
31
+ USER app
32
+ EXPOSE 8000
33
+
34
+ # SCALE-004: HEALTHCHECK fuer Load-Balancer-Integration (nutzt /healthz).
35
+ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
36
+ CMD python -c "import os,urllib.request; urllib.request.urlopen('http://127.0.0.1:%s/healthz' % os.environ.get('MCP_PORT','8000'), timeout=3)" || exit 1
37
+
38
+ CMD ["python", "-m", "bag_epl_mcp.server"]
@@ -0,0 +1,81 @@
1
+ # Use Cases & Examples — bag-epl-mcp
2
+
3
+ Real-world queries by audience. Indicate per example whether an API key is required.
4
+
5
+ ## 🏫 Bildung & Schule
6
+ Lehrpersonen, Schulbehörden, Fachreferent:innen
7
+
8
+ ### Abklärung von Hilfsmitteln für den Schulalltag
9
+ «Zahlt die Krankenkasse das Hörgerät für einen Schüler oder müssen das die Eltern übernehmen?»
10
+
11
+ → epl_migel_suche(suchbegriff="Hoergeraet")
12
+ → epl_rechtskontext(frage="Zahlt die Grundversicherung Hörgeräte für Kinder?")
13
+ Warum nützlich: Hilft Lehrpersonen und Heilpädagog:innen schnell zu klären, welche Unterstützungsmöglichkeiten für Inklusionsmaterialien über die Grundversicherung bestehen. (Kein API-Key erforderlich)
14
+
15
+ ### Medikamentengabe bei chronischen Erkrankungen im Schullager
16
+ «Wird Ritalin (Methylphenidat) von der Grundversicherung bezahlt und welche Regeln gelten?»
17
+
18
+ → epl_sl_suche(suchbegriff="Methylphenidat")
19
+ → epl_rechtskontext(frage="WZW Kriterien für ADHS Medikamente")
20
+ Warum nützlich: Unterstützt Schulleitungen und Lagerleitende dabei, den gesetzlichen und finanziellen Rahmen von häufigen Dauermedikationen bei Schulkindern zu verstehen. (Kein API-Key erforderlich)
21
+
22
+ ## 👨👩👧 Eltern & Schulgemeinde
23
+ Elternräte, interessierte Erziehungsberechtigte
24
+
25
+ ### Übernahme bei Geburtsgebrechen
26
+ «Mein Kind hat Zystische Fibrose (Geburtsgebrechen 404). Werden die speziellen Medikamente von der IV bezahlt?»
27
+
28
+ → epl_ggsl_abfrage(geburtsgebrechen_nr="404")
29
+ → epl_rechtskontext(frage="Medikamente bei Geburtsgebrechen IVG")
30
+ Warum nützlich: Gibt betroffenen Eltern konkrete Anhaltspunkte, ob wichtige, dauerhafte Medikationen von der IV über die GGSL übernommen werden. (Kein API-Key erforderlich)
31
+
32
+ ### Kosten für Hilfsmittel
33
+ «Wird eine Gehhilfe oder ein Rollstuhl für mein Kind von der Krankenkasse übernommen?»
34
+
35
+ → epl_migel_suche(suchbegriff="Rollstuhl")
36
+ Warum nützlich: Erleichtert Eltern den Zugang zu Informationen, welche medizinischen Hilfsmittel in der MiGeL gelistet und somit kassenpflichtig sind. (Kein API-Key erforderlich)
37
+
38
+ ## 🗳️ Bevölkerung & öffentliches Interesse
39
+ Allgemeine Öffentlichkeit, politisch und gesellschaftlich Interessierte
40
+
41
+ ### Transparenz bei neuen Medikamenten
42
+ «Welche neuen Medikamente sind aktuell zur Aufnahme in die Spezialitätenliste beantragt?»
43
+
44
+ → epl_gesuchseingaenge()
45
+ Warum nützlich: Bietet der Öffentlichkeit und Medienschaffenden Transparenz darüber, welche neuen Therapien auf Zulassung zur Kassenpflicht warten. (Kein API-Key erforderlich)
46
+
47
+ ### Verständnis der Kassenpflicht (WZW)
48
+ «Nach welchen Kriterien entscheidet das BAG eigentlich, ob ein neues Medikament von allen Prämienzahlern bezahlt wird?»
49
+
50
+ → epl_rechtskontext(frage="Nach welchen Kriterien wird über die Aufnahme in die Spezialitätenliste entschieden?")
51
+ Warum nützlich: Fördert das demokratische Verständnis für gesundheitspolitische Entscheide und die Beurteilung nach Wirksamkeit, Zweckmässigkeit und Wirtschaftlichkeit. (Kein API-Key erforderlich)
52
+
53
+ ## 🤖 KI-Interessierte & Entwickler:innen
54
+ MCP-Enthusiast:innen, Forscher:innen, Prompt Engineers, öffentliche Verwaltung
55
+
56
+ ### Integration von Bundesrecht und Gesundheitsdaten
57
+ «Zeige mir die Medikamente in der Spezialitätenliste für Diabetes und gib mir direkt den genauen Fedlex-Link zum Krankenversicherungsgesetz (KVG) betreffend Leistungen.»
58
+
59
+ → epl_sl_suche(suchbegriff="Insulin")
60
+ → fedlex_suche(suchbegriff="Krankenversicherungsgesetz KVG Leistungen") (via https://github.com/malkreide/fedlex-mcp)
61
+ Warum nützlich: Demonstriert eindrücklich, wie fachspezifische Gesundheitsdaten automatisiert mit der primären Rechtsquelle (Fedlex) aus dem Swiss Public Data Portfolio verknüpft werden können. (Kein API-Key erforderlich)
62
+
63
+ ### Automatisierter Kassenpflicht-Check
64
+ «Erstelle mir eine Übersicht der rechtlichen Grundlagen zur MiGeL und suche nach Inkontinenzhilfen.»
65
+
66
+ → epl_rechtskontext(frage="Rechtliche Grundlagen MiGeL")
67
+ → epl_migel_suche(suchbegriff="Inkontinenz")
68
+ Warum nützlich: Erlaubt Entwickler:innen den Bau von automatisierten Systemen für Patientenanfragen, um Kassenpflicht und Gesetze direkt abzugleichen. (Kein API-Key erforderlich)
69
+
70
+ ---
71
+
72
+ ## 🔧 Technische Referenz: Tool-Auswahl nach Anwendungsfall
73
+
74
+ | Ich möchte… | Tool(s) | Auth nötig? |
75
+ |---|---|---|
76
+ | prüfen, ob ein Medikament kassenpflichtig ist | `epl_sl_suche` | Nein |
77
+ | schauen, ob die IV Medikamente für ein Geburtsgebrechen zahlt | `epl_ggsl_abfrage` | Nein |
78
+ | die Kassenpflicht für Hilfsmittel (z.B. Rollstuhl) klären | `epl_migel_suche` | Nein |
79
+ | wissen, welche Medikamente auf die SL-Zulassung warten | `epl_gesuchseingaenge` | Nein |
80
+ | die rechtlichen Kriterien (z.B. WZW, KVG) verstehen | `epl_rechtskontext` | Nein |
81
+ | den Server-Status und geplante ePL-Phasen abfragen | `epl_server_info` | Nein |
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 malkreide
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.