switzerland-parliament-mcp-server
RemoteAbout switzerland-parliament-mcp-server
An MCP server for public Swiss Federal Assembly records from Curia Vista and the Amtliches Bulletin. It searches parliamentary business, debates, votes, members, and declared interests, with stdio and Streamable HTTP transports.
Key features
- 01Search business by title, submitter, and status
- 02Read business texts and procedural history
- 03Retrieve Nationalrat roll-call votes
- 04Search debate passages and original text
- 05Find members and declared interests
- 06Browse identifier, language, and session references
Use cases
- 01Follow the procedural history of parliamentary business
- 02Inspect Nationalrat votes and member decisions
- 03Find debate contributions by member or subject
- 04Review a member’s committee roles and declared interests
MCP support
Provides MCP clients with seven read-only tools for Swiss parliamentary business, votes, debates, members, and declared interests. Supports stdio and Streamable HTTP.
Configuration
Remote servers need no local install. Pick your client, then copy the config or install in one click.
https://switzerland-parliament.caseyjhand.com/mcpclaude mcp add --transport http cyanheads-switzerland-parliament https://switzerland-parliament.caseyjhand.com/mcp{"mcpServers":{"cyanheads-switzerland-parliament":{"type":"http","url":"https://switzerland-parliament.caseyjhand.com/mcp"}}}Open in Cursor{"servers":{"cyanheads-switzerland-parliament":{"type":"http","url":"https://switzerland-parliament.caseyjhand.com/mcp"}}}Open in VS Code{"mcpServers":{"cyanheads-switzerland-parliament":{"type":"http","url":"https://switzerland-parliament.caseyjhand.com/mcp"}}}{"mcpServers":{"cyanheads-switzerland-parliament":{"serverUrl":"https://switzerland-parliament.caseyjhand.com/mcp"}}}Run commands in a terminal; paste JSON into the client’s MCP config and restart. Servers that need sign-in will prompt on first connect.
Tools
7| Parameter | Type | Required | Description |
|---|---|---|---|
| limit | integer | No | Page request, default 20; clamped to 100. Committee pages count candidate roles, and byte fitting may return fewer businesses. |
| query | string | No | One title substring, preserving punctuation and diacritics. |
| author | string | No | Substring of the published submitter label, not every co-signatory. |
| cursor | string | No | Opaque nextCursor from a previous call with identical filters; omit to start. |
| typeId | string | No | Business type code from chparl_list_reference topic business_types. |
| language | enum | No | Metadata language: de (default), fr, or it.de | fr | it |
| statusId | string | No | Current status code from chparl_list_reference topic statuses. |
| sessionId | string | No | Submission session ID from chparl_list_reference topic sessions. |
| committeeId | string | No | Committee number from chparl_list_reference topic committees; matches any published committee role. |
| submittedTo | string | No | Inclusive submission-date upper bound, YYYY-MM-DD. |
| submittedFrom | string | No | Inclusive submission-date lower bound, YYYY-MM-DD. |
Details
From the project README@cyanheads/switzerland-parliament-mcp-server
Search Swiss parliamentary business, votes, debates, members, and declared interests via MCP. STDIO or Streamable HTTP.
Overview
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 or a local Streamable HTTP server.
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.
Tools
| 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 |
Capability reference
chparl_search_business tool
querymatches one title substring;authormatches the published submitter label, not every co-signatory. OptionaltypeId,statusId,sessionId,committeeId,submittedFrom, andsubmittedTorefine it; dates are inclusive and session means submission session- Newest canonical business IDs first, default 20/max 100 per page. Continue with
nextCursorand unchanged filters; a mismatched cursor fails asinvalid_cursor - Committee searches examine role candidates and return distinct businesses, so pages can be short and omit
totalCount. Without a committee filter,totalCountis the original-filter business count when supplied - Pass a returned
businessIdtochparl_get_business,chparl_get_votes, orchparl_search_transcripts
chparl_get_business tool
- Accepts a canonical eight-digit
businessIdor exact published short number, such as99.1234; ambiguous short numbers fail asambiguous_business_number.overview(default) returns metadata andavailableTextFieldswith UTF-8 sizes section: "texts"returnstextDocument.kind: "full" | "outline". Automatic disclosure becomes a field-size outline when several populated fields make the complete result reach 24,000 bytes; explicittextFieldsselections or one populated field return whole, even above that threshold- Select
description,initialSituation,proceedings,draftText,submittedText,reasonText,documentationText,motionText,federalCouncilResponseText, orfederalCouncilProposalText. Unpublished selections appear inunavailableTextFields; original HTML, whitespace, and line endings stay intact status_history,bills,related_business,preconsultations, andsubjectspage independently: default 20/max 100 withnextOffset. Paging belongs only to these sections;textFieldsbelongs only totexts- Missing business returns
found: false. Linked bills are parliamentary records, not enacted-law text; usesubjectsto narrow a large debate
chparl_get_votes tool
- Start with
mode: "votes"andbusinessIdfor roll-call metadata, default 10/max 50 per page. Then use a returnedvoteIdwithmode: "members"(default 50/max 100, optionalpersonNumber) ormode: "groups"(complete, unpaged) - Member roll calls cover the Nationalrat from the 2003 winter session. Ständerat member decisions are unavailable here; an empty result does not establish that no vote occurred
membersreturnsdecisionCounts.scope: "page";groupsreads all published member rows before returningscope: "vote",complete: true, and group totals. When member data is unavailable, complete zero-vote totals are not inferred- Counts use affiliations published on vote rows, not current memberships. Original decision labels, unknown codes, and missing decisions remain visible; documented codes include 1 yes, 2 no, 3 abstention, 6 excused under Art. 57 para. 4, and 7 president does not vote
- Continue paged modes with
nextOffset;found: falsedistinguishes a missing business or selected vote from an empty child collection
chparl_search_transcripts tool
- Bulletin coverage begins with the 1999 winter session and includes both councils. Supply at least
businessId,subjectId,personNumber,sessionId, a date bound, or exacttranscriptId; council alone is insufficient querysearches one original-text substring and additionally requiressessionIdor a closeddateFrom/dateTorange of at most 31 inclusive calendar days- Searches return metadata and
textSizeBytesby default, default 20/max 100 per page.includeText: truereturns whole passages, default 3/max 10. Continue withnextOffsetand unchanged filters - Exact
transcriptIddefaults to full text and accepts onlylanguageandincludeTextalongside it; explicitincludeText: falsereturns metadata. Transcript and subject IDs stay decimal strings businessIdandsubjectIdare mutually exclusive. A business search covers every associated subject up to 20;business_scope_too_broaddirects larger debates throughchparl_get_businesssectionsubjects, then onesubjectIdmetadataLanguagenever translates the speaker's text; absentlanguageOfTextstays unknown.foundidentifies exact-ID misses;scopeFounddistinguishes a missing business from one with no passages
chparl_search_members tool
querymatches a published first, last, or official name substring.membershipiscurrent(default),former, orall; optionalcouncil,cantonId,partyId, andgroupIdrefine it- Results are MemberCouncil affiliation records, not deduplicated people. Default 20/max 100 per page, continued with
nextOffset - Use
personNumberfor profiles and member-decision filters; it need not equalPerson.ID. Council selectors acceptN/Sand the exact German, French, or Italian council names
chparl_get_member tool
- Requires
personNumber.overview(default) returns parliamentary identity and every base MemberCouncil affiliation without paging council_terms,committees, andinterestspage independently, default 20/max 100 withnextOffset; omit paging foroverview- Committee rows are published memberships, not a complete historical committee ledger. A missing
paidflag means unknown, and empty interests do not establish absence of conflicts of interest - Missing identity returns
found: falsewith discovery guidance
chparl_list_reference tool
- Topics:
identifiers,coverage,languages,business_types,statuses,councils,cantons,parties,groups,committees, andsessions. Use returned IDs in the other tools - Default 50/max 100 per page, continued with
nextOffset. Onlysessionsaccepts dates:dateFrombounds session start,dateTobounds session end, both inclusively statusesis the official legacy/current vocabulary snapshot effective 1 July 2023, carryingera; unknown live codes remain visible. Identifier, coverage, and language explanations are static; other dictionaries are live
Features
Built 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:
- Keyless, read-only access to business, procedural relationships, Nationalrat roll calls, debate passages, members, and declared interests
de(default),fr, anditmetadata without translation fallback. Completeness of every Italian field is not established; English and Romansh translations are not offered- Published text and HTML remain unchanged; an accompanying link/image reference index preserves original attributes and supplies clickable absolute HTTP(S) URLs where available
- One 45-second deadline per complete workflow; narrow text scopes help with slow upstream searches
Agent-friendly output:
- Both
content[]andstructuredContentcarry the same records, provenance, filters, notices, and paging information. Multiline text and HTML are JSON-encoded in fenced text blocks for lossless recovery - Collections report
returnedCount,effectiveLimitwhere paged,hasMore, and a continuation when available;totalCountappears only when known. Offset pages can change as live data updates - Collection replies fit whole records around a 24,000-byte result budget; fitting can shorten a page, never a returned field. An indivisible oversized record is returned whole with a notice; text selections, member overviews, and group totals also remain complete
- Missing values stay unknown; empty results carry guidance, and rate limits return
upstream_rate_limitedwith retry guidance
Getting started
Self-Hosted / Local
Add the following to your MCP client configuration file.
{
"mcpServers": {
"switzerland-parliament-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/switzerland-parliament-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"switzerland-parliament-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/switzerland-parliament-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with Docker:
{
"mcpServers": {
"switzerland-parliament-mcp-server": {
"type": "stdio",
"command": "docker",
"args": ["run", "-i", "--rm", "-e", "MCP_TRANSPORT_TYPE=stdio", "ghcr.io/cyanheads/switzerland-parliament-mcp-server:latest"]
}
}
}
For Streamable HTTP, set the transport and start the server:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
# Server listens at http://localhost:3010/mcp
Prerequisites
- Bun v1.4.0 or higher (or Node.js v24+).
- No API key or account is required.
Installation
- Clone the repository:
git clone https://github.com/cyanheads/switzerland-parliament-mcp-server.git
- Navigate into the directory:
cd switzerland-parliament-mcp-server
- Install dependencies:
bun install
- Configure environment:
cp .env.example .env
# Optional: edit framework transport, auth, logging, or telemetry settings
Configuration
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.
Running the server
Local development
-
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
Project structure
| 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. |
Development guide
See CLAUDE.md for development guidelines and architectural rules. The short version:
- Handlers throw, framework catches — no
try/catchin tool logic - Use
ctx.logfor logging,ctx.statefor storage - Register new tools and resources in the
createApp()arrays insrc/index.ts - Wrap external API calls: validate raw → normalize to domain type → return output schema; never fabricate missing fields
Contributing
Issues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
License
This project is licensed under the Apache 2.0 License. See the LICENSE file for details.
Comments
New to MCP?
From what the protocol is to installing your first server, these answers cover most first questions.
An open protocol that defines how AI applications connect to external tools and data in a uniform way. Clients talk to the model; MCP servers provide tools, resources and prompt templates. They exchange standard messages, so one server can be reused by many clients.