Skip to main content
Glama
malkreide

lobbywatch-mcp

by malkreide

πŸ›οΈ lobbywatch-mcp

CI PyPI version Python License: MIT Data: CC BY-SA 4.0 Swiss Public Data MCP Portfolio

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.

πŸ‡©πŸ‡ͺ Deutsche Version

Part of the Swiss Public Data MCP Portfolio β€” connecting AI models to Swiss public data sources.


🎯 Anchor Demo Query

"Welche Mitglieder der WBK-N haben Interessenbindungen zu Bildungsverlagen oder privaten BildungstrΓ€gern, und wie ist ihre Transparenz-Bewertung?"

Which 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?

β†’ More use cases by audience β†’

Demo

Demo: Claude using lobbywatch_search_parlamentarier_nach_branche and lobbywatch_get_transparenzquote


Related MCP server: parlament-mcp

Overview

Lobbywatch.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.

lobbywatch-mcp exposes this data to Large Language Models via the Model Context Protocol. It is designed to be used alongside 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.

Features

  • 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).

  • Stale-cache fallback, never silent. If the dump cannot be refreshed, a cached copy up to 7 days old answers instead, and every dump-backed response then carries a stale block with its download time, age and the reason the refresh failed. The source is retried every 15 minutes; lobbywatch_refresh_dump never falls back.

  • Seven Phase 1 tools β€” parliamentarian lookup, conflict-of-interest listing, branche search, lobby group fetch, rankings, transparency quota, cache control.

  • CC BY-SA 4.0 attribution baked into every response via Pydantic envelopes.

  • Dual transport β€” stdio for Claude Desktop, streamable-http / sse for cloud deployments.

  • Fuzzy name matching via rapidfuzz for natural LLM input like "Jositsch" or "Wehrli".

  • No authentication required (Phase 1 β€” No-Auth-First).

Architecture

                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   LLM client       β”‚     LobbywatchClient        β”‚
  (Claude Desktop,  β”‚                             β”‚
   Inspector, …)    β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”‚
        β”‚           β”‚   β”‚  Dump cache       β”‚     β”‚      cms.lobbywatch.ch
        β”‚  MCP      β”‚   β”‚  (24 h TTL,       β”‚     β”‚      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β–Ό  stdio /  β”‚   β”‚   ~80 MB resident)β”œβ”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β–Ίβ”‚ weekly JSON      β”‚
   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” HTTP β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚      β”‚ export (~17 MB)  β”‚
   β”‚ FastMCP │◄────►│                             β”‚      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
   β”‚ server  β”‚      β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”‚      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β”‚   β”‚  dataIF REST      β”œβ”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β–Ίβ”‚ /interface/v1/   β”‚
                    β”‚   β”‚  (live fallback)  β”‚     β”‚      β”‚   json/…         β”‚
                    β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Outbound 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.

Prerequisites

  • Python 3.11 or newer

  • Internet access to download the weekly Lobbywatch JSON export (~17 MB zipped)

Installation

From PyPI (after first release):

pip install lobbywatch-mcp

From source:

git clone https://github.com/malkreide/lobbywatch-mcp.git
cd lobbywatch-mcp
pip install -e ".[dev]"

Usage

Standalone

lobbywatch-mcp

This starts the server in stdio mode. For HTTP:

LOBBYWATCH_MCP_TRANSPORT=http LOBBYWATCH_MCP_PORT=8000 lobbywatch-mcp

Container

A hardened multi-stage Dockerfile ships with the repo (non-root, read-only-rootfs compatible). See docs/deployment.md and deploy/docker-compose.example.yml for resource limits, sticky-LB guidance and egress hardening.

docker build -t lobbywatch-mcp:0.2.0 .
docker run --rm -p 127.0.0.1:8000:8000 lobbywatch-mcp:0.2.0

Claude Desktop

Add to your claude_desktop_config.json:

{
  "mcpServers": {
    "lobbywatch": {
      "command": "uvx",
      "args": ["lobbywatch-mcp"]
    }
  }
}

A full example is provided in claude_desktop_config.json.

Example Queries

Once connected, try prompts such as:

  • "Give me the top 10 parliamentarians by number of interessenbindungen in the SP party."

  • "Which WBK-N members have mandates in the publishing or education industry?"

  • "Look up the lobby group 'economiesuisse' and list its connected parliamentarians."

  • "What is the compensation-transparency score distribution for the finance commission (FK-N)?"

Tools

All tool names use the lobbywatch_ namespace prefix (since 0.2.0) to avoid collisions with sibling portfolio servers.

Tool

Purpose

Source

lobbywatch_get_parlamentarier(name_or_id)

Full profile + all conflicts of interest

Dump

lobbywatch_list_interessenbindungen(name_or_id, nur_hauptberuflich, nur_aktiv)

Filtered mandate list

Dump

lobbywatch_search_parlamentarier_nach_branche(branche_query, kommission, limit)

Cross-filter by industry and commission

Dump

lobbywatch_get_lobbygruppe(name_or_id)

Lobby group with connected MPs and organisations

Live dataIF

lobbywatch_get_ranking(kriterium, kommission, partei, limit)

Top-N by criterion

Dump

lobbywatch_get_transparenzquote(kommission)

Distribution of compensation transparency labels

Dump

lobbywatch_refresh_dump() / lobbywatch_dump_status()

Cache control

Dump

Configuration

All behaviour is controlled via environment variables:

Variable

Default

Purpose

LOBBYWATCH_MCP_TRANSPORT

stdio

Transport (stdio, http, sse)

LOBBYWATCH_MCP_HOST

127.0.0.1

HTTP bind host (set to 0.0.0.0 only behind an auth gateway)

LOBBYWATCH_MCP_PORT

8000

HTTP bind port

LOBBYWATCH_MCP_CACHE_DIR

~/.cache/lobbywatch-mcp

Dump cache location

LOBBYWATCH_MCP_CACHE_TTL

86400 (24h)

Cache time-to-live in seconds

LOBBYWATCH_MCP_STALE_MAX_AGE

604800 (7 days)

Oldest cached dump that may answer when the refresh fails, in seconds; 0 disables the fallback

LOBBYWATCH_MCP_HTTP_TIMEOUT

60

HTTP timeout in seconds

LOBBYWATCH_MCP_CORS_ORIGINS

(unset)

Comma-separated origin allow-list for HTTP/SSE; when set, exposes Mcp-Session-Id to browsers

LOBBYWATCH_MCP_LOG_FORMAT

text

text (stdlib formatter) or json (structured via structlog)

LOBBYWATCH_MCP_LOG_LEVEL

INFO

DEBUG / INFO / WARNING / ERROR

LOBBYWATCH_MCP_OTEL_ENABLED

0

Set to 1 to enable OpenTelemetry tracing (requires pip install 'lobbywatch-mcp[obs]')

LOBBYWATCH_MCP_OTEL_ENDPOINT

(unset)

OTLP/HTTP collector endpoint (e.g. http://localhost:4318/v1/traces)

Project Structure

lobbywatch-mcp/
β”œβ”€β”€ src/lobbywatch_mcp/
β”‚   β”œβ”€β”€ __init__.py
β”‚   β”œβ”€β”€ __main__.py        # CLI + transport selection
β”‚   β”œβ”€β”€ config.py          # URLs, cache paths, attribution
β”‚   β”œβ”€β”€ client.py          # Dump download + dataIF client
β”‚   β”œβ”€β”€ models.py          # Pydantic v2 response envelopes
β”‚   └── server.py          # FastMCP tool registrations
β”œβ”€β”€ tests/
β”‚   β”œβ”€β”€ conftest.py        # Fixture parliamentarians
β”‚   β”œβ”€β”€ test_client.py     # Respx-mocked unit tests
β”‚   β”œβ”€β”€ test_server.py     # Tool integration tests
β”‚   └── test_live.py       # @pytest.mark.live β€” excluded from CI
β”œβ”€β”€ .github/workflows/
β”‚   β”œβ”€β”€ ci.yml             # Test matrix + ruff
β”‚   └── publish.yml        # PyPI OIDC Trusted Publisher
β”œβ”€β”€ claude_desktop_config.json
β”œβ”€β”€ pyproject.toml
└── ...

Data License & Attribution

The code is released under the MIT License.

The data served through this MCP is Β© Lobbywatch.ch and licensed under CC BY-SA 4.0. Every response envelope includes the attribution string. Downstream users must:

  1. Credit Lobbywatch.ch as the data source.

  2. Share derivative datasets under the same CC BY-SA 4.0 terms.

  3. 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.

Known Limitations

  • The upstream /table/parlamentarier/... dataIF REST endpoint currently returns empty result sets. The server works around this by using the weekly JSON dump instead.

  • 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.

Contributing

See CONTRIBUTING.md.

Security

See SECURITY.md for the security posture, accepted-risk decisions, and how to report a vulnerability.

MCP Protocol Version

This server speaks two protocol eras over the same endpoint. The client's first request on a connection decides which one applies; a later claim from the other era is refused.

Era

Revision

Who reaches it

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.

Per-request envelope

2026-07-28

A request carrying the 2026-07-28 _meta envelope opens a modern connection.

Both revisions are pinned in tests/test_protocol_version.py and asserted against the installed SDK, so a Dependabot bump of mcp cannot move either one silently. The constants alone are the weaker form, so tests/test_spec_2026_07_28.py measures both eras through the assembled ASGI app: server/discover without a session, an initialize asking for a future revision capped at 2025-11-25, and every tool called over a 2026-07-28 connection with SDK deprecations raised as errors.

Native to 2026-07-28. lobbywatch_refresh_dump reports its progress as notifications/progress, which both eras deliver to any client that sends a progressToken. It used to log via notifications/message; logging is deprecated in 2026-07-28 (SEP-2577) and only delivered on requests that opt in with a log level, so modern clients silently received nothing. serverInfo now carries the package version β€” under 2026-07-28 it is stamped into the _meta of every response.

Note that the SDK's LATEST_PROTOCOL_VERSION is an alias for the modern era, not for the handshake era β€” pinning against it alone would leave the era that current clients actually negotiate free to drift.

Update policy. When the gate fails, do not edit the constant blindly: read the spec changelog between the two revisions, verify the server still behaves, then move the constant, this section, README.de.md and CHANGELOG.md together.

Changelog

See CHANGELOG.md.

License

MIT License β€” see LICENSE. Data CC BY-SA 4.0 β€” see NOTICE.md.

Author

malkreide Β· GitHub


Part of the 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": {
    "lobbywatch-mcp": {
      "command": "uvx",
      "args": [
        "lobbywatch-mcp"
      ]
    }
  }
}

Available Tools

8 tools
lobbywatch_dump_statusA
Read-onlyIdempotent

Return current dump cache status without forcing a refresh.

Use cases: - "How fresh is the cached data right now?" - "When was the dump last loaded?"

NameRequiredDescriptionDefault

No parameters

Output Schema

NameRequiredDescription
staleNoSet only when the dump could not be refreshed and an older cached copy answered instead. The data may miss changes made since `stale.cached_at` β€” say so when relaying it.
loadedYes
sourceNo
cached_atNo
provenanceYesWhich endpoint / dump the payload came from
age_secondsNo
record_countNo

TDQS

A4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, idempotentHint=true, and openWorldHint=false, so the safety profile is covered. The description adds only the 'without forcing a refresh' constraint, which is useful but modest given annotations carry the main behavioral burden.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded one-line purpose followed by two tightly scoped use-case bullets; every line earns its place with zero filler or restatement of the title.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need no explanation, and annotations fully cover the read-only/idempotent safety profile. For a zero-parameter status read, nothing an agent needs to invoke it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, so there is nothing to document and no ambiguity for an agent to resolve. Baseline of 4 applies for a no-argument tool.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (Return) and resource (dump cache status), and the qualifier 'without forcing a refresh' distinguishes it from the mutation-oriented sibling lobbywatch_refresh_dump. It stops short of naming that sibling explicitly, but the scope is unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides concrete use cases ('How fresh is the cached data?' / 'When was the dump last loaded?') and an implicit when-not via 'without forcing a refresh'. Clear usage context, though it never names lobbywatch_refresh_dump as the alternative for forcing a reload.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

lobbywatch_get_lobbygruppeA
Read-onlyIdempotent

Fetch a lobby group (interessengruppe) from the live Lobbywatch dataIF, including its connected organisations and parliamentarians.

Uses the live REST API since this endpoint returns fresh data.

Use cases: - "Look up 'economiesuisse' and list connected MPs" - "Who's affiliated with the lobby group #42?" - "Show me all parliamentarians linked to the pharma lobby"

NameRequiredDescriptionDefault
name_or_idYes

Output Schema

NameRequiredDescription
sourceNo
provenanceYesWhich endpoint / dump the payload came from
lobbygruppeNo

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already indicate read-only and idempotent behavior. The description adds transparency by noting the live REST API and fresh data, plus the inclusion of connected entities. No contradictions, but more detail on rate limits or auth could further enhance.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is concise with a clear purpose statement followed by illustrative use cases. Every sentence adds value, and the structure is front-loaded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description adequately covers what the tool does, including its return content (connected organisations and parliamentarians). With an output schema available, further detail on return format is unnecessary.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has no parameter descriptions (0% coverage), but the description compensates by showing examples of using a name or ID, clarifying the parameter's purpose and format.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool fetches a lobby group including connected organisations and parliamentarians. It is specific and distinguishes from sibling tools that focus on individual parliamentarians, rankings, or transparency quotes.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides example use cases that illustrate common queries, giving clear context for when to use the tool. However, it lacks explicit guidance on when not to use it or alternatives among siblings.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

lobbywatch_get_parlamentarierA
Read-onlyIdempotent

Look up a Swiss federal parliamentarian and return their full profile, including all declared/researched interessenbindungen.

On a fuzzy miss, the response surfaces near-miss candidates so the LLM can prompt the user with "did you mean…?" suggestions instead of treating the empty result as authoritative (audit ARCH-003).

Args: name_or_id: Either the numeric Lobbywatch ID (as string) or a name. Name matching is fuzzy β€” partial last names work.

Use cases: - "Show me Anna Mustermann's full lobbying profile" - "What conflicts of interest does parliamentarian #42 declare?" - "Look up Wehrli β€” give me everything you have"

NameRequiredDescriptionDefault
name_or_idYes

Output Schema

NameRequiredDescription
staleNoSet only when the dump could not be refreshed and an older cached copy answered instead. The data may miss changes made since `stale.cached_at` β€” say so when relaying it.
sourceNo
provenanceYesWhich endpoint / dump the payload came from
suggestionsNoNear-miss fuzzy candidates when the lookup failed (ARCH-003)
parlamentarierNo

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations cover the safety profile (readOnly, idempotent, closed-world), so the bar is lower, and the description adds non-obvious behavior: fuzzy miss returns near-miss candidates rather than an authoritative empty result. Useful and specific, though nothing is said about profile size, rate limits, or error behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Purpose and fuzzy-miss caveat are front-loaded, followed by clearly labeled Args and Use cases blocks. Slightly verbose with the use-case list and audit reference, but every section is scannable and none of it is filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

A single-parameter lookup with an output schema present, so return-field detail is not required, and the description still explains the fuzzy-miss return behavior. Complete enough for correct invocation; only marginal gaps remain around input sanitization and failure modes.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With schema description coverage at 0%, the description carries the burden and does so: it explains name_or_id accepts either a numeric Lobbywatch ID as a string or a name, and that name matching is fuzzy with partial last names supported. That is substantive semantics beyond the bare string type in the schema, though it omits edge cases like max length behavior.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (look up) and resource (Swiss federal parliamentarian) plus what it returns (full profile including interessenbindungen). Clearly distinct from siblings like lobbywatch_search_parlamentarier_nach_branche and lobbywatch_list_interessenbindungen.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides three concrete use cases that make the intended invocation context clear, and explains the fuzzy-miss path. It does not, however, explicitly name alternatives or state when not to use it (e.g., vs. the search-by-branche sibling), so it stops short of full routing guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

lobbywatch_get_rankingA
Read-onlyIdempotent

Rank parliamentarians by a criterion.

Args: kriterium: One of "anzahl_interessenbindungen", "anzahl_hauptberuflich". kommission: Optional commission abbreviation filter (e.g. "WBK-N"). partei: Optional party filter (e.g. "SP", "SVP", "Mitte"). limit: Top-N to return (1–100).

Use cases: - "Top 10 SP MPs by total mandate count" - "Which Mitte-Fraktion members have the most full-time mandates?" - "Rank WBK-N by IB count β€” who's most involved?"

NameRequiredDescriptionDefault
limitNo
parteiNo
kriteriumNoanzahl_interessenbindungen
kommissionNo

Output Schema

NameRequiredDescription
staleNoSet only when the dump could not be refreshed and an older cached copy answered instead. The data may miss changes made since `stale.cached_at` β€” say so when relaying it.
sourceNo
eintraegeNo
kriteriumYes
provenanceYesWhich endpoint / dump the payload came from

TDQS

A3.8/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, idempotentHint=true and openWorldHint=false, so the safety profile is covered. The description adds that kriterium is restricted to two values and that filters can be combined, but says nothing about ordering direction, tie-breaking, or behavior of defaults beyond the schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The one-line purpose is front-loaded, followed by a compact Arg list and three terse examples. Every element earns its place, though the labeled sections add slight overhead relative to the amount of content.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present, return values need not be described, and all four parameters plus the two enum values receive coverage. Combined with annotations covering idempotency and safety, an agent has enough to invoke it correctly; only ordering/ranking semantics remain unstated.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must carry the load, and it largely does: it names each of the four parameters, gives examples for kommission ('WBK-N') and partei ('SP', 'SVP', 'Mitte'), and the use cases disambiguate the opaque enum values (e.g. 'night-most full-time mandates' for anzahl_hauptberuflich). It still does not explain the full semantics of anzahl_interessenbindungen beyond 'IB count'.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Rank parliamentarians by a criterion'), which is clearly distinct from sibling tools like get_parlamentarier or list_interessenbindungen. It does not explicitly name or contrast against those siblings, so it falls short of a 5.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The 'Use cases' block gives three concrete scenarios that show when the tool applies (ranking by mandate count, filtering by party, filtering by commission), which is clear context. It does not state when NOT to use it or name an alternative sibling, so no exclusions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

lobbywatch_get_transparenzquoteA
Read-onlyIdempotent

Aggregate the verguetungstransparenz_beurteilung values across all parliamentarians (or a commission subset) and return the distribution.

Useful to answer: 'How transparent is the education commission on compensation disclosure?'

Use cases: - "How transparent is the FK-N on compensation disclosure?" - "Distribution of transparency labels across the whole parliament" - "Compare WBK-N transparency vs the council average"

NameRequiredDescriptionDefault
kommissionNo

Output Schema

NameRequiredDescription
scopeYes
staleNoSet only when the dump could not be refreshed and an older cached copy answered instead. The data may miss changes made since `stale.cached_at` β€” say so when relaying it.
totalYes
sourceNo
provenanceYesWhich endpoint / dump the payload came from
nach_bewertungNo
quote_ausreichendNo

TDQS

A3.9/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint, and openWorldHint=false, so the safety profile is covered. The description adds real behavioral context beyond that: it states what underlying field is aggregated and that omitting the commission defaults to the whole parliament. No permission or rate-limit detail is given, but none is needed for a local-dump read.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The core operation is front-loaded in the first sentence and the example questions are tucked below it. The bulleted use cases partly restate the 'Useful to answer' line, so there is slight redundancy, but nothing is bloated for a tool of this complexity.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so the description needn't explain the return shape, and it correctly focuses on intent and scope. Combined with the annotations it gives an agent enough to invoke the tool correctly; only the commission-identifier format is left to inference.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

There is one parameter with 0% schema description coverage, so the description has to carry the load. It implies 'kommission' accepts a commission identifier and the examples (FK-N, WBK-N) hint at the accepted format, which is genuinely useful. However, it never states the format or matching rules explicitly and doesn't mention the maxLength constraint, so compensation is only partial.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description gives a specific verb and resource: it aggregates verguetungstransparenz_beurteilung values and returns the distribution, with scope stated as 'all parliamentarians (or a commission subset)'. That is far more informative than the opaque German tool name. It does not, however, explicitly differentiate itself from siblings such as lobbywatch_get_ranking, so it stops short of a 5.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage context is clear and concrete: 'Useful to answer: How transparent is the education commission...' plus three example questions covering whole-parliament, single-commission, and comparison scenarios. There is no explicit when-not guidance or named alternative (e.g. get_ranking), so it does not reach 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

lobbywatch_list_interessenbindungenA
Read-onlyIdempotent

Return the list of interessenbindungen (conflicts of interest) for one parliamentarian, optionally restricted to full-time or currently-active mandates.

On a fuzzy miss, near-miss candidates are returned in suggestions (audit ARCH-003).

Args: name_or_id: ID or name (fuzzy). nur_hauptberuflich: If True, only main-occupation mandates. nur_aktiv: If True, drop mandates with an end date (bis) set.

Use cases: - "Which active mandates does Jositsch hold today?" - "List Anna Mustermann's full-time mandates only" - "Give me every IB ever declared by parliamentarian #1"

NameRequiredDescriptionDefault
nur_aktivNo
name_or_idYes
nur_hauptberuflichNo

Output Schema

NameRequiredDescription
countYes
staleNoSet only when the dump could not be refreshed and an older cached copy answered instead. The data may miss changes made since `stale.cached_at` β€” say so when relaying it.
sourceNo
provenanceYesWhich endpoint / dump the payload came from
suggestionsNoNear-miss fuzzy candidates when the lookup failed (ARCH-003)
parlamentarier_idYes
interessenbindungenNo

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly, idempotent and non-open-world semantics, so the safety profile is covered. The description adds genuine behavioral context beyond that: fuzzy-name misses return near-miss candidates in a 'suggestions' field (audit ARCH-003), and nur_aktiv drops mandates with an end date set. It does not mention rate limits or result ordering, but for a read-only list tool this is solid.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Purpose and the fuzzy-miss caveat are front-loaded, then args, then use cases. Every section earns its place, though the use-case block repeats filter semantics already stated in Args, adding mild redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need not be explained, and the description covers the mutation-free behavior, the three parameters and the fuzzy-match edge case. Missing only the default of nur_aktiv and any note on result scope/ordering for a complete picture.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must carry the parameter meaning, and it does: name_or_id is 'ID or name (fuzzy)', nur_hauptberuflich means only main-occupation mandates, nur_aktiv drops mandates with an end date set. The one gap is not noting that nur_aktiv defaults to true in the schema, which could surprise an agent expecting all mandates.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Return the list of interessenbindungen'), glosses the German term as conflicts of interest, and scopes it to 'one parliamentarian' with optional filters. This is clearly distinguishable from siblings like get_parlamentarier or get_lobbygruppe without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Three concrete use cases ('Which active mandates does Jositsch hold today?', 'full-time mandates only', 'every IB ever declared by #1') make the intended usage unambiguous and show how the filter flags combine. It stops short of naming an alternative tool or stating when not to use it, so it misses the top tier.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

lobbywatch_refresh_dumpA
Idempotent

Force re-download of the weekly Lobbywatch dump. Returns the new cache status.

Reports progress via the MCP Context (audit SDK-003) so long-running downloads (~17 MB compressed) surface useful feedback to the calling LLM and operator.

Use cases: - "Force a fresh download β€” the data looks stale" - "I just heard about a new declaration β€” refresh and re-check"

NameRequiredDescriptionDefault

No parameters

Output Schema

NameRequiredDescription
staleNoSet only when the dump could not be refreshed and an older cached copy answered instead. The data may miss changes made since `stale.cached_at` β€” say so when relaying it.
loadedYes
sourceNo
cached_atNo
provenanceYesWhich endpoint / dump the payload came from
age_secondsNo
record_countNo

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly=false, destructive=false, idempotent=true, and openWorld=true, so the safety profile is covered. The description adds genuinely useful context beyond that: progress reporting via MCP Context, the ~17 MB compressed payload size, and that it is long-running – helping an agent anticipate a slow call.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The core action and its return are front-loaded in the first two sentences, with operational detail and use cases following. Nothing is padded; the quoted examples are compact and aid routing.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present, the description need not explain return values, and it correctly defers to it while noting 'returns the new cache status'. It covers behavior, size, and progress feedback; only the expected wall-clock duration or blocking behavior is left unstated.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, so there is nothing for the description to disambiguate; the empty schema is self-explanatory. Baseline 4 applies for a parameterless tool.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Force re-download of the weekly Lobbywatch dump') and adds the scoping detail that it is weekly and a full re-download. It does not explicitly differentiate from the sibling lobbywatch_dump_status, so an agent must infer that this mutates the cache while the sibling only reports it.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The two quoted use cases ('data looks stale', 'new declaration β€” refresh and re-check') give clear triggering conditions. However, it names no alternatives (e.g. check lobbywatch_dump_status first to see if a refresh is even needed) and no when-not-to-use guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

lobbywatch_search_parlamentarier_nach_brancheA
Read-onlyIdempotent

Find parliamentarians with interessenbindungen matching a search term against the linked organisation's name, alias, and (when available) branche field. Optional commission filter.

Note: the Lobbywatch "essential" dump does not embed a branche taxonomy on each organisation β€” branche is referenced by id and resolved via the separate interessengruppe table. Version 0.1 therefore performs a substring match against organisation names and the branche field when present. Version 0.2 will add full cross-reference resolution.

Args: branche_query: Substring match (case-insensitive) against organisation.anzeige_name, organisation.name, organisation.branche (where present), e.g. "Verlag", "Pharma", "Bank", "Krankenkasse", "Bildung". kommission: Commission abbreviation to restrict the result to, e.g. "WBK-N" for the education commission of the National Council. limit: Max number of {parlamentarier, ib} pairs returned (1–200).

Use cases: - "Which WBK-N members hold mandates in the publishing industry?" - "Cross-filter Pharma Γ— FK-N to surface health-policy lobbyists" - "List every parliamentarian with a 'Krankenkasse' connection"

NameRequiredDescriptionDefault
limitNo
kommissionNo
branche_queryYes

Output Schema

NameRequiredDescription
countYes
queryYes
staleNoSet only when the dump could not be refreshed and an older cached copy answered instead. The data may miss changes made since `stale.cached_at` β€” say so when relaying it.
sourceNo
trefferNoList of {parlamentarier, interessenbindung} pairs matching the branche
provenanceYesWhich endpoint / dump the payload came from

TDQS

A3.9/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint and openWorldHint=false, yet the description still adds real behavioral context: the match is a case-insensitive substring over organisation names and the branche field where present, and the current 0.1 implementation cannot resolve branche ids via interessengruppe. That limitation disclosure materially affects result interpretation.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The Args and Use cases sections are well front-loaded and earn their place, but the paragraph about version 0.1 vs. a planned 0.2 adds roadmap narrative that is tangential to calling the tool today. The definition is longer than it needs to be for three parameters.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need not be described, and the description still notes the pair shape ({parlamentarier, ib}). Combined with the disclosed substring/version limitation and full parameter coverage, the agent has what it needs; the undefined 'ib' abbreviation and unstated default are minor gaps.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 0% schema description coverage the description carries the burden and largely succeeds: branche_query is documented with its matching fields and examples ('Verlag', 'Pharma'), kommission with a concrete abbreviation ('WBK-N'), and limit as the max number of pairs returned. It omits the limit default (25) and maxLength constraints, so it stops short of a 5.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description gives a specific verb+resource: find parliamentarians whose interessenbindungen match a term across an organisation's name/alias/branche. It is clearly a search over mandate links rather than a plain parliamentarian lookup, but it never names a sibling (e.g., lobbywatch_list_interessenbindungen or lobbywatch_get_parlamentarier) to route the agent between them.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Three concrete use cases establish the intended context (e.g., 'Which WBK-N members hold mandates in the publishing industry?'), and the kommission/branche combination is explained. However, there is no explicit when-not or alternative-tool guidance, so the agent must infer boundaries with the sibling list/search tools.

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.

  1. 7 tool updatesv0.3.8
    • Changedlobbywatch_dump_status2 fields changed
      • addedOutput schema / $defs
        Added value: +{
        +  "StaleDump": {
        +    "description": "The weekly dump could not be refreshed; an older cached copy answered.",
        +    "properties": {
        +      "age_seconds": {
        +        "description": "Age of the answering copy",
        +        "title": "Age Seconds",
        +        "type": "integer"
        +      },
        +      "cached_at": {
        +        "description": "When the answering copy was downloaded (UTC, ISO 8601)",
        +        "title": "Cached At",
        +        "type": "string"
        +      },
        +      "reason": {
        +        "description": "Why the refresh failed",
        +        "title": "Reason",
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "cached_at",
        +      "age_seconds",
        +      "reason"
        +    ],
        +    "title": "StaleDump",
        +    "type": "object"
        +  }
        +}
      • addedOutput schema / properties / stale
        Added value: +{
        +  "anyOf": [
        +    {
        +      "$ref": "#/$defs/StaleDump"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Set only when the dump could not be refreshed and an older cached copy answered instead. The data may miss changes made since `stale.cached_at` β€” say so when relaying it."
        +}
    • Changedlobbywatch_get_parlamentarier2 fields changed
      • addedOutput schema / $defs / StaleDump
        Added value: +{
        +  "description": "The weekly dump could not be refreshed; an older cached copy answered.",
        +  "properties": {
        +    "age_seconds": {
        +      "description": "Age of the answering copy",
        +      "title": "Age Seconds",
        +      "type": "integer"
        +    },
        +    "cached_at": {
        +      "description": "When the answering copy was downloaded (UTC, ISO 8601)",
        +      "title": "Cached At",
        +      "type": "string"
        +    },
        +    "reason": {
        +      "description": "Why the refresh failed",
        +      "title": "Reason",
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "cached_at",
        +    "age_seconds",
        +    "reason"
        +  ],
        +  "title": "StaleDump",
        +  "type": "object"
        +}
      • addedOutput schema / properties / stale
        Added value: +{
        +  "anyOf": [
        +    {
        +      "$ref": "#/$defs/StaleDump"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Set only when the dump could not be refreshed and an older cached copy answered instead. The data may miss changes made since `stale.cached_at` β€” say so when relaying it."
        +}
    • Changedlobbywatch_get_ranking2 fields changed
      • addedOutput schema / $defs / StaleDump
        Added value: +{
        +  "description": "The weekly dump could not be refreshed; an older cached copy answered.",
        +  "properties": {
        +    "age_seconds": {
        +      "description": "Age of the answering copy",
        +      "title": "Age Seconds",
        +      "type": "integer"
        +    },
        +    "cached_at": {
        +      "description": "When the answering copy was downloaded (UTC, ISO 8601)",
        +      "title": "Cached At",
        +      "type": "string"
        +    },
        +    "reason": {
        +      "description": "Why the refresh failed",
        +      "title": "Reason",
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "cached_at",
        +    "age_seconds",
        +    "reason"
        +  ],
        +  "title": "StaleDump",
        +  "type": "object"
        +}
      • addedOutput schema / properties / stale
        Added value: +{
        +  "anyOf": [
        +    {
        +      "$ref": "#/$defs/StaleDump"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Set only when the dump could not be refreshed and an older cached copy answered instead. The data may miss changes made since `stale.cached_at` β€” say so when relaying it."
        +}
    • Changedlobbywatch_get_transparenzquote2 fields changed
      • addedOutput schema / $defs
        Added value: +{
        +  "StaleDump": {
        +    "description": "The weekly dump could not be refreshed; an older cached copy answered.",
        +    "properties": {
        +      "age_seconds": {
        +        "description": "Age of the answering copy",
        +        "title": "Age Seconds",
        +        "type": "integer"
        +      },
        +      "cached_at": {
        +        "description": "When the answering copy was downloaded (UTC, ISO 8601)",
        +        "title": "Cached At",
        +        "type": "string"
        +      },
        +      "reason": {
        +        "description": "Why the refresh failed",
        +        "title": "Reason",
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "cached_at",
        +      "age_seconds",
        +      "reason"
        +    ],
        +    "title": "StaleDump",
        +    "type": "object"
        +  }
        +}
      • addedOutput schema / properties / stale
        Added value: +{
        +  "anyOf": [
        +    {
        +      "$ref": "#/$defs/StaleDump"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Set only when the dump could not be refreshed and an older cached copy answered instead. The data may miss changes made since `stale.cached_at` β€” say so when relaying it."
        +}
    • Changedlobbywatch_list_interessenbindungen2 fields changed
      • addedOutput schema / $defs / StaleDump
        Added value: +{
        +  "description": "The weekly dump could not be refreshed; an older cached copy answered.",
        +  "properties": {
        +    "age_seconds": {
        +      "description": "Age of the answering copy",
        +      "title": "Age Seconds",
        +      "type": "integer"
        +    },
        +    "cached_at": {
        +      "description": "When the answering copy was downloaded (UTC, ISO 8601)",
        +      "title": "Cached At",
        +      "type": "string"
        +    },
        +    "reason": {
        +      "description": "Why the refresh failed",
        +      "title": "Reason",
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "cached_at",
        +    "age_seconds",
        +    "reason"
        +  ],
        +  "title": "StaleDump",
        +  "type": "object"
        +}
      • addedOutput schema / properties / stale
        Added value: +{
        +  "anyOf": [
        +    {
        +      "$ref": "#/$defs/StaleDump"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Set only when the dump could not be refreshed and an older cached copy answered instead. The data may miss changes made since `stale.cached_at` β€” say so when relaying it."
        +}
    • Changedlobbywatch_refresh_dump2 fields changed
      • addedOutput schema / $defs
        Added value: +{
        +  "StaleDump": {
        +    "description": "The weekly dump could not be refreshed; an older cached copy answered.",
        +    "properties": {
        +      "age_seconds": {
        +        "description": "Age of the answering copy",
        +        "title": "Age Seconds",
        +        "type": "integer"
        +      },
        +      "cached_at": {
        +        "description": "When the answering copy was downloaded (UTC, ISO 8601)",
        +        "title": "Cached At",
        +        "type": "string"
        +      },
        +      "reason": {
        +        "description": "Why the refresh failed",
        +        "title": "Reason",
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "cached_at",
        +      "age_seconds",
        +      "reason"
        +    ],
        +    "title": "StaleDump",
        +    "type": "object"
        +  }
        +}
      • addedOutput schema / properties / stale
        Added value: +{
        +  "anyOf": [
        +    {
        +      "$ref": "#/$defs/StaleDump"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Set only when the dump could not be refreshed and an older cached copy answered instead. The data may miss changes made since `stale.cached_at` β€” say so when relaying it."
        +}
    • Changedlobbywatch_search_parlamentarier_nach_branche2 fields changed
      • addedOutput schema / $defs / StaleDump
        Added value: +{
        +  "description": "The weekly dump could not be refreshed; an older cached copy answered.",
        +  "properties": {
        +    "age_seconds": {
        +      "description": "Age of the answering copy",
        +      "title": "Age Seconds",
        +      "type": "integer"
        +    },
        +    "cached_at": {
        +      "description": "When the answering copy was downloaded (UTC, ISO 8601)",
        +      "title": "Cached At",
        +      "type": "string"
        +    },
        +    "reason": {
        +      "description": "Why the refresh failed",
        +      "title": "Reason",
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "cached_at",
        +    "age_seconds",
        +    "reason"
        +  ],
        +  "title": "StaleDump",
        +  "type": "object"
        +}
      • addedOutput schema / properties / stale
        Added value: +{
        +  "anyOf": [
        +    {
        +      "$ref": "#/$defs/StaleDump"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Set only when the dump could not be refreshed and an older cached copy answered instead. The data may miss changes made since `stale.cached_at` β€” say so when relaying it."
        +}
  2. 8 tool updatesv0.3.1
    • First observedlobbywatch_dump_status
    • First observedlobbywatch_get_lobbygruppe
    • First observedlobbywatch_get_parlamentarier
    • First observedlobbywatch_get_ranking
    • First observedlobbywatch_get_transparenzquote
    • First observedlobbywatch_list_interessenbindungen
    • First observedlobbywatch_refresh_dump
    • First observedlobbywatch_search_parlamentarier_nach_branche

TDQS

A4.1/5.0

Scored across 8 tools

Disambiguation4/5

Most tools target clearly distinct actions (lookup, industry search, ranking, transparency, lobby group, cache ops). However, get_parlamentarier returns the full profile including interessenbindungen, which overlaps with list_interessenbindungen; the descriptions clarify that the latter adds filtering, so the boundary is workable but not perfectly sharp.

Naming Consistency5/5

All tools share the lobbywatch_ prefix and use snake_case with a mostly verb_noun pattern (get_parlamentarier, list_interessenbindungen, get_ranking, refresh_dump). dump_status is a minor noun-only deviation but still fully consistent in style.

Tool Count5/5

Eight tools is well-scoped for a domain-specific lobbying data server. Each tool earns its place, including the two cache-management tools that are justified by the dump-backed architecture.

Completeness4/5

The surface covers lookup, IB listing, industry/commission cross-filtering, rankings, transparency aggregates, lobby groups, and cache lifecycle. Minor gaps exist: no direct organisation or commission listing, and branche search is only a substring match pending v0.2, but core workflows are covered.

Maintenance

ActivityActive
ResponsivenessUnresponsive

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    A
    maintenance
    An MCP server connecting AI models to the Swiss Federal Parliament via the Curia Vista OData API, enabling queries of motions, votes, members, sessions, and debate transcripts without authentication.
    7
    42 PyPI
    2
    MIT
  • A
    license
    A
    quality
    A
    maintenance
    An MCP server for Swiss Federal Railways (SBB) open data, enabling AI models to query real-time disruptions, passenger frequencies, station details, and more without an API key.
    9
    31 PyPI
    2
    MIT