lobbywatch-mcp 0.3.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.
Files changed (70) hide show
  1. lobbywatch_mcp-0.3.0/.dockerignore +35 -0
  2. lobbywatch_mcp-0.3.0/.github/workflows/ci.yml +36 -0
  3. lobbywatch_mcp-0.3.0/.github/workflows/publish.yml +86 -0
  4. lobbywatch_mcp-0.3.0/.gitignore +36 -0
  5. lobbywatch_mcp-0.3.0/CHANGELOG.md +170 -0
  6. lobbywatch_mcp-0.3.0/CONTRIBUTING.md +99 -0
  7. lobbywatch_mcp-0.3.0/Dockerfile +73 -0
  8. lobbywatch_mcp-0.3.0/EXAMPLES.md +71 -0
  9. lobbywatch_mcp-0.3.0/LICENSE +34 -0
  10. lobbywatch_mcp-0.3.0/PKG-INFO +270 -0
  11. lobbywatch_mcp-0.3.0/README.de.md +213 -0
  12. lobbywatch_mcp-0.3.0/README.md +227 -0
  13. lobbywatch_mcp-0.3.0/assets/README.md +6 -0
  14. lobbywatch_mcp-0.3.0/audits/2026-05-09T115855-Z-lobbywatch-mcp/applicability.json +342 -0
  15. lobbywatch_mcp-0.3.0/audits/2026-05-09T115855-Z-lobbywatch-mcp/applicability.txt +55 -0
  16. lobbywatch_mcp-0.3.0/audits/2026-05-09T115855-Z-lobbywatch-mcp/audit-meta.json +13 -0
  17. lobbywatch_mcp-0.3.0/audits/2026-05-09T115855-Z-lobbywatch-mcp/audit-report.md +1094 -0
  18. lobbywatch_mcp-0.3.0/audits/2026-05-09T115855-Z-lobbywatch-mcp/checklist.txt +47 -0
  19. lobbywatch_mcp-0.3.0/audits/2026-05-09T115855-Z-lobbywatch-mcp/findings/ARCH-002-use-case-tags.md +35 -0
  20. lobbywatch_mcp-0.3.0/audits/2026-05-09T115855-Z-lobbywatch-mcp/findings/ARCH-003-not-found-suggestions.md +33 -0
  21. lobbywatch_mcp-0.3.0/audits/2026-05-09T115855-Z-lobbywatch-mcp/findings/ARCH-008-resources-and-prompts.md +33 -0
  22. lobbywatch_mcp-0.3.0/audits/2026-05-09T115855-Z-lobbywatch-mcp/findings/ARCH-009-tool-annotations-missing.md +36 -0
  23. lobbywatch_mcp-0.3.0/audits/2026-05-09T115855-Z-lobbywatch-mcp/findings/ARCH-012-protocol-version-pinning.md +32 -0
  24. lobbywatch_mcp-0.3.0/audits/2026-05-09T115855-Z-lobbywatch-mcp/findings/OBS-001-protocol-vs-execution-errors.md +36 -0
  25. lobbywatch_mcp-0.3.0/audits/2026-05-09T115855-Z-lobbywatch-mcp/findings/OBS-002-mask-error-details.md +35 -0
  26. lobbywatch_mcp-0.3.0/audits/2026-05-09T115855-Z-lobbywatch-mcp/findings/OBS-003-structured-logging.md +32 -0
  27. lobbywatch_mcp-0.3.0/audits/2026-05-09T115855-Z-lobbywatch-mcp/findings/OBS-006-opentelemetry-tracing.md +32 -0
  28. lobbywatch_mcp-0.3.0/audits/2026-05-09T115855-Z-lobbywatch-mcp/findings/OPS-002-doku-standard-ascii-diagram-and-broken-ci-badge.md +34 -0
  29. lobbywatch_mcp-0.3.0/audits/2026-05-09T115855-Z-lobbywatch-mcp/findings/SCALE-002-stateful-lb-undocumented.md +33 -0
  30. lobbywatch_mcp-0.3.0/audits/2026-05-09T115855-Z-lobbywatch-mcp/findings/SCALE-003-haproxy-stick-table-missing.md +32 -0
  31. lobbywatch_mcp-0.3.0/audits/2026-05-09T115855-Z-lobbywatch-mcp/findings/SCALE-004-containerization.md +32 -0
  32. lobbywatch_mcp-0.3.0/audits/2026-05-09T115855-Z-lobbywatch-mcp/findings/SCALE-006-resource-limits.md +32 -0
  33. lobbywatch_mcp-0.3.0/audits/2026-05-09T115855-Z-lobbywatch-mcp/findings/SDK-001-lifespan-asynccontextmanager-missing.md +44 -0
  34. lobbywatch_mcp-0.3.0/audits/2026-05-09T115855-Z-lobbywatch-mcp/findings/SDK-002-pydantic-typed-tool-returns.md +32 -0
  35. lobbywatch_mcp-0.3.0/audits/2026-05-09T115855-Z-lobbywatch-mcp/findings/SDK-003-context-injection.md +34 -0
  36. lobbywatch_mcp-0.3.0/audits/2026-05-09T115855-Z-lobbywatch-mcp/findings/SDK-004-cors-mcp-session-id.md +34 -0
  37. lobbywatch_mcp-0.3.0/audits/2026-05-09T115855-Z-lobbywatch-mcp/findings/SEC-004-ssrf-search-path.md +49 -0
  38. lobbywatch_mcp-0.3.0/audits/2026-05-09T115855-Z-lobbywatch-mcp/findings/SEC-005-dns-rebinding-pinning.md +32 -0
  39. lobbywatch_mcp-0.3.0/audits/2026-05-09T115855-Z-lobbywatch-mcp/findings/SEC-007-container-sandboxing.md +32 -0
  40. lobbywatch_mcp-0.3.0/audits/2026-05-09T115855-Z-lobbywatch-mcp/findings/SEC-016-zero-binding-default.md +44 -0
  41. lobbywatch_mcp-0.3.0/audits/2026-05-09T115855-Z-lobbywatch-mcp/findings/SEC-018-input-validation-pydantic-strict.md +34 -0
  42. lobbywatch_mcp-0.3.0/audits/2026-05-09T115855-Z-lobbywatch-mcp/findings/SEC-021-egress-allow-list.md +34 -0
  43. lobbywatch_mcp-0.3.0/audits/2026-05-09T115855-Z-lobbywatch-mcp/findings/SEC-022-namespace-prefix-rugpull.md +33 -0
  44. lobbywatch_mcp-0.3.0/audits/2026-05-09T115855-Z-lobbywatch-mcp/profile.json +26 -0
  45. lobbywatch_mcp-0.3.0/audits/2026-05-09T115855-Z-lobbywatch-mcp/summary.json +278 -0
  46. lobbywatch_mcp-0.3.0/audits/2026-05-09T115855-Z-lobbywatch-mcp/verification-results.json +516 -0
  47. lobbywatch_mcp-0.3.0/audits/2026-05-09T133506-Z-lobbywatch-mcp/applicability.json +342 -0
  48. lobbywatch_mcp-0.3.0/audits/2026-05-09T133506-Z-lobbywatch-mcp/audit-meta.json +13 -0
  49. lobbywatch_mcp-0.3.0/audits/2026-05-09T133506-Z-lobbywatch-mcp/audit-report.md +131 -0
  50. lobbywatch_mcp-0.3.0/audits/2026-05-09T133506-Z-lobbywatch-mcp/findings/OPS-002-ascii-diagram-missing.md +50 -0
  51. lobbywatch_mcp-0.3.0/audits/2026-05-09T133506-Z-lobbywatch-mcp/profile.json +26 -0
  52. lobbywatch_mcp-0.3.0/audits/2026-05-09T133506-Z-lobbywatch-mcp/summary.json +100 -0
  53. lobbywatch_mcp-0.3.0/audits/2026-05-09T133506-Z-lobbywatch-mcp/verification-results.json +434 -0
  54. lobbywatch_mcp-0.3.0/claude_desktop_config.json +15 -0
  55. lobbywatch_mcp-0.3.0/deploy/docker-compose.example.yml +60 -0
  56. lobbywatch_mcp-0.3.0/docs/deployment.md +136 -0
  57. lobbywatch_mcp-0.3.0/pyproject.toml +93 -0
  58. lobbywatch_mcp-0.3.0/src/lobbywatch_mcp/__init__.py +13 -0
  59. lobbywatch_mcp-0.3.0/src/lobbywatch_mcp/__main__.py +86 -0
  60. lobbywatch_mcp-0.3.0/src/lobbywatch_mcp/_observability.py +214 -0
  61. lobbywatch_mcp-0.3.0/src/lobbywatch_mcp/client.py +332 -0
  62. lobbywatch_mcp-0.3.0/src/lobbywatch_mcp/config.py +45 -0
  63. lobbywatch_mcp-0.3.0/src/lobbywatch_mcp/models.py +166 -0
  64. lobbywatch_mcp-0.3.0/src/lobbywatch_mcp/server.py +694 -0
  65. lobbywatch_mcp-0.3.0/tests/__init__.py +0 -0
  66. lobbywatch_mcp-0.3.0/tests/conftest.py +121 -0
  67. lobbywatch_mcp-0.3.0/tests/test_client.py +119 -0
  68. lobbywatch_mcp-0.3.0/tests/test_live.py +40 -0
  69. lobbywatch_mcp-0.3.0/tests/test_observability.py +112 -0
  70. lobbywatch_mcp-0.3.0/tests/test_server.py +193 -0
@@ -0,0 +1,35 @@
1
+ # Build context hygiene — keep image small and leak-free.
2
+ .git
3
+ .github
4
+ .venv
5
+ venv
6
+ env
7
+ __pycache__
8
+ *.py[cod]
9
+ *.egg-info
10
+ build
11
+ dist
12
+ .pytest_cache
13
+ .ruff_cache
14
+ .mypy_cache
15
+ .coverage
16
+ htmlcov
17
+
18
+ # Documentation that doesn't belong in the runtime image.
19
+ audits
20
+ docs
21
+ EXAMPLES.md
22
+ CONTRIBUTING.md
23
+ CHANGELOG.md
24
+ README.de.md
25
+
26
+ # Tests are not needed at runtime.
27
+ tests
28
+
29
+ # Local secrets / IDE state.
30
+ .env
31
+ .env.*
32
+ .idea
33
+ .vscode
34
+ *.swp
35
+ .DS_Store
@@ -0,0 +1,36 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ python-version: ["3.11", "3.12", "3.13"]
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Set up Python ${{ matrix.python-version }}
20
+ uses: actions/setup-python@v5
21
+ with:
22
+ python-version: ${{ matrix.python-version }}
23
+ cache: pip
24
+
25
+ - name: Install
26
+ run: |
27
+ python -m pip install --upgrade pip
28
+ pip install -e ".[dev]"
29
+
30
+ - name: Lint (ruff)
31
+ run: |
32
+ ruff check .
33
+ ruff format --check .
34
+
35
+ - name: Test (offline only)
36
+ run: pytest -m "not live" -q
@@ -0,0 +1,86 @@
1
+ name: Publish to PyPI
2
+
3
+ # Tag-triggered release. Builds wheel + sdist and uploads via PyPI's
4
+ # Trusted Publisher (OIDC) — no PYPI_API_TOKEN secret required.
5
+ #
6
+ # Setup (one-time, on PyPI):
7
+ # 1. Create the project on PyPI (or skip — PyPI auto-creates on first
8
+ # successful trusted publish).
9
+ # 2. PyPI → Account → Publishing → Add pending publisher
10
+ # Owner: malkreide
11
+ # Repo: lobbywatch-mcp
12
+ # Workflow: publish.yml
13
+ # Environment: pypi (must match the `environment:` below)
14
+ # 3. Push a tag matching v*.*.* — this workflow takes it from there.
15
+
16
+ on:
17
+ push:
18
+ tags:
19
+ - "v*.*.*"
20
+
21
+ permissions:
22
+ contents: read
23
+
24
+ jobs:
25
+ build:
26
+ name: Build distribution
27
+ runs-on: ubuntu-latest
28
+
29
+ steps:
30
+ - uses: actions/checkout@v4
31
+
32
+ - name: Set up Python 3.13
33
+ uses: actions/setup-python@v5
34
+ with:
35
+ python-version: "3.13"
36
+
37
+ - name: Verify tag matches pyproject.toml version
38
+ run: |
39
+ tag="${GITHUB_REF##*/}" # e.g. v0.3.0
40
+ tag_version="${tag#v}" # 0.3.0
41
+ manifest_version=$(python -c "import tomllib,sys; print(tomllib.loads(open('pyproject.toml','rb').read().decode())['project']['version'])")
42
+ if [ "$tag_version" != "$manifest_version" ]; then
43
+ echo "::error::Tag $tag (=> $tag_version) does not match pyproject.toml version $manifest_version"
44
+ exit 1
45
+ fi
46
+ echo "Tag and manifest agree on version $manifest_version"
47
+
48
+ - name: Install build tooling
49
+ run: |
50
+ python -m pip install --upgrade pip
51
+ pip install build
52
+
53
+ - name: Build wheel + sdist
54
+ run: python -m build
55
+
56
+ - name: Upload artefacts
57
+ uses: actions/upload-artifact@v4
58
+ with:
59
+ name: dist
60
+ path: dist/
61
+
62
+ publish:
63
+ name: Publish to PyPI
64
+ needs: build
65
+ runs-on: ubuntu-latest
66
+
67
+ # OIDC trusted-publisher requirements.
68
+ environment:
69
+ name: pypi
70
+ url: https://pypi.org/p/lobbywatch-mcp
71
+ permissions:
72
+ id-token: write # required for trusted publishing
73
+
74
+ steps:
75
+ - name: Download build artefacts
76
+ uses: actions/download-artifact@v4
77
+ with:
78
+ name: dist
79
+ path: dist/
80
+
81
+ - name: Publish via Trusted Publisher
82
+ uses: pypa/gh-action-pypi-publish@release/v1
83
+ with:
84
+ # No `password:` — OIDC handles auth.
85
+ # No `repository-url:` — defaults to upload.pypi.org.
86
+ print-hash: true
@@ -0,0 +1,36 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # Distribution / packaging
7
+ build/
8
+ dist/
9
+ *.egg-info/
10
+ *.egg
11
+
12
+ # Virtual environments
13
+ .venv/
14
+ venv/
15
+ env/
16
+
17
+ # Caches
18
+ .pytest_cache/
19
+ .ruff_cache/
20
+ .mypy_cache/
21
+ .coverage
22
+ htmlcov/
23
+
24
+ # IDE
25
+ .idea/
26
+ .vscode/
27
+ *.swp
28
+
29
+ # Local secrets
30
+ .env
31
+ .env.local
32
+ .env.*.local
33
+
34
+ # OS
35
+ .DS_Store
36
+ Thumbs.db
@@ -0,0 +1,170 @@
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
+ ### Added
11
+ - `.github/workflows/publish.yml` — tag-triggered PyPI release via
12
+ Trusted Publisher (OIDC, no API token in repo secrets). Verifies that
13
+ the pushed tag matches `pyproject.toml`'s `version` before publishing.
14
+ One-time setup on PyPI: register `malkreide/lobbywatch-mcp` ·
15
+ `publish.yml` · environment `pypi` as a pending publisher.
16
+ - ASCII architecture diagram in README.md / README.de.md (audit OPS-002
17
+ closure — was the single remaining cosmetic finding from the
18
+ 2026-05-09 re-audit). Visualises the dump-first / dataIF-fallback
19
+ split plus the SSRF-guarded outbound HTTP path.
20
+
21
+ ## [0.3.0] - 2026-05-09
22
+
23
+ Audit-driven hardening release. Closes 24 of 25 findings from the
24
+ initial mcp-audit-skill v1.0.0 run; the re-audit
25
+ (`audits/2026-05-09T133506-Z-lobbywatch-mcp/`) reports
26
+ `production_ready: true` (41 pass, 0 fail, 1 cosmetic partial).
27
+
28
+ ### Added
29
+ - Structured JSON logging via structlog (audit OBS-003). Opt-in with
30
+ `LOBBYWATCH_MCP_LOG_FORMAT=json` — default stays `text` to preserve
31
+ the quiet stdio experience. Each tool invocation generates a
32
+ 16-char correlation id that's bound into the structlog context vars
33
+ and surfaces in every JSON line as `correlation_id`.
34
+ - OpenTelemetry distributed tracing (audit OBS-006). Opt-in with
35
+ `LOBBYWATCH_MCP_OTEL_ENABLED=1`; OTel is an optional dep installed
36
+ via `pip install 'lobbywatch-mcp[obs]'`. Wraps each tool call in a
37
+ `tool.<name>` span and auto-instruments the httpx client. The
38
+ `_observability.observed_tool` async context manager keeps OBS-003
39
+ + OBS-006 in sync — every tool body is wrapped exactly once.
40
+ - Lifespan startup now logs the active MCP `protocolVersion` (audit
41
+ ARCH-012 closure: SDK upper-bound was already pinned in 0.2.0; this
42
+ closes the remaining "log it for operators" gap).
43
+ - New optional dependency group `obs` covering
44
+ `opentelemetry-api`, `opentelemetry-sdk`,
45
+ `opentelemetry-exporter-otlp-proto-http`,
46
+ `opentelemetry-instrumentation-httpx`. Without it, tracing config
47
+ warns and no-ops; logging stays available either way.
48
+ - Multi-stage `Dockerfile` and `.dockerignore` (audit SCALE-004). Runtime
49
+ image runs as `uid 1000`, read-only-rootfs compatible, no build
50
+ toolchain in the final stage (audit SEC-007).
51
+ - `deploy/docker-compose.example.yml` with hardening defaults:
52
+ `read_only`, `cap_drop: [ALL]`, `no-new-privileges`, tmpfs cache,
53
+ loopback-only port bind, healthcheck, and `deploy.resources` limits
54
+ (256 MiB / 0.1 CPU request, 512 MiB / 0.5 CPU limit) — audit SCALE-006.
55
+ - `docs/deployment.md`: trust-model recap, container build/run commands,
56
+ resource-sizing rationale, HAProxy stick-table example for sticky LBs
57
+ (audit SCALE-002, SCALE-003), and a Kubernetes egress `NetworkPolicy`
58
+ template (audit SEC-021).
59
+ - SSRF / DNS-rebinding guard in the httpx client: an event hook re-resolves
60
+ every outbound host and refuses connections to RFC1918 / link-local /
61
+ loopback / cloud-metadata addresses (audit SEC-005). Hardcoded URL
62
+ allow-list + `follow_redirects=False` + this guard provide
63
+ defence-in-depth.
64
+ - Optional CORS support for HTTP/SSE deployments via the
65
+ `LOBBYWATCH_MCP_CORS_ORIGINS` env var (comma-separated origin list).
66
+ When set, the FastMCP ASGI app is wrapped with a Starlette
67
+ `CORSMiddleware` that exposes `Mcp-Session-Id` to browser clients
68
+ (audit SDK-004). Default (empty) emits no CORS headers.
69
+ - `lobbywatch_refresh_dump` now accepts a `Context` parameter and emits
70
+ `ctx.info` start/end notifications around the dump download (audit
71
+ SDK-003), giving long-running clients useful progress feedback.
72
+ - Fuzzy-match suggestions on missed lookups (audit ARCH-003).
73
+ `lobbywatch_get_parlamentarier` and `lobbywatch_list_interessenbindungen`
74
+ return up to three near-miss candidates (rapidfuzz WRatio in
75
+ [50, 70)) in a new `suggestions` field, so the LLM can offer
76
+ "did you mean…?" instead of treating the empty result as truth. New
77
+ `LobbywatchClient.find_candidates()` powers this.
78
+ - MCP Resources and Prompts (audit ARCH-008):
79
+ - Resource `lobbywatch://attribution` (text/plain) — the CC BY-SA 4.0
80
+ licence string for clients that want to display it standalone.
81
+ - Prompt `lobbywatch_anchor_demo` — parameterised by `branche`, scaffolds
82
+ the canonical Schulamt / KI-Fachgruppe demo query.
83
+ - Prompt `lobbywatch_top_lobbyists_by_party` — parameterised by `partei`,
84
+ surfaces the top-10 ranking with transparency metadata.
85
+
86
+ ### Changed
87
+ - Tool-boundary error handling: upstream `RuntimeError` /
88
+ `httpx.HTTPError` are explicitly converted to `McpError` with code
89
+ `INTERNAL_ERROR` (audit OBS-001). Previously these fell through to
90
+ FastMCP's auto-coercion — same wire effect, but the categorisation is
91
+ now intentional and operators get a structured `logger.error` for the
92
+ underlying exception.
93
+ - HTTP/SSE transport now runs through `uvicorn.run` directly on
94
+ `mcp.streamable_http_app()` / `mcp.sse_app()` so middleware can be
95
+ attached. `stdio` transport is unchanged.
96
+ - All eight tools now declare typed Pydantic return models instead of
97
+ `dict[str, Any]` (audit SDK-002). FastMCP synthesises a real JSON
98
+ schema for each tool's output, giving downstream clients (Inspector,
99
+ custom UIs, schema-aware LLMs) field-level type information instead
100
+ of opaque `additionalProperties: true`. Wire format is unchanged.
101
+ - Tool docstrings now carry `Use cases:` blocks (audit ARCH-002) with
102
+ three concrete example queries each, sharpening LLM tool selection.
103
+ - `USER_AGENT` bumped to `lobbywatch-mcp/0.3.0`.
104
+
105
+ ### Audit verification
106
+
107
+ - **Production-ready:** ✅ yes
108
+ - **Audit run-id:** `2026-05-09T133506-Z-lobbywatch-mcp`
109
+ - **Skill version:** `1.0.0`
110
+ - **Catalog hash:** `091f446b27965044…`
111
+ - **Check results:** 41 pass · 0 fail · 1 partial · 2 todo
112
+ - **Remaining finding:** `OPS-002` — README ASCII architecture diagram
113
+ (cosmetic, ~10 min effort).
114
+
115
+ ## [0.2.0] - 2026-05-09
116
+
117
+ ### Changed (Breaking)
118
+ - **Tool names now carry a `lobbywatch_` namespace prefix** (audit SEC-022).
119
+ All eight tools were renamed:
120
+ `get_parlamentarier` → `lobbywatch_get_parlamentarier`,
121
+ `list_interessenbindungen` → `lobbywatch_list_interessenbindungen`,
122
+ `search_parlamentarier_nach_branche` → `lobbywatch_search_parlamentarier_nach_branche`,
123
+ `get_lobbygruppe` → `lobbywatch_get_lobbygruppe`,
124
+ `get_ranking` → `lobbywatch_get_ranking`,
125
+ `get_transparenzquote` → `lobbywatch_get_transparenzquote`,
126
+ `refresh_dump` → `lobbywatch_refresh_dump`,
127
+ `dump_status` → `lobbywatch_dump_status`.
128
+ Existing clients referencing the old names must be updated.
129
+
130
+ ### Added
131
+ - FastMCP `lifespan` context manager owns the `LobbywatchClient` lifecycle
132
+ (audit SDK-001). The shared `httpx.AsyncClient` is now closed cleanly on
133
+ server shutdown; previously it leaked at process exit.
134
+ - Input validation at tool boundaries (audit SEC-018):
135
+ `name_or_id` and `branche_query` bound to 1–200 chars; `kommission` /
136
+ `partei` bound to ≤80 chars; `limit` bounded to 1–200 (search) /
137
+ 1–100 (ranking); `kriterium` is now a `Literal` enum so invalid values
138
+ are rejected at the schema layer instead of via runtime `ValueError`.
139
+
140
+ ### Changed
141
+ - `mcp[cli]` dependency is now bounded `<2.0.0` (audit ARCH-012).
142
+ - `USER_AGENT` bumped to `lobbywatch-mcp/0.2.0`.
143
+
144
+ ## [0.1.0] - 2026-04-21
145
+
146
+ ### Added
147
+ - Initial scaffold for `lobbywatch-mcp`, part of the Swiss Public Data MCP Portfolio.
148
+ - Hybrid data access: dump-first (weekly Lobbywatch JSON export) with live
149
+ `dataIF` fallback for lobby groups.
150
+ - Phase 1 tools:
151
+ - `get_parlamentarier` — profile lookup with fuzzy name match
152
+ - `list_interessenbindungen` — conflict-of-interest records per MP
153
+ - `search_parlamentarier_nach_branche` — branche + commission filter
154
+ - `get_lobbygruppe` — live lobby group fetch via dataIF
155
+ - `get_ranking` — top-N by criterion, commission/party filters
156
+ - `get_transparenzquote` — distribution of verguetungstransparenz labels
157
+ - `refresh_dump`, `dump_status` — cache control
158
+ - Retry + exponential backoff (2s/4s/8s) for dump downloads — tolerant of upstream HTTP 503 blips observed live.
159
+ - Dual transport: `stdio` (default) and `streamable-http` / `sse`.
160
+ - Pydantic v2 response envelopes carrying CC BY-SA 4.0 attribution.
161
+ - CI matrix (Python 3.11–3.13), ruff lint/format, respx-mocked unit tests.
162
+ - Tag-triggered PyPI publish workflow via OIDC Trusted Publisher.
163
+ - Live test suite (`@pytest.mark.live`) excluded from CI.
164
+
165
+ ### Known limitations
166
+ - The upstream `/table/parlamentarier/...` dataIF endpoint returns empty
167
+ responses at release time; all parliamentarian-facing tools therefore use
168
+ the weekly dump. Lobby group lookups go through the live API.
169
+ - `zutrittsberechtigungen` are present in the data model but empty in the
170
+ essential dump used here — surfaced as a stub for future expansion.
@@ -0,0 +1,99 @@
1
+ # Contributing / Mitwirken
2
+
3
+ [🇬🇧 English](#english) · [🇩🇪 Deutsch](#deutsch)
4
+
5
+ ---
6
+
7
+ ## English
8
+
9
+ Thanks for your interest in improving `lobbywatch-mcp`. This project is part of the [Swiss Public Data MCP Portfolio](https://github.com/malkreide). Contributions of any size — bug reports, documentation fixes, new tools, performance work — are welcome.
10
+
11
+ ### Development setup
12
+
13
+ ```bash
14
+ git clone https://github.com/malkreide/lobbywatch-mcp.git
15
+ cd lobbywatch-mcp
16
+ python -m venv .venv
17
+ source .venv/bin/activate
18
+ pip install -e ".[dev]"
19
+ ```
20
+
21
+ ### Running tests
22
+
23
+ Offline only (fast, what CI runs):
24
+
25
+ ```bash
26
+ pytest -m "not live"
27
+ ```
28
+
29
+ Including live API + dump probes:
30
+
31
+ ```bash
32
+ pytest -m live
33
+ ```
34
+
35
+ ### Linting
36
+
37
+ ```bash
38
+ ruff check .
39
+ ruff format .
40
+ ```
41
+
42
+ ### Design principles
43
+
44
+ 1. **No-Auth-First.** All Phase 1 tools must work without credentials.
45
+ 2. **Live validation before coding.** If you add a tool that hits an upstream endpoint, verify the endpoint with a live probe first and document what you found in the PR.
46
+ 3. **Attribution is non-negotiable.** Every response goes through a Pydantic envelope that carries the CC BY-SA 4.0 credit. Don't route around it.
47
+ 4. **Portfolio synergy.** If a feature fits better in another server (`parlament-mcp`, `register-mcp`, …), suggest that instead of growing this one.
48
+
49
+ ### Pull requests
50
+
51
+ Follow Conventional Commits (`feat:`, `fix:`, `docs:`, `refactor:`, `test:`, `chore:`) and update `CHANGELOG.md` under `[Unreleased]`.
52
+
53
+ ---
54
+
55
+ ## Deutsch
56
+
57
+ Danke für das Interesse an `lobbywatch-mcp`. Das Projekt ist Teil des [Swiss Public Data MCP Portfolios](https://github.com/malkreide). Beiträge jeder Grössenordnung — Bug-Reports, Dokumentation, neue Tools, Performance — sind willkommen.
58
+
59
+ ### Entwicklungs-Setup
60
+
61
+ ```bash
62
+ git clone https://github.com/malkreide/lobbywatch-mcp.git
63
+ cd lobbywatch-mcp
64
+ python -m venv .venv
65
+ source .venv/bin/activate
66
+ pip install -e ".[dev]"
67
+ ```
68
+
69
+ ### Tests ausführen
70
+
71
+ Nur offline (schnell, entspricht CI):
72
+
73
+ ```bash
74
+ pytest -m "not live"
75
+ ```
76
+
77
+ Inklusive Live-Probes gegen API und Dump:
78
+
79
+ ```bash
80
+ pytest -m live
81
+ ```
82
+
83
+ ### Linting
84
+
85
+ ```bash
86
+ ruff check .
87
+ ruff format .
88
+ ```
89
+
90
+ ### Design-Prinzipien
91
+
92
+ 1. **No-Auth-First.** Alle Phase-1-Tools müssen ohne Credentials funktionieren.
93
+ 2. **Live-Validierung vor dem Coden.** Wer ein Tool gegen einen Upstream-Endpoint baut, probiert den Endpoint zuerst live aus und dokumentiert die Erkenntnisse im PR.
94
+ 3. **Namensnennung ist nicht verhandelbar.** Jede Antwort läuft durch ein Pydantic-Envelope mit der CC-BY-SA-4.0-Attribution. Nicht umgehen.
95
+ 4. **Portfolio-Synergie.** Wenn ein Feature besser in einen anderen Server passt (`parlament-mcp`, `register-mcp`, …), besser dort einbringen als diesen Server aufblähen.
96
+
97
+ ### Pull Requests
98
+
99
+ Conventional Commits verwenden (`feat:`, `fix:`, `docs:`, `refactor:`, `test:`, `chore:`) und `CHANGELOG.md` unter `[Unreleased]` nachführen.
@@ -0,0 +1,73 @@
1
+ # syntax=docker/dockerfile:1.7
2
+ #
3
+ # Multi-stage build for lobbywatch-mcp (audit SCALE-004, SEC-007, SCALE-006).
4
+ # Hardening goals:
5
+ # - Non-root runtime user (uid/gid 1000)
6
+ # - read-only rootfs compatible (cache lives in /home/lobbywatch/.cache,
7
+ # mount as tmpfs in compose/k8s)
8
+ # - No build toolchain or apt cache in the runtime image
9
+ # - Image small enough for routine pulls (~80 MB on python:slim)
10
+
11
+ ARG PYTHON_VERSION=3.13
12
+
13
+ # ---------------------------------------------------------------------------
14
+ # Builder: install the project into an isolated venv we can copy out.
15
+ # ---------------------------------------------------------------------------
16
+ FROM python:${PYTHON_VERSION}-slim AS builder
17
+
18
+ ENV PIP_DISABLE_PIP_VERSION_CHECK=1 \
19
+ PIP_NO_CACHE_DIR=1 \
20
+ PYTHONDONTWRITEBYTECODE=1 \
21
+ PYTHONUNBUFFERED=1
22
+
23
+ WORKDIR /build
24
+
25
+ # Install into a venv so we can COPY just /opt/venv to the runtime image.
26
+ RUN python -m venv /opt/venv
27
+ ENV PATH="/opt/venv/bin:$PATH"
28
+
29
+ # Copy only what hatchling needs to build the wheel.
30
+ COPY pyproject.toml README.md LICENSE ./
31
+ COPY src ./src
32
+
33
+ RUN pip install --no-cache-dir .
34
+
35
+ # ---------------------------------------------------------------------------
36
+ # Runtime: minimal image, non-root user, no build tools.
37
+ # ---------------------------------------------------------------------------
38
+ FROM python:${PYTHON_VERSION}-slim AS runtime
39
+
40
+ LABEL org.opencontainers.image.source="https://github.com/malkreide/lobbywatch-mcp" \
41
+ org.opencontainers.image.description="MCP server for the Lobbywatch.ch lobby database" \
42
+ org.opencontainers.image.licenses="MIT"
43
+
44
+ # Create a fixed-uid non-root user. uid 1000 is the convention; matches the
45
+ # default Pod SecurityContext in most k8s presets and avoids volume-permission
46
+ # surprises on host-mount setups.
47
+ RUN groupadd --system --gid 1000 lobbywatch \
48
+ && useradd --system --uid 1000 --gid 1000 \
49
+ --home-dir /home/lobbywatch --shell /sbin/nologin lobbywatch \
50
+ && mkdir -p /home/lobbywatch/.cache/lobbywatch-mcp \
51
+ && chown -R lobbywatch:lobbywatch /home/lobbywatch
52
+
53
+ COPY --from=builder /opt/venv /opt/venv
54
+
55
+ ENV PATH="/opt/venv/bin:$PATH" \
56
+ PYTHONDONTWRITEBYTECODE=1 \
57
+ PYTHONUNBUFFERED=1 \
58
+ LOBBYWATCH_MCP_TRANSPORT=http \
59
+ LOBBYWATCH_MCP_HOST=0.0.0.0 \
60
+ LOBBYWATCH_MCP_PORT=8000 \
61
+ LOBBYWATCH_MCP_CACHE_DIR=/home/lobbywatch/.cache/lobbywatch-mcp
62
+
63
+ USER 1000:1000
64
+ WORKDIR /home/lobbywatch
65
+ EXPOSE 8000
66
+
67
+ # In-image healthcheck: ensure the entry point still imports cleanly.
68
+ # This is intentionally lightweight — full liveness/readiness probes belong on
69
+ # the orchestrator (see docs/deployment.md).
70
+ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
71
+ CMD python -c "import lobbywatch_mcp" || exit 1
72
+
73
+ ENTRYPOINT ["lobbywatch-mcp"]
@@ -0,0 +1,71 @@
1
+ # Use Cases & Examples — lobbywatch-mcp
2
+
3
+ Real-world queries by audience. Indicate per example whether an API key is required.
4
+
5
+ > **Hinweis zur Authentifizierung:** Der `lobbywatch-mcp` Server benötigt **keinen API-Key** und keine Authentifizierung für den Zugriff auf die öffentlichen Daten.
6
+
7
+ ### 🏫 Bildung & Schule
8
+ Lehrpersonen, Schulbehörden, Fachreferent:innen
9
+
10
+ **Interessenbindungen im Bildungswesen**
11
+ «Welche Mitglieder der nationalrätlichen Bildungskommission (WBK-N) haben deklarierte Interessenbindungen zu Bildungsverlagen oder privaten Bildungsträgern?»
12
+ → `search_parlamentarier_nach_branche(branche_query="Bildung", kommission="WBK-N")`
13
+ Warum nützlich: Erlaubt Lehrpersonen und Schulbehörden, mögliche Befangenheiten bei bildungspolitischen Entscheidungen zu erkennen.
14
+
15
+ **Transparenz bei Bildungspolitiker:innen**
16
+ «Wie transparent sind die Mitglieder der Bildungskommission (WBK-N) bezüglich ihrer Entschädigungen bei Nebeneinkünften?»
17
+ → `get_transparenzquote(kommission="WBK-N")`
18
+ Warum nützlich: Zeigt auf, wie offen die zuständigen Politiker:innen ihre finanziellen Interessen im Bildungsbereich deklarieren.
19
+
20
+ ### 👨‍👩‍👧 Eltern & Schulgemeinde
21
+ Elternräte, interessierte Erziehungsberechtigte
22
+
23
+ **Verbindungen zu Krankenkassen**
24
+ «Gibt es Parlamentarier aus meinem Kanton, die bezahlte Mandate bei Krankenkassen haben, und wie beeinflusst das familienpolitische Vorlagen?»
25
+ → `search_parlamentarier_nach_branche(branche_query="Krankenkasse")`
26
+ Warum nützlich: Hilft Eltern, die Hintergründe von Entscheidungen zu Gesundheits- und Prämienfragen zu verstehen.
27
+
28
+ **Einfluss von Familienorganisationen**
29
+ «Welche Nationalräte sind mit Lobbygruppen aus dem Bereich Familie oder Kinderbetreuung verbunden?»
30
+ → `search_parlamentarier_nach_branche(branche_query="Familie")`
31
+ Warum nützlich: Macht sichtbar, welche Politiker:innen direkte Verbindungen zu familienpolitischen Interessenvertretungen pflegen.
32
+
33
+ ### 🗳️ Bevölkerung & öffentliches Interesse
34
+ Allgemeine Öffentlichkeit, politisch und gesellschaftlich Interessierte
35
+
36
+ **Ranking der Interessenbindungen**
37
+ «Welche Nationalrätinnen oder Ständeräte haben am meisten bezahlte Nebenmandate (hauptberufliche Interessenbindungen)?»
38
+ → `get_ranking(kriterium="anzahl_hauptberuflich", limit=10)`
39
+ Warum nützlich: Bietet der Öffentlichkeit einen klaren Überblick über die politische Unabhängigkeit und mögliche zeitliche Überbelastung von Gewählten.
40
+
41
+ **Überprüfung einer Lobbygruppe**
42
+ «Wer sitzt für den Verband 'economiesuisse' im Parlament und welche Organisationen sind damit verknüpft?»
43
+ → `get_lobbygruppe(name_or_id="economiesuisse")`
44
+ Warum nützlich: Schafft Transparenz über den direkten Einfluss grosser Wirtschaftsverbände auf die Gesetzgebung.
45
+
46
+ ### 🤖 KI-Interessierte & Entwickler:innen
47
+ MCP-Enthusiast:innen, Forscher:innen, Prompt Engineers, öffentliche Verwaltung
48
+
49
+ **Analyse politischer Netzwerke (mit parlament-mcp)**
50
+ «Finde heraus, wer für 'economiesuisse' lobbyiert, und suche dann mit parlament-mcp nach Vorstössen dieser Personen zum Thema Unternehmenssteuern.»
51
+ → `get_lobbygruppe(name_or_id="economiesuisse")` (lobbywatch-mcp)
52
+ → `search_affairs(query="Unternehmenssteuer")` (parlament-mcp: https://github.com/malkreide/parlament-mcp)
53
+ Warum nützlich: Demonstriert die mächtige Kombination von Lobby-Netzwerken mit tatsächlichem Abstimmungs- und Vorstossverhalten.
54
+
55
+ **Branchen-Profiling und parlamentarisches Wirken**
56
+ «Welche Politiker:innen sind im Bereich 'Pharma' aktiv und welche Vorstösse haben sie in der letzten Session eingereicht?»
57
+ → `search_parlamentarier_nach_branche(branche_query="Pharma")` (lobbywatch-mcp)
58
+ → `get_parlamentarier(name_or_id="[Name]")` (lobbywatch-mcp)
59
+ Warum nützlich: Verbindet finanzielle Interessen direkt mit parlamentarischem Handeln durch serverübergreifende oder kombinierte Abfragen.
60
+
61
+ ### 🔧 Technische Referenz: Tool-Auswahl nach Anwendungsfall
62
+
63
+ | Ich möchte… | Tool(s) | Auth nötig? |
64
+ | :--- | :--- | :--- |
65
+ | **das vollständige Profil eines Politikers abrufen** | `get_parlamentarier` | Nein |
66
+ | **alle Nebenmandate einer Person auflisten** | `list_interessenbindungen` | Nein |
67
+ | **nach Branchen oder Themen suchen** | `search_parlamentarier_nach_branche` | Nein |
68
+ | **das Netzwerk einer Lobbygruppe analysieren** | `get_lobbygruppe` | Nein |
69
+ | **Ranglisten nach Anzahl Mandaten erstellen** | `get_ranking` | Nein |
70
+ | **die Transparenz einer Kommission auswerten** | `get_transparenzquote` | Nein |
71
+ | **den lokalen Datenbestand aktualisieren** | `refresh_dump` / `dump_status` | Nein |
@@ -0,0 +1,34 @@
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.
22
+
23
+ ---
24
+
25
+ Data notice
26
+ -----------
27
+
28
+ This software accesses and redistributes data from Lobbywatch.ch, which is
29
+ licensed under the Creative Commons Attribution-ShareAlike 4.0 International
30
+ License (CC BY-SA 4.0): https://creativecommons.org/licenses/by-sa/4.0/
31
+
32
+ Users of this software must comply with the CC BY-SA 4.0 terms for the data,
33
+ independently of the MIT licence that covers the code. Every tool response
34
+ produced by this server includes the required attribution string.