@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.
Files changed (69) hide show
  1. package/README.md +83 -6
  2. package/dist/api.type-test.d.ts +2 -0
  3. package/dist/api.type-test.d.ts.map +1 -0
  4. package/dist/api.type-test.js +14 -0
  5. package/dist/bin.js +2 -1
  6. package/dist/errors.d.ts +29 -0
  7. package/dist/errors.d.ts.map +1 -0
  8. package/dist/errors.js +65 -0
  9. package/dist/executor.d.ts +50 -0
  10. package/dist/executor.d.ts.map +1 -0
  11. package/dist/executor.js +103 -0
  12. package/dist/index.d.ts +8 -3
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +7 -2
  15. package/dist/protocol-server.d.ts +26 -0
  16. package/dist/protocol-server.d.ts.map +1 -0
  17. package/dist/protocol-server.js +48 -0
  18. package/dist/server.d.ts +15 -44
  19. package/dist/server.d.ts.map +1 -1
  20. package/dist/server.js +21 -271
  21. package/dist/stdio.d.ts +8 -0
  22. package/dist/stdio.d.ts.map +1 -0
  23. package/dist/stdio.js +14 -0
  24. package/dist/tools/args.d.ts +16 -16
  25. package/dist/tools/args.d.ts.map +1 -1
  26. package/dist/tools/args.js +9 -8
  27. package/dist/tools/introspect.d.ts +8 -6
  28. package/dist/tools/introspect.d.ts.map +1 -1
  29. package/dist/tools/introspect.js +45 -168
  30. package/dist/tools/list-datasets.d.ts.map +1 -1
  31. package/dist/tools/list-datasets.js +2 -8
  32. package/dist/tools/query-dataset.d.ts.map +1 -1
  33. package/dist/tools/query-dataset.js +26 -30
  34. package/dist/tools/query-metric.d.ts.map +1 -1
  35. package/dist/tools/query-metric.js +27 -31
  36. package/dist/tools/tool-manifest.d.ts +28 -0
  37. package/dist/tools/tool-manifest.d.ts.map +1 -0
  38. package/dist/tools/tool-manifest.js +302 -0
  39. package/dist/tools/utils/canonical-query-schemas.d.ts +9 -0
  40. package/dist/tools/utils/canonical-query-schemas.d.ts.map +1 -0
  41. package/dist/tools/utils/canonical-query-schemas.js +108 -0
  42. package/dist/tools/utils/execution-budget.d.ts +11 -0
  43. package/dist/tools/utils/execution-budget.d.ts.map +1 -0
  44. package/dist/tools/utils/execution-budget.js +71 -0
  45. package/dist/tools/utils/legacy-agent-catalog.d.ts +19 -0
  46. package/dist/tools/utils/legacy-agent-catalog.d.ts.map +1 -0
  47. package/dist/tools/utils/legacy-agent-catalog.js +171 -0
  48. package/dist/tools/utils/query-limits.d.ts +25 -0
  49. package/dist/tools/utils/query-limits.d.ts.map +1 -0
  50. package/dist/tools/utils/query-limits.js +69 -0
  51. package/dist/tools/utils/query-result.d.ts +4 -0
  52. package/dist/tools/utils/query-result.d.ts.map +1 -0
  53. package/dist/tools/utils/query-result.js +19 -0
  54. package/dist/tools/utils/query-schema.d.ts +8 -0
  55. package/dist/tools/utils/query-schema.d.ts.map +1 -0
  56. package/dist/tools/utils/query-schema.js +36 -0
  57. package/dist/tools/utils/tool-response.d.ts +6 -0
  58. package/dist/tools/utils/tool-response.d.ts.map +1 -0
  59. package/dist/tools/utils/tool-response.js +33 -0
  60. package/dist/types.d.ts +54 -58
  61. package/dist/types.d.ts.map +1 -1
  62. package/dist/types.js +9 -0
  63. package/dist/utils/tenant-config.d.ts +3 -0
  64. package/dist/utils/tenant-config.d.ts.map +1 -0
  65. package/dist/utils/tenant-config.js +23 -0
  66. package/dist/version.d.ts +2 -0
  67. package/dist/version.d.ts.map +1 -0
  68. package/dist/version.js +5 -0
  69. package/package.json +7 -3
@@ -0,0 +1,302 @@
1
+ const semanticMetadataProperties = {
2
+ examples: { type: 'array', items: { type: 'string' } },
3
+ synonyms: { type: 'array', items: { type: 'string' } },
4
+ format: { type: 'string' },
5
+ unit: { type: 'string' },
6
+ currency: { type: 'string' },
7
+ timezone: { type: 'string' },
8
+ sensitivity: {
9
+ type: 'string',
10
+ enum: ['public', 'internal', 'confidential', 'restricted'],
11
+ },
12
+ };
13
+ const errorEnvelopeSchema = {
14
+ type: 'object',
15
+ additionalProperties: false,
16
+ properties: {
17
+ error: {
18
+ type: 'object',
19
+ additionalProperties: false,
20
+ properties: {
21
+ code: {
22
+ type: 'string',
23
+ enum: [
24
+ 'MCP_INVALID_ARGUMENTS',
25
+ 'MCP_NOT_FOUND',
26
+ 'MCP_UNKNOWN_TOOL',
27
+ 'MCP_UNAUTHORIZED',
28
+ 'MCP_STALE_CONTRACT',
29
+ 'MCP_REQUEST_CANCELLED',
30
+ 'MCP_QUERY_TIMEOUT',
31
+ 'MCP_RESULT_TOO_LARGE',
32
+ 'MCP_EXECUTION_FAILED',
33
+ ],
34
+ },
35
+ message: { type: 'string' },
36
+ category: {
37
+ type: 'string',
38
+ enum: [
39
+ 'correctable_input',
40
+ 'unauthorized',
41
+ 'stale_contract',
42
+ 'budget',
43
+ 'internal',
44
+ ],
45
+ },
46
+ retryable: { type: 'boolean' },
47
+ correctable: { type: 'boolean' },
48
+ },
49
+ required: ['code', 'category', 'message', 'retryable', 'correctable'],
50
+ },
51
+ },
52
+ required: ['error'],
53
+ };
54
+ function resultSchema(successSchema) {
55
+ return {
56
+ type: 'object',
57
+ oneOf: [successSchema, errorEnvelopeSchema],
58
+ };
59
+ }
60
+ export const DATASET_LIST_OUTPUT_SCHEMA = resultSchema({
61
+ type: 'object',
62
+ additionalProperties: false,
63
+ properties: {
64
+ datasets: {
65
+ type: 'array',
66
+ items: {
67
+ type: 'object',
68
+ additionalProperties: false,
69
+ properties: {
70
+ name: { type: 'string' },
71
+ description: { type: 'string' },
72
+ dimensionCount: { type: 'integer', minimum: 0 },
73
+ measureCount: { type: 'integer', minimum: 0 },
74
+ metricCount: { type: 'integer', minimum: 0 },
75
+ },
76
+ required: ['name', 'description', 'dimensionCount', 'metricCount'],
77
+ },
78
+ },
79
+ total: { type: 'integer', minimum: 0 },
80
+ },
81
+ required: ['datasets', 'total'],
82
+ });
83
+ export const DATASET_SCHEMA_OUTPUT_SCHEMA = resultSchema({
84
+ type: 'object',
85
+ additionalProperties: false,
86
+ properties: {
87
+ name: { type: 'string' },
88
+ description: { type: 'string' },
89
+ ...semanticMetadataProperties,
90
+ freshness: {
91
+ type: 'object',
92
+ additionalProperties: false,
93
+ properties: { maxAgeSeconds: { type: 'integer', minimum: 1 } },
94
+ required: ['maxAgeSeconds'],
95
+ },
96
+ owner: { type: 'string' },
97
+ defaults: {
98
+ type: 'object',
99
+ additionalProperties: false,
100
+ properties: {
101
+ dimensions: { type: 'array', items: { type: 'string' } },
102
+ timeGrain: { type: 'string', enum: ['day', 'week', 'month', 'quarter', 'year'] },
103
+ },
104
+ },
105
+ timeDimension: { type: ['string', 'null'] },
106
+ dimensions: {
107
+ type: 'array',
108
+ items: {
109
+ type: 'object',
110
+ additionalProperties: false,
111
+ properties: {
112
+ name: { type: 'string' },
113
+ type: { type: 'string', enum: ['boolean', 'number', 'string', 'timestamp'] },
114
+ label: { type: 'string' },
115
+ description: { type: 'string' },
116
+ ...semanticMetadataProperties,
117
+ filterable: { type: 'boolean' },
118
+ groupable: { type: 'boolean' },
119
+ },
120
+ required: ['name', 'type', 'filterable', 'groupable'],
121
+ },
122
+ },
123
+ measures: {
124
+ type: 'array',
125
+ items: {
126
+ type: 'object',
127
+ additionalProperties: false,
128
+ properties: {
129
+ name: { type: 'string' },
130
+ label: { type: 'string' },
131
+ description: { type: 'string' },
132
+ ...semanticMetadataProperties,
133
+ },
134
+ required: ['name'],
135
+ },
136
+ },
137
+ metrics: {
138
+ type: 'array',
139
+ items: {
140
+ type: 'object',
141
+ additionalProperties: false,
142
+ properties: {
143
+ name: { type: 'string' },
144
+ label: { type: 'string' },
145
+ description: { type: 'string' },
146
+ ...semanticMetadataProperties,
147
+ dimensions: { type: 'array', items: { type: 'string' } },
148
+ filters: { type: 'array', items: { type: 'string' } },
149
+ grains: { type: 'array', items: { type: 'string' } },
150
+ grain: { type: 'string' },
151
+ },
152
+ required: ['name', 'dimensions', 'filters', 'grains'],
153
+ },
154
+ },
155
+ filters: {
156
+ type: 'array',
157
+ items: {
158
+ type: 'object',
159
+ additionalProperties: false,
160
+ properties: {
161
+ name: { type: 'string' },
162
+ type: { type: 'string', enum: ['boolean', 'number', 'string', 'timestamp'] },
163
+ label: { type: 'string' },
164
+ description: { type: 'string' },
165
+ ...semanticMetadataProperties,
166
+ operators: { type: 'array', items: { type: 'string' } },
167
+ },
168
+ required: ['name', 'type', 'operators'],
169
+ },
170
+ },
171
+ relationships: {
172
+ type: 'array',
173
+ items: {
174
+ type: 'object',
175
+ additionalProperties: false,
176
+ properties: {
177
+ name: { type: 'string' },
178
+ target: { type: 'string' },
179
+ fields: { type: 'array', items: { type: 'string' } },
180
+ },
181
+ required: ['name', 'target', 'fields'],
182
+ },
183
+ },
184
+ limits: {
185
+ type: 'object',
186
+ additionalProperties: false,
187
+ properties: {
188
+ maxDimensions: { type: 'integer', minimum: 1 },
189
+ maxMeasures: { type: 'integer', minimum: 1 },
190
+ maxFilters: { type: 'integer', minimum: 1 },
191
+ maxResultSize: { type: 'integer', minimum: 1 },
192
+ },
193
+ },
194
+ },
195
+ required: [
196
+ 'name',
197
+ 'description',
198
+ 'timeDimension',
199
+ 'dimensions',
200
+ 'measures',
201
+ 'metrics',
202
+ 'filters',
203
+ 'relationships',
204
+ ],
205
+ });
206
+ export const QUERY_OUTPUT_SCHEMA = resultSchema({
207
+ type: 'object',
208
+ additionalProperties: false,
209
+ properties: {
210
+ data: {
211
+ type: 'array',
212
+ items: { type: 'object', additionalProperties: true },
213
+ },
214
+ meta: {
215
+ type: 'object',
216
+ additionalProperties: false,
217
+ properties: {
218
+ sql: { type: 'string' },
219
+ timingMs: { type: 'number', minimum: 0 },
220
+ rowCount: { type: 'integer', minimum: 0 },
221
+ pagination: {
222
+ type: 'object',
223
+ additionalProperties: false,
224
+ properties: {
225
+ limit: { type: 'integer', minimum: 1 },
226
+ offset: { type: 'integer', minimum: 0 },
227
+ hasMore: { type: 'boolean' },
228
+ },
229
+ required: ['limit', 'offset', 'hasMore'],
230
+ },
231
+ cache: {
232
+ type: 'object',
233
+ additionalProperties: false,
234
+ properties: {
235
+ status: { type: 'string', enum: ['hit', 'miss', 'bypass'] },
236
+ ageMs: { type: 'number', minimum: 0 },
237
+ stale: { type: 'boolean' },
238
+ },
239
+ required: ['status'],
240
+ },
241
+ },
242
+ required: ['rowCount', 'cache'],
243
+ },
244
+ },
245
+ required: ['data', 'meta'],
246
+ });
247
+ function annotations(title) {
248
+ return {
249
+ title,
250
+ readOnlyHint: true,
251
+ destructiveHint: false,
252
+ idempotentHint: true,
253
+ openWorldHint: false,
254
+ };
255
+ }
256
+ export function buildMCPToolManifest(querySchemas) {
257
+ return {
258
+ tools: [
259
+ {
260
+ name: 'list_datasets',
261
+ title: 'List datasets',
262
+ description: 'List all available datasets in the semantic layer',
263
+ inputSchema: { type: 'object', properties: {} },
264
+ outputSchema: DATASET_LIST_OUTPUT_SCHEMA,
265
+ annotations: annotations('List datasets'),
266
+ },
267
+ {
268
+ name: 'get_dataset_schema',
269
+ title: 'Inspect dataset schema',
270
+ description: 'Get the schema (dimensions, measures, named metrics, filters, relationships) for a specific dataset',
271
+ inputSchema: {
272
+ type: 'object',
273
+ properties: {
274
+ dataset: {
275
+ type: 'string',
276
+ description: 'Name of the dataset to introspect',
277
+ },
278
+ },
279
+ required: ['dataset'],
280
+ },
281
+ outputSchema: DATASET_SCHEMA_OUTPUT_SCHEMA,
282
+ annotations: annotations('Inspect dataset schema'),
283
+ },
284
+ {
285
+ name: 'query_metric',
286
+ title: 'Query named metric',
287
+ description: 'Execute a metric query with optional dimensions, filters, time grain, and sorting',
288
+ inputSchema: querySchemas.queryMetricJsonSchema,
289
+ outputSchema: QUERY_OUTPUT_SCHEMA,
290
+ annotations: annotations('Query named metric'),
291
+ },
292
+ {
293
+ name: 'query_dataset',
294
+ title: 'Query dataset',
295
+ description: 'Execute an ad-hoc dataset query with custom dimensions and measures',
296
+ inputSchema: querySchemas.queryDatasetJsonSchema,
297
+ outputSchema: QUERY_OUTPUT_SCHEMA,
298
+ annotations: annotations('Query dataset'),
299
+ },
300
+ ],
301
+ };
302
+ }
@@ -0,0 +1,9 @@
1
+ import { type CanonicalSemanticQuerySchemas } from '@hypequery/datasets';
2
+ import type { DatasetRegistry, MCPQueryLimits } from '../../types.js';
3
+ /**
4
+ * Compile exact schemas for real Dataset registries. The shipped loose registry
5
+ * type also accepts legacy metadata-only objects; retain a generic validator for
6
+ * those compatibility inputs until the publishing API replaces them.
7
+ */
8
+ export declare function buildMCPQuerySchemas(datasets: DatasetRegistry, configured?: MCPQueryLimits): CanonicalSemanticQuerySchemas;
9
+ //# sourceMappingURL=canonical-query-schemas.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonical-query-schemas.d.ts","sourceRoot":"","sources":["../../../src/tools/utils/canonical-query-schemas.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,6BAA6B,EAEnC,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AA0BtE;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,eAAe,EACzB,UAAU,CAAC,EAAE,cAAc,GAC1B,6BAA6B,CAqF/B"}
@@ -0,0 +1,108 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { buildCanonicalSemanticQuerySchemas, toSemanticJsonSchema, } from '@hypequery/datasets';
3
+ import { z } from 'zod';
4
+ import { queryDatasetArgsSchema, queryMetricArgsSchema } from '../args.js';
5
+ import { resolveQueryLimits } from './query-limits.js';
6
+ import { advertiseDatasetQueryLimits } from './query-schema.js';
7
+ function withLegacyDirectMetrics(datasets) {
8
+ return Object.fromEntries(Object.entries(datasets).map(([name, dataset]) => {
9
+ if (!dataset || typeof dataset !== 'object')
10
+ return [name, dataset];
11
+ const directMetrics = Object.fromEntries(Object.entries(dataset).filter(([, value]) => (value && typeof value === 'object' && 'contract' in value
12
+ && typeof value.contract === 'function')));
13
+ return [name, {
14
+ ...dataset,
15
+ metrics: {
16
+ ...('metrics' in dataset && dataset.metrics && typeof dataset.metrics === 'object'
17
+ ? dataset.metrics
18
+ : {}),
19
+ ...directMetrics,
20
+ },
21
+ }];
22
+ }));
23
+ }
24
+ /**
25
+ * Compile exact schemas for real Dataset registries. The shipped loose registry
26
+ * type also accepts legacy metadata-only objects; retain a generic validator for
27
+ * those compatibility inputs until the publishing API replaces them.
28
+ */
29
+ export function buildMCPQuerySchemas(datasets, configured) {
30
+ const limits = resolveQueryLimits(undefined, configured);
31
+ const entries = Object.entries(datasets).sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0));
32
+ if (entries.length === 0) {
33
+ const queryDatasetJsonSchema = toSemanticJsonSchema(queryDatasetArgsSchema);
34
+ const queryMetricJsonSchema = toSemanticJsonSchema(queryMetricArgsSchema);
35
+ const manifestHash = createHash('sha256').update(JSON.stringify({
36
+ query_dataset: queryDatasetJsonSchema,
37
+ query_metric: queryMetricJsonSchema,
38
+ })).digest('hex');
39
+ return Object.freeze({
40
+ queryDataset: queryDatasetArgsSchema,
41
+ queryMetric: queryMetricArgsSchema,
42
+ queryDatasetJsonSchema,
43
+ queryMetricJsonSchema,
44
+ manifestHash,
45
+ });
46
+ }
47
+ if (entries.every(([, dataset]) => isCanonicalSchemaSource(dataset))) {
48
+ return buildCanonicalSemanticQuerySchemas(withLegacyDirectMetrics(datasets), { grainField: 'grain', ...limits });
49
+ }
50
+ const datasetSchemas = [];
51
+ const metricSchemas = [];
52
+ const datasetJsonSchemas = [];
53
+ const metricJsonSchemas = [];
54
+ for (const [name, dataset] of entries) {
55
+ if (isCanonicalSchemaSource(dataset)) {
56
+ const exact = buildCanonicalSemanticQuerySchemas(withLegacyDirectMetrics({ [name]: dataset }), { grainField: 'grain', ...limits });
57
+ datasetSchemas.push(exact.queryDataset);
58
+ metricSchemas.push(exact.queryMetric);
59
+ datasetJsonSchemas.push(exact.queryDatasetJsonSchema);
60
+ metricJsonSchemas.push(exact.queryMetricJsonSchema);
61
+ }
62
+ else {
63
+ // Keep compatibility local to the legacy entry. A registry-wide fallback
64
+ // would erase exact contracts for every valid Dataset beside it.
65
+ const queryDataset = queryDatasetArgsSchema.extend({ dataset: z.literal(name) });
66
+ const queryMetric = queryMetricArgsSchema.extend({
67
+ dataset: z.literal(name),
68
+ metric: z.string().min(1),
69
+ });
70
+ datasetSchemas.push(queryDataset);
71
+ metricSchemas.push(queryMetric);
72
+ datasetJsonSchemas.push(advertiseDatasetQueryLimits(toSemanticJsonSchema(queryDataset), { [name]: dataset }, configured, true));
73
+ metricJsonSchemas.push(advertiseDatasetQueryLimits(toSemanticJsonSchema(queryMetric), { [name]: dataset }, configured, false));
74
+ }
75
+ }
76
+ const queryDataset = unionSchemas(datasetSchemas);
77
+ const queryMetric = unionSchemas(metricSchemas);
78
+ const queryDatasetJsonSchema = unionJsonSchemas(datasetJsonSchemas);
79
+ const queryMetricJsonSchema = unionJsonSchemas(metricJsonSchemas);
80
+ const manifestHash = createHash('sha256').update(JSON.stringify({
81
+ query_dataset: queryDatasetJsonSchema,
82
+ query_metric: queryMetricJsonSchema,
83
+ })).digest('hex');
84
+ return Object.freeze({
85
+ queryDataset,
86
+ queryMetric,
87
+ queryDatasetJsonSchema,
88
+ queryMetricJsonSchema,
89
+ manifestHash,
90
+ });
91
+ }
92
+ function isCanonicalSchemaSource(dataset) {
93
+ if (!dataset || typeof dataset !== 'object')
94
+ return false;
95
+ const source = dataset;
96
+ return source.__type === 'dataset'
97
+ || ('requiresTenant' in source && 'supportedGrains' in source && 'orderableFields' in source);
98
+ }
99
+ function unionSchemas(schemas) {
100
+ return schemas.length === 1
101
+ ? schemas[0]
102
+ : z.union(schemas);
103
+ }
104
+ function unionJsonSchemas(schemas) {
105
+ return schemas.length === 1
106
+ ? schemas[0]
107
+ : { type: 'object', anyOf: schemas };
108
+ }
@@ -0,0 +1,11 @@
1
+ import { type MCPExecutionBudget } from '../../types.js';
2
+ export declare const MIN_RESPONSE_BYTES: number;
3
+ export interface EffectiveExecutionBudget {
4
+ readonly timeoutMs: number;
5
+ readonly maxResponseBytes: number;
6
+ }
7
+ export declare function resolveExecutionBudget(configured?: MCPExecutionBudget): EffectiveExecutionBudget;
8
+ export declare function executeWithinBudget<T>(operation: (signal: AbortSignal) => Promise<T>, budget: EffectiveExecutionBudget, requestSignal?: AbortSignal): Promise<T>;
9
+ export declare function serializeWithinBudget(value: unknown, budget: EffectiveExecutionBudget): string;
10
+ export declare function assertWithinBudget(value: unknown, budget: EffectiveExecutionBudget): void;
11
+ //# sourceMappingURL=execution-budget.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"execution-budget.d.ts","sourceRoot":"","sources":["../../../src/tools/utils/execution-budget.ts"],"names":[],"mappings":"AACA,OAAO,EAKL,KAAK,kBAAkB,EACxB,MAAM,gBAAgB,CAAC;AAGxB,eAAO,MAAM,kBAAkB,QAG9B,CAAC;AAEF,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;CACnC;AAgBD,wBAAgB,sBAAsB,CACpC,UAAU,GAAE,kBAAuB,GAClC,wBAAwB,CAe1B;AAED,wBAAsB,mBAAmB,CAAC,CAAC,EACzC,SAAS,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,EAC9C,MAAM,EAAE,wBAAwB,EAChC,aAAa,CAAC,EAAE,WAAW,GAC1B,OAAO,CAAC,CAAC,CAAC,CA0CZ;AAED,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,wBAAwB,GAC/B,MAAM,CAIR;AAED,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,wBAAwB,GAC/B,IAAI,CAEN"}
@@ -0,0 +1,71 @@
1
+ import { MCPExecutionBudgetError } from '../../errors.js';
2
+ import { DEFAULT_QUERY_TIMEOUT_MS, DEFAULT_RESPONSE_BYTES, MAX_QUERY_TIMEOUT_MS, MAX_RESPONSE_BYTES, } from '../../types.js';
3
+ import { createMCPResultTooLargeResponse } from './tool-response.js';
4
+ export const MIN_RESPONSE_BYTES = Buffer.byteLength(JSON.stringify(createMCPResultTooLargeResponse()), 'utf8');
5
+ function positiveInteger(value, fallback, maximum, name) {
6
+ const resolved = value ?? fallback;
7
+ const minimum = name === 'maxResponseBytes' ? MIN_RESPONSE_BYTES : 1;
8
+ if (!Number.isSafeInteger(resolved) || resolved < minimum || resolved > maximum) {
9
+ throw new Error(`${name} must be an integer between ${minimum} and ${maximum}`);
10
+ }
11
+ return resolved;
12
+ }
13
+ export function resolveExecutionBudget(configured = {}) {
14
+ return Object.freeze({
15
+ timeoutMs: positiveInteger(configured.timeoutMs, DEFAULT_QUERY_TIMEOUT_MS, MAX_QUERY_TIMEOUT_MS, 'timeoutMs'),
16
+ maxResponseBytes: positiveInteger(configured.maxResponseBytes, DEFAULT_RESPONSE_BYTES, MAX_RESPONSE_BYTES, 'maxResponseBytes'),
17
+ });
18
+ }
19
+ export async function executeWithinBudget(operation, budget, requestSignal) {
20
+ const controller = new AbortController();
21
+ const cancellationError = new MCPExecutionBudgetError('MCP_REQUEST_CANCELLED', 'The MCP request was cancelled');
22
+ const timeoutError = new MCPExecutionBudgetError('MCP_QUERY_TIMEOUT', `The query exceeded its ${budget.timeoutMs}ms execution deadline`);
23
+ const cancel = () => controller.abort(cancellationError);
24
+ if (requestSignal?.aborted) {
25
+ throw cancellationError;
26
+ }
27
+ requestSignal?.addEventListener('abort', cancel, { once: true });
28
+ const timeout = setTimeout(() => controller.abort(timeoutError), budget.timeoutMs);
29
+ let removeBudgetAbortListener = () => { };
30
+ const budgetAbort = new Promise((_, reject) => {
31
+ const rejectForAbort = () => reject(controller.signal.reason);
32
+ if (controller.signal.aborted) {
33
+ rejectForAbort();
34
+ return;
35
+ }
36
+ controller.signal.addEventListener('abort', rejectForAbort, { once: true });
37
+ removeBudgetAbortListener = () => {
38
+ controller.signal.removeEventListener('abort', rejectForAbort);
39
+ };
40
+ });
41
+ try {
42
+ const result = await Promise.race([operation(controller.signal), budgetAbort]);
43
+ if (controller.signal.aborted)
44
+ throw controller.signal.reason;
45
+ return result;
46
+ }
47
+ catch (error) {
48
+ if (controller.signal.aborted)
49
+ throw controller.signal.reason;
50
+ throw error;
51
+ }
52
+ finally {
53
+ clearTimeout(timeout);
54
+ removeBudgetAbortListener();
55
+ requestSignal?.removeEventListener('abort', cancel);
56
+ }
57
+ }
58
+ export function serializeWithinBudget(value, budget) {
59
+ const serialized = JSON.stringify(value);
60
+ assertSerializedWithinBudget(serialized, budget);
61
+ return serialized;
62
+ }
63
+ export function assertWithinBudget(value, budget) {
64
+ assertSerializedWithinBudget(JSON.stringify(value), budget);
65
+ }
66
+ function assertSerializedWithinBudget(serialized, budget) {
67
+ const size = Buffer.byteLength(serialized, 'utf8');
68
+ if (size > budget.maxResponseBytes) {
69
+ throw new MCPExecutionBudgetError('MCP_RESULT_TOO_LARGE', `The serialized result is ${size} bytes; maximum is ${budget.maxResponseBytes} bytes`);
70
+ }
71
+ }
@@ -0,0 +1,19 @@
1
+ import type { AgentCatalogDataset } from '@hypequery/datasets';
2
+ type UnknownRecord = Record<string, unknown>;
3
+ /**
4
+ * Safely adapts the pre-catalog, object-shaped MCP registry compatibility input.
5
+ *
6
+ * A legacy entry is arbitrary user data, so every name it carries is checked
7
+ * against what this projection already publishes before it is emitted:
8
+ * references that do not resolve to a declared dimension, filter, grain, or
9
+ * sibling dataset are dropped rather than passed through. Otherwise a physical
10
+ * column or tenant key sitting in a legacy `metrics`/`relationships` block
11
+ * would reach the agent through `get_dataset_schema`.
12
+ *
13
+ * `registry` is the surrounding dataset map, used to resolve relationship
14
+ * targets. A relationship whose target is not in it is unreachable, so it is
15
+ * omitted instead of advertising a name the agent cannot query.
16
+ */
17
+ export declare function projectLegacyAgentDataset(name: string, input: UnknownRecord, registry?: Readonly<Record<string, unknown>>): AgentCatalogDataset;
18
+ export {};
19
+ //# sourceMappingURL=legacy-agent-catalog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"legacy-agent-catalog.d.ts","sourceRoot":"","sources":["../../../src/tools/utils/legacy-agent-catalog.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EAQpB,MAAM,qBAAqB,CAAC;AAE7B,KAAK,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AA4F7C;;;;;;;;;;;;;GAaG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,aAAa,EACpB,QAAQ,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAM,GAC/C,mBAAmB,CAuFrB"}