Skip to content

Commit 6d9167b

Browse files
- json-centric MCP API.
1 parent 2a2ac31 commit 6d9167b

File tree

6 files changed

+88
-78
lines changed

6 files changed

+88
-78
lines changed

docs/mcp.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11

22
## Running the MCP server
33

4+
**Note**: before starting an MCP server, remember to export all appropriate auth env vars.
5+
6+
We have a nice debug config for running an MCP server with `vscode`, please see [the `vscode` debug launch config](/.vscode/launch.json) for that. Otherwise, you can run with stackql (assuming locally built into `./build/stackql`):
47

5-
```bash
68

9+
```bash
710

11+
./build/stackql mcp --mcp.server.type=http --mcp.config '{"server": {"transport": "http", "address": "127.0.0.1:9992"} }'
812

913

1014
```
@@ -25,6 +29,15 @@ Then, assuming you have a `stackql` MCP server serving streamable HTTP on port `
2529

2630
```bash
2731

32+
./build/stackql_mcp_client exec --client-type=http --url=http://127.0.0.1:9992 --exec.action list_providers
33+
2834
./build/stackql_mcp_client exec --client-type=http --url=http://127.0.0.1:9992 --exec.action list_services --exec.args '{"provider": "google"}'
2935

36+
./build/stackql_mcp_client exec --client-type=http --url=http://127.0.0.1:9992 --exec.action list_resources --exec.args '{"provider": "google", "service": "compute"}'
37+
38+
39+
./build/stackql_mcp_client exec --client-type=http --url=http://127.0.0.1:9992 --exec.action list_methods --exec.args '{"provider": "google", "service": "compute", "resource": "networks"}'
40+
41+
./build/stackql_mcp_client exec --client-type=http --url=http://127.0.0.1:9992 --exec.action query_json_v2 --exec.args '{"sql": "select name from google.compute.networks where project = '"'"'stackql-demo'"'"';"}'
42+
3043
```

internal/stackql/mcpbackend/mcp_reverse_proxy_backend_service.go

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -230,44 +230,44 @@ func (b *stackqlMCPReverseProxyService) PromptWriteSafeSelectTool(ctx context.Co
230230
// return "stub", nil
231231
// }
232232

233-
func (b *stackqlMCPReverseProxyService) DescribeTable(ctx context.Context, hI dto.HierarchyInput) (string, error) {
233+
func (b *stackqlMCPReverseProxyService) DescribeTable(ctx context.Context, hI dto.HierarchyInput) ([]map[string]interface{}, error) {
234234
q, qErr := b.interrogator.GetDescribeTable(hI)
235235
if qErr != nil {
236-
return "", qErr
236+
return nil, qErr
237237
}
238-
return b.renderQueryResults(q, hI.Format, hI.RowLimit)
238+
return b.query(ctx, q, hI.RowLimit)
239239
}
240240

241-
func (b *stackqlMCPReverseProxyService) GetForeignKeys(ctx context.Context, hI dto.HierarchyInput) (string, error) {
242-
return b.interrogator.GetForeignKeys(hI)
241+
func (b *stackqlMCPReverseProxyService) GetForeignKeys(ctx context.Context, hI dto.HierarchyInput) ([]map[string]interface{}, error) {
242+
return nil, fmt.Errorf("GetForeignKeys not implemented")
243243
}
244244

245245
func (b *stackqlMCPReverseProxyService) FindRelationships(ctx context.Context, hI dto.HierarchyInput) (string, error) {
246246
return b.interrogator.FindRelationships(hI)
247247
}
248248

249-
func (b *stackqlMCPReverseProxyService) ListProviders(ctx context.Context) (string, error) {
249+
func (b *stackqlMCPReverseProxyService) ListProviders(ctx context.Context) ([]map[string]interface{}, error) {
250250
q, qErr := b.interrogator.GetShowProviders(dto.HierarchyInput{}, "")
251251
if qErr != nil {
252-
return "", qErr
252+
return nil, qErr
253253
}
254-
return b.renderQueryResults(q, "", unlimitedRowLimit)
254+
return b.query(ctx, q, unlimitedRowLimit)
255255
}
256256

257-
func (b *stackqlMCPReverseProxyService) ListServices(ctx context.Context, hI dto.HierarchyInput) (string, error) {
257+
func (b *stackqlMCPReverseProxyService) ListServices(ctx context.Context, hI dto.HierarchyInput) ([]map[string]interface{}, error) {
258258
q, qErr := b.interrogator.GetShowServices(hI, "")
259259
if qErr != nil {
260-
return "", qErr
260+
return nil, qErr
261261
}
262-
return b.renderQueryResults(q, hI.Format, hI.RowLimit)
262+
return b.query(ctx, q, hI.RowLimit)
263263
}
264264

265-
func (b *stackqlMCPReverseProxyService) ListResources(ctx context.Context, hI dto.HierarchyInput) (string, error) {
265+
func (b *stackqlMCPReverseProxyService) ListResources(ctx context.Context, hI dto.HierarchyInput) ([]map[string]interface{}, error) {
266266
q, qErr := b.interrogator.GetShowResources(hI, "")
267267
if qErr != nil {
268-
return "", qErr
268+
return nil, qErr
269269
}
270-
return b.renderQueryResults(q, hI.Format, hI.RowLimit)
270+
return b.query(ctx, q, hI.RowLimit)
271271
}
272272

273273
func (b *stackqlMCPReverseProxyService) ListTablesJSON(ctx context.Context, input dto.ListTablesInput) ([]map[string]interface{}, error) {
@@ -290,14 +290,14 @@ func (b *stackqlMCPReverseProxyService) ListTablesJSONPage(ctx context.Context,
290290
return map[string]interface{}{}, nil
291291
}
292292

293-
func (b *stackqlMCPReverseProxyService) ListTables(ctx context.Context, hI dto.HierarchyInput) (string, error) {
293+
func (b *stackqlMCPReverseProxyService) ListTables(ctx context.Context, hI dto.HierarchyInput) ([]map[string]interface{}, error) {
294294
return b.ListResources(ctx, hI)
295295
}
296296

297-
func (b *stackqlMCPReverseProxyService) ListMethods(ctx context.Context, hI dto.HierarchyInput) (string, error) {
297+
func (b *stackqlMCPReverseProxyService) ListMethods(ctx context.Context, hI dto.HierarchyInput) ([]map[string]interface{}, error) {
298298
q, qErr := b.interrogator.GetShowMethods(hI)
299299
if qErr != nil {
300-
return "", qErr
300+
return nil, qErr
301301
}
302-
return b.renderQueryResults(q, hI.Format, hI.RowLimit)
302+
return b.query(ctx, q, hI.RowLimit)
303303
}

internal/stackql/mcpbackend/mcp_service_stackql.go

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,11 @@ func (b *stackqlMCPService) RunQueryJSON(ctx context.Context, input dto.QueryJSO
308308
if q == "" {
309309
return nil, fmt.Errorf("no SQL provided")
310310
}
311-
results, ok := b.extractQueryResults(q, input.RowLimit)
311+
return b.runPreprocessedQueryJSON(ctx, q, input.RowLimit)
312+
}
313+
314+
func (b *stackqlMCPService) runPreprocessedQueryJSON(ctx context.Context, query string, rowLimit int) ([]map[string]interface{}, error) {
315+
results, ok := b.extractQueryResults(query, rowLimit)
312316
if !ok {
313317
return nil, fmt.Errorf("failed to extract query results")
314318
}
@@ -355,17 +359,16 @@ func (b *stackqlMCPService) ListTablesJSONPage(ctx context.Context, input dto.Li
355359
return map[string]interface{}{}, nil
356360
}
357361

358-
func (b *stackqlMCPService) ListTables(ctx context.Context, hI dto.HierarchyInput) (string, error) {
362+
func (b *stackqlMCPService) ListTables(ctx context.Context, hI dto.HierarchyInput) ([]map[string]interface{}, error) {
359363
return b.ListResources(ctx, hI)
360364
}
361365

362-
func (b *stackqlMCPService) ListMethods(ctx context.Context, hI dto.HierarchyInput) (string, error) {
366+
func (b *stackqlMCPService) ListMethods(ctx context.Context, hI dto.HierarchyInput) ([]map[string]interface{}, error) {
363367
q, qErr := b.interrogator.GetShowMethods(hI)
364368
if qErr != nil {
365-
return "", qErr
369+
return nil, qErr
366370
}
367-
rv := b.renderQueryResults(q, hI.Format, hI.RowLimit)
368-
return rv, nil
371+
return b.runPreprocessedQueryJSON(ctx, q, unlimitedRowLimit)
369372
}
370373

371374
func (b *stackqlMCPService) getUpdatedHandlerCtx(query string) (handler.HandlerContext, error) {
@@ -451,46 +454,42 @@ func (b *stackqlMCPService) renderQueryResults(query string, format string, rowL
451454
}
452455
}
453456

454-
func (b *stackqlMCPService) DescribeTable(ctx context.Context, hI dto.HierarchyInput) (string, error) {
457+
func (b *stackqlMCPService) DescribeTable(ctx context.Context, hI dto.HierarchyInput) ([]map[string]interface{}, error) {
455458
q, qErr := b.interrogator.GetDescribeTable(hI)
456459
if qErr != nil {
457-
return "", qErr
460+
return nil, qErr
458461
}
459-
rv := b.renderQueryResults(q, hI.Format, hI.RowLimit)
460-
return rv, nil
462+
return b.runPreprocessedQueryJSON(ctx, q, unlimitedRowLimit)
461463
}
462464

463-
func (b *stackqlMCPService) GetForeignKeys(ctx context.Context, hI dto.HierarchyInput) (string, error) {
464-
return b.interrogator.GetForeignKeys(hI)
465+
func (b *stackqlMCPService) GetForeignKeys(ctx context.Context, hI dto.HierarchyInput) ([]map[string]interface{}, error) {
466+
return nil, fmt.Errorf("GetForeignKeys not implemented")
465467
}
466468

467469
func (b *stackqlMCPService) FindRelationships(ctx context.Context, hI dto.HierarchyInput) (string, error) {
468470
return b.interrogator.FindRelationships(hI)
469471
}
470472

471-
func (b *stackqlMCPService) ListProviders(ctx context.Context) (string, error) {
473+
func (b *stackqlMCPService) ListProviders(ctx context.Context) ([]map[string]interface{}, error) {
472474
q, qErr := b.interrogator.GetShowProviders(dto.HierarchyInput{}, "")
473475
if qErr != nil {
474-
return "", qErr
476+
return nil, qErr
475477
}
476-
rv := b.renderQueryResults(q, "", unlimitedRowLimit)
477-
return rv, nil
478+
return b.runPreprocessedQueryJSON(ctx, q, unlimitedRowLimit)
478479
}
479480

480-
func (b *stackqlMCPService) ListServices(ctx context.Context, hI dto.HierarchyInput) (string, error) {
481+
func (b *stackqlMCPService) ListServices(ctx context.Context, hI dto.HierarchyInput) ([]map[string]interface{}, error) {
481482
q, qErr := b.interrogator.GetShowServices(hI, "")
482483
if qErr != nil {
483-
return "", qErr
484+
return nil, qErr
484485
}
485-
rv := b.renderQueryResults(q, hI.Format, hI.RowLimit)
486-
return rv, nil
486+
return b.runPreprocessedQueryJSON(ctx, q, unlimitedRowLimit)
487487
}
488488

489-
func (b *stackqlMCPService) ListResources(ctx context.Context, hI dto.HierarchyInput) (string, error) {
489+
func (b *stackqlMCPService) ListResources(ctx context.Context, hI dto.HierarchyInput) ([]map[string]interface{}, error) {
490490
q, qErr := b.interrogator.GetShowResources(hI, "")
491491
if qErr != nil {
492-
return "", qErr
492+
return nil, qErr
493493
}
494-
rv := b.renderQueryResults(q, hI.Format, hI.RowLimit)
495-
return rv, nil
494+
return b.runPreprocessedQueryJSON(ctx, q, unlimitedRowLimit)
496495
}

pkg/mcp_server/backend.go

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,22 +47,20 @@ type Backend interface {
4747
ListTablesJSONPage(ctx context.Context, input dto.ListTablesPageInput) (map[string]interface{}, error)
4848

4949
// List all schemas in the database
50-
ListProviders(ctx context.Context) (string, error)
50+
ListProviders(ctx context.Context) ([]map[string]any, error)
5151

52-
ListServices(ctx context.Context, hI dto.HierarchyInput) (string, error)
53-
54-
ListResources(ctx context.Context, hI dto.HierarchyInput) (string, error)
55-
56-
ListMethods(ctx context.Context, hI dto.HierarchyInput) (string, error)
52+
ListServices(ctx context.Context, hI dto.HierarchyInput) ([]map[string]any, error)
5753

54+
ListResources(ctx context.Context, hI dto.HierarchyInput) ([]map[string]any, error)
55+
ListMethods(ctx context.Context, hI dto.HierarchyInput) ([]map[string]any, error)
5856
// List all tables in a specific schema
5957
// ListTables(ctx context.Context, hI HierarchyInput) (string, error)
6058

6159
// Get detailed information about a table
62-
DescribeTable(ctx context.Context, hI dto.HierarchyInput) (string, error)
60+
DescribeTable(ctx context.Context, hI dto.HierarchyInput) ([]map[string]any, error)
6361

6462
// Get foreign key information for a table
65-
GetForeignKeys(ctx context.Context, hI dto.HierarchyInput) (string, error)
63+
GetForeignKeys(ctx context.Context, hI dto.HierarchyInput) ([]map[string]any, error)
6664

6765
// Find both explicit and implied relationships for a table
6866
FindRelationships(ctx context.Context, hI dto.HierarchyInput) (string, error)

pkg/mcp_server/example_backend.go

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -76,36 +76,36 @@ func (b *ExampleBackend) ListTablesJSONPage(ctx context.Context, input dto.ListT
7676
return map[string]interface{}{}, nil
7777
}
7878

79-
func (b *ExampleBackend) ListTables(ctx context.Context, hI dto.HierarchyInput) (string, error) {
80-
return "stub", nil
79+
func (b *ExampleBackend) ListTables(ctx context.Context, hI dto.HierarchyInput) ([]map[string]interface{}, error) {
80+
return []map[string]interface{}{}, nil
8181
}
8282

83-
func (b *ExampleBackend) ListMethods(ctx context.Context, hI dto.HierarchyInput) (string, error) {
84-
return "stub", nil
83+
func (b *ExampleBackend) ListMethods(ctx context.Context, hI dto.HierarchyInput) ([]map[string]any, error) {
84+
return []map[string]any{}, nil
8585
}
8686

87-
func (b *ExampleBackend) DescribeTable(ctx context.Context, hI dto.HierarchyInput) (string, error) {
88-
return "stub", nil
87+
func (b *ExampleBackend) DescribeTable(ctx context.Context, hI dto.HierarchyInput) ([]map[string]any, error) {
88+
return []map[string]any{}, nil
8989
}
9090

91-
func (b *ExampleBackend) GetForeignKeys(ctx context.Context, hI dto.HierarchyInput) (string, error) {
92-
return ExplainerForeignKeyStackql, nil
91+
func (b *ExampleBackend) GetForeignKeys(ctx context.Context, hI dto.HierarchyInput) ([]map[string]any, error) {
92+
return []map[string]any{}, nil
9393
}
9494

9595
func (b *ExampleBackend) FindRelationships(ctx context.Context, hI dto.HierarchyInput) (string, error) {
9696
return ExplainerFindRelationships, nil
9797
}
9898

99-
func (b *ExampleBackend) ListProviders(ctx context.Context) (string, error) {
100-
return "stub", nil
99+
func (b *ExampleBackend) ListProviders(ctx context.Context) ([]map[string]any, error) {
100+
return []map[string]any{}, nil
101101
}
102102

103-
func (b *ExampleBackend) ListServices(ctx context.Context, hI dto.HierarchyInput) (string, error) {
104-
return "stub", nil
103+
func (b *ExampleBackend) ListServices(ctx context.Context, hI dto.HierarchyInput) ([]map[string]any, error) {
104+
return []map[string]any{}, nil
105105
}
106106

107-
func (b *ExampleBackend) ListResources(ctx context.Context, hI dto.HierarchyInput) (string, error) {
108-
return "stub", nil
107+
func (b *ExampleBackend) ListResources(ctx context.Context, hI dto.HierarchyInput) ([]map[string]any, error) {
108+
return []map[string]any{}, nil
109109
}
110110

111111
// NewExampleBackend creates a new example backend instance.

0 commit comments

Comments
 (0)