euparliamentmonitor 0.8.36 → 0.8.39

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.9 for accessing real EU Parliament data via the Model Context Protocol.
127
+ v1.2.10 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.36",
3
+ "version": "0.8.39",
4
4
  "type": "module",
5
5
  "description": "European Parliament Intelligence Platform - Monitor political activity with systematic transparency",
6
6
  "main": "scripts/index.js",
@@ -171,7 +171,7 @@
171
171
  "node": ">=25"
172
172
  },
173
173
  "dependencies": {
174
- "european-parliament-mcp-server": "1.2.9"
174
+ "european-parliament-mcp-server": "1.2.10"
175
175
  },
176
176
  "optionalDependencies": {
177
177
  "worldbank-mcp": "1.0.1"
@@ -271,40 +271,42 @@ export declare function fetchMEPsFeedWithTotal(client: EuropeanParliamentMCPClie
271
271
  }>;
272
272
  /**
273
273
  * Fetch documents feed from MCP.
274
- * Falls back to a wider timeframe when the initial timeframe returns no data.
274
+ * The underlying EP MCP `get_documents_feed` tool serves a server-defined
275
+ * fixed window and ignores any `timeframe` parameter; the parameter is kept
276
+ * on this function's signature for backwards compatibility with callers.
275
277
  *
276
278
  * @param client - MCP client or null
277
- * @param timeframe - How far back to look (default: 'one-week')
279
+ * @param _timeframe - Retained for signature compatibility (ignored by the server)
278
280
  * @returns Array of document feed items
279
281
  */
280
- export declare function fetchDocumentsFeed(client: EuropeanParliamentMCPClient | null, timeframe?: FeedTimeframe): Promise<DocumentFeedItem[]>;
282
+ export declare function fetchDocumentsFeed(client: EuropeanParliamentMCPClient | null, _timeframe?: FeedTimeframe): Promise<DocumentFeedItem[]>;
281
283
  /**
282
284
  * Fetch plenary documents feed from MCP.
283
- * Falls back to a wider timeframe when the initial timeframe returns no data.
285
+ * Fixed-window feed; `timeframe` is ignored by the server.
284
286
  *
285
287
  * @param client - MCP client or null
286
- * @param timeframe - How far back to look (default: 'one-week')
288
+ * @param _timeframe - Retained for signature compatibility (ignored by the server)
287
289
  * @returns Array of document feed items
288
290
  */
289
- export declare function fetchPlenaryDocumentsFeed(client: EuropeanParliamentMCPClient | null, timeframe?: FeedTimeframe): Promise<DocumentFeedItem[]>;
291
+ export declare function fetchPlenaryDocumentsFeed(client: EuropeanParliamentMCPClient | null, _timeframe?: FeedTimeframe): Promise<DocumentFeedItem[]>;
290
292
  /**
291
293
  * Fetch committee documents feed from MCP.
292
- * Falls back to a wider timeframe when the initial timeframe returns no data.
294
+ * Fixed-window feed; `timeframe` is ignored by the server.
293
295
  *
294
296
  * @param client - MCP client or null
295
- * @param timeframe - How far back to look (default: 'one-week')
297
+ * @param _timeframe - Retained for signature compatibility (ignored by the server)
296
298
  * @returns Array of document feed items
297
299
  */
298
- export declare function fetchCommitteeDocumentsFeed(client: EuropeanParliamentMCPClient | null, timeframe?: FeedTimeframe): Promise<DocumentFeedItem[]>;
300
+ export declare function fetchCommitteeDocumentsFeed(client: EuropeanParliamentMCPClient | null, _timeframe?: FeedTimeframe): Promise<DocumentFeedItem[]>;
299
301
  /**
300
302
  * Fetch plenary session documents feed from MCP.
301
- * Falls back to a wider timeframe when the initial timeframe returns no data.
303
+ * Fixed-window feed; `timeframe` is ignored by the server.
302
304
  *
303
305
  * @param client - MCP client or null
304
- * @param timeframe - How far back to look (default: 'one-week')
306
+ * @param _timeframe - Retained for signature compatibility (ignored by the server)
305
307
  * @returns Array of document feed items
306
308
  */
307
- export declare function fetchPlenarySessionDocumentsFeed(client: EuropeanParliamentMCPClient | null, timeframe?: FeedTimeframe): Promise<DocumentFeedItem[]>;
309
+ export declare function fetchPlenarySessionDocumentsFeed(client: EuropeanParliamentMCPClient | null, _timeframe?: FeedTimeframe): Promise<DocumentFeedItem[]>;
308
310
  /**
309
311
  * Fetch external documents feed from MCP.
310
312
  * Falls back to a wider timeframe when the initial timeframe returns no data.
@@ -316,13 +318,13 @@ export declare function fetchPlenarySessionDocumentsFeed(client: EuropeanParliam
316
318
  export declare function fetchExternalDocumentsFeed(client: EuropeanParliamentMCPClient | null, timeframe?: FeedTimeframe): Promise<DocumentFeedItem[]>;
317
319
  /**
318
320
  * Fetch parliamentary questions feed from MCP.
319
- * Falls back to a wider timeframe when the initial timeframe returns no data.
321
+ * Fixed-window feed; `timeframe` is ignored by the server.
320
322
  *
321
323
  * @param client - MCP client or null
322
- * @param timeframe - How far back to look (default: 'one-week')
324
+ * @param _timeframe - Retained for signature compatibility (ignored by the server)
323
325
  * @returns Array of question feed items
324
326
  */
325
- export declare function fetchQuestionsFeed(client: EuropeanParliamentMCPClient | null, timeframe?: FeedTimeframe): Promise<QuestionFeedItem[]>;
327
+ export declare function fetchQuestionsFeed(client: EuropeanParliamentMCPClient | null, _timeframe?: FeedTimeframe): Promise<QuestionFeedItem[]>;
326
328
  /**
327
329
  * Fetch MEP declarations feed from MCP.
328
330
  *
@@ -334,11 +336,17 @@ export declare function fetchDeclarationsFeed(client: EuropeanParliamentMCPClien
334
336
  /**
335
337
  * Fetch corporate bodies feed from MCP.
336
338
  *
339
+ * `_timeframe` is retained only for signature compatibility with sliding-window
340
+ * fetchers (so the shared `fetchEPFeedData` orchestrator can dispatch uniformly);
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
343
+ * `INVALID_PARAMS` — see Hack23/European-Parliament-MCP-Server#377).
344
+ *
337
345
  * @param client - MCP client or null
338
- * @param timeframe - How far back to look (default: 'one-week')
346
+ * @param _timeframe - Ignored by the server; kept for signature compatibility
339
347
  * @returns Array of corporate body feed items
340
348
  */
341
- export declare function fetchCorporateBodiesFeed(client: EuropeanParliamentMCPClient | null, timeframe?: FeedTimeframe): Promise<CorporateBodyFeedItem[]>;
349
+ export declare function fetchCorporateBodiesFeed(client: EuropeanParliamentMCPClient | null, _timeframe?: FeedTimeframe): Promise<CorporateBodyFeedItem[]>;
342
350
  /**
343
351
  * Fetch all EP feed data for breaking news articles.
344
352
  * Calls adopted texts, events, procedures, and MEPs feeds in parallel.
@@ -1084,6 +1084,32 @@ class UpstreamTimeoutError extends Error {
1084
1084
  this.name = 'UpstreamTimeoutError';
1085
1085
  }
1086
1086
  }
1087
+ /**
1088
+ * Error thrown when the EP MCP server returns a response indicating the feed
1089
+ * is unavailable (uniform `{status:"unavailable"}` envelope or the legacy raw
1090
+ * upstream 404 envelope historically emitted pre-v1.2.10). Distinct from
1091
+ * {@link UpstreamTimeoutError} so logs/diagnostics do not misattribute a
1092
+ * 404/unavailable response to a timeout. Shares the same control-flow role —
1093
+ * callers treat it as "stop the timeframe-widening retry loop and return the
1094
+ * empty sentinel".
1095
+ */
1096
+ class FeedUnavailableError extends Error {
1097
+ constructor(toolName) {
1098
+ super(`EP MCP feed unavailable for ${toolName} — treating as known-empty`);
1099
+ this.name = 'FeedUnavailableError';
1100
+ }
1101
+ }
1102
+ /**
1103
+ * Type guard: returns `true` for either error type that should stop the
1104
+ * timeframe-widening retry loop ({@link UpstreamTimeoutError} or
1105
+ * {@link FeedUnavailableError}).
1106
+ *
1107
+ * @param error - Caught error value
1108
+ * @returns `true` when the caller should stop retrying and return the empty sentinel
1109
+ */
1110
+ function isStopRetryError(error) {
1111
+ return error instanceof UpstreamTimeoutError || error instanceof FeedUnavailableError;
1112
+ }
1087
1113
  /**
1088
1114
  * Check whether a parsed MCP response envelope indicates an upstream timeout
1089
1115
  * and throw {@link UpstreamTimeoutError} if so. The EP MCP server returns
@@ -1105,6 +1131,26 @@ function checkUpstreamTimeout(value) {
1105
1131
  'Consider using year-based endpoints as fallback.');
1106
1132
  throw new UpstreamTimeoutError(toolName);
1107
1133
  }
1134
+ // Defensive detection of the legacy raw upstream 404 shape that pre-v1.2.10
1135
+ // get_events_feed / get_procedures_feed emitted
1136
+ // (Hack23/European-Parliament-MCP-Server#378, closed by PR #380 in
1137
+ // v1.2.10). Shape:
1138
+ // {"@id":"https://data.europarl.europa.eu/eli/dl/...", "error":"404 N..."}
1139
+ // Treated identically to the uniform `{status:"unavailable"}` envelope —
1140
+ // i.e. stop timeframe-widening retry loops instead of silently returning [].
1141
+ // Retained as belt-and-braces so older pinned server versions (or any future
1142
+ // regression) do not bypass the NOT_FOUND bookkeeping done by the EP MCP
1143
+ // client's safeCallTool.
1144
+ const idField = envelope['@id'];
1145
+ const errorField = envelope['error'];
1146
+ if (typeof idField === 'string' &&
1147
+ idField.startsWith('https://data.europarl.europa.eu/') &&
1148
+ typeof errorField === 'string' &&
1149
+ errorField.includes('404')) {
1150
+ console.warn(`${WARN_PREFIX} EP MCP returned raw upstream 404 shape — treating feed as unavailable ` +
1151
+ '(upstream #378). This should have been caught earlier as a NOT_FOUND failure.');
1152
+ throw new FeedUnavailableError('raw_404_envelope');
1153
+ }
1108
1154
  }
1109
1155
  /**
1110
1156
  * Handle errors from feed-fetching functions with timeframe-widening logic.
@@ -1118,7 +1164,7 @@ function checkUpstreamTimeout(value) {
1118
1164
  * @returns A wider {@link FeedTimeframe} to retry, or `undefined` to stop
1119
1165
  */
1120
1166
  function handleFeedFetchError(error, tf, toolName) {
1121
- if (error instanceof UpstreamTimeoutError)
1167
+ if (isStopRetryError(error))
1122
1168
  return undefined;
1123
1169
  const message = error instanceof Error ? error.message : String(error);
1124
1170
  const wider = getWiderTimeframe(tf);
@@ -1378,152 +1424,101 @@ export async function fetchMEPsFeedWithTotal(client, timeframe = 'one-week') {
1378
1424
  return { items, total };
1379
1425
  }
1380
1426
  catch (error) {
1381
- if (error instanceof UpstreamTimeoutError)
1427
+ if (isStopRetryError(error))
1382
1428
  return { items: [], total: 0 };
1383
1429
  const message = error instanceof Error ? error.message : String(error);
1384
1430
  console.warn(`${WARN_PREFIX} get_meps_feed failed:`, message);
1385
1431
  return { items: [], total: 0 };
1386
1432
  }
1387
1433
  }
1434
+ /**
1435
+ * Fetch a fixed-window EP API v2 feed that ignores the `timeframe` parameter.
1436
+ *
1437
+ * The EP MCP server splits feed tools into two groups — sliding-window feeds
1438
+ * accept `timeframe`/`startDate`, fixed-window feeds (documents,
1439
+ * plenary_documents, committee_documents, plenary_session_documents,
1440
+ * parliamentary_questions, corporate_bodies, controlled_vocabularies) serve a
1441
+ * server-defined window. As of v1.2.10 the server silently ignores
1442
+ * `timeframe`/`startDate` on fixed-window tools
1443
+ * (Hack23/European-Parliament-MCP-Server#379); pre-v1.2.10 it rejected them
1444
+ * with `INVALID_PARAMS` (#377). This helper issues a single RPC either way —
1445
+ * there is no point in timeframe-widening retry loops because the server does
1446
+ * not narrow/widen results based on timeframe.
1447
+ *
1448
+ * @param client - MCP client (null returns `[]`)
1449
+ * @param toolName - Tool name for log messages
1450
+ * @param callFn - Callback that issues the feed RPC
1451
+ * @returns Array of mapped feed items (may be empty)
1452
+ */
1453
+ async function fetchFixedWindowFeed(client, toolName, callFn) {
1454
+ if (!client)
1455
+ return [];
1456
+ try {
1457
+ console.log(`${MCP_FETCH_PREFIX} Fetching ${toolName}...`);
1458
+ const result = await callMCP(callFn, undefined, toolName);
1459
+ return parseFeedResult(result).map((item) => mapFeedItemBase(item));
1460
+ }
1461
+ catch (error) {
1462
+ if (isStopRetryError(error))
1463
+ return [];
1464
+ const message = error instanceof Error ? error.message : String(error);
1465
+ console.warn(`${WARN_PREFIX} ${toolName} failed:`, message);
1466
+ return [];
1467
+ }
1468
+ }
1388
1469
  /**
1389
1470
  * Fetch documents feed from MCP.
1390
- * Falls back to a wider timeframe when the initial timeframe returns no data.
1471
+ * The underlying EP MCP `get_documents_feed` tool serves a server-defined
1472
+ * fixed window and ignores any `timeframe` parameter; the parameter is kept
1473
+ * on this function's signature for backwards compatibility with callers.
1391
1474
  *
1392
1475
  * @param client - MCP client or null
1393
- * @param timeframe - How far back to look (default: 'one-week')
1476
+ * @param _timeframe - Retained for signature compatibility (ignored by the server)
1394
1477
  * @returns Array of document feed items
1395
1478
  */
1396
- export async function fetchDocumentsFeed(client, timeframe = 'one-week') {
1397
- if (!client)
1398
- return [];
1399
- let currentTimeframe = timeframe;
1400
- while (currentTimeframe) {
1401
- const tf = currentTimeframe;
1402
- try {
1403
- console.log(`${MCP_FETCH_PREFIX} Fetching documents feed (${currentTimeframe})...`);
1404
- const result = await callMCP(() => client.getDocumentsFeed({ timeframe: tf, limit: 20 }), undefined, 'get_documents_feed');
1405
- const items = parseFeedResult(result).map((item) => mapFeedItemBase(item));
1406
- if (items.length > 0 || !getWiderTimeframe(currentTimeframe))
1407
- return items;
1408
- console.log(`${INFO_PREFIX} documents feed empty for ${currentTimeframe}, widening timeframe...`);
1409
- currentTimeframe = getWiderTimeframe(currentTimeframe);
1410
- }
1411
- catch (error) {
1412
- const wider = handleFeedFetchError(error, tf, 'get_documents_feed');
1413
- if (wider) {
1414
- currentTimeframe = wider;
1415
- }
1416
- else {
1417
- return [];
1418
- }
1419
- }
1420
- }
1421
- return [];
1479
+ export async function fetchDocumentsFeed(client,
1480
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1481
+ _timeframe = 'one-week') {
1482
+ return fetchFixedWindowFeed(client, 'get_documents_feed', () => client.getDocumentsFeed({ limit: 20 }));
1422
1483
  }
1423
1484
  /**
1424
1485
  * Fetch plenary documents feed from MCP.
1425
- * Falls back to a wider timeframe when the initial timeframe returns no data.
1486
+ * Fixed-window feed; `timeframe` is ignored by the server.
1426
1487
  *
1427
1488
  * @param client - MCP client or null
1428
- * @param timeframe - How far back to look (default: 'one-week')
1489
+ * @param _timeframe - Retained for signature compatibility (ignored by the server)
1429
1490
  * @returns Array of document feed items
1430
1491
  */
1431
- export async function fetchPlenaryDocumentsFeed(client, timeframe = 'one-week') {
1432
- if (!client)
1433
- return [];
1434
- let currentTimeframe = timeframe;
1435
- while (currentTimeframe) {
1436
- const tf = currentTimeframe;
1437
- try {
1438
- console.log(`${MCP_FETCH_PREFIX} Fetching plenary documents feed (${currentTimeframe})...`);
1439
- const result = await callMCP(() => client.getPlenaryDocumentsFeed({ timeframe: tf, limit: 20 }), undefined, 'get_plenary_documents_feed');
1440
- const items = parseFeedResult(result).map((item) => mapFeedItemBase(item));
1441
- if (items.length > 0 || !getWiderTimeframe(currentTimeframe))
1442
- return items;
1443
- console.log(`${INFO_PREFIX} plenary documents feed empty for ${currentTimeframe}, widening timeframe...`);
1444
- currentTimeframe = getWiderTimeframe(currentTimeframe);
1445
- }
1446
- catch (error) {
1447
- const wider = handleFeedFetchError(error, tf, 'get_plenary_documents_feed');
1448
- if (wider) {
1449
- currentTimeframe = wider;
1450
- }
1451
- else {
1452
- return [];
1453
- }
1454
- }
1455
- }
1456
- return [];
1492
+ export async function fetchPlenaryDocumentsFeed(client,
1493
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1494
+ _timeframe = 'one-week') {
1495
+ return fetchFixedWindowFeed(client, 'get_plenary_documents_feed', () => client.getPlenaryDocumentsFeed({ limit: 20 }));
1457
1496
  }
1458
1497
  /**
1459
1498
  * Fetch committee documents feed from MCP.
1460
- * Falls back to a wider timeframe when the initial timeframe returns no data.
1499
+ * Fixed-window feed; `timeframe` is ignored by the server.
1461
1500
  *
1462
1501
  * @param client - MCP client or null
1463
- * @param timeframe - How far back to look (default: 'one-week')
1502
+ * @param _timeframe - Retained for signature compatibility (ignored by the server)
1464
1503
  * @returns Array of document feed items
1465
1504
  */
1466
- export async function fetchCommitteeDocumentsFeed(client, timeframe = 'one-week') {
1467
- if (!client)
1468
- return [];
1469
- let currentTimeframe = timeframe;
1470
- while (currentTimeframe) {
1471
- const tf = currentTimeframe;
1472
- try {
1473
- console.log(`${MCP_FETCH_PREFIX} Fetching committee documents feed (${currentTimeframe})...`);
1474
- const result = await callMCP(() => client.getCommitteeDocumentsFeed({ timeframe: tf, limit: 20 }), undefined, 'get_committee_documents_feed');
1475
- const items = parseFeedResult(result).map((item) => mapFeedItemBase(item));
1476
- if (items.length > 0 || !getWiderTimeframe(currentTimeframe))
1477
- return items;
1478
- console.log(`${INFO_PREFIX} committee documents feed empty for ${currentTimeframe}, widening timeframe...`);
1479
- currentTimeframe = getWiderTimeframe(currentTimeframe);
1480
- }
1481
- catch (error) {
1482
- const wider = handleFeedFetchError(error, tf, 'get_committee_documents_feed');
1483
- if (wider) {
1484
- currentTimeframe = wider;
1485
- }
1486
- else {
1487
- return [];
1488
- }
1489
- }
1490
- }
1491
- return [];
1505
+ export async function fetchCommitteeDocumentsFeed(client,
1506
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1507
+ _timeframe = 'one-week') {
1508
+ return fetchFixedWindowFeed(client, 'get_committee_documents_feed', () => client.getCommitteeDocumentsFeed({ limit: 20 }));
1492
1509
  }
1493
1510
  /**
1494
1511
  * Fetch plenary session documents feed from MCP.
1495
- * Falls back to a wider timeframe when the initial timeframe returns no data.
1512
+ * Fixed-window feed; `timeframe` is ignored by the server.
1496
1513
  *
1497
1514
  * @param client - MCP client or null
1498
- * @param timeframe - How far back to look (default: 'one-week')
1515
+ * @param _timeframe - Retained for signature compatibility (ignored by the server)
1499
1516
  * @returns Array of document feed items
1500
1517
  */
1501
- export async function fetchPlenarySessionDocumentsFeed(client, timeframe = 'one-week') {
1502
- if (!client)
1503
- return [];
1504
- let currentTimeframe = timeframe;
1505
- while (currentTimeframe) {
1506
- const tf = currentTimeframe;
1507
- try {
1508
- console.log(`${MCP_FETCH_PREFIX} Fetching plenary session documents feed (${currentTimeframe})...`);
1509
- const result = await callMCP(() => client.getPlenarySessionDocumentsFeed({ timeframe: tf, limit: 20 }), undefined, 'get_plenary_session_documents_feed');
1510
- const items = parseFeedResult(result).map((item) => mapFeedItemBase(item));
1511
- if (items.length > 0 || !getWiderTimeframe(currentTimeframe))
1512
- return items;
1513
- console.log(`${INFO_PREFIX} plenary session docs feed empty for ${currentTimeframe}, widening timeframe...`);
1514
- currentTimeframe = getWiderTimeframe(currentTimeframe);
1515
- }
1516
- catch (error) {
1517
- const wider = handleFeedFetchError(error, tf, 'get_plenary_session_documents_feed');
1518
- if (wider) {
1519
- currentTimeframe = wider;
1520
- }
1521
- else {
1522
- return [];
1523
- }
1524
- }
1525
- }
1526
- return [];
1518
+ export async function fetchPlenarySessionDocumentsFeed(client,
1519
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1520
+ _timeframe = 'one-week') {
1521
+ return fetchFixedWindowFeed(client, 'get_plenary_session_documents_feed', () => client.getPlenarySessionDocumentsFeed({ limit: 20 }));
1527
1522
  }
1528
1523
  /**
1529
1524
  * Fetch external documents feed from MCP.
@@ -1562,38 +1557,16 @@ export async function fetchExternalDocumentsFeed(client, timeframe = 'one-week')
1562
1557
  }
1563
1558
  /**
1564
1559
  * Fetch parliamentary questions feed from MCP.
1565
- * Falls back to a wider timeframe when the initial timeframe returns no data.
1560
+ * Fixed-window feed; `timeframe` is ignored by the server.
1566
1561
  *
1567
1562
  * @param client - MCP client or null
1568
- * @param timeframe - How far back to look (default: 'one-week')
1563
+ * @param _timeframe - Retained for signature compatibility (ignored by the server)
1569
1564
  * @returns Array of question feed items
1570
1565
  */
1571
- export async function fetchQuestionsFeed(client, timeframe = 'one-week') {
1572
- if (!client)
1573
- return [];
1574
- let currentTimeframe = timeframe;
1575
- while (currentTimeframe) {
1576
- const tf = currentTimeframe;
1577
- try {
1578
- console.log(`${MCP_FETCH_PREFIX} Fetching parliamentary questions feed (${currentTimeframe})...`);
1579
- const result = await callMCP(() => client.getParliamentaryQuestionsFeed({ timeframe: tf, limit: 20 }), undefined, 'get_parliamentary_questions_feed');
1580
- const items = parseFeedResult(result).map((item) => mapFeedItemBase(item));
1581
- if (items.length > 0 || !getWiderTimeframe(currentTimeframe))
1582
- return items;
1583
- console.log(`${INFO_PREFIX} questions feed empty for ${currentTimeframe}, widening timeframe...`);
1584
- currentTimeframe = getWiderTimeframe(currentTimeframe);
1585
- }
1586
- catch (error) {
1587
- const wider = handleFeedFetchError(error, tf, 'get_parliamentary_questions_feed');
1588
- if (wider) {
1589
- currentTimeframe = wider;
1590
- }
1591
- else {
1592
- return [];
1593
- }
1594
- }
1595
- }
1596
- return [];
1566
+ export async function fetchQuestionsFeed(client,
1567
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1568
+ _timeframe = 'one-week') {
1569
+ return fetchFixedWindowFeed(client, 'get_parliamentary_questions_feed', () => client.getParliamentaryQuestionsFeed({ limit: 20 }));
1597
1570
  }
1598
1571
  /**
1599
1572
  * Fetch MEP declarations feed from MCP.
@@ -1611,7 +1584,7 @@ export async function fetchDeclarationsFeed(client, timeframe = 'one-week') {
1611
1584
  return parseFeedResult(result).map((item) => mapFeedItemBase(item));
1612
1585
  }
1613
1586
  catch (error) {
1614
- if (error instanceof UpstreamTimeoutError)
1587
+ if (isStopRetryError(error))
1615
1588
  return [];
1616
1589
  const message = error instanceof Error ? error.message : String(error);
1617
1590
  console.warn(`${WARN_PREFIX} get_mep_declarations_feed failed:`, message);
@@ -1621,20 +1594,28 @@ export async function fetchDeclarationsFeed(client, timeframe = 'one-week') {
1621
1594
  /**
1622
1595
  * Fetch corporate bodies feed from MCP.
1623
1596
  *
1597
+ * `_timeframe` is retained only for signature compatibility with sliding-window
1598
+ * fetchers (so the shared `fetchEPFeedData` orchestrator can dispatch uniformly);
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
1601
+ * `INVALID_PARAMS` — see Hack23/European-Parliament-MCP-Server#377).
1602
+ *
1624
1603
  * @param client - MCP client or null
1625
- * @param timeframe - How far back to look (default: 'one-week')
1604
+ * @param _timeframe - Ignored by the server; kept for signature compatibility
1626
1605
  * @returns Array of corporate body feed items
1627
1606
  */
1628
- export async function fetchCorporateBodiesFeed(client, timeframe = 'one-week') {
1607
+ export async function fetchCorporateBodiesFeed(client,
1608
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1609
+ _timeframe = 'one-week') {
1629
1610
  if (!client)
1630
1611
  return [];
1631
1612
  try {
1632
- console.log(`${MCP_FETCH_PREFIX} Fetching corporate bodies feed (${timeframe})...`);
1633
- const result = await callMCP(() => client.getCorporateBodiesFeed({ timeframe, limit: 20 }), undefined, 'get_corporate_bodies_feed');
1613
+ console.log(`${MCP_FETCH_PREFIX} Fetching corporate bodies feed (fixed window)...`);
1614
+ const result = await callMCP(() => client.getCorporateBodiesFeed({ limit: 20 }), undefined, 'get_corporate_bodies_feed');
1634
1615
  return parseFeedResult(result).map((item) => mapFeedItemBase(item));
1635
1616
  }
1636
1617
  catch (error) {
1637
- if (error instanceof UpstreamTimeoutError)
1618
+ if (isStopRetryError(error))
1638
1619
  return [];
1639
1620
  const message = error instanceof Error ? error.message : String(error);
1640
1621
  console.warn(`${WARN_PREFIX} get_corporate_bodies_feed failed:`, message);
@@ -37,6 +37,7 @@ export * from './types/index.js';
37
37
  export { MCPConnection, MCPSessionExpiredError, MCPRateLimitError, isRetriableError, formatRetryAfter, parseSSEResponse, } from './mcp/mcp-connection.js';
38
38
  export { EuropeanParliamentMCPClient, getEPMCPClient, closeEPMCPClient, } from './mcp/ep-mcp-client.js';
39
39
  export { WorldBankMCPClient, getWBMCPClient, closeWBMCPClient } from './mcp/wb-mcp-client.js';
40
+ export { IMFMCPClient, IMF_MCP_TOOLS, getIMFMCPClient, closeIMFMCPClient, } from './mcp/imf-mcp-client.js';
40
41
  export { type CircuitState, type CircuitBreakerOptions, CircuitBreaker, type MCPRetryPolicy, withRetry, } from './mcp/mcp-retry.js';
41
42
  export { type ToolHealthEntry, type HealthSnapshot, MCPHealthMonitor } from './mcp/mcp-health.js';
42
43
  export { scoreVotingAnomaly, analyzeCoalitionCohesion, scoreMEPInfluence, calculateLegislativeVelocity, rankBySignificance, buildIntelligenceSection, buildDefaultStakeholderPerspectives, scoreStakeholderInfluence, buildStakeholderOutcomeMatrix, rankStakeholdersByInfluence, computeVotingIntensity, detectCoalitionShifts, computePolarizationIndex, detectVotingTrends, computeCrossSessionCoalitionStability, rankMEPInfluenceByTopic, buildLegislativeVelocityReport, } from './utils/intelligence-analysis.js';
@@ -45,6 +46,7 @@ export { assessAnalysisDepth, assessStakeholderCoverage, assessVisualizationQual
45
46
  export { scoreSignificance, scoreBatch, clampScore, deriveDecision, formatScoreMarkdown, formatBatchMarkdown, WEIGHT_PARLIAMENTARY, WEIGHT_POLICY, WEIGHT_PUBLIC_INTEREST, WEIGHT_URGENCY, WEIGHT_INSTITUTIONAL, THRESHOLD_PUBLISH, THRESHOLD_HOLD, } from './utils/significance-scoring.js';
46
47
  export { parseFrontmatter, aggregateSWOT, aggregateRisks, extractSummaryLine, aggregateConfidence, findMarkdownFiles, generateEditorialRecommendations, buildSynthesisSummary, formatSynthesisMarkdown, } from './generators/synthesis-summary.js';
47
48
  export { validateArticleContent, validateTranslationCompleteness, } from './utils/content-validator.js';
49
+ export { WORLD_BANK_STRONG_FINGERPRINTS, WORLD_BANK_INDICATOR_CODES, WORLD_BANK_FINGERPRINTS, hasWorldBankEvidence, articlePolicyHasWorldBank, IMF_STRONG_FINGERPRINTS, IMF_INDICATOR_CODES, hasIMFEvidence, articlePolicyHasEconomicContext, } from './utils/content-validator.js';
48
50
  export { enrichMetadataFromContent } from './utils/content-metadata.js';
49
51
  export { buildMetadataDatabase, writeMetadataDatabase, readMetadataDatabase, updateMetadataDatabase, updateIntelligenceIndex, } from './utils/news-metadata.js';
50
52
  export { pl, pl as pluralizeCount } from './utils/metadata-utils.js';
@@ -53,6 +55,7 @@ export { stripHtmlTags, stripScriptBlocks } from './utils/html-sanitize.js';
53
55
  export { parseArticleFilename, formatSlug, calculateReadTime, escapeHTML, isSafeURL, validateArticleHTML, type ArticleValidationResult, } from './utils/file-utils.js';
54
56
  export { detectCategory } from './utils/article-category.js';
55
57
  export { EU_COUNTRY_CODES, EU_AGGREGATE_CODE, COMPARISON_COUNTRIES, WB_AGGREGATE_LABELS, POLICY_INDICATORS, parseWorldBankCSV, formatIndicatorValue, getMostRecentValue, buildEconomicContext, getWorldBankCountryCode, isEUMemberState, buildEconomicContextHTML, } from './utils/world-bank-data.js';
58
+ export { IMF_EU_COUNTRY_CODES, IMF_COUNTRY_CODE_OVERRIDES, IMF_EURO_AREA_CODE, IMF_AGGREGATE_LABELS, IMF_POLICY_INDICATORS, IMF_INDICATOR_SDMX_CODES, getIMFCountryCode, isIMFEUMemberState, parseSDMXJSON, getMostRecentObservation, getForecastPoints, formatIMFValue, buildIMFEconomicContext, buildIMFEconomicContextHTML, } from './utils/imf-data.js';
56
59
  export { generateArticleHTML } from './templates/article-template.js';
57
60
  export { computeArticleQualityScore, buildTableOfContents, buildQualityScoreBadge, } from './templates/section-builders.js';
58
61
  export { ALL_LANGUAGES, LANGUAGE_PRESETS, LANGUAGE_FLAGS, LANGUAGE_NAMES, getLocalizedString, isSupportedLanguage, getTextDirection, } from './constants/language-core.js';
package/scripts/index.js CHANGED
@@ -41,6 +41,7 @@ export * from './types/index.js';
41
41
  export { MCPConnection, MCPSessionExpiredError, MCPRateLimitError, isRetriableError, formatRetryAfter, parseSSEResponse, } from './mcp/mcp-connection.js';
42
42
  export { EuropeanParliamentMCPClient, getEPMCPClient, closeEPMCPClient, } from './mcp/ep-mcp-client.js';
43
43
  export { WorldBankMCPClient, getWBMCPClient, closeWBMCPClient } from './mcp/wb-mcp-client.js';
44
+ export { IMFMCPClient, IMF_MCP_TOOLS, getIMFMCPClient, closeIMFMCPClient, } from './mcp/imf-mcp-client.js';
44
45
  export { CircuitBreaker, withRetry, } from './mcp/mcp-retry.js';
45
46
  export { MCPHealthMonitor } from './mcp/mcp-health.js';
46
47
  // ─── Intelligence Analysis ───────────────────────────────────────────────────
@@ -55,6 +56,8 @@ export { scoreSignificance, scoreBatch, clampScore, deriveDecision, formatScoreM
55
56
  export { parseFrontmatter, aggregateSWOT, aggregateRisks, extractSummaryLine, aggregateConfidence, findMarkdownFiles, generateEditorialRecommendations, buildSynthesisSummary, formatSynthesisMarkdown, } from './generators/synthesis-summary.js';
56
57
  // ─── Content Validation ──────────────────────────────────────────────────────
57
58
  export { validateArticleContent, validateTranslationCompleteness, } from './utils/content-validator.js';
59
+ // ─── Economic-context evidence helpers (Wave 1 dual-source) ──────────────────
60
+ export { WORLD_BANK_STRONG_FINGERPRINTS, WORLD_BANK_INDICATOR_CODES, WORLD_BANK_FINGERPRINTS, hasWorldBankEvidence, articlePolicyHasWorldBank, IMF_STRONG_FINGERPRINTS, IMF_INDICATOR_CODES, hasIMFEvidence, articlePolicyHasEconomicContext, } from './utils/content-validator.js';
58
61
  // ─── Content Metadata ────────────────────────────────────────────────────────
59
62
  export { enrichMetadataFromContent } from './utils/content-metadata.js';
60
63
  // ─── News Metadata ───────────────────────────────────────────────────────────
@@ -70,6 +73,8 @@ export { parseArticleFilename, formatSlug, calculateReadTime, escapeHTML, isSafe
70
73
  export { detectCategory } from './utils/article-category.js';
71
74
  // ─── World Bank Data Utilities ───────────────────────────────────────────────
72
75
  export { EU_COUNTRY_CODES, EU_AGGREGATE_CODE, COMPARISON_COUNTRIES, WB_AGGREGATE_LABELS, POLICY_INDICATORS, parseWorldBankCSV, formatIndicatorValue, getMostRecentValue, buildEconomicContext, getWorldBankCountryCode, isEUMemberState, buildEconomicContextHTML, } from './utils/world-bank-data.js';
76
+ // ─── IMF Data Utilities ──────────────────────────────────────────────────────
77
+ export { IMF_EU_COUNTRY_CODES, IMF_COUNTRY_CODE_OVERRIDES, IMF_EURO_AREA_CODE, IMF_AGGREGATE_LABELS, IMF_POLICY_INDICATORS, IMF_INDICATOR_SDMX_CODES, getIMFCountryCode, isIMFEUMemberState, parseSDMXJSON, getMostRecentObservation, getForecastPoints, formatIMFValue, buildIMFEconomicContext, buildIMFEconomicContextHTML, } from './utils/imf-data.js';
73
78
  // ─── Templates ───────────────────────────────────────────────────────────────
74
79
  export { generateArticleHTML } from './templates/article-template.js';
75
80
  export { computeArticleQualityScore, buildTableOfContents, buildQualityScoreBadge, } from './templates/section-builders.js';