canada-parliament-mcp-server
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 |
| Search a session's official bills by title, number, chamber, type, status, or indexed MP sponsor |
| Read an official bill dossier: sponsor and title, stages, reading milestones, divisions, publications, legislative summary, notes, web references, and enacted statute citation |
| Search the current House roster, a past Parliament's official roster, or OpenParliament's historical name index |
| Read a member profile, dated memberships, sponsored bills, and recent ballots |
| Search recorded House divisions by bill, result, document type, date, or subject |
| Read a division's official tally, party totals, and individual recorded ballots |
| Find indexed House speeches by speaker, bill debated or mentioned, date, and literal English or French text |
| Discover actual session coverage and source-specific filter vocabulary |
Related MCP server: openparliament-ca
Capability reference
canparl_search_bills tool
titleContains(matching ignores case, accents, dash and quote styles, and repeated whitespace),billCode,originatingChamber,billType,status, andsponsor, combined with AND;limit1–100 (default 10; values up to 10,000 clamp to 100 with a notice) with offset paginationBilingual titles, status, latest major stage, and royal assent date, with the exact filtered
totalandpagination.nextOffsetWhole bill rows stop early at the 24,000-byte response budget; follow
pagination.nextOffsetwith identical filters. An oversized single row returns whole with a noticesponsortakes 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 usedSponsor 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, andstatuteAlso the Library of Parliament legislative
summaryand dossiernotesas plain text (each note link kept astext (href)), the six readingmilestonesas UTC instants converted from LEGISinfo's Ottawa-time clocks, and relatedwebReferences(press releases, background documents, costing notes) whose URLs are relayed as published, never fetchedAbove 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.nextOffsetPages 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 (matching ignores case, accents, dash and quote styles, and repeated whitespace),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 filteredtotalparliament(36th onward) searches that Parliament's official roster with the same filters: everyone who sat in it, one row each with the caucus and riding last held there, the membershipfromandtodates, andcurrent. An unoffered Parliament fails listing the ones offered; it is rejected withincludeFormerincludeFormer: truesearches OpenParliament's historical name index instead: one token matches a surname, several match a full recorded name. Parliament, geographic, and party filters are rejected there, rows may carry only a name and slug, andtotalisnullPass
detailLookupunchanged tocanparl_get_member: official roster rows carry an officialpersonId, historical rows an OpenParliamentslugWhole 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. A former member'spersonId(36th Parliament onward, as oncanparl_get_voteballots) resolves on the all-Parliaments roster withcurrent: false, carrying their latest Parliament's caucus and riding; slug inputs stay current-roster onlypersonIdcrosswalks 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 slugsponsoredBills(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 noticeWith 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 ballotsEach activity section reports
available,unavailable, ornot_requested, withfailuremetadata; unavailable history is never reported as emptyProfile
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(matching ignores case, accents, dash and quote styles, and repeated whitespace);limit1–100 (default 25; values up to 10,000 clamp to 100 with a notice) with offset paginationNewest first, with official tallies and the exact filtered
total; passsessionandnumbertocanparl_get_voteWhole 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 countBallots are
Yea,Nay, orPaired; nonvoters are not listed, so apersonIdwith no ballot returns an empty pageA 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 substring of the selectedlanguage(endefault, orfr; matching ignores case, accents, dash and quote styles, and repeated whitespace) 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 wholepoliticiantakes the same slug, path, URL, or currentpersonIdvalues ascanparl_search_billssponsor, resolved the same way;appliedFilters.politicianNamenames the profile usedScans 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 onlyComplete
content,speaker,h1, andh2in the selectedlanguage(nullwhere that language has none, never the other language's text; older records carry no French), with speech-date party and riding in English (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;partiestakes only the current session
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 1994Bills cover both chambers; member, division, and speech tools cover the House of Commons. Missing historical fields stay
nullSessions accept
"45-1"or{ "parliament": 45, "session": 1 }; an omitted session resolves from the current official bill feed, except in speech bill scopesShared 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 retrievalExplicit coverage: normalized
appliedFilters, continuation fields, notices, and typed failure reasons separate no matches from unavailable or incomplete dataAdvertised input formats: dates, sessions, person IDs, bill codes, and bill sections carry a
patternin the tool schema that accepts what the tool accepts, padded values included. A value outside the format fails at argument validation (-32602,invalid_arguments) naming the field and the expected form; impossible dates, reversed bounds, out-of-range session numbers, and unknown vocabulary values keep their typed tool reasonsBoth 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/mcpOr run it locally:
claude mcp add canada-parliament-mcp-server -- npx -y @cyanheads/canada-parliament-mcp-serverFor 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 |
| HTTP User-Agent sent to the upstream sources. Blank uses the default; CR and LF are rejected. |
|
| Transport: |
|
| HTTP bind address. |
|
| HTTP server port. |
|
| HTTP session mode: |
|
| Authentication: |
|
| Log level ( |
|
| Directory for log files (Node.js only). |
|
| Storage backend: |
|
| Enable OpenTelemetry. |
|
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 .envBuild and run the production version:
# One-time build bun run rebuild # Run the built server bun run start:http # or bun run start:stdioRun 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:latestThe 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 |
| Tool definitions ( |
| LEGISinfo service — session discovery, official bill lists and dossiers. |
| House of Commons service — current and per-Parliament rosters, division, and member-vote XML. |
| OpenParliament service — historical identities, activity, speeches, and memberships. |
| Shared schemas, normalization, reference vocabulary, and request budgets. |
| Server-specific environment variable parsing and validation with Zod. |
| Published parliamentary research guide for agents calling the tools. |
| 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 noticesRegister new tools in the
createApp()arrays insrc/index.tsWrap 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 testLicense
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.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
This server cannot be deployed
Maintenance
Related MCP Connectors
Search MPs and Lords, fetch profiles, synopses, and Westminster constituencies
Access U.S. congressional data (bills, votes, members, committees) via MCP.
Search Swiss parliamentary business, votes, debates, members, and declared interests.
Query SEC EDGAR filings, XBRL financials, and company data via MCP. STDIO or Streamable HTTP.
Related MCP Servers
- AlicenseNot gradedqualityNot gradedmaintenanceProvides access to Canadian federal parliamentary data (debates, bills, MPs, votes, Hansard transcripts) and legal information (case law and legislation through CanLII) for research and analysis.3MIT
- AlicenseNot gradedqualityCmaintenanceProvides access to Canadian parliamentary data including debates, bills, votes, politicians, and committees via the OpenParliament.ca civic-tech mirror.114 npmMIT
- AlicenseNot gradedqualityBmaintenanceEnables access to U.S. congressional data including bills, laws, members, committees, votes, nominations, CRS reports, and the Congressional Record through MCP, with stdio and Streamable HTTP support.585 npm2Apache 2.0
- AlicenseNot gradedqualityBmaintenanceEnables discovery and retrieval of European Parliament procedures, MEPs, plenary votes, documents, speeches, and questions from the Open Data API over STDIO or Streamable HTTP.382 npm1Apache 2.0