@pipeworx/mcp-cbs-nl 0.1.2 → 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
@@ -4,7 +4,7 @@ Statistics Netherlands (CBS) StatLine — the Dutch national statistics office's
4
4
  open-data catalogue, over OData v3. Population, labour, energy, prices, trade and
5
5
  several thousand other official tables, in Dutch and English.
6
6
 
7
- Part of [Pipeworx](https://pipeworx.io) — an MCP gateway connecting AI agents to 1683+ live data sources.
7
+ Part of [Pipeworx](https://pipeworx.io) — an MCP gateway connecting AI agents to 1684+ live data sources.
8
8
 
9
9
  ## Tools
10
10
 
@@ -82,7 +82,7 @@ directly, instead of just this one's:
82
82
  }
83
83
  ```
84
84
 
85
- Both URLs reach the same gateway and the same 1683+ data sources. The
85
+ Both URLs reach the same gateway and the same 1684+ data sources. The
86
86
  only difference is which pack's tools are listed **directly**; `ask_pipeworx`
87
87
  reaches all of them from either one.
88
88
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pipeworx/mcp-cbs-nl",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Statistics Netherlands (CBS / StatLine) OData MCP.",
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-aaa752e84d6a0466fab8beb6c5dc408ee8e407470b895f710734fdd0947ab09b",
30
- "sourceCommit": "babdd280d475dceddecc9ebf8f2a661712781a81"
29
+ "sourceHash": "v1-1f4013c99a6c89d86557fa722f1ed30372a6b8f380159c808b093354aeb4e965",
30
+ "sourceCommit": "a00767651388cfd017fdeac82188622721db2051"
31
31
  }
32
32
  }
package/server.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "name": "io.github.pipeworx-io/cbs-nl",
4
4
  "title": "Cbs Nl",
5
5
  "description": "Statistics Netherlands (CBS / StatLine) OData MCP.",
6
- "version": "0.1.2",
6
+ "version": "0.1.3",
7
7
  "websiteUrl": "https://pipeworx.io/packs/cbs-nl",
8
8
  "repository": {
9
9
  "url": "https://github.com/pipeworx-io/mcp-cbs-nl",
package/src/index.ts CHANGED
@@ -764,6 +764,32 @@ const tools: McpToolExport['tools'] = [
764
764
  ];
765
765
 
766
766
  async function callTool(name: string, args: Record<string, unknown>): Promise<unknown> {
767
+ try {
768
+ return await dispatch(name, args);
769
+ } catch (err) {
770
+ // CBS answers an unknown table id (and an unknown dimension endpoint) with
771
+ // a 404 ASP.NET page titled "The resource cannot be found." That threw as a
772
+ // bare `CBS: 404 …` and booked as a Pipeworx defect with "retry the same
773
+ // tool" (fleet #2496). It is the caller naming a thing CBS does not have.
774
+ if (err instanceof CbsHttpError && err.status === 404 && /resource cannot be found/i.test(err.body)) {
775
+ const table = typeof args.table === 'string' ? args.table : '';
776
+ const what =
777
+ name === 'dimension_values'
778
+ ? `no table "${table}" or no dimension "${String(args.dimension ?? '')}" in it`
779
+ : `no table "${table}"`;
780
+ return {
781
+ error: 'not_found',
782
+ message:
783
+ `CBS StatLine has ${what}. Call search_tables({keyword: "..."}) to find the table id` +
784
+ `${name === 'dimension_values' ? ', then table_dimensions for its dimension names' : ''}.`,
785
+ table,
786
+ };
787
+ }
788
+ throw err;
789
+ }
790
+ }
791
+
792
+ async function dispatch(name: string, args: Record<string, unknown>): Promise<unknown> {
767
793
  PROXY =
768
794
  typeof args._proxyUrl === 'string' && typeof args._proxyToken === 'string'
769
795
  ? { url: args._proxyUrl, token: args._proxyToken }
@@ -798,7 +824,23 @@ async function callTool(name: string, args: Record<string, unknown>): Promise<un
798
824
  if (typeof args.select === 'string' && args.select.trim()) params.set('$select', args.select);
799
825
  if (typeof args.filter === 'string' && args.filter.trim()) params.set('$filter', args.filter);
800
826
  if (args.skip !== undefined) params.set('$skip', String(numArg(args.skip, 0)));
801
- return cbsGet(`${TABLE}/${tableId(args)}/TypedDataSet?${params.toString()}`);
827
+ const out = (await cbsGet(`${TABLE}/${tableId(args)}/TypedDataSet?${params.toString()}`)) as {
828
+ value?: unknown[];
829
+ };
830
+ // `value: []` is an honest no-match (the filter or skip matched no rows)
831
+ // and used to go out as a bare empty success (fleet #2496).
832
+ if (Array.isArray(out?.value) && out.value.length === 0) {
833
+ return {
834
+ ...out,
835
+ empty_reason: 'no_match',
836
+ note:
837
+ `CBS table "${tableId(args)}" has no rows matching` +
838
+ `${typeof args.filter === 'string' && args.filter.trim() ? ` filter ${args.filter}` : ' this request'}` +
839
+ `${args.skip !== undefined ? ` at skip ${String(args.skip)}` : ''}. ` +
840
+ `Check the codes with dimension_values (e.g. dimension "Periods") before filtering.`,
841
+ };
842
+ }
843
+ return out;
802
844
  }
803
845
  default:
804
846
  throw new Error(`Unknown tool: ${name}`);
@@ -821,7 +863,9 @@ async function cbsGet(url: string): Promise<unknown> {
821
863
  if (!res.ok) {
822
864
  const body = await res.text().catch(() => '');
823
865
  const title = body.match(/<title>([\s\S]*?)<\/title>/i)?.[1]?.trim();
824
- throw new Error(
866
+ throw new CbsHttpError(
867
+ res.status,
868
+ body,
825
869
  `CBS: ${res.status}${title ? ` ${title}` : ''} — ${url} — ${body.replace(/\s+/g, ' ').slice(0, 300)}`,
826
870
  );
827
871
  }
@@ -834,6 +878,16 @@ async function cbsGet(url: string): Promise<unknown> {
834
878
  }
835
879
  }
836
880
 
881
+ class CbsHttpError extends Error {
882
+ readonly status: number;
883
+ readonly body: string;
884
+ constructor(status: number, body: string, message: string) {
885
+ super(message);
886
+ this.status = status;
887
+ this.body = body;
888
+ }
889
+ }
890
+
837
891
  function tableId(args: Record<string, unknown>): string {
838
892
  return reqStr(args, 'table', '"37296eng"').trim().replace(/[^A-Za-z0-9_]/g, '');
839
893
  }
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-cbs-nl', version: '0.1.2' },
12
+ { name: '@pipeworx/mcp-cbs-nl', version: '0.1.3' },
13
13
  { capabilities: { tools: {} } },
14
14
  );
15
15