@cyanheads/eurostat-mcp-server 0.11.2 → 0.11.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/AGENTS.md +6 -4
- package/CLAUDE.md +6 -4
- package/README.md +17 -15
- package/changelog/0.11.x/0.11.3.md +45 -0
- package/changelog/template.md +10 -3
- package/dist/config/server-config.js +1 -1
- package/dist/config/server-config.js.map +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-browse-themes.tool.d.ts +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-browse-themes.tool.js +2 -2
- package/dist/mcp-server/tools/definitions/eurostat-browse-themes.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-dataframe-describe.tool.js +2 -2
- package/dist/mcp-server/tools/definitions/eurostat-dataframe-describe.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-dataframe-drop.tool.js +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-dataframe-drop.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-dataframe-query.tool.js +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-dataframe-query.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-download-dataset.tool.d.ts +18 -6
- package/dist/mcp-server/tools/definitions/eurostat-download-dataset.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-download-dataset.tool.js +129 -31
- package/dist/mcp-server/tools/definitions/eurostat-download-dataset.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-get-dataset-info.tool.js +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-get-dataset-info.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-get-dimension-values.tool.d.ts +6 -4
- package/dist/mcp-server/tools/definitions/eurostat-get-dimension-values.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-get-dimension-values.tool.js +139 -53
- package/dist/mcp-server/tools/definitions/eurostat-get-dimension-values.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-query-dataset.tool.d.ts +20 -8
- package/dist/mcp-server/tools/definitions/eurostat-query-dataset.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-query-dataset.tool.js +208 -43
- package/dist/mcp-server/tools/definitions/eurostat-query-dataset.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-search-datasets.tool.d.ts +16 -6
- package/dist/mcp-server/tools/definitions/eurostat-search-datasets.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/eurostat-search-datasets.tool.js +62 -23
- package/dist/mcp-server/tools/definitions/eurostat-search-datasets.tool.js.map +1 -1
- package/dist/mcp-server/tools/narrowing-advice.d.ts +64 -3
- package/dist/mcp-server/tools/narrowing-advice.d.ts.map +1 -1
- package/dist/mcp-server/tools/narrowing-advice.js +160 -4
- package/dist/mcp-server/tools/narrowing-advice.js.map +1 -1
- package/dist/mcp-server/tools/notice-text.d.ts +18 -0
- package/dist/mcp-server/tools/notice-text.d.ts.map +1 -0
- package/dist/mcp-server/tools/notice-text.js +40 -0
- package/dist/mcp-server/tools/notice-text.js.map +1 -0
- package/dist/mcp-server/tools/value-list-paging.d.ts +11 -1
- package/dist/mcp-server/tools/value-list-paging.d.ts.map +1 -1
- package/dist/mcp-server/tools/value-list-paging.js +13 -1
- package/dist/mcp-server/tools/value-list-paging.js.map +1 -1
- package/dist/services/eurostat-bulk/eurostat-bulk-service.d.ts +4 -3
- package/dist/services/eurostat-bulk/eurostat-bulk-service.d.ts.map +1 -1
- package/dist/services/eurostat-bulk/eurostat-bulk-service.js +107 -54
- package/dist/services/eurostat-bulk/eurostat-bulk-service.js.map +1 -1
- package/dist/services/eurostat-bulk/types.d.ts +27 -4
- package/dist/services/eurostat-bulk/types.d.ts.map +1 -1
- package/dist/services/eurostat-bulk/types.js.map +1 -1
- package/dist/services/eurostat-catalogue/dimension-value-index.d.ts +81 -0
- package/dist/services/eurostat-catalogue/dimension-value-index.d.ts.map +1 -0
- package/dist/services/eurostat-catalogue/dimension-value-index.js +410 -0
- package/dist/services/eurostat-catalogue/dimension-value-index.js.map +1 -0
- package/dist/services/eurostat-catalogue/eurostat-catalogue-service.d.ts +87 -7
- package/dist/services/eurostat-catalogue/eurostat-catalogue-service.d.ts.map +1 -1
- package/dist/services/eurostat-catalogue/eurostat-catalogue-service.js +333 -81
- package/dist/services/eurostat-catalogue/eurostat-catalogue-service.js.map +1 -1
- package/dist/services/eurostat-catalogue/search-text.d.ts +12 -0
- package/dist/services/eurostat-catalogue/search-text.d.ts.map +1 -0
- package/dist/services/eurostat-catalogue/search-text.js +31 -0
- package/dist/services/eurostat-catalogue/search-text.js.map +1 -0
- package/dist/services/eurostat-catalogue/types.d.ts +11 -0
- package/dist/services/eurostat-catalogue/types.d.ts.map +1 -1
- package/dist/services/eurostat-data/eurostat-data-service.d.ts +26 -5
- package/dist/services/eurostat-data/eurostat-data-service.d.ts.map +1 -1
- package/dist/services/eurostat-data/eurostat-data-service.js +132 -16
- package/dist/services/eurostat-data/eurostat-data-service.js.map +1 -1
- package/dist/services/eurostat-data/sdmx-metadata.d.ts.map +1 -1
- package/dist/services/eurostat-data/sdmx-metadata.js +4 -20
- package/dist/services/eurostat-data/sdmx-metadata.js.map +1 -1
- package/dist/services/eurostat-data/types.d.ts +92 -2
- package/dist/services/eurostat-data/types.d.ts.map +1 -1
- package/dist/services/eurostat-data/types.js.map +1 -1
- package/dist/services/eurostat-filters.d.ts +18 -6
- package/dist/services/eurostat-filters.d.ts.map +1 -1
- package/dist/services/eurostat-filters.js +24 -7
- package/dist/services/eurostat-filters.js.map +1 -1
- package/dist/services/eurostat-periods.d.ts +54 -1
- package/dist/services/eurostat-periods.d.ts.map +1 -1
- package/dist/services/eurostat-periods.js +101 -1
- package/dist/services/eurostat-periods.js.map +1 -1
- package/dist/services/xml-entities.d.ts +13 -0
- package/dist/services/xml-entities.d.ts.map +1 -0
- package/dist/services/xml-entities.js +31 -0
- package/dist/services/xml-entities.js.map +1 -0
- package/package.json +5 -5
- package/server.json +5 -5
- package/skills/eurostat-guide/SKILL.md +6 -6
- package/skills/eurostat-guide/references/sql-analysis.md +5 -5
|
@@ -22,22 +22,27 @@ export declare const createEurostatSearchDatasets: (clampLimits: boolean) => imp
|
|
|
22
22
|
lastUpdated: z.ZodOptional<z.ZodString>;
|
|
23
23
|
obsCount: z.ZodOptional<z.ZodNumber>;
|
|
24
24
|
themePath: z.ZodArray<z.ZodString>;
|
|
25
|
+
matchedValues: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
26
|
+
dimension: z.ZodString;
|
|
27
|
+
code: z.ZodString;
|
|
28
|
+
label: z.ZodString;
|
|
29
|
+
}, z.core.$strip>>>;
|
|
25
30
|
}, z.core.$strip>>;
|
|
26
31
|
nextStep: z.ZodOptional<z.ZodString>;
|
|
27
32
|
}, z.core.$strip>, readonly [{
|
|
28
33
|
readonly reason: "no_match";
|
|
29
34
|
readonly code: JsonRpcErrorCode.NotFound;
|
|
30
35
|
readonly when: "No datasets matched the query string — including a query naming a DS-* code that neither the dissemination table of contents nor the Comext dataflow list carries, which is a collection Eurostat does not disseminate, and any query while the Comext dataflow list cannot be loaded and only the table of contents is searched.";
|
|
31
|
-
readonly recovery: "Try a broader or different search term, or eurostat_browse_themes to explore without text search; for a multi-word query the hint names each word whose removal alone finds matches, with the count, or else, when both catalogue sources were searched, the words that match no dataset label, theme breadcrumb, or code. A DS-* code on neither list is not disseminated, so search for its subject (e.g. \"prodcom\", \"CN8\") to find the collections that are. While the Comext dataflow list cannot be loaded, eurostat_get_dataset_info reads a DS-* code directly.";
|
|
36
|
+
readonly recovery: "Try a broader or different search term, or eurostat_browse_themes to explore without text search; for a multi-word query the hint names each word whose removal alone finds matches, with the count, or else, when both catalogue sources were searched, the words that match no dataset label, theme breadcrumb, or code. A DS-* code on neither list is not disseminated, so search for its subject (e.g. \"prodcom\", \"CN8\") to find the collections that are. While the Comext dataflow list cannot be loaded, eurostat_get_dataset_info reads a DS-* code directly. The hint also says when dimension value labels were not searched: while their index loads, or for a query of more than 10 distinct words.";
|
|
32
37
|
}, {
|
|
33
38
|
readonly reason: "invalid_cursor";
|
|
34
39
|
readonly code: JsonRpcErrorCode.InvalidParams;
|
|
35
|
-
readonly when: "The cursor is malformed, came from a different query, or came from a catalogue snapshot that has since refreshed.";
|
|
40
|
+
readonly when: "The cursor is malformed, came from a different query, or came from a catalogue snapshot — or, for results matched through dimension values, a value index — that has since refreshed.";
|
|
36
41
|
readonly recovery: "Repeat the search without a cursor, then page using the nextCursor that call returns.";
|
|
37
42
|
}, {
|
|
38
43
|
readonly reason: "upstream_fault";
|
|
39
44
|
readonly code: JsonRpcErrorCode.ServiceUnavailable;
|
|
40
|
-
readonly when: "The server had no catalogue loaded yet, and Eurostat's table of contents answered HTTP 501, which is not retried; or it answered another HTTP 5xx or could not be reached on every retry, which the error marks retryable. Once a catalogue is loaded, a failed refresh serves that copy instead.";
|
|
45
|
+
readonly when: "The server had no catalogue loaded yet, and Eurostat's table of contents answered HTTP 501, which is not retried, or a 4xx status other than 408 and 429, which its URL carries no caller input to cause; or it answered HTTP 408, HTTP 429 or another HTTP 5xx, or could not be reached on every retry, which the error marks retryable. Once a catalogue is loaded, a failed refresh serves that copy instead.";
|
|
41
46
|
readonly recovery: "Retry in a few minutes: the catalogue load failed on Eurostat's side and runs again on the next call.";
|
|
42
47
|
}], {
|
|
43
48
|
readonly query: z.ZodString;
|
|
@@ -66,22 +71,27 @@ export declare const eurostatSearchDatasets: import("@cyanheads/mcp-ts-core").To
|
|
|
66
71
|
lastUpdated: z.ZodOptional<z.ZodString>;
|
|
67
72
|
obsCount: z.ZodOptional<z.ZodNumber>;
|
|
68
73
|
themePath: z.ZodArray<z.ZodString>;
|
|
74
|
+
matchedValues: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
75
|
+
dimension: z.ZodString;
|
|
76
|
+
code: z.ZodString;
|
|
77
|
+
label: z.ZodString;
|
|
78
|
+
}, z.core.$strip>>>;
|
|
69
79
|
}, z.core.$strip>>;
|
|
70
80
|
nextStep: z.ZodOptional<z.ZodString>;
|
|
71
81
|
}, z.core.$strip>, readonly [{
|
|
72
82
|
readonly reason: "no_match";
|
|
73
83
|
readonly code: JsonRpcErrorCode.NotFound;
|
|
74
84
|
readonly when: "No datasets matched the query string — including a query naming a DS-* code that neither the dissemination table of contents nor the Comext dataflow list carries, which is a collection Eurostat does not disseminate, and any query while the Comext dataflow list cannot be loaded and only the table of contents is searched.";
|
|
75
|
-
readonly recovery: "Try a broader or different search term, or eurostat_browse_themes to explore without text search; for a multi-word query the hint names each word whose removal alone finds matches, with the count, or else, when both catalogue sources were searched, the words that match no dataset label, theme breadcrumb, or code. A DS-* code on neither list is not disseminated, so search for its subject (e.g. \"prodcom\", \"CN8\") to find the collections that are. While the Comext dataflow list cannot be loaded, eurostat_get_dataset_info reads a DS-* code directly.";
|
|
85
|
+
readonly recovery: "Try a broader or different search term, or eurostat_browse_themes to explore without text search; for a multi-word query the hint names each word whose removal alone finds matches, with the count, or else, when both catalogue sources were searched, the words that match no dataset label, theme breadcrumb, or code. A DS-* code on neither list is not disseminated, so search for its subject (e.g. \"prodcom\", \"CN8\") to find the collections that are. While the Comext dataflow list cannot be loaded, eurostat_get_dataset_info reads a DS-* code directly. The hint also says when dimension value labels were not searched: while their index loads, or for a query of more than 10 distinct words.";
|
|
76
86
|
}, {
|
|
77
87
|
readonly reason: "invalid_cursor";
|
|
78
88
|
readonly code: JsonRpcErrorCode.InvalidParams;
|
|
79
|
-
readonly when: "The cursor is malformed, came from a different query, or came from a catalogue snapshot that has since refreshed.";
|
|
89
|
+
readonly when: "The cursor is malformed, came from a different query, or came from a catalogue snapshot — or, for results matched through dimension values, a value index — that has since refreshed.";
|
|
80
90
|
readonly recovery: "Repeat the search without a cursor, then page using the nextCursor that call returns.";
|
|
81
91
|
}, {
|
|
82
92
|
readonly reason: "upstream_fault";
|
|
83
93
|
readonly code: JsonRpcErrorCode.ServiceUnavailable;
|
|
84
|
-
readonly when: "The server had no catalogue loaded yet, and Eurostat's table of contents answered HTTP 501, which is not retried; or it answered another HTTP 5xx or could not be reached on every retry, which the error marks retryable. Once a catalogue is loaded, a failed refresh serves that copy instead.";
|
|
94
|
+
readonly when: "The server had no catalogue loaded yet, and Eurostat's table of contents answered HTTP 501, which is not retried, or a 4xx status other than 408 and 429, which its URL carries no caller input to cause; or it answered HTTP 408, HTTP 429 or another HTTP 5xx, or could not be reached on every retry, which the error marks retryable. Once a catalogue is loaded, a failed refresh serves that copy instead.";
|
|
85
95
|
readonly recovery: "Retry in a few minutes: the catalogue load failed on Eurostat's side and runs again on the next call.";
|
|
86
96
|
}], {
|
|
87
97
|
readonly query: z.ZodString;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"eurostat-search-datasets.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/eurostat-search-datasets.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAiB,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"eurostat-search-datasets.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/eurostat-search-datasets.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAiB,MAAM,+BAA+B,CAAC;AA4GhF,+DAA+D;AAC/D,eAAO,MAAM,4BAA4B,gBAAiB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2R7D,CAAC;AAEL,sFAAsF;AACtF,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAAqC,CAAC"}
|
|
@@ -4,20 +4,29 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { tool, z } from '@cyanheads/mcp-ts-core';
|
|
6
6
|
import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
7
|
+
import { escapeMarkdownInline } from '@cyanheads/mcp-ts-core/utils';
|
|
7
8
|
import { clampReadLimit } from '../clamp-read-limit.js';
|
|
8
|
-
import { getEurostatCatalogueService } from '../../../services/eurostat-catalogue/eurostat-catalogue-service.js';
|
|
9
|
+
import { getEurostatCatalogueService, MAX_UNMATCHED_WORD_CHECKS, MAX_VALUE_SEARCH_WORDS, } from '../../../services/eurostat-catalogue/eurostat-catalogue-service.js';
|
|
9
10
|
import { outageAnswer } from '../../../services/eurostat-outage.js';
|
|
10
11
|
const COMEXT_NOT_SEARCHED = 'The Comext dataflow list could not be loaded, so the DS-* collections (detailed trade and PRODCOM) were not searched; the merge is retried about once a minute. eurostat_get_dataset_info reads a DS-* code directly.';
|
|
11
12
|
const BROADEN_HINT = 'Try a broader term or use eurostat_browse_themes to explore themes without text search.';
|
|
13
|
+
const UNMATCHED_WORDS_PARTIAL = `Only the first ${MAX_UNMATCHED_WORD_CHECKS} distinct words of the query were checked for words no dataset carries.`;
|
|
14
|
+
const MATCHED_THROUGH_VALUES = 'No dataset label, theme breadcrumb, or code holds every word of the query, so these datasets were found by also matching the words they lack at the start of a word in the English labels of their dimension values. Each row\'s matchedValues names those values; a value\'s code filters its dimension in eurostat_query_dataset, as in filters `{"med_spec": ["PHARM"]}`.';
|
|
15
|
+
/** The sentence a `no_match` hint closes with when dimension value labels were not searched. */
|
|
16
|
+
const VALUES_NOT_SEARCHED = {
|
|
17
|
+
index_unavailable: 'Dimension value labels were not searched because their index has not loaded yet; it loads in the background, so the same search may later find datasets through them.',
|
|
18
|
+
too_many_words: `Dimension value labels were not searched because the query has more than ${MAX_VALUE_SEARCH_WORDS} distinct words; a query of ${MAX_VALUE_SEARCH_WORDS} or fewer is also matched against them.`,
|
|
19
|
+
};
|
|
12
20
|
/**
|
|
13
21
|
* The `no_match` recovery: first what the catalogue could not answer for (the unmerged
|
|
14
22
|
* Comext list, or a DS-* code no source lists), then the expired copy a failed refresh
|
|
15
23
|
* left in service, then each word whose removal alone finds matches — or, when none
|
|
16
24
|
* does, the words no dataset carries, unless the unmerged Comext list leaves that
|
|
17
|
-
* unproven
|
|
18
|
-
* a
|
|
25
|
+
* unproven, and that only the first words were read for them when a later one may be
|
|
26
|
+
* such a word — and the broaden hint only when neither a count, a word left to search,
|
|
27
|
+
* nor a code points elsewhere. Last, that dimension value labels were not searched.
|
|
19
28
|
*/
|
|
20
|
-
function noMatchHint({ allWordsUnmatched, catalogueLoadedAt, comextListMissing, undisseminatedCodes, unmatchedWords, wordRemovalCounts, }) {
|
|
29
|
+
function noMatchHint({ allWordsUnmatched, catalogueLoadedAt, comextListMissing, undisseminatedCodes, unmatchedWords, unmatchedWordsPartial, valuesNotSearched, wordRemovalCounts, }) {
|
|
21
30
|
const codes = undisseminatedCodes?.join(', ');
|
|
22
31
|
const plural = (undisseminatedCodes?.length ?? 0) > 1;
|
|
23
32
|
const hint = [];
|
|
@@ -38,32 +47,41 @@ function noMatchHint({ allWordsUnmatched, catalogueLoadedAt, comextListMissing,
|
|
|
38
47
|
}
|
|
39
48
|
hint.push('Search again without one of those words to see its matches.');
|
|
40
49
|
}
|
|
41
|
-
else
|
|
42
|
-
const
|
|
43
|
-
const one =
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
50
|
+
else {
|
|
51
|
+
const dead = comextListMissing ? undefined : unmatchedWords?.map((word) => `"${word}"`);
|
|
52
|
+
const one = dead?.length === 1;
|
|
53
|
+
if (dead?.length) {
|
|
54
|
+
const named = one ? dead[0] : `${dead.slice(0, -1).join(', ')} and ${dead.at(-1)}`;
|
|
55
|
+
hint.push(`${named} ${one ? 'matches' : 'match'} no dataset label, theme breadcrumb, or code.`);
|
|
56
|
+
}
|
|
57
|
+
if (unmatchedWordsPartial && !comextListMissing)
|
|
58
|
+
hint.push(UNMATCHED_WORDS_PARTIAL);
|
|
59
|
+
if (dead?.length && !allWordsUnmatched)
|
|
47
60
|
hint.push(`Search again without ${one ? 'it' : 'them'}.`);
|
|
48
61
|
else if (!codes)
|
|
49
62
|
hint.push(BROADEN_HINT);
|
|
50
63
|
}
|
|
51
|
-
|
|
52
|
-
hint.push(
|
|
53
|
-
}
|
|
64
|
+
if (valuesNotSearched)
|
|
65
|
+
hint.push(VALUES_NOT_SEARCHED[valuesNotSearched]);
|
|
54
66
|
return hint.join(' ');
|
|
55
67
|
}
|
|
68
|
+
/** One row's matched values: codes verbatim as code spans, labels escaped. */
|
|
69
|
+
function renderMatchedValues(values) {
|
|
70
|
+
return values
|
|
71
|
+
.map((v) => `\`${v.dimension}=${v.code}\` ${escapeMarkdownInline(v.label)}`)
|
|
72
|
+
.join('; ');
|
|
73
|
+
}
|
|
56
74
|
/** Selects the advertised limit policy before registration. */
|
|
57
75
|
export const createEurostatSearchDatasets = (clampLimits) => tool('eurostat_search_datasets', {
|
|
58
76
|
title: 'Search Eurostat Datasets',
|
|
59
|
-
description:
|
|
77
|
+
description: `Search the Eurostat catalogue by keyword. Returns matching datasets with codes, descriptions, period coverage, and theme breadcrumbs. Matches are ordered by where the words were found: first datasets whose label contains the query as a phrase beginning at the start of a word, then those whose label or code contains every word, then those that match a word only through their theme breadcrumb, each group in table-of-contents order. When no dataset's label, theme breadcrumb, or code holds every word, a query of up to ${MAX_VALUE_SEARCH_WORDS} distinct words also has the words a dataset lacks looked for at the start of a word in the English labels of the dimension values Eurostat lists for it (time excluded); those rows carry matchedValues, whose codes are the filters eurostat_query_dataset takes. Use this to discover dataset codes before calling eurostat_get_dataset_info, then eurostat_query_dataset for a slice of a dataset or eurostat_download_dataset for the whole of one. Results are limited to datasets and predefined tables — folders are excluded. The catalogue joins two sources: the dissemination table of contents, and the Comext host's dataflow list, which adds the DS-* collections — detailed trade by CN8, HS, SITC, BEC and CPA, and PRODCOM — filed under "International trade in goods - detailed data (Comext)" and "Statistics on the production of manufactured goods (PRODCOM)". Comext entries report a last-update date but no period coverage or observation count. A collection on neither list, such as the legacy PRODCOM DS-056120, is not disseminated and cannot be reached through this server.`,
|
|
60
78
|
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
61
79
|
input: z.object({
|
|
62
80
|
query: z
|
|
63
81
|
.string()
|
|
64
82
|
.min(1)
|
|
65
83
|
.regex(/\S/, 'Query must contain at least one non-whitespace search term.')
|
|
66
|
-
.describe(
|
|
84
|
+
.describe(`Search terms — at least one non-whitespace token is required. Split on whitespace into tokens; every token must match (AND), case-insensitively, somewhere across the dataset label, theme breadcrumb, or code. Spacing beside punctuation in the catalogue is ignored, so "Rev.2" also finds "Rev. 2" and "2015=100" finds "2015 = 100"; a token keeps its own punctuation, as in "R&D", "non-EU", or "DS-045409". Word order does not matter, so "business demography NUTS 3" or "regional economic accounts" resolve without naming a label verbatim. When no dataset holds every token, a query of up to ${MAX_VALUE_SEARCH_WORDS} distinct tokens is searched again with each token a dataset lacks also matching where it begins a word of the English label of one of its dimension values: "pharmacist" finds "Pharmacists" (med_spec=PHARM), "sex" does not find "Essex". When nothing matches, the error names each word whose removal alone would find matches or, when no single removal would and both catalogue sources were searched, the words that match no dataset label, theme breadcrumb, or code, and says when dimension value labels were not searched.`),
|
|
67
85
|
limit: (clampLimits ? z.number().multipleOf(1).min(1) : z.number().int().min(1).max(100))
|
|
68
86
|
.default(20)
|
|
69
87
|
.describe(`Page size — maximum datasets returned per page. Default 20; ${clampLimits ? 'positive integers above 100 are clamped to 100 with a notice' : 'maximum 100; larger values are rejected'}. To retrieve matches beyond one page, pass the returned nextCursor back as cursor; the page size is fixed by this first call and limit is ignored on cursor calls.`),
|
|
@@ -102,9 +120,25 @@ export const createEurostatSearchDatasets = (clampLimits) => tool('eurostat_sear
|
|
|
102
120
|
themePath: z
|
|
103
121
|
.array(z.string())
|
|
104
122
|
.describe('Breadcrumb path from root theme to this dataset (e.g., ["Database by themes", "Economy and finance"]). Eurostat files some datasets under several branches; this is the first branch that matched the query. Empty for top-level entries.'),
|
|
123
|
+
matchedValues: z
|
|
124
|
+
.array(z
|
|
125
|
+
.object({
|
|
126
|
+
dimension: z
|
|
127
|
+
.string()
|
|
128
|
+
.describe('Dimension the value belongs to (e.g., "med_spec") — the key in eurostat_query_dataset filters.'),
|
|
129
|
+
code: z
|
|
130
|
+
.string()
|
|
131
|
+
.describe('The value\'s code (e.g., "PHARM") — the value in eurostat_query_dataset filters.'),
|
|
132
|
+
label: z
|
|
133
|
+
.string()
|
|
134
|
+
.describe("The value's English label, which holds a query word at the start of a word."),
|
|
135
|
+
})
|
|
136
|
+
.describe('A dimension value of this dataset whose label carries a query word.'))
|
|
137
|
+
.optional()
|
|
138
|
+
.describe("Present only when no dataset's label, theme breadcrumb, or code holds every query word: this dataset's dimension values whose English labels carry the words its own text lacks, those carrying the most of the words first and then in Eurostat's metabase order, each value once. Omitted on every other search."),
|
|
105
139
|
})
|
|
106
140
|
.describe('A matched dataset entry.'))
|
|
107
|
-
.describe(
|
|
141
|
+
.describe("Matching datasets for the current page, up to the requested limit. Ordered first by label phrase matches (the query as a phrase beginning at the start of a word of the label), then datasets whose label or code holds every word, then datasets that match a word only through their theme breadcrumb; table-of-contents order within each group. Rows matched through dimension values (each carrying matchedValues) are ordered by fewer words found only in value labels, then datasets where one value's label carries all of those words, then the groups above over the words the dataset's own text holds, then table-of-contents order."),
|
|
108
142
|
nextStep: z
|
|
109
143
|
.string()
|
|
110
144
|
.optional()
|
|
@@ -133,25 +167,25 @@ export const createEurostatSearchDatasets = (clampLimits) => tool('eurostat_sear
|
|
|
133
167
|
notice: z
|
|
134
168
|
.string()
|
|
135
169
|
.optional()
|
|
136
|
-
.describe('
|
|
170
|
+
.describe('Says when the results came through dimension value labels, and names each part of the catalogue this answer is missing — the Comext collections, or updates since a refresh failed — and what to do about it. Omitted when the whole catalogue answered through its own text.'),
|
|
137
171
|
},
|
|
138
172
|
errors: [
|
|
139
173
|
{
|
|
140
174
|
reason: 'no_match',
|
|
141
175
|
code: JsonRpcErrorCode.NotFound,
|
|
142
176
|
when: 'No datasets matched the query string — including a query naming a DS-* code that neither the dissemination table of contents nor the Comext dataflow list carries, which is a collection Eurostat does not disseminate, and any query while the Comext dataflow list cannot be loaded and only the table of contents is searched.',
|
|
143
|
-
recovery:
|
|
177
|
+
recovery: `Try a broader or different search term, or eurostat_browse_themes to explore without text search; for a multi-word query the hint names each word whose removal alone finds matches, with the count, or else, when both catalogue sources were searched, the words that match no dataset label, theme breadcrumb, or code. A DS-* code on neither list is not disseminated, so search for its subject (e.g. "prodcom", "CN8") to find the collections that are. While the Comext dataflow list cannot be loaded, eurostat_get_dataset_info reads a DS-* code directly. The hint also says when dimension value labels were not searched: while their index loads, or for a query of more than ${MAX_VALUE_SEARCH_WORDS} distinct words.`,
|
|
144
178
|
},
|
|
145
179
|
{
|
|
146
180
|
reason: 'invalid_cursor',
|
|
147
181
|
code: JsonRpcErrorCode.InvalidParams,
|
|
148
|
-
when: 'The cursor is malformed, came from a different query, or came from a catalogue snapshot that has since refreshed.',
|
|
182
|
+
when: 'The cursor is malformed, came from a different query, or came from a catalogue snapshot — or, for results matched through dimension values, a value index — that has since refreshed.',
|
|
149
183
|
recovery: 'Repeat the search without a cursor, then page using the nextCursor that call returns.',
|
|
150
184
|
},
|
|
151
185
|
{
|
|
152
186
|
reason: 'upstream_fault',
|
|
153
187
|
code: JsonRpcErrorCode.ServiceUnavailable,
|
|
154
|
-
when: "The server had no catalogue loaded yet, and Eurostat's table of contents answered HTTP 501, which is not retried; or it answered another HTTP 5xx or could not be reached on every retry, which the error marks retryable. Once a catalogue is loaded, a failed refresh serves that copy instead.",
|
|
188
|
+
when: "The server had no catalogue loaded yet, and Eurostat's table of contents answered HTTP 501, which is not retried, or a 4xx status other than 408 and 429, which its URL carries no caller input to cause; or it answered HTTP 408, HTTP 429 or another HTTP 5xx, or could not be reached on every retry, which the error marks retryable. Once a catalogue is loaded, a failed refresh serves that copy instead.",
|
|
155
189
|
recovery: "Retry in a few minutes: the catalogue load failed on Eurostat's side and runs again on the next call.",
|
|
156
190
|
},
|
|
157
191
|
],
|
|
@@ -171,7 +205,7 @@ export const createEurostatSearchDatasets = (clampLimits) => tool('eurostat_sear
|
|
|
171
205
|
recovery: {
|
|
172
206
|
hint: `Repeat the search for "${input.query}" without a cursor, then page using the nextCursor it returns.`,
|
|
173
207
|
},
|
|
174
|
-
});
|
|
208
|
+
}, { literals: [input.query] });
|
|
175
209
|
}
|
|
176
210
|
if (data?.reason === 'upstream_fault' && data.retryable === true) {
|
|
177
211
|
throw ctx.fail('upstream_fault', err.message, {
|
|
@@ -183,7 +217,7 @@ export const createEurostatSearchDatasets = (clampLimits) => tool('eurostat_sear
|
|
|
183
217
|
}
|
|
184
218
|
throw err;
|
|
185
219
|
}
|
|
186
|
-
const { datasets, totalMatches, nextCursor, undisseminatedCodes, comextListMissing, catalogueLoadedAt, } = found;
|
|
220
|
+
const { datasets, totalMatches, nextCursor, undisseminatedCodes, comextListMissing, catalogueLoadedAt, matchedThroughValues, } = found;
|
|
187
221
|
if (datasets.length === 0) {
|
|
188
222
|
/**
|
|
189
223
|
* A DS-* code on neither list is absent by design, so broadening cannot find it.
|
|
@@ -213,6 +247,8 @@ export const createEurostatSearchDatasets = (clampLimits) => tool('eurostat_sear
|
|
|
213
247
|
});
|
|
214
248
|
// `notice` is last-wins, so every sentence is collected here and written once.
|
|
215
249
|
const notices = pageLimit.notice ? [pageLimit.notice] : [];
|
|
250
|
+
if (matchedThroughValues)
|
|
251
|
+
notices.push(MATCHED_THROUGH_VALUES);
|
|
216
252
|
if (comextListMissing)
|
|
217
253
|
notices.push(COMEXT_NOT_SEARCHED);
|
|
218
254
|
if (catalogueLoadedAt) {
|
|
@@ -236,11 +272,14 @@ export const createEurostatSearchDatasets = (clampLimits) => tool('eurostat_sear
|
|
|
236
272
|
lines.push(`**Period:** ${d.dataStart ?? '?'} – ${d.dataEnd ?? '?'}`);
|
|
237
273
|
}
|
|
238
274
|
if (d.obsCount != null)
|
|
239
|
-
lines.push(`**Observations:** ${d.obsCount.toLocaleString()}`);
|
|
275
|
+
lines.push(`**Observations:** ${d.obsCount.toLocaleString('en-US')}`);
|
|
240
276
|
if (d.lastUpdated)
|
|
241
277
|
lines.push(`**Last updated:** ${d.lastUpdated}`);
|
|
242
278
|
if (d.themePath.length > 0)
|
|
243
279
|
lines.push(`**Theme:** ${d.themePath.join(' › ')}`);
|
|
280
|
+
if (d.matchedValues) {
|
|
281
|
+
lines.push(`**Matched values:** ${renderMatchedValues(d.matchedValues)}`);
|
|
282
|
+
}
|
|
244
283
|
}
|
|
245
284
|
if (result.nextStep)
|
|
246
285
|
lines.push(`\n**Next step:** ${result.nextStep}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"eurostat-search-datasets.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/eurostat-search-datasets.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAiB,MAAM,+BAA+B,CAAC;AAChF,OAAO,EAAE,cAAc,EAAE,MAAM,wCAAwC,CAAC;AACxE,OAAO,
|
|
1
|
+
{"version":3,"file":"eurostat-search-datasets.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/eurostat-search-datasets.tool.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,10 +1,45 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Recovery wording
|
|
3
|
-
*
|
|
4
|
-
* Eurostat refused as too large
|
|
2
|
+
* @fileoverview Recovery wording shared by the two data tools: the best-effort reads
|
|
3
|
+
* behind a hint that names a dataset's own dimensions or values, the advice for a
|
|
4
|
+
* request Eurostat refused as too large, and the wording for a filter key or value that
|
|
5
|
+
* cannot match the dataset.
|
|
5
6
|
* @module mcp-server/tools/narrowing-advice
|
|
6
7
|
*/
|
|
7
8
|
import type { Context } from '@cyanheads/mcp-ts-core';
|
|
9
|
+
import type { SdmxDatasetMetadata } from '../../services/eurostat-data/sdmx-metadata.js';
|
|
10
|
+
import type { UnmatchedValues, ValidValues } from '../../services/eurostat-data/types.js';
|
|
11
|
+
/**
|
|
12
|
+
* The opening of both data tools' `invalid_dimension` `when`. Each tool appends the
|
|
13
|
+
* further causes its own path raises.
|
|
14
|
+
*/
|
|
15
|
+
export declare const UNKNOWN_FILTER_KEY_WHEN = "A filter key is not a dimension of this dataset; the error names every such key and lists the dataset's dimensions in key order";
|
|
16
|
+
/** The hint for a filter key that names no dimension, shared by both data tools. */
|
|
17
|
+
export declare const RENAME_FILTER_KEYS_HINT = "Rename each filter key the error names to one of the dimensions it lists.";
|
|
18
|
+
/** A filter value holding ".", with the dimension it was sent for. */
|
|
19
|
+
type DottedEntry = {
|
|
20
|
+
dimension: string;
|
|
21
|
+
value: string;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Advice for filter values holding ".", which no Eurostat code contains, as one hint:
|
|
25
|
+
* the opening sentence once, then one piece of advice per value. A Comext `DS-*`
|
|
26
|
+
* collection's classification codes are bare digits, so there a value of digits and dots
|
|
27
|
+
* is a code printed with separators and its stripped form is the code to send
|
|
28
|
+
* (`2204.21` → `220421`). Elsewhere stripping can yield a non-code (COICOP `01.1.1` is
|
|
29
|
+
* `CP0111`), so the advice is a value search on the stripped form instead. `literals`
|
|
30
|
+
* holds the values the hint quotes for the caller to send back.
|
|
31
|
+
*/
|
|
32
|
+
export declare function dottedValueAdvice(datasetCode: string, entries: [DottedEntry, ...DottedEntry[]]): {
|
|
33
|
+
hint: string;
|
|
34
|
+
literals: string[];
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* `geo=[DE, XX]; age=[ZZZ]`, each dimension code and value passed through `encode`.
|
|
38
|
+
* Authored Markdown (a notice, a `format()` line, the filters trailer) passes
|
|
39
|
+
* `escapeMarkdownInline`, so a caller's value renders as sent; an error message stays
|
|
40
|
+
* raw, as the framework escapes it.
|
|
41
|
+
*/
|
|
42
|
+
export declare function formatValues(values: Record<string, string[]>, encode?: (text: string) => string): string;
|
|
8
43
|
/**
|
|
9
44
|
* The dataset's key dimensions, for a recovery hint to name. `known` is the order the
|
|
10
45
|
* request already read, if any; otherwise the structure definition is read now, a few
|
|
@@ -14,6 +49,31 @@ import type { Context } from '@cyanheads/mcp-ts-core';
|
|
|
14
49
|
* rethrown so it reaches the caller as a cancellation.
|
|
15
50
|
*/
|
|
16
51
|
export declare function dimensionsToNarrow(datasetCode: string, known: string[], ctx: Context): Promise<string[] | undefined>;
|
|
52
|
+
/**
|
|
53
|
+
* The dataset's metadata, for a recovery hint to name its dimension values, or
|
|
54
|
+
* `undefined` when it is not in hand within {@link HINT_READ_DEADLINE_MS}. The metadata
|
|
55
|
+
* load is shared and takes no deadline of its own (a cold Comext dataflow outlasts the
|
|
56
|
+
* allowance several times over), so the read races a timer instead; a load the timer
|
|
57
|
+
* beats keeps running and fills the cache, so a follow-up value lookup is served from
|
|
58
|
+
* it. A failed read degrades the hint the same way, and a cancelled call is rethrown.
|
|
59
|
+
*/
|
|
60
|
+
export declare function dimensionValuesForHint(datasetCode: string, ctx: Context): Promise<SdmxDatasetMetadata | undefined>;
|
|
61
|
+
/**
|
|
62
|
+
* What to send instead of each unmatched filter value, from the dataset's own value
|
|
63
|
+
* lists. A dimension with at most {@link VALUES_LISTED_MAX} values has every code and
|
|
64
|
+
* label listed; a longer one has its total and, per value sent, a value search for it,
|
|
65
|
+
* or {@link dottedValueAdvice} for a value holding ".". `totalCount` counts every value
|
|
66
|
+
* the dimension lists, every `geo` level included, where eurostat_get_dimension_values
|
|
67
|
+
* defaults `geo` to countries. A key the metadata lists no values for lands in
|
|
68
|
+
* `unresolved`, for the caller's generic sentence. `literals` holds the codes and values
|
|
69
|
+
* the sentences quote for sending back.
|
|
70
|
+
*/
|
|
71
|
+
export declare function unmatchedValuesAdvice(datasetCode: string, unmatched: UnmatchedValues, { meta, valuesByDimension }: SdmxDatasetMetadata): {
|
|
72
|
+
literals: string[];
|
|
73
|
+
sentences: string[];
|
|
74
|
+
unresolved: string[];
|
|
75
|
+
validValues: ValidValues;
|
|
76
|
+
};
|
|
17
77
|
/**
|
|
18
78
|
* Tell the caller how to shrink a refused request, naming the dimensions of this
|
|
19
79
|
* dataset it left unfiltered. `dimensions` is the dataset's key dimensions, or
|
|
@@ -21,4 +81,5 @@ export declare function dimensionsToNarrow(datasetCode: string, known: string[],
|
|
|
21
81
|
* filtered, in any case.
|
|
22
82
|
*/
|
|
23
83
|
export declare function narrowingAdvice(datasetCode: string, dimensions: string[] | undefined, filtered: string[]): string;
|
|
84
|
+
export {};
|
|
24
85
|
//# sourceMappingURL=narrowing-advice.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"narrowing-advice.d.ts","sourceRoot":"","sources":["../../../src/mcp-server/tools/narrowing-advice.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"narrowing-advice.d.ts","sourceRoot":"","sources":["../../../src/mcp-server/tools/narrowing-advice.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAEtD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2CAA2C,CAAC;AACrF,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,mCAAmC,CAAC;AAGtF;;;GAGG;AACH,eAAO,MAAM,uBAAuB,oIAC+F,CAAC;AAEpI,oFAAoF;AACpF,eAAO,MAAM,uBAAuB,8EACyC,CAAC;AAE9E,sEAAsE;AACtE,KAAK,WAAW,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAExD;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,CAAC,WAAW,EAAE,GAAG,WAAW,EAAE,CAAC,GACvC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CA8BtC;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EAChC,MAAM,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAuB,GAChD,MAAM,CAIR;AASD;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CACtC,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,EAAE,EACf,GAAG,EAAE,OAAO,GACX,OAAO,CAAC,MAAM,EAAE,GAAG,SAAS,CAAC,CAgB/B;AAED;;;;;;;GAOG;AACH,wBAAsB,sBAAsB,CAC1C,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,OAAO,GACX,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC,CAiB1C;AASD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,eAAe,EAC1B,EAAE,IAAI,EAAE,iBAAiB,EAAE,EAAE,mBAAmB,GAC/C;IAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;IAAC,SAAS,EAAE,MAAM,EAAE,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAC;IAAC,WAAW,EAAE,WAAW,CAAA;CAAE,CAuD7F;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAAE,GAAG,SAAS,EAChC,QAAQ,EAAE,MAAM,EAAE,GACjB,MAAM,CASR"}
|
|
@@ -1,14 +1,74 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Recovery wording
|
|
3
|
-
*
|
|
4
|
-
* Eurostat refused as too large
|
|
2
|
+
* @fileoverview Recovery wording shared by the two data tools: the best-effort reads
|
|
3
|
+
* behind a hint that names a dataset's own dimensions or values, the advice for a
|
|
4
|
+
* request Eurostat refused as too large, and the wording for a filter key or value that
|
|
5
|
+
* cannot match the dataset.
|
|
5
6
|
* @module mcp-server/tools/narrowing-advice
|
|
6
7
|
*/
|
|
7
8
|
import { getEurostatDataService } from '../../services/eurostat-data/eurostat-data-service.js';
|
|
9
|
+
import { isComextDataset } from '../../services/eurostat-hosts.js';
|
|
10
|
+
/**
|
|
11
|
+
* The opening of both data tools' `invalid_dimension` `when`. Each tool appends the
|
|
12
|
+
* further causes its own path raises.
|
|
13
|
+
*/
|
|
14
|
+
export const UNKNOWN_FILTER_KEY_WHEN = "A filter key is not a dimension of this dataset; the error names every such key and lists the dataset's dimensions in key order";
|
|
15
|
+
/** The hint for a filter key that names no dimension, shared by both data tools. */
|
|
16
|
+
export const RENAME_FILTER_KEYS_HINT = 'Rename each filter key the error names to one of the dimensions it lists.';
|
|
17
|
+
/**
|
|
18
|
+
* Advice for filter values holding ".", which no Eurostat code contains, as one hint:
|
|
19
|
+
* the opening sentence once, then one piece of advice per value. A Comext `DS-*`
|
|
20
|
+
* collection's classification codes are bare digits, so there a value of digits and dots
|
|
21
|
+
* is a code printed with separators and its stripped form is the code to send
|
|
22
|
+
* (`2204.21` → `220421`). Elsewhere stripping can yield a non-code (COICOP `01.1.1` is
|
|
23
|
+
* `CP0111`), so the advice is a value search on the stripped form instead. `literals`
|
|
24
|
+
* holds the values the hint quotes for the caller to send back.
|
|
25
|
+
*/
|
|
26
|
+
export function dottedValueAdvice(datasetCode, entries) {
|
|
27
|
+
const advise = ({ dimension, value }) => {
|
|
28
|
+
const stripped = value.replaceAll('.', '');
|
|
29
|
+
if (isComextDataset(datasetCode) && value.split('.').every((part) => /^\d+$/.test(part))) {
|
|
30
|
+
return {
|
|
31
|
+
joiner: '. ',
|
|
32
|
+
literals: [stripped, value, dimension],
|
|
33
|
+
text: `Write "${stripped}" for "${value}" in filters.${dimension}.`,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (stripped.trim() === '') {
|
|
37
|
+
return {
|
|
38
|
+
joiner: '. ',
|
|
39
|
+
literals: [dimension],
|
|
40
|
+
text: `List the codes of "${dimension}" with eurostat_get_dimension_values.`,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
joiner: ', and ',
|
|
45
|
+
literals: [value, stripped, dimension],
|
|
46
|
+
text: `"${value}" without its dots may not be a code of "${datasetCode}" either. Find the code with eurostat_get_dimension_values, dimension "${dimension}" and filter "${stripped}".`,
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
const [head, ...tail] = entries;
|
|
50
|
+
const first = advise(head);
|
|
51
|
+
const rest = tail.map(advise);
|
|
52
|
+
return {
|
|
53
|
+
hint: `No Eurostat code contains "."${first.joiner}${first.text}${rest.map(({ text }) => ` ${text}`).join('')}`,
|
|
54
|
+
literals: [first, ...rest].flatMap(({ literals }) => literals),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* `geo=[DE, XX]; age=[ZZZ]`, each dimension code and value passed through `encode`.
|
|
59
|
+
* Authored Markdown (a notice, a `format()` line, the filters trailer) passes
|
|
60
|
+
* `escapeMarkdownInline`, so a caller's value renders as sent; an error message stays
|
|
61
|
+
* raw, as the framework escapes it.
|
|
62
|
+
*/
|
|
63
|
+
export function formatValues(values, encode = (text) => text) {
|
|
64
|
+
return Object.entries(values)
|
|
65
|
+
.map(([dim, codes]) => `${encode(dim)}=[${codes.map((code) => encode(code)).join(', ')}]`)
|
|
66
|
+
.join('; ');
|
|
67
|
+
}
|
|
8
68
|
/**
|
|
9
69
|
* Total allowance for the read behind a hint, every attempt and backoff included. It
|
|
10
70
|
* runs after the request it explains, which can have spent its own 30 s, and MCP SDK
|
|
11
|
-
* clients give up at 60 s.
|
|
71
|
+
* clients give up at 60 s.
|
|
12
72
|
*/
|
|
13
73
|
const HINT_READ_DEADLINE_MS = 5_000;
|
|
14
74
|
/**
|
|
@@ -35,6 +95,102 @@ export async function dimensionsToNarrow(datasetCode, known, ctx) {
|
|
|
35
95
|
return;
|
|
36
96
|
}
|
|
37
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* The dataset's metadata, for a recovery hint to name its dimension values, or
|
|
100
|
+
* `undefined` when it is not in hand within {@link HINT_READ_DEADLINE_MS}. The metadata
|
|
101
|
+
* load is shared and takes no deadline of its own (a cold Comext dataflow outlasts the
|
|
102
|
+
* allowance several times over), so the read races a timer instead; a load the timer
|
|
103
|
+
* beats keeps running and fills the cache, so a follow-up value lookup is served from
|
|
104
|
+
* it. A failed read degrades the hint the same way, and a cancelled call is rethrown.
|
|
105
|
+
*/
|
|
106
|
+
export async function dimensionValuesForHint(datasetCode, ctx) {
|
|
107
|
+
let timer;
|
|
108
|
+
const late = new Promise((resolve) => {
|
|
109
|
+
timer = setTimeout(() => resolve(undefined), HINT_READ_DEADLINE_MS);
|
|
110
|
+
});
|
|
111
|
+
try {
|
|
112
|
+
return await Promise.race([getEurostatDataService().getSdmxMetadata(datasetCode, ctx), late]);
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
if (ctx.signal.aborted)
|
|
116
|
+
throw error;
|
|
117
|
+
ctx.log.debug('Could not read dimension values for a recovery hint', {
|
|
118
|
+
datasetCode,
|
|
119
|
+
error: error instanceof Error ? error.message : String(error),
|
|
120
|
+
});
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
finally {
|
|
124
|
+
clearTimeout(timer);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Most values a hint lists for one dimension: eurostat_get_dimension_values' default
|
|
129
|
+
* page. A longer list is not cut to its first values, which in Eurostat's order say
|
|
130
|
+
* nothing about the value sent (a CN product list opens with confidential-trade totals).
|
|
131
|
+
*/
|
|
132
|
+
const VALUES_LISTED_MAX = 25;
|
|
133
|
+
/**
|
|
134
|
+
* What to send instead of each unmatched filter value, from the dataset's own value
|
|
135
|
+
* lists. A dimension with at most {@link VALUES_LISTED_MAX} values has every code and
|
|
136
|
+
* label listed; a longer one has its total and, per value sent, a value search for it,
|
|
137
|
+
* or {@link dottedValueAdvice} for a value holding ".". `totalCount` counts every value
|
|
138
|
+
* the dimension lists, every `geo` level included, where eurostat_get_dimension_values
|
|
139
|
+
* defaults `geo` to countries. A key the metadata lists no values for lands in
|
|
140
|
+
* `unresolved`, for the caller's generic sentence. `literals` holds the codes and values
|
|
141
|
+
* the sentences quote for sending back.
|
|
142
|
+
*/
|
|
143
|
+
export function unmatchedValuesAdvice(datasetCode, unmatched, { meta, valuesByDimension }) {
|
|
144
|
+
const sentences = [];
|
|
145
|
+
const literals = new Set();
|
|
146
|
+
const validValues = {};
|
|
147
|
+
const unresolved = [];
|
|
148
|
+
const dotted = [];
|
|
149
|
+
let dottedAt = -1;
|
|
150
|
+
for (const [key, sent] of Object.entries(unmatched)) {
|
|
151
|
+
const folded = key.toLowerCase();
|
|
152
|
+
const dimension = meta.dimensions.find(({ code }) => code === key) ??
|
|
153
|
+
meta.dimensions.find(({ code }) => code.toLowerCase() === folded);
|
|
154
|
+
const values = dimension && valuesByDimension[dimension.code];
|
|
155
|
+
if (!dimension || !values?.length) {
|
|
156
|
+
unresolved.push(key);
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (values.length <= VALUES_LISTED_MAX) {
|
|
160
|
+
validValues[key] = {
|
|
161
|
+
totalCount: values.length,
|
|
162
|
+
values: values.map(({ code, label }) => ({ code, label })),
|
|
163
|
+
};
|
|
164
|
+
const listed = values.map(({ code, label }) => `${code} (${label})`).join(', ');
|
|
165
|
+
sentences.push(`"${key}" has ${values.length === 1 ? '1 code' : `${values.length} codes`}: ${listed}.`);
|
|
166
|
+
for (const { code } of values)
|
|
167
|
+
literals.add(code);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
validValues[key] = { totalCount: values.length };
|
|
171
|
+
sentences.push(`"${key}" has ${values.length.toLocaleString('en-US')} codes, too many to list here.`);
|
|
172
|
+
for (const value of sent) {
|
|
173
|
+
if (value.includes('.')) {
|
|
174
|
+
dotted.push({ dimension: key, value });
|
|
175
|
+
if (dotted.length === 1) {
|
|
176
|
+
dottedAt = sentences.length;
|
|
177
|
+
sentences.push('');
|
|
178
|
+
}
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
sentences.push(`Find the code for "${value}" with eurostat_get_dimension_values, dimension "${key}" and filter "${value}"${dimension.code === 'geo' ? ', adding geo_level for a code below country level' : ''}.`);
|
|
182
|
+
literals.add(value).add(key);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const [firstDotted, ...otherDotted] = dotted;
|
|
186
|
+
if (firstDotted) {
|
|
187
|
+
const advice = dottedValueAdvice(datasetCode, [firstDotted, ...otherDotted]);
|
|
188
|
+
sentences[dottedAt] = advice.hint;
|
|
189
|
+
for (const literal of advice.literals)
|
|
190
|
+
literals.add(literal);
|
|
191
|
+
}
|
|
192
|
+
return { literals: [...literals], sentences, unresolved, validValues };
|
|
193
|
+
}
|
|
38
194
|
/**
|
|
39
195
|
* Tell the caller how to shrink a refused request, naming the dimensions of this
|
|
40
196
|
* dataset it left unfiltered. `dimensions` is the dataset's key dimensions, or
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"narrowing-advice.js","sourceRoot":"","sources":["../../../src/mcp-server/tools/narrowing-advice.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"narrowing-advice.js","sourceRoot":"","sources":["../../../src/mcp-server/tools/narrowing-advice.ts"],"names":[],"mappings":"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"}
|