feat(tools/looker): Query tracking for MCP Toolbox in Looker System Activity views (#1410)

## Description

---

Customers have requested that queries to Looker from MCP Toolbox show up
in
the Looker System Activity under a separate category. To do this we need
to
create the category `MCP Toolbox` on the Looker side. That should happen
by Looker 25.18 release. With 25.18 the request goes to a new
undocumented
API endpoint. If that endpoint results in an error, the Toolbox will
fall back to using
the documented API endpoint.

---

- [x] Make sure you reviewed

[CONTRIBUTING.md](https://github.com/googleapis/genai-toolbox/blob/main/CONTRIBUTING.md)
- [x] Make sure to open an issue as a

[bug/issue](https://github.com/googleapis/genai-toolbox/issues/new/choose)
before writing your code! That way we can discuss the change, evaluate
  designs, and agree on the general idea
- [x] Ensure the tests and linter pass
- [x] Code coverage does not decrease (if any source code was changed)
- [x] Appropriate docs were updated (if necessary)
- [x] Make sure to add `!` if this involve a breaking change

🛠️ Fixes #1409
This commit is contained in:
Dr. Strangelove
2025-09-15 13:31:41 -07:00
committed by GitHub
parent 971001400f
commit 2036c8efd2
8 changed files with 93 additions and 8 deletions

View File

@@ -29,6 +29,10 @@ It's compatible with the following sources:
7. an optional `limit`
8. an optional `tz`
Starting in Looker v25.18, these queries can be identified in Looker's
System Activity. In the History explore, use the field API Client Name
to find MCP Toolbox queries.
## Example
```yaml

View File

@@ -29,6 +29,10 @@ It's compatible with the following sources:
7. an optional `limit`
8. an optional `tz`
Starting in Looker v25.18, these queries can be identified in Looker's
System Activity. In the History explore, use the field API Client Name
to find MCP Toolbox queries.
## Example
```yaml

2
go.mod
View File

@@ -30,7 +30,7 @@ require (
github.com/google/uuid v1.6.0
github.com/jackc/pgx/v5 v5.7.6
github.com/json-iterator/go v1.1.12
github.com/looker-open-source/sdk-codegen/go v0.25.10
github.com/looker-open-source/sdk-codegen/go v0.25.11
github.com/microsoft/go-mssqldb v1.9.3
github.com/nakagami/firebirdsql v0.9.15
github.com/neo4j/neo4j-go-driver/v5 v5.28.3

5
go.sum
View File

@@ -1121,8 +1121,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/looker-open-source/sdk-codegen/go v0.25.10 h1:ltBbwkwZrQEHEIKrE5QbF+EtBlweKN0RZpQR0w2GIqo=
github.com/looker-open-source/sdk-codegen/go v0.25.10/go.mod h1:YM/IYSsTPk7I54j4l6PduNJYgXyOShuaMi7mD6xic8E=
github.com/looker-open-source/sdk-codegen/go v0.25.11 h1:IPxG3eTqz8ICd1ImqffEKQVd8G9/IAbjH7dg4IhiQtU=
github.com/looker-open-source/sdk-codegen/go v0.25.11/go.mod h1:Br1ntSiruDJ/4nYNjpYyWyCbqJ7+GQceWbIgn0hYims=
github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA=
github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o=
@@ -2039,7 +2039,6 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.61.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@@ -262,3 +262,25 @@ func ProcessQueryArgs(ctx context.Context, params tools.ParamValues) (*v4.WriteQ
}
return &wq, nil
}
type QueryApiClientContext struct {
Name string `json:"name"`
Attributes map[string]string `json:"attributes,omitempty"`
ExtraAttributes map[string]string `json:"extra_attributes,omitempty"`
}
type RenderOptions struct {
Format string `json:"format"`
}
type RequestRunInlineQuery2 struct {
Query v4.WriteQuery `json:"query"`
RenderOpts RenderOptions `json:"render_options"`
QueryApiClientCtx QueryApiClientContext `json:"query_api_client_context"`
}
func RunInlineQuery2(l *v4.LookerSDK, request RequestRunInlineQuery2, options *rtl.ApiSettings) (string, error) {
var result string
err := l.AuthSession.Do(&result, "POST", "/4.0", "/queries/run_inline", nil, request, options)
return result, err
}

View File

@@ -15,6 +15,7 @@
package lookercommon_test
import (
"encoding/json"
"testing"
"github.com/google/go-cmp/cmp"
@@ -169,3 +170,32 @@ func TestExtractLookerFieldPropertiesWithNilFields(t *testing.T) {
t.Fatalf("incorrect result: diff %v", diff)
}
}
func TestRequestRunInlineQuery2(t *testing.T) {
fields := make([]string, 1)
fields[0] = "foo.bar"
wq := v4.WriteQuery{
Model: "model",
View: "explore",
Fields: &fields,
}
req2 := lookercommon.RequestRunInlineQuery2{
Query: wq,
RenderOpts: lookercommon.RenderOptions{
Format: "json",
},
QueryApiClientCtx: lookercommon.QueryApiClientContext{
Name: "MCP Toolbox",
},
}
json, err := json.Marshal(req2)
if err != nil {
t.Fatalf("Could not marshall req2 as json")
}
got := string(json)
want := `{"query":{"model":"model","view":"explore","fields":["foo.bar"]},"render_options":{"format":"json"},"query_api_client_context":{"name":"MCP Toolbox"}}`
if diff := cmp.Diff(want, got); diff != "" {
t.Fatalf("incorrect result: diff %v", diff)
}
}

View File

@@ -131,9 +131,22 @@ func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken
Body: *wq,
ResultFormat: "json",
}
resp, err := sdk.RunInlineQuery(req, t.ApiSettings)
req2 := lookercommon.RequestRunInlineQuery2{
Query: *wq,
RenderOpts: lookercommon.RenderOptions{
Format: "json",
},
QueryApiClientCtx: lookercommon.QueryApiClientContext{
Name: "MCP Toolbox",
},
}
resp, err := lookercommon.RunInlineQuery2(sdk, req2, t.ApiSettings)
if err != nil {
return nil, fmt.Errorf("error making query request: %s", err)
logger.DebugContext(ctx, "error querying with new endpoint, trying again with original", err)
resp, err = sdk.RunInlineQuery(req, t.ApiSettings)
if err != nil {
return nil, fmt.Errorf("error making query request: %s", err)
}
}
logger.DebugContext(ctx, "resp = ", resp)

View File

@@ -130,9 +130,22 @@ func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken
Body: *wq,
ResultFormat: "sql",
}
resp, err := sdk.RunInlineQuery(req, t.ApiSettings)
req2 := lookercommon.RequestRunInlineQuery2{
Query: *wq,
RenderOpts: lookercommon.RenderOptions{
Format: "sql",
},
QueryApiClientCtx: lookercommon.QueryApiClientContext{
Name: "MCP Toolbox",
},
}
resp, err := lookercommon.RunInlineQuery2(sdk, req2, t.ApiSettings)
if err != nil {
return nil, fmt.Errorf("error making query_sql request: %s", err)
logger.DebugContext(ctx, "error querying with new endpoint, trying again with original", err)
resp, err = sdk.RunInlineQuery(req, t.ApiSettings)
if err != nil {
return nil, fmt.Errorf("error making query_sql request: %s", err)
}
}
logger.DebugContext(ctx, "resp = ", resp)