Skip to content

Commit 89371ed

Browse files
authored
feat: upgrade mcp-trino v0.4.0 / mcp-datahub v0.6.0 with description overrides (#101)
* feat: upgrade mcp-trino v0.4.0 / mcp-datahub v0.6.0 with config-driven description overrides Bump mcp-trino v0.3.0 → v0.4.0 and mcp-datahub v0.5.2 → v0.6.0 to pick up improved default tool descriptions. Wire the new WithDescriptions toolkit option through YAML config so deployers can customize tool descriptions per-instance without code changes. * docs: add descriptions config option to toolkit reference
1 parent d167596 commit 89371ed

File tree

13 files changed

+362
-28
lines changed

13 files changed

+362
-28
lines changed

configs/platform.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,9 @@ toolkits:
150150
default_limit: 1000
151151
max_limit: 10000
152152
read_only: true
153+
# descriptions:
154+
# trino_query: "Execute a SQL query against the data warehouse"
155+
# trino_describe_table: "Get table schema with column types and sample data"
153156

154157
datahub:
155158
enabled: false
@@ -162,6 +165,9 @@ toolkits:
162165
config:
163166
search_limit: 50
164167
lineage_max_depth: 5
168+
# descriptions:
169+
# datahub_search: "Search the data catalog for datasets and dashboards"
170+
# datahub_get_entity: "Get full metadata for a catalog entity by URN"
165171

166172
s3:
167173
enabled: false

docs/llms-full.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,7 @@ The `database` store requires `database.dsn`. Database-backed sessions survive r
326326
| `default_limit` | int | 1000 | Default row limit |
327327
| `max_limit` | int | 10000 | Maximum row limit |
328328
| `read_only` | bool | false | Restrict to read-only queries |
329+
| `descriptions` | map | {} | Override tool descriptions (key: tool name, value: description text) |
329330

330331
### DataHub
331332

@@ -336,6 +337,7 @@ The `database` store requires `database.dsn`. Database-backed sessions survive r
336337
| `timeout` | duration | 30s | API request timeout |
337338
| `default_limit` | int | 10 | Default search limit |
338339
| `max_limit` | int | 100 | Maximum search limit |
340+
| `descriptions` | map | {} | Override tool descriptions (key: tool name, value: description text) |
339341

340342
### S3
341343

docs/reference/configuration.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,9 @@ toolkits:
244244
max_limit: 10000
245245
read_only: false
246246
connection_name: "Production"
247+
descriptions:
248+
trino_query: "Execute SQL with automatic semantic enrichment from DataHub"
249+
trino_describe_table: "Get table schema with DataHub context — the richest single-call way to understand a table"
247250
```
248251

249252
| Option | Type | Default | Description |
@@ -261,6 +264,7 @@ toolkits:
261264
| `max_limit` | int | `10000` | Maximum row limit |
262265
| `read_only` | bool | `false` | Restrict to read-only queries |
263266
| `connection_name` | string | instance name | Display name |
267+
| `descriptions` | map | `{}` | Override tool descriptions (key: tool name, value: description text) |
264268

265269
### DataHub
266270

@@ -276,6 +280,9 @@ toolkits:
276280
max_lineage_depth: 5
277281
connection_name: "Primary Catalog"
278282
debug: false
283+
descriptions:
284+
datahub_search: "Search the data catalog for datasets and dashboards"
285+
datahub_get_entity: "Get full metadata for a catalog entity by URN"
279286
```
280287

281288
| Option | Type | Default | Description |
@@ -288,6 +295,7 @@ toolkits:
288295
| `max_lineage_depth` | int | `5` | Maximum lineage depth |
289296
| `connection_name` | string | instance name | Display name |
290297
| `debug` | bool | `false` | Enable debug logging for GraphQL operations |
298+
| `descriptions` | map | `{}` | Override tool descriptions (key: tool name, value: description text) |
291299

292300
### S3
293301

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ require (
1414
github.com/swaggo/swag v1.16.4
1515
github.com/testcontainers/testcontainers-go v0.40.0
1616
github.com/testcontainers/testcontainers-go/modules/postgres v0.40.0
17-
github.com/txn2/mcp-datahub v0.5.2
17+
github.com/txn2/mcp-datahub v0.6.0
1818
github.com/txn2/mcp-s3 v0.1.4
19-
github.com/txn2/mcp-trino v0.3.0
19+
github.com/txn2/mcp-trino v0.4.0
2020
golang.org/x/crypto v0.47.0
2121
gopkg.in/yaml.v3 v3.0.1
2222
)

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -264,12 +264,12 @@ github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+F
264264
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
265265
github.com/trinodb/trino-go-client v0.333.0 h1:+bsW8/uLFNF00MEL9JZJym94LlUnle25VgDlWGPEZos=
266266
github.com/trinodb/trino-go-client v0.333.0/go.mod h1:91okdYtRUZoj3XJu/tqdzu11sNliQuN4A+vMFEB8GVE=
267-
github.com/txn2/mcp-datahub v0.5.2 h1:Nq2VetunmEwyW2kR7xnpV9Wl487BFnZ+PpKSgDZjh+U=
268-
github.com/txn2/mcp-datahub v0.5.2/go.mod h1:9YWiNjhmFjmXMK9PX9QmlQgouaZLu/edqJ5hKC5qZUA=
267+
github.com/txn2/mcp-datahub v0.6.0 h1:8iiaQ9xR67Qt8B84QK6TtdljP4hFEKYLBzZQDVA1BTY=
268+
github.com/txn2/mcp-datahub v0.6.0/go.mod h1:UyXoTLT9H5DFkrdi/k0G82x+fZN5N4PSjom6FPGTkI0=
269269
github.com/txn2/mcp-s3 v0.1.4 h1:73VfwofCNUoVAzWQgh1LoaAsNW42jEUTGfRPe1s7Vkk=
270270
github.com/txn2/mcp-s3 v0.1.4/go.mod h1:yu2m8DZdsHJEWj+Ktkhz1XUk17jqPdVSa4UcyZWetkc=
271-
github.com/txn2/mcp-trino v0.3.0 h1:q3mLQPvgDDfODZNgL86JKUrHaDUxpdWhNzaOt6iu6sM=
272-
github.com/txn2/mcp-trino v0.3.0/go.mod h1:1lDb2HYgMtMQWjF7NcC1cRmkXsWMu2SWq85oc4qIriU=
271+
github.com/txn2/mcp-trino v0.4.0 h1:QrCNv0d6NR4OW3+AhIiVYX2tDyph6uDm5tRLz3/UQyk=
272+
github.com/txn2/mcp-trino v0.4.0/go.mod h1:6m5jlHTExGTr2swFRFp2McDBHti8l/F7mHi2b1cYHk4=
273273
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
274274
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
275275
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=

pkg/toolkits/datahub/config.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ func ParseConfig(cfg map[string]any) (Config, error) {
4242
// Optional bool fields
4343
c.Debug = getBool(cfg, "debug", false)
4444

45+
// Optional description overrides
46+
c.Descriptions = getStringMap(cfg, "descriptions")
47+
4548
return c, nil
4649
}
4750

@@ -64,6 +67,21 @@ func getInt(cfg map[string]any, key string, defaultVal int) int {
6467
return defaultVal
6568
}
6669

70+
// getStringMap extracts a map[string]string value from a config map.
71+
func getStringMap(cfg map[string]any, key string) map[string]string { //nolint:unparam // consistent with getString/getInt helpers
72+
raw, ok := cfg[key].(map[string]any)
73+
if !ok {
74+
return nil
75+
}
76+
result := make(map[string]string, len(raw))
77+
for k, v := range raw {
78+
if s, ok := v.(string); ok {
79+
result[k] = s
80+
}
81+
}
82+
return result
83+
}
84+
6785
// getDuration extracts a duration value from a config map.
6886
func getDuration(cfg map[string]any, key string) (time.Duration, error) {
6987
if v, ok := cfg[key].(string); ok {

pkg/toolkits/datahub/config_test.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,93 @@ func TestDatahubGetInt(t *testing.T) {
231231
}
232232
}
233233

234+
func TestGetStringMap(t *testing.T) {
235+
t.Run("valid map", func(t *testing.T) {
236+
cfg := map[string]any{
237+
"descriptions": map[string]any{
238+
"datahub_search": "Search the catalog",
239+
"datahub_get_entity": "Get entity details",
240+
},
241+
}
242+
result := getStringMap(cfg, "descriptions")
243+
if len(result) != 2 {
244+
t.Fatalf("expected 2 entries, got %d", len(result))
245+
}
246+
if result["datahub_search"] != "Search the catalog" {
247+
t.Errorf("datahub_search = %q", result["datahub_search"])
248+
}
249+
if result["datahub_get_entity"] != "Get entity details" {
250+
t.Errorf("datahub_get_entity = %q", result["datahub_get_entity"])
251+
}
252+
})
253+
254+
t.Run("missing key", func(t *testing.T) {
255+
cfg := map[string]any{}
256+
result := getStringMap(cfg, "descriptions")
257+
if result != nil {
258+
t.Errorf("expected nil for missing key, got %v", result)
259+
}
260+
})
261+
262+
t.Run("wrong type", func(t *testing.T) {
263+
cfg := map[string]any{"descriptions": "not a map"}
264+
result := getStringMap(cfg, "descriptions")
265+
if result != nil {
266+
t.Errorf("expected nil for wrong type, got %v", result)
267+
}
268+
})
269+
270+
t.Run("skips non-string values", func(t *testing.T) {
271+
cfg := map[string]any{
272+
"descriptions": map[string]any{
273+
"valid": "a string",
274+
"invalid": dhCfgTestNumVal,
275+
},
276+
}
277+
result := getStringMap(cfg, "descriptions")
278+
if len(result) != 1 {
279+
t.Fatalf("expected 1 entry (non-string skipped), got %d", len(result))
280+
}
281+
if result["valid"] != "a string" {
282+
t.Errorf("valid = %q", result["valid"])
283+
}
284+
})
285+
}
286+
287+
func TestParseConfig_WithDescriptions(t *testing.T) {
288+
cfg := map[string]any{
289+
dhCfgTestURLKey: dhCfgTestExampleURL,
290+
"descriptions": map[string]any{
291+
"datahub_search": "Custom search description",
292+
},
293+
}
294+
295+
result, err := ParseConfig(cfg)
296+
if err != nil {
297+
t.Fatalf(dhCfgTestUnexpectedErr, err)
298+
}
299+
if len(result.Descriptions) != 1 {
300+
t.Fatalf("expected 1 description, got %d", len(result.Descriptions))
301+
}
302+
if result.Descriptions["datahub_search"] != "Custom search description" {
303+
t.Errorf("datahub_search description = %q", result.Descriptions["datahub_search"])
304+
}
305+
}
306+
307+
func TestParseConfig_NoDescriptions(t *testing.T) {
308+
cfg := map[string]any{
309+
dhCfgTestURLKey: dhCfgTestExampleURL,
310+
}
311+
312+
result, err := ParseConfig(cfg)
313+
if err != nil {
314+
t.Fatalf(dhCfgTestUnexpectedErr, err)
315+
}
316+
if result.Descriptions != nil {
317+
t.Errorf("expected nil descriptions, got %v", result.Descriptions)
318+
}
319+
}
320+
234321
func TestDatahubGetDuration(t *testing.T) {
235322
cfg := map[string]any{
236323
dhCfgTestString: "5m",

pkg/toolkits/datahub/toolkit.go

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,15 @@ const (
2929

3030
// Config holds DataHub toolkit configuration.
3131
type Config struct {
32-
URL string `yaml:"url"`
33-
Token string `yaml:"token"`
34-
Timeout time.Duration `yaml:"timeout"`
35-
DefaultLimit int `yaml:"default_limit"`
36-
MaxLimit int `yaml:"max_limit"`
37-
MaxLineageDepth int `yaml:"max_lineage_depth"`
38-
ConnectionName string `yaml:"connection_name"`
39-
Debug bool `yaml:"debug"` // Enable debug logging
32+
URL string `yaml:"url"`
33+
Token string `yaml:"token"`
34+
Timeout time.Duration `yaml:"timeout"`
35+
DefaultLimit int `yaml:"default_limit"`
36+
MaxLimit int `yaml:"max_limit"`
37+
MaxLineageDepth int `yaml:"max_lineage_depth"`
38+
ConnectionName string `yaml:"connection_name"`
39+
Debug bool `yaml:"debug"` // Enable debug logging
40+
Descriptions map[string]string `yaml:"descriptions"`
4041
}
4142

4243
// Toolkit wraps mcp-datahub toolkit for the platform.
@@ -119,14 +120,30 @@ func createClient(cfg Config) (*dhclient.Client, error) {
119120
return client, nil
120121
}
121122

123+
// toDataHubToolNames converts a generic string map to typed ToolName keys.
124+
func toDataHubToolNames(m map[string]string) map[dhtools.ToolName]string {
125+
if m == nil {
126+
return nil
127+
}
128+
result := make(map[dhtools.ToolName]string, len(m))
129+
for k, v := range m {
130+
result[dhtools.ToolName(k)] = v
131+
}
132+
return result
133+
}
134+
122135
// createToolkit creates the mcp-datahub toolkit.
123136
func createToolkit(client *dhclient.Client, cfg Config) *dhtools.Toolkit {
137+
var opts []dhtools.ToolkitOption
138+
if len(cfg.Descriptions) > 0 {
139+
opts = append(opts, dhtools.WithDescriptions(toDataHubToolNames(cfg.Descriptions)))
140+
}
124141
return dhtools.NewToolkit(client, dhtools.Config{
125142
DefaultLimit: cfg.DefaultLimit,
126143
MaxLimit: cfg.MaxLimit,
127144
MaxLineageDepth: cfg.MaxLineageDepth,
128145
Debug: cfg.Debug,
129-
})
146+
}, opts...)
130147
}
131148

132149
// Kind returns the toolkit kind.

pkg/toolkits/datahub/toolkit_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"time"
77

88
"github.com/modelcontextprotocol/go-sdk/mcp"
9+
dhtools "github.com/txn2/mcp-datahub/pkg/tools"
910

1011
"github.com/txn2/mcp-data-platform/pkg/query"
1112
"github.com/txn2/mcp-data-platform/pkg/semantic"
@@ -327,6 +328,41 @@ func TestToolkit_ClientAndClose(t *testing.T) {
327328
}
328329
}
329330

331+
func TestToDataHubToolNames(t *testing.T) {
332+
t.Run("nil input", func(t *testing.T) {
333+
result := toDataHubToolNames(nil)
334+
if result != nil {
335+
t.Errorf("expected nil, got %v", result)
336+
}
337+
})
338+
339+
t.Run("valid conversion", func(t *testing.T) {
340+
input := map[string]string{
341+
"datahub_search": "Custom search",
342+
"datahub_get_entity": "Custom entity",
343+
}
344+
result := toDataHubToolNames(input)
345+
if len(result) != 2 {
346+
t.Fatalf("expected 2 entries, got %d", len(result))
347+
}
348+
for k, v := range input {
349+
if got := result[dhtools.ToolName(k)]; got != v {
350+
t.Errorf("result[%q] = %q, want %q", k, got, v)
351+
}
352+
}
353+
})
354+
355+
t.Run("empty map", func(t *testing.T) {
356+
result := toDataHubToolNames(map[string]string{})
357+
if result == nil {
358+
t.Error("expected non-nil empty map")
359+
}
360+
if len(result) != 0 {
361+
t.Errorf("expected 0 entries, got %d", len(result))
362+
}
363+
})
364+
}
365+
330366
func TestToolkit_RegisterTools(_ *testing.T) {
331367
tk := newTestDatahubToolkit()
332368
// Should not panic with nil server

pkg/toolkits/trino/config.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ func ParseConfig(cfg map[string]any) (Config, error) {
4646
c.Timeout = timeout
4747
}
4848

49+
// Optional description overrides
50+
c.Descriptions = getStringMap(cfg, "descriptions")
51+
4952
return c, nil
5053
}
5154

@@ -84,6 +87,21 @@ func getBoolDefault(cfg map[string]any, key string, defaultVal bool) bool {
8487
return defaultVal
8588
}
8689

90+
// getStringMap extracts a map[string]string value from a config map.
91+
func getStringMap(cfg map[string]any, key string) map[string]string { //nolint:unparam // consistent with getString/getInt helpers
92+
raw, ok := cfg[key].(map[string]any)
93+
if !ok {
94+
return nil
95+
}
96+
result := make(map[string]string, len(raw))
97+
for k, v := range raw {
98+
if s, ok := v.(string); ok {
99+
result[k] = s
100+
}
101+
}
102+
return result
103+
}
104+
87105
// getDuration extracts a duration value from a config map.
88106
func getDuration(cfg map[string]any, key string) (time.Duration, error) {
89107
if v, ok := cfg[key].(string); ok {

0 commit comments

Comments
 (0)