@hypequery/mcp 0.0.0-canary-20260603161950

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.
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Dataset Guide Prompt
3
+ *
4
+ * Provides natural language guidance for querying datasets.
5
+ */
6
+ export function datasetGuidePrompt(datasets, datasetName) {
7
+ if (datasetName) {
8
+ const dataset = datasets[datasetName];
9
+ if (!dataset) {
10
+ throw new Error(`Dataset not found: ${datasetName}`);
11
+ }
12
+ const datasetAny = dataset;
13
+ // Generate dataset-specific guide
14
+ const dimensions = datasetAny.dimensions ? Object.keys(datasetAny.dimensions) : [];
15
+ const metrics = datasetAny.metrics ? Object.keys(datasetAny.metrics) : [];
16
+ const guide = `# Querying the ${datasetName} dataset
17
+
18
+ ## Available Dimensions
19
+ ${dimensions.map((d) => `- ${d}`).join('\n')}
20
+
21
+ ## Available Metrics
22
+ ${metrics.map((m) => `- ${m}`).join('\n')}
23
+
24
+ ## Example Queries
25
+
26
+ ### Simple metric query
27
+ \`\`\`
28
+ Query the "${metrics[0] || 'revenue'}" metric
29
+ \`\`\`
30
+
31
+ ### Grouped by dimension
32
+ \`\`\`
33
+ Show "${metrics[0] || 'revenue'}" by "${dimensions[0] || 'region'}"
34
+ \`\`\`
35
+
36
+ ### With filters
37
+ \`\`\`
38
+ Show "${metrics[0] || 'revenue'}" by "${dimensions[0] || 'region'}" where ${dimensions[1] || 'status'} = 'active'
39
+ \`\`\`
40
+
41
+ ### Time-series
42
+ \`\`\`
43
+ Show "${metrics[0] || 'revenue'}" by month for the last year
44
+ \`\`\`
45
+
46
+ ## Tips
47
+ - Use natural language to describe what you want to see
48
+ - The system will translate your query into the appropriate dataset query
49
+ - You can filter, group, and aggregate data using the available dimensions and metrics
50
+ `;
51
+ return {
52
+ messages: [
53
+ {
54
+ role: 'user',
55
+ content: {
56
+ type: 'text',
57
+ text: guide,
58
+ },
59
+ },
60
+ ],
61
+ };
62
+ }
63
+ // Generate general guide for all datasets
64
+ const datasetList = Object.keys(datasets);
65
+ const guide = `# Hypequery Semantic Layer Guide
66
+
67
+ ## Available Datasets
68
+ ${datasetList.map((name) => `- ${name}`).join('\n')}
69
+
70
+ ## How to Query
71
+
72
+ 1. **List datasets**: Use the \`list_datasets\` tool to see all available datasets
73
+ 2. **Get schema**: Use the \`get_dataset_schema\` tool to see dimensions and metrics for a dataset
74
+ 3. **Query metric**: Use the \`query_metric\` tool to execute a pre-defined metric
75
+ 4. **Query dataset**: Use the \`query_dataset\` tool for ad-hoc queries with custom dimensions and metrics
76
+
77
+ ## Example Workflow
78
+
79
+ 1. First, explore available datasets:
80
+ \`\`\`
81
+ list_datasets()
82
+ \`\`\`
83
+
84
+ 2. Get the schema for a specific dataset:
85
+ \`\`\`
86
+ get_dataset_schema({ dataset: "orders" })
87
+ \`\`\`
88
+
89
+ 3. Query a metric:
90
+ \`\`\`
91
+ query_metric({
92
+ dataset: "orders",
93
+ metric: "revenue",
94
+ dimensions: ["region"],
95
+ filters: [{ field: "status", operator: "eq", value: "completed" }],
96
+ grain: "month"
97
+ })
98
+ \`\`\`
99
+
100
+ ## Filter Operators
101
+ - \`eq\`: Equal to
102
+ - \`neq\`: Not equal to
103
+ - \`gt\`: Greater than
104
+ - \`gte\`: Greater than or equal to
105
+ - \`lt\`: Less than
106
+ - \`lte\`: Less than or equal to
107
+ - \`in\`: In list
108
+ - \`notIn\`: Not in list
109
+ - \`between\`: Between two values
110
+ - \`like\`: Pattern match
111
+
112
+ ## Time Grains
113
+ - \`day\`: Daily aggregation
114
+ - \`week\`: Weekly aggregation
115
+ - \`month\`: Monthly aggregation
116
+ - \`quarter\`: Quarterly aggregation
117
+ - \`year\`: Yearly aggregation
118
+ `;
119
+ return {
120
+ messages: [
121
+ {
122
+ role: 'user',
123
+ content: {
124
+ type: 'text',
125
+ text: guide,
126
+ },
127
+ },
128
+ ],
129
+ };
130
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * MCP Server for Hypequery Semantic Layer
3
+ *
4
+ * Exposes datasets and metrics via Model Context Protocol (MCP)
5
+ * for use with Claude Desktop, Cursor, and other MCP-compatible tools.
6
+ */
7
+ import type { DatasetClient } from '@hypequery/datasets';
8
+ import type { DatasetRegistry } from './types.js';
9
+ export interface MCPServerConfig {
10
+ /**
11
+ * Dataset registry - map of dataset names to instances
12
+ */
13
+ datasets: DatasetRegistry;
14
+ /**
15
+ * Semantic analytics for running metric and dataset queries
16
+ */
17
+ analytics: DatasetClient;
18
+ /**
19
+ * Server name (shown in MCP client)
20
+ */
21
+ name?: string;
22
+ /**
23
+ * Server version
24
+ */
25
+ version?: string;
26
+ }
27
+ export declare class HypequeryMCPServer {
28
+ private server;
29
+ private config;
30
+ constructor(config: MCPServerConfig);
31
+ private setupHandlers;
32
+ /**
33
+ * Start the MCP server with stdio transport
34
+ */
35
+ start(): Promise<void>;
36
+ /**
37
+ * Stop the MCP server
38
+ */
39
+ stop(): Promise<void>;
40
+ }
41
+ /**
42
+ * Create and start an MCP server
43
+ */
44
+ export declare function createMCPServer(config: MCPServerConfig): Promise<HypequeryMCPServer>;
45
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAUH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAMzD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,QAAQ,EAAE,eAAe,CAAC;IAE1B;;OAEG;IACH,SAAS,EAAE,aAAa,CAAC;IAEzB;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,MAAM,CAAkB;gBAEpB,MAAM,EAAE,eAAe;IAmBnC,OAAO,CAAC,aAAa;IA6NrB;;OAEG;IACG,KAAK;IAQX;;OAEG;IACG,IAAI;CAGX;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAI1F"}
package/dist/server.js ADDED
@@ -0,0 +1,257 @@
1
+ /**
2
+ * MCP Server for Hypequery Semantic Layer
3
+ *
4
+ * Exposes datasets and metrics via Model Context Protocol (MCP)
5
+ * for use with Claude Desktop, Cursor, and other MCP-compatible tools.
6
+ */
7
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
8
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
9
+ import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
10
+ import { listDatasetsTool } from './tools/list-datasets.js';
11
+ import { getDatasetSchemaTool } from './tools/introspect.js';
12
+ import { queryMetricTool } from './tools/query-metric.js';
13
+ import { queryDatasetTool } from './tools/query-dataset.js';
14
+ import { datasetGuidePrompt } from './prompts/dataset-guide.js';
15
+ export class HypequeryMCPServer {
16
+ server;
17
+ config;
18
+ constructor(config) {
19
+ this.config = config;
20
+ this.server = new Server({
21
+ name: config.name ?? 'hypequery-mcp-server',
22
+ version: config.version ?? '0.1.0',
23
+ }, {
24
+ capabilities: {
25
+ tools: {},
26
+ prompts: {},
27
+ },
28
+ });
29
+ this.setupHandlers();
30
+ }
31
+ setupHandlers() {
32
+ // List available tools
33
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
34
+ tools: [
35
+ {
36
+ name: 'list_datasets',
37
+ description: 'List all available datasets in the semantic layer',
38
+ inputSchema: {
39
+ type: 'object',
40
+ properties: {},
41
+ },
42
+ },
43
+ {
44
+ name: 'get_dataset_schema',
45
+ description: 'Get the schema (dimensions, metrics, relationships) for a specific dataset',
46
+ inputSchema: {
47
+ type: 'object',
48
+ properties: {
49
+ dataset: {
50
+ type: 'string',
51
+ description: 'Name of the dataset to introspect',
52
+ },
53
+ },
54
+ required: ['dataset'],
55
+ },
56
+ },
57
+ {
58
+ name: 'query_metric',
59
+ description: 'Execute a metric query with optional dimensions, filters, time grain, and sorting',
60
+ inputSchema: {
61
+ type: 'object',
62
+ properties: {
63
+ dataset: {
64
+ type: 'string',
65
+ description: 'Name of the dataset containing the metric',
66
+ },
67
+ metric: {
68
+ type: 'string',
69
+ description: 'Name of the metric to query',
70
+ },
71
+ dimensions: {
72
+ type: 'array',
73
+ items: { type: 'string' },
74
+ description: 'Dimensions to group by (optional)',
75
+ },
76
+ filters: {
77
+ type: 'array',
78
+ items: {
79
+ type: 'object',
80
+ properties: {
81
+ field: { type: 'string' },
82
+ operator: {
83
+ type: 'string',
84
+ enum: ['eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'in', 'notIn', 'between', 'like']
85
+ },
86
+ value: {},
87
+ },
88
+ required: ['field', 'operator', 'value'],
89
+ },
90
+ description: 'Filters to apply (optional)',
91
+ },
92
+ grain: {
93
+ type: 'string',
94
+ enum: ['day', 'week', 'month', 'quarter', 'year'],
95
+ description: 'Time grain for time-series queries (optional)',
96
+ },
97
+ orderBy: {
98
+ type: 'array',
99
+ items: {
100
+ type: 'object',
101
+ properties: {
102
+ field: { type: 'string' },
103
+ direction: { type: 'string', enum: ['asc', 'desc'] },
104
+ },
105
+ required: ['field', 'direction'],
106
+ },
107
+ description: 'Sort order (optional)',
108
+ },
109
+ limit: {
110
+ type: 'number',
111
+ description: 'Maximum number of rows to return (optional)',
112
+ },
113
+ },
114
+ required: ['dataset', 'metric'],
115
+ },
116
+ },
117
+ {
118
+ name: 'query_dataset',
119
+ description: 'Execute an ad-hoc dataset query with custom dimensions and metrics',
120
+ inputSchema: {
121
+ type: 'object',
122
+ properties: {
123
+ dataset: {
124
+ type: 'string',
125
+ description: 'Name of the dataset to query',
126
+ },
127
+ dimensions: {
128
+ type: 'array',
129
+ items: { type: 'string' },
130
+ description: 'Dimensions to select',
131
+ },
132
+ metrics: {
133
+ type: 'array',
134
+ items: { type: 'string' },
135
+ description: 'Metrics to calculate',
136
+ },
137
+ filters: {
138
+ type: 'array',
139
+ items: {
140
+ type: 'object',
141
+ properties: {
142
+ field: { type: 'string' },
143
+ operator: {
144
+ type: 'string',
145
+ enum: ['eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'in', 'notIn', 'between', 'like']
146
+ },
147
+ value: {},
148
+ },
149
+ required: ['field', 'operator', 'value'],
150
+ },
151
+ description: 'Filters to apply (optional)',
152
+ },
153
+ grain: {
154
+ type: 'string',
155
+ enum: ['day', 'week', 'month', 'quarter', 'year'],
156
+ description: 'Time grain for time-series queries (optional)',
157
+ },
158
+ orderBy: {
159
+ type: 'array',
160
+ items: {
161
+ type: 'object',
162
+ properties: {
163
+ field: { type: 'string' },
164
+ direction: { type: 'string', enum: ['asc', 'desc'] },
165
+ },
166
+ required: ['field', 'direction'],
167
+ },
168
+ description: 'Sort order (optional)',
169
+ },
170
+ limit: {
171
+ type: 'number',
172
+ description: 'Maximum number of rows to return (optional)',
173
+ },
174
+ },
175
+ required: ['dataset'],
176
+ },
177
+ },
178
+ ],
179
+ }));
180
+ // Handle tool calls
181
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
182
+ const { name, arguments: args } = request.params;
183
+ try {
184
+ switch (name) {
185
+ case 'list_datasets':
186
+ return await listDatasetsTool(this.config.datasets);
187
+ case 'get_dataset_schema':
188
+ return await getDatasetSchemaTool(this.config.datasets, args);
189
+ case 'query_metric':
190
+ return await queryMetricTool(this.config.datasets, this.config.analytics, args);
191
+ case 'query_dataset':
192
+ return await queryDatasetTool(this.config.datasets, this.config.analytics, args);
193
+ default:
194
+ throw new Error(`Unknown tool: ${name}`);
195
+ }
196
+ }
197
+ catch (error) {
198
+ return {
199
+ content: [
200
+ {
201
+ type: 'text',
202
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`,
203
+ },
204
+ ],
205
+ isError: true,
206
+ };
207
+ }
208
+ });
209
+ // List available prompts
210
+ this.server.setRequestHandler(ListPromptsRequestSchema, async () => ({
211
+ prompts: [
212
+ {
213
+ name: 'dataset_guide',
214
+ description: 'Guide for querying datasets with natural language',
215
+ arguments: [
216
+ {
217
+ name: 'dataset',
218
+ description: 'Name of the dataset to get guidance for',
219
+ required: false,
220
+ },
221
+ ],
222
+ },
223
+ ],
224
+ }));
225
+ // Get prompt content
226
+ this.server.setRequestHandler(GetPromptRequestSchema, async (request) => {
227
+ const { name, arguments: args } = request.params;
228
+ if (name === 'dataset_guide') {
229
+ return datasetGuidePrompt(this.config.datasets, args?.dataset);
230
+ }
231
+ throw new Error(`Unknown prompt: ${name}`);
232
+ });
233
+ }
234
+ /**
235
+ * Start the MCP server with stdio transport
236
+ */
237
+ async start() {
238
+ const transport = new StdioServerTransport();
239
+ await this.server.connect(transport);
240
+ // Log to stderr (stdout is used for MCP protocol)
241
+ console.error('Hypequery MCP Server started');
242
+ }
243
+ /**
244
+ * Stop the MCP server
245
+ */
246
+ async stop() {
247
+ await this.server.close();
248
+ }
249
+ }
250
+ /**
251
+ * Create and start an MCP server
252
+ */
253
+ export async function createMCPServer(config) {
254
+ const server = new HypequeryMCPServer(config);
255
+ await server.start();
256
+ return server;
257
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Get Dataset Schema Tool
3
+ *
4
+ * Returns the complete schema for a dataset including dimensions, metrics,
5
+ * and relationships.
6
+ */
7
+ import type { DatasetRegistry, MCPToolResponse } from '../types.js';
8
+ export declare function getDatasetSchemaTool(datasets: DatasetRegistry, args: unknown): Promise<MCPToolResponse>;
9
+ //# sourceMappingURL=introspect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"introspect.d.ts","sourceRoot":"","sources":["../../src/tools/introspect.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAwB,eAAe,EAAoE,MAAM,aAAa,CAAC;AAE5J,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,eAAe,EACzB,IAAI,EAAE,OAAO,GACZ,OAAO,CAAC,eAAe,CAAC,CA6E1B"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Get Dataset Schema Tool
3
+ *
4
+ * Returns the complete schema for a dataset including dimensions, metrics,
5
+ * and relationships.
6
+ */
7
+ export async function getDatasetSchemaTool(datasets, args) {
8
+ // Parse and validate args
9
+ const validatedArgs = args;
10
+ const datasetName = validatedArgs.dataset;
11
+ if (!datasetName) {
12
+ throw new Error('dataset parameter is required');
13
+ }
14
+ const dataset = datasets[datasetName];
15
+ if (!dataset) {
16
+ throw new Error(`Dataset not found: ${datasetName}`);
17
+ }
18
+ // Build schema response with proper types
19
+ const datasetAny = dataset;
20
+ const schema = {
21
+ name: datasetName,
22
+ description: datasetAny.description || datasetAny.config?.description || '',
23
+ source: datasetAny.source || datasetAny.config?.source || '',
24
+ timeKey: datasetAny.timeKey || datasetAny.config?.timeKey || null,
25
+ tenantKey: datasetAny.tenantKey || datasetAny.config?.tenantKey || null,
26
+ dimensions: {},
27
+ metrics: {},
28
+ relationships: {},
29
+ };
30
+ // Extract dimensions with proper typing
31
+ if (datasetAny.dimensions) {
32
+ for (const [name, dimension] of Object.entries(datasetAny.dimensions)) {
33
+ const dimSchema = {
34
+ type: dimension.fieldType || dimension.type || 'unknown',
35
+ column: dimension.column || name,
36
+ label: dimension.label || name,
37
+ description: dimension.description || '',
38
+ examples: dimension.examples || [],
39
+ };
40
+ schema.dimensions[name] = dimSchema;
41
+ }
42
+ }
43
+ // Extract metrics with proper typing
44
+ if (datasetAny.metrics) {
45
+ for (const [name, metric] of Object.entries(datasetAny.metrics)) {
46
+ const metSchema = {
47
+ type: metric.spec?.__type || metric.type || 'unknown',
48
+ aggregation: metric.spec?.aggregation || metric.aggregation || metric.type || '',
49
+ label: metric.label || name,
50
+ description: metric.description || '',
51
+ format: metric.format || null,
52
+ };
53
+ schema.metrics[name] = metSchema;
54
+ }
55
+ }
56
+ // Extract relationships with proper typing
57
+ if (datasetAny.relationships) {
58
+ for (const [name, relationship] of Object.entries(datasetAny.relationships)) {
59
+ const rel = relationship;
60
+ const relSchema = {
61
+ type: rel.type || rel.kind || 'unknown',
62
+ target: typeof rel.target === 'function' ? rel.target()?.name || '' : rel.target || rel.dataset?.name || '',
63
+ description: rel.description || '',
64
+ };
65
+ schema.relationships[name] = relSchema;
66
+ }
67
+ }
68
+ return {
69
+ content: [
70
+ {
71
+ type: 'text',
72
+ text: JSON.stringify(schema, null, 2),
73
+ },
74
+ ],
75
+ };
76
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * List Datasets Tool
3
+ *
4
+ * Returns a list of all available datasets with their descriptions.
5
+ */
6
+ import type { DatasetRegistry, MCPToolResponse } from '../types.js';
7
+ export declare function listDatasetsTool(datasets: DatasetRegistry): Promise<MCPToolResponse>;
8
+ //# sourceMappingURL=list-datasets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-datasets.d.ts","sourceRoot":"","sources":["../../src/tools/list-datasets.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAyC,MAAM,aAAa,CAAC;AAE3G,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CA6B1F"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * List Datasets Tool
3
+ *
4
+ * Returns a list of all available datasets with their descriptions.
5
+ */
6
+ export async function listDatasetsTool(datasets) {
7
+ const datasetList = Object.entries(datasets).map(([name, dataset]) => {
8
+ const datasetAny = dataset;
9
+ // Try to extract description from dataset instance
10
+ const description = datasetAny.description || datasetAny.config?.description || 'No description available';
11
+ const dimensionCount = datasetAny.dimensions ? Object.keys(datasetAny.dimensions).length : 0;
12
+ const metricCount = datasetAny.metrics ? Object.keys(datasetAny.metrics).length : 0;
13
+ return {
14
+ name,
15
+ description,
16
+ dimensionCount,
17
+ metricCount,
18
+ };
19
+ });
20
+ const response = {
21
+ datasets: datasetList,
22
+ total: datasetList.length,
23
+ };
24
+ return {
25
+ content: [
26
+ {
27
+ type: 'text',
28
+ text: JSON.stringify(response, null, 2),
29
+ },
30
+ ],
31
+ };
32
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Query Dataset Tool
3
+ *
4
+ * Executes an ad-hoc dataset query with custom dimensions and metrics.
5
+ */
6
+ import type { DatasetClient } from '@hypequery/datasets';
7
+ import type { DatasetRegistry, MCPToolResponse } from '../types.js';
8
+ export declare function queryDatasetTool(datasets: DatasetRegistry, analytics: DatasetClient, args: unknown): Promise<MCPToolResponse>;
9
+ //# sourceMappingURL=query-dataset.d.ts.map
@@ -0,0 +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,EAAoB,eAAe,EAAwC,MAAM,aAAa,CAAC;AAE5H,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,eAAe,EACzB,SAAS,EAAE,aAAa,EACxB,IAAI,EAAE,OAAO,GACZ,OAAO,CAAC,eAAe,CAAC,CA6D1B"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Query Dataset Tool
3
+ *
4
+ * Executes an ad-hoc dataset query with custom dimensions and metrics.
5
+ */
6
+ export async function queryDatasetTool(datasets, analytics, args) {
7
+ // Parse and validate args
8
+ const validatedArgs = args;
9
+ const { dataset: datasetName, dimensions, metrics, filters, grain, orderBy, limit } = validatedArgs;
10
+ if (!datasetName) {
11
+ throw new Error('dataset parameter is required');
12
+ }
13
+ const dataset = datasets[datasetName];
14
+ if (!dataset) {
15
+ throw new Error(`Dataset not found: ${datasetName}`);
16
+ }
17
+ if (!dimensions?.length && !metrics?.length) {
18
+ throw new Error('At least one dimension or metric must be specified');
19
+ }
20
+ // Build the query with proper types
21
+ const query = {
22
+ dimensions: dimensions || [],
23
+ measures: metrics || [],
24
+ filters: filters || [],
25
+ orderBy: orderBy || [],
26
+ };
27
+ if (grain) {
28
+ query.by = grain;
29
+ }
30
+ // Apply limit with maximum cap
31
+ const MAX_LIMIT = 10000;
32
+ if (limit !== undefined) {
33
+ query.limit = Math.min(limit, MAX_LIMIT);
34
+ }
35
+ const result = await analytics.execute(dataset, query, {
36
+ runtime: {
37
+ tenant: undefined,
38
+ },
39
+ });
40
+ // Format the response with proper types
41
+ const response = {
42
+ data: result.data,
43
+ meta: {
44
+ sql: result.meta?.sql,
45
+ timingMs: result.meta?.timingMs,
46
+ rowCount: result.data.length,
47
+ },
48
+ };
49
+ return {
50
+ content: [
51
+ {
52
+ type: 'text',
53
+ text: JSON.stringify(response, null, 2),
54
+ },
55
+ ],
56
+ };
57
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Query Metric Tool
3
+ *
4
+ * Executes a metric query with optional dimensions, filters, grain, and sorting.
5
+ */
6
+ import type { DatasetClient } from '@hypequery/datasets';
7
+ import type { DatasetRegistry, MCPToolResponse } from '../types.js';
8
+ export declare function queryMetricTool(datasets: DatasetRegistry, analytics: DatasetClient, args: unknown): Promise<MCPToolResponse>;
9
+ //# sourceMappingURL=query-metric.d.ts.map
@@ -0,0 +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,EAAmB,eAAe,EAAwC,MAAM,aAAa,CAAC;AAE3H,wBAAsB,eAAe,CACnC,QAAQ,EAAE,eAAe,EACzB,SAAS,EAAE,aAAa,EACxB,IAAI,EAAE,OAAO,GACZ,OAAO,CAAC,eAAe,CAAC,CAoE1B"}