@pipeworx/mcp-data360 0.1.1 → 0.1.3

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
@@ -2,7 +2,7 @@
2
2
 
3
3
  World Bank Data360 MCP — the World Bank's modern unified data platform.
4
4
 
5
- Part of [Pipeworx](https://pipeworx.io) — an MCP gateway connecting AI agents to 1679+ live data sources.
5
+ Part of [Pipeworx](https://pipeworx.io) — an MCP gateway connecting AI agents to 1684+ live data sources.
6
6
 
7
7
  ## Tools
8
8
 
@@ -56,7 +56,7 @@ directly, instead of just this one's:
56
56
  }
57
57
  ```
58
58
 
59
- Both URLs reach the same gateway and the same 1679+ data sources. The
59
+ Both URLs reach the same gateway and the same 1684+ data sources. The
60
60
  only difference is which pack's tools are listed **directly**; `ask_pipeworx`
61
61
  reaches all of them from either one.
62
62
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pipeworx/mcp-data360",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "World Bank Data360 MCP — the World Bank's modern unified data platform.",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -26,7 +26,7 @@
26
26
  "@cloudflare/workers-types": "^4.20260405.1"
27
27
  },
28
28
  "pipeworx": {
29
- "sourceHash": "v1-9111b17a396691d0fd844ff83b3d1af903aebd79e12dbba249568b6663c4f4a8",
30
- "sourceCommit": "839ab210d969ef7f822cd747ea516f6bb49e9231"
29
+ "sourceHash": "v1-65667b630bb15d46ce130f4a24495fed088134e0b2301b8e8f7e71ea0734d4c1",
30
+ "sourceCommit": "904686a205e7659f86731df3f45eb88b9333c5a0"
31
31
  }
32
32
  }
package/server.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "name": "io.github.pipeworx-io/data360",
4
4
  "title": "Data360",
5
5
  "description": "World Bank Data360 MCP — the World Bank's modern unified data platform.",
6
- "version": "0.1.1",
6
+ "version": "0.1.3",
7
7
  "websiteUrl": "https://pipeworx.io/packs/data360",
8
8
  "repository": {
9
9
  "url": "https://github.com/pipeworx-io/mcp-data360",
package/src/index.ts CHANGED
@@ -452,7 +452,42 @@ async function fetchWithTimeout(
452
452
  ),
453
453
  );
454
454
  }
455
- throw err;
455
+ // Fleet #2382. Everything that isn't a timeout/abort here is a genuine
456
+ // NETWORK-LEVEL failure — DNS resolution, connection refused, TLS handshake,
457
+ // Cloudflare's own "Network connection lost." — meaning `fetch()` itself
458
+ // threw and no HTTP response of any kind was ever received. Until this fix
459
+ // that raw exception was rethrown VERBATIM: a bare `TypeError: fetch failed`
460
+ // (or the Workers-runtime equivalent) names no upstream, carries no class
461
+ // token, and reads exactly like a defect in OUR code — because it says
462
+ // nothing about the call at all. It landed in `error`, the tier that means
463
+ // "Pipeworx has a defect", for every one of the (at the time of writing)
464
+ // ~470 packs that call this helper directly with no wrapper of their own.
465
+ //
466
+ // `dexscreener` hit this independently (fleet #1579) and fixed it with a
467
+ // bespoke per-pack try/catch around `fetchWithTimeout`. That fix is correct
468
+ // but only covers one pack; every other caller of this shared helper still
469
+ // leaked the raw exception. Moving the same fix HERE — the one place that
470
+ // already carries the timeout case — covers every pack that uses
471
+ // `fetchWithTimeout` without a wrapper, for free, and without widening
472
+ // `classifyToolError`'s regex list: the fix is giving the message a proper
473
+ // `upstream_down:` token at the point the two facts (no response was ever
474
+ // received, and which host we were trying to reach) are actually in hand,
475
+ // not teaching the classifier to guess from prose after the fact.
476
+ //
477
+ // Safe on the same grounds as the timeout branch above: no argument a
478
+ // caller passes can make `fetch()` itself throw a connection-level error,
479
+ // so this is always an availability failure, never a caller mistake. Same
480
+ // `markInternalOrigin` treatment — an origin we run that never answered is
481
+ // still ours, not a third party's outage.
482
+ const raw = err instanceof Error ? err.message : String(err);
483
+ throw new Error(
484
+ markInternalOrigin(
485
+ `upstream_down: could not reach ${name} at all (${raw.slice(0, 160)}). ` +
486
+ `No request reached ${name}, so this says NOTHING about whether the arguments you passed ` +
487
+ 'are valid — do not re-check them on the strength of this error. Retry shortly.',
488
+ url,
489
+ ),
490
+ );
456
491
  }
457
492
  }
458
493
 
@@ -761,7 +796,39 @@ async function callTool(name: string, args: Record<string, unknown>): Promise<un
761
796
  if (period) params.set('TIME_PERIOD', period);
762
797
  params.set('skip', String(numArg(args.skip, 0)));
763
798
  params.set('top', String(numArg(args.top, 100)));
764
- return get(`/data?${params.toString()}`);
799
+ const res = await pwFetch(`${BASE}/data?${params.toString()}`, {
800
+ headers: { Accept: 'application/json', 'User-Agent': UA },
801
+ });
802
+ if (!res.ok) {
803
+ const text = await res.text();
804
+ // Data360 answers an unknown DATABASE_ID with 417 EA500001 "Data
805
+ // retrieval failed" -- the same body it sends on a real outage, so the
806
+ // body cannot tell them apart. Check the id against the database list
807
+ // and only call it not_found when it is genuinely absent (fleet #2497).
808
+ const dbId = params.get('DATABASE_ID')!;
809
+ const known = await databaseIds().catch(() => null);
810
+ if (known && !known.includes(dbId)) {
811
+ const near = known.find((k) => k.toLowerCase() === dbId.toLowerCase());
812
+ return {
813
+ error: 'not_found',
814
+ message: near
815
+ ? `Data360 has no database "${dbId}" -- ids are case-sensitive; did you mean "${near}"?`
816
+ : `Data360 has no database "${dbId}". Call data360_list_databases for valid DATABASE_IDs (e.g. "WB_WDI").`,
817
+ DATABASE_ID: dbId,
818
+ };
819
+ }
820
+ throw new Error(`Data360: ${res.status} ${text.slice(0, 200)}`);
821
+ }
822
+ const data = (await res.json()) as { count?: number; value?: unknown[] };
823
+ if ((data.count ?? data.value?.length ?? 0) === 0) {
824
+ // An unknown INDICATOR / REF_AREA pair is a 200 with zero rows, not an error.
825
+ return {
826
+ ...data,
827
+ empty_reason: 'no_match',
828
+ note: `No observations for INDICATOR "${params.get('INDICATOR')}" in ${params.get('DATABASE_ID')} for REF_AREA "${params.get('REF_AREA')}"${period ? ` in ${period}` : ''}. Check the code with data360_search_indicators (database_id: "${params.get('DATABASE_ID')}"), or drop TIME_PERIOD.`,
829
+ };
830
+ }
831
+ return data;
765
832
  }
766
833
 
767
834
  default:
@@ -779,10 +846,16 @@ async function searchPost(body: unknown): Promise<unknown> {
779
846
  return res.json();
780
847
  }
781
848
 
782
- async function get(path: string): Promise<unknown> {
783
- const res = await pwFetch(`${BASE}${path}`, { headers: { Accept: 'application/json', 'User-Agent': UA } });
784
- if (!res.ok) throw new Error(`Data360: ${res.status} ${await res.text().then((t) => t.slice(0, 200))}`);
785
- return res.json();
849
+ async function databaseIds(): Promise<string[]> {
850
+ const res = (await searchPost({
851
+ count: true,
852
+ search: '*',
853
+ top: 0,
854
+ facets: ['series_description/database_id,count:1000'],
855
+ })) as any;
856
+ const ids: Array<{ value: string }> = res?.['@search.facets']?.['series_description/database_id'] ?? [];
857
+ if (ids.length === 0) throw new Error('Data360 database list came back empty');
858
+ return ids.map((d) => d.value);
786
859
  }
787
860
 
788
861
  function reqStr(args: Record<string, unknown>, key: string, example: string): string {
package/src/server.ts CHANGED
@@ -9,7 +9,7 @@ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprot
9
9
  import pack from './index.js';
10
10
 
11
11
  const server = new Server(
12
- { name: '@pipeworx/mcp-data360', version: '0.1.1' },
12
+ { name: '@pipeworx/mcp-data360', version: '0.1.3' },
13
13
  { capabilities: { tools: {} } },
14
14
  );
15
15