euparliamentmonitor 0.9.2 → 0.9.4
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/package.json +3 -3
- package/scripts/aggregator/analysis-aggregator.d.ts +11 -0
- package/scripts/aggregator/analysis-aggregator.js +25 -6
- package/scripts/aggregator/article-generator.js +7 -1
- package/scripts/aggregator/article-html.d.ts +44 -2
- package/scripts/aggregator/article-html.js +631 -6
- package/scripts/aggregator/reader-intelligence-guide.d.ts +23 -2
- package/scripts/aggregator/reader-intelligence-guide.js +344 -9
- package/scripts/mcp/fetch-proxy-server.d.ts +1 -13
- package/scripts/mcp/fetch-proxy-server.js +148 -32
- package/scripts/mcp/imf-mcp-client.d.ts +68 -19
- package/scripts/mcp/imf-mcp-client.js +471 -100
- package/scripts/types/imf.d.ts +3 -3
|
@@ -6,18 +6,30 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Implements the Model Context Protocol (JSON-RPC 2.0 over stdio) with a
|
|
8
8
|
* single tool — `fetch_url` — that proxies HTTPS GET requests to the IMF
|
|
9
|
-
* SDMX 3.0 REST API at `https://
|
|
9
|
+
* Data Portal SDMX 3.0 REST API at `https://api.imf.org/external/sdmx/3.0/`.
|
|
10
10
|
*
|
|
11
11
|
* ## Why this exists
|
|
12
12
|
*
|
|
13
13
|
* The Agent Workflow Firewall (AWF) runs a Squid proxy that blocks outbound
|
|
14
|
-
* HTTPS even to allowlisted domains such as `
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
14
|
+
* HTTPS even to allowlisted domains such as `api.imf.org`. This server is
|
|
15
|
+
* mounted as an MCP container in gh-aw workflows; because MCP containers
|
|
16
|
+
* run in a Docker network with direct outbound access (bypassing Squid),
|
|
17
|
+
* `fetch_url` can reach the IMF API while the main runner cannot.
|
|
18
18
|
*
|
|
19
|
-
* The server only allows calls to `https://
|
|
20
|
-
* —
|
|
19
|
+
* The server only allows calls to `https://api.imf.org/external/sdmx/3.0/`
|
|
20
|
+
* — SDMX 2.1 paths and any other URLs are rejected with an error message.
|
|
21
|
+
*
|
|
22
|
+
* ## Authentication
|
|
23
|
+
*
|
|
24
|
+
* The IMF Data Portal API is fronted by Azure API Management and requires
|
|
25
|
+
* a subscription key in the `Ocp-Apim-Subscription-Key` header for every
|
|
26
|
+
* request. The server reads the key from `IMF_API_PRIMARY_KEY` (with
|
|
27
|
+
* `IMF_API_SECONDARY_KEY` as a warm-standby fallback used on `401`/`403`
|
|
28
|
+
* responses to enable zero-downtime key rotation). When neither env var
|
|
29
|
+
* is set, the request is sent unauthenticated and IMF will return `204`
|
|
30
|
+
* (no subscription matched) — useful for diagnosing auth misconfiguration.
|
|
31
|
+
*
|
|
32
|
+
* The header is injected server-side; agent prompts never see the key.
|
|
21
33
|
*
|
|
22
34
|
* ## Usage
|
|
23
35
|
*
|
|
@@ -37,25 +49,27 @@
|
|
|
37
49
|
*/
|
|
38
50
|
import * as readline from 'node:readline';
|
|
39
51
|
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
40
|
-
const IMF_ALLOWED_HOSTNAME = '
|
|
41
|
-
const IMF_ALLOWED_PATH_PREFIX = '/
|
|
52
|
+
const IMF_ALLOWED_HOSTNAME = 'api.imf.org';
|
|
53
|
+
const IMF_ALLOWED_PATH_PREFIX = '/external/sdmx/3.0/';
|
|
42
54
|
const IMF_ALLOWED_PROTOCOL = 'https:';
|
|
43
55
|
/** Per-request fetch timeout (ms). */
|
|
44
56
|
const FETCH_TIMEOUT_MS = 180_000;
|
|
45
57
|
/** Product identifier sent to IMF SDMX endpoints. */
|
|
46
58
|
const IMF_USER_AGENT = 'euparliamentmonitor/0.9.0 (+https://github.com/Hack23/euparliamentmonitor)';
|
|
47
|
-
/** Common
|
|
59
|
+
/** Common headers for IMF SDMX REST requests (auth header added per-request). */
|
|
48
60
|
const IMF_REQUEST_HEADERS = Object.freeze({
|
|
49
61
|
Accept: 'application/json, application/vnd.sdmx.data+json, */*;q=0.8',
|
|
50
62
|
'User-Agent': IMF_USER_AGENT,
|
|
51
63
|
'Accept-Language': 'en-US,en;q=0.9',
|
|
52
64
|
'Cache-Control': 'no-cache',
|
|
53
65
|
});
|
|
66
|
+
/** Azure APIM subscription-key header expected by `api.imf.org`. */
|
|
67
|
+
const IMF_SUBSCRIPTION_KEY_HEADER = 'Ocp-Apim-Subscription-Key';
|
|
54
68
|
// ─── Allowlist check ─────────────────────────────────────────────────────────
|
|
55
69
|
/**
|
|
56
70
|
* Returns `true` when `url` is allowed by the IMF-only fetch-proxy policy.
|
|
57
71
|
*
|
|
58
|
-
* Allowed: `https://
|
|
72
|
+
* Allowed: `https://api.imf.org/external/sdmx/3.0/...` (SDMX 2.1 rejected).
|
|
59
73
|
*
|
|
60
74
|
* @param url - Raw URL string to validate.
|
|
61
75
|
* @returns Whether the URL is permitted.
|
|
@@ -137,17 +151,83 @@ export function handleToolsList(id) {
|
|
|
137
151
|
};
|
|
138
152
|
}
|
|
139
153
|
/**
|
|
140
|
-
*
|
|
154
|
+
* Read IMF subscription keys from the environment, in priority order.
|
|
141
155
|
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
156
|
+
* Returns up to two keys: the primary (first attempt) and the secondary
|
|
157
|
+
* (used to retry on `401`/`403` so live key rotation never breaks a run).
|
|
158
|
+
* Empty / unset keys are filtered out so `[]` is returned only when no
|
|
159
|
+
* key is configured at all.
|
|
145
160
|
*
|
|
146
|
-
* @
|
|
147
|
-
* @
|
|
148
|
-
* @param fetchImpl - Injectable `fetch` implementation (defaults to global).
|
|
149
|
-
* @returns JSON-RPC success or error.
|
|
161
|
+
* @returns Ordered list of candidate API keys (length 0–2).
|
|
162
|
+
* @internal
|
|
150
163
|
*/
|
|
164
|
+
function readImfSubscriptionKeys() {
|
|
165
|
+
const candidates = [process.env['IMF_API_PRIMARY_KEY'], process.env['IMF_API_SECONDARY_KEY']];
|
|
166
|
+
const keys = [];
|
|
167
|
+
for (const k of candidates) {
|
|
168
|
+
if (typeof k === 'string' && k.length > 0 && !keys.includes(k)) {
|
|
169
|
+
keys.push(k);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return keys;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Build the request headers for an outbound IMF call. The `Ocp-Apim-Subscription-Key`
|
|
176
|
+
* header is added when a key is supplied; otherwise the request is sent
|
|
177
|
+
* unauthenticated (and IMF will return `204 No Content`).
|
|
178
|
+
*
|
|
179
|
+
* @param key - Subscription key, or `undefined` to send unauthenticated.
|
|
180
|
+
* @returns Plain object suitable for `fetch(..., { headers })`.
|
|
181
|
+
* @internal
|
|
182
|
+
*/
|
|
183
|
+
function buildImfHeaders(key) {
|
|
184
|
+
const headers = { ...IMF_REQUEST_HEADERS };
|
|
185
|
+
if (key !== undefined && key.length > 0) {
|
|
186
|
+
headers[IMF_SUBSCRIPTION_KEY_HEADER] = key;
|
|
187
|
+
}
|
|
188
|
+
return headers;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Classify a single `fetch()` response from `api.imf.org` so
|
|
192
|
+
* {@link handleFetchUrl} can decide whether to rotate keys, return
|
|
193
|
+
* success, or surface an explicit error.
|
|
194
|
+
*
|
|
195
|
+
* - `204 No Content` → explicit error (Azure APIM accepted the request
|
|
196
|
+
* but no Ocp-Apim-Subscription-Key matched; without this guard the
|
|
197
|
+
* empty body would be indistinguishable from a successful 200).
|
|
198
|
+
* - `401`/`403` with another key available → auth-retry signal.
|
|
199
|
+
* - Any other non-2xx → error with the HTTP status.
|
|
200
|
+
*
|
|
201
|
+
* @internal Exported for tests.
|
|
202
|
+
*
|
|
203
|
+
* @param response - The HTTP response returned by `fetch()`.
|
|
204
|
+
* @param hasNextAttempt - `true` when another subscription key is available for retry.
|
|
205
|
+
* @returns A classified outcome — `'ok'` with body text, `'auth-retry'` to rotate keys,
|
|
206
|
+
* or `'error'` with a JSON-RPC error envelope.
|
|
207
|
+
*/
|
|
208
|
+
async function classifyFetchResponse(response, hasNextAttempt) {
|
|
209
|
+
if ((response.status === 401 || response.status === 403) && hasNextAttempt) {
|
|
210
|
+
return { kind: 'auth-retry', response };
|
|
211
|
+
}
|
|
212
|
+
if (response.status === 204) {
|
|
213
|
+
return {
|
|
214
|
+
kind: 'error',
|
|
215
|
+
rpcError: {
|
|
216
|
+
code: -1,
|
|
217
|
+
message: `HTTP 204 No Content from ${IMF_ALLOWED_HOSTNAME} — likely missing or invalid ${IMF_SUBSCRIPTION_KEY_HEADER} (set IMF_API_PRIMARY_KEY)`,
|
|
218
|
+
},
|
|
219
|
+
response,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
if (!response.ok) {
|
|
223
|
+
return {
|
|
224
|
+
kind: 'error',
|
|
225
|
+
rpcError: { code: -1, message: `HTTP ${response.status} ${response.statusText}` },
|
|
226
|
+
response,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
return { kind: 'ok', text: await response.text() };
|
|
230
|
+
}
|
|
151
231
|
export async function handleFetchUrl(id, url, fetchImpl = globalThis.fetch) {
|
|
152
232
|
if (!url || !isAllowedImfUrl(url)) {
|
|
153
233
|
return {
|
|
@@ -159,29 +239,65 @@ export async function handleFetchUrl(id, url, fetchImpl = globalThis.fetch) {
|
|
|
159
239
|
},
|
|
160
240
|
};
|
|
161
241
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
242
|
+
// Try the primary key, then the secondary key on 401/403 (live rotation).
|
|
243
|
+
// When no keys are configured, fall through to a single unauthenticated
|
|
244
|
+
// attempt so the diagnostic surface (e.g. 204 No Content from IMF) is
|
|
245
|
+
// visible to the caller.
|
|
246
|
+
const keys = readImfSubscriptionKeys();
|
|
247
|
+
const attempts = keys.length > 0 ? [...keys] : [undefined];
|
|
248
|
+
let lastResponse;
|
|
249
|
+
let lastError;
|
|
250
|
+
for (let i = 0; i < attempts.length; i += 1) {
|
|
251
|
+
const key = attempts[i];
|
|
252
|
+
try {
|
|
253
|
+
const response = (await fetchImpl(url, {
|
|
254
|
+
headers: buildImfHeaders(key),
|
|
255
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
256
|
+
}));
|
|
257
|
+
lastResponse = response;
|
|
258
|
+
const outcome = await classifyFetchResponse(response, i + 1 < attempts.length);
|
|
259
|
+
if (outcome.kind === 'auth-retry') {
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
if (outcome.kind === 'error') {
|
|
263
|
+
return { jsonrpc: '2.0', id, error: outcome.rpcError };
|
|
264
|
+
}
|
|
168
265
|
return {
|
|
169
266
|
jsonrpc: '2.0',
|
|
170
267
|
id,
|
|
171
|
-
|
|
268
|
+
result: { content: [{ type: 'text', text: outcome.text }] },
|
|
172
269
|
};
|
|
173
270
|
}
|
|
174
|
-
|
|
271
|
+
catch (err) {
|
|
272
|
+
lastError = err;
|
|
273
|
+
// Network errors are not auth-class — do not retry with the secondary
|
|
274
|
+
// key (the IMF endpoint is the same, only the header differs). Clear
|
|
275
|
+
// any HTTP response captured by an earlier attempt so the post-loop
|
|
276
|
+
// branch does not surface a stale 401/403 in place of this thrown
|
|
277
|
+
// error (the caller needs to see the real failure mode).
|
|
278
|
+
lastResponse = undefined;
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
if (lastError !== undefined) {
|
|
283
|
+
const message = lastError instanceof Error ? lastError.message : String(lastError);
|
|
284
|
+
return { jsonrpc: '2.0', id, error: { code: -1, message } };
|
|
285
|
+
}
|
|
286
|
+
if (lastResponse !== undefined && !lastResponse.ok) {
|
|
175
287
|
return {
|
|
176
288
|
jsonrpc: '2.0',
|
|
177
289
|
id,
|
|
178
|
-
|
|
290
|
+
error: {
|
|
291
|
+
code: -1,
|
|
292
|
+
message: `HTTP ${lastResponse.status} ${lastResponse.statusText}`,
|
|
293
|
+
},
|
|
179
294
|
};
|
|
180
295
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
296
|
+
return {
|
|
297
|
+
jsonrpc: '2.0',
|
|
298
|
+
id,
|
|
299
|
+
error: { code: -1, message: 'fetch_url failed without a response' },
|
|
300
|
+
};
|
|
185
301
|
}
|
|
186
302
|
// ─── Main server loop ─────────────────────────────────────────────────────────
|
|
187
303
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module MCP/IMFMCPClient
|
|
3
3
|
* @description Native TypeScript IMF Data client — calls the IMF SDMX 3.0
|
|
4
|
-
* REST API at {@link https://
|
|
4
|
+
* REST API at {@link https://api.imf.org/external/sdmx/3.0/} via the
|
|
5
5
|
* shared IMF-only `fetch-proxy` MCP gateway in gh-aw/AWF runs and direct
|
|
6
6
|
* `fetch()` in local/non-AWF contexts.
|
|
7
7
|
*
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
*
|
|
35
35
|
* - Uses the native Node 25 `fetch()` — no extra runtime dependency.
|
|
36
36
|
* - Every call has an independent `AbortController` with a configurable
|
|
37
|
-
* timeout (`IMF_API_TIMEOUT_MS`, default
|
|
37
|
+
* timeout (`IMF_API_TIMEOUT_MS`, default 90 s).
|
|
38
38
|
* - Errors (HTTP 4xx/5xx, network faults, JSON parse failures, abort) are
|
|
39
39
|
* caught and converted to the {@link IMF_FALLBACK} envelope. Callers
|
|
40
40
|
* upstream can therefore treat "no IMF" as "empty data" without
|
|
@@ -42,12 +42,17 @@
|
|
|
42
42
|
*
|
|
43
43
|
* Environment variables:
|
|
44
44
|
* - `IMF_API_BASE_URL` — override base URL (default
|
|
45
|
-
* `https://
|
|
46
|
-
* - `IMF_API_TIMEOUT_MS` — per-request timeout (default `
|
|
45
|
+
* `https://api.imf.org/external/sdmx/3.0`).
|
|
46
|
+
* - `IMF_API_TIMEOUT_MS` — per-request timeout (default `90000`).
|
|
47
|
+
* - `IMF_API_PRIMARY_KEY` — Azure APIM subscription key for `api.imf.org`
|
|
48
|
+
* (required since September 2025; sent as `Ocp-Apim-Subscription-Key`).
|
|
49
|
+
* - `IMF_API_SECONDARY_KEY` — warm-standby subscription key, used to retry
|
|
50
|
+
* on `401`/`403` so live key rotation never breaks a run.
|
|
47
51
|
*
|
|
48
52
|
* Historic env vars (`IMF_MCP_GATEWAY_URL`, `IMF_MCP_GATEWAY_API_KEY`,
|
|
49
|
-
* `IMF_MCP_SERVER_PATH`) are no longer consulted —
|
|
50
|
-
*
|
|
53
|
+
* `IMF_MCP_SERVER_PATH`) are no longer consulted — the IMF SDMX surface
|
|
54
|
+
* runs over plain HTTPS (with subscription-key auth) via the AWF
|
|
55
|
+
* fetch-proxy MCP server when reached from a sandboxed agent.
|
|
51
56
|
*/
|
|
52
57
|
import type { MCPToolResult, MCPClientOptions } from '../types/index.js';
|
|
53
58
|
/**
|
|
@@ -113,6 +118,7 @@ export declare class IMFMCPClient {
|
|
|
113
118
|
private readonly _fetchImpl;
|
|
114
119
|
private readonly _fetchProxyGatewayUrl;
|
|
115
120
|
private readonly _fetchProxyApiKey;
|
|
121
|
+
private readonly _imfSubscriptionKeys;
|
|
116
122
|
private _connected;
|
|
117
123
|
constructor(options?: IMFClientOptions);
|
|
118
124
|
/**
|
|
@@ -147,10 +153,16 @@ export declare class IMFMCPClient {
|
|
|
147
153
|
/**
|
|
148
154
|
* List every IMF database (dataflow) exposed by the SDMX 3.0 API.
|
|
149
155
|
*
|
|
150
|
-
* Virtual tool: `imf-list-databases`.
|
|
156
|
+
* Virtual tool: `imf-list-databases`. Hits the umbrella
|
|
157
|
+
* `/structure/dataflow` endpoint which returns every published
|
|
158
|
+
* dataflow across all IMF sub-agencies (`IMF.RES`, `IMF.STA`,
|
|
159
|
+
* `IMF.FAD`, `IMF.WHD`, `IMF.MCM`, …) — typically ~190 entries.
|
|
160
|
+
* Each row includes the publishing `agency` so callers know which
|
|
161
|
+
* agency to use when calling {@link getParameterDefs} or {@link fetchData}.
|
|
151
162
|
*
|
|
152
163
|
* @returns MCP-shaped result whose `content[0].text` carries a JSON
|
|
153
|
-
* array of `{ id, name, description }` entries.
|
|
164
|
+
* array of `{ id, name, description, agency, version }` entries.
|
|
165
|
+
* Empty on error.
|
|
154
166
|
*/
|
|
155
167
|
listDatabases(): Promise<MCPToolResult>;
|
|
156
168
|
/**
|
|
@@ -170,28 +182,37 @@ export declare class IMFMCPClient {
|
|
|
170
182
|
* dataflow. Essential before building an SDMX key for
|
|
171
183
|
* {@link fetchData} because each database has its own dimension set.
|
|
172
184
|
*
|
|
173
|
-
* Virtual tool: `imf-get-parameter-defs`.
|
|
185
|
+
* Virtual tool: `imf-get-parameter-defs`. Uses the
|
|
186
|
+
* `/structure/dataflow/{agency}/{id}/+?references=datastructure`
|
|
187
|
+
* endpoint because the legacy `/structure/datastructure/IMF/{id}/+`
|
|
188
|
+
* shape returns 204 on `api.imf.org` after the September-2025 IMF
|
|
189
|
+
* Data Portal migration retired the umbrella `IMF` agency.
|
|
174
190
|
*
|
|
175
|
-
* @param databaseId - IMF dataflow identifier (e.g. `"WEO"`, `"
|
|
191
|
+
* @param databaseId - IMF dataflow identifier (e.g. `"WEO"`, `"FM"`).
|
|
192
|
+
* @param agencyId - Optional override; defaults to {@link resolveAgency}.
|
|
176
193
|
* @returns MCP-shaped result whose `content[0].text` carries the
|
|
177
194
|
* ordered list of dimensions (`[{ id, name }]`). Empty on error.
|
|
178
195
|
*/
|
|
179
|
-
getParameterDefs(databaseId: string): Promise<MCPToolResult>;
|
|
196
|
+
getParameterDefs(databaseId: string, agencyId?: string): Promise<MCPToolResult>;
|
|
180
197
|
/**
|
|
181
198
|
* List valid codes for a single dimension of an IMF dataflow, with
|
|
182
199
|
* an optional free-text filter to narrow the result.
|
|
183
200
|
*
|
|
184
|
-
* Virtual tool: `imf-get-parameter-codes`.
|
|
185
|
-
* `/
|
|
186
|
-
*
|
|
187
|
-
*
|
|
201
|
+
* Virtual tool: `imf-get-parameter-codes`. Uses
|
|
202
|
+
* `/structure/dataflow/{agency}/{id}/+?references=all` to fetch the
|
|
203
|
+
* DSD plus its referenced conceptSchemes and codelists in one
|
|
204
|
+
* round-trip — SDMX 3.0 binds the codelist on the *concept*
|
|
205
|
+
* (`coreRepresentation.enumeration`), so resolving codes requires
|
|
206
|
+
* walking dim → conceptIdentity → conceptScheme → concept → codelist.
|
|
188
207
|
*
|
|
189
208
|
* @param databaseId - IMF dataflow identifier.
|
|
190
|
-
* @param parameter - Dimension name (e.g. `"
|
|
209
|
+
* @param parameter - Dimension name (e.g. `"COUNTRY"`, `"INDICATOR"`;
|
|
210
|
+
* matched case-insensitively).
|
|
191
211
|
* @param search - Optional free-text search (case-insensitive substring).
|
|
212
|
+
* @param agencyId - Optional agency override; defaults to {@link resolveAgency}.
|
|
192
213
|
* @returns MCP-shaped result with `[{ id, name }]` rows; empty on error.
|
|
193
214
|
*/
|
|
194
|
-
getParameterCodes(databaseId: string, parameter: string, search?: string): Promise<MCPToolResult>;
|
|
215
|
+
getParameterCodes(databaseId: string, parameter: string, search?: string, agencyId?: string): Promise<MCPToolResult>;
|
|
195
216
|
/**
|
|
196
217
|
* Fetch a time-series slice from an IMF dataflow as SDMX-JSON.
|
|
197
218
|
*
|
|
@@ -202,13 +223,17 @@ export declare class IMFMCPClient {
|
|
|
202
223
|
* April-2026 aggregator-pipeline migration.)
|
|
203
224
|
*
|
|
204
225
|
* @param options - Fetch parameters.
|
|
205
|
-
* @param options.databaseId - IMF dataflow ID (`"WEO"`, `"
|
|
226
|
+
* @param options.databaseId - IMF dataflow ID (`"WEO"`, `"FM"`, ...).
|
|
206
227
|
* @param options.startYear - Inclusive start year (e.g. `2015`).
|
|
207
228
|
* @param options.endYear - Inclusive end year (e.g. `2030` for WEO forecasts).
|
|
208
|
-
* @param options.filters - Map of dimension → selected codes.
|
|
229
|
+
* @param options.filters - Map of dimension → selected codes. Filter
|
|
230
|
+
* keys are matched case-insensitively against the DSD dimensions
|
|
231
|
+
* (legacy lowercase `country`/`indicator`/`frequency` continue to work).
|
|
209
232
|
* @param options.dimensionOrder - Optional override of the dimension order
|
|
210
233
|
* used to build the SDMX key. Defaults to
|
|
211
234
|
* {@link defaultDimensionOrder} for the database.
|
|
235
|
+
* @param options.agencyId - Optional SDMX agency override (e.g. `"IMF.RES"`,
|
|
236
|
+
* `"IMF.STA"`). Defaults to {@link resolveAgency}.
|
|
212
237
|
* @returns MCP-shaped result whose `content[0].text` carries the raw
|
|
213
238
|
* SDMX-JSON response. Empty on error or invalid inputs.
|
|
214
239
|
*/
|
|
@@ -218,6 +243,7 @@ export declare class IMFMCPClient {
|
|
|
218
243
|
endYear: number;
|
|
219
244
|
filters: Readonly<Record<string, readonly string[]>>;
|
|
220
245
|
dimensionOrder?: readonly string[];
|
|
246
|
+
agencyId?: string;
|
|
221
247
|
}): Promise<MCPToolResult>;
|
|
222
248
|
/**
|
|
223
249
|
* Build a full URL and GET it as text, enforcing the client-wide timeout.
|
|
@@ -231,6 +257,29 @@ export declare class IMFMCPClient {
|
|
|
231
257
|
* @internal
|
|
232
258
|
*/
|
|
233
259
|
private _getText;
|
|
260
|
+
/**
|
|
261
|
+
* Direct-fetch strategy with subscription-key rotation.
|
|
262
|
+
*
|
|
263
|
+
* Iterates configured `IMF_API_PRIMARY_KEY` → `IMF_API_SECONDARY_KEY`,
|
|
264
|
+
* retrying only on `401`/`403`. Network errors short-circuit immediately.
|
|
265
|
+
*
|
|
266
|
+
* @param url - Fully-qualified IMF SDMX URL.
|
|
267
|
+
* @returns Response body text on success.
|
|
268
|
+
* @throws The last HTTP/network error when all configured keys are exhausted.
|
|
269
|
+
* @internal
|
|
270
|
+
*/
|
|
271
|
+
private _fetchDirectWithKeyRotation;
|
|
272
|
+
/**
|
|
273
|
+
* Single direct-fetch attempt with one subscription key. Classifies the
|
|
274
|
+
* outcome so {@link _fetchDirectWithKeyRotation} can decide whether to
|
|
275
|
+
* rotate keys or surface the error.
|
|
276
|
+
*
|
|
277
|
+
* @param url - Fully-qualified IMF SDMX URL.
|
|
278
|
+
* @param key - Subscription key for this attempt, or `undefined` to send unauthenticated.
|
|
279
|
+
* @returns `'ok'` with body text, `'auth'` with the 401/403 error, or `'error'` for everything else.
|
|
280
|
+
* @internal
|
|
281
|
+
*/
|
|
282
|
+
private _fetchOnceWithKey;
|
|
234
283
|
/**
|
|
235
284
|
* Fetch a URL via the MCP fetch-proxy gateway (JSON-RPC 2.0 over HTTP).
|
|
236
285
|
* The fetch-proxy server runs in a container that bypasses the AWF Squid proxy.
|