@hypequery/mcp 0.5.5 → 0.6.0
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 +83 -6
- package/dist/api.type-test.d.ts +2 -0
- package/dist/api.type-test.d.ts.map +1 -0
- package/dist/api.type-test.js +14 -0
- package/dist/bin.js +2 -1
- package/dist/errors.d.ts +29 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +65 -0
- package/dist/executor.d.ts +50 -0
- package/dist/executor.d.ts.map +1 -0
- package/dist/executor.js +103 -0
- package/dist/index.d.ts +8 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -2
- package/dist/protocol-server.d.ts +26 -0
- package/dist/protocol-server.d.ts.map +1 -0
- package/dist/protocol-server.js +48 -0
- package/dist/server.d.ts +15 -44
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +21 -271
- package/dist/stdio.d.ts +8 -0
- package/dist/stdio.d.ts.map +1 -0
- package/dist/stdio.js +14 -0
- package/dist/tools/args.d.ts +16 -16
- package/dist/tools/args.d.ts.map +1 -1
- package/dist/tools/args.js +9 -8
- package/dist/tools/introspect.d.ts +8 -6
- package/dist/tools/introspect.d.ts.map +1 -1
- package/dist/tools/introspect.js +45 -168
- package/dist/tools/list-datasets.d.ts.map +1 -1
- package/dist/tools/list-datasets.js +2 -8
- package/dist/tools/query-dataset.d.ts.map +1 -1
- package/dist/tools/query-dataset.js +26 -30
- package/dist/tools/query-metric.d.ts.map +1 -1
- package/dist/tools/query-metric.js +27 -31
- package/dist/tools/tool-manifest.d.ts +28 -0
- package/dist/tools/tool-manifest.d.ts.map +1 -0
- package/dist/tools/tool-manifest.js +302 -0
- package/dist/tools/utils/canonical-query-schemas.d.ts +9 -0
- package/dist/tools/utils/canonical-query-schemas.d.ts.map +1 -0
- package/dist/tools/utils/canonical-query-schemas.js +108 -0
- package/dist/tools/utils/execution-budget.d.ts +11 -0
- package/dist/tools/utils/execution-budget.d.ts.map +1 -0
- package/dist/tools/utils/execution-budget.js +71 -0
- package/dist/tools/utils/legacy-agent-catalog.d.ts +19 -0
- package/dist/tools/utils/legacy-agent-catalog.d.ts.map +1 -0
- package/dist/tools/utils/legacy-agent-catalog.js +171 -0
- package/dist/tools/utils/query-limits.d.ts +25 -0
- package/dist/tools/utils/query-limits.d.ts.map +1 -0
- package/dist/tools/utils/query-limits.js +69 -0
- package/dist/tools/utils/query-result.d.ts +4 -0
- package/dist/tools/utils/query-result.d.ts.map +1 -0
- package/dist/tools/utils/query-result.js +19 -0
- package/dist/tools/utils/query-schema.d.ts +8 -0
- package/dist/tools/utils/query-schema.d.ts.map +1 -0
- package/dist/tools/utils/query-schema.js +36 -0
- package/dist/tools/utils/tool-response.d.ts +6 -0
- package/dist/tools/utils/tool-response.d.ts.map +1 -0
- package/dist/tools/utils/tool-response.js +33 -0
- package/dist/types.d.ts +54 -58
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +9 -0
- package/dist/utils/tenant-config.d.ts +3 -0
- package/dist/utils/tenant-config.d.ts.map +1 -0
- package/dist/utils/tenant-config.js +23 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +5 -0
- package/package.json +7 -3
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grains the semantic planner can actually execute. A legacy registry is
|
|
3
|
+
* untyped, so anything outside this set is dropped rather than advertised.
|
|
4
|
+
*/
|
|
5
|
+
const TIME_GRAINS = new Set(['day', 'week', 'month', 'quarter', 'year']);
|
|
6
|
+
function record(value) {
|
|
7
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
8
|
+
? value
|
|
9
|
+
: {};
|
|
10
|
+
}
|
|
11
|
+
function text(value) {
|
|
12
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
13
|
+
}
|
|
14
|
+
function fieldType(value) {
|
|
15
|
+
return value === 'string' || value === 'number' || value === 'boolean' || value === 'timestamp'
|
|
16
|
+
? value
|
|
17
|
+
: undefined;
|
|
18
|
+
}
|
|
19
|
+
function namedEntries(value) {
|
|
20
|
+
return Object.entries(record(value))
|
|
21
|
+
.map(([name, entry]) => [name, record(entry)])
|
|
22
|
+
.sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
|
|
23
|
+
}
|
|
24
|
+
function optionalDescription(value) {
|
|
25
|
+
const label = text(value.label);
|
|
26
|
+
const description = text(value.description);
|
|
27
|
+
return {
|
|
28
|
+
...(label !== undefined ? { label } : {}),
|
|
29
|
+
...(description !== undefined ? { description } : {}),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function semanticMetadata(value) {
|
|
33
|
+
const list = (input) => Array.isArray(input)
|
|
34
|
+
? [...new Set(input.filter((item) => typeof item === 'string'))].sort()
|
|
35
|
+
: undefined;
|
|
36
|
+
const sensitivity = ['public', 'internal', 'confidential', 'restricted'].includes(String(value.sensitivity))
|
|
37
|
+
? value.sensitivity
|
|
38
|
+
: undefined;
|
|
39
|
+
return {
|
|
40
|
+
...(list(value.examples) !== undefined ? { examples: list(value.examples) } : {}),
|
|
41
|
+
...(list(value.synonyms) !== undefined ? { synonyms: list(value.synonyms) } : {}),
|
|
42
|
+
...(text(value.format) !== undefined ? { format: text(value.format) } : {}),
|
|
43
|
+
...(text(value.unit) !== undefined ? { unit: text(value.unit) } : {}),
|
|
44
|
+
...(/^[A-Z]{3}$/.test(String(value.currency)) ? { currency: String(value.currency) } : {}),
|
|
45
|
+
...(text(value.timezone) !== undefined ? { timezone: text(value.timezone) } : {}),
|
|
46
|
+
...(sensitivity !== undefined ? { sensitivity } : {}),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function limits(value) {
|
|
50
|
+
const input = record(value);
|
|
51
|
+
const result = {};
|
|
52
|
+
for (const key of ['maxDimensions', 'maxMeasures', 'maxFilters', 'maxResultSize']) {
|
|
53
|
+
const candidate = input[key];
|
|
54
|
+
if (Number.isSafeInteger(candidate) && candidate > 0)
|
|
55
|
+
result[key] = candidate;
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
/** Keeps only the members of `value` that name something the agent may already see. */
|
|
60
|
+
function declaredNames(value, declared) {
|
|
61
|
+
return Array.isArray(value)
|
|
62
|
+
? [...new Set(value.filter((item) => (typeof item === 'string' && declared.has(item))))].sort()
|
|
63
|
+
: [];
|
|
64
|
+
}
|
|
65
|
+
/** The dimensions a legacy registry entry exposes, in the shape the agent sees. */
|
|
66
|
+
function legacyDimensions(input) {
|
|
67
|
+
return namedEntries(input.dimensions)
|
|
68
|
+
.map(([dimensionName, dimension]) => ({
|
|
69
|
+
name: dimensionName,
|
|
70
|
+
type: fieldType(dimension.fieldType ?? dimension.type),
|
|
71
|
+
...optionalDescription(dimension),
|
|
72
|
+
...semanticMetadata(dimension),
|
|
73
|
+
filterable: dimension.filterable !== false,
|
|
74
|
+
groupable: dimension.groupable !== false,
|
|
75
|
+
}))
|
|
76
|
+
.filter((dimension) => (dimension.type !== undefined && (dimension.filterable || dimension.groupable)));
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Safely adapts the pre-catalog, object-shaped MCP registry compatibility input.
|
|
80
|
+
*
|
|
81
|
+
* A legacy entry is arbitrary user data, so every name it carries is checked
|
|
82
|
+
* against what this projection already publishes before it is emitted:
|
|
83
|
+
* references that do not resolve to a declared dimension, filter, grain, or
|
|
84
|
+
* sibling dataset are dropped rather than passed through. Otherwise a physical
|
|
85
|
+
* column or tenant key sitting in a legacy `metrics`/`relationships` block
|
|
86
|
+
* would reach the agent through `get_dataset_schema`.
|
|
87
|
+
*
|
|
88
|
+
* `registry` is the surrounding dataset map, used to resolve relationship
|
|
89
|
+
* targets. A relationship whose target is not in it is unreachable, so it is
|
|
90
|
+
* omitted instead of advertising a name the agent cannot query.
|
|
91
|
+
*/
|
|
92
|
+
export function projectLegacyAgentDataset(name, input, registry = {}) {
|
|
93
|
+
const config = record(input.config);
|
|
94
|
+
const dimensions = legacyDimensions(input);
|
|
95
|
+
const dimensionTypes = new Map(dimensions.map(dimension => [dimension.name, dimension.type]));
|
|
96
|
+
const dimensionNames = new Set(dimensionTypes.keys());
|
|
97
|
+
const filters = namedEntries(input.filters)
|
|
98
|
+
.map(([filterName, filter]) => ({
|
|
99
|
+
name: filterName,
|
|
100
|
+
type: dimensionTypes.get(text(filter.field) ?? filterName),
|
|
101
|
+
...optionalDescription(filter),
|
|
102
|
+
...semanticMetadata(filter),
|
|
103
|
+
operators: Array.isArray(filter.operators)
|
|
104
|
+
? [...new Set(filter.operators.filter((operator) => typeof operator === 'string'))].sort()
|
|
105
|
+
: [],
|
|
106
|
+
}))
|
|
107
|
+
.filter((filter) => filter.type !== undefined);
|
|
108
|
+
const filterNames = new Set(filters.map(filter => filter.name));
|
|
109
|
+
const timeKey = text(input.timeKey) ?? text(config.timeKey);
|
|
110
|
+
const freshnessInput = record(input.freshness);
|
|
111
|
+
const freshness = Number.isSafeInteger(freshnessInput.maxAgeSeconds)
|
|
112
|
+
&& freshnessInput.maxAgeSeconds > 0
|
|
113
|
+
? { maxAgeSeconds: freshnessInput.maxAgeSeconds }
|
|
114
|
+
: undefined;
|
|
115
|
+
const defaultsInput = record(input.defaults);
|
|
116
|
+
const defaultDimensions = Array.isArray(defaultsInput.dimensions)
|
|
117
|
+
? declaredNames(defaultsInput.dimensions, dimensionNames)
|
|
118
|
+
: undefined;
|
|
119
|
+
const timeGrain = TIME_GRAINS.has(String(defaultsInput.timeGrain))
|
|
120
|
+
? defaultsInput.timeGrain
|
|
121
|
+
: undefined;
|
|
122
|
+
const defaults = defaultDimensions !== undefined || timeGrain !== undefined
|
|
123
|
+
? {
|
|
124
|
+
...(defaultDimensions !== undefined ? { dimensions: defaultDimensions } : {}),
|
|
125
|
+
...(timeGrain !== undefined ? { timeGrain } : {}),
|
|
126
|
+
}
|
|
127
|
+
: undefined;
|
|
128
|
+
return {
|
|
129
|
+
name,
|
|
130
|
+
description: text(input.description) ?? text(config.description) ?? `${name} analytics dataset.`,
|
|
131
|
+
...semanticMetadata(input),
|
|
132
|
+
...(freshness !== undefined ? { freshness } : {}),
|
|
133
|
+
...(text(input.owner) !== undefined ? { owner: text(input.owner) } : {}),
|
|
134
|
+
...(defaults !== undefined ? { defaults } : {}),
|
|
135
|
+
timeDimension: timeKey !== undefined && dimensionNames.has(timeKey) ? timeKey : null,
|
|
136
|
+
dimensions,
|
|
137
|
+
measures: namedEntries(input.measures).map(([measureName, measure]) => ({
|
|
138
|
+
name: measureName,
|
|
139
|
+
...optionalDescription(measure),
|
|
140
|
+
...semanticMetadata(measure),
|
|
141
|
+
})),
|
|
142
|
+
metrics: namedEntries(input.metrics).map(([metricName, metric]) => ({
|
|
143
|
+
name: metricName,
|
|
144
|
+
...optionalDescription(metric),
|
|
145
|
+
...semanticMetadata(metric),
|
|
146
|
+
dimensions: declaredNames(metric.dimensions, dimensionNames),
|
|
147
|
+
filters: declaredNames(metric.filters, filterNames),
|
|
148
|
+
grains: declaredNames(metric.grains, TIME_GRAINS),
|
|
149
|
+
})),
|
|
150
|
+
filters,
|
|
151
|
+
relationships: namedEntries(input.relationships)
|
|
152
|
+
.filter(([, relationship]) => relationship.queryable !== false && relationship.kind !== 'hasMany')
|
|
153
|
+
.map(([relationshipName, relationship]) => {
|
|
154
|
+
const target = text(relationship.target) ?? text(record(relationship.dataset).name);
|
|
155
|
+
if (target === undefined || !Object.prototype.hasOwnProperty.call(registry, target)) {
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
// A queryable relationship field is exactly `<relationship>.<dimension>`
|
|
159
|
+
// over a single hop, addressing a dimension the target already
|
|
160
|
+
// publishes. See `listQueryableRelationshipFields`.
|
|
161
|
+
const targetDimensions = new Set(legacyDimensions(record(registry[target])).map(dimension => dimension.name));
|
|
162
|
+
return {
|
|
163
|
+
name: relationshipName,
|
|
164
|
+
target,
|
|
165
|
+
fields: declaredNames(relationship.fields, new Set([...targetDimensions].map(field => `${relationshipName}.${field}`))),
|
|
166
|
+
};
|
|
167
|
+
})
|
|
168
|
+
.filter((relationship) => (relationship !== undefined)),
|
|
169
|
+
limits: limits(input.limits),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type MCPQueryLimits } from '../../types.js';
|
|
2
|
+
interface QueryCollections {
|
|
3
|
+
readonly dimensions?: readonly unknown[];
|
|
4
|
+
readonly measures?: readonly unknown[];
|
|
5
|
+
readonly filters?: readonly unknown[];
|
|
6
|
+
readonly orderBy?: readonly unknown[];
|
|
7
|
+
readonly limit?: number;
|
|
8
|
+
readonly offset?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface EffectiveQueryLimits {
|
|
11
|
+
readonly defaultResultSize: number;
|
|
12
|
+
readonly maxResultSize: number;
|
|
13
|
+
readonly maxOffset: number;
|
|
14
|
+
readonly maxDimensions: number;
|
|
15
|
+
readonly maxMeasures: number;
|
|
16
|
+
readonly maxFilters: number;
|
|
17
|
+
readonly maxOrderBy: number;
|
|
18
|
+
}
|
|
19
|
+
export declare function resolveQueryLimits(dataset: unknown, configured?: MCPQueryLimits): EffectiveQueryLimits;
|
|
20
|
+
export declare function applyQueryLimits(dataset: unknown, query: QueryCollections, configured?: MCPQueryLimits): {
|
|
21
|
+
readonly limit: number;
|
|
22
|
+
readonly offset?: number;
|
|
23
|
+
};
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=query-limits.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-limits.d.ts","sourceRoot":"","sources":["../../../src/tools/utils/query-limits.ts"],"names":[],"mappings":"AAEA,OAAO,EAQL,KAAK,cAAc,EACpB,MAAM,gBAAgB,CAAC;AAExB,UAAU,gBAAgB;IACxB,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;IACzC,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;IACvC,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;IACtC,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;IACtC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAsCD,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,OAAO,EAChB,UAAU,GAAE,cAAmB,GAC9B,oBAAoB,CAkCtB;AAeD,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,gBAAgB,EACvB,UAAU,GAAE,cAAmB,GAC9B;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAqBtD"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { MCPToolError } from '../../errors.js';
|
|
2
|
+
import { DEFAULT_QUERY_LIMIT, MAX_QUERY_DIMENSIONS, MAX_QUERY_FILTERS, MAX_QUERY_LIMIT, MAX_QUERY_MEASURES, MAX_QUERY_OFFSET, MAX_QUERY_ORDER_BY, } from '../../types.js';
|
|
3
|
+
function positiveInteger(value, fallback, maximum, name) {
|
|
4
|
+
const resolved = value ?? fallback;
|
|
5
|
+
if (!Number.isSafeInteger(resolved) || resolved < 1 || resolved > maximum) {
|
|
6
|
+
throw new Error(`${name} must be an integer between 1 and ${maximum}`);
|
|
7
|
+
}
|
|
8
|
+
return resolved;
|
|
9
|
+
}
|
|
10
|
+
function nonNegativeInteger(value, fallback, maximum, name) {
|
|
11
|
+
const resolved = value ?? fallback;
|
|
12
|
+
if (!Number.isSafeInteger(resolved) || resolved < 0 || resolved > maximum) {
|
|
13
|
+
throw new Error(`${name} must be an integer between 0 and ${maximum}`);
|
|
14
|
+
}
|
|
15
|
+
return resolved;
|
|
16
|
+
}
|
|
17
|
+
function datasetLimits(dataset) {
|
|
18
|
+
if (!dataset || typeof dataset !== 'object' || Array.isArray(dataset))
|
|
19
|
+
return undefined;
|
|
20
|
+
const limits = dataset.limits;
|
|
21
|
+
return limits && typeof limits === 'object' && !Array.isArray(limits)
|
|
22
|
+
? limits
|
|
23
|
+
: undefined;
|
|
24
|
+
}
|
|
25
|
+
function datasetPositiveInteger(value, name) {
|
|
26
|
+
if (value === undefined)
|
|
27
|
+
return undefined;
|
|
28
|
+
if (!Number.isSafeInteger(value) || value < 1) {
|
|
29
|
+
throw new Error(`Dataset ${name} must be a positive integer`);
|
|
30
|
+
}
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
function lowerLimit(...values) {
|
|
34
|
+
return Math.min(...values.filter((value) => value !== undefined));
|
|
35
|
+
}
|
|
36
|
+
export function resolveQueryLimits(dataset, configured = {}) {
|
|
37
|
+
const semantic = datasetLimits(dataset);
|
|
38
|
+
const maxResultSize = lowerLimit(positiveInteger(configured.maxResultSize, MAX_QUERY_LIMIT, MAX_QUERY_LIMIT, 'maxResultSize'), datasetPositiveInteger(semantic?.maxResultSize, 'maxResultSize'));
|
|
39
|
+
const defaultResultSize = lowerLimit(positiveInteger(configured.defaultResultSize, DEFAULT_QUERY_LIMIT, MAX_QUERY_LIMIT, 'defaultResultSize'), maxResultSize);
|
|
40
|
+
return Object.freeze({
|
|
41
|
+
defaultResultSize,
|
|
42
|
+
maxResultSize,
|
|
43
|
+
maxOffset: nonNegativeInteger(configured.maxOffset, MAX_QUERY_OFFSET, MAX_QUERY_OFFSET, 'maxOffset'),
|
|
44
|
+
maxDimensions: lowerLimit(positiveInteger(configured.maxDimensions, MAX_QUERY_DIMENSIONS, MAX_QUERY_DIMENSIONS, 'maxDimensions'), datasetPositiveInteger(semantic?.maxDimensions, 'maxDimensions')),
|
|
45
|
+
maxMeasures: lowerLimit(positiveInteger(configured.maxMeasures, MAX_QUERY_MEASURES, MAX_QUERY_MEASURES, 'maxMeasures'), datasetPositiveInteger(semantic?.maxMeasures, 'maxMeasures')),
|
|
46
|
+
maxFilters: lowerLimit(positiveInteger(configured.maxFilters, MAX_QUERY_FILTERS, MAX_QUERY_FILTERS, 'maxFilters'), datasetPositiveInteger(semantic?.maxFilters, 'maxFilters')),
|
|
47
|
+
maxOrderBy: positiveInteger(configured.maxOrderBy, MAX_QUERY_ORDER_BY, MAX_QUERY_ORDER_BY, 'maxOrderBy'),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
function assertCollectionLimit(values, maximum, name) {
|
|
51
|
+
if ((values?.length ?? 0) > maximum) {
|
|
52
|
+
throw new MCPToolError('MCP_INVALID_ARGUMENTS', `Invalid ${name}: maximum ${maximum} items`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export function applyQueryLimits(dataset, query, configured = {}) {
|
|
56
|
+
const limits = resolveQueryLimits(dataset, configured);
|
|
57
|
+
assertCollectionLimit(query.dimensions, limits.maxDimensions, 'dimensions');
|
|
58
|
+
assertCollectionLimit(query.measures, limits.maxMeasures, 'measures');
|
|
59
|
+
assertCollectionLimit(query.filters, limits.maxFilters, 'filters');
|
|
60
|
+
assertCollectionLimit(query.orderBy, limits.maxOrderBy, 'orderBy');
|
|
61
|
+
const limit = query.limit ?? limits.defaultResultSize;
|
|
62
|
+
if (limit > limits.maxResultSize) {
|
|
63
|
+
throw new MCPToolError('MCP_INVALID_ARGUMENTS', `Invalid limit: ${limit}. Max: ${limits.maxResultSize}`);
|
|
64
|
+
}
|
|
65
|
+
if (query.offset !== undefined && query.offset > limits.maxOffset) {
|
|
66
|
+
throw new MCPToolError('MCP_INVALID_ARGUMENTS', `Invalid offset: ${query.offset}. Max: ${limits.maxOffset}`);
|
|
67
|
+
}
|
|
68
|
+
return Object.freeze({ limit, ...(query.offset === undefined ? {} : { offset: query.offset }) });
|
|
69
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { DatasetQueryResult } from '@hypequery/datasets';
|
|
2
|
+
import type { QueryResultResponse } from '../../types.js';
|
|
3
|
+
export declare function buildMCPQueryResult(result: DatasetQueryResult, includeSql?: boolean): QueryResultResponse;
|
|
4
|
+
//# sourceMappingURL=query-result.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-result.d.ts","sourceRoot":"","sources":["../../../src/tools/utils/query-result.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAE1D,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,kBAAkB,EAC1B,UAAU,UAAQ,GACjB,mBAAmB,CAkBrB"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function buildMCPQueryResult(result, includeSql = false) {
|
|
2
|
+
const cache = result.meta?.cache;
|
|
3
|
+
return {
|
|
4
|
+
data: result.data,
|
|
5
|
+
meta: {
|
|
6
|
+
...(includeSql && result.meta?.sql ? { sql: result.meta.sql } : {}),
|
|
7
|
+
...(result.meta?.timingMs === undefined ? {} : { timingMs: result.meta.timingMs }),
|
|
8
|
+
rowCount: result.data.length,
|
|
9
|
+
...(result.meta?.pagination ? { pagination: result.meta.pagination } : {}),
|
|
10
|
+
cache: cache
|
|
11
|
+
? {
|
|
12
|
+
status: cache.hit ? 'hit' : 'miss',
|
|
13
|
+
...(cache.ageMs === undefined ? {} : { ageMs: cache.ageMs }),
|
|
14
|
+
...(cache.stale === undefined ? {} : { stale: cache.stale }),
|
|
15
|
+
}
|
|
16
|
+
: { status: 'bypass' },
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { DatasetRegistry, MCPQueryLimits } from '../../types.js';
|
|
2
|
+
export type JsonObject = Record<string, unknown>;
|
|
3
|
+
/**
|
|
4
|
+
* Builds a dataset-discriminated query schema that advertises each dataset's
|
|
5
|
+
* effective server and semantic-layer limits to MCP clients.
|
|
6
|
+
*/
|
|
7
|
+
export declare function advertiseDatasetQueryLimits(schema: JsonObject, datasets: DatasetRegistry, configured: MCPQueryLimits | undefined, includeMeasures: boolean): JsonObject;
|
|
8
|
+
//# sourceMappingURL=query-schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-schema.d.ts","sourceRoot":"","sources":["../../../src/tools/utils/query-schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGtE,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEjD;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,UAAU,EAClB,QAAQ,EAAE,eAAe,EACzB,UAAU,EAAE,cAAc,GAAG,SAAS,EACtC,eAAe,EAAE,OAAO,GACvB,UAAU,CA8BZ"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { resolveQueryLimits } from './query-limits.js';
|
|
2
|
+
/**
|
|
3
|
+
* Builds a dataset-discriminated query schema that advertises each dataset's
|
|
4
|
+
* effective server and semantic-layer limits to MCP clients.
|
|
5
|
+
*/
|
|
6
|
+
export function advertiseDatasetQueryLimits(schema, datasets, configured, includeMeasures) {
|
|
7
|
+
const entries = Object.entries(datasets);
|
|
8
|
+
if (entries.length === 0)
|
|
9
|
+
return schema;
|
|
10
|
+
const properties = schema.properties;
|
|
11
|
+
return {
|
|
12
|
+
type: 'object',
|
|
13
|
+
anyOf: entries.map(([name, dataset]) => {
|
|
14
|
+
const limits = resolveQueryLimits(dataset, configured);
|
|
15
|
+
return {
|
|
16
|
+
...schema,
|
|
17
|
+
properties: {
|
|
18
|
+
...properties,
|
|
19
|
+
dataset: { ...properties.dataset, enum: [name] },
|
|
20
|
+
dimensions: { ...properties.dimensions, maxItems: limits.maxDimensions },
|
|
21
|
+
...(includeMeasures ? {
|
|
22
|
+
measures: { ...properties.measures, maxItems: limits.maxMeasures },
|
|
23
|
+
} : {}),
|
|
24
|
+
filters: { ...properties.filters, maxItems: limits.maxFilters },
|
|
25
|
+
orderBy: { ...properties.orderBy, maxItems: limits.maxOrderBy },
|
|
26
|
+
limit: {
|
|
27
|
+
...properties.limit,
|
|
28
|
+
maximum: limits.maxResultSize,
|
|
29
|
+
default: limits.defaultResultSize,
|
|
30
|
+
},
|
|
31
|
+
offset: { ...properties.offset, maximum: limits.maxOffset },
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { MCPToolResponse } from '../../types.js';
|
|
2
|
+
export declare function createMCPToolResponse<T extends object>(value: T, text?: string): MCPToolResponse;
|
|
3
|
+
export declare function createMCPErrorResponse(error: unknown): MCPToolResponse;
|
|
4
|
+
/** Smallest schema-valid error used when even the original error exceeds its budget. */
|
|
5
|
+
export declare function createMCPResultTooLargeResponse(): MCPToolResponse;
|
|
6
|
+
//# sourceMappingURL=tool-response.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-response.d.ts","sourceRoot":"","sources":["../../../src/tools/utils/tool-response.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEtD,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,MAAM,EACpD,KAAK,EAAE,CAAC,EACR,IAAI,SAAwB,GAC3B,eAAe,CAKjB;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,CAOtE;AAED,wFAAwF;AACxF,wBAAgB,+BAA+B,IAAI,eAAe,CAgBjE"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { classifyMCPToolError, formatMCPToolError } from '../../errors.js';
|
|
2
|
+
export function createMCPToolResponse(value, text = JSON.stringify(value)) {
|
|
3
|
+
return {
|
|
4
|
+
content: [{ type: 'text', text }],
|
|
5
|
+
structuredContent: value,
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
export function createMCPErrorResponse(error) {
|
|
9
|
+
const details = classifyMCPToolError(error);
|
|
10
|
+
return {
|
|
11
|
+
content: [{ type: 'text', text: formatMCPToolError(error) }],
|
|
12
|
+
structuredContent: { error: details },
|
|
13
|
+
isError: true,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/** Smallest schema-valid error used when even the original error exceeds its budget. */
|
|
17
|
+
export function createMCPResultTooLargeResponse() {
|
|
18
|
+
const details = {
|
|
19
|
+
code: 'MCP_RESULT_TOO_LARGE',
|
|
20
|
+
category: 'budget',
|
|
21
|
+
message: 'Result exceeds response byte limit',
|
|
22
|
+
retryable: false,
|
|
23
|
+
correctable: false,
|
|
24
|
+
};
|
|
25
|
+
return {
|
|
26
|
+
content: [{
|
|
27
|
+
type: 'text',
|
|
28
|
+
text: `Error [${details.code}]: ${details.message}`,
|
|
29
|
+
}],
|
|
30
|
+
structuredContent: { error: details },
|
|
31
|
+
isError: true,
|
|
32
|
+
};
|
|
33
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Type definitions for MCP Server
|
|
3
3
|
*/
|
|
4
|
-
import type { AnyDatasetInstance, MetricFilter, TimeGrain, MetricOrderBy } from '@hypequery/datasets';
|
|
4
|
+
import type { AgentCatalogDataset, AgentCatalogDimension, AgentCatalogFilter, AgentCatalogMeasure, AgentCatalogMetric, AgentCatalogRelationship, AnyDatasetInstance, MetricFilter, TimeGrain, MetricOrderBy } from '@hypequery/datasets';
|
|
5
|
+
import type { ZodTypeAny } from 'zod';
|
|
5
6
|
/**
|
|
6
7
|
* Registry of datasets - maps dataset names to dataset instances
|
|
7
8
|
*/
|
|
@@ -35,8 +36,38 @@ export interface QueryDatasetArgs {
|
|
|
35
36
|
export interface QueryToolOptions {
|
|
36
37
|
tenantId?: string;
|
|
37
38
|
includeSql?: boolean;
|
|
39
|
+
limits?: MCPQueryLimits;
|
|
40
|
+
executionBudget?: MCPExecutionBudget;
|
|
41
|
+
signal?: AbortSignal;
|
|
42
|
+
/** Canonical catalog-derived validator supplied by the owning server. */
|
|
43
|
+
inputSchema?: ZodTypeAny;
|
|
44
|
+
}
|
|
45
|
+
/** Per-query wall-clock and serialized-response ceilings. */
|
|
46
|
+
export interface MCPExecutionBudget {
|
|
47
|
+
/** Maximum query duration in milliseconds. Defaults to 30 seconds. */
|
|
48
|
+
timeoutMs?: number;
|
|
49
|
+
/** Maximum UTF-8 bytes in the serialized query response. Defaults to 1 MiB. */
|
|
50
|
+
maxResponseBytes?: number;
|
|
51
|
+
}
|
|
52
|
+
/** Server-side ceilings applied in addition to Dataset limits. */
|
|
53
|
+
export interface MCPQueryLimits {
|
|
54
|
+
/** Rows used when a tool call omits `limit`. Defaults to 100. */
|
|
55
|
+
defaultResultSize?: number;
|
|
56
|
+
/** Maximum explicit or default row limit. Cannot exceed 10,000. */
|
|
57
|
+
maxResultSize?: number;
|
|
58
|
+
/** Maximum pagination offset. Cannot exceed 10,000. */
|
|
59
|
+
maxOffset?: number;
|
|
60
|
+
/** Maximum selected dimensions. Cannot exceed 50. */
|
|
61
|
+
maxDimensions?: number;
|
|
62
|
+
/** Maximum selected measures. Cannot exceed 50. */
|
|
63
|
+
maxMeasures?: number;
|
|
64
|
+
/** Maximum filters. Cannot exceed 100. */
|
|
65
|
+
maxFilters?: number;
|
|
66
|
+
/** Maximum order clauses. Cannot exceed 50. */
|
|
67
|
+
maxOrderBy?: number;
|
|
38
68
|
}
|
|
39
69
|
export interface SchemaToolOptions {
|
|
70
|
+
/** @deprecated Agent-facing introspection is always safe. Use getTrustedDatasetSchema separately. */
|
|
40
71
|
includeSql?: boolean;
|
|
41
72
|
}
|
|
42
73
|
/**
|
|
@@ -54,84 +85,34 @@ export interface MCPToolResponse {
|
|
|
54
85
|
type: 'text';
|
|
55
86
|
text: string;
|
|
56
87
|
}>;
|
|
88
|
+
structuredContent?: Record<string, unknown>;
|
|
57
89
|
isError?: boolean;
|
|
58
90
|
[key: string]: unknown;
|
|
59
91
|
}
|
|
60
92
|
/**
|
|
61
93
|
* Dataset schema response structure
|
|
62
94
|
*/
|
|
63
|
-
export
|
|
64
|
-
name: string;
|
|
65
|
-
description: string;
|
|
66
|
-
source: string;
|
|
67
|
-
timeKey: string | null;
|
|
68
|
-
tenantKey: string | null;
|
|
69
|
-
dimensions: Record<string, DimensionSchema>;
|
|
70
|
-
measures: Record<string, MeasureSchema>;
|
|
71
|
-
metrics: Record<string, MetricSchema>;
|
|
72
|
-
filters: Record<string, FilterSchema>;
|
|
73
|
-
relationships: Record<string, RelationshipSchema>;
|
|
74
|
-
limits?: {
|
|
75
|
-
maxDimensions?: number;
|
|
76
|
-
maxMeasures?: number;
|
|
77
|
-
maxFilters?: number;
|
|
78
|
-
maxResultSize?: number;
|
|
79
|
-
};
|
|
80
|
-
}
|
|
95
|
+
export type DatasetSchema = AgentCatalogDataset;
|
|
81
96
|
/**
|
|
82
97
|
* Dimension schema in response
|
|
83
98
|
*/
|
|
84
|
-
export
|
|
85
|
-
type: string;
|
|
86
|
-
column: string | null;
|
|
87
|
-
sql: string | null;
|
|
88
|
-
label: string;
|
|
89
|
-
description: string;
|
|
90
|
-
examples: string[];
|
|
91
|
-
filterable: boolean;
|
|
92
|
-
groupable: boolean;
|
|
93
|
-
}
|
|
99
|
+
export type DimensionSchema = AgentCatalogDimension;
|
|
94
100
|
/**
|
|
95
101
|
* Measure schema in response
|
|
96
102
|
*/
|
|
97
|
-
export
|
|
98
|
-
aggregation: string;
|
|
99
|
-
field: string;
|
|
100
|
-
sql: string | null;
|
|
101
|
-
label: string;
|
|
102
|
-
description: string;
|
|
103
|
-
}
|
|
103
|
+
export type MeasureSchema = AgentCatalogMeasure;
|
|
104
104
|
/**
|
|
105
105
|
* Filter schema in response
|
|
106
106
|
*/
|
|
107
|
-
export
|
|
108
|
-
field: string;
|
|
109
|
-
label: string;
|
|
110
|
-
description: string;
|
|
111
|
-
operators: string[] | null;
|
|
112
|
-
}
|
|
107
|
+
export type FilterSchema = AgentCatalogFilter;
|
|
113
108
|
/**
|
|
114
109
|
* Metric schema in response
|
|
115
110
|
*/
|
|
116
|
-
export
|
|
117
|
-
type: string;
|
|
118
|
-
aggregation: string;
|
|
119
|
-
label: string;
|
|
120
|
-
description: string;
|
|
121
|
-
format: string | null;
|
|
122
|
-
}
|
|
111
|
+
export type MetricSchema = AgentCatalogMetric;
|
|
123
112
|
/**
|
|
124
113
|
* Relationship schema in response
|
|
125
114
|
*/
|
|
126
|
-
export
|
|
127
|
-
type: string;
|
|
128
|
-
target: string;
|
|
129
|
-
from?: string;
|
|
130
|
-
to?: string;
|
|
131
|
-
queryable?: boolean;
|
|
132
|
-
fields?: string[];
|
|
133
|
-
description: string;
|
|
134
|
-
}
|
|
115
|
+
export type RelationshipSchema = AgentCatalogRelationship;
|
|
135
116
|
/**
|
|
136
117
|
* Dataset list item
|
|
137
118
|
*/
|
|
@@ -165,6 +146,12 @@ export interface QueryResultMeta {
|
|
|
165
146
|
offset: number;
|
|
166
147
|
hasMore: boolean;
|
|
167
148
|
};
|
|
149
|
+
/** Cache outcome for agent observability. */
|
|
150
|
+
cache?: {
|
|
151
|
+
status: 'hit' | 'miss' | 'bypass';
|
|
152
|
+
ageMs?: number;
|
|
153
|
+
stale?: boolean;
|
|
154
|
+
};
|
|
168
155
|
}
|
|
169
156
|
/**
|
|
170
157
|
* Query result response
|
|
@@ -178,4 +165,13 @@ export interface QueryResultResponse {
|
|
|
178
165
|
*/
|
|
179
166
|
export declare const MAX_QUERY_LIMIT = 10000;
|
|
180
167
|
export declare const DEFAULT_QUERY_LIMIT = 100;
|
|
168
|
+
export declare const MAX_QUERY_OFFSET = 10000;
|
|
169
|
+
export declare const MAX_QUERY_DIMENSIONS = 50;
|
|
170
|
+
export declare const MAX_QUERY_MEASURES = 50;
|
|
171
|
+
export declare const MAX_QUERY_FILTERS = 100;
|
|
172
|
+
export declare const MAX_QUERY_ORDER_BY = 50;
|
|
173
|
+
export declare const DEFAULT_QUERY_TIMEOUT_MS = 30000;
|
|
174
|
+
export declare const MAX_QUERY_TIMEOUT_MS = 120000;
|
|
175
|
+
export declare const DEFAULT_RESPONSE_BYTES = 1048576;
|
|
176
|
+
export declare const MAX_RESPONSE_BYTES = 10485760;
|
|
181
177
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,SAAS,EACT,aAAa,EACd,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
CHANGED
|
@@ -6,3 +6,12 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export const MAX_QUERY_LIMIT = 10000;
|
|
8
8
|
export const DEFAULT_QUERY_LIMIT = 100;
|
|
9
|
+
export const MAX_QUERY_OFFSET = 10000;
|
|
10
|
+
export const MAX_QUERY_DIMENSIONS = 50;
|
|
11
|
+
export const MAX_QUERY_MEASURES = 50;
|
|
12
|
+
export const MAX_QUERY_FILTERS = 100;
|
|
13
|
+
export const MAX_QUERY_ORDER_BY = 50;
|
|
14
|
+
export const DEFAULT_QUERY_TIMEOUT_MS = 30_000;
|
|
15
|
+
export const MAX_QUERY_TIMEOUT_MS = 120_000;
|
|
16
|
+
export const DEFAULT_RESPONSE_BYTES = 1_048_576;
|
|
17
|
+
export const MAX_RESPONSE_BYTES = 10_485_760;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenant-config.d.ts","sourceRoot":"","sources":["../../src/utils/tenant-config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAiBxD,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAc7E"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
function isRecord(value) {
|
|
2
|
+
return !!value && typeof value === 'object';
|
|
3
|
+
}
|
|
4
|
+
function getTenantKey(dataset) {
|
|
5
|
+
if (!isRecord(dataset)) {
|
|
6
|
+
return undefined;
|
|
7
|
+
}
|
|
8
|
+
const config = dataset.config;
|
|
9
|
+
const configTenantKey = isRecord(config) ? config.tenantKey : undefined;
|
|
10
|
+
const tenantKey = dataset.tenantKey ?? configTenantKey;
|
|
11
|
+
return typeof tenantKey === 'string' && tenantKey.length > 0 ? tenantKey : undefined;
|
|
12
|
+
}
|
|
13
|
+
export function validateMCPServerTenantConfig(config) {
|
|
14
|
+
if (config.tenantId) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const tenantScopedDatasets = Object.entries(config.datasets ?? {})
|
|
18
|
+
.filter(([, dataset]) => getTenantKey(dataset))
|
|
19
|
+
.map(([name]) => name);
|
|
20
|
+
if (tenantScopedDatasets.length > 0) {
|
|
21
|
+
throw new Error(`MCP server tenantId is required for tenant-scoped datasets: ${tenantScopedDatasets.join(', ')}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,mBAAmB,UAAU,CAAC"}
|
package/dist/version.js
ADDED