About canada-parliament-mcp-server
canada-parliament-mcp-server exposes Canadian federal parliamentary records for research by AI clients and other MCP clients. It provides searchable bills, members, House votes, and Hansard speeches with source provenance and coverage details.
Key features
- 01Search bills and read dossiers
- 02Search current and historical members
- 03Inspect House divisions and ballots
- 04Find indexed Hansard speeches
- 05Report sources and data coverage
Use cases
- 01Trace a bill through legislative stages
- 02Review an MP's memberships and votes
- 03Compare recorded House division results
- 04Find speeches related to a bill
MCP support
The MCP server exposes eight tools for searching and retrieving bills, members, votes, speeches, and reference data. It supports stdio and Streamable HTTP transports.
Configuration
Remote servers need no local install. Pick your client, then copy the config or install in one click.
https://canada-parliament.caseyjhand.com/mcpclaude mcp add --transport http cyanheads-canada-parliament https://canada-parliament.caseyjhand.com/mcp{"mcpServers":{"cyanheads-canada-parliament":{"type":"http","url":"https://canada-parliament.caseyjhand.com/mcp"}}}Open in Cursor{"servers":{"cyanheads-canada-parliament":{"type":"http","url":"https://canada-parliament.caseyjhand.com/mcp"}}}Open in VS Code{"mcpServers":{"cyanheads-canada-parliament":{"type":"http","url":"https://canada-parliament.caseyjhand.com/mcp"}}}{"mcpServers":{"cyanheads-canada-parliament":{"serverUrl":"https://canada-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
8| Parameter | Type | Required | Description |
|---|---|---|---|
| limit | integer | No | Maximum returned rows, 1–100 per call; larger values up to 10000 return 100 rows with a notice. |
| offset | integer | No | Zero-based offset in the selected ordering. |
| status | string | No | Official bill status from canparl_list_reference topic bill_statuses. |
| session | any | No | Parliamentary session; omitted means current. |
| sponsor | string | No | MP OpenParliament slug or politician path/HTTPS URL (member.slug from canparl_get_member, or an includeFormer canparl_search_members row), or a current MP's official personId; Senators are not indexed. A digit-only value equal to a current personId, or its /politicians/<digits>/ path or profile URL, is read as that personId. |
| billCode | string | No | Exact bill code, including a split-bill suffix letter such as C-15A; accepts bill s321, S321, S 321, or S-321. |
| billType | string | No | Official bill type from canparl_list_reference topic bill_types. |
| titleContains | string | No | Literal case-insensitive substring of any English or French long or short title. |
| originatingChamber | string | No | House, House of Commons, or Senate; case-insensitive. |
Details
From the project README@cyanheads/canada-parliament-mcp-server
Track Canadian federal bills, MPs, House votes, and Hansard speeches via MCP. STDIO or Streamable HTTP.
Public Hosted Server: https://canada-parliament.caseyjhand.com/mcp
Overview
Canadian federal parliamentary records from LEGISinfo, the House of Commons, and OpenParliament. Follow House and Senate bills through their stages, find current and former MPs, inspect recorded House divisions, and read indexed House Hansard speeches. Public sources require no API key.
Tools
| Tool | Description |
|---|---|
canparl_search_bills | Search a session's official bills by title, number, chamber, type, status, or indexed MP sponsor |
canparl_get_bill | Read an official bill dossier: sponsor and title, stages, reading milestones, divisions, publications, legislative summary, notes, web references, and enacted statute citation |
canparl_search_members | Search the current House roster or OpenParliament's historical name index |
canparl_get_member | Read a member profile, dated memberships, sponsored bills, and recent ballots |
canparl_search_votes | Search recorded House divisions by bill, result, document type, date, or subject |
canparl_get_vote | Read a division's official tally, party totals, and individual recorded ballots |
canparl_search_speeches | Find indexed House speeches by speaker, bill debated or mentioned, date, and literal English text |
canparl_list_reference | Discover actual session coverage and source-specific filter vocabulary |
Capability reference
canparl_search_bills tool
titleContains,billCode,originatingChamber,billType,status, andsponsor, combined with AND;limit1–100 (default 25; values up to 10,000 clamp to 100 with a notice) with offset pagination- Bilingual titles, status, latest major stage, and royal assent date, with the exact filtered
totalandpagination.nextOffset - Whole bill rows stop early at the 24,000-byte response budget; follow
pagination.nextOffsetwith identical filters. An oversized single row returns whole with a notice sponsortakes an OpenParliament slug,/politicians/path, or profile URL (member.slugfromcanparl_get_member, or anincludeFormersearch row), or a current MP'spersonId. A digit-only value equal to a currentpersonId, or its/politicians/<digits>/path or URL, resolves through the same verified crosswalk ascanparl_get_member;appliedFilters.sponsorNamenames the profile used- Sponsor results cover only OpenParliament's index (
sponsorCoverage: "openparliament_index"), which excludes Senators; a scan past ten 100-row pages fails assponsor_scan_incomplete
canparl_get_bill tool
billCodeand itssessionfromcanparl_search_bills; returns the sponsor and their affiliationtitle, House/Senate/royal-assent stages, House divisions (dateTimeis the LEGISinfo sitting date-time, not when the division was recorded), Senate vote records, publications, andstatute- Also the Library of Parliament legislative
summaryand dossiernotesas plain text (each note link kept astext (href)), the six readingmilestonesas UTC instants, and relatedwebReferences(press releases, background documents, costing notes) whose URLs are relayed as published, never fetched - Above a 24,000-byte response budget, returns
kind: "outline"withsponsor,statute, andmilestonesinline, listing the sectionssponsor,stages.house,stages.senate,stages.royalAssent,divisions,senateVoteDetails,publications,statute,summary.en,summary.fr,notes.en,notes.fr,milestones, andwebReferences; re-call withsection, paging collections byoffsetandlimit(1–100, default 20; values up to 10,000 clamp to 100 with a notice) throughpagination.nextOffset - Pages hold whole records; an indivisible record or language section over the budget returns whole with
disclosure, and null, empty, and past-end sections stay distinct
canparl_search_members tool
- Current roster:
nameandridingsubstrings,province(postal code or full name), andparty;limit1–100 (default 25; values up to 10,000 clamp to 100 with a notice) with the exact filteredtotal includeFormer: truesearches OpenParliament's historical name index instead: one token matches a surname, several match a full recorded name. Geographic and party filters are rejected there, rows may carry only a name and slug, andtotalisnull- Pass
detailLookupunchanged tocanparl_get_member: current rows carry an officialpersonId, historical rows an OpenParliamentslug - Whole member rows stop early at the 24,000-byte response budget; follow
pagination.nextOffsetwith identical filters. An oversized single row returns whole with a notice
canparl_get_member tool
- Exactly one
personIdorslugfromdetailLookup; returns the profile and datedmemberships. A digit-onlyslugequal to a currentpersonId, or its/politicians/<digits>/path or URL, is read as thatpersonId - Current-person crosswalks verify the official ID and a recorded name or published alias; a bounded surname lookup after an empty exact-name page accommodates accents and middle initials without guessing a slug
sponsoredBills(0–5, default 5) andrecentVotes(0–10, default 10) page independently throughbillsOffsetandvotesOffset;0skips a section, and values up to 10,000 clamp to the cap with a notice- With one official
personId, ballots come newest first from the official member-votes feed in one request, for the member's latest session orvotesSession, optionally narrowed byvotesBillCode.ballotreadsYes/No/Paired,officialBallotkeepsYea/Nay/Paired, andsourcenames the feed; other members keep OpenParliament ballots - Each activity section reports
available,unavailable, ornot_requested, withfailuremetadata; unavailable history is never reported as empty - Profile
linksare never fetched: an absolutehttporhttpsURL is relayed as published, and a relative value is resolved againsthttps://openparliament.ca
canparl_search_votes tool
billCode,result(Agreed To,Negatived,Tie),documentType, inclusive Ottawa-datedateFromanddateTo, andsubjectContains;limit1–100 (default 25; values up to 10,000 clamp to 100 with a notice) with offset pagination- Newest first, with official tallies and the exact filtered
total; passsessionandnumbertocanparl_get_vote - Whole division rows stop early at the 24,000-byte response budget; follow
pagination.nextOffsetwith identical filters. An oversized single row returns whole with a notice
canparl_get_vote tool
numberand itssession;ballot(Yea,Nay,Paired;Yes/Noaccepted),personId,party, andprovincefilter only the ballot page;limit1–400 (default 30; values up to 10,000 clamp to 400 with a notice) with offset paginationtally,byParty, andballotsTotalcover the whole division andballotsMatchedthe filtered set;tallyMatchesBallotsflags a disagreement without changing either count- Ballots are
Yea,Nay, orPaired; nonvoters are not listed, so apersonIdwith no ballot returns an empty page - A page stops at a 24,000-byte response budget with
truncatedand a notice; follownextOffsetfor the rest
canparl_search_speeches tool
- Needs a scope:
politician,billDebatedormentionedBill({ session, code }),dateFrom, ordateTo;containsis a literal English substring and never a scope on its own.limit1–10 (default 5; values up to 10,000 clamp to 10 with a notice), and a page stops at a 24,000-byte response budget, returning at least one speech whole politiciantakes the same slug, path, URL, or currentpersonIdvalues ascanparl_search_billssponsor, resolved the same way;appliedFilters.politicianNamenames the profile used- Scans at most 100 raw candidates per call: follow
nextOffsetwith identical filters untilexhaustedbefore concluding there is no match, and even then the answer covers OpenParliament's index only - Complete English
contentwith speech-date party and riding (nullwhen unavailable);substantiveOnly(defaulttrue) drops procedural remarks, and committee records are excluded and counted inexcludedCommittees
canparl_list_reference tool
topic:sessions,bill_types,bill_statuses,vote_results,vote_document_types, orparties; returns the complete set without paginationsessionstakes nosessionand reportsbasicorfullbill coverage and House division coverage per session; other topics take a session and mark each valueobservedin its feed
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.
Canadian Parliament-specific:
- Official bill coverage from session
35-1(1994), full dossiers from37-1(2001), and House divisions from38-1(2004); OpenParliament's historical name index reaches 1994 - Bills cover both chambers; member, division, and speech tools cover the House of Commons. Missing historical fields stay
null - Sessions accept
"45-1"or{ "parliament": 45, "session": 1 }; an omitted session resolves from the current official bill feed, except in speech bill scopes - Shared source pacing, short-lived in-memory caches, retries, and one 50-second tool deadline. OpenParliament requests are serialized with a two-second start gap, so sponsored bills and OpenParliament ballots can take tens of seconds
- Official-source HTTP 403 refusals fail with
upstream_blocked, the source name, and guidance to retry that source later; response HTML is excluded, and official data is never replaced with another source
Agent-friendly output:
- Provenance:
source,url, originalretrievedAt, andcachedon every successful retrieval - Explicit coverage: normalized
appliedFilters, continuation fields, notices, and typed failure reasons separate no matches from unavailable or incomplete data - Both surfaces: every
structuredContentvalue, complete source text included, also renders in Markdowncontent[]
The Canadian Parliament research guide connects bill, division, member, and Hansard records and explains the evidence needed for historical conclusions. It ships with plugin installs, npm, Docker, and the desktop bundle, and is available over MCP as skill://canada-parliament-guide/SKILL.md.
Getting started
Connect to the hosted server in Claude Code:
claude mcp add --transport http canada-parliament-mcp-server https://canada-parliament.caseyjhand.com/mcp
Or run it locally:
claude mcp add canada-parliament-mcp-server -- npx -y @cyanheads/canada-parliament-mcp-server
For other clients, add this to the MCP config file:
{
"mcpServers": {
"canada-parliament-mcp-server": {
"command": "npx",
"args": ["-y", "@cyanheads/canada-parliament-mcp-server"]
}
}
}
No API key is required. Optionally set CANPARL_USER_AGENT to identify your deployment to the upstream sources. For Docker or a source build, see Running the server.
Configuration
| Variable | Description | Default |
|---|---|---|
CANPARL_USER_AGENT | HTTP User-Agent sent to the upstream sources. Blank uses the default; CR and LF are rejected. | canada-parliament-mcp-server ([email protected]; https://github.com/cyanheads/canada-parliament-mcp-server) |
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_HTTP_HOST | HTTP bind address. | 127.0.0.1 |
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 every server setting and the common framework overrides.
Running the server
Local development
Requires Bun v1.4.0 or higher (or Node.js v24+), and Internet access to parl.ca, ourcommons.ca, and openparliament.ca.
git clone https://github.com/cyanheads/canada-parliament-mcp-server.git
cd canada-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
docker run --rm -p 3010:3010 ghcr.io/cyanheads/canada-parliament-mcp-server:latest
The image defaults to HTTP transport, stateless sessions, and logs in /var/log/canada-parliament-mcp-server. To run it over stdio from an MCP client, use "command": "docker" with "args": ["run", "-i", "--rm", "-e", "MCP_TRANSPORT_TYPE=stdio", "ghcr.io/cyanheads/canada-parliament-mcp-server:latest"]. Build locally with docker build -t canada-parliament-mcp-server .; OpenTelemetry peers are installed by default, or omitted with --build-arg OTEL_ENABLED=false.
Project structure
| Directory | Purpose |
|---|---|
src/mcp-server/tools/definitions | Tool definitions (*.tool.ts), the shared Markdown renderer, and the shared member-identity crosswalk. |
src/services/legisinfo | LEGISinfo service — session discovery, official bill lists and dossiers. |
src/services/ourcommons | House of Commons service — current roster, division, and member-vote XML. |
src/services/openparliament | OpenParliament service — historical identities, activity, speeches, and memberships. |
src/domain | Shared schemas, normalization, reference vocabulary, and request budgets. |
src/config | Server-specific environment variable parsing and validation with Zod. |
skills/ | Published parliamentary research guide for agents calling the tools. |
tests/ | Parser, service, tool, and integration tests over synthetic upstream fixtures. |
See docs/tree.md for the full tree.
Development guide
See CLAUDE.md for development guidelines and architectural rules. The short version:
- Handlers throw, framework catches; catch only to translate a known source failure or keep optional enrichment
- Use
ctx.logfor logging andctx.enrichfor applied filters, totals, and notices - Register new tools 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
Code is licensed under Apache 2.0. Parliamentary data is an unofficial reproduction credited to Parliament and, where used, OpenParliament; Parliament retains copyright. Reuse is governed separately by the Parliament notices, the Speaker's Permission, the Senate terms, and the OpenParliament API terms. This free, noncommercial relay does not imply government endorsement or an open-data licence.
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.