euparliamentmonitor 0.8.55 → 0.8.57
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 +2 -2
- package/package.json +4 -4
- package/scripts/aggregator/article-html.js +3 -6
- package/scripts/aggregator/artifact-order.d.ts +18 -6
- package/scripts/aggregator/artifact-order.js +23 -11
- package/scripts/constants/build-info-meta.d.ts +1 -1
- package/scripts/constants/build-info-meta.js +14 -11
- package/scripts/generators/news-indexes.js +3 -6
- package/scripts/generators/political-intelligence/html.js +3 -6
- package/scripts/generators/sitemap/html.js +3 -6
- package/scripts/mcp/ep-mcp-client.d.ts +5 -5
- package/scripts/mcp/ep-mcp-client.js +7 -7
- package/scripts/mcp/imf-mcp-client.d.ts +17 -0
- package/scripts/mcp/imf-mcp-client.js +77 -1
- package/scripts/templates/section-builders.js +1 -1
package/README.md
CHANGED
|
@@ -136,7 +136,7 @@ The published site is the audience-facing companion to this npm/TypeScript packa
|
|
|
136
136
|
|
|
137
137
|
**MCP Server Integration**: The project uses the
|
|
138
138
|
[European-Parliament-MCP-Server](https://github.com/Hack23/European-Parliament-MCP-Server)
|
|
139
|
-
v1.2.
|
|
139
|
+
v1.2.21 for accessing real EU Parliament data via the Model Context Protocol.
|
|
140
140
|
|
|
141
141
|
- **MCP Server Status**: ✅ Fully operational — 60+ EP data tools available
|
|
142
142
|
(feeds, direct lookups, analytical tools, intelligence correlation)
|
|
@@ -432,7 +432,7 @@ import type { ArticleCategory, LanguageCode } from 'euparliamentmonitor/types';
|
|
|
432
432
|
|
|
433
433
|
## 🔌 Data Sources
|
|
434
434
|
|
|
435
|
-
**Primary — European Parliament MCP Server** ([Hack23/European-Parliament-MCP-Server](https://github.com/Hack23/European-Parliament-MCP-Server) v1.2.
|
|
435
|
+
**Primary — European Parliament MCP Server** ([Hack23/European-Parliament-MCP-Server](https://github.com/Hack23/European-Parliament-MCP-Server) v1.2.21+, fully operational):
|
|
436
436
|
|
|
437
437
|
- 🗳️ Plenary sessions, voting records, roll-call votes
|
|
438
438
|
- 📜 Adopted texts, motions, resolutions, urgency files
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "euparliamentmonitor",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.57",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "European Parliament Intelligence Platform - Monitor political activity with systematic transparency",
|
|
6
6
|
"main": "scripts/index.js",
|
|
@@ -140,8 +140,8 @@
|
|
|
140
140
|
"@types/markdown-it": "^14.1.2",
|
|
141
141
|
"@types/node": "25.6.0",
|
|
142
142
|
"@types/papaparse": "5.5.2",
|
|
143
|
-
"@typescript-eslint/eslint-plugin": "8.59.
|
|
144
|
-
"@typescript-eslint/parser": "8.59.
|
|
143
|
+
"@typescript-eslint/eslint-plugin": "8.59.2",
|
|
144
|
+
"@typescript-eslint/parser": "8.59.2",
|
|
145
145
|
"@vitest/coverage-v8": "4.1.5",
|
|
146
146
|
"@vitest/ui": "4.1.5",
|
|
147
147
|
"chart.js": "4.5.1",
|
|
@@ -171,7 +171,7 @@
|
|
|
171
171
|
"node": ">=25"
|
|
172
172
|
},
|
|
173
173
|
"dependencies": {
|
|
174
|
-
"european-parliament-mcp-server": "1.2.
|
|
174
|
+
"european-parliament-mcp-server": "1.2.21",
|
|
175
175
|
"markdown-it": "^14.1.1",
|
|
176
176
|
"markdown-it-anchor": "^9.2.0",
|
|
177
177
|
"markdown-it-attrs": "^4.3.1",
|
|
@@ -18,9 +18,9 @@
|
|
|
18
18
|
* `11.14.0`); regenerating articles after a Mermaid bump invalidates
|
|
19
19
|
* browser and CloudFront caches automatically.
|
|
20
20
|
*/
|
|
21
|
-
import { BASE_URL, MERMAID_VERSION } from '../constants/config.js';
|
|
21
|
+
import { BASE_URL, BUILD_SHORT, MERMAID_VERSION } from '../constants/config.js';
|
|
22
22
|
import { buildHeadFreshnessTags } from '../constants/build-info-meta.js';
|
|
23
|
-
import { ALL_LANGUAGES, LANGUAGE_NAMES, LANGUAGE_FLAGS, PAGE_TITLES, SKIP_LINK_TEXTS, TOC_ARIA_LABELS,
|
|
23
|
+
import { ALL_LANGUAGES, LANGUAGE_NAMES, LANGUAGE_FLAGS, PAGE_TITLES, SKIP_LINK_TEXTS, TOC_ARIA_LABELS, getLocalizedString, getTextDirection, } from '../constants/languages.js';
|
|
24
24
|
import { escapeHTML } from '../utils/file-utils.js';
|
|
25
25
|
import { buildSiteFooter, buildSiteHeader, buildPageBanner, } from '../templates/section-builders.js';
|
|
26
26
|
import { READER_GUIDE_SECTION_ID } from './reader-guide-constants.js';
|
|
@@ -233,10 +233,7 @@ ${hreflangLinks}
|
|
|
233
233
|
<link rel="apple-touch-icon" sizes="180x180" href="../images/apple-touch-icon.png">
|
|
234
234
|
<link rel="manifest" href="../site.webmanifest">
|
|
235
235
|
<meta name="theme-color" content="#003399">
|
|
236
|
-
<link rel="stylesheet" href="../styles.css">
|
|
237
|
-
<meta name="ep-i18n-update-text" content="${escapeHTML(getLocalizedString(UPDATE_AVAILABLE_LABELS, safeLang))}">
|
|
238
|
-
<meta name="ep-i18n-update-cta" content="${escapeHTML(getLocalizedString(UPDATE_REFRESH_CTA_LABELS, safeLang))}">
|
|
239
|
-
<meta name="ep-i18n-dismiss" content="${escapeHTML(getLocalizedString(UPDATE_DISMISS_LABELS, safeLang))}">
|
|
236
|
+
<link rel="stylesheet" href="../styles.css?v=${BUILD_SHORT}">
|
|
240
237
|
${buildHeadFreshnessTags('../')}
|
|
241
238
|
<script type="application/ld+json">${jsonLdString}</script>
|
|
242
239
|
<script type="module" src="../js/mermaid-init.js?v=${MERMAID_VERSION}" defer></script>
|
|
@@ -24,12 +24,24 @@ export interface ArtifactSection {
|
|
|
24
24
|
readonly artifacts: readonly string[];
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
|
-
* Canonical ordering. Order
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
27
|
+
* Canonical ordering. Order is shaped for a political-intelligence
|
|
28
|
+
* journalist: news-arc first (story → stakes → forecast), then deep
|
|
29
|
+
* structural context, then provenance/audit appendices:
|
|
30
|
+
*
|
|
31
|
+
* 1. Executive brief 2. Synthesis 3. Significance
|
|
32
|
+
* 4. Actors & forces 5. Coalitions 6. Stakeholders
|
|
33
|
+
* 7. Economic 8. Risk 9. Threat
|
|
34
|
+
* 10. Scenarios 11. Forward (What to Watch) 12. Electoral arc
|
|
35
|
+
* 13. PESTLE & history 14. Continuity 15. Deep analysis
|
|
36
|
+
* 16. Documents 17. Extended 18. MCP audit
|
|
37
|
+
* 19. Quality & reflection.
|
|
38
|
+
*
|
|
39
|
+
* Rationale: Stakeholder Map (who is affected) flows directly into
|
|
40
|
+
* Economic Context (macro stakes) without the structural-frame
|
|
41
|
+
* detour of PESTLE. Risk → Threat → Scenarios → Forward Projection →
|
|
42
|
+
* Electoral Arc forms a coherent "consequences and forecast" block.
|
|
43
|
+
* PESTLE & historical-baseline then opens the deep-context section
|
|
44
|
+
* for readers who want structural backdrop after the news.
|
|
33
45
|
*/
|
|
34
46
|
export declare const ARTIFACT_SECTIONS: readonly ArtifactSection[];
|
|
35
47
|
/** Id of the catch-all bucket for artifacts not matched by any section. */
|
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2024-2026 Hack23 AB
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
/**
|
|
4
|
-
* Canonical ordering. Order
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* Canonical ordering. Order is shaped for a political-intelligence
|
|
5
|
+
* journalist: news-arc first (story → stakes → forecast), then deep
|
|
6
|
+
* structural context, then provenance/audit appendices:
|
|
7
|
+
*
|
|
8
|
+
* 1. Executive brief 2. Synthesis 3. Significance
|
|
9
|
+
* 4. Actors & forces 5. Coalitions 6. Stakeholders
|
|
10
|
+
* 7. Economic 8. Risk 9. Threat
|
|
11
|
+
* 10. Scenarios 11. Forward (What to Watch) 12. Electoral arc
|
|
12
|
+
* 13. PESTLE & history 14. Continuity 15. Deep analysis
|
|
13
|
+
* 16. Documents 17. Extended 18. MCP audit
|
|
14
|
+
* 19. Quality & reflection.
|
|
15
|
+
*
|
|
16
|
+
* Rationale: Stakeholder Map (who is affected) flows directly into
|
|
17
|
+
* Economic Context (macro stakes) without the structural-frame
|
|
18
|
+
* detour of PESTLE. Risk → Threat → Scenarios → Forward Projection →
|
|
19
|
+
* Electoral Arc forms a coherent "consequences and forecast" block.
|
|
20
|
+
* PESTLE & historical-baseline then opens the deep-context section
|
|
21
|
+
* for readers who want structural backdrop after the news.
|
|
10
22
|
*/
|
|
11
23
|
export const ARTIFACT_SECTIONS = [
|
|
12
24
|
{
|
|
@@ -58,11 +70,6 @@ export const ARTIFACT_SECTIONS = [
|
|
|
58
70
|
title: 'Stakeholder Map',
|
|
59
71
|
artifacts: ['intelligence/stakeholder-map.md', 'existing/stakeholder-impact.md'],
|
|
60
72
|
},
|
|
61
|
-
{
|
|
62
|
-
id: 'pestle-context',
|
|
63
|
-
title: 'PESTLE & Context',
|
|
64
|
-
artifacts: ['intelligence/pestle-analysis.md', 'intelligence/historical-baseline.md'],
|
|
65
|
-
},
|
|
66
73
|
{
|
|
67
74
|
id: 'economic-context',
|
|
68
75
|
title: 'Economic Context',
|
|
@@ -120,6 +127,11 @@ export const ARTIFACT_SECTIONS = [
|
|
|
120
127
|
'intelligence/commission-wp-alignment.md',
|
|
121
128
|
],
|
|
122
129
|
},
|
|
130
|
+
{
|
|
131
|
+
id: 'pestle-context',
|
|
132
|
+
title: 'PESTLE & Context',
|
|
133
|
+
artifacts: ['intelligence/pestle-analysis.md', 'intelligence/historical-baseline.md'],
|
|
134
|
+
},
|
|
123
135
|
{
|
|
124
136
|
id: 'continuity',
|
|
125
137
|
title: 'Cross-Run Continuity',
|
|
@@ -2,13 +2,17 @@
|
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
/**
|
|
4
4
|
* @module Constants/BuildInfoMeta
|
|
5
|
-
* @description Shared helper that emits the `<head>`
|
|
6
|
-
* generator must include
|
|
5
|
+
* @description Shared helper that emits the `<head>` build-identity and PWA
|
|
6
|
+
* script tags every generator must include.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* The `build-id` / `build-time` meta tags are informational only — they
|
|
9
|
+
* identify which deploy produced the page (useful for debugging / cache
|
|
10
|
+
* audits) but are **not** consumed by any client-side JavaScript.
|
|
11
|
+
*
|
|
12
|
+
* The `pwa-register.js` script tag loads the service-worker registration
|
|
13
|
+
* and relative-time formatting IIFE. A `?v=<BUILD_SHORT>` query string
|
|
14
|
+
* is appended so that immutable-cached old versions are automatically
|
|
15
|
+
* evicted when a new build is deployed.
|
|
12
16
|
*
|
|
13
17
|
* Every value is HTML-escaped — `BUILD_ID`/`BUILD_TIME` are tightly
|
|
14
18
|
* formatted (40-char hex / ISO 8601) but defence-in-depth is cheap.
|
|
@@ -18,10 +22,10 @@
|
|
|
18
22
|
* 'self'` because the only emitted `<script>` references a same-origin
|
|
19
23
|
* file with a `defer` attribute.
|
|
20
24
|
*/
|
|
21
|
-
import { BUILD_ID, BUILD_TIME } from './config.js';
|
|
25
|
+
import { BUILD_ID, BUILD_SHORT, BUILD_TIME } from './config.js';
|
|
22
26
|
import { escapeHTML } from '../utils/file-utils.js';
|
|
23
27
|
/**
|
|
24
|
-
* Build the shared
|
|
28
|
+
* Build the shared build-identity + PWA `<head>` block.
|
|
25
29
|
*
|
|
26
30
|
* @param pathPrefix - Asset path prefix (`''` for root pages, `'../'`
|
|
27
31
|
* for `news/` pages).
|
|
@@ -31,15 +35,14 @@ import { escapeHTML } from '../utils/file-utils.js';
|
|
|
31
35
|
export function buildHeadFreshnessTags(pathPrefix) {
|
|
32
36
|
const safeBuildId = escapeHTML(BUILD_ID);
|
|
33
37
|
const safeBuildTime = escapeHTML(BUILD_TIME);
|
|
38
|
+
const safeBuildShort = escapeHTML(BUILD_SHORT);
|
|
34
39
|
// Path prefix is built from controlled string literals (`''` or `'../'`),
|
|
35
40
|
// but escape it anyway to keep the helper safe under future callers.
|
|
36
41
|
const safePrefix = escapeHTML(pathPrefix);
|
|
37
42
|
return [
|
|
38
43
|
` <meta name="build-id" content="${safeBuildId}">`,
|
|
39
44
|
` <meta name="build-time" content="${safeBuildTime}">`,
|
|
40
|
-
` <
|
|
41
|
-
` <meta http-equiv="Pragma" content="no-cache">`,
|
|
42
|
-
` <script src="${safePrefix}js/pwa-register.js" defer></script>`,
|
|
45
|
+
` <script src="${safePrefix}js/pwa-register.js?v=${safeBuildShort}" defer></script>`,
|
|
43
46
|
].join('\n');
|
|
44
47
|
}
|
|
45
48
|
//# sourceMappingURL=build-info-meta.js.map
|
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import path, { resolve } from 'path';
|
|
11
11
|
import { pathToFileURL } from 'url';
|
|
12
|
-
import { PROJECT_ROOT, APP_VERSION, NEWS_DIR, BASE_URL } from '../constants/config.js';
|
|
12
|
+
import { PROJECT_ROOT, APP_VERSION, BUILD_SHORT, NEWS_DIR, BASE_URL } from '../constants/config.js';
|
|
13
13
|
import { getNewsIndexSeo } from './seo-copy.js';
|
|
14
14
|
import { buildHeadFreshnessTags } from '../constants/build-info-meta.js';
|
|
15
|
-
import { ALL_LANGUAGES, LANGUAGE_NAMES, LANGUAGE_FLAGS, PAGE_TITLES, PAGE_DESCRIPTIONS, SECTION_HEADINGS, NO_ARTICLES_MESSAGES, SKIP_LINK_TEXTS, AI_SECTION_CONTENT, FILTER_LABELS, ARTICLE_TYPE_LABELS, HEADER_SUBTITLE_LABELS,
|
|
15
|
+
import { ALL_LANGUAGES, LANGUAGE_NAMES, LANGUAGE_FLAGS, PAGE_TITLES, PAGE_DESCRIPTIONS, SECTION_HEADINGS, NO_ARTICLES_MESSAGES, SKIP_LINK_TEXTS, AI_SECTION_CONTENT, FILTER_LABELS, ARTICLE_TYPE_LABELS, HEADER_SUBTITLE_LABELS, getLocalizedString, getTextDirection, } from '../constants/languages.js';
|
|
16
16
|
import { buildSiteFooter, buildSiteHeader } from '../templates/section-builders.js';
|
|
17
17
|
import { getNewsArticles, groupArticlesByLanguage, formatSlug, parseArticleFilename, extractArticleMeta, escapeHTML, atomicWrite, } from '../utils/file-utils.js';
|
|
18
18
|
import { writeMetadataDatabase } from '../utils/news-metadata.js';
|
|
@@ -294,10 +294,7 @@ export function generateIndexHTML(lang, articles, metaMap = new Map()) {
|
|
|
294
294
|
<link rel="manifest" href="site.webmanifest">
|
|
295
295
|
<meta name="theme-color" content="#003399">
|
|
296
296
|
<link rel="alternate" type="application/rss+xml" title="EU Parliament Monitor RSS" href="rss.xml">
|
|
297
|
-
<link rel="stylesheet" href="styles.css">
|
|
298
|
-
<meta name="ep-i18n-update-text" content="${escapeHTML(getLocalizedString(UPDATE_AVAILABLE_LABELS, lang))}">
|
|
299
|
-
<meta name="ep-i18n-update-cta" content="${escapeHTML(getLocalizedString(UPDATE_REFRESH_CTA_LABELS, lang))}">
|
|
300
|
-
<meta name="ep-i18n-dismiss" content="${escapeHTML(getLocalizedString(UPDATE_DISMISS_LABELS, lang))}">
|
|
297
|
+
<link rel="stylesheet" href="styles.css?v=${BUILD_SHORT}">
|
|
301
298
|
${buildHeadFreshnessTags('')}
|
|
302
299
|
<script type="application/ld+json">${websiteJsonLd}</script>
|
|
303
300
|
<script type="application/ld+json">${organizationJsonLd}</script>
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
* link points at GitHub blob/tree URLs so readers can audit the raw
|
|
13
13
|
* tradecraft behind every published article.
|
|
14
14
|
*/
|
|
15
|
-
import { BASE_URL, THEME_TOGGLE_SCRIPT } from '../../constants/config.js';
|
|
15
|
+
import { BASE_URL, BUILD_SHORT, THEME_TOGGLE_SCRIPT } from '../../constants/config.js';
|
|
16
16
|
import { buildHeadFreshnessTags } from '../../constants/build-info-meta.js';
|
|
17
|
-
import { ALL_LANGUAGES, LANGUAGE_FLAGS, LANGUAGE_NAMES, PAGE_TITLES, SKIP_LINK_TEXTS,
|
|
17
|
+
import { ALL_LANGUAGES, LANGUAGE_FLAGS, LANGUAGE_NAMES, PAGE_TITLES, SKIP_LINK_TEXTS, getLocalizedString, getTextDirection, } from '../../constants/languages.js';
|
|
18
18
|
import { FOOTER_SITEMAP_LABELS } from '../../constants/language-ui.js';
|
|
19
19
|
import { buildSiteFooter, buildSiteHeader, buildPageBanner, } from '../../templates/section-builders.js';
|
|
20
20
|
import { escapeHTML } from '../../utils/file-utils.js';
|
|
@@ -418,10 +418,7 @@ ${hreflangLinks}
|
|
|
418
418
|
<link rel="apple-touch-icon" sizes="180x180" href="images/apple-touch-icon.png">
|
|
419
419
|
<link rel="manifest" href="site.webmanifest">
|
|
420
420
|
<meta name="theme-color" content="#003399">
|
|
421
|
-
<link rel="stylesheet" href="styles.css">
|
|
422
|
-
<meta name="ep-i18n-update-text" content="${escapeHTML(getLocalizedString(UPDATE_AVAILABLE_LABELS, lang))}">
|
|
423
|
-
<meta name="ep-i18n-update-cta" content="${escapeHTML(getLocalizedString(UPDATE_REFRESH_CTA_LABELS, lang))}">
|
|
424
|
-
<meta name="ep-i18n-dismiss" content="${escapeHTML(getLocalizedString(UPDATE_DISMISS_LABELS, lang))}">
|
|
421
|
+
<link rel="stylesheet" href="styles.css?v=${BUILD_SHORT}">
|
|
425
422
|
${buildHeadFreshnessTags('')}
|
|
426
423
|
<script type="application/ld+json">${websiteJsonLd}</script>
|
|
427
424
|
<script type="application/ld+json">${organizationJsonLd}</script>
|
|
@@ -19,10 +19,10 @@
|
|
|
19
19
|
* `test/unit/sitemap-byte-equality.test.js` (compares against the
|
|
20
20
|
* golden snapshots taken from `npm run prebuild`).
|
|
21
21
|
*/
|
|
22
|
-
import { BASE_URL, THEME_TOGGLE_SCRIPT } from '../../constants/config.js';
|
|
22
|
+
import { BASE_URL, BUILD_SHORT, THEME_TOGGLE_SCRIPT } from '../../constants/config.js';
|
|
23
23
|
import { buildHeadFreshnessTags } from '../../constants/build-info-meta.js';
|
|
24
24
|
import { getSitemapSeo } from '../seo-copy.js';
|
|
25
|
-
import { ALL_LANGUAGES, LANGUAGE_NAMES, LANGUAGE_FLAGS, PAGE_TITLES, PAGE_DESCRIPTIONS, SKIP_LINK_TEXTS,
|
|
25
|
+
import { ALL_LANGUAGES, LANGUAGE_NAMES, LANGUAGE_FLAGS, PAGE_TITLES, PAGE_DESCRIPTIONS, SKIP_LINK_TEXTS, getLocalizedString, getTextDirection, } from '../../constants/languages.js';
|
|
26
26
|
import { escapeHTML } from '../../utils/file-utils.js';
|
|
27
27
|
import { detectCategory } from '../../utils/article-category.js';
|
|
28
28
|
import { ARTICLE_TYPE_LABELS, FOOTER_POLITICAL_INTELLIGENCE_LABELS, } from '../../constants/language-ui.js';
|
|
@@ -311,10 +311,7 @@ ${hreflangLinks}
|
|
|
311
311
|
<link rel="apple-touch-icon" sizes="180x180" href="images/apple-touch-icon.png">
|
|
312
312
|
<link rel="manifest" href="site.webmanifest">
|
|
313
313
|
<meta name="theme-color" content="#003399">
|
|
314
|
-
<link rel="stylesheet" href="styles.css">
|
|
315
|
-
<meta name="ep-i18n-update-text" content="${escapeHTML(getLocalizedString(UPDATE_AVAILABLE_LABELS, lang))}">
|
|
316
|
-
<meta name="ep-i18n-update-cta" content="${escapeHTML(getLocalizedString(UPDATE_REFRESH_CTA_LABELS, lang))}">
|
|
317
|
-
<meta name="ep-i18n-dismiss" content="${escapeHTML(getLocalizedString(UPDATE_DISMISS_LABELS, lang))}">
|
|
314
|
+
<link rel="stylesheet" href="styles.css?v=${BUILD_SHORT}">
|
|
318
315
|
${buildHeadFreshnessTags('')}
|
|
319
316
|
<script type="application/ld+json">${websiteJsonLd}</script>
|
|
320
317
|
<script type="application/ld+json">${organizationJsonLd}</script>
|
|
@@ -7,7 +7,7 @@ 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, GetFreshProceduresOptions } from '../types/index.js';
|
|
8
8
|
/**
|
|
9
9
|
* Canonical list of tools exposed by the European Parliament MCP gateway
|
|
10
|
-
* (`european-parliament-mcp-server@1.2.
|
|
10
|
+
* (`european-parliament-mcp-server@1.2.21`). The news workflows, prompt
|
|
11
11
|
* library (`.github/prompts/07-mcp-reference.md`), and the integration test
|
|
12
12
|
* suite all reference this list so a regression that adds/removes a tool
|
|
13
13
|
* fails a single drift guard
|
|
@@ -22,7 +22,7 @@ export declare const EP_MCP_TOOLS: readonly string[];
|
|
|
22
22
|
* covering the two shapes historically emitted by the EP MCP server.
|
|
23
23
|
*
|
|
24
24
|
* 1. **Uniform envelope** (all feeds as of
|
|
25
|
-
* `european-parliament-mcp-server@1.2.
|
|
25
|
+
* `european-parliament-mcp-server@1.2.21`) —
|
|
26
26
|
* `{status:"unavailable", items:[], generatedAt:"..."}` established by
|
|
27
27
|
* Hack23/European-Parliament-MCP-Server#301 and extended to
|
|
28
28
|
* `get_events_feed`/`get_procedures_feed` by
|
|
@@ -206,9 +206,9 @@ export declare class EuropeanParliamentMCPClient extends MCPConnection {
|
|
|
206
206
|
*
|
|
207
207
|
* @remarks
|
|
208
208
|
* This repository is currently documented/configured against
|
|
209
|
-
* `european-parliament-mcp-server@1.2.
|
|
209
|
+
* `european-parliament-mcp-server@1.2.21`.
|
|
210
210
|
*
|
|
211
|
-
* **Upstream date-filter contract (v1.2.14+, active on the pinned v1.2.
|
|
211
|
+
* **Upstream date-filter contract (v1.2.14+, active on the pinned v1.2.21 server):** the upstream server
|
|
212
212
|
* applies a server-side post-filter on `dateFrom`/`dateTo` before serialisation, because the
|
|
213
213
|
* EP Open Data Portal `/meetings` endpoint silently ignores its `date-from`/`date-to` query
|
|
214
214
|
* parameters (Defect #5). Under this contract:
|
|
@@ -217,7 +217,7 @@ export declare class EuropeanParliamentMCPClient extends MCPConnection {
|
|
|
217
217
|
* - Per-window session counts are reproducible because the EP-side regression is masked by
|
|
218
218
|
* the upstream post-filter.
|
|
219
219
|
*
|
|
220
|
-
* No local post-filter is applied here. The repository is pinned to v1.2.
|
|
220
|
+
* No local post-filter is applied here. The repository is pinned to v1.2.21, so the
|
|
221
221
|
* date-filter guarantees above apply; consumers running against an older server image
|
|
222
222
|
* (pre-v1.2.14) must not assume them.
|
|
223
223
|
*/
|
|
@@ -11,7 +11,7 @@ import { recordPendingDocument, markDocumentResolved, getPendingDocumentsForRepr
|
|
|
11
11
|
import { EP_NEXT_ELECTION_START, EP_NEXT_ELECTION_END, EP_CURRENT_TERM, EP_NEXT_TERM, } from '../constants/config.js';
|
|
12
12
|
/**
|
|
13
13
|
* Canonical list of tools exposed by the European Parliament MCP gateway
|
|
14
|
-
* (`european-parliament-mcp-server@1.2.
|
|
14
|
+
* (`european-parliament-mcp-server@1.2.21`). The news workflows, prompt
|
|
15
15
|
* library (`.github/prompts/07-mcp-reference.md`), and the integration test
|
|
16
16
|
* suite all reference this list so a regression that adds/removes a tool
|
|
17
17
|
* fails a single drift guard
|
|
@@ -115,7 +115,7 @@ const CONTENT_NOT_YET_AVAILABLE_SUBSTRING = 'document indexed but content not ye
|
|
|
115
115
|
/**
|
|
116
116
|
* Classify an error message into a diagnostic error category.
|
|
117
117
|
*
|
|
118
|
-
* Maps EP MCP Server v1.2.
|
|
118
|
+
* Maps EP MCP Server v1.2.21 structured error codes and generic HTTP/network
|
|
119
119
|
* errors into one of six broad categories used for logging and retry decisions:
|
|
120
120
|
*
|
|
121
121
|
* Returned categories (priority order):
|
|
@@ -131,7 +131,7 @@ const CONTENT_NOT_YET_AVAILABLE_SUBSTRING = 'document indexed but content not ye
|
|
|
131
131
|
*/
|
|
132
132
|
function classifyToolError(message) {
|
|
133
133
|
const lowerMsg = message.toLowerCase();
|
|
134
|
-
// EP MCP Server v1.2.
|
|
134
|
+
// EP MCP Server v1.2.21 structured error codes (matched case-insensitively)
|
|
135
135
|
if (lowerMsg.includes('internal_error')) {
|
|
136
136
|
return 'INTERNAL_ERROR';
|
|
137
137
|
}
|
|
@@ -190,7 +190,7 @@ function _parseResultPayload(result) {
|
|
|
190
190
|
* covering the two shapes historically emitted by the EP MCP server.
|
|
191
191
|
*
|
|
192
192
|
* 1. **Uniform envelope** (all feeds as of
|
|
193
|
-
* `european-parliament-mcp-server@1.2.
|
|
193
|
+
* `european-parliament-mcp-server@1.2.21`) —
|
|
194
194
|
* `{status:"unavailable", items:[], generatedAt:"..."}` established by
|
|
195
195
|
* Hack23/European-Parliament-MCP-Server#301 and extended to
|
|
196
196
|
* `get_events_feed`/`get_procedures_feed` by
|
|
@@ -591,9 +591,9 @@ export class EuropeanParliamentMCPClient extends MCPConnection {
|
|
|
591
591
|
*
|
|
592
592
|
* @remarks
|
|
593
593
|
* This repository is currently documented/configured against
|
|
594
|
-
* `european-parliament-mcp-server@1.2.
|
|
594
|
+
* `european-parliament-mcp-server@1.2.21`.
|
|
595
595
|
*
|
|
596
|
-
* **Upstream date-filter contract (v1.2.14+, active on the pinned v1.2.
|
|
596
|
+
* **Upstream date-filter contract (v1.2.14+, active on the pinned v1.2.21 server):** the upstream server
|
|
597
597
|
* applies a server-side post-filter on `dateFrom`/`dateTo` before serialisation, because the
|
|
598
598
|
* EP Open Data Portal `/meetings` endpoint silently ignores its `date-from`/`date-to` query
|
|
599
599
|
* parameters (Defect #5). Under this contract:
|
|
@@ -602,7 +602,7 @@ export class EuropeanParliamentMCPClient extends MCPConnection {
|
|
|
602
602
|
* - Per-window session counts are reproducible because the EP-side regression is masked by
|
|
603
603
|
* the upstream post-filter.
|
|
604
604
|
*
|
|
605
|
-
* No local post-filter is applied here. The repository is pinned to v1.2.
|
|
605
|
+
* No local post-filter is applied here. The repository is pinned to v1.2.21, so the
|
|
606
606
|
* date-filter guarantees above apply; consumers running against an older server image
|
|
607
607
|
* (pre-v1.2.14) must not assume them.
|
|
608
608
|
*/
|
|
@@ -81,6 +81,10 @@ export interface IMFClientOptions extends MCPClientOptions {
|
|
|
81
81
|
timeoutMs?: number;
|
|
82
82
|
/** Optional `fetch` implementation injection for testing. */
|
|
83
83
|
fetchImpl?: typeof fetch;
|
|
84
|
+
/** MCP fetch-proxy gateway URL (bypasses AWF Squid proxy). */
|
|
85
|
+
fetchProxyGatewayUrl?: string;
|
|
86
|
+
/** API key for the MCP gateway. */
|
|
87
|
+
fetchProxyApiKey?: string;
|
|
84
88
|
}
|
|
85
89
|
/**
|
|
86
90
|
* Count observations in an IMF SDMX-JSON data payload.
|
|
@@ -106,6 +110,8 @@ export declare class IMFMCPClient {
|
|
|
106
110
|
private readonly _apiBaseUrl;
|
|
107
111
|
private readonly _timeoutMs;
|
|
108
112
|
private readonly _fetchImpl;
|
|
113
|
+
private readonly _fetchProxyGatewayUrl;
|
|
114
|
+
private readonly _fetchProxyApiKey;
|
|
109
115
|
private _connected;
|
|
110
116
|
constructor(options?: IMFClientOptions);
|
|
111
117
|
/**
|
|
@@ -214,6 +220,8 @@ export declare class IMFMCPClient {
|
|
|
214
220
|
}): Promise<MCPToolResult>;
|
|
215
221
|
/**
|
|
216
222
|
* Build a full URL and GET it as text, enforcing the client-wide timeout.
|
|
223
|
+
* Tries the MCP fetch-proxy gateway first (bypasses AWF Squid proxy in
|
|
224
|
+
* agentic workflow sandbox), then falls back to direct fetch.
|
|
217
225
|
*
|
|
218
226
|
* @param path - Path (already URL-encoded) to append to the base URL.
|
|
219
227
|
* @returns Response body (`text/*` or `application/*`) as a string.
|
|
@@ -222,6 +230,15 @@ export declare class IMFMCPClient {
|
|
|
222
230
|
* @internal
|
|
223
231
|
*/
|
|
224
232
|
private _getText;
|
|
233
|
+
/**
|
|
234
|
+
* Fetch a URL via the MCP fetch-proxy gateway (JSON-RPC 2.0 over HTTP).
|
|
235
|
+
* The fetch-proxy server runs in a container that bypasses the AWF Squid proxy.
|
|
236
|
+
*
|
|
237
|
+
* @param url - Fully-qualified URL to fetch.
|
|
238
|
+
* @returns Response text, or null if the gateway call fails.
|
|
239
|
+
* @internal
|
|
240
|
+
*/
|
|
241
|
+
private _fetchViaGateway;
|
|
225
242
|
/**
|
|
226
243
|
* GET a URL and parse the response body as JSON.
|
|
227
244
|
*
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
/** Default base URL for the IMF SDMX 3.0 REST API. */
|
|
5
5
|
const DEFAULT_IMF_API_BASE_URL = 'https://dataservices.imf.org/REST/SDMX_3.0';
|
|
6
6
|
/** Default per-request timeout (milliseconds). */
|
|
7
|
-
const DEFAULT_IMF_API_TIMEOUT_MS =
|
|
7
|
+
const DEFAULT_IMF_API_TIMEOUT_MS = 90_000;
|
|
8
8
|
/** Fallback payload shape when an IMF call fails or the server is offline. */
|
|
9
9
|
const IMF_FALLBACK = {
|
|
10
10
|
content: [{ type: 'text', text: '' }],
|
|
@@ -191,6 +191,8 @@ export class IMFMCPClient {
|
|
|
191
191
|
_apiBaseUrl;
|
|
192
192
|
_timeoutMs;
|
|
193
193
|
_fetchImpl;
|
|
194
|
+
_fetchProxyGatewayUrl;
|
|
195
|
+
_fetchProxyApiKey;
|
|
194
196
|
_connected = false;
|
|
195
197
|
constructor(options = {}) {
|
|
196
198
|
const envBase = process.env['IMF_API_BASE_URL'];
|
|
@@ -211,6 +213,11 @@ export class IMFMCPClient {
|
|
|
211
213
|
? parsedEnvTimeout
|
|
212
214
|
: DEFAULT_IMF_API_TIMEOUT_MS;
|
|
213
215
|
this._fetchImpl = options.fetchImpl ?? globalThis.fetch.bind(globalThis);
|
|
216
|
+
// MCP fetch-proxy gateway for AWF sandbox (bypasses Squid proxy)
|
|
217
|
+
this._fetchProxyGatewayUrl =
|
|
218
|
+
options.fetchProxyGatewayUrl ?? process.env['FETCH_MCP_GATEWAY_URL'] ?? undefined;
|
|
219
|
+
this._fetchProxyApiKey =
|
|
220
|
+
options.fetchProxyApiKey ?? process.env['EP_MCP_GATEWAY_API_KEY'] ?? undefined;
|
|
214
221
|
}
|
|
215
222
|
/**
|
|
216
223
|
* Base URL currently in use (read-only — set at construction time).
|
|
@@ -469,6 +476,8 @@ export class IMFMCPClient {
|
|
|
469
476
|
// ─── private transport helpers ─────────────────────────────────────────────
|
|
470
477
|
/**
|
|
471
478
|
* Build a full URL and GET it as text, enforcing the client-wide timeout.
|
|
479
|
+
* Tries the MCP fetch-proxy gateway first (bypasses AWF Squid proxy in
|
|
480
|
+
* agentic workflow sandbox), then falls back to direct fetch.
|
|
472
481
|
*
|
|
473
482
|
* @param path - Path (already URL-encoded) to append to the base URL.
|
|
474
483
|
* @returns Response body (`text/*` or `application/*`) as a string.
|
|
@@ -478,6 +487,18 @@ export class IMFMCPClient {
|
|
|
478
487
|
*/
|
|
479
488
|
async _getText(path) {
|
|
480
489
|
const url = `${this._apiBaseUrl}${path.startsWith('/') ? path : `/${path}`}`;
|
|
490
|
+
// Strategy 1: MCP fetch-proxy gateway (bypasses AWF Squid proxy)
|
|
491
|
+
if (this._fetchProxyGatewayUrl && this._fetchProxyApiKey) {
|
|
492
|
+
try {
|
|
493
|
+
const result = await this._fetchViaGateway(url);
|
|
494
|
+
if (result !== null)
|
|
495
|
+
return result;
|
|
496
|
+
}
|
|
497
|
+
catch {
|
|
498
|
+
// Gateway unavailable — fall through to direct fetch
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
// Strategy 2: Direct fetch (works outside AWF sandbox)
|
|
481
502
|
const controller = new AbortController();
|
|
482
503
|
const timer = setTimeout(() => controller.abort(), this._timeoutMs);
|
|
483
504
|
try {
|
|
@@ -495,6 +516,61 @@ export class IMFMCPClient {
|
|
|
495
516
|
clearTimeout(timer);
|
|
496
517
|
}
|
|
497
518
|
}
|
|
519
|
+
/**
|
|
520
|
+
* Fetch a URL via the MCP fetch-proxy gateway (JSON-RPC 2.0 over HTTP).
|
|
521
|
+
* The fetch-proxy server runs in a container that bypasses the AWF Squid proxy.
|
|
522
|
+
*
|
|
523
|
+
* @param url - Fully-qualified URL to fetch.
|
|
524
|
+
* @returns Response text, or null if the gateway call fails.
|
|
525
|
+
* @internal
|
|
526
|
+
*/
|
|
527
|
+
async _fetchViaGateway(url) {
|
|
528
|
+
const rpcRequest = {
|
|
529
|
+
jsonrpc: '2.0',
|
|
530
|
+
id: Date.now(),
|
|
531
|
+
method: 'tools/call',
|
|
532
|
+
params: {
|
|
533
|
+
name: 'fetch_url',
|
|
534
|
+
arguments: { url },
|
|
535
|
+
},
|
|
536
|
+
};
|
|
537
|
+
const headers = {
|
|
538
|
+
'Content-Type': 'application/json',
|
|
539
|
+
Accept: 'application/json, text/event-stream',
|
|
540
|
+
};
|
|
541
|
+
if (this._fetchProxyApiKey) {
|
|
542
|
+
headers['Authorization'] = `Bearer ${this._fetchProxyApiKey}`;
|
|
543
|
+
}
|
|
544
|
+
const controller = new AbortController();
|
|
545
|
+
const timer = setTimeout(() => controller.abort(), this._timeoutMs);
|
|
546
|
+
try {
|
|
547
|
+
const response = await this._fetchImpl(this._fetchProxyGatewayUrl, {
|
|
548
|
+
method: 'POST',
|
|
549
|
+
headers,
|
|
550
|
+
body: JSON.stringify(rpcRequest),
|
|
551
|
+
signal: controller.signal,
|
|
552
|
+
});
|
|
553
|
+
if (!response.ok)
|
|
554
|
+
return null;
|
|
555
|
+
let body = await response.text();
|
|
556
|
+
// Handle SSE format (data: lines)
|
|
557
|
+
if (body.trimStart().startsWith('data:')) {
|
|
558
|
+
const lines = body.split('\n').filter((l) => l.startsWith('data:'));
|
|
559
|
+
body = lines.map((l) => l.slice(5).trim()).join('');
|
|
560
|
+
}
|
|
561
|
+
const parsed = JSON.parse(body);
|
|
562
|
+
if (parsed.error)
|
|
563
|
+
return null;
|
|
564
|
+
const text = parsed.result?.content?.[0]?.text;
|
|
565
|
+
return text && text.length > 0 ? text : null;
|
|
566
|
+
}
|
|
567
|
+
catch {
|
|
568
|
+
return null;
|
|
569
|
+
}
|
|
570
|
+
finally {
|
|
571
|
+
clearTimeout(timer);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
498
574
|
/**
|
|
499
575
|
* GET a URL and parse the response body as JSON.
|
|
500
576
|
*
|
|
@@ -300,7 +300,7 @@ export function buildSiteHeader(options) {
|
|
|
300
300
|
<a href="${escapeHTML(homeHref)}" class="site-header__brand" aria-label="${safeTitle}">
|
|
301
301
|
<picture class="site-header__logo-picture">
|
|
302
302
|
<source srcset="${pathPrefix}images/banner.webp" type="image/webp">
|
|
303
|
-
<img class="site-header__logo site-header__logo--banner" src="${pathPrefix}images/banner.jpg" alt="${safeTitle}" width="
|
|
303
|
+
<img class="site-header__logo site-header__logo--banner" src="${pathPrefix}images/banner.jpg" alt="${safeTitle}" width="180" height="60" loading="eager">
|
|
304
304
|
</picture>
|
|
305
305
|
<span class="site-header__brand-text">
|
|
306
306
|
<span class="site-header__title">${safeTitle}</span>
|