@pipeworx/mcp-datacube-sk 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 +2 -2
- package/package.json +3 -3
- package/server.json +1 -1
- package/src/index.ts +82 -3
- package/src/server.ts +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
DATAcube — Statistical Office of the Slovak Republic (data.statistics.sk).
|
|
4
4
|
|
|
5
|
-
Part of [Pipeworx](https://pipeworx.io) — an MCP gateway connecting AI agents to
|
|
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
|
|
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-datacube-sk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "DATAcube — Statistical Office of the Slovak Republic (data.statistics.sk).",
|
|
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-
|
|
30
|
-
"sourceCommit": "
|
|
29
|
+
"sourceHash": "v1-1f5767dfe4a92bdc524970d0bd33d2502d4fd95a7ec3e33a948e5bfff971addc",
|
|
30
|
+
"sourceCommit": "a00767651388cfd017fdeac82188622721db2051"
|
|
31
31
|
}
|
|
32
32
|
}
|
package/server.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "io.github.pipeworx-io/datacube-sk",
|
|
4
4
|
"title": "Datacube Sk",
|
|
5
5
|
"description": "DATAcube — Statistical Office of the Slovak Republic (data.statistics.sk).",
|
|
6
|
-
"version": "0.1.
|
|
6
|
+
"version": "0.1.3",
|
|
7
7
|
"websiteUrl": "https://pipeworx.io/packs/datacube-sk",
|
|
8
8
|
"repository": {
|
|
9
9
|
"url": "https://github.com/pipeworx-io/mcp-datacube-sk",
|
package/src/index.ts
CHANGED
|
@@ -452,7 +452,42 @@ async function fetchWithTimeout(
|
|
|
452
452
|
),
|
|
453
453
|
);
|
|
454
454
|
}
|
|
455
|
-
|
|
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
|
|
|
@@ -795,17 +830,61 @@ async function getData(args: Record<string, unknown>): Promise<unknown> {
|
|
|
795
830
|
throw new Error('selections must be a non-empty array of strings (one per dimension, in dataset_dimensions order). Use dataset_dimensions to find dimension order and valid codes.');
|
|
796
831
|
}
|
|
797
832
|
const path = (sel as string[]).map((s) => encodeURIComponent(s.trim())).join('/');
|
|
798
|
-
|
|
833
|
+
let out: { size?: number[]; id?: string[]; value?: unknown[] | Record<string, unknown> };
|
|
834
|
+
try {
|
|
835
|
+
out = (await dcGet(`/dataset/${encodeURIComponent(cube)}/${path}?lang=${lang}`)) as typeof out;
|
|
836
|
+
} catch (err) {
|
|
837
|
+
// DATAcube answers an unknown cube with 400 {"status_message":"Name API not
|
|
838
|
+
// foud: <cube>"} (sic). That threw as a Pipeworx defect (fleet #2496).
|
|
839
|
+
if (err instanceof DcHttpError && err.status === 400 && /not fou?n?d/i.test(err.body)) {
|
|
840
|
+
return {
|
|
841
|
+
error: 'not_found',
|
|
842
|
+
message: `DATAcube has no cube "${cube}". Call list_datasets({query: "..."}) to find the cube code.`,
|
|
843
|
+
cube,
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
throw err;
|
|
847
|
+
}
|
|
848
|
+
// A selection code the cube does not have is NOT an error upstream: DATAcube
|
|
849
|
+
// returns a normal JSON-stat dataset with that dimension's size 0 and no
|
|
850
|
+
// values — a plain success with nothing in it (fleet #2496). Say so.
|
|
851
|
+
const size = Array.isArray(out?.size) ? out.size : [];
|
|
852
|
+
const zeroDims = (out?.id ?? []).filter((_, i) => size[i] === 0);
|
|
853
|
+
const noValues = Array.isArray(out?.value) ? out.value.length === 0 : out?.value != null && Object.keys(out.value).length === 0;
|
|
854
|
+
if (zeroDims.length > 0 || noValues) {
|
|
855
|
+
return {
|
|
856
|
+
...out,
|
|
857
|
+
empty_reason: 'no_match',
|
|
858
|
+
note:
|
|
859
|
+
`DATAcube cube "${cube}" returned no values for selections ${JSON.stringify(sel)}` +
|
|
860
|
+
`${zeroDims.length ? ` — nothing matched in dimension(s) ${zeroDims.join(', ')}` : ''}. ` +
|
|
861
|
+
`Check the codes with dataset_dimensions({cube: "${cube}"}).`,
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
return out;
|
|
799
865
|
}
|
|
800
866
|
|
|
801
867
|
async function dcGet(path: string): Promise<unknown> {
|
|
802
868
|
const res = await pwFetch(`${BASE}${path}`, {
|
|
803
869
|
headers: { Accept: 'application/json', 'User-Agent': UA },
|
|
804
870
|
});
|
|
805
|
-
if (!res.ok)
|
|
871
|
+
if (!res.ok) {
|
|
872
|
+
const body = await res.text().then((t) => t.slice(0, 200));
|
|
873
|
+
throw new DcHttpError(res.status, body, `DATAcube: ${res.status} ${body}`);
|
|
874
|
+
}
|
|
806
875
|
return res.json();
|
|
807
876
|
}
|
|
808
877
|
|
|
878
|
+
class DcHttpError extends Error {
|
|
879
|
+
readonly status: number;
|
|
880
|
+
readonly body: string;
|
|
881
|
+
constructor(status: number, body: string, message: string) {
|
|
882
|
+
super(message);
|
|
883
|
+
this.status = status;
|
|
884
|
+
this.body = body;
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
|
|
809
888
|
function cubeFromHref(href: string | undefined): string | undefined {
|
|
810
889
|
if (!href) return undefined;
|
|
811
890
|
const m = href.match(/\/dataset\/([^/?]+)/);
|
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-datacube-sk', version: '0.1.
|
|
12
|
+
{ name: '@pipeworx/mcp-datacube-sk', version: '0.1.3' },
|
|
13
13
|
{ capabilities: { tools: {} } },
|
|
14
14
|
);
|
|
15
15
|
|