euparliamentmonitor 0.8.30 → 0.8.32

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
@@ -124,7 +124,7 @@ import {
124
124
 
125
125
  **MCP Server Integration**: The project uses the
126
126
  [European-Parliament-MCP-Server](https://github.com/Hack23/European-Parliament-MCP-Server)
127
- v1.2.6 for accessing real EU Parliament data via the Model Context Protocol.
127
+ v1.2.8 for accessing real EU Parliament data via the Model Context Protocol.
128
128
 
129
129
  - **MCP Server Status**: ✅ Fully operational — 60+ EP data tools available
130
130
  (feeds, direct lookups, analytical tools, intelligence correlation)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "euparliamentmonitor",
3
- "version": "0.8.30",
3
+ "version": "0.8.32",
4
4
  "type": "module",
5
5
  "description": "European Parliament Intelligence Platform - Monitor political activity with systematic transparency",
6
6
  "main": "scripts/index.js",
@@ -152,14 +152,14 @@
152
152
  "eslint-config-prettier": "10.1.8",
153
153
  "eslint-plugin-jsdoc": "62.9.0",
154
154
  "eslint-plugin-security": "4.0.0",
155
- "eslint-plugin-sonarjs": "4.0.2",
155
+ "eslint-plugin-sonarjs": "4.0.3",
156
156
  "happy-dom": "20.9.0",
157
157
  "htmlhint": "1.9.2",
158
158
  "husky": "9.1.7",
159
159
  "jscpd": "4.0.9",
160
160
  "lint-staged": "16.4.0",
161
161
  "papaparse": "5.5.3",
162
- "prettier": "3.8.2",
162
+ "prettier": "3.8.3",
163
163
  "ts-api-utils": "2.5.0",
164
164
  "tsx": "4.21.0",
165
165
  "typedoc": "0.28.19",
@@ -170,7 +170,7 @@
170
170
  "node": ">=25"
171
171
  },
172
172
  "dependencies": {
173
- "european-parliament-mcp-server": "1.2.6"
173
+ "european-parliament-mcp-server": "1.2.8"
174
174
  },
175
175
  "optionalDependencies": {
176
176
  "worldbank-mcp": "1.0.1"
@@ -349,9 +349,13 @@ export function buildAdoptedTextsSection(adoptedTexts, language) {
349
349
  if (adoptedTexts.length === 0)
350
350
  return '';
351
351
  const heading = ADOPTED_TEXTS_HEADINGS[language] ?? ADOPTED_TEXTS_HEADINGS['en'] ?? 'Recently Adopted Texts';
352
- const countFn = ADOPTED_TEXTS_COUNT_STRINGS[language] ?? ADOPTED_TEXTS_COUNT_STRINGS['en'];
352
+ const countFn = ADOPTED_TEXTS_COUNT_STRINGS[language] ??
353
+ ADOPTED_TEXTS_COUNT_STRINGS['en'] ??
354
+ ((n) => `${n} adopted texts`);
353
355
  const countText = countFn(adoptedTexts.length);
354
- const unknownDate = ADOPTED_TEXTS_DATE_UNKNOWN_STRINGS[language] ?? ADOPTED_TEXTS_DATE_UNKNOWN_STRINGS['en'];
356
+ const unknownDate = ADOPTED_TEXTS_DATE_UNKNOWN_STRINGS[language] ??
357
+ ADOPTED_TEXTS_DATE_UNKNOWN_STRINGS['en'] ??
358
+ 'Unknown date';
355
359
  // Group by date, sort most recent first
356
360
  const byDate = new Map();
357
361
  for (const item of adoptedTexts) {
@@ -118,8 +118,9 @@ let languagesInput = languagesArg
118
118
  ? (languagesArg.split(ARG_SEPARATOR)[1] ?? '').trim().toLowerCase()
119
119
  : 'en';
120
120
  // Expand presets
121
- if (LANGUAGE_PRESETS[languagesInput]) {
122
- languagesInput = LANGUAGE_PRESETS[languagesInput].join(',');
121
+ const presetLanguages = LANGUAGE_PRESETS[languagesInput];
122
+ if (presetLanguages) {
123
+ languagesInput = presetLanguages.join(',');
123
124
  }
124
125
  const languages = languagesInput
125
126
  .split(',')
@@ -402,7 +403,7 @@ function wireAIMetadata() {
402
403
  */
403
404
  export function computeDedupSuffix(articleTypes, analysisDir) {
404
405
  const baseSlugNoRun = deriveArticleTypeSlug(articleTypes.filter((t) => VALID_ARTICLE_CATEGORIES.includes(t)));
405
- const rawSuffix = analysisDir !== undefined && analysisDir.startsWith(baseSlugNoRun)
406
+ const rawSuffix = analysisDir?.startsWith(baseSlugNoRun)
406
407
  ? analysisDir.slice(baseSlugNoRun.length)
407
408
  : '';
408
409
  // Suffix validation patterns for dedup suffix extraction.
@@ -427,17 +427,17 @@ export async function fetchWeekAheadData(client, dateRange) {
427
427
  const wasHalfOpen = mcpCircuitBreaker.getState() === 'HALF_OPEN';
428
428
  console.log(`${MCP_FETCH_PREFIX} Fetching week-ahead data from MCP (parallel)...`);
429
429
  const [plenarySessions, committeeInfo, documents, pipeline, questions, epEvents] = await Promise.allSettled([
430
- client.getPlenarySessions({ startDate: dateRange.start, endDate: dateRange.end, limit: 50 }),
431
- client.getCommitteeInfo({ limit: 20 }),
432
- client.searchDocuments({ query: 'parliament', limit: 20 }),
430
+ client.getPlenarySessions({ dateFrom: dateRange.start, dateTo: dateRange.end, limit: 50 }),
431
+ client.getCommitteeInfo({ showCurrent: true }),
432
+ client.searchDocuments({ keyword: 'parliament', limit: 20 }),
433
433
  client.monitorLegislativePipeline({
434
434
  dateFrom: dateRange.start,
435
435
  dateTo: dateRange.end,
436
436
  status: 'ACTIVE',
437
437
  limit: 20,
438
438
  }),
439
- client.getParliamentaryQuestions({ startDate: dateRange.start, limit: 20 }),
440
- client.getEvents({ dateFrom: dateRange.start, dateTo: dateRange.end, limit: 20 }),
439
+ client.getParliamentaryQuestions({ dateFrom: dateRange.start, limit: 20 }),
440
+ client.getEvents({ limit: 20 }),
441
441
  ]);
442
442
  const allFailed = [
443
443
  plenarySessions,
@@ -459,7 +459,7 @@ export async function fetchWeekAheadData(client, dateRange) {
459
459
  const additionalEvents = parseEPEvents(epEvents, dateRange.start);
460
460
  const events = [...plenaryEvents, ...additionalEvents];
461
461
  return {
462
- events: events.length > 0 ? events : [{ ...PLACEHOLDER_EVENTS[0], date: dateRange.start }],
462
+ events: events.length > 0 ? events : PLACEHOLDER_EVENTS.map((e) => ({ ...e, date: dateRange.start })),
463
463
  committees: parseCommitteeMeetings(committeeInfo, dateRange.start),
464
464
  documents: parseLegislativeDocuments(documents),
465
465
  pipeline: parseLegislativePipeline(pipeline),
@@ -728,7 +728,7 @@ export async function fetchCommitteeData(client, abbreviation) {
728
728
  return defaultResult;
729
729
  try {
730
730
  console.log(`${MCP_FETCH_PREFIX} Fetching committee info for ${abbreviation}...`);
731
- const committeeResult = await callMCP(() => client.getCommitteeInfo({ committeeId: abbreviation }), null, `getCommitteeInfo(${abbreviation})`);
731
+ const committeeResult = await callMCP(() => client.getCommitteeInfo({ abbreviation }), null, `getCommitteeInfo(${abbreviation})`);
732
732
  if (committeeResult)
733
733
  applyCommitteeInfo(committeeResult, defaultResult, abbreviation);
734
734
  }
@@ -738,7 +738,7 @@ export async function fetchCommitteeData(client, abbreviation) {
738
738
  }
739
739
  try {
740
740
  console.log(`${MCP_FETCH_PREFIX} Fetching documents for ${abbreviation}...`);
741
- const docsResult = await callMCP(() => client.searchDocuments({ query: abbreviation, limit: 5 }), null, `searchDocuments(${abbreviation})`);
741
+ const docsResult = await callMCP(() => client.searchDocuments({ keyword: abbreviation, limit: 5 }), null, `searchDocuments(${abbreviation})`);
742
742
  if (docsResult)
743
743
  applyDocuments(docsResult, defaultResult);
744
744
  }
@@ -1062,8 +1062,7 @@ const TIMEFRAME_FALLBACK_CHAIN = new Map([
1062
1062
  ['one-day', 'one-week'],
1063
1063
  ['one-week', 'one-month'],
1064
1064
  ['one-month', undefined],
1065
- ['three-months', undefined],
1066
- ['one-year', undefined],
1065
+ ['custom', undefined],
1067
1066
  ]);
1068
1067
  /**
1069
1068
  * Get the next wider timeframe for fallback, or `undefined` if no fallback exists.
@@ -49,7 +49,7 @@ export function collectDocsHtmlFiles(dir, rootDir = PROJECT_ROOT) {
49
49
  */
50
50
  export function generateSitemap(articles, docsFiles = []) {
51
51
  const urls = [];
52
- const today = new Date().toISOString().split('T')[0];
52
+ const today = new Date().toISOString().slice(0, 10);
53
53
  // Add home pages for each language
54
54
  for (const lang of ALL_LANGUAGES) {
55
55
  const filename = lang === 'en' ? 'index.html' : `index-${lang}.html`;
@@ -301,8 +301,8 @@ export function generateSitemapHTML(lang, articleInfos, hasDocsDir = false) {
301
301
  const skipLinkText = getLocalizedString(SKIP_LINK_TEXTS, lang);
302
302
  const dir = getTextDirection(lang);
303
303
  const year = new Date().getFullYear();
304
- const sections = SITEMAP_SECTIONS[lang] ?? SITEMAP_SECTIONS['en'];
305
- const docsLabels = DOCS_LABELS[lang] ?? DOCS_LABELS['en'];
304
+ const sections = (SITEMAP_SECTIONS[lang] ?? SITEMAP_SECTIONS['en']);
305
+ const docsLabels = (DOCS_LABELS[lang] ?? DOCS_LABELS['en']);
306
306
  const heroTitle = getLocalizedString(PAGE_TITLES, lang).split(' - ')[0] ?? '';
307
307
  const headerSubtitle = escapeHTML(getLocalizedString(HEADER_SUBTITLE_LABELS, lang));
308
308
  const themeToggleLabel = escapeHTML(getLocalizedString(THEME_TOGGLE_LABELS, lang));
@@ -539,7 +539,7 @@ function main() {
539
539
  // Generate sitemap HTML for each language
540
540
  let htmlGenerated = 0;
541
541
  for (const lang of ALL_LANGUAGES) {
542
- const langArticles = articlesByLang.get(lang) || [];
542
+ const langArticles = articlesByLang.get(lang) ?? [];
543
543
  // Sort newest first
544
544
  langArticles.sort((a, b) => b.date.localeCompare(a.date));
545
545
  const html = generateSitemapHTML(lang, langArticles, hasDocsDir);
@@ -249,7 +249,7 @@ function buildAdoptedTextsSection(feedData, lang) {
249
249
  const sections = displayOrder
250
250
  .filter((cat) => grouped[cat]?.length)
251
251
  .map((cat) => {
252
- const items = grouped[cat];
252
+ const items = grouped[cat] ?? [];
253
253
  const listItems = items
254
254
  .map((item) => `<li class="adopted-text-item"><strong>${escapeHTML(item.title)}</strong> <span class="document-date">(${escapeHTML(item.date)})</span></li>`)
255
255
  .join('\n ');
@@ -70,32 +70,28 @@ export declare class EuropeanParliamentMCPClient extends MCPConnection {
70
70
  /**
71
71
  * Get plenary sessions
72
72
  *
73
- * @param options - Filter options. `dateFrom` is mapped to `startDate` and `dateTo` to `endDate`
74
- * per the tool schema when the canonical fields are absent.
73
+ * @param options - Filter options including dateFrom, dateTo, eventId, year, location
75
74
  * @returns Plenary sessions data
76
75
  */
77
76
  getPlenarySessions(options?: GetPlenarySessionsOptions): Promise<MCPToolResult>;
78
77
  /**
79
78
  * Search legislative documents
80
79
  *
81
- * @param options - Search options (normalizes `query` to `keyword` if `keyword` is absent,
82
- * since the MCP tool schema requires the `keyword` parameter)
80
+ * @param options - Search options using v1.2.8 parameters: keyword, documentType, docId, etc.
83
81
  * @returns Search results
84
82
  */
85
83
  searchDocuments(options?: SearchDocumentsOptions): Promise<MCPToolResult>;
86
84
  /**
87
85
  * Get parliamentary questions
88
86
  *
89
- * @param options - Filter options. `dateFrom` is mapped to `startDate` per the tool schema.
90
- * `dateTo` is intentionally ignored because the `get_parliamentary_questions` tool schema
91
- * only supports `startDate` as a date filter; passing `dateTo` would have no effect.
87
+ * @param options - Filter options including docId, type, author, topic, status, dateFrom, dateTo
92
88
  * @returns Parliamentary questions data
93
89
  */
94
90
  getParliamentaryQuestions(options?: GetParliamentaryQuestionsOptions): Promise<MCPToolResult>;
95
91
  /**
96
92
  * Get committee information
97
93
  *
98
- * @param options - Filter options
94
+ * @param options - Filter options: id, abbreviation, showCurrent
99
95
  * @returns Committee info data
100
96
  */
101
97
  getCommitteeInfo(options?: GetCommitteeInfoOptions): Promise<MCPToolResult>;
@@ -123,21 +119,21 @@ export declare class EuropeanParliamentMCPClient extends MCPConnection {
123
119
  /**
124
120
  * Analyze coalition dynamics and cohesion
125
121
  *
126
- * @param options - Options including optional political groups and date range
122
+ * @param options - Options including optional groupIds and date range
127
123
  * @returns Coalition cohesion and stress analysis
128
124
  */
129
125
  analyzeCoalitionDynamics(options?: AnalyzeCoalitionDynamicsOptions): Promise<MCPToolResult>;
130
126
  /**
131
127
  * Detect voting anomalies and party defections
132
128
  *
133
- * @param options - Options including optional MEP id, political group, and date
129
+ * @param options - Options including optional MEP id, groupId, and date range
134
130
  * @returns Anomaly detection results
135
131
  */
136
132
  detectVotingAnomalies(options?: DetectVotingAnomaliesOptions): Promise<MCPToolResult>;
137
133
  /**
138
134
  * Compare political groups across dimensions
139
135
  *
140
- * @param options - Options including required groups and optional metrics and date
136
+ * @param options - Options including required groupIds and optional dimensions and date range
141
137
  * @returns Cross-group comparative analysis
142
138
  */
143
139
  comparePoliticalGroups(options: ComparePoliticalGroupsOptions): Promise<MCPToolResult>;
@@ -151,7 +147,7 @@ export declare class EuropeanParliamentMCPClient extends MCPConnection {
151
147
  /**
152
148
  * Retrieve voting records with optional filters
153
149
  *
154
- * @param options - Filter options (mepId, sessionId, limit)
150
+ * @param options - Filter options (sessionId, mepId, topic, dateFrom, dateTo, limit, offset)
155
151
  * @returns Voting records data
156
152
  */
157
153
  getVotingRecords(options?: VotingRecordsOptions): Promise<MCPToolResult>;
@@ -214,14 +210,14 @@ export declare class EuropeanParliamentMCPClient extends MCPConnection {
214
210
  /**
215
211
  * Get plenary speeches and debate contributions
216
212
  *
217
- * @param options - Filter options including optional speechId or date range
213
+ * @param options - Filter options including optional speechId, dateFrom/dateTo (v1.2.8: year removed)
218
214
  * @returns Speeches data
219
215
  */
220
216
  getSpeeches(options?: GetSpeechesOptions): Promise<MCPToolResult>;
221
217
  /**
222
218
  * Get legislative procedures
223
219
  *
224
- * @param options - Filter options including optional processId or year
220
+ * @param options - Filter options including optional processId (v1.2.8: year removed)
225
221
  * @returns Procedures data
226
222
  */
227
223
  getProcedures(options?: GetProceduresOptions): Promise<MCPToolResult>;
@@ -235,7 +231,7 @@ export declare class EuropeanParliamentMCPClient extends MCPConnection {
235
231
  /**
236
232
  * Get European Parliament events (hearings, conferences, seminars)
237
233
  *
238
- * @param options - Filter options including optional eventId or date range
234
+ * @param options - Filter options including optional eventId, pagination only (v1.2.8: year/dateFrom/dateTo removed — EP API /events has no date filtering)
239
235
  * @returns Events data
240
236
  */
241
237
  getEvents(options?: GetEventsOptions): Promise<MCPToolResult>;
@@ -291,7 +287,7 @@ export declare class EuropeanParliamentMCPClient extends MCPConnection {
291
287
  /**
292
288
  * Get committee documents
293
289
  *
294
- * @param options - Filter options including optional docId or year
290
+ * @param options - Filter options including optional docId (v1.2.8: year removed)
295
291
  * @returns Committee documents data
296
292
  */
297
293
  getCommitteeDocuments(options?: GetCommitteeDocumentsOptions): Promise<MCPToolResult>;
@@ -319,7 +315,7 @@ export declare class EuropeanParliamentMCPClient extends MCPConnection {
319
315
  /**
320
316
  * Get external documents (non-EP documents such as Council positions)
321
317
  *
322
- * @param options - Filter options including optional docId or year
318
+ * @param options - Filter options including optional docId (v1.2.8: year removed)
323
319
  * @returns External documents data
324
320
  */
325
321
  getExternalDocuments(options?: GetExternalDocumentsOptions): Promise<MCPToolResult>;
@@ -382,10 +378,10 @@ export declare class EuropeanParliamentMCPClient extends MCPConnection {
382
378
  /**
383
379
  * Cross-tool OSINT intelligence correlation engine
384
380
  *
385
- * @param options - Options including optional mepId and correlation scenarios
381
+ * @param options - Options including required mepIds, optional groups, sensitivityLevel, includeNetworkAnalysis
386
382
  * @returns Correlated intelligence alerts and insights
387
383
  */
388
- correlateIntelligence(options?: CorrelateIntelligenceOptions): Promise<MCPToolResult>;
384
+ correlateIntelligence(options: CorrelateIntelligenceOptions): Promise<MCPToolResult>;
389
385
  /**
390
386
  * Retrieve precomputed European Parliament activity statistics (EP6–EP10, 2004–2025).
391
387
  * Includes yearly stats, category rankings, political landscape history, and
@@ -29,7 +29,7 @@ const SERVER_HEALTH_FALLBACK = '{"server": null, "feeds": []}';
29
29
  /**
30
30
  * Classify an error message into a diagnostic error category.
31
31
  *
32
- * Maps EP MCP Server v1.2.6 structured error codes and generic HTTP/network
32
+ * Maps EP MCP Server v1.2.8 structured error codes and generic HTTP/network
33
33
  * errors into one of six broad categories used for logging and retry decisions:
34
34
  *
35
35
  * Returned categories (priority order):
@@ -45,7 +45,7 @@ const SERVER_HEALTH_FALLBACK = '{"server": null, "feeds": []}';
45
45
  */
46
46
  function classifyToolError(message) {
47
47
  const lowerMsg = message.toLowerCase();
48
- // EP MCP Server v1.2.6 structured error codes (matched case-insensitively)
48
+ // EP MCP Server v1.2.8 structured error codes (matched case-insensitively)
49
49
  if (lowerMsg.includes('internal_error')) {
50
50
  return 'INTERNAL_ERROR';
51
51
  }
@@ -205,78 +205,38 @@ export class EuropeanParliamentMCPClient extends MCPConnection {
205
205
  /**
206
206
  * Get plenary sessions
207
207
  *
208
- * @param options - Filter options. `dateFrom` is mapped to `startDate` and `dateTo` to `endDate`
209
- * per the tool schema when the canonical fields are absent.
208
+ * @param options - Filter options including dateFrom, dateTo, eventId, year, location
210
209
  * @returns Plenary sessions data
211
210
  */
212
211
  async getPlenarySessions(options = {}) {
213
- return this.safeCallTool('get_plenary_sessions', () => {
214
- const { dateFrom, dateTo, ...rest } = options;
215
- const normalizedOptions = { ...rest };
216
- if (normalizedOptions['startDate'] === undefined && dateFrom !== undefined) {
217
- normalizedOptions['startDate'] = dateFrom;
218
- }
219
- if (normalizedOptions['endDate'] === undefined && dateTo !== undefined) {
220
- normalizedOptions['endDate'] = dateTo;
221
- }
222
- return normalizedOptions;
223
- }, '{"sessions": []}');
212
+ return this.safeCallTool('get_plenary_sessions', options, '{"sessions": []}');
224
213
  }
225
214
  /**
226
215
  * Search legislative documents
227
216
  *
228
- * @param options - Search options (normalizes `query` to `keyword` if `keyword` is absent,
229
- * since the MCP tool schema requires the `keyword` parameter)
217
+ * @param options - Search options using v1.2.8 parameters: keyword, documentType, docId, etc.
230
218
  * @returns Search results
231
219
  */
232
220
  async searchDocuments(options = {}) {
233
- return this.safeCallTool('search_documents', () => {
234
- const { query, ...rest } = options;
235
- const normalizedOptions = { ...rest };
236
- // MCP tool schema expects 'keyword', not 'query'
237
- if (normalizedOptions['keyword'] === undefined && query !== undefined) {
238
- const trimmed = String(query).trim();
239
- if (trimmed.length > 0) {
240
- normalizedOptions['keyword'] = trimmed;
241
- }
242
- }
243
- return normalizedOptions;
244
- }, DOCUMENTS_FALLBACK);
221
+ return this.safeCallTool('search_documents', options, DOCUMENTS_FALLBACK);
245
222
  }
246
223
  /**
247
224
  * Get parliamentary questions
248
225
  *
249
- * @param options - Filter options. `dateFrom` is mapped to `startDate` per the tool schema.
250
- * `dateTo` is intentionally ignored because the `get_parliamentary_questions` tool schema
251
- * only supports `startDate` as a date filter; passing `dateTo` would have no effect.
226
+ * @param options - Filter options including docId, type, author, topic, status, dateFrom, dateTo
252
227
  * @returns Parliamentary questions data
253
228
  */
254
229
  async getParliamentaryQuestions(options = {}) {
255
- return this.safeCallTool('get_parliamentary_questions', () => {
256
- const { dateFrom, dateTo: _dateTo, ...rest } = options;
257
- const toolOptions = { ...rest };
258
- if (toolOptions['startDate'] === undefined && dateFrom !== undefined) {
259
- toolOptions['startDate'] = dateFrom;
260
- }
261
- return toolOptions;
262
- }, '{"questions": []}');
230
+ return this.safeCallTool('get_parliamentary_questions', options, '{"questions": []}');
263
231
  }
264
232
  /**
265
233
  * Get committee information
266
234
  *
267
- * @param options - Filter options
235
+ * @param options - Filter options: id, abbreviation, showCurrent
268
236
  * @returns Committee info data
269
237
  */
270
238
  async getCommitteeInfo(options = {}) {
271
- return this.safeCallTool('get_committee_info', () => {
272
- const { committeeId, ...rest } = options;
273
- const toolOptions = { ...rest };
274
- // MCP tool schema expects 'abbreviation', not 'committeeId'
275
- if (toolOptions['abbreviation'] === undefined && committeeId !== undefined) {
276
- toolOptions['abbreviation'] = committeeId;
277
- }
278
- return toolOptions;
279
- }, '{"committees": []}');
239
+ return this.safeCallTool('get_committee_info', options, '{"committees": []}');
280
240
  }
281
241
  /**
282
242
  * Monitor legislative pipeline
@@ -319,7 +279,7 @@ export class EuropeanParliamentMCPClient extends MCPConnection {
319
279
  /**
320
280
  * Analyze coalition dynamics and cohesion
321
281
  *
322
- * @param options - Options including optional political groups and date range
282
+ * @param options - Options including optional groupIds and date range
323
283
  * @returns Coalition cohesion and stress analysis
324
284
  */
325
285
  async analyzeCoalitionDynamics(options = {}) {
@@ -328,7 +288,7 @@ export class EuropeanParliamentMCPClient extends MCPConnection {
328
288
  /**
329
289
  * Detect voting anomalies and party defections
330
290
  *
331
- * @param options - Options including optional MEP id, political group, and date
291
+ * @param options - Options including optional MEP id, groupId, and date range
332
292
  * @returns Anomaly detection results
333
293
  */
334
294
  async detectVotingAnomalies(options = {}) {
@@ -337,19 +297,18 @@ export class EuropeanParliamentMCPClient extends MCPConnection {
337
297
  /**
338
298
  * Compare political groups across dimensions
339
299
  *
340
- * @param options - Options including required groups and optional metrics and date
300
+ * @param options - Options including required groupIds and optional dimensions and date range
341
301
  * @returns Cross-group comparative analysis
342
302
  */
343
303
  async comparePoliticalGroups(options) {
344
- const rawGroups = options && Array.isArray(options.groups) ? options.groups : [];
345
- const groups = rawGroups
304
+ const groupIds = (Array.isArray(options.groupIds) ? options.groupIds : [])
346
305
  .map((g) => (typeof g === 'string' ? g.trim() : ''))
347
306
  .filter((g) => g.length > 0);
348
- if (groups.length === 0) {
349
- console.warn('compare_political_groups called without valid groups (non-empty string array required)');
307
+ if (groupIds.length === 0) {
308
+ console.warn('compare_political_groups called without valid groupIds (non-empty string array required)');
350
309
  return { content: [{ type: 'text', text: '{"comparison": {}}' }] };
351
310
  }
352
- return this.safeCallTool('compare_political_groups', { ...options, groups }, '{"comparison": {}}');
311
+ return this.safeCallTool('compare_political_groups', { ...options, groupIds }, '{"comparison": {}}');
353
312
  }
354
313
  /**
355
314
  * Get detailed information about a specific MEP
@@ -367,7 +326,7 @@ export class EuropeanParliamentMCPClient extends MCPConnection {
367
326
  /**
368
327
  * Retrieve voting records with optional filters
369
328
  *
370
- * @param options - Filter options (mepId, sessionId, limit)
329
+ * @param options - Filter options (sessionId, mepId, topic, dateFrom, dateTo, limit, offset)
371
330
  * @returns Voting records data
372
331
  */
373
332
  async getVotingRecords(options = {}) {
@@ -464,7 +423,7 @@ export class EuropeanParliamentMCPClient extends MCPConnection {
464
423
  /**
465
424
  * Get plenary speeches and debate contributions
466
425
  *
467
- * @param options - Filter options including optional speechId or date range
426
+ * @param options - Filter options including optional speechId, dateFrom/dateTo (v1.2.8: year removed)
468
427
  * @returns Speeches data
469
428
  */
470
429
  async getSpeeches(options = {}) {
@@ -473,7 +432,7 @@ export class EuropeanParliamentMCPClient extends MCPConnection {
473
432
  /**
474
433
  * Get legislative procedures
475
434
  *
476
- * @param options - Filter options including optional processId or year
435
+ * @param options - Filter options including optional processId (v1.2.8: year removed)
477
436
  * @returns Procedures data
478
437
  */
479
438
  async getProcedures(options = {}) {
@@ -491,7 +450,7 @@ export class EuropeanParliamentMCPClient extends MCPConnection {
491
450
  /**
492
451
  * Get European Parliament events (hearings, conferences, seminars)
493
452
  *
494
- * @param options - Filter options including optional eventId or date range
453
+ * @param options - Filter options including optional eventId, pagination only (v1.2.8: year/dateFrom/dateTo removed — EP API /events has no date filtering)
495
454
  * @returns Events data
496
455
  */
497
456
  async getEvents(options = {}) {
@@ -571,7 +530,7 @@ export class EuropeanParliamentMCPClient extends MCPConnection {
571
530
  /**
572
531
  * Get committee documents
573
532
  *
574
- * @param options - Filter options including optional docId or year
533
+ * @param options - Filter options including optional docId (v1.2.8: year removed)
575
534
  * @returns Committee documents data
576
535
  */
577
536
  async getCommitteeDocuments(options = {}) {
@@ -607,7 +566,7 @@ export class EuropeanParliamentMCPClient extends MCPConnection {
607
566
  /**
608
567
  * Get external documents (non-EP documents such as Council positions)
609
568
  *
610
- * @param options - Filter options including optional docId or year
569
+ * @param options - Filter options including optional docId (v1.2.8: year removed)
611
570
  * @returns External documents data
612
571
  */
613
572
  async getExternalDocuments(options = {}) {
@@ -708,10 +667,14 @@ export class EuropeanParliamentMCPClient extends MCPConnection {
708
667
  /**
709
668
  * Cross-tool OSINT intelligence correlation engine
710
669
  *
711
- * @param options - Options including optional mepId and correlation scenarios
670
+ * @param options - Options including required mepIds, optional groups, sensitivityLevel, includeNetworkAnalysis
712
671
  * @returns Correlated intelligence alerts and insights
713
672
  */
714
- async correlateIntelligence(options = {}) {
673
+ async correlateIntelligence(options) {
674
+ if (!Array.isArray(options.mepIds) || options.mepIds.length === 0) {
675
+ console.warn('correlate_intelligence called without valid mepIds (non-empty string array required)');
676
+ return { content: [{ type: 'text', text: INTELLIGENCE_FALLBACK }] };
677
+ }
715
678
  return this.safeCallTool('correlate_intelligence', options, INTELLIGENCE_FALLBACK);
716
679
  }
717
680
  /**
@@ -419,7 +419,7 @@ export class MCPConnection {
419
419
  return trimmedKey;
420
420
  }
421
421
  }
422
- const rawScheme = typeof process !== 'undefined' && process.env && process.env['EP_MCP_GATEWAY_AUTH_SCHEME'];
422
+ const rawScheme = typeof process !== 'undefined' && process.env?.['EP_MCP_GATEWAY_AUTH_SCHEME'];
423
423
  const scheme = typeof rawScheme === 'string' ? rawScheme.trim() : '';
424
424
  if (scheme && tokenRegex.test(scheme)) {
425
425
  return `${scheme} ${trimmedKey}`;
@@ -430,6 +430,9 @@ export class MCPConnection {
430
430
  * Attempt a single connection via MCP Gateway (HTTP transport)
431
431
  */
432
432
  async _attemptGatewayConnection() {
433
+ if (!this.gatewayUrl) {
434
+ throw new Error('Gateway URL not configured. Set the EP_MCP_GATEWAY_URL environment variable or provide the gatewayUrl constructor option.');
435
+ }
433
436
  try {
434
437
  const headers = {
435
438
  'Content-Type': 'application/json',
@@ -562,17 +565,24 @@ export class MCPConnection {
562
565
  handleMessage(line) {
563
566
  try {
564
567
  const message = JSON.parse(line);
565
- if (message.id && this.pendingRequests.has(message.id)) {
568
+ if (message.id !== null && message.id !== undefined && this.pendingRequests.has(message.id)) {
566
569
  const pending = this.pendingRequests.get(message.id);
567
- this.pendingRequests.delete(message.id);
568
- if (message.error) {
569
- pending.reject(new Error(message.error.message ?? 'MCP server error'));
570
+ if (pending) {
571
+ this.pendingRequests.delete(message.id);
572
+ if (message.error) {
573
+ pending.reject(new Error(message.error.message ?? 'MCP server error'));
574
+ }
575
+ else {
576
+ pending.resolve(message.result);
577
+ }
570
578
  }
571
579
  else {
572
- pending.resolve(message.result);
580
+ // has() returned true but get() returned undefined — unexpected
581
+ this.pendingRequests.delete(message.id);
582
+ console.error(`MCP pending request ${String(message.id)} vanished before handling`);
573
583
  }
574
584
  }
575
- else if (!message.id && message.method) {
585
+ else if ((message.id === null || message.id === undefined) && message.method) {
576
586
  console.log(`MCP Notification: ${message.method}`);
577
587
  }
578
588
  }
@@ -619,6 +629,9 @@ export class MCPConnection {
619
629
  * @returns Server response
620
630
  */
621
631
  async _sendGatewayRequest(method, params = {}) {
632
+ if (!this.gatewayUrl) {
633
+ throw new Error('Gateway URL not configured. Set EP_MCP_GATEWAY_URL or provide gatewayUrl in MCP client options.');
634
+ }
622
635
  const id = ++this.requestId;
623
636
  const request = {
624
637
  jsonrpc: '2.0',