euparliamentmonitor 0.8.40 → 0.8.41

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/README.md CHANGED
@@ -115,6 +115,13 @@ import {
115
115
  - [📔 API Documentation](https://euparliamentmonitor.com/docs/api/index.html) - TypeDoc-generated API reference
116
116
  - [📓 Test Coverage](https://euparliamentmonitor.com/docs/coverage/index.html) - Interactive coverage report
117
117
 
118
+ **🤖 Agentic Pipeline:**
119
+ - [Agent Catalog](.github/agents/README.md) — custom Copilot agents (analysis producers / consumers / gh-aw infrastructure)
120
+ - [Skills Library](.github/skills/README.md) — shared skills (security, compliance, intelligence, gh-aw)
121
+ - [Prompt Library](.github/prompts/README.md) — 10-file bounded-context prompt set (`00`→`09`) + `npm run lint:prompts` drift-guard
122
+ - [Workflows](.github/workflows/README.md) + [WORKFLOWS.md](WORKFLOWS.md) — 10 `news-*.md` agentic workflows + CI workflows
123
+ - [Analysis Chain](analysis/README.md) — 5-stage pipeline (Data → Analysis → Completeness Gate → Article → Single PR), methodologies, 39 templates, quality thresholds
124
+
118
125
  **🔒 ISMS Compliance:**
119
126
  - [🛡️ Hack23 ISMS Framework](https://github.com/Hack23/ISMS-PUBLIC) - Information Security Management System
120
127
  - [🔐 Secure Development Policy](https://github.com/Hack23/ISMS-PUBLIC/blob/main/Secure_Development_Policy.md) - Development standards
@@ -124,12 +131,17 @@ import {
124
131
 
125
132
  **MCP Server Integration**: The project uses the
126
133
  [European-Parliament-MCP-Server](https://github.com/Hack23/European-Parliament-MCP-Server)
127
- v1.2.10 for accessing real EU Parliament data via the Model Context Protocol.
134
+ v1.2.11 for accessing real EU Parliament data via the Model Context Protocol.
128
135
 
129
136
  - **MCP Server Status**: ✅ Fully operational — 60+ EP data tools available
130
137
  (feeds, direct lookups, analytical tools, intelligence correlation)
131
- - **Agentic Workflows**: 10 gh-aw markdown workflows for automated news
132
- generation with AI-driven political intelligence analysis
138
+ - **Agentic Workflows**: 10 gh-aw markdown workflows (compiled with
139
+ `gh-aw v0.69.3`) for automated news generation with AI-driven political
140
+ intelligence analysis. See [`.github/workflows/README.md`](.github/workflows/README.md).
141
+ - **Analysis Chain**: 5-stage pipeline (Data → Analysis → Completeness Gate →
142
+ Article → Single PR) producing 39 structured analysis templates per run.
143
+ See [`analysis/README.md`](analysis/README.md) and
144
+ [`analysis/methodologies/ai-driven-analysis-guide.md`](analysis/methodologies/ai-driven-analysis-guide.md).
133
145
  - **Fallback Mode**: News generation can work with reduced data when EP API
134
146
  endpoints are temporarily unavailable
135
147
  - **Environment Variable**: Set `USE_EP_MCP=false` to disable MCP client
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "euparliamentmonitor",
3
- "version": "0.8.40",
3
+ "version": "0.8.41",
4
4
  "type": "module",
5
5
  "description": "European Parliament Intelligence Platform - Monitor political activity with systematic transparency",
6
6
  "main": "scripts/index.js",
@@ -70,6 +70,7 @@
70
70
  "validate-articles:strict": "npx tsx src/utils/validate-articles.ts --strict",
71
71
  "validate-analysis": "npx tsx src/utils/validate-analysis-completeness.ts",
72
72
  "validate-ep-api": "npx tsx src/utils/validate-ep-api.ts",
73
+ "lint:prompts": "node scripts/lint-prompts.js",
73
74
  "htmlhint": "sh -c 'htmlhint *.html; set -- news/*.html; if [ -e \"$1\" ]; then htmlhint \"$@\"; else echo \"No news/*.html files to lint\"; fi'",
74
75
  "serve": "python3 -m http.server 8080",
75
76
  "test": "vitest run",
@@ -144,8 +145,8 @@
144
145
  "@types/papaparse": "5.5.2",
145
146
  "@typescript-eslint/eslint-plugin": "8.59.0",
146
147
  "@typescript-eslint/parser": "8.59.0",
147
- "@vitest/coverage-v8": "4.1.4",
148
- "@vitest/ui": "4.1.4",
148
+ "@vitest/coverage-v8": "4.1.5",
149
+ "@vitest/ui": "4.1.5",
149
150
  "chart.js": "4.5.1",
150
151
  "chartjs-plugin-annotation": "3.1.0",
151
152
  "d3": "7.9.0",
@@ -165,13 +166,13 @@
165
166
  "tsx": "4.21.0",
166
167
  "typedoc": "0.28.19",
167
168
  "typescript": "6.0.3",
168
- "vitest": "4.1.4"
169
+ "vitest": "4.1.5"
169
170
  },
170
171
  "engines": {
171
172
  "node": ">=25"
172
173
  },
173
174
  "dependencies": {
174
- "european-parliament-mcp-server": "1.2.10"
175
+ "european-parliament-mcp-server": "1.2.11"
175
176
  },
176
177
  "optionalDependencies": {
177
178
  "worldbank-mcp": "1.0.1"
@@ -339,7 +339,7 @@ export declare function fetchDeclarationsFeed(client: EuropeanParliamentMCPClien
339
339
  * `_timeframe` is retained only for signature compatibility with sliding-window
340
340
  * fetchers (so the shared `fetchEPFeedData` orchestrator can dispatch uniformly);
341
341
  * the EP MCP server serves a server-defined fixed window for this feed and
342
- * ignores any timeframe input (as of v1.2.10; pre-v1.2.10 it rejected with
342
+ * ignores any timeframe input (as of v1.2.11; pre-v1.2.11 it rejected with
343
343
  * `INVALID_PARAMS` — see Hack23/European-Parliament-MCP-Server#377).
344
344
  *
345
345
  * @param client - MCP client or null
@@ -1087,7 +1087,7 @@ class UpstreamTimeoutError extends Error {
1087
1087
  /**
1088
1088
  * Error thrown when the EP MCP server returns a response indicating the feed
1089
1089
  * is unavailable (uniform `{status:"unavailable"}` envelope or the legacy raw
1090
- * upstream 404 envelope historically emitted pre-v1.2.10). Distinct from
1090
+ * upstream 404 envelope historically emitted pre-v1.2.11). Distinct from
1091
1091
  * {@link UpstreamTimeoutError} so logs/diagnostics do not misattribute a
1092
1092
  * 404/unavailable response to a timeout. Shares the same control-flow role —
1093
1093
  * callers treat it as "stop the timeframe-widening retry loop and return the
@@ -1131,10 +1131,10 @@ function checkUpstreamTimeout(value) {
1131
1131
  'Consider using year-based endpoints as fallback.');
1132
1132
  throw new UpstreamTimeoutError(toolName);
1133
1133
  }
1134
- // Defensive detection of the legacy raw upstream 404 shape that pre-v1.2.10
1134
+ // Defensive detection of the legacy raw upstream 404 shape that pre-v1.2.11
1135
1135
  // get_events_feed / get_procedures_feed emitted
1136
1136
  // (Hack23/European-Parliament-MCP-Server#378, closed by PR #380 in
1137
- // v1.2.10). Shape:
1137
+ // v1.2.11). Shape:
1138
1138
  // {"@id":"https://data.europarl.europa.eu/eli/dl/...", "error":"404 N..."}
1139
1139
  // Treated identically to the uniform `{status:"unavailable"}` envelope —
1140
1140
  // i.e. stop timeframe-widening retry loops instead of silently returning [].
@@ -1438,9 +1438,9 @@ export async function fetchMEPsFeedWithTotal(client, timeframe = 'one-week') {
1438
1438
  * accept `timeframe`/`startDate`, fixed-window feeds (documents,
1439
1439
  * plenary_documents, committee_documents, plenary_session_documents,
1440
1440
  * parliamentary_questions, corporate_bodies, controlled_vocabularies) serve a
1441
- * server-defined window. As of v1.2.10 the server silently ignores
1441
+ * server-defined window. As of v1.2.11 the server silently ignores
1442
1442
  * `timeframe`/`startDate` on fixed-window tools
1443
- * (Hack23/European-Parliament-MCP-Server#379); pre-v1.2.10 it rejected them
1443
+ * (Hack23/European-Parliament-MCP-Server#379); pre-v1.2.11 it rejected them
1444
1444
  * with `INVALID_PARAMS` (#377). This helper issues a single RPC either way —
1445
1445
  * there is no point in timeframe-widening retry loops because the server does
1446
1446
  * not narrow/widen results based on timeframe.
@@ -1597,7 +1597,7 @@ export async function fetchDeclarationsFeed(client, timeframe = 'one-week') {
1597
1597
  * `_timeframe` is retained only for signature compatibility with sliding-window
1598
1598
  * fetchers (so the shared `fetchEPFeedData` orchestrator can dispatch uniformly);
1599
1599
  * the EP MCP server serves a server-defined fixed window for this feed and
1600
- * ignores any timeframe input (as of v1.2.10; pre-v1.2.10 it rejected with
1600
+ * ignores any timeframe input (as of v1.2.11; pre-v1.2.11 it rejected with
1601
1601
  * `INVALID_PARAMS` — see Hack23/European-Parliament-MCP-Server#377).
1602
1602
  *
1603
1603
  * @param client - MCP client or null
@@ -0,0 +1,201 @@
1
+ #!/usr/bin/env node
2
+ // SPDX-FileCopyrightText: 2024-2026 Hack23 AB
3
+ // SPDX-License-Identifier: Apache-2.0
4
+
5
+ /**
6
+ * Agentic-workflow prompt linter.
7
+ *
8
+ * Enforces the single-PR rule and forbidden-phrase list across every
9
+ * `.github/workflows/news-*.md` gh-aw workflow.
10
+ *
11
+ * Exceptions:
12
+ * - `news-translate.md` uses a legitimate multi-call flush pattern and is
13
+ * fully exempt from all three rules.
14
+ *
15
+ * Rules (applied per workflow file):
16
+ * 1. `safeoutputs___create_pull_request` appears AT MOST ONCE.
17
+ * 2. No forbidden phrases (case-insensitive): "checkpoint pr", "checkpoint-pr",
18
+ * "keep-alive", "keepalive", "keep alive", "heartbeat",
19
+ * "progressive safe output".
20
+ * 3. No `safeoutputs___push_repo_memory` references.
21
+ * 4. Analysis-awareness: news-*.md must either directly reference
22
+ * `analysis/methodologies/ai-driven-analysis-guide.md` and
23
+ * `03-analysis-completeness-gate.md`, OR import
24
+ * `.github/agents/news-generation.agent.md` (which provides both).
25
+ * news-translate.md is exempt.
26
+ *
27
+ * Usage:
28
+ * node scripts/lint-prompts.js
29
+ * node scripts/lint-prompts.js --workflows-dir .github/workflows
30
+ *
31
+ * Exit 0 if clean; non-zero with violations listed on stderr otherwise.
32
+ */
33
+
34
+ import fs from 'node:fs';
35
+ import path from 'node:path';
36
+ import process from 'node:process';
37
+
38
+ const ROOT = process.cwd();
39
+
40
+ const DEFAULT_DIR = path.join('.github', 'workflows');
41
+
42
+ const ARGS = process.argv.slice(2);
43
+ const dirIdx = ARGS.indexOf('--workflows-dir');
44
+ const WORKFLOWS_DIR = dirIdx !== -1 && ARGS[dirIdx + 1]
45
+ ? ARGS[dirIdx + 1]
46
+ : DEFAULT_DIR;
47
+
48
+ const ABS_DIR = path.resolve(ROOT, WORKFLOWS_DIR);
49
+
50
+ const EXEMPT_FROM_SINGLE_PR = new Set(['news-translate.md']);
51
+ const EXEMPT_FROM_PUSH_MEMORY = new Set(['news-translate.md']);
52
+ // news-translate legitimately describes its own multi-call flush cadence, so it
53
+ // is allowed to reference "checkpoint", "keep-alive", "heartbeat" etc. in
54
+ // documentation form. The single-PR rule does not apply to it.
55
+ const EXEMPT_FROM_PHRASE_CHECK = new Set(['news-translate.md']);
56
+ // news-translate does not need the analysis chain (it is a translation-only
57
+ // flush pattern with no Stage B/C/D analysis).
58
+ const EXEMPT_FROM_ANALYSIS_AWARENESS = new Set(['news-translate.md']);
59
+
60
+ // Workflows either reference these anchors directly, OR import the shared
61
+ // news-generation agent which brings them in transitively.
62
+ const ANALYSIS_ANCHOR_GUIDE = 'analysis/methodologies/ai-driven-analysis-guide.md';
63
+ const ANALYSIS_ANCHOR_GATE = '03-analysis-completeness-gate.md';
64
+ const NEWS_GENERATION_IMPORT = '.github/agents/news-generation.agent.md';
65
+
66
+ const FORBIDDEN_PHRASES = [
67
+ /\bcheckpoint\s+pr\b/i,
68
+ /\bcheckpoint-pr\b/i,
69
+ /\bkeep-alive\b/i,
70
+ /\bkeepalive\b/i,
71
+ /\bkeep\s+alive\b/i,
72
+ /\bheartbeat\b/i,
73
+ /\bprogressive\s+safe\s+output\b/i,
74
+ ];
75
+
76
+ function collectWorkflowFiles(dir) {
77
+ if (!fs.existsSync(dir)) {
78
+ return [];
79
+ }
80
+ return fs
81
+ .readdirSync(dir)
82
+ .filter((name) => name.startsWith('news-') && name.endsWith('.md'))
83
+ .sort();
84
+ }
85
+
86
+ function countOccurrences(text, needle) {
87
+ if (!needle) return 0;
88
+ let count = 0;
89
+ let idx = text.indexOf(needle);
90
+ while (idx !== -1) {
91
+ count += 1;
92
+ idx = text.indexOf(needle, idx + needle.length);
93
+ }
94
+ return count;
95
+ }
96
+
97
+ function lintFile(filePath, fileName) {
98
+ const content = fs.readFileSync(filePath, 'utf8');
99
+ const violations = [];
100
+
101
+ // Rule 1: at most one safeoutputs___create_pull_request reference.
102
+ if (!EXEMPT_FROM_SINGLE_PR.has(fileName)) {
103
+ const prCount = countOccurrences(content, 'safeoutputs___create_pull_request');
104
+ if (prCount > 1) {
105
+ violations.push(
106
+ `references 'safeoutputs___create_pull_request' ${prCount} times — must appear at most once (single-PR rule). See .github/prompts/06-pr-and-safe-outputs.md`,
107
+ );
108
+ }
109
+ }
110
+
111
+ // Rule 2: forbidden phrases.
112
+ if (!EXEMPT_FROM_PHRASE_CHECK.has(fileName)) {
113
+ for (const pattern of FORBIDDEN_PHRASES) {
114
+ const match = content.match(pattern);
115
+ if (match) {
116
+ // Only record the first hit per pattern to keep output concise.
117
+ const lineNumber = content.slice(0, match.index).split('\n').length;
118
+ violations.push(
119
+ `line ${lineNumber}: forbidden phrase ${JSON.stringify(match[0])} matched by ${pattern.toString()} — banned by single-PR rule. See .github/prompts/06-pr-and-safe-outputs.md`,
120
+ );
121
+ }
122
+ }
123
+ }
124
+
125
+ // Rule 3: no safeoutputs___push_repo_memory outside news-translate.md.
126
+ if (!EXEMPT_FROM_PUSH_MEMORY.has(fileName)) {
127
+ if (content.includes('safeoutputs___push_repo_memory')) {
128
+ const idx = content.indexOf('safeoutputs___push_repo_memory');
129
+ const lineNumber = content.slice(0, idx).split('\n').length;
130
+ violations.push(
131
+ `line ${lineNumber}: 'safeoutputs___push_repo_memory' is banned (heartbeat pattern). Remove the reference.`,
132
+ );
133
+ }
134
+ }
135
+
136
+ // Rule 4: analysis-awareness.
137
+ // Each news-*.md (except news-translate) must either directly reference the
138
+ // analysis anchors or import the shared news-generation agent which carries
139
+ // them transitively. This prevents a workflow from drifting out of the
140
+ // Data → Analysis Artifacts → Completeness Gate → Article → PR chain.
141
+ if (!EXEMPT_FROM_ANALYSIS_AWARENESS.has(fileName)) {
142
+ const importsNewsGen = content.includes(NEWS_GENERATION_IMPORT);
143
+ const refsGuide = content.includes(ANALYSIS_ANCHOR_GUIDE);
144
+ const refsGate = content.includes(ANALYSIS_ANCHOR_GATE);
145
+ if (!importsNewsGen) {
146
+ if (!refsGuide) {
147
+ violations.push(
148
+ `missing analysis-awareness anchor: must either import '${NEWS_GENERATION_IMPORT}' or reference '${ANALYSIS_ANCHOR_GUIDE}'. See .github/prompts/README.md § Analysis Artifact Integration.`,
149
+ );
150
+ }
151
+ if (!refsGate) {
152
+ violations.push(
153
+ `missing completeness-gate anchor: must either import '${NEWS_GENERATION_IMPORT}' or reference '${ANALYSIS_ANCHOR_GATE}'. See .github/prompts/README.md § Analysis Artifact Integration.`,
154
+ );
155
+ }
156
+ }
157
+ }
158
+
159
+ return violations;
160
+ }
161
+
162
+ function main() {
163
+ const files = collectWorkflowFiles(ABS_DIR);
164
+ if (files.length === 0) {
165
+ console.error(`lint-prompts: no news-*.md workflows found in ${ABS_DIR}`);
166
+ process.exit(2);
167
+ }
168
+
169
+ let totalViolations = 0;
170
+ let filesWithViolations = 0;
171
+ const report = [];
172
+ for (const fileName of files) {
173
+ const filePath = path.join(ABS_DIR, fileName);
174
+ const violations = lintFile(filePath, fileName);
175
+ if (violations.length > 0) {
176
+ totalViolations += violations.length;
177
+ filesWithViolations += 1;
178
+ report.push(`\n❌ ${fileName}`);
179
+ for (const v of violations) {
180
+ report.push(` - ${v}`);
181
+ }
182
+ }
183
+ }
184
+
185
+ if (totalViolations === 0) {
186
+ console.log(`lint-prompts: ✅ ${files.length} workflow(s) checked, 0 violations`);
187
+ process.exit(0);
188
+ }
189
+
190
+ console.error(
191
+ `lint-prompts: ❌ ${totalViolations} violation(s) across ${filesWithViolations} file(s)`,
192
+ );
193
+ for (const line of report) {
194
+ console.error(line);
195
+ }
196
+ console.error('');
197
+ console.error('Fix: see .github/prompts/06-pr-and-safe-outputs.md — the single-PR rule.');
198
+ process.exit(1);
199
+ }
200
+
201
+ main();
@@ -5,17 +5,29 @@
5
5
  */
6
6
  import { MCPConnection } from './mcp-connection.js';
7
7
  import type { MCPClientOptions, MCPToolResult, GetMEPsOptions, GetPlenarySessionsOptions, SearchDocumentsOptions, GetParliamentaryQuestionsOptions, GetCommitteeInfoOptions, MonitorLegislativePipelineOptions, AssessMEPInfluenceOptions, AnalyzeCoalitionDynamicsOptions, DetectVotingAnomaliesOptions, ComparePoliticalGroupsOptions, VotingRecordsOptions, VotingPatternsOptions, GenerateReportOptions, AnalyzeLegislativeEffectivenessOptions, AnalyzeCommitteeActivityOptions, TrackMEPAttendanceOptions, AnalyzeCountryDelegationOptions, GeneratePoliticalLandscapeOptions, GetCurrentMEPsOptions, GetSpeechesOptions, GetProceduresOptions, GetAdoptedTextsOptions, GetEventsOptions, GetMeetingActivitiesOptions, GetMeetingDecisionsOptions, GetMEPDeclarationsOptions, GetIncomingMEPsOptions, GetOutgoingMEPsOptions, GetHomonymMEPsOptions, GetPlenaryDocumentsOptions, GetCommitteeDocumentsOptions, GetPlenarySessionDocumentsOptions, GetPlenarySessionDocumentItemsOptions, GetControlledVocabulariesOptions, GetExternalDocumentsOptions, GetMeetingForeseenActivitiesOptions, GetProcedureEventsOptions, GetMeetingPlenarySessionDocumentsOptions, GetMeetingPlenarySessionDocumentItemsOptions, NetworkAnalysisOptions, SentimentTrackerOptions, EarlyWarningSystemOptions, ComparativeIntelligenceOptions, CorrelateIntelligenceOptions, GetAllGeneratedStatsOptions, GetMEPsFeedOptions, GetEventsFeedOptions, GetProceduresFeedOptions, GetAdoptedTextsFeedOptions, GetMEPDeclarationsFeedOptions, GetDocumentsFeedOptions, GetPlenaryDocumentsFeedOptions, GetCommitteeDocumentsFeedOptions, GetPlenarySessionDocumentsFeedOptions, GetExternalDocumentsFeedOptions, GetParliamentaryQuestionsFeedOptions, GetCorporateBodiesFeedOptions, GetControlledVocabulariesFeedOptions, GetProcedureEventByIdOptions } from '../types/index.js';
8
+ /**
9
+ * Canonical list of tools exposed by the European Parliament MCP gateway
10
+ * (`european-parliament-mcp-server@1.2.11`). The news workflows, prompt
11
+ * library (`.github/prompts/07-mcp-reference.md`), and the integration test
12
+ * suite all reference this list so a regression that adds/removes a tool
13
+ * fails a single drift guard
14
+ * (`test/integration/mcp/ep-mcp.test.js`) instead of silently breaking
15
+ * prompt/validator/probe coverage.
16
+ *
17
+ * Kept in sync with every `this.safeCallTool('<name>', ...)` call below.
18
+ */
19
+ export declare const EP_MCP_TOOLS: readonly string[];
8
20
  /**
9
21
  * Detect whether an MCP feed result represents an "unavailable" response,
10
22
  * covering the two shapes historically emitted by the EP MCP server.
11
23
  *
12
24
  * 1. **Uniform envelope** (all feeds as of
13
- * `european-parliament-mcp-server@1.2.10`) —
25
+ * `european-parliament-mcp-server@1.2.11`) —
14
26
  * `{status:"unavailable", items:[], generatedAt:"..."}` established by
15
27
  * Hack23/European-Parliament-MCP-Server#301 and extended to
16
28
  * `get_events_feed`/`get_procedures_feed` by
17
29
  * Hack23/European-Parliament-MCP-Server#380 (which closed #378).
18
- * 2. **Legacy raw upstream 404 shape** (historically emitted pre-v1.2.10 by
30
+ * 2. **Legacy raw upstream 404 shape** (historically emitted pre-v1.2.11 by
19
31
  * `get_events_feed` / `get_procedures_feed`, fixed upstream in PR #380) —
20
32
  * `{"@id":"https://data.europarl.europa.eu/eli/dl/...","error":"404 N..."}`.
21
33
  * Retained purely as defense-in-depth for older pinned server versions or
@@ -117,7 +129,7 @@ export declare class EuropeanParliamentMCPClient extends MCPConnection {
117
129
  /**
118
130
  * Search legislative documents
119
131
  *
120
- * @param options - Search options using v1.2.10 parameters: keyword, documentType, docId, etc.
132
+ * @param options - Search options using v1.2.11 parameters: keyword, documentType, docId, etc.
121
133
  * @returns Search results
122
134
  */
123
135
  searchDocuments(options?: SearchDocumentsOptions): Promise<MCPToolResult>;
@@ -250,14 +262,14 @@ export declare class EuropeanParliamentMCPClient extends MCPConnection {
250
262
  /**
251
263
  * Get plenary speeches and debate contributions
252
264
  *
253
- * @param options - Filter options including optional speechId, dateFrom/dateTo (v1.2.10: year removed)
265
+ * @param options - Filter options including optional speechId, dateFrom/dateTo (v1.2.11: year removed)
254
266
  * @returns Speeches data
255
267
  */
256
268
  getSpeeches(options?: GetSpeechesOptions): Promise<MCPToolResult>;
257
269
  /**
258
270
  * Get legislative procedures
259
271
  *
260
- * @param options - Filter options including optional processId (v1.2.10: year removed)
272
+ * @param options - Filter options including optional processId (v1.2.11: year removed)
261
273
  * @returns Procedures data
262
274
  */
263
275
  getProcedures(options?: GetProceduresOptions): Promise<MCPToolResult>;
@@ -277,7 +289,7 @@ export declare class EuropeanParliamentMCPClient extends MCPConnection {
277
289
  /**
278
290
  * Get European Parliament events (hearings, conferences, seminars)
279
291
  *
280
- * @param options - Filter options including optional eventId, pagination only (v1.2.10: year/dateFrom/dateTo removed — EP API /events has no date filtering)
292
+ * @param options - Filter options including optional eventId, pagination only (v1.2.11: year/dateFrom/dateTo removed — EP API /events has no date filtering)
281
293
  * @returns Events data
282
294
  */
283
295
  getEvents(options?: GetEventsOptions): Promise<MCPToolResult>;
@@ -333,7 +345,7 @@ export declare class EuropeanParliamentMCPClient extends MCPConnection {
333
345
  /**
334
346
  * Get committee documents
335
347
  *
336
- * @param options - Filter options including optional docId (v1.2.10: year removed)
348
+ * @param options - Filter options including optional docId (v1.2.11: year removed)
337
349
  * @returns Committee documents data
338
350
  */
339
351
  getCommitteeDocuments(options?: GetCommitteeDocumentsOptions): Promise<MCPToolResult>;
@@ -361,7 +373,7 @@ export declare class EuropeanParliamentMCPClient extends MCPConnection {
361
373
  /**
362
374
  * Get external documents (non-EP documents such as Council positions)
363
375
  *
364
- * @param options - Filter options including optional docId (v1.2.10: year removed)
376
+ * @param options - Filter options including optional docId (v1.2.11: year removed)
365
377
  * @returns External documents data
366
378
  */
367
379
  getExternalDocuments(options?: GetExternalDocumentsOptions): Promise<MCPToolResult>;
@@ -6,6 +6,81 @@
6
6
  * built on top of the generic {@link MCPConnection} transport.
7
7
  */
8
8
  import { MCPConnection } from './mcp-connection.js';
9
+ /**
10
+ * Canonical list of tools exposed by the European Parliament MCP gateway
11
+ * (`european-parliament-mcp-server@1.2.11`). The news workflows, prompt
12
+ * library (`.github/prompts/07-mcp-reference.md`), and the integration test
13
+ * suite all reference this list so a regression that adds/removes a tool
14
+ * fails a single drift guard
15
+ * (`test/integration/mcp/ep-mcp.test.js`) instead of silently breaking
16
+ * prompt/validator/probe coverage.
17
+ *
18
+ * Kept in sync with every `this.safeCallTool('<name>', ...)` call below.
19
+ */
20
+ export const EP_MCP_TOOLS = [
21
+ 'analyze_coalition_dynamics',
22
+ 'analyze_committee_activity',
23
+ 'analyze_country_delegation',
24
+ 'analyze_legislative_effectiveness',
25
+ 'analyze_voting_patterns',
26
+ 'assess_mep_influence',
27
+ 'comparative_intelligence',
28
+ 'compare_political_groups',
29
+ 'correlate_intelligence',
30
+ 'detect_voting_anomalies',
31
+ 'early_warning_system',
32
+ 'generate_political_landscape',
33
+ 'generate_report',
34
+ 'get_adopted_texts',
35
+ 'get_adopted_texts_feed',
36
+ 'get_all_generated_stats',
37
+ 'get_committee_documents',
38
+ 'get_committee_documents_feed',
39
+ 'get_committee_info',
40
+ 'get_controlled_vocabularies',
41
+ 'get_controlled_vocabularies_feed',
42
+ 'get_corporate_bodies_feed',
43
+ 'get_current_meps',
44
+ 'get_documents_feed',
45
+ 'get_events',
46
+ 'get_events_feed',
47
+ 'get_external_documents',
48
+ 'get_external_documents_feed',
49
+ 'get_homonym_meps',
50
+ 'get_incoming_meps',
51
+ 'get_meeting_activities',
52
+ 'get_meeting_decisions',
53
+ 'get_meeting_foreseen_activities',
54
+ 'get_meeting_plenary_session_document_items',
55
+ 'get_meeting_plenary_session_documents',
56
+ 'get_mep_declarations',
57
+ 'get_mep_declarations_feed',
58
+ 'get_mep_details',
59
+ 'get_meps',
60
+ 'get_meps_feed',
61
+ 'get_outgoing_meps',
62
+ 'get_parliamentary_questions',
63
+ 'get_parliamentary_questions_feed',
64
+ 'get_plenary_documents',
65
+ 'get_plenary_documents_feed',
66
+ 'get_plenary_session_document_items',
67
+ 'get_plenary_session_documents',
68
+ 'get_plenary_session_documents_feed',
69
+ 'get_plenary_sessions',
70
+ 'get_procedure_event_by_id',
71
+ 'get_procedure_events',
72
+ 'get_procedures',
73
+ 'get_procedures_feed',
74
+ 'get_server_health',
75
+ 'get_speeches',
76
+ 'get_voting_records',
77
+ 'monitor_legislative_pipeline',
78
+ 'network_analysis',
79
+ 'search_documents',
80
+ 'sentiment_tracker',
81
+ 'track_legislation',
82
+ 'track_mep_attendance',
83
+ ];
9
84
  /** Fallback payload for analyze_legislative_effectiveness when validation fails or tool is unavailable */
10
85
  const EFFECTIVENESS_FALLBACK = '{"effectiveness": null}';
11
86
  /** Fallback payload for MEP list tools */
@@ -29,7 +104,7 @@ const SERVER_HEALTH_FALLBACK = '{"server": null, "feeds": []}';
29
104
  /**
30
105
  * Classify an error message into a diagnostic error category.
31
106
  *
32
- * Maps EP MCP Server v1.2.10 structured error codes and generic HTTP/network
107
+ * Maps EP MCP Server v1.2.11 structured error codes and generic HTTP/network
33
108
  * errors into one of six broad categories used for logging and retry decisions:
34
109
  *
35
110
  * Returned categories (priority order):
@@ -45,7 +120,7 @@ const SERVER_HEALTH_FALLBACK = '{"server": null, "feeds": []}';
45
120
  */
46
121
  function classifyToolError(message) {
47
122
  const lowerMsg = message.toLowerCase();
48
- // EP MCP Server v1.2.10 structured error codes (matched case-insensitively)
123
+ // EP MCP Server v1.2.11 structured error codes (matched case-insensitively)
49
124
  if (lowerMsg.includes('internal_error')) {
50
125
  return 'INTERNAL_ERROR';
51
126
  }
@@ -104,12 +179,12 @@ function _parseResultPayload(result) {
104
179
  * covering the two shapes historically emitted by the EP MCP server.
105
180
  *
106
181
  * 1. **Uniform envelope** (all feeds as of
107
- * `european-parliament-mcp-server@1.2.10`) —
182
+ * `european-parliament-mcp-server@1.2.11`) —
108
183
  * `{status:"unavailable", items:[], generatedAt:"..."}` established by
109
184
  * Hack23/European-Parliament-MCP-Server#301 and extended to
110
185
  * `get_events_feed`/`get_procedures_feed` by
111
186
  * Hack23/European-Parliament-MCP-Server#380 (which closed #378).
112
- * 2. **Legacy raw upstream 404 shape** (historically emitted pre-v1.2.10 by
187
+ * 2. **Legacy raw upstream 404 shape** (historically emitted pre-v1.2.11 by
113
188
  * `get_events_feed` / `get_procedures_feed`, fixed upstream in PR #380) —
114
189
  * `{"@id":"https://data.europarl.europa.eu/eli/dl/...","error":"404 N..."}`.
115
190
  * Retained purely as defense-in-depth for older pinned server versions or
@@ -129,7 +204,7 @@ export function isFeedUnavailable(result) {
129
204
  // Shape 1 — uniform {status:"unavailable"} envelope (#301 / #380).
130
205
  if (envelope['status'] === 'unavailable')
131
206
  return true;
132
- // Shape 2 — legacy raw upstream 404 leak (historically pre-v1.2.10, #378).
207
+ // Shape 2 — legacy raw upstream 404 leak (historically pre-v1.2.11, #378).
133
208
  const error = envelope['error'];
134
209
  const idField = envelope['@id'];
135
210
  if (typeof error === 'string' &&
@@ -227,8 +302,8 @@ export class EuropeanParliamentMCPClient extends MCPConnection {
227
302
  return this._recordToolFailure(toolName, result.content?.[0]?.text ?? '', fallbackText);
228
303
  }
229
304
  // Detect the unavailable-feed envelope — uniform `{status:"unavailable"}`
230
- // (all feeds as of v1.2.10, #301/#380) as well as the legacy raw upstream
231
- // 404 shape `{"@id":..., "error":"404 ..."}` that pre-v1.2.10
305
+ // (all feeds as of v1.2.11, #301/#380) as well as the legacy raw upstream
306
+ // 404 shape `{"@id":..., "error":"404 ..."}` that pre-v1.2.11
232
307
  // get_events_feed / get_procedures_feed emitted
233
308
  // (Hack23/European-Parliament-MCP-Server#378, closed by PR #380). The
234
309
  // server returns HTTP 200 with a payload that bypasses isError — record
@@ -359,7 +434,7 @@ export class EuropeanParliamentMCPClient extends MCPConnection {
359
434
  /**
360
435
  * Search legislative documents
361
436
  *
362
- * @param options - Search options using v1.2.10 parameters: keyword, documentType, docId, etc.
437
+ * @param options - Search options using v1.2.11 parameters: keyword, documentType, docId, etc.
363
438
  * @returns Search results
364
439
  */
365
440
  async searchDocuments(options = {}) {
@@ -568,7 +643,7 @@ export class EuropeanParliamentMCPClient extends MCPConnection {
568
643
  /**
569
644
  * Get plenary speeches and debate contributions
570
645
  *
571
- * @param options - Filter options including optional speechId, dateFrom/dateTo (v1.2.10: year removed)
646
+ * @param options - Filter options including optional speechId, dateFrom/dateTo (v1.2.11: year removed)
572
647
  * @returns Speeches data
573
648
  */
574
649
  async getSpeeches(options = {}) {
@@ -577,7 +652,7 @@ export class EuropeanParliamentMCPClient extends MCPConnection {
577
652
  /**
578
653
  * Get legislative procedures
579
654
  *
580
- * @param options - Filter options including optional processId (v1.2.10: year removed)
655
+ * @param options - Filter options including optional processId (v1.2.11: year removed)
581
656
  * @returns Procedures data
582
657
  */
583
658
  async getProcedures(options = {}) {
@@ -610,7 +685,7 @@ export class EuropeanParliamentMCPClient extends MCPConnection {
610
685
  /**
611
686
  * Get European Parliament events (hearings, conferences, seminars)
612
687
  *
613
- * @param options - Filter options including optional eventId, pagination only (v1.2.10: year/dateFrom/dateTo removed — EP API /events has no date filtering)
688
+ * @param options - Filter options including optional eventId, pagination only (v1.2.11: year/dateFrom/dateTo removed — EP API /events has no date filtering)
614
689
  * @returns Events data
615
690
  */
616
691
  async getEvents(options = {}) {
@@ -690,7 +765,7 @@ export class EuropeanParliamentMCPClient extends MCPConnection {
690
765
  /**
691
766
  * Get committee documents
692
767
  *
693
- * @param options - Filter options including optional docId (v1.2.10: year removed)
768
+ * @param options - Filter options including optional docId (v1.2.11: year removed)
694
769
  * @returns Committee documents data
695
770
  */
696
771
  async getCommitteeDocuments(options = {}) {
@@ -726,7 +801,7 @@ export class EuropeanParliamentMCPClient extends MCPConnection {
726
801
  /**
727
802
  * Get external documents (non-EP documents such as Council positions)
728
803
  *
729
- * @param options - Filter options including optional docId (v1.2.10: year removed)
804
+ * @param options - Filter options including optional docId (v1.2.11: year removed)
730
805
  * @returns External documents data
731
806
  */
732
807
  async getExternalDocuments(options = {}) {
@@ -218,7 +218,7 @@ export interface GetCurrentMEPsOptions {
218
218
  limit?: number | undefined;
219
219
  offset?: number | undefined;
220
220
  }
221
- /** Options for getSpeeches — v1.2.10 removed `year` (EP API ignores it for /speeches) */
221
+ /** Options for getSpeeches — v1.2.11 removed `year` (EP API ignores it for /speeches) */
222
222
  export interface GetSpeechesOptions {
223
223
  speechId?: string | undefined;
224
224
  /** Filter by sitting date start (maps to sitting-date in EP API) */
@@ -228,7 +228,7 @@ export interface GetSpeechesOptions {
228
228
  limit?: number | undefined;
229
229
  offset?: number | undefined;
230
230
  }
231
- /** Options for getProcedures — v1.2.10 removed `year` (EP API ignores it for /procedures) */
231
+ /** Options for getProcedures — v1.2.11 removed `year` (EP API ignores it for /procedures) */
232
232
  export interface GetProceduresOptions {
233
233
  processId?: string | undefined;
234
234
  limit?: number | undefined;
@@ -241,7 +241,7 @@ export interface GetAdoptedTextsOptions {
241
241
  limit?: number | undefined;
242
242
  offset?: number | undefined;
243
243
  }
244
- /** Options for getEvents — v1.2.10 removed `year`, `dateFrom`, `dateTo` (EP API /events has no date filtering) */
244
+ /** Options for getEvents — v1.2.11 removed `year`, `dateFrom`, `dateTo` (EP API /events has no date filtering) */
245
245
  export interface GetEventsOptions {
246
246
  eventId?: string | undefined;
247
247
  limit?: number | undefined;
@@ -288,7 +288,7 @@ export interface GetPlenaryDocumentsOptions {
288
288
  limit?: number | undefined;
289
289
  offset?: number | undefined;
290
290
  }
291
- /** Options for getCommitteeDocuments — v1.2.10 removed `year` (EP API ignores it for /committee-documents) */
291
+ /** Options for getCommitteeDocuments — v1.2.11 removed `year` (EP API ignores it for /committee-documents) */
292
292
  export interface GetCommitteeDocumentsOptions {
293
293
  docId?: string | undefined;
294
294
  limit?: number | undefined;
@@ -311,7 +311,7 @@ export interface GetControlledVocabulariesOptions {
311
311
  limit?: number | undefined;
312
312
  offset?: number | undefined;
313
313
  }
314
- /** Options for getExternalDocuments — v1.2.10 removed `year` (EP API ignores it for /external-documents) */
314
+ /** Options for getExternalDocuments — v1.2.11 removed `year` (EP API ignores it for /external-documents) */
315
315
  export interface GetExternalDocumentsOptions {
316
316
  docId?: string | undefined;
317
317
  limit?: number | undefined;
@@ -395,10 +395,10 @@ export type FeedTimeframe = 'today' | 'one-day' | 'one-week' | 'one-month' | 'cu
395
395
  * `plenary_session_documents`, `parliamentary_questions`,
396
396
  * `corporate_bodies`, `controlled_vocabularies`).
397
397
  *
398
- * These feeds serve a server-defined window. Historically (pre-v1.2.10) they
398
+ * These feeds serve a server-defined window. Historically (pre-v1.2.11) they
399
399
  * rejected `timeframe`/`startDate` with `INVALID_PARAMS`
400
400
  * (Hack23/European-Parliament-MCP-Server#377); as of
401
- * `european-parliament-mcp-server@1.2.10` (PR #379) the server silently
401
+ * `european-parliament-mcp-server@1.2.11` (PR #379) the server silently
402
402
  * ignores those params on fixed-window tools. The client continues to omit
403
403
  * them so intent matches behaviour and so we remain compatible with any
404
404
  * older pinned server version in downstream environments.
@@ -444,16 +444,16 @@ export interface GetMEPDeclarationsFeedOptions extends FeedBaseOptions {
444
444
  /** Filter by work type */
445
445
  workType?: string | undefined;
446
446
  }
447
- /** Options for getDocumentsFeed (fixed-window — server ignores `timeframe` as of v1.2.10) */
447
+ /** Options for getDocumentsFeed (fixed-window — server ignores `timeframe` as of v1.2.11) */
448
448
  export interface GetDocumentsFeedOptions extends FixedWindowFeedOptions {
449
449
  }
450
- /** Options for getPlenaryDocumentsFeed (fixed-window — server ignores `timeframe` as of v1.2.10) */
450
+ /** Options for getPlenaryDocumentsFeed (fixed-window — server ignores `timeframe` as of v1.2.11) */
451
451
  export interface GetPlenaryDocumentsFeedOptions extends FixedWindowFeedOptions {
452
452
  }
453
- /** Options for getCommitteeDocumentsFeed (fixed-window — server ignores `timeframe` as of v1.2.10) */
453
+ /** Options for getCommitteeDocumentsFeed (fixed-window — server ignores `timeframe` as of v1.2.11) */
454
454
  export interface GetCommitteeDocumentsFeedOptions extends FixedWindowFeedOptions {
455
455
  }
456
- /** Options for getPlenarySessionDocumentsFeed (fixed-window — server ignores `timeframe` as of v1.2.10) */
456
+ /** Options for getPlenarySessionDocumentsFeed (fixed-window — server ignores `timeframe` as of v1.2.11) */
457
457
  export interface GetPlenarySessionDocumentsFeedOptions extends FixedWindowFeedOptions {
458
458
  }
459
459
  /** Options for getExternalDocumentsFeed */
@@ -461,13 +461,13 @@ export interface GetExternalDocumentsFeedOptions extends FeedBaseOptions {
461
461
  /** Filter by work type */
462
462
  workType?: string | undefined;
463
463
  }
464
- /** Options for getParliamentaryQuestionsFeed (fixed-window — server ignores `timeframe` as of v1.2.10) */
464
+ /** Options for getParliamentaryQuestionsFeed (fixed-window — server ignores `timeframe` as of v1.2.11) */
465
465
  export interface GetParliamentaryQuestionsFeedOptions extends FixedWindowFeedOptions {
466
466
  }
467
- /** Options for getCorporateBodiesFeed (fixed-window — server ignores `timeframe` as of v1.2.10) */
467
+ /** Options for getCorporateBodiesFeed (fixed-window — server ignores `timeframe` as of v1.2.11) */
468
468
  export interface GetCorporateBodiesFeedOptions extends FixedWindowFeedOptions {
469
469
  }
470
- /** Options for getControlledVocabulariesFeed (fixed-window — server ignores `timeframe` as of v1.2.10) */
470
+ /** Options for getControlledVocabulariesFeed (fixed-window — server ignores `timeframe` as of v1.2.11) */
471
471
  export interface GetControlledVocabulariesFeedOptions extends FixedWindowFeedOptions {
472
472
  }
473
473
  /** Options for getProcedureEventById */