@hypequery/mcp 0.5.5 → 0.7.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 (72) 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/discovery-executor.d.ts +78 -0
  7. package/dist/discovery-executor.d.ts.map +1 -0
  8. package/dist/discovery-executor.js +111 -0
  9. package/dist/errors.d.ts +29 -0
  10. package/dist/errors.d.ts.map +1 -0
  11. package/dist/errors.js +65 -0
  12. package/dist/executor.d.ts +50 -0
  13. package/dist/executor.d.ts.map +1 -0
  14. package/dist/executor.js +103 -0
  15. package/dist/index.d.ts +9 -3
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +8 -2
  18. package/dist/protocol-server.d.ts +26 -0
  19. package/dist/protocol-server.d.ts.map +1 -0
  20. package/dist/protocol-server.js +48 -0
  21. package/dist/server.d.ts +15 -44
  22. package/dist/server.d.ts.map +1 -1
  23. package/dist/server.js +21 -271
  24. package/dist/stdio.d.ts +8 -0
  25. package/dist/stdio.d.ts.map +1 -0
  26. package/dist/stdio.js +14 -0
  27. package/dist/tools/args.d.ts +16 -16
  28. package/dist/tools/args.d.ts.map +1 -1
  29. package/dist/tools/args.js +9 -8
  30. package/dist/tools/introspect.d.ts +8 -6
  31. package/dist/tools/introspect.d.ts.map +1 -1
  32. package/dist/tools/introspect.js +45 -168
  33. package/dist/tools/list-datasets.d.ts.map +1 -1
  34. package/dist/tools/list-datasets.js +2 -8
  35. package/dist/tools/query-dataset.d.ts.map +1 -1
  36. package/dist/tools/query-dataset.js +26 -30
  37. package/dist/tools/query-metric.d.ts.map +1 -1
  38. package/dist/tools/query-metric.js +27 -31
  39. package/dist/tools/tool-manifest.d.ts +28 -0
  40. package/dist/tools/tool-manifest.d.ts.map +1 -0
  41. package/dist/tools/tool-manifest.js +302 -0
  42. package/dist/tools/utils/canonical-query-schemas.d.ts +17 -0
  43. package/dist/tools/utils/canonical-query-schemas.d.ts.map +1 -0
  44. package/dist/tools/utils/canonical-query-schemas.js +131 -0
  45. package/dist/tools/utils/execution-budget.d.ts +11 -0
  46. package/dist/tools/utils/execution-budget.d.ts.map +1 -0
  47. package/dist/tools/utils/execution-budget.js +71 -0
  48. package/dist/tools/utils/legacy-agent-catalog.d.ts +19 -0
  49. package/dist/tools/utils/legacy-agent-catalog.d.ts.map +1 -0
  50. package/dist/tools/utils/legacy-agent-catalog.js +171 -0
  51. package/dist/tools/utils/query-limits.d.ts +25 -0
  52. package/dist/tools/utils/query-limits.d.ts.map +1 -0
  53. package/dist/tools/utils/query-limits.js +69 -0
  54. package/dist/tools/utils/query-result.d.ts +4 -0
  55. package/dist/tools/utils/query-result.d.ts.map +1 -0
  56. package/dist/tools/utils/query-result.js +19 -0
  57. package/dist/tools/utils/query-schema.d.ts +8 -0
  58. package/dist/tools/utils/query-schema.d.ts.map +1 -0
  59. package/dist/tools/utils/query-schema.js +36 -0
  60. package/dist/tools/utils/tool-response.d.ts +6 -0
  61. package/dist/tools/utils/tool-response.d.ts.map +1 -0
  62. package/dist/tools/utils/tool-response.js +33 -0
  63. package/dist/types.d.ts +54 -58
  64. package/dist/types.d.ts.map +1 -1
  65. package/dist/types.js +9 -0
  66. package/dist/utils/tenant-config.d.ts +3 -0
  67. package/dist/utils/tenant-config.d.ts.map +1 -0
  68. package/dist/utils/tenant-config.js +23 -0
  69. package/dist/version.d.ts +2 -0
  70. package/dist/version.d.ts.map +1 -0
  71. package/dist/version.js +5 -0
  72. package/package.json +7 -3
@@ -1,6 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { SEMANTIC_FILTER_OPERATORS } from '@hypequery/datasets';
3
- import { MAX_QUERY_LIMIT } from '../types.js';
3
+ import { MCPToolError } from '../errors.js';
4
+ import { MAX_QUERY_DIMENSIONS, MAX_QUERY_FILTERS, MAX_QUERY_LIMIT, MAX_QUERY_MEASURES, MAX_QUERY_OFFSET, MAX_QUERY_ORDER_BY, } from '../types.js';
4
5
  const filterSchema = z.object({
5
6
  field: z.string().min(1),
6
7
  operator: z.enum(SEMANTIC_FILTER_OPERATORS),
@@ -11,12 +12,12 @@ const orderBySchema = z.object({
11
12
  direction: z.enum(['asc', 'desc']),
12
13
  }).strict();
13
14
  const baseQuerySchema = z.object({
14
- dimensions: z.array(z.string().min(1)).optional(),
15
- filters: z.array(filterSchema).optional(),
15
+ dimensions: z.array(z.string().min(1)).max(MAX_QUERY_DIMENSIONS).optional(),
16
+ filters: z.array(filterSchema).max(MAX_QUERY_FILTERS).optional(),
16
17
  grain: z.enum(['day', 'week', 'month', 'quarter', 'year']).optional(),
17
- orderBy: z.array(orderBySchema).optional(),
18
- limit: z.number().int().nonnegative().max(MAX_QUERY_LIMIT).optional(),
19
- offset: z.number().int().nonnegative().optional(),
18
+ orderBy: z.array(orderBySchema).max(MAX_QUERY_ORDER_BY).optional(),
19
+ limit: z.number().int().positive().max(MAX_QUERY_LIMIT).optional(),
20
+ offset: z.number().int().nonnegative().max(MAX_QUERY_OFFSET).optional(),
20
21
  }).strict();
21
22
  export const queryMetricArgsSchema = baseQuerySchema.extend({
22
23
  dataset: z.string().min(1).optional(),
@@ -24,7 +25,7 @@ export const queryMetricArgsSchema = baseQuerySchema.extend({
24
25
  });
25
26
  export const queryDatasetArgsSchema = baseQuerySchema.extend({
26
27
  dataset: z.string().min(1).optional(),
27
- measures: z.array(z.string().min(1)).optional(),
28
+ measures: z.array(z.string().min(1)).max(MAX_QUERY_MEASURES).optional(),
28
29
  });
29
30
  function formatZodError(error) {
30
31
  return error.issues
@@ -37,7 +38,7 @@ function formatZodError(error) {
37
38
  export function parseToolArgs(schema, toolName, args) {
38
39
  const result = schema.safeParse(args);
39
40
  if (!result.success) {
40
- throw new Error(`Invalid ${toolName} arguments: ${formatZodError(result.error)}`);
41
+ throw new MCPToolError('MCP_INVALID_ARGUMENTS', `Invalid ${toolName} arguments: ${formatZodError(result.error)}`);
41
42
  }
42
43
  return result.data;
43
44
  }
@@ -1,9 +1,11 @@
1
+ /** Agent-safe dataset discovery and separately authorized trusted debugging. */
2
+ import { type TrustedDebugCatalogAuthorization } from '@hypequery/datasets';
3
+ import type { DatasetRegistry, MCPToolResponse, SchemaToolOptions } from '../types.js';
4
+ export declare function getDatasetSchemaTool(datasets: DatasetRegistry, args: unknown, _options?: SchemaToolOptions): Promise<MCPToolResponse>;
1
5
  /**
2
- * Get Dataset Schema Tool
3
- *
4
- * Returns the complete schema for a dataset including dimensions, measures,
5
- * named metrics, filters, and relationships.
6
+ * Physical catalog diagnostics for a caller that has already passed a
7
+ * separate authorization check. This projection is intentionally not
8
+ * registered as an agent-facing MCP tool.
6
9
  */
7
- import type { DatasetRegistry, MCPToolResponse, SchemaToolOptions } from '../types.js';
8
- export declare function getDatasetSchemaTool(datasets: DatasetRegistry, args: unknown, options?: SchemaToolOptions): Promise<MCPToolResponse>;
10
+ export declare function getTrustedDatasetSchema(datasets: DatasetRegistry, args: unknown, authorization: TrustedDebugCatalogAuthorization): Promise<unknown>;
9
11
  //# sourceMappingURL=introspect.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"introspect.d.ts","sourceRoot":"","sources":["../../src/tools/introspect.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EACV,eAAe,EAEf,eAAe,EAOf,iBAAiB,EAClB,MAAM,aAAa,CAAC;AAMrB,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,eAAe,EACzB,IAAI,EAAE,OAAO,EACb,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,eAAe,CAAC,CAuL1B"}
1
+ {"version":3,"file":"introspect.d.ts","sourceRoot":"","sources":["../../src/tools/introspect.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAEhF,OAAO,EAML,KAAK,gCAAgC,EACtC,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EACV,eAAe,EAEf,eAAe,EACf,iBAAiB,EAClB,MAAM,aAAa,CAAC;AAkBrB,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,eAAe,EACzB,IAAI,EAAE,OAAO,EACb,QAAQ,GAAE,iBAAsB,GAC/B,OAAO,CAAC,eAAe,CAAC,CAuB1B;AAED;;;;GAIG;AACH,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,eAAe,EACzB,IAAI,EAAE,OAAO,EACb,aAAa,EAAE,gCAAgC,GAC9C,OAAO,CAAC,OAAO,CAAC,CAclB"}
@@ -1,180 +1,57 @@
1
- /**
2
- * Get Dataset Schema Tool
3
- *
4
- * Returns the complete schema for a dataset including dimensions, measures,
5
- * named metrics, filters, and relationships.
6
- */
7
- import { getDatasetCatalog, listQueryableRelationshipFields } from '@hypequery/datasets';
1
+ /** Agent-safe dataset discovery and separately authorized trusted debugging. */
2
+ import { assertAgentSafeCatalogBudget, projectAgentSafeCatalog, projectTrustedDebugCatalog, } from '@hypequery/datasets';
3
+ import { MCPToolError } from '../errors.js';
4
+ import { projectLegacyAgentDataset } from './utils/legacy-agent-catalog.js';
5
+ import { createMCPToolResponse } from './utils/tool-response.js';
6
+ function datasetNameFromArgs(args) {
7
+ const datasetName = args && typeof args === 'object' && !Array.isArray(args)
8
+ ? args.dataset
9
+ : undefined;
10
+ if (typeof datasetName !== 'string' || datasetName.length === 0) {
11
+ throw new MCPToolError('MCP_INVALID_ARGUMENTS', 'dataset parameter is required');
12
+ }
13
+ return datasetName;
14
+ }
8
15
  function isDatasetInstance(value) {
9
16
  return !!value && typeof value === 'object' && value.__type === 'dataset';
10
17
  }
11
- export async function getDatasetSchemaTool(datasets, args, options = {}) {
12
- // Parse and validate args
13
- const validatedArgs = args;
14
- const datasetName = validatedArgs.dataset;
15
- if (!datasetName) {
16
- throw new Error('dataset parameter is required');
17
- }
18
+ export async function getDatasetSchemaTool(datasets, args, _options = {}) {
19
+ const datasetName = datasetNameFromArgs(args);
18
20
  const dataset = datasets[datasetName];
19
21
  if (!dataset) {
20
- throw new Error(`Dataset not found: ${datasetName}`);
22
+ throw new MCPToolError('MCP_NOT_FOUND', `Dataset not found: ${datasetName}`);
21
23
  }
22
- const datasetAny = dataset;
23
- const schema = {
24
- name: datasetName,
25
- description: datasetAny.description || datasetAny.config?.description || '',
26
- source: datasetAny.source || datasetAny.config?.source || '',
27
- timeKey: datasetAny.timeKey || datasetAny.config?.timeKey || null,
28
- tenantKey: datasetAny.tenantKey || datasetAny.config?.tenantKey || null,
29
- dimensions: {},
30
- measures: {},
31
- metrics: {},
32
- filters: {},
33
- relationships: {},
34
- limits: datasetAny.limits,
35
- };
36
- if (isDatasetInstance(dataset)) {
37
- const catalog = getDatasetCatalog(dataset);
38
- schema.source = catalog.source;
39
- schema.timeKey = catalog.timeKey ?? null;
40
- schema.tenantKey = catalog.tenantKey ?? null;
41
- schema.limits = catalog.limits;
42
- for (const [name, dimension] of Object.entries(catalog.dimensions)) {
43
- const dimSchema = {
44
- type: dimension.type,
45
- column: dimension.column ?? name,
46
- sql: options.includeSql ? dimension.sql ?? null : null,
47
- label: dimension.label || name,
48
- description: dimension.description || '',
49
- examples: [],
50
- filterable: dimension.filterable,
51
- groupable: dimension.groupable,
52
- };
53
- schema.dimensions[name] = dimSchema;
24
+ let schema;
25
+ try {
26
+ if (isDatasetInstance(dataset)) {
27
+ schema = projectAgentSafeCatalog({ [datasetName]: dataset }).datasets[0];
54
28
  }
55
- for (const [name, measure] of Object.entries(catalog.measures)) {
56
- const measureSchema = {
57
- aggregation: measure.aggregation,
58
- field: measure.field,
59
- sql: options.includeSql ? measure.sql ?? null : null,
60
- label: measure.label || name,
61
- description: measure.description || '',
62
- };
63
- schema.measures[name] = measureSchema;
64
- }
65
- for (const [name, filter] of Object.entries(catalog.filters)) {
66
- const filterSchema = {
67
- field: filter.field,
68
- label: filter.label || name,
69
- description: filter.description || '',
70
- operators: filter.operators ? [...filter.operators] : null,
71
- };
72
- schema.filters[name] = filterSchema;
73
- }
74
- for (const [name, metric] of Object.entries(catalog.metrics)) {
75
- const metSchema = {
76
- type: metric.kind,
77
- aggregation: metric.measures?.join(', ') || '',
78
- label: metric.label || name,
79
- description: metric.description || '',
80
- format: null,
81
- };
82
- schema.metrics[name] = metSchema;
83
- }
84
- for (const [name, relationship] of Object.entries(catalog.relationships)) {
85
- const relSchema = {
86
- type: relationship.kind,
87
- target: relationship.target,
88
- from: relationship.from,
89
- to: relationship.to,
90
- queryable: relationship.queryable,
91
- fields: relationship.fields,
92
- description: '',
93
- };
94
- schema.relationships[name] = relSchema;
29
+ else {
30
+ schema = projectLegacyAgentDataset(datasetName, dataset, datasets);
31
+ assertAgentSafeCatalogBudget({ datasets: [schema] });
95
32
  }
96
33
  }
97
- else {
98
- // Legacy object-shaped fixtures/configs.
99
- if (datasetAny.dimensions) {
100
- for (const [name, dimension] of Object.entries(datasetAny.dimensions)) {
101
- const dimSchema = {
102
- type: dimension.fieldType || dimension.type || 'unknown',
103
- column: dimension.column || name,
104
- sql: options.includeSql ? dimension.sql || null : null,
105
- label: dimension.label || name,
106
- description: dimension.description || '',
107
- examples: dimension.examples || [],
108
- filterable: dimension.filterable !== false,
109
- groupable: dimension.groupable !== false,
110
- };
111
- schema.dimensions[name] = dimSchema;
112
- }
113
- }
114
- if (datasetAny.measures) {
115
- for (const [name, measure] of Object.entries(datasetAny.measures)) {
116
- const measureSchema = {
117
- aggregation: measure.aggregation || measure.type || 'unknown',
118
- field: measure.field || name,
119
- sql: options.includeSql ? measure.sql || null : null,
120
- label: measure.label || name,
121
- description: measure.description || '',
122
- };
123
- schema.measures[name] = measureSchema;
124
- }
125
- }
126
- if (datasetAny.filters) {
127
- for (const [name, filter] of Object.entries(datasetAny.filters)) {
128
- const filterSchema = {
129
- field: filter.field || name,
130
- label: filter.label || name,
131
- description: filter.description || '',
132
- operators: filter.operators || null,
133
- };
134
- schema.filters[name] = filterSchema;
135
- }
136
- }
137
- if (datasetAny.relationships) {
138
- for (const [name, relationship] of Object.entries(datasetAny.relationships)) {
139
- const rel = relationship;
140
- // Raw config relationships don't carry the catalog's computed
141
- // queryable/fields metadata; derive it when the shape allows.
142
- const kind = rel.type || rel.kind;
143
- const hasResolvableTarget = typeof rel.target === 'function';
144
- const relSchema = {
145
- type: kind || 'unknown',
146
- target: hasResolvableTarget ? rel.target()?.name || '' : rel.target || rel.dataset?.name || '',
147
- from: rel.from,
148
- to: rel.to,
149
- queryable: rel.queryable ?? (kind ? kind !== 'hasMany' : undefined),
150
- fields: rel.fields ?? (kind && hasResolvableTarget
151
- ? listQueryableRelationshipFields(name, { ...rel, kind })
152
- : kind && kind !== 'hasMany' ? [] : undefined),
153
- description: rel.description || '',
154
- };
155
- schema.relationships[name] = relSchema;
156
- }
157
- }
34
+ catch (error) {
35
+ if (!(error instanceof RangeError && error.message.startsWith('Agent-safe catalog exceeds ')))
36
+ throw error;
37
+ throw new MCPToolError('MCP_RESULT_TOO_LARGE', `Dataset schema exceeds the agent-safe catalog byte limit: ${datasetName}`);
158
38
  }
159
- // Backward compatibility for legacy object-shaped dataset fixtures.
160
- if (!isDatasetInstance(dataset) && datasetAny.metrics) {
161
- for (const [name, metric] of Object.entries(datasetAny.metrics)) {
162
- const metSchema = {
163
- type: metric.spec?.__type || metric.type || 'unknown',
164
- aggregation: metric.spec?.aggregation || metric.aggregation || metric.type || '',
165
- label: metric.label || name,
166
- description: metric.description || '',
167
- format: metric.format || null,
168
- };
169
- schema.metrics[name] = metSchema;
170
- }
39
+ return createMCPToolResponse(schema);
40
+ }
41
+ /**
42
+ * Physical catalog diagnostics for a caller that has already passed a
43
+ * separate authorization check. This projection is intentionally not
44
+ * registered as an agent-facing MCP tool.
45
+ */
46
+ export async function getTrustedDatasetSchema(datasets, args, authorization) {
47
+ const datasetName = datasetNameFromArgs(args);
48
+ const dataset = datasets[datasetName];
49
+ if (!dataset) {
50
+ throw new MCPToolError('MCP_NOT_FOUND', `Dataset not found: ${datasetName}`);
51
+ }
52
+ if (!isDatasetInstance(dataset)) {
53
+ throw new MCPToolError('MCP_INVALID_ARGUMENTS', 'Trusted debugging requires a canonical dataset instance');
171
54
  }
172
- return {
173
- content: [
174
- {
175
- type: 'text',
176
- text: JSON.stringify(schema, null, 2),
177
- },
178
- ],
179
- };
55
+ const debug = projectTrustedDebugCatalog({ [datasetName]: dataset }, authorization);
56
+ return debug.kind === 'dataset-catalog' ? debug.datasets[datasetName] : debug;
180
57
  }
@@ -1 +1 @@
1
- {"version":3,"file":"list-datasets.d.ts","sourceRoot":"","sources":["../../src/tools/list-datasets.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAyC,MAAM,aAAa,CAAC;AAM3G,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CA2C1F"}
1
+ {"version":3,"file":"list-datasets.d.ts","sourceRoot":"","sources":["../../src/tools/list-datasets.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAyC,MAAM,aAAa,CAAC;AAO3G,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAoC1F"}
@@ -4,6 +4,7 @@
4
4
  * Returns a list of all available datasets with their descriptions.
5
5
  */
6
6
  import { getDatasetCatalog } from '@hypequery/datasets';
7
+ import { createMCPToolResponse } from './utils/tool-response.js';
7
8
  function isDatasetInstance(value) {
8
9
  return !!value && typeof value === 'object' && value.__type === 'dataset';
9
10
  }
@@ -37,12 +38,5 @@ export async function listDatasetsTool(datasets) {
37
38
  datasets: datasetList,
38
39
  total: datasetList.length,
39
40
  };
40
- return {
41
- content: [
42
- {
43
- type: 'text',
44
- text: JSON.stringify(response, null, 2),
45
- },
46
- ],
47
- };
41
+ return createMCPToolResponse(response);
48
42
  }
@@ -1 +1 @@
1
- {"version":3,"file":"query-dataset.d.ts","sourceRoot":"","sources":["../../src/tools/query-dataset.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAgB,MAAM,qBAAqB,CAAC;AACvE,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAuB,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAG3G,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,eAAe,EACzB,SAAS,EAAE,aAAa,EACxB,IAAI,EAAE,OAAO,EACb,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,eAAe,CAAC,CA8D1B"}
1
+ {"version":3,"file":"query-dataset.d.ts","sourceRoot":"","sources":["../../src/tools/query-dataset.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAgB,MAAM,qBAAqB,CAAC;AAEvE,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAYtF,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,eAAe,EACzB,SAAS,EAAE,aAAa,EACxB,IAAI,EAAE,OAAO,EACb,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,eAAe,CAAC,CA2D1B"}
@@ -3,57 +3,53 @@
3
3
  *
4
4
  * Executes an ad-hoc dataset query with custom dimensions and measures.
5
5
  */
6
- import { parseToolArgs, queryDatasetArgsSchema, toMetricFilters } from './args.js';
6
+ import { MCPToolError } from '../errors.js';
7
+ import { parseToolArgs, toMetricFilters } from './args.js';
8
+ import { buildMCPQuerySchemas } from './utils/canonical-query-schemas.js';
9
+ import { applyQueryLimits } from './utils/query-limits.js';
10
+ import { assertWithinBudget, executeWithinBudget, resolveExecutionBudget, } from './utils/execution-budget.js';
11
+ import { buildMCPQueryResult } from './utils/query-result.js';
12
+ import { createMCPToolResponse } from './utils/tool-response.js';
7
13
  export async function queryDatasetTool(datasets, analytics, args, options = {}) {
8
- const validatedArgs = parseToolArgs(queryDatasetArgsSchema, 'query_dataset', args);
9
- const { dataset: datasetName, dimensions, measures, filters, grain, orderBy, limit, offset } = validatedArgs;
14
+ const inputSchema = options.inputSchema ?? buildMCPQuerySchemas(datasets, options.limits).queryDataset;
15
+ const validatedArgs = parseToolArgs(inputSchema, 'query_dataset', args);
16
+ const { dataset: datasetName, dimensions, measures, filters, grain, orderBy } = validatedArgs;
10
17
  if (!datasetName) {
11
- throw new Error('dataset parameter is required');
18
+ throw new MCPToolError('MCP_INVALID_ARGUMENTS', 'dataset parameter is required');
12
19
  }
13
20
  const dataset = datasets[datasetName];
14
21
  if (!dataset) {
15
- throw new Error(`Dataset not found: ${datasetName}`);
22
+ throw new MCPToolError('MCP_NOT_FOUND', `Dataset not found: ${datasetName}`);
16
23
  }
17
24
  if (!dimensions?.length && !measures?.length) {
18
- throw new Error('At least one dimension or measure must be specified');
25
+ throw new MCPToolError('MCP_INVALID_ARGUMENTS', 'At least one dimension or measure must be specified');
19
26
  }
27
+ const pagination = applyQueryLimits(dataset, validatedArgs, options.limits);
28
+ const executionBudget = resolveExecutionBudget(options.executionBudget);
20
29
  // Build the query with proper types
21
30
  const query = {
22
31
  dimensions: dimensions || [],
23
32
  measures: measures || [],
24
33
  filters: toMetricFilters(filters),
25
34
  orderBy: orderBy || [],
35
+ limit: pagination.limit,
26
36
  };
27
37
  if (grain) {
28
38
  query.by = grain;
29
39
  }
30
- if (limit !== undefined) {
31
- query.limit = limit;
40
+ if (pagination.offset !== undefined) {
41
+ query.offset = pagination.offset;
32
42
  }
33
- if (offset !== undefined) {
34
- query.offset = offset;
35
- }
36
- const result = await analytics.execute(dataset, query, {
43
+ const result = await executeWithinBudget(signal => analytics.execute(dataset, query, {
37
44
  runtime: {
38
45
  tenant: options.tenantId ? { id: options.tenantId } : undefined,
39
46
  },
40
- });
47
+ abortSignal: signal,
48
+ cache: false,
49
+ }), executionBudget, options.signal);
41
50
  // Format the response with proper types
42
- const response = {
43
- data: result.data,
44
- meta: {
45
- ...(options.includeSql ? { sql: result.meta?.sql } : {}),
46
- timingMs: result.meta?.timingMs,
47
- rowCount: result.data.length,
48
- ...(result.meta?.pagination ? { pagination: result.meta.pagination } : {}),
49
- },
50
- };
51
- return {
52
- content: [
53
- {
54
- type: 'text',
55
- text: JSON.stringify(response, null, 2),
56
- },
57
- ],
58
- };
51
+ const response = buildMCPQueryResult(result, options.includeSql);
52
+ const toolResponse = createMCPToolResponse(response);
53
+ assertWithinBudget(toolResponse, executionBudget);
54
+ return toolResponse;
59
55
  }
@@ -1 +1 @@
1
- {"version":3,"file":"query-metric.d.ts","sourceRoot":"","sources":["../../src/tools/query-metric.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAe,MAAM,qBAAqB,CAAC;AACtE,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAuB,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAG3G,wBAAsB,eAAe,CACnC,QAAQ,EAAE,eAAe,EACzB,SAAS,EAAE,aAAa,EACxB,IAAI,EAAE,OAAO,EACb,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,eAAe,CAAC,CAqE1B"}
1
+ {"version":3,"file":"query-metric.d.ts","sourceRoot":"","sources":["../../src/tools/query-metric.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAe,MAAM,qBAAqB,CAAC;AAEtE,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAYtF,wBAAsB,eAAe,CACnC,QAAQ,EAAE,eAAe,EACzB,SAAS,EAAE,aAAa,EACxB,IAAI,EAAE,OAAO,EACb,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,eAAe,CAAC,CAkE1B"}
@@ -3,62 +3,58 @@
3
3
  *
4
4
  * Executes a metric query with optional dimensions, filters, grain, and sorting.
5
5
  */
6
- import { parseToolArgs, queryMetricArgsSchema, toMetricFilters } from './args.js';
6
+ import { MCPToolError } from '../errors.js';
7
+ import { parseToolArgs, toMetricFilters } from './args.js';
8
+ import { buildMCPQuerySchemas } from './utils/canonical-query-schemas.js';
9
+ import { applyQueryLimits } from './utils/query-limits.js';
10
+ import { assertWithinBudget, executeWithinBudget, resolveExecutionBudget, } from './utils/execution-budget.js';
11
+ import { buildMCPQueryResult } from './utils/query-result.js';
12
+ import { createMCPToolResponse } from './utils/tool-response.js';
7
13
  export async function queryMetricTool(datasets, analytics, args, options = {}) {
8
- const validatedArgs = parseToolArgs(queryMetricArgsSchema, 'query_metric', args);
9
- const { dataset: datasetName, metric: metricName, dimensions, filters, grain, orderBy, limit, offset } = validatedArgs;
14
+ const inputSchema = options.inputSchema ?? buildMCPQuerySchemas(datasets, options.limits).queryMetric;
15
+ const validatedArgs = parseToolArgs(inputSchema, 'query_metric', args);
16
+ const { dataset: datasetName, metric: metricName, dimensions, filters, grain, orderBy } = validatedArgs;
10
17
  if (!datasetName) {
11
- throw new Error('dataset parameter is required');
18
+ throw new MCPToolError('MCP_INVALID_ARGUMENTS', 'dataset parameter is required');
12
19
  }
13
20
  if (!metricName) {
14
- throw new Error('metric parameter is required');
21
+ throw new MCPToolError('MCP_INVALID_ARGUMENTS', 'metric parameter is required');
15
22
  }
16
23
  const dataset = datasets[datasetName];
17
24
  if (!dataset) {
18
- throw new Error(`Dataset not found: ${datasetName}`);
25
+ throw new MCPToolError('MCP_NOT_FOUND', `Dataset not found: ${datasetName}`);
19
26
  }
20
27
  // Get the metric from the dataset
21
28
  const metric = dataset[metricName] || dataset.metrics?.[metricName];
22
29
  if (!metric) {
23
- throw new Error(`Metric not found: ${metricName} in dataset ${datasetName}`);
30
+ throw new MCPToolError('MCP_NOT_FOUND', `Metric not found: ${metricName} in dataset ${datasetName}`);
24
31
  }
32
+ const pagination = applyQueryLimits(dataset, validatedArgs, options.limits);
33
+ const executionBudget = resolveExecutionBudget(options.executionBudget);
25
34
  // Build the query with proper types
26
35
  const query = {
27
36
  dimensions: dimensions || [],
28
37
  filters: toMetricFilters(filters),
29
38
  orderBy: orderBy || [],
39
+ limit: pagination.limit,
30
40
  };
31
41
  if (grain) {
32
42
  query.by = grain;
33
43
  }
34
- if (limit !== undefined) {
35
- query.limit = limit;
36
- }
37
- if (offset !== undefined) {
38
- query.offset = offset;
44
+ if (pagination.offset !== undefined) {
45
+ query.offset = pagination.offset;
39
46
  }
40
47
  // Execute the query
41
- const result = await analytics.execute(metric, query, {
48
+ const result = await executeWithinBudget(signal => analytics.execute(metric, query, {
42
49
  runtime: {
43
50
  tenant: options.tenantId ? { id: options.tenantId } : undefined,
44
51
  },
45
- });
52
+ abortSignal: signal,
53
+ cache: false,
54
+ }), executionBudget, options.signal);
46
55
  // Format the response with proper types
47
- const response = {
48
- data: result.data,
49
- meta: {
50
- ...(options.includeSql ? { sql: result.meta?.sql } : {}),
51
- timingMs: result.meta?.timingMs,
52
- rowCount: result.data.length,
53
- ...(result.meta?.pagination ? { pagination: result.meta.pagination } : {}),
54
- },
55
- };
56
- return {
57
- content: [
58
- {
59
- type: 'text',
60
- text: JSON.stringify(response, null, 2),
61
- },
62
- ],
63
- };
56
+ const response = buildMCPQueryResult(result, options.includeSql);
57
+ const toolResponse = createMCPToolResponse(response);
58
+ assertWithinBudget(toolResponse, executionBudget);
59
+ return toolResponse;
64
60
  }
@@ -0,0 +1,28 @@
1
+ import type { CanonicalSemanticQuerySchemas } from '@hypequery/datasets';
2
+ import type { ListToolsResult } from '@modelcontextprotocol/sdk/types.js';
3
+ export declare const DATASET_LIST_OUTPUT_SCHEMA: {
4
+ [x: string]: unknown;
5
+ type: "object";
6
+ properties?: {
7
+ [x: string]: object;
8
+ } | undefined;
9
+ required?: string[] | undefined;
10
+ } & Record<string, unknown>;
11
+ export declare const DATASET_SCHEMA_OUTPUT_SCHEMA: {
12
+ [x: string]: unknown;
13
+ type: "object";
14
+ properties?: {
15
+ [x: string]: object;
16
+ } | undefined;
17
+ required?: string[] | undefined;
18
+ } & Record<string, unknown>;
19
+ export declare const QUERY_OUTPUT_SCHEMA: {
20
+ [x: string]: unknown;
21
+ type: "object";
22
+ properties?: {
23
+ [x: string]: object;
24
+ } | undefined;
25
+ required?: string[] | undefined;
26
+ } & Record<string, unknown>;
27
+ export declare function buildMCPToolManifest(querySchemas: CanonicalSemanticQuerySchemas): ListToolsResult;
28
+ //# sourceMappingURL=tool-manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-manifest.d.ts","sourceRoot":"","sources":["../../src/tools/tool-manifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,qBAAqB,CAAC;AACzE,OAAO,KAAK,EAAE,eAAe,EAAQ,MAAM,oCAAoC,CAAC;AAkEhF,eAAO,MAAM,0BAA0B;;;;;;;2BAsBrC,CAAC;AAEH,eAAO,MAAM,4BAA4B;;;;;;;2BA0HvC,CAAC;AAEH,eAAO,MAAM,mBAAmB;;;;;;;2BAwC9B,CAAC;AAYH,wBAAgB,oBAAoB,CAClC,YAAY,EAAE,6BAA6B,GAC1C,eAAe,CA8CjB"}