Search Swiss parliamentary business, votes, debates, members, and declared interests.
One address for any MCP client. No sign-in needed on this server.
7 read
Grouped by tool name. At most: only reads or looks up information.
Odel charges nothing per call. The publisher may charge for its own service.
Search Swiss parliamentary business, votes, debates, members, and declared interests via MCP. STDIO or Streamable HTTP.
Public Hosted Server: https://switzerland-parliament.caseyjhand.com/mcp
Swiss Federal Assembly records from Curia Vista and the Amtliches Bulletin. Find parliamentary business, read its original texts and procedural history, follow the debate in either council, inspect Nationalrat roll calls, and look up members and declared interests. Uses the public OData service without an API key; runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
Results credit Parlamentsdienste der Bundesversammlung, Bern, with source URLs and download dates. This is an unofficial reproduction. The Parliament's open-data conditions apply to source content; underlying author rights remain intact.
| Tool | Description |
|---|---|
chparl_search_business | Find business by title, submitter, type, status, submission session, dates, or committee role |
chparl_get_business | Read business metadata, original texts, status history, bills, related business, committee consultations, or debate subjects |
chparl_get_votes | Find Nationalrat roll calls, page member decisions, or read complete vote and parliamentary-group counts |
chparl_search_transcripts | Find debate passages by business, subject, speaker, session, council, or dates; retrieve original text |
chparl_search_members | Find current or former parliamentary affiliation records by name, council, canton, party, or group |
chparl_get_member | Read parliamentary identity, council terms, committee memberships, or declared interests |
chparl_list_reference | Resolve identifiers, coverage, languages, codes, committees, and sessions |
chparl_search_business toolquery (title substring), author (published submitter label, not every co-signatory), typeId, statusId, sessionId (submission session), committeeId, and inclusive submittedFrom / submittedTo. Newest business IDs first, default 20/max 100nextCursor and unchanged filters; a cursor from other filters fails as invalid_cursor. totalCount is reported only without author or committee filters. Author searches use an extra matching row to establish continuation; committee searches page role candidates, so pages can be shortchparl_get_business toolbusinessId or an exact published short number such as 99.1234. A short number matching several businesses fails as ambiguous_business_number, and a miss returns found: falseoverview (default) returns metadata and availableTextFields with UTF-8 sizes. texts returns textDocument.kind: "full" | "outline": several large fields become an outline, and named textFields always return wholestatus_history, bills, related_business, preconsultations, and subjects page independently, default 20/max 100 with nextOffset. subjects IDs narrow chparl_search_transcripts; linked bills are parliamentary records, not enacted lawchparl_get_votes toolmode: "votes" with businessId, sessionId, or both lists roll calls (default 10/max 50). A returned voteId feeds mode: "members" (default 50/max 100, optional personNumber) or mode: "groups" (unpaged). Paged modes continue with nextOffsetmode: "members" with personNumber and no voteId pages that member's voting history, optionally narrowed by businessId and sessionId. Each history row carries its published vote, business, session, and time fields; selected-vote pages carry that context once in vote. cantonId uses the canton reference's numbering; votingCantonId preserves the source Voting identifiermembers counts carry decisionCounts.scope: "page". groups reads every published member row and returns scope: "vote", complete: true, and per-group totals by the affiliation recorded on the vote. Count buckets retain every distinct published label in decisionTexts. Decision codes: 1 yes, 2 no, 3 abstention, 4 present, 5 did not participate, 6 excused under Art. 57 para. 4, 7 president does not vote, 8 resignedchparl_search_transcripts toolbusinessId or subjectId (exclusive), personNumber, sessionId, or a date bound; council only narrows. query (original-text substring) also needs sessionId or a closed range of at most 31 calendar daystextSizeBytes by default (20/max 100); includeText: true returns whole passages (3/max 10); an exact transcriptId defaults to full text. Continue with nextOffsetbusiness_scope_too_broad, so page chparl_get_business section subjects and pass one subjectId. found marks exact-ID misses, scopeFound a missing businesschparl_search_members toolquery (first, last, or official name substring), membership (current default, former, all), council (N/S or the German, French, or Italian council name), cantonId, partyId, groupId. Default 20/max 100, nextOffsetpersonNumber, which need not equal Person.ID, feeds chparl_get_member, chparl_get_votes, and chparl_search_transcriptschparl_get_member toolpersonNumber. overview (default) returns identity and every MemberCouncil affiliation, unpaged; a miss returns found: falsecouncil_terms, committees, and interests page independently, default 20/max 100 with nextOffset. Committee rows are published memberships, not a historical ledger. A missing paid flag means unknown, and empty interests do not establish the absence of conflictschparl_list_reference toolidentifiers, coverage, languages, and statuses (the official vocabulary effective 1 July 2023, each code tagged era: "legacy" | "current"). Live dictionaries business_types, councils, cantons, parties, groups, committees, and sessionsnextOffset. Only sessions accepts dates: dateFrom bounds the start and dateTo the end, inclusivelyBuilt on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.
Swiss Parliament-specific:
de (default), fr, and it metadata with no translation fallback; Italian completeness is not established. Bulletin text stays in the language the speaker usedAgent-friendly output:
returnedCount, hasMore, totalCount when known, and the continuationcontent[] carries every structuredContent field, with multiline text and HTML in JSON-encoded fenced blocks that decode back losslesslyupstream_rate_limitedConnect to the hosted instance, no install:
claude mcp add --transport http switzerland-parliament-mcp-server https://switzerland-parliament.caseyjhand.com/mcp
Or run it locally:
claude mcp add switzerland-parliament-mcp-server -- npx -y @cyanheads/switzerland-parliament-mcp-server
For other clients, add this to the MCP config file:
{
"mcpServers": {
"switzerland-parliament-mcp-server": {
"command": "npx",
"args": ["-y", "@cyanheads/switzerland-parliament-mcp-server"]
}
}
}
For Docker or a source build, see Running the server.
No server-specific environment variables are required.
| Variable | Description | Default |
|---|---|---|
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_HTTP_PORT | HTTP server port. | 3010 |
MCP_SESSION_MODE | HTTP session mode: stateless, stateful, or auto. | stateless |
MCP_AUTH_MODE | Authentication: none, jwt, or oauth. | none |
MCP_LOG_LEVEL | Log level (debug, info, warning, error, etc.). | info |
LOGS_DIR | Directory for log files (Node.js only). | <app-root>/logs |
STORAGE_PROVIDER_TYPE | Storage backend: in-memory, filesystem, supabase, cloudflare-kv/r2/d1. | in-memory |
OTEL_ENABLED | Enable OpenTelemetry. | false |
See .env.example for the common framework overrides.
Requires Bun v1.4.0 or higher (or Node.js v24+).
git clone https://github.com/cyanheads/switzerland-parliament-mcp-server.git
cd switzerland-parliament-mcp-server
bun install
cp .env.example .env
Build and run the production version:
# One-time build
bun run rebuild
# Run the built server
bun run start:http
# or
bun run start:stdio
Run checks and tests:
bun run devcheck # Lints, formats, type-checks, and more
bun run test # Runs the test suite
docker run --rm -p 3010:3010 ghcr.io/cyanheads/switzerland-parliament-mcp-server:latest
The image defaults to HTTP transport, stateless sessions, and logs to /var/log/switzerland-parliament-mcp-server. For stdio, use "command": "docker" with "args": ["run", "-i", "--rm", "-e", "MCP_TRANSPORT_TYPE=stdio", "ghcr.io/cyanheads/switzerland-parliament-mcp-server:latest"]. Build locally with docker build -t switzerland-parliament-mcp-server .; --build-arg OTEL_ENABLED=false omits OpenTelemetry peers.
| Directory | Purpose |
|---|---|
src/index.ts | createApp() entry point and service setup; seven tools, no resources or prompts. |
src/mcp-server/tools | Tool definitions, shared lossless rendering, and whole-record page fitting. |
src/services/parliament | OData client, workflow deadlines, domain schemas and services, provenance, and reference snapshots. |
tests/ | Unit and integration tests, mirroring the src/ structure. |
See CLAUDE.md for development guidelines and architectural rules. The short version:
try/catch in tool logicctx.log for logging, ctx.state for storagecreateApp() arrays in src/index.tsIssues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
This project is licensed under the Apache 2.0 License. See the LICENSE file for details.