@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
package/dist/tools/introspect.js
CHANGED
|
@@ -1,180 +1,57 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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,
|
|
12
|
-
|
|
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
|
|
22
|
+
throw new MCPToolError('MCP_NOT_FOUND', `Dataset not found: ${datasetName}`);
|
|
21
23
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
173
|
-
|
|
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;
|
|
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;
|
|
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 {
|
|
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
|
|
9
|
-
const
|
|
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
|
|
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
|
|
22
|
+
throw new MCPToolError('MCP_NOT_FOUND', `Dataset not found: ${datasetName}`);
|
|
16
23
|
}
|
|
17
24
|
if (!dimensions?.length && !measures?.length) {
|
|
18
|
-
throw new
|
|
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 (
|
|
31
|
-
query.
|
|
40
|
+
if (pagination.offset !== undefined) {
|
|
41
|
+
query.offset = pagination.offset;
|
|
32
42
|
}
|
|
33
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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;
|
|
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 {
|
|
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
|
|
9
|
-
const
|
|
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
|
|
18
|
+
throw new MCPToolError('MCP_INVALID_ARGUMENTS', 'dataset parameter is required');
|
|
12
19
|
}
|
|
13
20
|
if (!metricName) {
|
|
14
|
-
throw new
|
|
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
|
|
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
|
|
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 (
|
|
35
|
-
query.
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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"}
|