metabase-agent-mcp-server 0.1.0 → 0.2.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/dist/index.js +77 -44
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +123 -84
package/dist/index.js
CHANGED
|
@@ -31,45 +31,47 @@ const json = (v) => JSON.stringify(v, null, 2);
|
|
|
31
31
|
// ---------------------------------------------------------------------------
|
|
32
32
|
// Tool: search
|
|
33
33
|
// ---------------------------------------------------------------------------
|
|
34
|
-
|
|
34
|
+
// Downstream: POST /api/agent/v1/search — body: { term_queries?, semantic_queries? }
|
|
35
|
+
server.tool("search", "Search for tables and metrics in Metabase. Supports term-based and semantic search queries. Results are ranked using Reciprocal Rank Fusion when both query types are provided. Provide at least one of term_queries or semantic_queries for meaningful results.", {
|
|
35
36
|
term_queries: z
|
|
36
37
|
.array(z.string().min(1))
|
|
37
38
|
.optional()
|
|
38
|
-
.describe("Term-based search queries"),
|
|
39
|
+
.describe("Term-based search queries (API: term_queries)"),
|
|
39
40
|
semantic_queries: z
|
|
40
41
|
.array(z.string().min(1))
|
|
41
42
|
.optional()
|
|
42
|
-
.describe("Semantic search queries"),
|
|
43
|
+
.describe("Semantic search queries (API: semantic_queries)"),
|
|
43
44
|
}, async ({ term_queries, semantic_queries }) => handleToolCall(() => client.search({ term_queries, semantic_queries }), json));
|
|
44
45
|
// ---------------------------------------------------------------------------
|
|
45
46
|
// Tool: get_table
|
|
46
47
|
// ---------------------------------------------------------------------------
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
// Downstream: GET /api/agent/v1/table/:id?with-fields=&with-field-values=...
|
|
49
|
+
server.tool("get_table", "Get details for a table including fields, related tables, metrics, measures, and segments. Required: id. Optional query params map to API with-fields, with-field-values, with-related-tables, with-metrics, with-measures, with-segments.", {
|
|
50
|
+
id: z.number().int().min(1).describe("Table ID (required)"),
|
|
49
51
|
with_fields: z
|
|
50
52
|
.boolean()
|
|
51
53
|
.optional()
|
|
52
|
-
.describe("Include table fields (default: true)"),
|
|
54
|
+
.describe("Include table fields (API: with-fields, default: true)"),
|
|
53
55
|
with_field_values: z
|
|
54
56
|
.boolean()
|
|
55
57
|
.optional()
|
|
56
|
-
.describe("Include sample field values (default: true)"),
|
|
58
|
+
.describe("Include sample field values (API: with-field-values, default: true)"),
|
|
57
59
|
with_related_tables: z
|
|
58
60
|
.boolean()
|
|
59
61
|
.optional()
|
|
60
|
-
.describe("Include related tables via FK (default: true)"),
|
|
62
|
+
.describe("Include related tables via FK (API: with-related-tables, default: true)"),
|
|
61
63
|
with_metrics: z
|
|
62
64
|
.boolean()
|
|
63
65
|
.optional()
|
|
64
|
-
.describe("Include metrics
|
|
66
|
+
.describe("Include metrics for this table (API: with-metrics, default: true)"),
|
|
65
67
|
with_measures: z
|
|
66
68
|
.boolean()
|
|
67
69
|
.optional()
|
|
68
|
-
.describe("Include reusable measure definitions (default: false)"),
|
|
70
|
+
.describe("Include reusable measure definitions (API: with-measures, default: false)"),
|
|
69
71
|
with_segments: z
|
|
70
72
|
.boolean()
|
|
71
73
|
.optional()
|
|
72
|
-
.describe("Include predefined filter segments (default: false)"),
|
|
74
|
+
.describe("Include predefined filter segments (API: with-segments, default: false)"),
|
|
73
75
|
}, async (args) => handleToolCall(() => client.getTable(args.id, {
|
|
74
76
|
withFields: args.with_fields,
|
|
75
77
|
withFieldValues: args.with_field_values,
|
|
@@ -81,24 +83,25 @@ server.tool("get_table", "Get details for a table including fields, related tabl
|
|
|
81
83
|
// ---------------------------------------------------------------------------
|
|
82
84
|
// Tool: get_metric
|
|
83
85
|
// ---------------------------------------------------------------------------
|
|
84
|
-
|
|
85
|
-
|
|
86
|
+
// Downstream: GET /api/agent/v1/metric/:id?with-queryable-dimensions=...
|
|
87
|
+
server.tool("get_metric", "Get details for a metric including its queryable dimensions and segments. Required: id. Optional params map to API with-queryable-dimensions, with-field-values, with-default-temporal-breakout, with-segments.", {
|
|
88
|
+
id: z.number().int().min(1).describe("Metric ID (required)"),
|
|
86
89
|
with_queryable_dimensions: z
|
|
87
90
|
.boolean()
|
|
88
91
|
.optional()
|
|
89
|
-
.describe("Include queryable dimension fields (default: true)"),
|
|
92
|
+
.describe("Include queryable dimension fields (API: with-queryable-dimensions, default: true)"),
|
|
90
93
|
with_field_values: z
|
|
91
94
|
.boolean()
|
|
92
95
|
.optional()
|
|
93
|
-
.describe("Include sample field values for dimensions (default: true)"),
|
|
96
|
+
.describe("Include sample field values for dimensions (API: with-field-values, default: true)"),
|
|
94
97
|
with_default_temporal_breakout: z
|
|
95
98
|
.boolean()
|
|
96
99
|
.optional()
|
|
97
|
-
.describe("Include default time dimension for temporal breakouts (default: true)"),
|
|
100
|
+
.describe("Include default time dimension for temporal breakouts (API: with-default-temporal-breakout, default: true)"),
|
|
98
101
|
with_segments: z
|
|
99
102
|
.boolean()
|
|
100
103
|
.optional()
|
|
101
|
-
.describe("Include predefined filter segments (default: false)"),
|
|
104
|
+
.describe("Include predefined filter segments (API: with-segments, default: false)"),
|
|
102
105
|
}, async (args) => handleToolCall(() => client.getMetric(args.id, {
|
|
103
106
|
withQueryableDimensions: args.with_queryable_dimensions,
|
|
104
107
|
withFieldValues: args.with_field_values,
|
|
@@ -108,28 +111,31 @@ server.tool("get_metric", "Get details for a metric including its queryable dime
|
|
|
108
111
|
// ---------------------------------------------------------------------------
|
|
109
112
|
// Tool: get_field_values
|
|
110
113
|
// ---------------------------------------------------------------------------
|
|
111
|
-
|
|
114
|
+
// Downstream: GET /api/agent/v1/:entity_type/:entity_id/field/:field_id/values
|
|
115
|
+
server.tool("get_field_values", "Get statistics and sample values for a specific field. Useful for understanding data distribution, valid filter values, and field characteristics. All three params are required and map to the Agent API path.", {
|
|
112
116
|
entity_type: z
|
|
113
117
|
.enum(["table", "metric"])
|
|
114
|
-
.describe("
|
|
115
|
-
entity_id: z.number().int().min(1).describe("Table or metric ID"),
|
|
118
|
+
.describe("Entity type (API path segment: table or metric)"),
|
|
119
|
+
entity_id: z.number().int().min(1).describe("Table or metric ID (API path segment)"),
|
|
116
120
|
field_id: z
|
|
117
121
|
.string()
|
|
118
122
|
.min(1)
|
|
119
|
-
.describe("Field ID (format
|
|
123
|
+
.describe("Field ID (API path segment; format 't<id>-<index>' for table fields, 'c<id>-<index>' for metric fields)"),
|
|
120
124
|
}, async ({ entity_type, entity_id, field_id }) => handleToolCall(() => client.getFieldValues(entity_type, entity_id, field_id), json));
|
|
121
125
|
// ---------------------------------------------------------------------------
|
|
122
126
|
// Shared schemas for query construction
|
|
123
127
|
// ---------------------------------------------------------------------------
|
|
124
128
|
const FilterSchema = z.union([
|
|
125
|
-
z.object({ segment_id: z.number().int() }).describe("Segment filter"),
|
|
129
|
+
z.object({ segment_id: z.number().int() }).describe("Segment filter (API: segment_id)"),
|
|
126
130
|
z
|
|
127
131
|
.object({
|
|
128
|
-
field_id: z.string(),
|
|
129
|
-
operation: z
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
132
|
+
field_id: z.string().describe("Field ID (from table/metric schema)"),
|
|
133
|
+
operation: z
|
|
134
|
+
.string()
|
|
135
|
+
.describe("Filter operation (API: operation; e.g. =, !=, >, <, >=, <=, contains)"),
|
|
136
|
+
value: z.any().optional().describe("Single value for the filter (API: value)"),
|
|
137
|
+
values: z.array(z.any()).optional().describe("Multiple values e.g. for 'in' (API: values)"),
|
|
138
|
+
bucket: z.string().optional().describe("Optional bucketing (API: bucket)"),
|
|
133
139
|
})
|
|
134
140
|
.describe("Field filter"),
|
|
135
141
|
]);
|
|
@@ -175,54 +181,81 @@ const AggregationSchema = z.union([
|
|
|
175
181
|
// ---------------------------------------------------------------------------
|
|
176
182
|
// Tool: run_query
|
|
177
183
|
// ---------------------------------------------------------------------------
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
For metrics: supports filters, group_by (aggregation is defined by the metric).
|
|
182
|
-
|
|
183
|
-
Provide EITHER table_id OR metric_id, not both.
|
|
184
|
-
Row limits: 2000 for simple queries, 10000 for aggregated queries.`, {
|
|
184
|
+
// Downstream: POST /api/agent/v1/construct-query (body = request) then POST /api/agent/v1/execute (body = { query })
|
|
185
|
+
// API requires exactly one of table_id or metric_id. MCP server.tool() accepts only a raw Zod shape (ZodRawShapeCompat), not ZodObject, so we validate in the handler.
|
|
186
|
+
const runQueryParamsShape = {
|
|
185
187
|
table_id: z
|
|
186
188
|
.number()
|
|
187
189
|
.int()
|
|
188
190
|
.min(1)
|
|
189
191
|
.optional()
|
|
190
|
-
.describe("Table ID (
|
|
192
|
+
.describe("Table ID for table-based query (API: table_id; required if metric_id omitted)"),
|
|
191
193
|
metric_id: z
|
|
192
194
|
.number()
|
|
193
195
|
.int()
|
|
194
196
|
.min(1)
|
|
195
197
|
.optional()
|
|
196
|
-
.describe("Metric ID (
|
|
198
|
+
.describe("Metric ID for metric-based query (API: metric_id; required if table_id omitted)"),
|
|
197
199
|
filters: z
|
|
198
200
|
.array(FilterSchema)
|
|
199
201
|
.optional()
|
|
200
|
-
.describe("Filter conditions
|
|
202
|
+
.describe("Filter conditions (API: filters)"),
|
|
201
203
|
fields: z
|
|
202
204
|
.array(FieldRefSchema)
|
|
203
205
|
.optional()
|
|
204
|
-
.describe("
|
|
206
|
+
.describe("Fields to select (API: fields; table queries only; omit for all fields)"),
|
|
205
207
|
aggregations: z
|
|
206
208
|
.array(AggregationSchema)
|
|
207
209
|
.optional()
|
|
208
|
-
.describe("
|
|
210
|
+
.describe("Aggregations (API: aggregations; table queries only: count, avg, sum, min, max, count-distinct, measure_id)"),
|
|
209
211
|
group_by: z
|
|
210
212
|
.array(GroupBySchema)
|
|
211
213
|
.optional()
|
|
212
|
-
.describe("
|
|
214
|
+
.describe("Group by fields with optional field_granularity (API: group_by)"),
|
|
213
215
|
order_by: z
|
|
214
216
|
.array(OrderBySchema)
|
|
215
217
|
.optional()
|
|
216
|
-
.describe("Order by
|
|
218
|
+
.describe("Order by (API: order_by; table only; for aggregation order use sort_order on aggregation)"),
|
|
217
219
|
limit: z
|
|
218
220
|
.number()
|
|
219
221
|
.int()
|
|
220
222
|
.min(1)
|
|
221
223
|
.optional()
|
|
222
|
-
.describe("
|
|
223
|
-
}
|
|
224
|
+
.describe("Max rows (API: limit; table queries only; 2000 simple, 10000 aggregated)"),
|
|
225
|
+
};
|
|
226
|
+
const RunQueryParamsSchema = z.object(runQueryParamsShape);
|
|
227
|
+
const RUN_QUERY_ONE_ID_ERROR = "Exactly one of table_id or metric_id is required (Metabase Agent API construct-query body).";
|
|
228
|
+
// Build API request with only the keys allowed for table vs metric (API rejects table-only keys on metric requests).
|
|
229
|
+
function buildConstructQueryRequest(args) {
|
|
230
|
+
if (args.table_id != null) {
|
|
231
|
+
return {
|
|
232
|
+
table_id: args.table_id,
|
|
233
|
+
filters: args.filters,
|
|
234
|
+
fields: args.fields,
|
|
235
|
+
aggregations: args.aggregations,
|
|
236
|
+
group_by: args.group_by,
|
|
237
|
+
order_by: args.order_by,
|
|
238
|
+
limit: args.limit,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
metric_id: args.metric_id,
|
|
243
|
+
filters: args.filters,
|
|
244
|
+
group_by: args.group_by,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
server.tool("run_query", `Construct and execute a query against Metabase (Agent API: POST construct-query then POST execute). Table queries: filters, fields, aggregations, group_by, order_by, limit. Metric queries: filters, group_by only. Exactly one of table_id or metric_id is required. Row limits: 2000 simple, 10000 aggregated.`, runQueryParamsShape, async (args) => {
|
|
248
|
+
const hasTable = args.table_id != null;
|
|
249
|
+
const hasMetric = args.metric_id != null;
|
|
250
|
+
if (hasTable === hasMetric) {
|
|
251
|
+
return {
|
|
252
|
+
content: [{ type: "text", text: RUN_QUERY_ONE_ID_ERROR }],
|
|
253
|
+
isError: true,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
224
256
|
try {
|
|
225
|
-
const
|
|
257
|
+
const request = buildConstructQueryRequest(args);
|
|
258
|
+
const constructed = await client.constructQuery(request);
|
|
226
259
|
const result = await client.executeQuery({ query: constructed.query });
|
|
227
260
|
return {
|
|
228
261
|
content: [{ type: "text", text: json(result) }],
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;AAC9C,MAAM,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC;AAEtD,IAAI,CAAC,YAAY,IAAI,CAAC,gBAAgB,EAAE,CAAC;IACvC,OAAO,CAAC,KAAK,CACX,gEAAgE,CACjE,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,MAAM,GAAG,IAAI,cAAc,CAAC;IAChC,GAAG,EAAE,YAAY;IACjB,MAAM,EAAE,gBAAgB;CACzB,CAAC,CAAC;AAEH,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,IAAI,EAAE,gBAAgB;IACtB,OAAO,EAAE,OAAO;CACjB,CAAC,CAAC;AAWH,KAAK,UAAU,cAAc,CAC3B,EAAoB,EACpB,MAA6B;IAE7B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,EAAE,EAAE,CAAC;QAC1B,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC;IAC/D,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IACnE,CAAC;AACH,CAAC;AAED,MAAM,IAAI,GAAG,CAAC,CAAU,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AAExD,8EAA8E;AAC9E,eAAe;AACf,8EAA8E;AAC9E,MAAM,CAAC,IAAI,CACT,QAAQ,EACR,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "metabase-agent-mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "MCP server for the Metabase Agent API — discover tables, metrics, and execute queries against Metabase's semantic layer",
|
|
6
6
|
"main": "dist/index.js",
|
package/src/index.ts
CHANGED
|
@@ -52,18 +52,19 @@ const json = (v: unknown) => JSON.stringify(v, null, 2);
|
|
|
52
52
|
// ---------------------------------------------------------------------------
|
|
53
53
|
// Tool: search
|
|
54
54
|
// ---------------------------------------------------------------------------
|
|
55
|
+
// Downstream: POST /api/agent/v1/search — body: { term_queries?, semantic_queries? }
|
|
55
56
|
server.tool(
|
|
56
57
|
"search",
|
|
57
|
-
"Search for tables and metrics in Metabase. Supports term-based and semantic search queries. Results are ranked using Reciprocal Rank Fusion when both query types are provided.",
|
|
58
|
+
"Search for tables and metrics in Metabase. Supports term-based and semantic search queries. Results are ranked using Reciprocal Rank Fusion when both query types are provided. Provide at least one of term_queries or semantic_queries for meaningful results.",
|
|
58
59
|
{
|
|
59
60
|
term_queries: z
|
|
60
61
|
.array(z.string().min(1))
|
|
61
62
|
.optional()
|
|
62
|
-
.describe("Term-based search queries"),
|
|
63
|
+
.describe("Term-based search queries (API: term_queries)"),
|
|
63
64
|
semantic_queries: z
|
|
64
65
|
.array(z.string().min(1))
|
|
65
66
|
.optional()
|
|
66
|
-
.describe("Semantic search queries"),
|
|
67
|
+
.describe("Semantic search queries (API: semantic_queries)"),
|
|
67
68
|
},
|
|
68
69
|
async ({ term_queries, semantic_queries }) =>
|
|
69
70
|
handleToolCall(
|
|
@@ -75,35 +76,36 @@ server.tool(
|
|
|
75
76
|
// ---------------------------------------------------------------------------
|
|
76
77
|
// Tool: get_table
|
|
77
78
|
// ---------------------------------------------------------------------------
|
|
79
|
+
// Downstream: GET /api/agent/v1/table/:id?with-fields=&with-field-values=...
|
|
78
80
|
server.tool(
|
|
79
81
|
"get_table",
|
|
80
|
-
"Get details for a table including fields, related tables, metrics, measures, and segments.",
|
|
82
|
+
"Get details for a table including fields, related tables, metrics, measures, and segments. Required: id. Optional query params map to API with-fields, with-field-values, with-related-tables, with-metrics, with-measures, with-segments.",
|
|
81
83
|
{
|
|
82
|
-
id: z.number().int().min(1).describe("Table ID"),
|
|
84
|
+
id: z.number().int().min(1).describe("Table ID (required)"),
|
|
83
85
|
with_fields: z
|
|
84
86
|
.boolean()
|
|
85
87
|
.optional()
|
|
86
|
-
.describe("Include table fields (default: true)"),
|
|
88
|
+
.describe("Include table fields (API: with-fields, default: true)"),
|
|
87
89
|
with_field_values: z
|
|
88
90
|
.boolean()
|
|
89
91
|
.optional()
|
|
90
|
-
.describe("Include sample field values (default: true)"),
|
|
92
|
+
.describe("Include sample field values (API: with-field-values, default: true)"),
|
|
91
93
|
with_related_tables: z
|
|
92
94
|
.boolean()
|
|
93
95
|
.optional()
|
|
94
|
-
.describe("Include related tables via FK (default: true)"),
|
|
96
|
+
.describe("Include related tables via FK (API: with-related-tables, default: true)"),
|
|
95
97
|
with_metrics: z
|
|
96
98
|
.boolean()
|
|
97
99
|
.optional()
|
|
98
|
-
.describe("Include metrics
|
|
100
|
+
.describe("Include metrics for this table (API: with-metrics, default: true)"),
|
|
99
101
|
with_measures: z
|
|
100
102
|
.boolean()
|
|
101
103
|
.optional()
|
|
102
|
-
.describe("Include reusable measure definitions (default: false)"),
|
|
104
|
+
.describe("Include reusable measure definitions (API: with-measures, default: false)"),
|
|
103
105
|
with_segments: z
|
|
104
106
|
.boolean()
|
|
105
107
|
.optional()
|
|
106
|
-
.describe("Include predefined filter segments (default: false)"),
|
|
108
|
+
.describe("Include predefined filter segments (API: with-segments, default: false)"),
|
|
107
109
|
},
|
|
108
110
|
async (args) =>
|
|
109
111
|
handleToolCall(
|
|
@@ -123,29 +125,30 @@ server.tool(
|
|
|
123
125
|
// ---------------------------------------------------------------------------
|
|
124
126
|
// Tool: get_metric
|
|
125
127
|
// ---------------------------------------------------------------------------
|
|
128
|
+
// Downstream: GET /api/agent/v1/metric/:id?with-queryable-dimensions=...
|
|
126
129
|
server.tool(
|
|
127
130
|
"get_metric",
|
|
128
|
-
"Get details for a metric including its queryable dimensions and segments.",
|
|
131
|
+
"Get details for a metric including its queryable dimensions and segments. Required: id. Optional params map to API with-queryable-dimensions, with-field-values, with-default-temporal-breakout, with-segments.",
|
|
129
132
|
{
|
|
130
|
-
id: z.number().int().min(1).describe("Metric ID"),
|
|
133
|
+
id: z.number().int().min(1).describe("Metric ID (required)"),
|
|
131
134
|
with_queryable_dimensions: z
|
|
132
135
|
.boolean()
|
|
133
136
|
.optional()
|
|
134
|
-
.describe("Include queryable dimension fields (default: true)"),
|
|
137
|
+
.describe("Include queryable dimension fields (API: with-queryable-dimensions, default: true)"),
|
|
135
138
|
with_field_values: z
|
|
136
139
|
.boolean()
|
|
137
140
|
.optional()
|
|
138
|
-
.describe("Include sample field values for dimensions (default: true)"),
|
|
141
|
+
.describe("Include sample field values for dimensions (API: with-field-values, default: true)"),
|
|
139
142
|
with_default_temporal_breakout: z
|
|
140
143
|
.boolean()
|
|
141
144
|
.optional()
|
|
142
145
|
.describe(
|
|
143
|
-
"Include default time dimension for temporal breakouts (default: true)",
|
|
146
|
+
"Include default time dimension for temporal breakouts (API: with-default-temporal-breakout, default: true)",
|
|
144
147
|
),
|
|
145
148
|
with_segments: z
|
|
146
149
|
.boolean()
|
|
147
150
|
.optional()
|
|
148
|
-
.describe("Include predefined filter segments (default: false)"),
|
|
151
|
+
.describe("Include predefined filter segments (API: with-segments, default: false)"),
|
|
149
152
|
},
|
|
150
153
|
async (args) =>
|
|
151
154
|
handleToolCall(
|
|
@@ -163,19 +166,20 @@ server.tool(
|
|
|
163
166
|
// ---------------------------------------------------------------------------
|
|
164
167
|
// Tool: get_field_values
|
|
165
168
|
// ---------------------------------------------------------------------------
|
|
169
|
+
// Downstream: GET /api/agent/v1/:entity_type/:entity_id/field/:field_id/values
|
|
166
170
|
server.tool(
|
|
167
171
|
"get_field_values",
|
|
168
|
-
"Get statistics and sample values for a specific field. Useful for understanding data distribution, valid filter values, and field characteristics.",
|
|
172
|
+
"Get statistics and sample values for a specific field. Useful for understanding data distribution, valid filter values, and field characteristics. All three params are required and map to the Agent API path.",
|
|
169
173
|
{
|
|
170
174
|
entity_type: z
|
|
171
175
|
.enum(["table", "metric"])
|
|
172
|
-
.describe("
|
|
173
|
-
entity_id: z.number().int().min(1).describe("Table or metric ID"),
|
|
176
|
+
.describe("Entity type (API path segment: table or metric)"),
|
|
177
|
+
entity_id: z.number().int().min(1).describe("Table or metric ID (API path segment)"),
|
|
174
178
|
field_id: z
|
|
175
179
|
.string()
|
|
176
180
|
.min(1)
|
|
177
181
|
.describe(
|
|
178
|
-
"Field ID (format
|
|
182
|
+
"Field ID (API path segment; format 't<id>-<index>' for table fields, 'c<id>-<index>' for metric fields)",
|
|
179
183
|
),
|
|
180
184
|
},
|
|
181
185
|
async ({ entity_type, entity_id, field_id }) =>
|
|
@@ -190,14 +194,16 @@ server.tool(
|
|
|
190
194
|
// ---------------------------------------------------------------------------
|
|
191
195
|
|
|
192
196
|
const FilterSchema = z.union([
|
|
193
|
-
z.object({ segment_id: z.number().int() }).describe("Segment filter"),
|
|
197
|
+
z.object({ segment_id: z.number().int() }).describe("Segment filter (API: segment_id)"),
|
|
194
198
|
z
|
|
195
199
|
.object({
|
|
196
|
-
field_id: z.string(),
|
|
197
|
-
operation: z
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
200
|
+
field_id: z.string().describe("Field ID (from table/metric schema)"),
|
|
201
|
+
operation: z
|
|
202
|
+
.string()
|
|
203
|
+
.describe("Filter operation (API: operation; e.g. =, !=, >, <, >=, <=, contains)"),
|
|
204
|
+
value: z.any().optional().describe("Single value for the filter (API: value)"),
|
|
205
|
+
values: z.array(z.any()).optional().describe("Multiple values e.g. for 'in' (API: values)"),
|
|
206
|
+
bucket: z.string().optional().describe("Optional bucketing (API: bucket)"),
|
|
201
207
|
})
|
|
202
208
|
.describe("Field filter"),
|
|
203
209
|
]);
|
|
@@ -248,66 +254,99 @@ const AggregationSchema = z.union([
|
|
|
248
254
|
// ---------------------------------------------------------------------------
|
|
249
255
|
// Tool: run_query
|
|
250
256
|
// ---------------------------------------------------------------------------
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
257
|
+
// Downstream: POST /api/agent/v1/construct-query (body = request) then POST /api/agent/v1/execute (body = { query })
|
|
258
|
+
// API requires exactly one of table_id or metric_id. MCP server.tool() accepts only a raw Zod shape (ZodRawShapeCompat), not ZodObject, so we validate in the handler.
|
|
259
|
+
const runQueryParamsShape = {
|
|
260
|
+
table_id: z
|
|
261
|
+
.number()
|
|
262
|
+
.int()
|
|
263
|
+
.min(1)
|
|
264
|
+
.optional()
|
|
265
|
+
.describe("Table ID for table-based query (API: table_id; required if metric_id omitted)"),
|
|
266
|
+
metric_id: z
|
|
267
|
+
.number()
|
|
268
|
+
.int()
|
|
269
|
+
.min(1)
|
|
270
|
+
.optional()
|
|
271
|
+
.describe("Metric ID for metric-based query (API: metric_id; required if table_id omitted)"),
|
|
272
|
+
filters: z
|
|
273
|
+
.array(FilterSchema)
|
|
274
|
+
.optional()
|
|
275
|
+
.describe("Filter conditions (API: filters)"),
|
|
276
|
+
fields: z
|
|
277
|
+
.array(FieldRefSchema)
|
|
278
|
+
.optional()
|
|
279
|
+
.describe(
|
|
280
|
+
"Fields to select (API: fields; table queries only; omit for all fields)",
|
|
281
|
+
),
|
|
282
|
+
aggregations: z
|
|
283
|
+
.array(AggregationSchema)
|
|
284
|
+
.optional()
|
|
285
|
+
.describe(
|
|
286
|
+
"Aggregations (API: aggregations; table queries only: count, avg, sum, min, max, count-distinct, measure_id)",
|
|
287
|
+
),
|
|
288
|
+
group_by: z
|
|
289
|
+
.array(GroupBySchema)
|
|
290
|
+
.optional()
|
|
291
|
+
.describe("Group by fields with optional field_granularity (API: group_by)"),
|
|
292
|
+
order_by: z
|
|
293
|
+
.array(OrderBySchema)
|
|
294
|
+
.optional()
|
|
295
|
+
.describe(
|
|
296
|
+
"Order by (API: order_by; table only; for aggregation order use sort_order on aggregation)",
|
|
297
|
+
),
|
|
298
|
+
limit: z
|
|
299
|
+
.number()
|
|
300
|
+
.int()
|
|
301
|
+
.min(1)
|
|
302
|
+
.optional()
|
|
303
|
+
.describe("Max rows (API: limit; table queries only; 2000 simple, 10000 aggregated)"),
|
|
304
|
+
};
|
|
254
305
|
|
|
255
|
-
|
|
256
|
-
|
|
306
|
+
const RunQueryParamsSchema = z.object(runQueryParamsShape);
|
|
307
|
+
type RunQueryArgs = z.infer<typeof RunQueryParamsSchema>;
|
|
257
308
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
.
|
|
269
|
-
.
|
|
270
|
-
.
|
|
271
|
-
.
|
|
272
|
-
.
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
.describe(
|
|
297
|
-
"Order by fields. To order by aggregation, use sort_order on the aggregation instead (table queries only)",
|
|
298
|
-
),
|
|
299
|
-
limit: z
|
|
300
|
-
.number()
|
|
301
|
-
.int()
|
|
302
|
-
.min(1)
|
|
303
|
-
.optional()
|
|
304
|
-
.describe("Maximum rows to return (table queries only)"),
|
|
305
|
-
},
|
|
306
|
-
async (args) => {
|
|
309
|
+
const RUN_QUERY_ONE_ID_ERROR =
|
|
310
|
+
"Exactly one of table_id or metric_id is required (Metabase Agent API construct-query body).";
|
|
311
|
+
|
|
312
|
+
// Build API request with only the keys allowed for table vs metric (API rejects table-only keys on metric requests).
|
|
313
|
+
function buildConstructQueryRequest(
|
|
314
|
+
args: RunQueryArgs,
|
|
315
|
+
): Parameters<typeof client.constructQuery>[0] {
|
|
316
|
+
if (args.table_id != null) {
|
|
317
|
+
return {
|
|
318
|
+
table_id: args.table_id,
|
|
319
|
+
filters: args.filters,
|
|
320
|
+
fields: args.fields,
|
|
321
|
+
aggregations: args.aggregations,
|
|
322
|
+
group_by: args.group_by,
|
|
323
|
+
order_by: args.order_by,
|
|
324
|
+
limit: args.limit,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
return {
|
|
328
|
+
metric_id: args.metric_id!,
|
|
329
|
+
filters: args.filters,
|
|
330
|
+
group_by: args.group_by,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
server.tool(
|
|
335
|
+
"run_query",
|
|
336
|
+
`Construct and execute a query against Metabase (Agent API: POST construct-query then POST execute). Table queries: filters, fields, aggregations, group_by, order_by, limit. Metric queries: filters, group_by only. Exactly one of table_id or metric_id is required. Row limits: 2000 simple, 10000 aggregated.`,
|
|
337
|
+
runQueryParamsShape,
|
|
338
|
+
async (args: RunQueryArgs) => {
|
|
339
|
+
const hasTable = args.table_id != null;
|
|
340
|
+
const hasMetric = args.metric_id != null;
|
|
341
|
+
if (hasTable === hasMetric) {
|
|
342
|
+
return {
|
|
343
|
+
content: [{ type: "text" as const, text: RUN_QUERY_ONE_ID_ERROR }],
|
|
344
|
+
isError: true,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
307
347
|
try {
|
|
308
|
-
const
|
|
309
|
-
|
|
310
|
-
);
|
|
348
|
+
const request = buildConstructQueryRequest(args);
|
|
349
|
+
const constructed = await client.constructQuery(request);
|
|
311
350
|
const result = await client.executeQuery({ query: constructed.query });
|
|
312
351
|
return {
|
|
313
352
|
content: [{ type: "text" as const, text: json(result) }],
|