@cyanheads/canada-parliament-mcp-server 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +8 -7
- package/CLAUDE.md +8 -7
- package/README.md +110 -74
- package/changelog/0.1.x/0.1.2.md +37 -0
- package/dist/domain/html-to-text.d.ts +10 -0
- package/dist/domain/html-to-text.d.ts.map +1 -0
- package/dist/domain/html-to-text.js +70 -0
- package/dist/domain/html-to-text.js.map +1 -0
- package/dist/domain/normalization.d.ts +51 -4
- package/dist/domain/normalization.d.ts.map +1 -1
- package/dist/domain/normalization.js +99 -28
- package/dist/domain/normalization.js.map +1 -1
- package/dist/domain/reference-catalog.d.ts +6 -0
- package/dist/domain/reference-catalog.d.ts.map +1 -1
- package/dist/domain/reference-catalog.js +12 -0
- package/dist/domain/reference-catalog.js.map +1 -1
- package/dist/domain/request-context.js +3 -3
- package/dist/domain/request-context.js.map +1 -1
- package/dist/domain/schemas.d.ts +15 -1
- package/dist/domain/schemas.d.ts.map +1 -1
- package/dist/domain/schemas.js +14 -6
- package/dist/domain/schemas.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-server/tools/definitions/_format.d.ts +20 -5
- package/dist/mcp-server/tools/definitions/_format.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/_format.js +138 -22
- package/dist/mcp-server/tools/definitions/_format.js.map +1 -1
- package/dist/mcp-server/tools/definitions/_member-identity.d.ts +22 -0
- package/dist/mcp-server/tools/definitions/_member-identity.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/_member-identity.js +63 -0
- package/dist/mcp-server/tools/definitions/_member-identity.js.map +1 -0
- package/dist/mcp-server/tools/definitions/get-bill.tool.d.ts +44 -3
- package/dist/mcp-server/tools/definitions/get-bill.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/get-bill.tool.js +111 -70
- package/dist/mcp-server/tools/definitions/get-bill.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/get-member.tool.d.ts +32 -1
- package/dist/mcp-server/tools/definitions/get-member.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/get-member.tool.js +220 -137
- package/dist/mcp-server/tools/definitions/get-member.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/get-vote.tool.d.ts +14 -10
- package/dist/mcp-server/tools/definitions/get-vote.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/get-vote.tool.js +137 -80
- package/dist/mcp-server/tools/definitions/get-vote.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/list-reference.tool.d.ts +1 -1
- package/dist/mcp-server/tools/definitions/list-reference.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/list-reference.tool.js +15 -15
- package/dist/mcp-server/tools/definitions/list-reference.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/search-bills.tool.d.ts +10 -6
- package/dist/mcp-server/tools/definitions/search-bills.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/search-bills.tool.js +54 -50
- package/dist/mcp-server/tools/definitions/search-bills.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/search-members.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/search-members.tool.js +25 -18
- package/dist/mcp-server/tools/definitions/search-members.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/search-speeches.tool.d.ts +14 -10
- package/dist/mcp-server/tools/definitions/search-speeches.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/search-speeches.tool.js +288 -163
- package/dist/mcp-server/tools/definitions/search-speeches.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/search-votes.tool.d.ts +2 -2
- package/dist/mcp-server/tools/definitions/search-votes.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/search-votes.tool.js +38 -37
- package/dist/mcp-server/tools/definitions/search-votes.tool.js.map +1 -1
- package/dist/services/legisinfo/index.d.ts +1 -1
- package/dist/services/legisinfo/index.d.ts.map +1 -1
- package/dist/services/legisinfo/legisinfo-service.d.ts +5 -4
- package/dist/services/legisinfo/legisinfo-service.d.ts.map +1 -1
- package/dist/services/legisinfo/legisinfo-service.js +25 -31
- package/dist/services/legisinfo/legisinfo-service.js.map +1 -1
- package/dist/services/legisinfo/parsing.d.ts +5 -2
- package/dist/services/legisinfo/parsing.d.ts.map +1 -1
- package/dist/services/legisinfo/parsing.js +99 -48
- package/dist/services/legisinfo/parsing.js.map +1 -1
- package/dist/services/legisinfo/types.d.ts +29 -16
- package/dist/services/legisinfo/types.d.ts.map +1 -1
- package/dist/services/openparliament/index.d.ts +1 -1
- package/dist/services/openparliament/index.d.ts.map +1 -1
- package/dist/services/openparliament/openparliament-service.d.ts +8 -3
- package/dist/services/openparliament/openparliament-service.d.ts.map +1 -1
- package/dist/services/openparliament/openparliament-service.js +22 -15
- package/dist/services/openparliament/openparliament-service.js.map +1 -1
- package/dist/services/openparliament/parsing.d.ts +0 -2
- package/dist/services/openparliament/parsing.d.ts.map +1 -1
- package/dist/services/openparliament/parsing.js +34 -43
- package/dist/services/openparliament/parsing.js.map +1 -1
- package/dist/services/openparliament/types.d.ts +1 -11
- package/dist/services/openparliament/types.d.ts.map +1 -1
- package/dist/services/ourcommons/index.d.ts +1 -1
- package/dist/services/ourcommons/index.d.ts.map +1 -1
- package/dist/services/ourcommons/ourcommons-service.d.ts +11 -1
- package/dist/services/ourcommons/ourcommons-service.d.ts.map +1 -1
- package/dist/services/ourcommons/ourcommons-service.js +26 -16
- package/dist/services/ourcommons/ourcommons-service.js.map +1 -1
- package/dist/services/ourcommons/parsing.d.ts +7 -1
- package/dist/services/ourcommons/parsing.d.ts.map +1 -1
- package/dist/services/ourcommons/parsing.js +61 -18
- package/dist/services/ourcommons/parsing.js.map +1 -1
- package/dist/services/ourcommons/types.d.ts +14 -7
- package/dist/services/ourcommons/types.d.ts.map +1 -1
- package/dist/services/ourcommons/types.js +1 -0
- package/dist/services/ourcommons/types.js.map +1 -1
- package/package.json +6 -1
- package/server.json +3 -3
package/AGENTS.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Developer Protocol
|
|
2
2
|
|
|
3
3
|
**Server:** canada-parliament-mcp-server
|
|
4
|
-
**Version:** 0.1.
|
|
4
|
+
**Version:** 0.1.2
|
|
5
5
|
**Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.15`
|
|
6
6
|
**Engines:** Bun ≥1.4.0, Node ≥24.0.0
|
|
7
7
|
**MCP SDK:** `@modelcontextprotocol/server` ^2.3.1
|
|
@@ -66,8 +66,8 @@ All definitions use `formatResult` from `_format.ts`, with reversible Markdown v
|
|
|
66
66
|
| Service | Owned data |
|
|
67
67
|
|:---|:---|
|
|
68
68
|
| `legisinfo` | Official sessions, current session, complete session bill lists, dossiers |
|
|
69
|
-
| `ourcommons` | Current House roster, division sessions/lists/participants |
|
|
70
|
-
| `openparliament` | Historical identities, sponsored bills, ballots, speeches, dated memberships |
|
|
69
|
+
| `ourcommons` | Current House roster, division sessions/lists/participants, member votes |
|
|
70
|
+
| `openparliament` | Historical identities, sponsored bills, ballots for members without one official personId, speeches, dated memberships |
|
|
71
71
|
|
|
72
72
|
`setup()` initializes all three services; `teardown()` disposes their pacers and caches. HTTP defaults to stateless. Source caches hold parsed public data only; no durable personal-data store.
|
|
73
73
|
|
|
@@ -78,12 +78,13 @@ Requests use fixed HTTPS origins `www.parl.ca`, `www.ourcommons.ca`, and `api.op
|
|
|
78
78
|
### Domain constraints
|
|
79
79
|
|
|
80
80
|
- Bills cover both chambers; members, divisions, and speeches cover the House only. Early bill sessions have basic coverage and nullable dossier fields.
|
|
81
|
-
- Official `personId` and OpenParliament `slug` are distinct. Use returned `detailLookup` and verified crosswalks, never name-derived slugs.
|
|
81
|
+
- Official `personId` and OpenParliament `slug` are distinct. Use returned `detailLookup` and verified crosswalks, never name-derived slugs. Slug inputs (`slug`, `sponsor`, `politician`) read a digit-only value equal to a current personId, or its `/politicians/<digits>/` path or profile URL, through the shared crosswalk in `_member-identity.ts`, the roster first; every other numeric value is an OpenParliament ID. Never request `/politicians/{personId}/`.
|
|
82
82
|
- Sponsor filtering is the OpenParliament-indexed subset; a bounded incomplete scan fails rather than inventing completeness.
|
|
83
83
|
- Current-member search filters the complete roster. Historical name lookup stays sparse and rejects unsupported geographic/caucus filters.
|
|
84
84
|
- Speech `contains` is a bounded scan with raw-offset continuation and `exhausted`. Hydrate affiliations from dated memberships, never current attributes.
|
|
85
|
-
- Large bill dossiers return an outline
|
|
86
|
-
- Preserve source-specific vote vocabulary and full tallies independently of ballot-page filters.
|
|
85
|
+
- One 24,000-byte complete-response budget and measure (`_format.ts`) bound `canparl_get_bill`, `canparl_get_vote`, and `canparl_search_speeches`; they render every enrichment field through `renderValue` so the measure equals the wire. Large bill dossiers return an outline; pages hold whole records and stop before the one that would exceed the budget, returning at least one; oversized indivisible records return with disclosure.
|
|
86
|
+
- Preserve source-specific vote vocabulary and full tallies independently of ballot-page filters. `canparl_get_member` ballots keep OpenParliament's `Yes`/`No` in `ballot` on every row and carry the official `Yea`/`Nay`/`Paired` in `officialBallot`.
|
|
87
|
+
- Profile links are never fetched: an absolute `http` or `https` URL without credentials is relayed as published, any other non-empty value is resolved against `https://openparliament.ca`, and empty, credentialed, `mailto:`, `javascript:`, `data:`, and `ftp:` values are rejected; only `https://` values render as links.
|
|
87
88
|
- Tests run real parsers/services/handlers through synthetic fake HTTP responses, not mocked domain service methods.
|
|
88
89
|
- Parliamentary data is unofficial and subject to its source reuse terms. Do not imply government endorsement or an open-data licence.
|
|
89
90
|
|
|
@@ -159,7 +160,7 @@ src/
|
|
|
159
160
|
legisinfo/ # index, service, parsing, types
|
|
160
161
|
ourcommons/ # index, service, parsing, types
|
|
161
162
|
openparliament/ # index, service, parsing, types
|
|
162
|
-
mcp-server/tools/definitions/ # eight *.tool.ts definitions and
|
|
163
|
+
mcp-server/tools/definitions/ # eight *.tool.ts definitions, _format.ts, and _member-identity.ts
|
|
163
164
|
tests/
|
|
164
165
|
config/
|
|
165
166
|
domain/
|
package/CLAUDE.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Developer Protocol
|
|
2
2
|
|
|
3
3
|
**Server:** canada-parliament-mcp-server
|
|
4
|
-
**Version:** 0.1.
|
|
4
|
+
**Version:** 0.1.2
|
|
5
5
|
**Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.15`
|
|
6
6
|
**Engines:** Bun ≥1.4.0, Node ≥24.0.0
|
|
7
7
|
**MCP SDK:** `@modelcontextprotocol/server` ^2.3.1
|
|
@@ -66,8 +66,8 @@ All definitions use `formatResult` from `_format.ts`, with reversible Markdown v
|
|
|
66
66
|
| Service | Owned data |
|
|
67
67
|
|:---|:---|
|
|
68
68
|
| `legisinfo` | Official sessions, current session, complete session bill lists, dossiers |
|
|
69
|
-
| `ourcommons` | Current House roster, division sessions/lists/participants |
|
|
70
|
-
| `openparliament` | Historical identities, sponsored bills, ballots, speeches, dated memberships |
|
|
69
|
+
| `ourcommons` | Current House roster, division sessions/lists/participants, member votes |
|
|
70
|
+
| `openparliament` | Historical identities, sponsored bills, ballots for members without one official personId, speeches, dated memberships |
|
|
71
71
|
|
|
72
72
|
`setup()` initializes all three services; `teardown()` disposes their pacers and caches. HTTP defaults to stateless. Source caches hold parsed public data only; no durable personal-data store.
|
|
73
73
|
|
|
@@ -78,12 +78,13 @@ Requests use fixed HTTPS origins `www.parl.ca`, `www.ourcommons.ca`, and `api.op
|
|
|
78
78
|
### Domain constraints
|
|
79
79
|
|
|
80
80
|
- Bills cover both chambers; members, divisions, and speeches cover the House only. Early bill sessions have basic coverage and nullable dossier fields.
|
|
81
|
-
- Official `personId` and OpenParliament `slug` are distinct. Use returned `detailLookup` and verified crosswalks, never name-derived slugs.
|
|
81
|
+
- Official `personId` and OpenParliament `slug` are distinct. Use returned `detailLookup` and verified crosswalks, never name-derived slugs. Slug inputs (`slug`, `sponsor`, `politician`) read a digit-only value equal to a current personId, or its `/politicians/<digits>/` path or profile URL, through the shared crosswalk in `_member-identity.ts`, the roster first; every other numeric value is an OpenParliament ID. Never request `/politicians/{personId}/`.
|
|
82
82
|
- Sponsor filtering is the OpenParliament-indexed subset; a bounded incomplete scan fails rather than inventing completeness.
|
|
83
83
|
- Current-member search filters the complete roster. Historical name lookup stays sparse and rejects unsupported geographic/caucus filters.
|
|
84
84
|
- Speech `contains` is a bounded scan with raw-offset continuation and `exhausted`. Hydrate affiliations from dated memberships, never current attributes.
|
|
85
|
-
- Large bill dossiers return an outline
|
|
86
|
-
- Preserve source-specific vote vocabulary and full tallies independently of ballot-page filters.
|
|
85
|
+
- One 24,000-byte complete-response budget and measure (`_format.ts`) bound `canparl_get_bill`, `canparl_get_vote`, and `canparl_search_speeches`; they render every enrichment field through `renderValue` so the measure equals the wire. Large bill dossiers return an outline; pages hold whole records and stop before the one that would exceed the budget, returning at least one; oversized indivisible records return with disclosure.
|
|
86
|
+
- Preserve source-specific vote vocabulary and full tallies independently of ballot-page filters. `canparl_get_member` ballots keep OpenParliament's `Yes`/`No` in `ballot` on every row and carry the official `Yea`/`Nay`/`Paired` in `officialBallot`.
|
|
87
|
+
- Profile links are never fetched: an absolute `http` or `https` URL without credentials is relayed as published, any other non-empty value is resolved against `https://openparliament.ca`, and empty, credentialed, `mailto:`, `javascript:`, `data:`, and `ftp:` values are rejected; only `https://` values render as links.
|
|
87
88
|
- Tests run real parsers/services/handlers through synthetic fake HTTP responses, not mocked domain service methods.
|
|
88
89
|
- Parliamentary data is unofficial and subject to its source reuse terms. Do not imply government endorsement or an open-data licence.
|
|
89
90
|
|
|
@@ -159,7 +160,7 @@ src/
|
|
|
159
160
|
legisinfo/ # index, service, parsing, types
|
|
160
161
|
ourcommons/ # index, service, parsing, types
|
|
161
162
|
openparliament/ # index, service, parsing, types
|
|
162
|
-
mcp-server/tools/definitions/ # eight *.tool.ts definitions and
|
|
163
|
+
mcp-server/tools/definitions/ # eight *.tool.ts definitions, _format.ts, and _member-identity.ts
|
|
163
164
|
tests/
|
|
164
165
|
config/
|
|
165
166
|
domain/
|
package/README.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
<h1>@cyanheads/canada-parliament-mcp-server</h1>
|
|
3
3
|
<p><b>Track Canadian federal bills, MPs, House votes, and Hansard speeches via MCP. STDIO or Streamable HTTP.</b>
|
|
4
|
-
<div>8 Tools
|
|
4
|
+
<div>8 Tools</div>
|
|
5
5
|
</p>
|
|
6
6
|
</div>
|
|
7
7
|
|
|
8
8
|
<div align="center">
|
|
9
9
|
|
|
10
|
-
[](./CHANGELOG.md) [](./LICENSE) [](https://github.com/users/cyanheads/packages/container/package/canada-parliament-mcp-server) [](https://modelcontextprotocol.io/) [](https://www.npmjs.com/package/@cyanheads/canada-parliament-mcp-server) [](https://www.typescriptlang.org/) [](https://bun.sh/)
|
|
11
11
|
|
|
12
12
|
</div>
|
|
13
13
|
|
|
@@ -30,7 +30,7 @@ Canadian federal parliamentary records from [LEGISinfo](https://www.parl.ca/legi
|
|
|
30
30
|
| Tool | Description |
|
|
31
31
|
|:---|:---|
|
|
32
32
|
| `canparl_search_bills` | Search a session's official bills by title, number, chamber, type, status, or indexed MP sponsor |
|
|
33
|
-
| `canparl_get_bill` | Read an official bill dossier, stages, divisions, publications, and enacted statute citation |
|
|
33
|
+
| `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 |
|
|
34
34
|
| `canparl_search_members` | Search the current House roster or OpenParliament's historical name index |
|
|
35
35
|
| `canparl_get_member` | Read a member profile, dated memberships, sponsored bills, and recent ballots |
|
|
36
36
|
| `canparl_search_votes` | Search recorded House divisions by bill, result, document type, date, or subject |
|
|
@@ -42,63 +42,69 @@ Canadian federal parliamentary records from [LEGISinfo](https://www.parl.ca/legi
|
|
|
42
42
|
|
|
43
43
|
### `canparl_search_bills` <sub>tool</sub>
|
|
44
44
|
|
|
45
|
-
-
|
|
46
|
-
-
|
|
47
|
-
- `sponsor` takes
|
|
45
|
+
- `titleContains`, `billCode`, `originatingChamber`, `billType`, `status`, and `sponsor`, combined with AND; `limit` 1–100 (default 25; values up to 10,000 clamp to 100 with a notice) with offset pagination
|
|
46
|
+
- Bilingual titles, status, latest major stage, and royal assent date, with the exact filtered `total` and `pagination.nextOffset`
|
|
47
|
+
- `sponsor` takes an OpenParliament slug, `/politicians/` path, or profile URL (`member.slug` from `canparl_get_member`, or an `includeFormer` search row), or a current MP's `personId`. A digit-only value equal to a current `personId`, or its `/politicians/<digits>/` path or URL, resolves through the same verified crosswalk as `canparl_get_member`; `appliedFilters.sponsorName` names the profile used
|
|
48
|
+
- Sponsor results cover only OpenParliament's index (`sponsorCoverage: "openparliament_index"`), which excludes Senators; a scan past ten 100-row pages fails as `sponsor_scan_incomplete`
|
|
48
49
|
|
|
49
50
|
---
|
|
50
51
|
|
|
51
52
|
### `canparl_get_bill` <sub>tool</sub>
|
|
52
53
|
|
|
53
|
-
-
|
|
54
|
-
-
|
|
55
|
-
-
|
|
54
|
+
- `billCode` and its `session` from `canparl_search_bills`; returns the sponsor and their affiliation `title`, House/Senate/royal-assent stages, House divisions (`dateTime` is the LEGISinfo sitting date-time, not when the division was recorded), Senate vote records, publications, and `statute`
|
|
55
|
+
- Also the Library of Parliament legislative `summary` and dossier `notes` as plain text (each note link kept as `text (href)`), the six reading `milestones` as UTC instants, and related `webReferences` (press releases, background documents, costing notes) whose URLs are relayed as published, never fetched
|
|
56
|
+
- Above a 24,000-byte response budget, returns `kind: "outline"` with `sponsor`, `statute`, and `milestones` inline, listing the sections `sponsor`, `stages.house`, `stages.senate`, `stages.royalAssent`, `divisions`, `senateVoteDetails`, `publications`, `statute`, `summary.en`, `summary.fr`, `notes.en`, `notes.fr`, `milestones`, and `webReferences`; re-call with `section`, paging collections by `offset` and `limit` (1–100, default 20; values up to 10,000 clamp to 100 with a notice) through `pagination.nextOffset`
|
|
57
|
+
- 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
|
|
56
58
|
|
|
57
59
|
---
|
|
58
60
|
|
|
59
61
|
### `canparl_search_members` <sub>tool</sub>
|
|
60
62
|
|
|
61
|
-
- Current roster:
|
|
62
|
-
- `includeFormer: true`
|
|
63
|
-
- Pass
|
|
63
|
+
- Current roster: `name` and `riding` substrings, `province` (postal code or full name), and `party`; `limit` 1–100 (default 25; values up to 10,000 clamp to 100 with a notice) with the exact filtered `total`
|
|
64
|
+
- `includeFormer: true` searches 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, and `total` is `null`
|
|
65
|
+
- Pass `detailLookup` unchanged to `canparl_get_member`: current rows carry an official `personId`, historical rows an OpenParliament `slug`
|
|
64
66
|
|
|
65
67
|
---
|
|
66
68
|
|
|
67
69
|
### `canparl_get_member` <sub>tool</sub>
|
|
68
70
|
|
|
69
|
-
-
|
|
70
|
-
-
|
|
71
|
-
-
|
|
71
|
+
- Exactly one `personId` or `slug` from `detailLookup`; returns the profile and dated `memberships`. A digit-only `slug` equal to a current `personId`, or its `/politicians/<digits>/` path or URL, is read as that `personId`
|
|
72
|
+
- `sponsoredBills` (0–5, default 5) and `recentVotes` (0–10, default 10) page independently through `billsOffset` and `votesOffset`; `0` skips a section, and values up to 10,000 clamp to the cap with a notice
|
|
73
|
+
- With one official `personId`, ballots come newest first from the official member-votes feed in one request, for the member's latest session or `votesSession`, optionally narrowed by `votesBillCode`. `ballot` reads `Yes`/`No`/`Paired`, `officialBallot` keeps `Yea`/`Nay`/`Paired`, and `source` names the feed; other members keep OpenParliament ballots
|
|
74
|
+
- Each activity section reports `available`, `unavailable`, or `not_requested`, with `failure` metadata; unavailable history is never reported as empty
|
|
75
|
+
- Profile `links` are never fetched: an absolute `http` or `https` URL is relayed as published, and a relative value is resolved against `https://openparliament.ca`
|
|
72
76
|
|
|
73
77
|
---
|
|
74
78
|
|
|
75
79
|
### `canparl_search_votes` <sub>tool</sub>
|
|
76
80
|
|
|
77
|
-
-
|
|
78
|
-
-
|
|
81
|
+
- `billCode`, `result` (`Agreed To`, `Negatived`, `Tie`), `documentType`, inclusive Ottawa-date `dateFrom` and `dateTo`, and `subjectContains`; `limit` 1–100 (default 25; values up to 10,000 clamp to 100 with a notice) with offset pagination
|
|
82
|
+
- Newest first, with official tallies and the exact filtered `total`; pass `session` and `number` to `canparl_get_vote`
|
|
79
83
|
|
|
80
84
|
---
|
|
81
85
|
|
|
82
86
|
### `canparl_get_vote` <sub>tool</sub>
|
|
83
87
|
|
|
84
|
-
-
|
|
85
|
-
- `tally`, `byParty`, and `ballotsTotal`
|
|
86
|
-
- Ballots are `Yea`, `Nay`, or `Paired`; nonvoters are
|
|
88
|
+
- `number` and its `session`; `ballot` (`Yea`, `Nay`, `Paired`; `Yes`/`No` accepted), `personId`, `party`, and `province` filter only the ballot page; `limit` 1–400 (default 30; values up to 10,000 clamp to 400 with a notice) with offset pagination
|
|
89
|
+
- `tally`, `byParty`, and `ballotsTotal` cover the whole division and `ballotsMatched` the filtered set; `tallyMatchesBallots` flags a disagreement without changing either count
|
|
90
|
+
- Ballots are `Yea`, `Nay`, or `Paired`; nonvoters are not listed, so a `personId` with no ballot returns an empty page
|
|
91
|
+
- A page stops at a 24,000-byte response budget with `truncated` and a notice; follow `nextOffset` for the rest
|
|
87
92
|
|
|
88
93
|
---
|
|
89
94
|
|
|
90
95
|
### `canparl_search_speeches` <sub>tool</sub>
|
|
91
96
|
|
|
92
|
-
-
|
|
93
|
-
- `
|
|
94
|
-
-
|
|
97
|
+
- Needs a scope: `politician`, `billDebated` or `mentionedBill` (`{ session, code }`), `dateFrom`, or `dateTo`; `contains` is a literal English substring and never a scope on its own. `limit` 1–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
|
|
98
|
+
- `politician` takes the same slug, path, URL, or current `personId` values as `canparl_search_bills` `sponsor`, resolved the same way; `appliedFilters.politicianName` names the profile used
|
|
99
|
+
- Scans at most 100 raw candidates per call: follow `nextOffset` with identical filters until `exhausted` before concluding there is no match, and even then the answer covers OpenParliament's index only
|
|
100
|
+
- Complete English `content` with speech-date party and riding (`null` when unavailable); `substantiveOnly` (default `true`) drops procedural remarks, and committee records are excluded and counted in `excludedCommittees`
|
|
95
101
|
|
|
96
102
|
---
|
|
97
103
|
|
|
98
104
|
### `canparl_list_reference` <sub>tool</sub>
|
|
99
105
|
|
|
100
|
-
-
|
|
101
|
-
-
|
|
106
|
+
- `topic`: `sessions`, `bill_types`, `bill_statuses`, `vote_results`, `vote_document_types`, or `parties`; returns the complete set without pagination
|
|
107
|
+
- `sessions` takes no `session` and reports `basic` or `full` bill coverage and House division coverage per session; other topics take a session and mark each value `observed` in its feed
|
|
102
108
|
|
|
103
109
|
## Features
|
|
104
110
|
|
|
@@ -106,20 +112,20 @@ Built on [`@cyanheads/mcp-ts-core`](https://github.com/cyanheads/mcp-ts-core): s
|
|
|
106
112
|
|
|
107
113
|
Canadian Parliament-specific:
|
|
108
114
|
|
|
109
|
-
- Official bill coverage
|
|
110
|
-
- Bills cover both chambers; member, division, and speech tools cover the House. Missing historical fields
|
|
111
|
-
-
|
|
112
|
-
- 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
|
|
115
|
+
- Official bill coverage from session `35-1` (1994), full dossiers from `37-1` (2001), and House divisions from `38-1` (2004); OpenParliament's historical name index reaches 1994
|
|
116
|
+
- Bills cover both chambers; member, division, and speech tools cover the House of Commons. Missing historical fields stay `null`
|
|
117
|
+
- Sessions accept `"45-1"` or `{ "parliament": 45, "session": 1 }`; an omitted session resolves from the current official bill feed, except in speech bill scopes
|
|
118
|
+
- 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
|
|
113
119
|
|
|
114
120
|
Agent-friendly output:
|
|
115
121
|
|
|
116
|
-
- Provenance
|
|
117
|
-
- Explicit coverage
|
|
118
|
-
-
|
|
122
|
+
- Provenance: `source`, `url`, original `retrievedAt`, and `cached` on every successful retrieval
|
|
123
|
+
- Explicit coverage: normalized `appliedFilters`, continuation fields, notices, and typed failure reasons separate no matches from unavailable or incomplete data
|
|
124
|
+
- Both surfaces: every `structuredContent` value, complete source text included, also renders in Markdown `content[]`
|
|
119
125
|
|
|
120
126
|
## Getting started
|
|
121
127
|
|
|
122
|
-
Add the following to your MCP client configuration file
|
|
128
|
+
Add the following to your MCP client configuration file.
|
|
123
129
|
|
|
124
130
|
```json
|
|
125
131
|
{
|
|
@@ -128,7 +134,10 @@ Add the following to your MCP client configuration file:
|
|
|
128
134
|
"type": "stdio",
|
|
129
135
|
"command": "bunx",
|
|
130
136
|
"args": ["@cyanheads/canada-parliament-mcp-server@latest"],
|
|
131
|
-
"env": {
|
|
137
|
+
"env": {
|
|
138
|
+
"MCP_TRANSPORT_TYPE": "stdio",
|
|
139
|
+
"MCP_LOG_LEVEL": "info"
|
|
140
|
+
}
|
|
132
141
|
}
|
|
133
142
|
}
|
|
134
143
|
}
|
|
@@ -143,7 +152,10 @@ Or with npx (no Bun required):
|
|
|
143
152
|
"type": "stdio",
|
|
144
153
|
"command": "npx",
|
|
145
154
|
"args": ["-y", "@cyanheads/canada-parliament-mcp-server@latest"],
|
|
146
|
-
"env": {
|
|
155
|
+
"env": {
|
|
156
|
+
"MCP_TRANSPORT_TYPE": "stdio",
|
|
157
|
+
"MCP_LOG_LEVEL": "info"
|
|
158
|
+
}
|
|
147
159
|
}
|
|
148
160
|
}
|
|
149
161
|
}
|
|
@@ -163,7 +175,7 @@ Or with Docker:
|
|
|
163
175
|
}
|
|
164
176
|
```
|
|
165
177
|
|
|
166
|
-
For Streamable HTTP,
|
|
178
|
+
For Streamable HTTP, set the transport and start the server:
|
|
167
179
|
|
|
168
180
|
```sh
|
|
169
181
|
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
|
|
@@ -172,73 +184,97 @@ MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
|
|
|
172
184
|
|
|
173
185
|
### Prerequisites
|
|
174
186
|
|
|
175
|
-
- [Bun v1.4.0](https://bun.sh/) or higher
|
|
176
|
-
- Internet access to
|
|
187
|
+
- [Bun v1.4.0](https://bun.sh/) or higher (or Node.js v24+).
|
|
188
|
+
- Internet access to parl.ca, ourcommons.ca, and openparliament.ca; no API key is required.
|
|
177
189
|
|
|
178
190
|
### Installation
|
|
179
191
|
|
|
192
|
+
1. **Clone the repository:**
|
|
193
|
+
|
|
180
194
|
```sh
|
|
181
195
|
git clone https://github.com/cyanheads/canada-parliament-mcp-server.git
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
2. **Navigate into the directory:**
|
|
199
|
+
|
|
200
|
+
```sh
|
|
182
201
|
cd canada-parliament-mcp-server
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
3. **Install dependencies:**
|
|
205
|
+
|
|
206
|
+
```sh
|
|
183
207
|
bun install
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
4. **Configure environment:**
|
|
211
|
+
|
|
212
|
+
```sh
|
|
184
213
|
cp .env.example .env
|
|
185
|
-
|
|
214
|
+
# optional: set CANPARL_USER_AGENT to identify your deployment to the upstream sources
|
|
186
215
|
```
|
|
187
216
|
|
|
188
217
|
## Configuration
|
|
189
218
|
|
|
190
219
|
| Variable | Description | Default |
|
|
191
220
|
|:---|:---|:---|
|
|
192
|
-
| `CANPARL_USER_AGENT` |
|
|
193
|
-
| `MCP_TRANSPORT_TYPE` | `stdio` or `http
|
|
194
|
-
| `MCP_HTTP_HOST` | HTTP bind address | `127.0.0.1` |
|
|
195
|
-
| `MCP_HTTP_PORT` | HTTP port | `3010` |
|
|
196
|
-
| `MCP_SESSION_MODE` | `stateless`, `stateful`, or `auto
|
|
197
|
-
| `MCP_AUTH_MODE` | `none`, `jwt`, or `oauth
|
|
198
|
-
| `MCP_LOG_LEVEL` |
|
|
199
|
-
| `LOGS_DIR` |
|
|
200
|
-
| `STORAGE_PROVIDER_TYPE` |
|
|
201
|
-
| `OTEL_ENABLED` | Enable OpenTelemetry | `false` |
|
|
202
|
-
|
|
203
|
-
See [`.env.example`](./.env.example) for common framework overrides.
|
|
221
|
+
| `CANPARL_USER_AGENT` | HTTP User-Agent sent to the upstream sources. Blank uses the default; CR and LF are rejected. | `canada-parliament-mcp-server (casey@caseyjhand.com; https://github.com/cyanheads/canada-parliament-mcp-server)` |
|
|
222
|
+
| `MCP_TRANSPORT_TYPE` | Transport: `stdio` or `http`. | `stdio` |
|
|
223
|
+
| `MCP_HTTP_HOST` | HTTP bind address. | `127.0.0.1` |
|
|
224
|
+
| `MCP_HTTP_PORT` | HTTP server port. | `3010` |
|
|
225
|
+
| `MCP_SESSION_MODE` | HTTP session mode: `stateless`, `stateful`, or `auto`. | `stateless` |
|
|
226
|
+
| `MCP_AUTH_MODE` | Authentication: `none`, `jwt`, or `oauth`. | `none` |
|
|
227
|
+
| `MCP_LOG_LEVEL` | Log level (`debug`, `info`, `warning`, `error`, etc.). | `info` |
|
|
228
|
+
| `LOGS_DIR` | Directory for log files (Node.js only). | `<app-root>/logs` |
|
|
229
|
+
| `STORAGE_PROVIDER_TYPE` | Storage backend: `in-memory`, `filesystem`, `supabase`, `cloudflare-kv/r2/d1`. | `in-memory` |
|
|
230
|
+
| `OTEL_ENABLED` | Enable [OpenTelemetry](https://github.com/cyanheads/mcp-ts-core/tree/main/docs/telemetry). | `false` |
|
|
231
|
+
|
|
232
|
+
See [`.env.example`](./.env.example) for every server setting and the common framework overrides.
|
|
204
233
|
|
|
205
234
|
## Running the server
|
|
206
235
|
|
|
207
|
-
|
|
208
|
-
bun run rebuild # Clean and build
|
|
209
|
-
bun run start:stdio # Built server over stdio
|
|
210
|
-
bun run start:http # Built server over Streamable HTTP
|
|
211
|
-
```
|
|
236
|
+
### Local development
|
|
212
237
|
|
|
213
|
-
|
|
238
|
+
- **Build and run the production version**:
|
|
214
239
|
|
|
215
|
-
```sh
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
240
|
+
```sh
|
|
241
|
+
# One-time build
|
|
242
|
+
bun run rebuild
|
|
243
|
+
|
|
244
|
+
# Run the built server
|
|
245
|
+
bun run start:http
|
|
246
|
+
# or
|
|
247
|
+
bun run start:stdio
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
- **Run checks and tests**:
|
|
251
|
+
```sh
|
|
252
|
+
bun run devcheck # Lints, formats, type-checks, and more
|
|
253
|
+
bun run test # Runs the test suite
|
|
254
|
+
```
|
|
219
255
|
|
|
220
256
|
## Project structure
|
|
221
257
|
|
|
222
258
|
| Directory | Purpose |
|
|
223
259
|
|:---|:---|
|
|
224
|
-
| `src/mcp-server/tools/definitions` |
|
|
225
|
-
| `src/services/legisinfo` |
|
|
226
|
-
| `src/services/ourcommons` |
|
|
227
|
-
| `src/services/openparliament` |
|
|
228
|
-
| `src/domain` | Shared schemas, normalization, vocabulary, and request budgets |
|
|
229
|
-
| `src/config` | Server-specific environment parsing |
|
|
230
|
-
| `tests
|
|
260
|
+
| `src/mcp-server/tools/definitions` | Tool definitions (`*.tool.ts`), the shared Markdown renderer, and the shared member-identity crosswalk. |
|
|
261
|
+
| `src/services/legisinfo` | LEGISinfo service — session discovery, official bill lists and dossiers. |
|
|
262
|
+
| `src/services/ourcommons` | House of Commons service — current roster, division, and member-vote XML. |
|
|
263
|
+
| `src/services/openparliament` | OpenParliament service — historical identities, activity, speeches, and memberships. |
|
|
264
|
+
| `src/domain` | Shared schemas, normalization, reference vocabulary, and request budgets. |
|
|
265
|
+
| `src/config` | Server-specific environment variable parsing and validation with Zod. |
|
|
266
|
+
| `tests/` | Parser, service, tool, and integration tests over synthetic upstream fixtures. |
|
|
231
267
|
|
|
232
268
|
See [`docs/tree.md`](./docs/tree.md) for the full tree.
|
|
233
269
|
|
|
234
270
|
## Development guide
|
|
235
271
|
|
|
236
|
-
See [`CLAUDE.md`](./CLAUDE.md)
|
|
272
|
+
See [`CLAUDE.md`](./CLAUDE.md) for development guidelines and architectural rules. The short version:
|
|
237
273
|
|
|
238
|
-
-
|
|
239
|
-
-
|
|
240
|
-
-
|
|
241
|
-
-
|
|
274
|
+
- Handlers throw, framework catches; catch only to translate a known source failure or keep optional enrichment
|
|
275
|
+
- Use `ctx.log` for logging and `ctx.enrich` for applied filters, totals, and notices
|
|
276
|
+
- Register new tools in the `createApp()` arrays in `src/index.ts`
|
|
277
|
+
- Wrap external API calls: validate raw → normalize to domain type → return output schema; never fabricate missing fields
|
|
242
278
|
|
|
243
279
|
## Contributing
|
|
244
280
|
|
|
@@ -251,4 +287,4 @@ bun run test
|
|
|
251
287
|
|
|
252
288
|
## License
|
|
253
289
|
|
|
254
|
-
Code is licensed under [Apache 2.0](./LICENSE). Parliamentary data is an unofficial reproduction credited to Parliament and, where used, OpenParliament; Parliament retains copyright. Reuse is governed separately by [Parliament notices](https://www.parl.ca/ImportantNotices-e.html), [Speaker's Permission](https://www.ourcommons.ca/en/important-notices#SpeakersPermission), [Senate terms](https://sencanada.ca/en/intellectual-property/), and [OpenParliament API terms](https://openparliament.ca/api/). This free, noncommercial relay does not imply government endorsement or an open-data licence.
|
|
290
|
+
Code is licensed under [Apache 2.0](./LICENSE). Parliamentary data is an unofficial reproduction credited to Parliament and, where used, OpenParliament; Parliament retains copyright. Reuse is governed separately by the [Parliament notices](https://www.parl.ca/ImportantNotices-e.html), the [Speaker's Permission](https://www.ourcommons.ca/en/important-notices#SpeakersPermission), the [Senate terms](https://sencanada.ca/en/intellectual-property/), and the [OpenParliament API terms](https://openparliament.ca/api/). This free, noncommercial relay does not imply government endorsement or an open-data licence.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "Bill dossiers gain legislative summaries, notes, web references, and reading milestones; member ballots come from the official feed; vote ballots filter by value and member; bill, vote, and speech replies share one 24,000-byte budget; member-identity, split-bill-code, and rendering bugs fixed."
|
|
3
|
+
breaking: false
|
|
4
|
+
security: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 0.1.2 — 2026-10-21
|
|
8
|
+
|
|
9
|
+
## Added
|
|
10
|
+
|
|
11
|
+
- **`canparl_get_bill`** returns the Library of Parliament legislative summary, dossier notes, web references, sponsor title, and reading milestones; dossiers outline 14 sections. ([#10](https://github.com/cyanheads/canada-parliament-mcp-server/issues/10))
|
|
12
|
+
- **`canparl_get_vote`** `ballot` and `personId` filters on the ballot page. ([#8](https://github.com/cyanheads/canada-parliament-mcp-server/issues/8))
|
|
13
|
+
- **`canparl_get_member`** reads recent ballots from the official OurCommons member-votes feed; new `votesSession` and `votesBillCode` inputs, additive row fields (`officialBallot`, `source`). ([#9](https://github.com/cyanheads/canada-parliament-mcp-server/issues/9))
|
|
14
|
+
- **Member identity:** a current MP's official personId passed as `slug`, `sponsor`, or `politician` (bare digits, or its `/politicians/<digits>/` path or URL) resolves through the verified roster crosswalk; `appliedFilters` adds `sponsorName` and `politicianName`. ([#4](https://github.com/cyanheads/canada-parliament-mcp-server/issues/4))
|
|
15
|
+
- **Response budget:** `canparl_get_bill`, `canparl_get_vote`, and `canparl_search_speeches` share one 24,000-byte complete-response budget. ([#7](https://github.com/cyanheads/canada-parliament-mcp-server/issues/7))
|
|
16
|
+
- **Page-size clamping:** over-cap counts up to 10,000 clamp to each tool's cap with a notice instead of rejecting the call. ([#12](https://github.com/cyanheads/canada-parliament-mcp-server/issues/12))
|
|
17
|
+
|
|
18
|
+
## Changed
|
|
19
|
+
|
|
20
|
+
- **`canparl_get_vote`** default `limit` 100 → 30. ([#7](https://github.com/cyanheads/canada-parliament-mcp-server/issues/7))
|
|
21
|
+
- **`latestMajorStage.chamber`** is `House`, `Senate`, or null on both bill tools (`House of Commons` → `House`). ([#5](https://github.com/cyanheads/canada-parliament-mcp-server/issues/5))
|
|
22
|
+
- **`royalAssentDate`** is a UTC `…Z` instant on both bill tools. ([#15](https://github.com/cyanheads/canada-parliament-mcp-server/issues/15))
|
|
23
|
+
- **`canparl_get_bill`** advertises the stage record once as `$defs.BillStage`. ([#13](https://github.com/cyanheads/canada-parliament-mcp-server/issues/13))
|
|
24
|
+
- **Filter errors** name the rejected field and its accepted values without echoing the value; malformed sessions get each tool's session reason, and `error.data` is reshaped. ([#11](https://github.com/cyanheads/canada-parliament-mcp-server/issues/11))
|
|
25
|
+
- **`content[]` text** uses minimal escaping, nested blockquotes, and recoverable scalar types; `structuredContent` is unchanged. ([#6](https://github.com/cyanheads/canada-parliament-mcp-server/issues/6))
|
|
26
|
+
- **Member recent ballots** come from the official feed when the member has one official personId. ([#9](https://github.com/cyanheads/canada-parliament-mcp-server/issues/9))
|
|
27
|
+
- **Slug inputs:** a digit-only `slug`, `sponsor`, or `politician` equal to a current personId now means that member. ([#4](https://github.com/cyanheads/canada-parliament-mcp-server/issues/4))
|
|
28
|
+
- **Speech text** keeps a paragraph break where inline text preceded a block element. ([#16](https://github.com/cyanheads/canada-parliament-mcp-server/issues/16))
|
|
29
|
+
|
|
30
|
+
## Fixed
|
|
31
|
+
|
|
32
|
+
- **Split-bill codes** (`C-15A`) no longer fail whole sessions; 37-1, 37-2, 38-1, and 40-3 list completely. ([#14](https://github.com/cyanheads/canada-parliament-mcp-server/issues/14))
|
|
33
|
+
- **Profile links:** an `http://` politician link no longer fails the whole profile; absolute links are relayed exactly as published. ([#17](https://github.com/cyanheads/canada-parliament-mcp-server/issues/17))
|
|
34
|
+
|
|
35
|
+
## Dependencies
|
|
36
|
+
|
|
37
|
+
- `commonmark` 0.31.2 and `@types/commonmark` 0.27.10 added as exact-pinned devDependencies (test-only Markdown decoder).
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert source HTML to complete decoded prose, retaining paragraph and hard breaks. Each block
|
|
3
|
+
* ends its paragraph, and a block's first visible character starts a new one unless a line break
|
|
4
|
+
* already precedes it. With `linkTargets`, each anchor with a nonblank `href` keeps its target as
|
|
5
|
+
* `text (href)`.
|
|
6
|
+
*/
|
|
7
|
+
export declare function htmlToText(html: string, options?: {
|
|
8
|
+
linkTargets?: boolean;
|
|
9
|
+
}): string;
|
|
10
|
+
//# sourceMappingURL=html-to-text.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html-to-text.d.ts","sourceRoot":"","sources":["../../src/domain/html-to-text.ts"],"names":[],"mappings":"AAgBA;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,MAAM,CAkDxF"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Source HTML to complete decoded plain text, shared by speech and dossier parsers.
|
|
3
|
+
* @module domain/html-to-text
|
|
4
|
+
*/
|
|
5
|
+
import { parseHTML } from 'linkedom';
|
|
6
|
+
import { assertRepresentableText } from './normalization.js';
|
|
7
|
+
const BLOCKS = new Set(['p', 'div', 'blockquote', 'li', 'section', 'h1', 'h2', 'h3', 'tr']);
|
|
8
|
+
/**
|
|
9
|
+
* Convert source HTML to complete decoded prose, retaining paragraph and hard breaks. Each block
|
|
10
|
+
* ends its paragraph, and a block's first visible character starts a new one unless a line break
|
|
11
|
+
* already precedes it. With `linkTargets`, each anchor with a nonblank `href` keeps its target as
|
|
12
|
+
* `text (href)`.
|
|
13
|
+
*/
|
|
14
|
+
export function htmlToText(html, options = {}) {
|
|
15
|
+
const { document } = parseHTML('<html><body></body></html>');
|
|
16
|
+
const container = document.createElement('div');
|
|
17
|
+
container.innerHTML = html;
|
|
18
|
+
const parts = [];
|
|
19
|
+
/** Visible text has been written with no line break after it. */
|
|
20
|
+
let open = false;
|
|
21
|
+
/** A block opened after the last visible character was written. */
|
|
22
|
+
let blockOpened = false;
|
|
23
|
+
function write(text) {
|
|
24
|
+
const visible = blockOpened ? text.search(/\S/) : -1;
|
|
25
|
+
if (visible >= 0) {
|
|
26
|
+
blockOpened = false;
|
|
27
|
+
const lead = text.slice(0, visible);
|
|
28
|
+
if (open && !/[\n\r]/.test(lead))
|
|
29
|
+
parts.push(lead, '\n\n', text.slice(visible));
|
|
30
|
+
else
|
|
31
|
+
parts.push(text);
|
|
32
|
+
}
|
|
33
|
+
else
|
|
34
|
+
parts.push(text);
|
|
35
|
+
const tail = text.slice(text.trimEnd().length);
|
|
36
|
+
if (/[\n\r]/.test(tail))
|
|
37
|
+
open = false;
|
|
38
|
+
else if (tail.length < text.length)
|
|
39
|
+
open = true;
|
|
40
|
+
}
|
|
41
|
+
function visit(node) {
|
|
42
|
+
const name = node.nodeName.toLowerCase();
|
|
43
|
+
if (node.nodeType === 3 || name === 'br') {
|
|
44
|
+
write(name === 'br' ? '\n' : (node.textContent ?? ''));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const block = node !== container && BLOCKS.has(name);
|
|
48
|
+
if (block)
|
|
49
|
+
blockOpened = true;
|
|
50
|
+
const children = Array.from(node.childNodes);
|
|
51
|
+
for (const [index, child] of children.entries()) {
|
|
52
|
+
const adjacentBlock = [children[index - 1], children[index + 1]].some((sibling) => sibling && BLOCKS.has(sibling.nodeName.toLowerCase()));
|
|
53
|
+
if (child.nodeType === 3 && /^\s*$/.test(child.textContent ?? '') && adjacentBlock)
|
|
54
|
+
continue;
|
|
55
|
+
visit(child);
|
|
56
|
+
}
|
|
57
|
+
const target = options.linkTargets && name === 'a' ? node.getAttribute?.('href')?.trim() : '';
|
|
58
|
+
if (target)
|
|
59
|
+
write(` (${target})`);
|
|
60
|
+
else if (block)
|
|
61
|
+
write('\n\n');
|
|
62
|
+
}
|
|
63
|
+
visit(container);
|
|
64
|
+
const rendered = parts.join('');
|
|
65
|
+
const last = Array.from(container.childNodes).findLast((node) => node.nodeType !== 3 || !/^\s*$/.test(node.textContent ?? ''));
|
|
66
|
+
const result = last && BLOCKS.has(last.nodeName.toLowerCase()) ? rendered.replace(/\n\n$/, '') : rendered;
|
|
67
|
+
assertRepresentableText(result);
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=html-to-text.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html-to-text.js","sourceRoot":"","sources":["../../src/domain/html-to-text.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAS7D,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;AAE5F;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,OAAO,GAA8B,EAAE;IAC9E,MAAM,EAAE,QAAQ,EAAE,GAAG,SAAS,CAAC,4BAA4B,CAAC,CAAC;IAC7D,MAAM,SAAS,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;IAChD,SAAS,CAAC,SAAS,GAAG,IAAI,CAAC;IAC3B,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,iEAAiE;IACjE,IAAI,IAAI,GAAG,KAAK,CAAC;IACjB,mEAAmE;IACnE,IAAI,WAAW,GAAG,KAAK,CAAC;IACxB,SAAS,KAAK,CAAC,IAAY;QACzB,MAAM,OAAO,GAAG,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACrD,IAAI,OAAO,IAAI,CAAC,EAAE,CAAC;YACjB,WAAW,GAAG,KAAK,CAAC;YACpB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;YACpC,IAAI,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;gBAAE,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;;gBAC3E,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxB,CAAC;;YAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC;QAC/C,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,IAAI,GAAG,KAAK,CAAC;aACjC,IAAI,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM;YAAE,IAAI,GAAG,IAAI,CAAC;IAClD,CAAC;IACD,SAAS,KAAK,CAAC,IAAc;QAC3B,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;QACzC,IAAI,IAAI,CAAC,QAAQ,KAAK,CAAC,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YACzC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,CAAC;YACvD,OAAO;QACT,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,KAAK,SAAS,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACrD,IAAI,KAAK;YAAE,WAAW,GAAG,IAAI,CAAC;QAC9B,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC7C,KAAK,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC;YAChD,MAAM,aAAa,GAAG,CAAC,QAAQ,CAAC,KAAK,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CACnE,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,IAAI,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,CACnE,CAAC;YACF,IAAI,KAAK,CAAC,QAAQ,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,IAAI,EAAE,CAAC,IAAI,aAAa;gBAAE,SAAS;YAC7F,KAAK,CAAC,KAAK,CAAC,CAAC;QACf,CAAC;QACD,MAAM,MAAM,GAAG,OAAO,CAAC,WAAW,IAAI,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9F,IAAI,MAAM;YAAE,KAAK,CAAC,KAAK,MAAM,GAAG,CAAC,CAAC;aAC7B,IAAI,KAAK;YAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAChC,CAAC;IACD,KAAK,CAAC,SAAS,CAAC,CAAC;IACjB,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAChC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAE,SAAsB,CAAC,UAAU,CAAC,CAAC,QAAQ,CAClE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC,CACvE,CAAC;IACF,MAAM,MAAM,GACV,IAAI,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IAC7F,uBAAuB,CAAC,MAAM,CAAC,CAAC;IAChC,OAAO,MAAM,CAAC;AAChB,CAAC"}
|