{
  "markdown": "# 🏛️ lobbywatch-mcp\n\n[![CI](https://github.com/malkreide/lobbywatch-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/malkreide/lobbywatch-mcp/actions/workflows/ci.yml)\n[![PyPI version](https://badge.fury.io/py/lobbywatch-mcp.svg)](https://badge.fury.io/py/lobbywatch-mcp)\n[![Python](https://img.shields.io/badge/python-3.11%2B-blue)](https://www.python.org)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n[![Data: CC BY-SA 4.0](https://img.shields.io/badge/Data-CC%20BY--SA%204.0-lightgrey.svg)](https://creativecommons.org/licenses/by-sa/4.0/)\n[![Swiss Public Data MCP Portfolio](https://img.shields.io/badge/Portfolio-Swiss%20Public%20Data%20MCP-blue)](https://github.com/malkreide)\n\n> An MCP server that connects AI models to **Lobbywatch.ch**, the largest lobby database of the Swiss Federal Parliament — conflicts of interest, lobby groups, access badges, and transparency scores.\n\n[🇩🇪 Deutsche Version](README.de.md)\n\n> **Part of the [Swiss Public Data MCP Portfolio](https://github.com/malkreide)** — connecting AI models to Swiss public data sources.\n\n---\n\n## 🎯 Anchor Demo Query\n\n> *\"Welche Mitglieder der WBK-N haben Interessenbindungen zu Bildungsverlagen oder privaten Bildungsträgern, und wie ist ihre Transparenz-Bewertung?\"*\n\nWhich members of the National Council's Education Commission have declared conflicts of interest with educational publishers or private education providers, and how does their compensation transparency score compare?\n\n[→ More use cases by audience →](EXAMPLES.md)\n\n### Demo\n\n![Demo: Claude using lobbywatch_search_parlamentarier_nach_branche and lobbywatch_get_transparenzquote](docs/assets/demo.svg)\n\n---\n\n## Overview\n\nLobbywatch.ch maintains the largest public database on Swiss federal parliamentarians and their connections to lobby organisations: 245 parliamentarians, ~7'800 interessenbindungen (declared mandates), 139 lobby groups, 368 access-badge holders, updated weekly, licensed CC BY-SA 4.0.\n\n`lobbywatch-mcp` exposes this data to Large Language Models via the Model Context Protocol. It is designed to be used alongside [`parlament-mcp`](https://github.com/malkreide/parlament-mcp) (the official Swiss Parliament's Curia Vista data): the pair makes it possible to ask *what* a parliamentarian did officially and *who* they are connected to — in a single conversation.\n\n## Features\n\n- **Dump-first, API-fallback architecture.** The weekly JSON dump is the primary source (stable, verified in production); the live `dataIF` REST API is used only where it returns reliable data (lobby groups, search).\n- **Seven Phase 1 tools** — parliamentarian lookup, conflict-of-interest listing, branche search, lobby group fetch, rankings, transparency quota, cache control.\n- **CC BY-SA 4.0 attribution** baked into every response via Pydantic envelopes.\n- **Dual transport** — `stdio` for Claude Desktop, `streamable-http` / `sse` for cloud deployments.\n- **Fuzzy name matching** via rapidfuzz for natural LLM input like \"Jositsch\" or \"Wehrli\".\n- **No authentication required** (Phase 1 — No-Auth-First).\n\n## Architecture\n\n```\n                    ┌─────────────────────────────┐\n   LLM client       │     LobbywatchClient        │\n  (Claude Desktop,  │                             │\n   Inspector, …)    │   ┌───────────────────┐     │\n        │           │   │  Dump cache       │     │      cms.lobbywatch.ch\n        │  MCP      │   │  (24 h TTL,       │     │      ┌──────────────────┐\n        ▼  stdio /  │   │   ~80 MB resident)├─────┼─────►│ weekly JSON      │\n   ┌─────────┐ HTTP │   └───────────────────┘     │      │ export (~17 MB)  │\n   │ FastMCP │◄────►│                             │      └──────────────────┘\n   │ server  │      │   ┌───────────────────┐     │      ┌──────────────────┐\n   └─────────┘      │   │  dataIF REST      ├─────┼─────►│ /interface/v1/   │\n                    │   │  (live fallback)  │     │      │   json/…         │\n                    │   └───────────────────┘     │      └──────────────────┘\n                    └─────────────────────────────┘\n```\n\nOutbound HTTP runs through a single `httpx.AsyncClient` with `follow_redirects=False`, an SSRF guard that blocks RFC1918 / link-local / metadata IPs, and an httpx event hook that re-resolves on every request. The dump path is the primary source of truth for parliamentarian queries; `dataIF` is only used for lobby group lookups and the search endpoint.\n\n## Prerequisites\n\n- Python 3.11 or newer\n- Internet access to download the weekly Lobbywatch JSON export (~17 MB zipped)\n\n## Installation\n\nFrom PyPI (after first release):\n\n```bash\npip install lobbywatch-mcp\n```\n\nFrom source:\n\n```bash\ngit clone https://github.com/malkreide/lobbywatch-mcp.git\ncd lobbywatch-mcp\npip install -e \".[dev]\"\n```\n\n## Usage\n\n### Standalone\n\n```bash\nlobbywatch-mcp\n```\n\nThis starts the server in `stdio` mode. For HTTP:\n\n```bash\nLOBBYWATCH_MCP_TRANSPORT=http LOBBYWATCH_MCP_PORT=8000 lobbywatch-mcp\n```\n\n### Container\n\nA hardened multi-stage `Dockerfile` ships with the repo (non-root,\nread-only-rootfs compatible). See [`docs/deployment.md`](docs/deployment.md)\nand [`deploy/docker-compose.example.yml`](deploy/docker-compose.example.yml)\nfor resource limits, sticky-LB guidance and egress hardening.\n\n```bash\ndocker build -t lobbywatch-mcp:0.2.0 .\ndocker run --rm -p 127.0.0.1:8000:8000 lobbywatch-mcp:0.2.0\n```\n\n### Claude Desktop\n\nAdd to your `claude_desktop_config.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"lobbywatch\": {\n      \"command\": \"uvx\",\n      \"args\": [\"lobbywatch-mcp\"]\n    }\n  }\n}\n```\n\nA full example is provided in [`claude_desktop_config.json`](claude_desktop_config.json).\n\n### Example Queries\n\nOnce connected, try prompts such as:\n\n- *\"Give me the top 10 parliamentarians by number of interessenbindungen in the SP party.\"*\n- *\"Which WBK-N members have mandates in the publishing or education industry?\"*\n- *\"Look up the lobby group 'economiesuisse' and list its connected parliamentarians.\"*\n- *\"What is the compensation-transparency score distribution for the finance commission (FK-N)?\"*\n\n## Tools\n\nAll tool names use the `lobbywatch_` namespace prefix (since 0.2.0) to\navoid collisions with sibling portfolio servers.\n\n| Tool | Purpose | Source |\n|---|---|---|\n| `lobbywatch_get_parlamentarier(name_or_id)` | Full profile + all conflicts of interest | Dump |\n| `lobbywatch_list_interessenbindungen(name_or_id, nur_hauptberuflich, nur_aktiv)` | Filtered mandate list | Dump |\n| `lobbywatch_search_parlamentarier_nach_branche(branche_query, kommission, limit)` | Cross-filter by industry and commission | Dump |\n| `lobbywatch_get_lobbygruppe(name_or_id)` | Lobby group with connected MPs and organisations | Live dataIF |\n| `lobbywatch_get_ranking(kriterium, kommission, partei, limit)` | Top-N by criterion | Dump |\n| `lobbywatch_get_transparenzquote(kommission)` | Distribution of compensation transparency labels | Dump |\n| `lobbywatch_refresh_dump()` / `lobbywatch_dump_status()` | Cache control | Dump |\n\n## Configuration\n\nAll behaviour is controlled via environment variables:\n\n| Variable | Default | Purpose |\n|---|---|---|\n| `LOBBYWATCH_MCP_TRANSPORT` | `stdio` | Transport (`stdio`, `http`, `sse`) |\n| `LOBBYWATCH_MCP_HOST` | `127.0.0.1` | HTTP bind host (set to `0.0.0.0` only behind an auth gateway) |\n| `LOBBYWATCH_MCP_PORT` | `8000` | HTTP bind port |\n| `LOBBYWATCH_MCP_CACHE_DIR` | `~/.cache/lobbywatch-mcp` | Dump cache location |\n| `LOBBYWATCH_MCP_CACHE_TTL` | `86400` (24h) | Cache time-to-live in seconds |\n| `LOBBYWATCH_MCP_HTTP_TIMEOUT` | `60` | HTTP timeout in seconds |\n| `LOBBYWATCH_MCP_CORS_ORIGINS` | _(unset)_ | Comma-separated origin allow-list for HTTP/SSE; when set, exposes `Mcp-Session-Id` to browsers |\n| `LOBBYWATCH_MCP_LOG_FORMAT` | `text` | `text` (stdlib formatter) or `json` (structured via structlog) |\n| `LOBBYWATCH_MCP_LOG_LEVEL` | `INFO` | `DEBUG` / `INFO` / `WARNING` / `ERROR` |\n| `LOBBYWATCH_MCP_OTEL_ENABLED` | `0` | Set to `1` to enable OpenTelemetry tracing (requires `pip install 'lobbywatch-mcp[obs]'`) |\n| `LOBBYWATCH_MCP_OTEL_ENDPOINT` | _(unset)_ | OTLP/HTTP collector endpoint (e.g. `http://localhost:4318/v1/traces`) |\n\n## Project Structure\n\n```\nlobbywatch-mcp/\n├── src/lobbywatch_mcp/\n│   ├── __init__.py\n│   ├── __main__.py        # CLI + transport selection\n│   ├── config.py          # URLs, cache paths, attribution\n│   ├── client.py          # Dump download + dataIF client\n│   ├── models.py          # Pydantic v2 response envelopes\n│   └── server.py          # FastMCP tool registrations\n├── tests/\n│   ├── conftest.py        # Fixture parliamentarians\n│   ├── test_client.py     # Respx-mocked unit tests\n│   ├── test_server.py     # Tool integration tests\n│   └── test_live.py       # @pytest.mark.live — excluded from CI\n├── .github/workflows/\n│   ├── ci.yml             # Test matrix + ruff\n│   └── publish.yml        # PyPI OIDC Trusted Publisher\n├── claude_desktop_config.json\n├── pyproject.toml\n└── ...\n```\n\n## Data License & Attribution\n\n**The code** is released under the MIT License.\n\n**The data** served through this MCP is © Lobbywatch.ch and licensed under [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/). Every response envelope includes the attribution string. Downstream users must:\n\n1. Credit Lobbywatch.ch as the data source.\n2. Share derivative datasets under the same CC BY-SA 4.0 terms.\n3. Understand that Lobbywatch is a **community-researched database** — not an official register. It is authoritative for transparency research but should not be confused with the Federal Parliament's own declarations.\n\n## Known Limitations\n\n- The upstream `/table/parlamentarier/...` `dataIF` REST endpoint currently returns empty result sets. The server works around this by using the weekly JSON dump instead.\n- `zutrittsberechtigungen` (access badges) are not populated in the \"essential\" dump variant used here. A future release will add a dedicated tool using the non-essential dump.\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md).\n\n## Security\n\nSee [SECURITY.md](SECURITY.md) for the security posture, accepted-risk\ndecisions, and how to report a vulnerability.\n\n## MCP Protocol Version\n\nThis server speaks **two protocol eras** over the same endpoint. The client's\nfirst request on a connection decides which one applies; a later claim from the\nother era is refused.\n\n| Era | Revision | Who reaches it |\n|---|---|---|\n| `initialize` handshake | `2024-11-05` … **`2025-11-25`** | What today's clients speak. The server answers with the revision asked for, or with the `2025-11-25` ceiling when the request asks for something newer. |\n| Per-request envelope | **`2026-07-28`** | A request carrying the `2026-07-28` `_meta` envelope opens a modern connection. |\n\nBoth revisions are pinned in\n[`tests/test_protocol_version.py`](tests/test_protocol_version.py) and asserted\nagainst the installed SDK, so a Dependabot bump of `mcp` cannot move either one\nsilently. This server builds no ASGI app to send an `initialize` through, so\nthe gate asserts the SDK constants rather than a measured response — the\nweaker form, named rather than left unsaid.\n\nNote that the SDK's `LATEST_PROTOCOL_VERSION` is an alias for the **modern**\nera, not for the handshake era — pinning against it alone would leave the era\nthat current clients actually negotiate free to drift.\n\n**Update policy.** When the gate fails, do not edit the constant blindly: read\nthe spec changelog between the two revisions, verify the server still behaves,\nthen move the constant, this section, `README.de.md` and\n[`CHANGELOG.md`](CHANGELOG.md) together.\n\n## Changelog\n\nSee [CHANGELOG.md](CHANGELOG.md).\n\n## License\n\nMIT License — see [LICENSE](LICENSE). Data CC BY-SA 4.0 — see [NOTICE.md](NOTICE.md).\n\n## Author\n\n**malkreide** · [GitHub](https://github.com/malkreide)\n\n---\n\n*Part of the [Swiss Public Data MCP Portfolio](https://github.com/malkreide).*\n\n<!-- mcp-name: io.github.malkreide/lobbywatch-mcp -->\n\n<!-- BEGIN GENERATED: install -->\n## Installation\n\nRun via [`uv`](https://docs.astral.sh/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`):\n\n```json\n{\n  \"mcpServers\": {\n    \"lobbywatch-mcp\": {\n      \"command\": \"uvx\",\n      \"args\": [\n        \"lobbywatch-mcp\"\n      ]\n    }\n  }\n}\n```\n<!-- END GENERATED: install -->\n",
  "bytes": 12415,
  "sha": "c02722791eb452e1dc6f9790234a4a9ef59f35790752f9c401f126f81332f730",
  "repo_slug": "malkreide/lobbywatch-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_malkreide_lobbywatch_mcp_f8e97c93/readme"
}