mcpField
S
switzerland-parliament-mcp-server

switzerland-parliament-mcp-server

Remote
github.com/cyanheadsLegal & Government1Updated 3d ago
Repository

About 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

  1. 01Search business by title, submitter, and status
  2. 02Read business texts and procedural history
  3. 03Retrieve Nationalrat roll-call votes
  4. 04Search debate passages and original text
  5. 05Find members and declared interests
  6. 06Browse identifier, language, and session references

Use cases

  1. 01Follow the procedural history of parliamentary business
  2. 02Inspect Nationalrat votes and member decisions
  3. 03Find debate contributions by member or subject
  4. 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.

Endpointhttps://switzerland-parliament.caseyjhand.com/mcp
Copy install command
Claude CodeTerminal
Cursor.cursor/mcp.json
VS Code.vscode/mcp.json
Claude Desktopclaude_desktop_config.json
Windsurf~/.codeium/windsurf/mcp_config.json

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.

Packages
npm@cyanheads/switzerland-parliament-mcp-server0.1.2

Tools

7
ParameterTypeRequiredDescription
limitintegerNoPage request, default 20; clamped to 100. Committee pages count candidate roles, and byte fitting may return fewer businesses.
querystringNoOne title substring, preserving punctuation and diacritics.
authorstringNoSubstring of the published submitter label, not every co-signatory.
cursorstringNoOpaque nextCursor from a previous call with identical filters; omit to start.
typeIdstringNoBusiness type code from chparl_list_reference topic business_types.
languageenumNoMetadata language: de (default), fr, or it.de | fr | it
statusIdstringNoCurrent status code from chparl_list_reference topic statuses.
sessionIdstringNoSubmission session ID from chparl_list_reference topic sessions.
committeeIdstringNoCommittee number from chparl_list_reference topic committees; matches any published committee role.
submittedTostringNoInclusive submission-date upper bound, YYYY-MM-DD.
submittedFromstringNoInclusive 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.

7 Tools • 0 Resources • 0 Prompts

Version License Docker MCP SDK npm TypeScript Bun

Install in Claude Desktop Install in Cursor Install in VS Code

Framework


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
ToolDescription
chparl_search_businessFind business by title, submitter, type, status, submission session, dates, or committee role
chparl_get_businessRead business metadata, original texts, status history, bills, related business, committee consultations, or debate subjects
chparl_get_votesFind Nationalrat roll calls, page member decisions, or read complete vote and parliamentary-group counts
chparl_search_transcriptsFind debate passages by business, subject, speaker, session, council, or dates; retrieve original text
chparl_search_membersFind current or former parliamentary affiliation records by name, council, canton, party, or group
chparl_get_memberRead parliamentary identity, council terms, committee memberships, or declared interests
chparl_list_referenceResolve identifiers, coverage, languages, codes, committees, and sessions

Capability reference

chparl_search_business tool
  • query matches one title substring; author matches the published submitter label, not every co-signatory. Optional typeId, statusId, sessionId, committeeId, submittedFrom, and submittedTo refine it; dates are inclusive and session means submission session
  • Newest canonical business IDs first, default 20/max 100 per page. Continue with nextCursor and unchanged filters; a mismatched cursor fails as invalid_cursor
  • Committee searches examine role candidates and return distinct businesses, so pages can be short and omit totalCount. Without a committee filter, totalCount is the original-filter business count when supplied
  • Pass a returned businessId to chparl_get_business, chparl_get_votes, or chparl_search_transcripts

chparl_get_business tool
  • Accepts a canonical eight-digit businessId or exact published short number, such as 99.1234; ambiguous short numbers fail as ambiguous_business_number. overview (default) returns metadata and availableTextFields with UTF-8 sizes
  • section: "texts" returns textDocument.kind: "full" | "outline". Automatic disclosure becomes a field-size outline when several populated fields make the complete result reach 24,000 bytes; explicit textFields selections or one populated field return whole, even above that threshold
  • Select description, initialSituation, proceedings, draftText, submittedText, reasonText, documentationText, motionText, federalCouncilResponseText, or federalCouncilProposalText. Unpublished selections appear in unavailableTextFields; original HTML, whitespace, and line endings stay intact
  • status_history, bills, related_business, preconsultations, and subjects page independently: default 20/max 100 with nextOffset. Paging belongs only to these sections; textFields belongs only to texts
  • Missing business returns found: false. Linked bills are parliamentary records, not enacted-law text; use subjects to narrow a large debate

chparl_get_votes tool
  • Start with mode: "votes" and businessId for roll-call metadata, default 10/max 50 per page. Then use a returned voteId with mode: "members" (default 50/max 100, optional personNumber) or mode: "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
  • members returns decisionCounts.scope: "page"; groups reads all published member rows before returning scope: "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: false distinguishes 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 exact transcriptId; council alone is insufficient
  • query searches one original-text substring and additionally requires sessionId or a closed dateFrom/dateTo range of at most 31 inclusive calendar days
  • Searches return metadata and textSizeBytes by default, default 20/max 100 per page. includeText: true returns whole passages, default 3/max 10. Continue with nextOffset and unchanged filters
  • Exact transcriptId defaults to full text and accepts only language and includeText alongside it; explicit includeText: false returns metadata. Transcript and subject IDs stay decimal strings
  • businessId and subjectId are mutually exclusive. A business search covers every associated subject up to 20; business_scope_too_broad directs larger debates through chparl_get_business section subjects, then one subjectId
  • metadataLanguage never translates the speaker's text; absent languageOfText stays unknown. found identifies exact-ID misses; scopeFound distinguishes a missing business from one with no passages

chparl_search_members tool
  • query matches a published first, last, or official name substring. membership is current (default), former, or all; optional council, cantonId, partyId, and groupId refine it
  • Results are MemberCouncil affiliation records, not deduplicated people. Default 20/max 100 per page, continued with nextOffset
  • Use personNumber for profiles and member-decision filters; it need not equal Person.ID. Council selectors accept N/S and 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, and interests page independently, default 20/max 100 with nextOffset; omit paging for overview
  • Committee rows are published memberships, not a complete historical committee ledger. A missing paid flag means unknown, and empty interests do not establish absence of conflicts of interest
  • Missing identity returns found: false with discovery guidance

chparl_list_reference tool
  • Topics: identifiers, coverage, languages, business_types, statuses, councils, cantons, parties, groups, committees, and sessions. Use returned IDs in the other tools
  • Default 50/max 100 per page, continued with nextOffset. Only sessions accepts dates: dateFrom bounds session start, dateTo bounds session end, both inclusively
  • statuses is the official legacy/current vocabulary snapshot effective 1 July 2023, carrying era; 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, and it metadata 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[] and structuredContent carry 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, effectiveLimit where paged, hasMore, and a continuation when available; totalCount appears 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_limited with 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
  1. Clone the repository:
git clone https://github.com/cyanheads/switzerland-parliament-mcp-server.git
  1. Navigate into the directory:
cd switzerland-parliament-mcp-server
  1. Install dependencies:
bun install
  1. Configure environment:
cp .env.example .env
# Optional: edit framework transport, auth, logging, or telemetry settings

Configuration

No server-specific environment variables are required.

VariableDescriptionDefault
MCP_TRANSPORT_TYPETransport: stdio or http.stdio
MCP_HTTP_PORTHTTP server port.3010
MCP_SESSION_MODEHTTP session mode: stateless, stateful, or auto.stateless
MCP_AUTH_MODEAuthentication: none, jwt, or oauth.none
MCP_LOG_LEVELLog level (debug, info, warning, error, etc.).info
LOGS_DIRDirectory for log files (Node.js only).<app-root>/logs
STORAGE_PROVIDER_TYPEStorage backend: in-memory, filesystem, supabase, cloudflare-kv/r2/d1.in-memory
OTEL_ENABLEDEnable 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

DirectoryPurpose
src/index.tscreateApp() entry point and service setup; seven tools, no resources or prompts.
src/mcp-server/toolsTool definitions, shared lossless rendering, and whole-record page fitting.
src/services/parliamentOData 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/catch in tool logic
  • Use ctx.log for logging, ctx.state for storage
  • Register new tools and resources in the createApp() arrays in src/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

Markdown supported

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.