Compare commits

...

31 Commits

Author SHA1 Message Date
Alex Talreja
0b7071ebfd adjust style to match tools 2025-08-07 21:37:22 +00:00
Alex Talreja
750c64bc03 feat: inspect authServices 2025-08-07 21:11:50 +00:00
Alex Talreja
63ed2ea4af feat: inspect sources from Toolbox UI 2025-08-07 21:11:18 +00:00
Mend Renovate
f78956a306 chore(deps): update dependency go to v1.24.6 (#1094)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [go](https://go.dev/)
([source](https://redirect.github.com/golang/go)) | toolchain | patch |
`1.24.5` -> `1.24.6` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>golang/go (go)</summary>

###
[`v1.24.6`](https://redirect.github.com/golang/go/compare/go1.24.5...go1.24.6)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/googleapis/genai-toolbox).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS41MS4xIiwidXBkYXRlZEluVmVyIjoiNDEuNTEuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->
2025-08-07 13:36:42 -07:00
Dr. Strangelove
baa0fe9260 docs(looker): Minor changes to docs to be uniform with everything else (#1110)
* Renamed the tools docs with dashes instead of underscores.
* Added make_look tool to list in how-to/connect-ide/looker_mcp.md

Co-authored-by: Wenxin Du <117315983+duwenxin99@users.noreply.github.com>
2025-08-07 19:08:56 +00:00
AlexTalreja
37ae55648d feat: add instructions and links to docs in UI (#1100)
Add instructions on basic usage + links to the public documentation on
the main content area of the Homepage, Tools page, and Toolsets page.
2025-08-07 11:33:09 -07:00
Mend Renovate
1c59459c94 chore(deps): update module google.golang.org/api to v0.246.0 (#1092)
This PR contains the following updates:

| Package | Change | Age | Confidence |
|---|---|---|---|
|
[google.golang.org/api](https://redirect.github.com/googleapis/google-api-go-client)
| `v0.245.0` -> `v0.246.0` |
[![age](https://developer.mend.io/api/mc/badges/age/go/google.golang.org%2fapi/v0.246.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/google.golang.org%2fapi/v0.245.0/v0.246.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>googleapis/google-api-go-client
(google.golang.org/api)</summary>

###
[`v0.246.0`](https://redirect.github.com/googleapis/google-api-go-client/releases/tag/v0.246.0)

[Compare
Source](https://redirect.github.com/googleapis/google-api-go-client/compare/v0.245.0...v0.246.0)

##### Features

- **all:** Auto-regenerate discovery clients
([#&#8203;3261](https://redirect.github.com/googleapis/google-api-go-client/issues/3261))
([b792200](b792200673))

##### Bug Fixes

- **idtoken:** Don't assume DefaultTransport is a http.Transport
([#&#8203;3263](https://redirect.github.com/googleapis/google-api-go-client/issues/3263))
([61fba51](61fba51991)),
refs
[#&#8203;3260](https://redirect.github.com/googleapis/google-api-go-client/issues/3260)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/googleapis/genai-toolbox).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS41MS4xIiwidXBkYXRlZEluVmVyIjoiNDEuNTEuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: Yuan Teoh <45984206+Yuan325@users.noreply.github.com>
2025-08-07 18:19:27 +00:00
Mend Renovate
b9de4f7f41 chore(deps): update actions/cache digest to 0400d5f (#1108)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [actions/cache](https://redirect.github.com/actions/cache) | action |
digest | `5a3ec84` -> `0400d5f` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/googleapis/genai-toolbox).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS41MS4xIiwidXBkYXRlZEluVmVyIjoiNDEuNTEuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->
2025-08-07 18:05:31 +00:00
Mend Renovate
2dff31ad8b chore(deps): update module github.com/valkey-io/valkey-go to v1.0.64 (#1087)
This PR contains the following updates:

| Package | Change | Age | Confidence |
|---|---|---|---|
|
[github.com/valkey-io/valkey-go](https://redirect.github.com/valkey-io/valkey-go)
| `v1.0.63` -> `v1.0.64` |
[![age](https://developer.mend.io/api/mc/badges/age/go/github.com%2fvalkey-io%2fvalkey-go/v1.0.64?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/github.com%2fvalkey-io%2fvalkey-go/v1.0.63/v1.0.64?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>valkey-io/valkey-go (github.com/valkey-io/valkey-go)</summary>

###
[`v1.0.64`](https://redirect.github.com/valkey-io/valkey-go/releases/tag/v1.0.64):
1.0.64

[Compare
Source](https://redirect.github.com/valkey-io/valkey-go/compare/v1.0.63...v1.0.64)

##### Changes

- feat: Add `FieldValueIter` to `Hsetex` command builder
- feat: Add `CreateAndAliasIndex` to `om.Repository`
- fix: clear placeholder pipeline errors in `valkeycompat`
([#&#8203;61](https://redirect.github.com/valkey-io/valkey-go/issues/61))

##### Contributors

We'd like to thank all the contributors who worked on this release!

[@&#8203;SoulPancake](https://redirect.github.com/SoulPancake),
[@&#8203;XHanL](https://redirect.github.com/XHanL),
[@&#8203;rueian](https://redirect.github.com/rueian) and
[@&#8203;zeeshan-zomato](https://redirect.github.com/zeeshan-zomato)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/googleapis/genai-toolbox).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS41MS4xIiwidXBkYXRlZEluVmVyIjoiNDEuNTEuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: Yuan Teoh <45984206+Yuan325@users.noreply.github.com>
2025-08-07 10:32:58 -07:00
Dr. Strangelove
61d9489344 feat(tools/looker): add looker-make-look tool to create Looks (#1099) 2025-08-07 12:48:40 -04:00
Twisha Bansal
f6b15de8cd docs: fix js quickstart (#1104)
Use the newly recommended `@langchain/google-genai` library to use
gemini API keys.
2025-08-07 10:35:20 +00:00
Dr. Strangelove
5bf275846a feat: add visualizations to query-url tool (#1090)
Co-authored-by: Wenxin Du <117315983+duwenxin99@users.noreply.github.com>
2025-08-06 18:17:02 -04:00
Wenxin Du
4fd19bf9af chore: Access params by maps for execute-sql tools (#1097)
It's safer and also more readable to access params by map.
2025-08-06 16:42:02 -04:00
Mend Renovate
99978bc697 chore(deps): update module cloud.google.com/go/spanner to v1.84.1 (#1096)
This PR contains the following updates:

| Package | Change | Age | Confidence |
|---|---|---|---|
|
[cloud.google.com/go/spanner](https://redirect.github.com/googleapis/google-cloud-go)
| `v1.84.0` -> `v1.84.1` |
[![age](https://developer.mend.io/api/mc/badges/age/go/cloud.google.com%2fgo%2fspanner/v1.84.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/cloud.google.com%2fgo%2fspanner/v1.84.0/v1.84.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/googleapis/genai-toolbox).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS41MS4xIiwidXBkYXRlZEluVmVyIjoiNDEuNTEuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->
2025-08-06 20:32:50 +00:00
Wenxin Du
5fda4d4ed1 Revert "chore: Access params by maps for execute-sql tools" (#1095)
This reverts commit 6455ba964b.
2025-08-06 16:13:36 -04:00
duwenxin99
6455ba964b chore: Access params by maps for execute-sql tools 2025-08-06 15:24:00 -04:00
prernakakkar-google
9c045253bd docs: add mysql and mssql prebuilt docs (#1060)
Co-authored-by: Yuan Teoh <45984206+Yuan325@users.noreply.github.com>
2025-08-06 17:16:35 +00:00
Dr. Strangelove
3b1cce72e7 fix(tools/looker): add isAuthorized call to all Looker tools (#1091) 2025-08-06 16:15:05 +00:00
Huan Chen
1cac9b5b37 feat(bigquery-execute-sql): add dry run support (#1057)
Add optional `dry_run` parameter to bigquery-execute-sql, which defaults
to `false`.

When the `dry_run` parameter is set to `true`, the tool returns the
metadata from the dry run instead of executing the query.

Fixes #703

---------

Co-authored-by: Yuan Teoh <45984206+Yuan325@users.noreply.github.com>
2025-08-05 23:56:28 +00:00
AlexTalreja
d91bdfcbdc feat: add login with google button for automatic id token retrieval (#1044)
Add `Sign in with Google` button within Toolbox UI's `Edit Header` modal
that automatically retrieves a valid ID token for users based on an
input clientID.

This should make it significantly easier/faster for users to format
request headers properly and test tools that use auth.
2025-08-05 15:55:28 -07:00
Mend Renovate
c60a9601b4 chore(deps): update module google.golang.org/api to v0.245.0 (#1081)
This PR contains the following updates:

| Package | Change | Age | Confidence |
|---|---|---|---|
|
[google.golang.org/api](https://redirect.github.com/googleapis/google-api-go-client)
| `v0.244.0` -> `v0.245.0` |
[![age](https://developer.mend.io/api/mc/badges/age/go/google.golang.org%2fapi/v0.245.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/google.golang.org%2fapi/v0.244.0/v0.245.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>googleapis/google-api-go-client
(google.golang.org/api)</summary>

###
[`v0.245.0`](https://redirect.github.com/googleapis/google-api-go-client/releases/tag/v0.245.0)

[Compare
Source](https://redirect.github.com/googleapis/google-api-go-client/compare/v0.244.0...v0.245.0)

##### Features

- **all:** Auto-regenerate discovery clients
([#&#8203;3252](https://redirect.github.com/googleapis/google-api-go-client/issues/3252))
([0716728](07167280e3))
- **all:** Auto-regenerate discovery clients
([#&#8203;3254](https://redirect.github.com/googleapis/google-api-go-client/issues/3254))
([702998a](702998a9a8))
- **all:** Auto-regenerate discovery clients
([#&#8203;3255](https://redirect.github.com/googleapis/google-api-go-client/issues/3255))
([0f10366](0f103667e9))
- **all:** Auto-regenerate discovery clients
([#&#8203;3256](https://redirect.github.com/googleapis/google-api-go-client/issues/3256))
([83176a9](83176a94b6))
- **all:** Auto-regenerate discovery clients
([#&#8203;3257](https://redirect.github.com/googleapis/google-api-go-client/issues/3257))
([efc3371](efc3371674))
- **all:** Auto-regenerate discovery clients
([#&#8203;3259](https://redirect.github.com/googleapis/google-api-go-client/issues/3259))
([bf38d3a](bf38d3ad99))

##### Bug Fixes

- **gensupport:** Fix transferChunk race condition by returning response
with non-cancelled context.
([#&#8203;3258](https://redirect.github.com/googleapis/google-api-go-client/issues/3258))
([091d422](091d42217a))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/googleapis/genai-toolbox).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS41MS4xIiwidXBkYXRlZEluVmVyIjoiNDEuNTEuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->
2025-08-05 15:25:44 -07:00
Anushka Saxena
bff528093d docs: add pre-built configurations for ide connection to toolbox using mcp (#962)
### Description
To provide clear, accessible documentation for each of these pre-built
tools, add a new heading for `Pre-built Tools` under the "Available
Tools" section on each Source page.

###  Related Issues
Address and resolves #960

---------

Signed-off-by: Anushka Saxena <anushkasaxenaa@google.com>
Co-authored-by: Yuan Teoh <45984206+Yuan325@users.noreply.github.com>
2025-08-05 15:13:09 -07:00
Mend Renovate
34f78bd89d chore(deps): update module github.com/redis/go-redis/v9 to v9.12.0 (#1078)
This PR contains the following updates:

| Package | Change | Age | Confidence |
|---|---|---|---|
|
[github.com/redis/go-redis/v9](https://redirect.github.com/redis/go-redis)
| `v9.11.0` -> `v9.12.0` |
[![age](https://developer.mend.io/api/mc/badges/age/go/github.com%2fredis%2fgo-redis%2fv9/v9.12.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/github.com%2fredis%2fgo-redis%2fv9/v9.11.0/v9.12.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>redis/go-redis (github.com/redis/go-redis/v9)</summary>

###
[`v9.12.0`](https://redirect.github.com/redis/go-redis/releases/tag/v9.12.0):
9.12.0

[Compare
Source](https://redirect.github.com/redis/go-redis/compare/v9.11.0...v9.12.0)

#### 🚀 Highlights

- This release includes support for [Redis
8.2](https://redis.io/docs/latest/operate/oss_and_stack/stack-with-enterprise/release-notes/redisce/redisos-8.2-release-notes/).
- Introduces an experimental Query Builders for `FTSearch`,
`FTAggregate` and other search commands.
- Adds support for `EPSILON` option in `FT.VSIM`.
- Includes bug fixes and improvements contributed by the community
related to ring and
[redisotel](https://redirect.github.com/redis/go-redis/tree/master/extra/redisotel).

#### Changes

- Improve stale issue workflow
([#&#8203;3458](https://redirect.github.com/redis/go-redis/pull/3458))
- chore(ci): Add 8.2 rc2 pre build for CI
([#&#8203;3459](https://redirect.github.com/redis/go-redis/pull/3459))
- Added new stream commands
([#&#8203;3450](https://redirect.github.com/redis/go-redis/pull/3450))
- feat: Add "skip\_verify" to Sentinel
([#&#8203;3428](https://redirect.github.com/redis/go-redis/pull/3428))
- fix: `errors.Join` requires Go 1.20 or later
([#&#8203;3442](https://redirect.github.com/redis/go-redis/pull/3442))
- DOC-4344 document quickstart examples
([#&#8203;3426](https://redirect.github.com/redis/go-redis/pull/3426))
- feat(bitop): add support for the new bitop operations
([#&#8203;3409](https://redirect.github.com/redis/go-redis/pull/3409))

#### 🚀 New Features

- feat: recover addIdleConn may occur panic
([#&#8203;2445](https://redirect.github.com/redis/go-redis/pull/2445))
- feat(ring): specify custom health check func via HeartbeatFn option
([#&#8203;2940](https://redirect.github.com/redis/go-redis/pull/2940))
- Add Query Builder for RediSearch commands
([#&#8203;3436](https://redirect.github.com/redis/go-redis/pull/3436))
- add configurable buffer sizes for Redis connections
([#&#8203;3453](https://redirect.github.com/redis/go-redis/pull/3453))
- Add VAMANA vector type to RediSearch
([#&#8203;3449](https://redirect.github.com/redis/go-redis/pull/3449))
- VSIM add `EPSILON` option
([#&#8203;3454](https://redirect.github.com/redis/go-redis/pull/3454))
- Add closing support to otel metrics instrumentation
([#&#8203;3444](https://redirect.github.com/redis/go-redis/pull/3444))

#### 🐛 Bug Fixes

- fix(redisotel): fix buggy append in reportPoolStats
([#&#8203;3122](https://redirect.github.com/redis/go-redis/pull/3122))
- fix(search): return results even if doc is empty
([#&#8203;3457](https://redirect.github.com/redis/go-redis/pull/3457))
- \[ISSUE-3402]: Ring.Pipelined return dial timeout error
([#&#8203;3403](https://redirect.github.com/redis/go-redis/pull/3403))

#### 🧰 Maintenance

- Merges stale issues jobs into one job with two steps
([#&#8203;3463](https://redirect.github.com/redis/go-redis/pull/3463))
- improve code readability
([#&#8203;3446](https://redirect.github.com/redis/go-redis/pull/3446))
- chore(release): 9.12.0-beta.1
([#&#8203;3460](https://redirect.github.com/redis/go-redis/pull/3460))
- DOC-5472 time series doc examples
([#&#8203;3443](https://redirect.github.com/redis/go-redis/pull/3443))
- Add VAMANA compression algorithm tests
([#&#8203;3461](https://redirect.github.com/redis/go-redis/pull/3461))
- bumped redis 8.2 version used in the CI/CD
([#&#8203;3451](https://redirect.github.com/redis/go-redis/pull/3451))

#### Contributors

We'd like to thank all the contributors who worked on this release!


[@&#8203;andy-stark-redis](https://redirect.github.com/andy-stark-redis),
[@&#8203;cxljs](https://redirect.github.com/cxljs),
[@&#8203;elena-kolevska](https://redirect.github.com/elena-kolevska),
[@&#8203;htemelski-redis](https://redirect.github.com/htemelski-redis),
[@&#8203;jouir](https://redirect.github.com/jouir),
[@&#8203;monkey92t](https://redirect.github.com/monkey92t),
[@&#8203;ndyakov](https://redirect.github.com/ndyakov),
[@&#8203;ofekshenawa](https://redirect.github.com/ofekshenawa),
[@&#8203;rokn](https://redirect.github.com/rokn),
[@&#8203;smnvdev](https://redirect.github.com/smnvdev),
[@&#8203;strobil](https://redirect.github.com/strobil) and
[@&#8203;wzy9607](https://redirect.github.com/wzy9607)

#### New Contributors

- [@&#8203;htemelski-redis](https://redirect.github.com/htemelski-redis)
made their first contribution in
[#&#8203;3409](https://redirect.github.com/redis/go-redis/pull/3409)
- [@&#8203;smnvdev](https://redirect.github.com/smnvdev) made their
first contribution in
[#&#8203;3403](https://redirect.github.com/redis/go-redis/pull/3403)
- [@&#8203;rokn](https://redirect.github.com/rokn) made their first
contribution in
[#&#8203;3444](https://redirect.github.com/redis/go-redis/pull/3444)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/googleapis/genai-toolbox).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS41MS4xIiwidXBkYXRlZEluVmVyIjoiNDEuNTEuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: Yuan Teoh <45984206+Yuan325@users.noreply.github.com>
2025-08-05 15:00:26 -07:00
Mend Renovate
000d6ada38 chore(deps): update module cloud.google.com/go/spanner to v1.84.0 (#1073)
This PR contains the following updates:

| Package | Change | Age | Confidence |
|---|---|---|---|
|
[cloud.google.com/go/spanner](https://redirect.github.com/googleapis/google-cloud-go)
| `v1.83.0` -> `v1.84.0` |
[![age](https://developer.mend.io/api/mc/badges/age/go/cloud.google.com%2fgo%2fspanner/v1.84.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/cloud.google.com%2fgo%2fspanner/v1.83.0/v1.84.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/googleapis/genai-toolbox).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS41MS4xIiwidXBkYXRlZEluVmVyIjoiNDEuNTEuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: Yuan Teoh <45984206+Yuan325@users.noreply.github.com>
2025-08-05 14:48:18 -07:00
release-please[bot]
a09f628b52 chore(main): release 0.11.0 (#1071)
🤖 I have created a release *beep* *boop*
---


##
[0.11.0](https://github.com/googleapis/genai-toolbox/compare/v0.11.0...v0.11.0)
(2025-08-05)


### ⚠ BREAKING CHANGES

* **tools/bigquery-sql:** Ensure invoke always returns a non-null value
([#1020](https://github.com/googleapis/genai-toolbox/issues/1020))
([9af55b6](9af55b651d))
* **tools/bigquery-execute-sql:** Update the return messages
([#1034](https://github.com/googleapis/genai-toolbox/issues/1034))
([051e686](051e686476))

### Features

* Add TiDB source and tool
([#829](https://github.com/googleapis/genai-toolbox/issues/829))
([6eaf36a](6eaf36ac85))
* Interactive web UI for Toolbox
([#1065](https://github.com/googleapis/genai-toolbox/issues/1065))
([8749b03](8749b03003))
* **prebuiltconfigs/cloud-sql-postgres:** Introduce additional parameter
to limit context in list tables
([#1062](https://github.com/googleapis/genai-toolbox/issues/1062))
([c3a58e1](c3a58e1d16))
* **tools/looker-query-url:** Add support for `looker-query-url` tool
([#1015](https://github.com/googleapis/genai-toolbox/issues/1015))
([327ddf0](327ddf0439))
* **tools/dataplex-lookup-entry:** Add support for
`dataplex-lookup-entry` tool
([#1009](https://github.com/googleapis/genai-toolbox/issues/1009))
([5fa1660](5fa1660fc8))

### Bug Fixes

* **tools/bigquery,mssql,mysql,postgres,spanner,tidb:** Add query
logging to execute-sql tools
([#1069](https://github.com/googleapis/genai-toolbox/issues/1069))
([0527532]([0527532bd7))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com>
Co-authored-by: Yuan Teoh <45984206+Yuan325@users.noreply.github.com>
2025-08-05 14:00:26 -07:00
Yuan Teoh
80a8ebfa0b chore: release 0.11.0 (#1082)
Release-As: 0.11.0
2025-08-05 20:17:01 +00:00
Yuan Teoh
0588e178d6 chore: include new docs in release please config (#1070) 2025-08-05 19:09:30 +00:00
Averi Kitsch
f80f18aaf6 ci(schedule_reporter): add continuous release to failure notification (#1080) 2025-08-05 11:56:35 -07:00
Averi Kitsch
f79cdd6144 revert: 'feat: add support for DuckDB' (#1079)
DuckDB requires an overhaul of the build system, see
https://github.com/marcboeker/go-duckdb/issues/94
2025-08-05 11:44:52 -07:00
prernakakkar-google
c3a58e1d16 feat(prebuiltconfigs/cloud-sql-postgres): Introduce additional parameter to limit context in list tables (#1062) 2025-08-05 17:13:03 +00:00
Twisha Bansal
c7b443d94a docs: update alloydb ai nl docs to enable parameterized_views (#1074)
Clarifies fix for
https://github.com/googleapis/genai-toolbox/issues/1041
2025-08-05 21:55:01 +05:30
94 changed files with 2902 additions and 1145 deletions

View File

@@ -62,7 +62,6 @@ steps:
postgressql \
postgresexecutesql
- id: "alloydb-pg"
name: golang:1
waitFor: ["compile-test-binary"]
@@ -121,8 +120,7 @@ steps:
- "BIGTABLE_PROJECT=$PROJECT_ID"
- "BIGTABLE_INSTANCE=$_BIGTABLE_INSTANCE"
- "SERVICE_ACCOUNT_EMAIL=$SERVICE_ACCOUNT_EMAIL"
secretEnv:
["CLIENT_ID"]
secretEnv: ["CLIENT_ID"]
volumes:
- name: "go"
path: "/gopath"
@@ -153,7 +151,7 @@ steps:
"BigQuery" \
bigquery \
bigquery
- id: "dataplex"
name: golang:1
waitFor: ["compile-test-binary"]
@@ -274,8 +272,7 @@ steps:
- "CLOUD_SQL_MYSQL_DATABASE=$_DATABASE_NAME"
- "CLOUD_SQL_MYSQL_REGION=$_REGION"
- "SERVICE_ACCOUNT_EMAIL=$SERVICE_ACCOUNT_EMAIL"
secretEnv:
["CLOUD_SQL_MYSQL_USER", "CLOUD_SQL_MYSQL_PASS", "CLIENT_ID"]
secretEnv: ["CLOUD_SQL_MYSQL_USER", "CLOUD_SQL_MYSQL_PASS", "CLIENT_ID"]
volumes:
- name: "go"
path: "/gopath"
@@ -387,7 +384,7 @@ steps:
sqlite
- id: "couchbase"
name : golang:1
name: golang:1
waitFor: ["compile-test-binary"]
entrypoint: /bin/bash
env:
@@ -395,7 +392,8 @@ steps:
- "COUCHBASE_SCOPE=$_COUCHBASE_SCOPE"
- "COUCHBASE_BUCKET=$_COUCHBASE_BUCKET"
- "SERVICE_ACCOUNT_EMAIL=$SERVICE_ACCOUNT_EMAIL"
secretEnv: ["COUCHBASE_CONNECTION", "COUCHBASE_USER", "COUCHBASE_PASS", "CLIENT_ID"]
secretEnv:
["COUCHBASE_CONNECTION", "COUCHBASE_USER", "COUCHBASE_PASS", "CLIENT_ID"]
volumes:
- name: "go"
path: "/gopath"
@@ -408,7 +406,7 @@ steps:
couchbase
- id: "redis"
name : golang:1
name: golang:1
waitFor: ["compile-test-binary"]
entrypoint: /bin/bash
env:
@@ -425,9 +423,9 @@ steps:
"Redis" \
redis \
redis
- id: "valkey"
name : golang:1
name: golang:1
waitFor: ["compile-test-binary"]
entrypoint: /bin/bash
env:
@@ -475,7 +473,13 @@ steps:
- "FIRESTORE_PROJECT=$PROJECT_ID"
- "SERVICE_ACCOUNT_EMAIL=$SERVICE_ACCOUNT_EMAIL"
- "LOOKER_VERIFY_SSL=$_LOOKER_VERIFY_SSL"
secretEnv: ["CLIENT_ID", "LOOKER_BASE_URL", "LOOKER_CLIENT_ID", "LOOKER_CLIENT_SECRET"]
secretEnv:
[
"CLIENT_ID",
"LOOKER_BASE_URL",
"LOOKER_CLIENT_ID",
"LOOKER_CLIENT_SECRET",
]
volumes:
- name: "go"
path: "/gopath"
@@ -487,25 +491,6 @@ steps:
looker \
looker
- id: "duckdb"
name: golang:1
waitFor: ["compile-test-binary"]
entrypoint: /bin/bash
env:
- "GOPATH=/gopath"
- "SERVICE_ACCOUNT_EMAIL=$SERVICE_ACCOUNT_EMAIL"
volumes:
- name: "go"
path: "/gopath"
secretEnv: ["CLIENT_ID"]
args:
- -c
- |
.ci/test_with_coverage.sh \
"DuckDB" \
duckdb \
duckdb
- id: "alloydbwaitforoperation"
name: golang:1
waitFor: ["compile-test-binary"]
@@ -545,8 +530,8 @@ steps:
.ci/test_with_coverage.sh \
"TiDB" \
tidb \
tidbsql tidbexecutesql
tidbsql tidbexecutesql
availableSecrets:
secretManager:
- versionName: projects/$PROJECT_ID/secrets/cloud_sql_pg_user/versions/latest

View File

@@ -24,6 +24,7 @@ extraFiles: [
"docs/en/getting-started/local_quickstart_js.md",
"docs/en/getting-started/local_quickstart_go.md",
"docs/en/getting-started/mcp_quickstart/_index.md",
"docs/en/samples/alloydb/_index.md",
"docs/en/samples/bigquery/local_quickstart.md",
"docs/en/samples/bigquery/mcp_quickstart/_index.md",
"docs/en/samples/bigquery/colab_quickstart_bigquery.ipynb",
@@ -37,6 +38,8 @@ extraFiles: [
"docs/en/how-to/connect-ide/cloud_sql_mysql_mcp.md",
"docs/en/how-to/connect-ide/firestore_mcp.md",
"docs/en/how-to/connect-ide/looker_mcp.md",
"docs/en/how-to/connect-ide/mysql_mcp.md",
"docs/en/how-to/connect-ide/mssql_mcp.md",
"docs/en/how-to/connect-ide/postgres_mcp.md",
"docs/en/how-to/connect-ide/spanner_mcp.md",
]

View File

@@ -55,7 +55,7 @@ jobs:
node-version: "22"
- name: Cache dependencies
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}

View File

@@ -67,7 +67,7 @@ jobs:
node-version: "22"
- name: Cache dependencies
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}

View File

@@ -26,4 +26,4 @@ jobs:
contents: 'read'
uses: ./.github/workflows/cloud_build_failure_reporter.yml
with:
trigger_names: "toolbox-test-nightly,toolbox-test-on-merge"
trigger_names: "toolbox-test-nightly,toolbox-test-on-merge,toolbox-continuous-release"

View File

@@ -1,6 +1,6 @@
# Changelog
## [0.11.0](https://github.com/googleapis/genai-toolbox/compare/v0.10.0...v0.11.0) (2025-08-04)
## [0.11.0](https://github.com/googleapis/genai-toolbox/compare/v0.11.0...v0.11.0) (2025-08-05)
### ⚠ BREAKING CHANGES
@@ -10,12 +10,16 @@
### Features
* Add DuckDB source and tool ([#879](https://github.com/googleapis/genai-toolbox/pull/879)) ([fd14933](https://github.com/googleapis/genai-toolbox/commit/fd149337e9fa8e912e8699962a7104d51cdffc5d))
* Add TiDB source and tool ([#829](https://github.com/googleapis/genai-toolbox/issues/829)) ([6eaf36a](https://github.com/googleapis/genai-toolbox/commit/6eaf36ac8505d523fa4f5a4ac3c97209fd688cef))
* Interactive web UI for Toolbox ([#1065](https://github.com/googleapis/genai-toolbox/issues/1065)) ([8749b03](https://github.com/googleapis/genai-toolbox/commit/8749b030035e65361047c4ead13dfacb8e9a9b59))
* **prebuiltconfigs/cloud-sql-postgres:** Introduce additional parameter to limit context in list tables ([#1062](https://github.com/googleapis/genai-toolbox/issues/1062)) ([c3a58e1](https://github.com/googleapis/genai-toolbox/commit/c3a58e1d1678dc14d8de5006511df597fd75faa3))
* **tools/looker-query-url:** Add support for `looker-query-url` tool ([#1015](https://github.com/googleapis/genai-toolbox/issues/1015)) ([327ddf0](https://github.com/googleapis/genai-toolbox/commit/327ddf0439058aa5ecd2c7ae8251fcde6aeff18c))
* **tools/dataplex-lookup-entry:** Add support for `dataplex-lookup-entry` tool ([#1009](https://github.com/googleapis/genai-toolbox/issues/1009)) ([5fa1660](https://github.com/googleapis/genai-toolbox/commit/5fa1660fc8631989b4d13abea205b6426bb506a5))
### Bug Fixes
* **tools/bigquery,mssql,mysql,postgres,spanner,tidb:** Add query logging to execute-sql tools ([#1069](https://github.com/googleapis/genai-toolbox/issues/1069)) ([0527532]([https://github.com/googleapis/genai-toolbox/commit/0527532bd7085ef9eb8f9c30f430a2f2f35cef32))
## [0.10.0](https://github.com/googleapis/genai-toolbox/compare/v0.9.0...v0.10.0) (2025-07-25)

View File

@@ -114,7 +114,7 @@ To install Toolbox as a binary:
<!-- {x-release-please-start-version} -->
```sh
# see releases page for other versions
export VERSION=0.10.0
export VERSION=0.11.0
curl -O https://storage.googleapis.com/genai-toolbox/v$VERSION/linux/amd64/toolbox
chmod +x toolbox
```
@@ -127,7 +127,7 @@ You can also install Toolbox as a container:
```sh
# see releases page for other versions
export VERSION=0.10.0
export VERSION=0.11.0
docker pull us-central1-docker.pkg.dev/database-toolbox/toolbox/toolbox:$VERSION
```
@@ -151,7 +151,7 @@ To install from source, ensure you have the latest version of
[Go installed](https://go.dev/doc/install), and then run the following command:
```sh
go install github.com/googleapis/genai-toolbox@v0.10.0
go install github.com/googleapis/genai-toolbox@v0.11.0
```
<!-- {x-release-please-end} -->

View File

@@ -54,7 +54,6 @@ import (
_ "github.com/googleapis/genai-toolbox/internal/tools/dataplex/dataplexlookupentry"
_ "github.com/googleapis/genai-toolbox/internal/tools/dataplex/dataplexsearchentries"
_ "github.com/googleapis/genai-toolbox/internal/tools/dgraph"
_ "github.com/googleapis/genai-toolbox/internal/tools/duckdbsql"
_ "github.com/googleapis/genai-toolbox/internal/tools/firestore/firestoredeletedocuments"
_ "github.com/googleapis/genai-toolbox/internal/tools/firestore/firestoregetdocuments"
_ "github.com/googleapis/genai-toolbox/internal/tools/firestore/firestoregetrules"
@@ -69,6 +68,7 @@ import (
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookergetmeasures"
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookergetmodels"
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookergetparameters"
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookermakelook"
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookerquery"
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookerquerysql"
_ "github.com/googleapis/genai-toolbox/internal/tools/looker/lookerqueryurl"
@@ -112,7 +112,6 @@ import (
_ "github.com/googleapis/genai-toolbox/internal/sources/couchbase"
_ "github.com/googleapis/genai-toolbox/internal/sources/dataplex"
_ "github.com/googleapis/genai-toolbox/internal/sources/dgraph"
_ "github.com/googleapis/genai-toolbox/internal/sources/duckdb"
_ "github.com/googleapis/genai-toolbox/internal/sources/firestore"
_ "github.com/googleapis/genai-toolbox/internal/sources/http"
_ "github.com/googleapis/genai-toolbox/internal/sources/looker"

View File

@@ -1290,7 +1290,7 @@ func TestPrebuiltTools(t *testing.T) {
wantToolset: server.ToolsetConfigs{
"looker-tools": tools.ToolsetConfig{
Name: "looker-tools",
ToolNames: []string{"get_models", "get_explores", "get_dimensions", "get_measures", "get_filters", "get_parameters", "query", "query_sql", "query_url", "get_looks", "run_look"},
ToolNames: []string{"get_models", "get_explores", "get_dimensions", "get_measures", "get_filters", "get_parameters", "query", "query_sql", "query_url", "get_looks", "run_look", "make_look"},
},
},
},

View File

@@ -86,7 +86,7 @@ To install Toolbox as a binary:
```sh
# see releases page for other versions
export VERSION=0.10.0
export VERSION=0.11.0
curl -O https://storage.googleapis.com/genai-toolbox/v$VERSION/linux/amd64/toolbox
chmod +x toolbox
```
@@ -97,7 +97,7 @@ You can also install Toolbox as a container:
```sh
# see releases page for other versions
export VERSION=0.10.0
export VERSION=0.11.0
docker pull us-central1-docker.pkg.dev/database-toolbox/toolbox/toolbox:$VERSION
```
@@ -115,7 +115,7 @@ To install from source, ensure you have the latest version of
[Go installed](https://go.dev/doc/install), and then run the following command:
```sh
go install github.com/googleapis/genai-toolbox@v0.10.0
go install github.com/googleapis/genai-toolbox@v0.11.0
```
{{% /tab %}}

View File

@@ -171,7 +171,7 @@ In this section, we will download Toolbox, configure our tools in a
<!-- {x-release-please-start-version} -->
```bash
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/$OS/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/$OS/toolbox
```
<!-- {x-release-please-end} -->

View File

@@ -167,7 +167,7 @@ In this section, we will download Toolbox, configure our tools in a
<!-- {x-release-please-start-version} -->
```bash
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/$OS/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/$OS/toolbox
```
<!-- {x-release-please-end} -->

View File

@@ -167,7 +167,7 @@ In this section, we will download Toolbox, configure our tools in a
<!-- {x-release-please-start-version} -->
```bash
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/$OS/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/$OS/toolbox
```
<!-- {x-release-please-end} -->
@@ -294,7 +294,7 @@ from Toolbox.
{{< tabpane persist=header >}}
{{< tab header="LangChain" lang="bash" >}}
npm install langchain @langchain/google-vertexai
npm install langchain @langchain/google-genai
{{< /tab >}}
{{< tab header="GenkitJS" lang="bash" >}}
npm install genkit @genkit-ai/vertexai
@@ -309,7 +309,7 @@ npm install llamaindex @llamaindex/google @llamaindex/workflow
{{< tabpane persist=header >}}
{{< tab header="LangChain" lang="js" >}}
import { ChatVertexAI } from "@langchain/google-vertexai";
import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
import { ToolboxClient } from "@toolbox-sdk/core";
import { tool } from "@langchain/core/tools";
import { createReactAgent } from "@langchain/langgraph/prebuilt";
@@ -336,7 +336,7 @@ const queries = [
];
async function runApplication() {
const model = new ChatVertexAI({
const model = new ChatGoogleGenerativeAI({
model: "gemini-2.0-flash",
});

View File

@@ -105,7 +105,7 @@ In this section, we will download Toolbox, configure our tools in a
<!-- {x-release-please-start-version} -->
```bash
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/$OS/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/$OS/toolbox
```
<!-- {x-release-please-end} -->

View File

@@ -62,19 +62,19 @@ to expose your developer assistant tools to a Firestore instance:
<!-- {x-release-please-start-version} -->
{{< tabpane persist=header >}}
{{< tab header="linux/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/linux/amd64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/linux/amd64/toolbox
{{< /tab >}}
{{< tab header="darwin/arm64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/darwin/arm64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/darwin/arm64/toolbox
{{< /tab >}}
{{< tab header="darwin/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/darwin/amd64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/darwin/amd64/toolbox
{{< /tab >}}
{{< tab header="windows/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/windows/amd64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/windows/amd64/toolbox
{{< /tab >}}
{{< /tabpane >}}
<!-- {x-release-please-end} -->

View File

@@ -46,19 +46,19 @@ to expose your developer assistant tools to a Looker instance:
<!-- {x-release-please-start-version} -->
{{< tabpane persist=header >}}
{{< tab header="linux/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/linux/amd64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/linux/amd64/toolbox
{{< /tab >}}
{{< tab header="darwin/arm64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/darwin/arm64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/darwin/arm64/toolbox
{{< /tab >}}
{{< tab header="darwin/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/darwin/amd64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/darwin/amd64/toolbox
{{< /tab >}}
{{< tab header="windows/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/windows/amd64/toolbox.exe
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/windows/amd64/toolbox.exe
{{< /tab >}}
{{< /tabpane >}}
<!-- {x-release-please-end} -->
@@ -263,11 +263,12 @@ The following tools are available to the LLM:
1. **get_measures**: list the measures in a given explore
1. **get_filters**: list the filters in a given explore
1. **get_parameters**: list the parameters in a given explore
1. **query**: Run a query
1. **query**: Run a query and return the data
1. **query_sql**: Return the SQL generated by Looker for a query
1. **query_url**: Return a link to the query in Looker for further exploration
1. **get_looks**: Return the saved Looks that match a title or description
1. **run_look**: Run a saved Look and return the data
1. **make_look**: Create a saved Look in Looker and return the URL
{{< notice note >}}
Prebuilt tools are pre-1.0, so expect some tool changes between versions. LLMs

View File

@@ -0,0 +1,289 @@
---
title: SQL Server using MCP
type: docs
weight: 2
description: "Connect your IDE to SQL Server using Toolbox."
---
[Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) is an open protocol for connecting Large Language Models (LLMs) to data sources like SQL Server. This guide covers how to use [MCP Toolbox for Databases][toolbox] to expose your developer assistant tools to a SQL Server instance:
* [Cursor][cursor]
* [Windsurf][windsurf] (Codium)
* [Visual Studio Code][vscode] (Copilot)
* [Cline][cline] (VS Code extension)
* [Claude desktop][claudedesktop]
* [Claude code][claudecode]
* [Gemini CLI][geminicli]
* [Gemini Code Assist][geminicodeassist]
[toolbox]: https://github.com/googleapis/genai-toolbox
[cursor]: #configure-your-mcp-client
[windsurf]: #configure-your-mcp-client
[vscode]: #configure-your-mcp-client
[cline]: #configure-your-mcp-client
[claudedesktop]: #configure-your-mcp-client
[claudecode]: #configure-your-mcp-client
[geminicli]: #configure-your-mcp-client
[geminicodeassist]: #configure-your-mcp-client
## Set up the database
1. [Create or select a SQL Server instance.](https://www.microsoft.com/en-us/sql-server/sql-server-downloads)
## Install MCP Toolbox
1. Download the latest version of Toolbox as a binary. Select the [correct binary](https://github.com/googleapis/genai-toolbox/releases) corresponding to your OS and CPU architecture. You are required to use Toolbox version V0.10.0+:
<!-- {x-release-please-start-version} -->
{{< tabpane persist=header >}}
{{< tab header="linux/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/linux/amd64/toolbox
{{< /tab >}}
{{< tab header="darwin/arm64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/darwin/arm64/toolbox
{{< /tab >}}
{{< tab header="darwin/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/darwin/amd64/toolbox
{{< /tab >}}
{{< tab header="windows/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/windows/amd64/toolbox.exe
{{< /tab >}}
{{< /tabpane >}}
<!-- {x-release-please-end} -->
1. Make the binary executable:
```bash
chmod +x toolbox
```
1. Verify the installation:
```bash
./toolbox --version
```
## Configure your MCP Client
{{< tabpane text=true >}}
{{% tab header="Claude code" lang="en" %}}
1. Install [Claude Code](https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/overview).
1. Create a `.mcp.json` file in your project root if it doesn't exist.
1. Add the following configuration, replace the environment variables with your values, and save:
```json
{
"mcpServers": {
"sqlserver": {
"command": "./PATH/TO/toolbox",
"args": ["--prebuilt","mssql","--stdio"],
"env": {
"MSSQL_HOST": "",
"MSSQL_PORT": "",
"MSSQL_DATABASE": "",
"MSSQL_USER": "",
"MSSQL_PASSWORD": ""
}
}
}
}
```
1. Restart Claude code to apply the new configuration.
{{% /tab %}}
{{% tab header="Claude desktop" lang="en" %}}
1. Open [Claude desktop](https://claude.ai/download) and navigate to Settings.
1. Under the Developer tab, tap Edit Config to open the configuration file.
1. Add the following configuration, replace the environment variables with your values, and save:
```json
{
"mcpServers": {
"sqlserver": {
"command": "./PATH/TO/toolbox",
"args": ["--prebuilt","mssql","--stdio"],
"env": {
"MSSQL_HOST": "",
"MSSQL_PORT": "",
"MSSQL_DATABASE": "",
"MSSQL_USER": "",
"MSSQL_PASSWORD": ""
}
}
}
}
```
1. Restart Claude desktop.
1. From the new chat screen, you should see a hammer (MCP) icon appear with the new MCP server available.
{{% /tab %}}
{{% tab header="Cline" lang="en" %}}
1. Open the [Cline](https://github.com/cline/cline) extension in VS Code and tap the **MCP Servers** icon.
1. Tap Configure MCP Servers to open the configuration file.
1. Add the following configuration, replace the environment variables with your values, and save:
```json
{
"mcpServers": {
"sqlserver": {
"command": "./PATH/TO/toolbox",
"args": ["--prebuilt","mssql","--stdio"],
"env": {
"MSSQL_HOST": "",
"MSSQL_PORT": "",
"MSSQL_DATABASE": "",
"MSSQL_USER": "",
"MSSQL_PASSWORD": ""
}
}
}
}
```
1. You should see a green active status after the server is successfully connected.
{{% /tab %}}
{{% tab header="Cursor" lang="en" %}}
1. Create a `.cursor` directory in your project root if it doesn't exist.
1. Create a `.cursor/mcp.json` file if it doesn't exist and open it.
1. Add the following configuration, replace the environment variables with your values, and save:
```json
{
"mcpServers": {
"sqlserver": {
"command": "./PATH/TO/toolbox",
"args": ["--prebuilt","mssql","--stdio"],
"env": {
"MSSQL_HOST": "",
"MSSQL_PORT": "",
"MSSQL_DATABASE": "",
"MSSQL_USER": "",
"MSSQL_PASSWORD": ""
}
}
}
}
```
1. Open [Cursor](https://www.cursor.com/) and navigate to **Settings > Cursor Settings > MCP**. You should see a green active status after the server is successfully connected.
{{% /tab %}}
{{% tab header="Visual Studio Code (Copilot)" lang="en" %}}
1. Open [VS Code](https://code.visualstudio.com/docs/copilot/overview) and create a `.vscode` directory in your project root if it doesn't exist.
1. Create a `.vscode/mcp.json` file if it doesn't exist and open it.
1. Add the following configuration, replace the environment variables with your values, and save:
```json
{
"mcp" : {
"servers": {
"cloud-sql-sqlserver": {
"command": "./PATH/TO/toolbox",
"args": ["--prebuilt","cloud-sql-mssql","--stdio"],
"env": {
"MSSQL_HOST": "",
"MSSQL_PORT": "",
"MSSQL_DATABASE": "",
"MSSQL_USER": "",
"MSSQL_PASSWORD": ""
}
}
}
}
}
```
{{% /tab %}}
{{% tab header="Windsurf" lang="en" %}}
1. Open [Windsurf](https://docs.codeium.com/windsurf) and navigate to the Cascade assistant.
1. Tap on the hammer (MCP) icon, then Configure to open the configuration file.
1. Add the following configuration, replace the environment variables with your values, and save:
```json
{
"mcpServers": {
"sqlserver": {
"command": "./PATH/TO/toolbox",
"args": ["--prebuilt","mssql","--stdio"],
"env": {
"MSSQL_HOST": "",
"MSSQL_PORT": "",
"MSSQL_DATABASE": "",
"MSSQL_USER": "",
"MSSQL_PASSWORD": ""
}
}
}
}
```
{{% /tab %}}
{{% tab header="Gemini CLI" lang="en" %}}
1. Install the [Gemini CLI](https://github.com/google-gemini/gemini-cli?tab=readme-ov-file#quickstart).
1. In your working directory, create a folder named `.gemini`. Within it, create a `settings.json` file.
1. Add the following configuration, replace the environment variables with your values, and then save:
```json
{
"mcpServers": {
"sqlserver": {
"command": "./PATH/TO/toolbox",
"args": ["--prebuilt","mssql","--stdio"],
"env": {
"MSSQL_HOST": "",
"MSSQL_PORT": "",
"MSSQL_DATABASE": "",
"MSSQL_USER": "",
"MSSQL_PASSWORD": ""
}
}
}
}
```
{{% /tab %}}
{{% tab header="Gemini Code Assist" lang="en" %}}
1. Install the [Gemini Code Assist](https://marketplace.visualstudio.com/items?itemName=Google.geminicodeassist) extension in Visual Studio Code.
1. Enable Agent Mode in Gemini Code Assist chat.
1. In your working directory, create a folder named `.gemini`. Within it, create a `settings.json` file.
1. Add the following configuration, replace the environment variables with your values, and then save:
```json
{
"mcpServers": {
"sqlserver": {
"command": "./PATH/TO/toolbox",
"args": ["--prebuilt","mssql","--stdio"],
"env": {
"MSSQL_HOST": "",
"MSSQL_PORT": "",
"MSSQL_DATABASE": "",
"MSSQL_USER": "",
"MSSQL_PASSWORD": ""
}
}
}
}
```
{{% /tab %}}
{{< /tabpane >}}
## Use Tools
Your AI tool is now connected to SQL Server using MCP. Try asking your AI assistant to list tables, create a table, or define and execute other SQL statements.
The following tools are available to the LLM:
1. **list_tables**: lists tables and descriptions
1. **execute_sql**: execute any SQL statement
{{< notice note >}}
Prebuilt tools are pre-1.0, so expect some tool changes between versions. LLMs will adapt to the tools available, so this shouldn't affect most users.
{{< /notice >}}

View File

@@ -0,0 +1,287 @@
---
title: MySQL using MCP
type: docs
weight: 2
description: "Connect your IDE to MySQL using Toolbox."
---
[Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) is an open protocol for connecting Large Language Models (LLMs) to data sources like MySQL. This guide covers how to use [MCP Toolbox for Databases][toolbox] to expose your developer assistant tools to a MySQL instance:
* [Cursor][cursor]
* [Windsurf][windsurf] (Codium)
* [Visual Studio Code][vscode] (Copilot)
* [Cline][cline] (VS Code extension)
* [Claude desktop][claudedesktop]
* [Claude code][claudecode]
* [Gemini CLI][geminicli]
* [Gemini Code Assist][geminicodeassist]
[toolbox]: https://github.com/googleapis/genai-toolbox
[cursor]: #configure-your-mcp-client
[windsurf]: #configure-your-mcp-client
[vscode]: #configure-your-mcp-client
[cline]: #configure-your-mcp-client
[claudedesktop]: #configure-your-mcp-client
[claudecode]: #configure-your-mcp-client
[geminicli]: #configure-your-mcp-client
[geminicodeassist]: #configure-your-mcp-client
## Set up the database
1. [Create or select a MySQL instance.](https://dev.mysql.com/downloads/installer/)
## Install MCP Toolbox
1. Download the latest version of Toolbox as a binary. Select the [correct binary](https://github.com/googleapis/genai-toolbox/releases) corresponding to your OS and CPU architecture. You are required to use Toolbox version V0.10.0+:
<!-- {x-release-please-start-version} -->
{{< tabpane persist=header >}}
{{< tab header="linux/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/linux/amd64/toolbox
{{< /tab >}}
{{< tab header="darwin/arm64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/darwin/arm64/toolbox
{{< /tab >}}
{{< tab header="darwin/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/darwin/amd64/toolbox
{{< /tab >}}
{{< tab header="windows/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/windows/amd64/toolbox.exe
{{< /tab >}}
{{< /tabpane >}}
<!-- {x-release-please-end} -->
1. Make the binary executable:
```bash
chmod +x toolbox
```
1. Verify the installation:
```bash
./toolbox --version
```
## Configure your MCP Client
{{< tabpane text=true >}}
{{% tab header="Claude code" lang="en" %}}
1. Install [Claude Code](https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/overview).
1. Create a `.mcp.json` file in your project root if it doesn't exist.
1. Add the following configuration, replace the environment variables with your values, and save:
```json
{
"mcpServers": {
"mysql": {
"command": "./PATH/TO/toolbox",
"args": ["--prebuilt", "mysql", "--stdio"],
"env": {
"MYSQL_HOST": "",
"MYSQL_PORT": "",
"MYSQL_DATABASE": "",
"MYSQL_USER": "",
"MYSQL_PASSWORD": ""
}
}
}
}
```
1. Restart Claude code to apply the new configuration.
{{% /tab %}}
{{% tab header="Claude desktop" lang="en" %}}
1. Open [Claude desktop](https://claude.ai/download) and navigate to Settings.
1. Under the Developer tab, tap Edit Config to open the configuration file.
1. Add the following configuration, replace the environment variables with your values, and save:
```json
{
"mcpServers": {
"mysql": {
"command": "./PATH/TO/toolbox",
"args": ["--prebuilt", "mysql", "--stdio"],
"env": {
"MYSQL_HOST": "",
"MYSQL_PORT": "",
"MYSQL_DATABASE": "",
"MYSQL_USER": "",
"MYSQL_PASSWORD": ""
}
}
}
}
```
1. Restart Claude desktop.
1. From the new chat screen, you should see a hammer (MCP) icon appear with the new MCP server available.
{{% /tab %}}
{{% tab header="Cline" lang="en" %}}
1. Open the [Cline](https://github.com/cline/cline) extension in VS Code and tap the **MCP Servers** icon.
1. Tap Configure MCP Servers to open the configuration file.
1. Add the following configuration, replace the environment variables with your values, and save:
```json
{
"mcpServers": {
"mysql": {
"command": "./PATH/TO/toolbox",
"args": ["--prebuilt", "mysql", "--stdio"],
"env": {
"MYSQL_HOST": "",
"MYSQL_PORT": "",
"MYSQL_DATABASE": "",
"MYSQL_USER": "",
"MYSQL_PASSWORD": ""
}
}
}
}
```
1. You should see a green active status after the server is successfully connected.
{{% /tab %}}
{{% tab header="Cursor" lang="en" %}}
1. Create a `.cursor` directory in your project root if it doesn't exist.
1. Create a `.cursor/mcp.json` file if it doesn't exist and open it.
1. Add the following configuration, replace the environment variables with your values, and save:
```json
{
"mcpServers": {
"mysql": {
"command": "./PATH/TO/toolbox",
"args": ["--prebuilt", "mysql", "--stdio"],
"env": {
"MYSQL_HOST": "",
"MYSQL_PORT": "",
"MYSQL_DATABASE": "",
"MYSQL_USER": "",
"MYSQL_PASSWORD": ""
}
}
}
}
```
1. Open [Cursor](https://www.cursor.com/) and navigate to **Settings > Cursor Settings > MCP**. You should see a green active status after the server is successfully connected.
{{% /tab %}}
{{% tab header="Visual Studio Code (Copilot)" lang="en" %}}
1. Open [VS Code](https://code.visualstudio.com/docs/copilot/overview) and create a `.vscode` directory in your project root if it doesn't exist.
1. Create a `.vscode/mcp.json` file if it doesn't exist and open it.
1. Add the following configuration, replace the environment variables with your values, and save:
```json
{
"mcpServers": {
"mysql": {
"command": "./PATH/TO/toolbox",
"args": ["--prebuilt","mysql","--stdio"],
"env": {
"MYSQL_HOST": "",
"MYSQL_PORT": "",
"MYSQL_DATABASE": "",
"MYSQL_USER": "",
"MYSQL_PASSWORD": ""
}
}
}
}
```
{{% /tab %}}
{{% tab header="Windsurf" lang="en" %}}
1. Open [Windsurf](https://docs.codeium.com/windsurf) and navigate to the Cascade assistant.
1. Tap on the hammer (MCP) icon, then Configure to open the configuration file.
1. Add the following configuration, replace the environment variables with your values, and save:
```json
{
"mcpServers": {
"mysql": {
"command": "./PATH/TO/toolbox",
"args": ["--prebuilt","mysql","--stdio"],
"env": {
"MYSQL_HOST": "",
"MYSQL_PORT": "",
"MYSQL_DATABASE": "",
"MYSQL_USER": "",
"MYSQL_PASSWORD": ""
}
}
}
}
```
{{% /tab %}}
{{% tab header="Gemini CLI" lang="en" %}}
1. Install the [Gemini CLI](https://github.com/google-gemini/gemini-cli?tab=readme-ov-file#quickstart).
1. In your working directory, create a folder named `.gemini`. Within it, create a `settings.json` file.
1. Add the following configuration, replace the environment variables with your values, and then save:
```json
{
"mcpServers": {
"mysql": {
"command": "./PATH/TO/toolbox",
"args": ["--prebuilt","mysql","--stdio"],
"env": {
"MYSQL_HOST": "",
"MYSQL_PORT": "",
"MYSQL_DATABASE": "",
"MYSQL_USER": "",
"MYSQL_PASSWORD": ""
}
}
}
}
```
{{% /tab %}}
{{% tab header="Gemini Code Assist" lang="en" %}}
1. Install the [Gemini Code Assist](https://marketplace.visualstudio.com/items?itemName=Google.geminicodeassist) extension in Visual Studio Code.
1. Enable Agent Mode in Gemini Code Assist chat.
1. In your working directory, create a folder named `.gemini`. Within it, create a `settings.json` file.
1. Add the following configuration, replace the environment variables with your values, and then save:
```json
{
"mcpServers": {
"mysql": {
"command": "./PATH/TO/toolbox",
"args": ["--prebuilt","mysql","--stdio"],
"env": {
"MYSQL_HOST": "",
"MYSQL_PORT": "",
"MYSQL_DATABASE": "",
"MYSQL_USER": "",
"MYSQL_PASSWORD": ""
}
}
}
}
```
{{% /tab %}}
{{< /tabpane >}}
## Use Tools
Your AI tool is now connected to MySQL using MCP. Try asking your AI assistant to list tables, create a table, or define and execute other SQL statements.
The following tools are available to the LLM:
1. **list_tables**: lists tables and descriptions
1. **execute_sql**: execute any SQL statement
{{< notice note >}}
Prebuilt tools are pre-1.0, so expect some tool changes between versions. LLMs will adapt to the tools available, so this shouldn't affect most users.
{{< /notice >}}

View File

@@ -52,19 +52,19 @@ Omni](https://cloud.google.com/alloydb/omni/current/docs/overview).
<!-- {x-release-please-start-version} -->
{{< tabpane persist=header >}}
{{< tab header="linux/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/linux/amd64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/linux/amd64/toolbox
{{< /tab >}}
{{< tab header="darwin/arm64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/darwin/arm64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/darwin/arm64/toolbox
{{< /tab >}}
{{< tab header="darwin/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/darwin/amd64/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/darwin/amd64/toolbox
{{< /tab >}}
{{< tab header="windows/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/windows/amd64/toolbox.exe
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/windows/amd64/toolbox.exe
{{< /tab >}}
{{< /tabpane >}}
<!-- {x-release-please-end} -->

View File

@@ -33,6 +33,14 @@ cluster][alloydb-free-trial].
- [`postgres-execute-sql`](../tools/postgres/postgres-execute-sql.md)
Run parameterized SQL statements in AlloyDB Postgres.
### Pre-built Configurations
- [AlloyDB using MCP](https://googleapis.github.io/genai-toolbox/how-to/connect-ide/alloydb_pg_mcp/)
Connect your IDE to AlloyDB using Toolbox.
- [AlloyDB Admin API using MCP](https://googleapis.github.io/genai-toolbox/how-to/connect-ide/alloydb_pg_admin_mcp/)
Create your AlloyDB database with MCP Toolbox.
## Requirements
### IAM Permissions

View File

@@ -54,6 +54,11 @@ avoiding full table scans or complex filters.
- [`bigquery-list-table-ids`](../tools/bigquery/bigquery-list-table-ids.md)
List tables in a given dataset.
### Pre-built Configurations
- [BigQuery using MCP](https://googleapis.github.io/genai-toolbox/how-to/connect-ide/bigquery_mcp/)
Connect your IDE to BigQuery using Toolbox.
## Requirements
### IAM Permissions

View File

@@ -27,6 +27,11 @@ to a database by following these instructions][csql-mssql-connect].
- [`mssql-execute-sql`](../tools/mssql/mssql-execute-sql.md)
Run parameterized SQL Server queries in Cloud SQL for SQL Server.
### Pre-built Configurations
- [Cloud SQL for SQL Server using MCP](https://googleapis.github.io/genai-toolbox/how-to/connect-ide/cloud_sql_mssql_mcp/)
Connect your IDE to Cloud SQL for SQL Server using Toolbox.
## Requirements
### IAM Permissions

View File

@@ -28,6 +28,11 @@ to a database by following these instructions][csql-mysql-quickstart].
- [`mysql-execute-sql`](../tools/mysql/mysql-execute-sql.md)
Run parameterized SQL queries in Cloud SQL for MySQL.
### Pre-built Configurations
- [Cloud SQL for MySQL using MCP](https://googleapis.github.io/genai-toolbox/how-to/connect-ide/cloud_sql_mysql_mcp/)
Connect your IDE to Cloud SQL for MySQL using Toolbox.
## Requirements
### IAM Permissions

View File

@@ -28,6 +28,12 @@ to a database by following these instructions][csql-pg-quickstart].
- [`postgres-execute-sql`](../tools/postgres/postgres-execute-sql.md)
Run parameterized SQL statements in PostgreSQL.
### Pre-built Configurations
- [Cloud SQL for Postgres using MCP](https://googleapis.github.io/genai-toolbox/how-to/connect-ide/cloud_sql_pg_mcp/)
Connect your IDE to Cloud SQL for Postgres using Toolbox.
## Requirements
### IAM Permissions

View File

@@ -1,73 +0,0 @@
---
title: DuckDB
linkTitle: DuckDB
type: docs
weight: 1
description: >
DuckDB is an in-process SQL OLAP database management system designed for analytical query processing.
---
## About
[DuckDB](https://duckdb.org/) is an embedded analytical database management system that runs in-process with the client application. It is optimized for analytical workloads, providing high performance for complex queries with minimal setup.
DuckDB has the following notable characteristics:
- In-process, serverless database engine
- Supports complex SQL queries for analytical processing
- Can operate on in-memory or persistent storage
- Zero-configuration - no external dependencies or server setup required
- Highly optimized for columnar data storage and query execution
For more details, refer to the [DuckDB Documentation](https://duckdb.org/).
## Available Tools
- [`duckdb-sql`](../tools/duckdb/duckdb-sql.md)
Execute pre-defined prepared SQL queries in DuckDB.
## Requirements
### Database File
To use DuckDB, you can either:
- Specify a file path for a persistent database stored on the filesystem
- Omit the file path to use an in-memory database
## Example
For a persistent DuckDB database:
```yaml
sources:
my-duckdb:
kind: "duckdb"
dbFilePath: "/path/to/database.db"
configuration:
memory_limit: "2GB"
threads: "4"
```
For an in-memory DuckDB database:
```yaml
sources:
my-duckdb-memory:
name: "my-duckdb-memory"
kind: "duckdb"
```
## Reference
### Configuration Fields
| **field** | **type** | **required** | **description** |
|-------------------|:-----------------:|:------------:|---------------------------------------------------------------------------------|
| kind | string | true | Must be "duckdb". |
| dbFilePath | string | false | Path to the DuckDB database file. Omit for an in-memory database. |
| configuration | map[string]string | false | Additional DuckDB configuration options (e.g., `memory_limit`, `threads`). |
For a complete list of available configuration options, refer to the [DuckDB Configuration Documentation](https://duckdb.org/docs/stable/configuration/overview.html#local-configuration-options).
For more details on the Go implementation, see the [go-duckdb package documentation](https://pkg.go.dev/github.com/scottlepp/go-duckdb#section-readme).

View File

@@ -23,6 +23,11 @@ reputation for reliability, feature robustness, and performance.
- [`postgres-execute-sql`](../tools/postgres/postgres-execute-sql.md)
Run parameterized SQL statements in PostgreSQL.
### Pre-built Configurations
- [PostgreSQL using MCP](https://googleapis.github.io/genai-toolbox/how-to/connect-ide/postgres_mcp/)
Connect your IDE to PostgreSQL using Toolbox.
## Requirements
### Database User

View File

@@ -31,6 +31,11 @@ the Google Cloud console][spanner-quickstart].
- [`spanner-execute-sql`](../tools/spanner/spanner-execute-sql.md)
Run structured and parameterized queries on Spanner.
### Pre-built Configurations
- [Spanner using MCP](https://googleapis.github.io/genai-toolbox/how-to/connect-ide/spanner_mcp/)
Connect your IDE to Spanner using Toolbox.
## Requirements
### IAM Permissions

View File

@@ -75,6 +75,12 @@ visible to the LLM.
[alloydb-psv]: https://cloud.google.com/alloydb/docs/parameterized-secure-views-overview
{{< notice tip >}} Make sure to enable the `parameterized_views` extension before running this tool. You can do so by running this command in the AlloyDB studio:
```sql
CREATE EXTENSION IF NOT EXISTS parameterized_views;
```
{{< /notice >}}
## Example
```yaml
@@ -95,7 +101,6 @@ tools:
- name: my_google_service
field: email
```
## Reference
| **field** | **type** | **required** | **description** |

View File

@@ -15,8 +15,9 @@ It's compatible with the following sources:
- [bigquery](../../sources/bigquery.md)
`bigquery-execute-sql` takes one input parameter `sql` and runs the sql
statement against the `source`.
`bigquery-execute-sql` takes a required `sql` input parameter and runs the SQL
statement against the configured `source`. It also supports an optional `dry_run`
parameter to validate a query without executing it.
## Example

View File

@@ -1,7 +0,0 @@
---
title: "DuckDB"
type: docs
weight: 1
description: >
Tools that work with DuckDB Sources.
---

View File

@@ -1,80 +0,0 @@
---
title: "duckdb-sql"
type: docs
weight: 1
description: >
Execute SQL statements against a DuckDB database using the DuckDB SQL tools configuration.
aliases:
- /resources/tools/duckdb-sql
---
## About
A `duckdb-sql` tool executes a pre-defined SQL statement against a [DuckDB](https://duckdb.org/) database. It is compatible with any DuckDB source configuration as defined in the [DuckDB source documentation](../../sources/duckdb.md).
The specified SQL statement is executed as a prepared statement, and parameters are inserted according to their position: e.g., `$1` is the first parameter, `$2` is the second, and so on. If template parameters are included, they are resolved before execution of the prepared statement.
DuckDB's SQL dialect closely follows the conventions of the PostgreSQL dialect, with a few exceptions listed in the [DuckDB PostgreSQL Compatibility documentation](https://duckdb.org/docs/stable/sql/dialect/postgresql_compatibility.html). For an introduction to DuckDB's SQL dialect, refer to the [DuckDB SQL Introduction](https://duckdb.org/docs/stable/sql/introduction).
### Concepts
DuckDB is a relational database management system (RDBMS). Data is stored in relations (tables), where each table is a named collection of rows. Each row in a table has the same set of named columns, each with a specific data type. Tables are stored within schemas, and a collection of schemas constitutes the entire database.
For more details, see the [DuckDB SQL Introduction](https://duckdb.org/docs/stable/sql/introduction).
## Example
> **Note:** This tool uses parameterized queries to prevent SQL injections. Query parameters can be used as substitutes for arbitrary expressions but cannot be used for identifiers, column names, table names, or other parts of the query.
```yaml
tools:
search-users:
kind: duckdb-sql
source: my-duckdb
description: Search users by name and age
statement: SELECT * FROM users WHERE name LIKE $1 AND age >= $2
parameters:
- name: name
type: string
description: The name to search for
- name: min_age
type: integer
description: Minimum age
```
## Example with Template Parameters
> **Note:** Template parameters allow direct modifications to the SQL statement, including identifiers, column names, and table names, which makes them more vulnerable to SQL injections. Using basic parameters (see above) is recommended for performance and safety. For more details, see the [templateParameters](../#template-parameters) section.
```yaml
tools:
list_table:
kind: duckdb-sql
source: my-duckdb
statement: |
SELECT * FROM {{.tableName}};
description: |
Use this tool to list all information from a specific table.
Example:
{{
"tableName": "flights",
}}
templateParameters:
- name: tableName
type: string
description: Table to select from
```
## Reference
### Configuration Fields
| **field** | **type** | **required** | **description** |
|--------------------|:-------------------------------:|:------------:|--------------------------------------------------------------------------------------------------------------------------------------------|
| kind | string | true | Must be "duckdb-sql". |
| source | string | true | Name of the DuckDB source configuration (see [DuckDB source documentation](../../sources/duckdb.md)). |
| description | string | true | Description of the tool that is passed to the LLM. |
| statement | string | true | The SQL statement to execute. |
| authRequired | []string | false | List of authentication requirements for the tool (if any). |
| parameters | [parameters](../#specifying-parameters) | false | List of parameters that will be inserted into the SQL statement |
| templateParameters | [templateParameters](../#template-parameters) | false | List of template parameters that will be inserted into the SQL statement before executing the prepared statement. |

View File

@@ -1,23 +1,24 @@
---
title: "looker-query-url"
title: "looker-make-look"
type: docs
weight: 1
description: >
"looker-query-url" generates a url link to a Looker explore.
"looker-make-look" generates a Looker look in the users personal folder in
Looker
aliases:
- /resources/tools/looker-query-url
- /resources/tools/looker-make-look
---
## About
The `looker-query-url` generates a url link to an explore in
Looker so the query can be investigated further.
The `looker-make-look` creates a saved Look in the user's
Looker personal folder.
It's compatible with the following sources:
- [looker](../../sources/looker.md)
`looker-query-url` takes eight parameters:
`looker-make-look` takes eight parameters:
1. the `model`
2. the `explore`
@@ -27,27 +28,38 @@ It's compatible with the following sources:
6. an optional set of `sorts`
7. an optional `limit`
8. an optional `tz`
9. an optional `vis_config`
10. the `title`
11. an optional `description`
## Example
```yaml
tools:
query_url:
kind: looker-query-url
make_look:
kind: looker-make-look
source: looker-source
description: |
Query URL Tool
make_look Tool
This tool is used to generate the URL of a query in Looker.
The user can then explore the query further inside Looker.
The tool also returns the query_id and slug. The parameters
are the same as the `looker-query` tool.
This tool creates a new look in Looker, using the query
parameters and the vis_config specified.
Most of the parameters are the same as the query_url
tool. In addition, there is a title and a description
that must be provided.
The newly created look will be created in the user's
personal folder in looker. The look name must be unique.
The result is a json document with a link to the newly
created look.
```
## Reference
| **field** | **type** | **required** | **description** |
|-------------|:------------------------------------------:|:------------:|--------------------------------------------------------------------------------------------------|
| kind | string | true | Must be "looker-query-url" |
| kind | string | true | Must be "looker-make-look" |
| source | string | true | Name of the source the SQL should execute on. |
| description | string | true | Description of the tool that is passed to the LLM. |

View File

@@ -0,0 +1,363 @@
---
title: "looker-query-url"
type: docs
weight: 1
description: >
"looker-query-url" generates a url link to a Looker explore.
aliases:
- /resources/tools/looker-query-url
---
## About
The `looker-query-url` generates a url link to an explore in
Looker so the query can be investigated further.
It's compatible with the following sources:
- [looker](../../sources/looker.md)
`looker-query-url` takes eight parameters:
1. the `model`
2. the `explore`
3. the `fields` list
4. an optional set of `filters`
5. an optional set of `pivots`
6. an optional set of `sorts`
7. an optional `limit`
8. an optional `tz`
9. an optional `vis_config`
## Example
```yaml
tools:
query_url:
kind: looker-query-url
source: looker-source
description: |
Query URL Tool
This tool is used to generate the URL of a query in Looker.
The user can then explore the query further inside Looker.
The tool also returns the query_id and slug. The parameters
are the same as the query tool with an additional vis_config
parameter.
The vis_config is optional. If provided, it will be used to
control the default visualization for the query. These are
some sample vis_config settings.
A bar chart -
{{
"defaults_version": 1,
"label_density": 25,
"legend_position": "center",
"limit_displayed_rows": false,
"ordering": "none",
"plot_size_by_field": false,
"point_style": "none",
"show_null_labels": false,
"show_silhouette": false,
"show_totals_labels": false,
"show_value_labels": false,
"show_view_names": false,
"show_x_axis_label": true,
"show_x_axis_ticks": true,
"show_y_axis_labels": true,
"show_y_axis_ticks": true,
"stacking": "normal",
"totals_color": "#808080",
"trellis": "",
"type": "looker_bar",
"x_axis_gridlines": false,
"x_axis_reversed": false,
"x_axis_scale": "auto",
"x_axis_zoom": true,
"y_axis_combined": true,
"y_axis_gridlines": true,
"y_axis_reversed": false,
"y_axis_scale_mode": "linear",
"y_axis_tick_density": "default",
"y_axis_tick_density_custom": 5,
"y_axis_zoom": true
}}
A column chart with an option advanced_vis_config -
{{
"advanced_vis_config": "{ chart: { type: 'pie', spacingBottom: 50, spacingLeft: 50, spacingRight: 50, spacingTop: 50, }, legend: { enabled: false, }, plotOptions: { pie: { dataLabels: { enabled: true, format: '\u003cb\u003e{key}\u003c/b\u003e\u003cspan style=\"font-weight: normal\"\u003e - {percentage:.2f}%\u003c/span\u003e', }, showInLegend: false, }, }, series: [], }",
"colors": [
"grey"
],
"defaults_version": 1,
"hidden_fields": [],
"label_density": 25,
"legend_position": "center",
"limit_displayed_rows": false,
"note_display": "below",
"note_state": "collapsed",
"note_text": "Unsold inventory only",
"ordering": "none",
"plot_size_by_field": false,
"point_style": "none",
"series_colors": {},
"show_null_labels": false,
"show_silhouette": false,
"show_totals_labels": false,
"show_value_labels": true,
"show_view_names": false,
"show_x_axis_label": true,
"show_x_axis_ticks": true,
"show_y_axis_labels": true,
"show_y_axis_ticks": true,
"stacking": "normal",
"totals_color": "#808080",
"trellis": "",
"type": "looker_column",
"x_axis_gridlines": false,
"x_axis_reversed": false,
"x_axis_scale": "auto",
"x_axis_zoom": true,
"y_axes": [],
"y_axis_combined": true,
"y_axis_gridlines": true,
"y_axis_reversed": false,
"y_axis_scale_mode": "linear",
"y_axis_tick_density": "default",
"y_axis_tick_density_custom": 5,
"y_axis_zoom": true
}}
A line chart -
{{
"defaults_version": 1,
"hidden_pivots": {},
"hidden_series": [],
"interpolation": "linear",
"label_density": 25,
"legend_position": "center",
"limit_displayed_rows": false,
"plot_size_by_field": false,
"point_style": "none",
"series_types": {},
"show_null_points": true,
"show_value_labels": false,
"show_view_names": false,
"show_x_axis_label": true,
"show_x_axis_ticks": true,
"show_y_axis_labels": true,
"show_y_axis_ticks": true,
"stacking": "",
"trellis": "",
"type": "looker_line",
"x_axis_gridlines": false,
"x_axis_reversed": false,
"x_axis_scale": "auto",
"y_axis_combined": true,
"y_axis_gridlines": true,
"y_axis_reversed": false,
"y_axis_scale_mode": "linear",
"y_axis_tick_density": "default",
"y_axis_tick_density_custom": 5
}}
An area chart -
{{
"defaults_version": 1,
"interpolation": "linear",
"label_density": 25,
"legend_position": "center",
"limit_displayed_rows": false,
"plot_size_by_field": false,
"point_style": "none",
"series_types": {},
"show_null_points": true,
"show_silhouette": false,
"show_totals_labels": false,
"show_value_labels": false,
"show_view_names": false,
"show_x_axis_label": true,
"show_x_axis_ticks": true,
"show_y_axis_labels": true,
"show_y_axis_ticks": true,
"stacking": "normal",
"totals_color": "#808080",
"trellis": "",
"type": "looker_area",
"x_axis_gridlines": false,
"x_axis_reversed": false,
"x_axis_scale": "auto",
"x_axis_zoom": true,
"y_axis_combined": true,
"y_axis_gridlines": true,
"y_axis_reversed": false,
"y_axis_scale_mode": "linear",
"y_axis_tick_density": "default",
"y_axis_tick_density_custom": 5,
"y_axis_zoom": true
}}
A scatter plot -
{{
"cluster_points": false,
"custom_quadrant_point_x": 5,
"custom_quadrant_point_y": 5,
"custom_value_label_column": "",
"custom_x_column": "",
"custom_y_column": "",
"defaults_version": 1,
"hidden_fields": [],
"hidden_pivots": {},
"hidden_points_if_no": [],
"hidden_series": [],
"interpolation": "linear",
"label_density": 25,
"legend_position": "center",
"limit_displayed_rows": false,
"limit_displayed_rows_values": {
"first_last": "first",
"num_rows": 0,
"show_hide": "hide"
},
"plot_size_by_field": false,
"point_style": "circle",
"quadrant_properties": {
"0": {
"color": "",
"label": "Quadrant 1"
},
"1": {
"color": "",
"label": "Quadrant 2"
},
"2": {
"color": "",
"label": "Quadrant 3"
},
"3": {
"color": "",
"label": "Quadrant 4"
}
},
"quadrants_enabled": false,
"series_labels": {},
"series_types": {},
"show_null_points": false,
"show_value_labels": false,
"show_view_names": true,
"show_x_axis_label": true,
"show_x_axis_ticks": true,
"show_y_axis_labels": true,
"show_y_axis_ticks": true,
"size_by_field": "roi",
"stacking": "normal",
"swap_axes": true,
"trellis": "",
"type": "looker_scatter",
"x_axis_gridlines": false,
"x_axis_reversed": false,
"x_axis_scale": "auto",
"x_axis_zoom": true,
"y_axes": [
{
"label": "",
"orientation": "bottom",
"series": [
{
"axisId": "Channel_0 - average_of_roi_first",
"id": "Channel_0 - average_of_roi_first",
"name": "Channel_0"
},
{
"axisId": "Channel_1 - average_of_roi_first",
"id": "Channel_1 - average_of_roi_first",
"name": "Channel_1"
},
{
"axisId": "Channel_2 - average_of_roi_first",
"id": "Channel_2 - average_of_roi_first",
"name": "Channel_2"
},
{
"axisId": "Channel_3 - average_of_roi_first",
"id": "Channel_3 - average_of_roi_first",
"name": "Channel_3"
},
{
"axisId": "Channel_4 - average_of_roi_first",
"id": "Channel_4 - average_of_roi_first",
"name": "Channel_4"
}
],
"showLabels": true,
"showValues": true,
"tickDensity": "custom",
"tickDensityCustom": 100,
"type": "linear",
"unpinAxis": false
}
],
"y_axis_combined": true,
"y_axis_gridlines": true,
"y_axis_reversed": false,
"y_axis_scale_mode": "linear",
"y_axis_tick_density": "default",
"y_axis_tick_density_custom": 5,
"y_axis_zoom": true
}}
A single value visualization -
{{
"defaults_version": 1,
"show_view_names": false,
"type": "looker_single_record"
}}
A Pie chart -
{{
"defaults_version": 1,
"label_density": 25,
"label_type": "labPer",
"legend_position": "center",
"limit_displayed_rows": false,
"ordering": "none",
"plot_size_by_field": false,
"point_style": "none",
"series_types": {},
"show_null_labels": false,
"show_silhouette": false,
"show_totals_labels": false,
"show_value_labels": false,
"show_view_names": false,
"show_x_axis_label": true,
"show_x_axis_ticks": true,
"show_y_axis_labels": true,
"show_y_axis_ticks": true,
"stacking": "",
"totals_color": "#808080",
"trellis": "",
"type": "looker_pie",
"value_labels": "legend",
"x_axis_gridlines": false,
"x_axis_reversed": false,
"x_axis_scale": "auto",
"y_axis_combined": true,
"y_axis_gridlines": true,
"y_axis_reversed": false,
"y_axis_scale_mode": "linear",
"y_axis_tick_density": "default",
"y_axis_tick_density_custom": 5
}}
The result is a JSON object with the id, slug, the url, and
the long_url.
```
## Reference
| **field** | **type** | **required** | **description** |
|-------------|:------------------------------------------:|:------------:|--------------------------------------------------------------------------------------------------|
| kind | string | true | Must be "looker-query-url" |
| source | string | true | Name of the source the SQL should execute on. |
| description | string | true | Description of the tool that is passed to the LLM. |

View File

@@ -114,7 +114,7 @@ In this section, we will download and install the Toolbox binary.
<!-- {x-release-please-start-version} -->
```bash
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
export VERSION="0.10.0"
export VERSION="0.11.0"
curl -O https://storage.googleapis.com/genai-toolbox/v$VERSION/$OS/toolbox
```
<!-- {x-release-please-end} -->

View File

@@ -179,7 +179,7 @@ to use BigQuery, and then run the Toolbox server.
<!-- {x-release-please-start-version} -->
```bash
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/$OS/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/$OS/toolbox
```
<!-- {x-release-please-end} -->

View File

@@ -98,7 +98,7 @@ In this section, we will download Toolbox, configure our tools in a
<!-- {x-release-please-start-version} -->
```bash
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/$OS/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/$OS/toolbox
```
<!-- {x-release-please-end} -->

View File

@@ -34,7 +34,7 @@ In this section, we will download Toolbox and run the Toolbox server.
<!-- {x-release-please-start-version} -->
```bash
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/$OS/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/$OS/toolbox
```
<!-- {x-release-please-end} -->

View File

@@ -34,7 +34,7 @@ In this section, we will download Toolbox and run the Toolbox server.
<!-- {x-release-please-start-version} -->
```bash
export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
curl -O https://storage.googleapis.com/genai-toolbox/v0.10.0/$OS/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.11.0/$OS/toolbox
```
<!-- {x-release-please-end} -->

26
go.mod
View File

@@ -2,7 +2,7 @@ module github.com/googleapis/genai-toolbox
go 1.24
toolchain go1.24.5
toolchain go1.24.6
require (
cloud.google.com/go/alloydbconn v1.15.4
@@ -11,7 +11,7 @@ require (
cloud.google.com/go/cloudsqlconn v1.17.3
cloud.google.com/go/dataplex v1.26.0
cloud.google.com/go/firestore v1.18.0
cloud.google.com/go/spanner v1.83.0
cloud.google.com/go/spanner v1.84.1
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.29.0
github.com/couchbase/gocb/v2 v2.10.1
@@ -31,10 +31,10 @@ require (
github.com/looker-open-source/sdk-codegen/go v0.25.10
github.com/microsoft/go-mssqldb v1.9.2
github.com/neo4j/neo4j-go-driver/v5 v5.28.1
github.com/redis/go-redis/v9 v9.11.0
github.com/redis/go-redis/v9 v9.12.0
github.com/spf13/cobra v1.9.1
github.com/thlib/go-timezone-local v0.0.7
github.com/valkey-io/valkey-go v1.0.63
github.com/valkey-io/valkey-go v1.0.64
go.mongodb.org/mongo-driver v1.17.4
go.opentelemetry.io/contrib/propagators/autoprop v0.62.0
go.opentelemetry.io/otel v1.37.0
@@ -45,26 +45,19 @@ require (
go.opentelemetry.io/otel/sdk/metric v1.37.0
go.opentelemetry.io/otel/trace v1.37.0
golang.org/x/oauth2 v0.30.0
google.golang.org/api v0.244.0
google.golang.org/api v0.246.0
modernc.org/sqlite v1.38.2
)
require (
github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/duckdb/duckdb-go-bindings v0.1.17 // indirect
github.com/duckdb/duckdb-go-bindings/darwin-amd64 v0.1.12 // indirect
github.com/duckdb/duckdb-go-bindings/darwin-arm64 v0.1.12 // indirect
github.com/duckdb/duckdb-go-bindings/linux-amd64 v0.1.12 // indirect
github.com/duckdb/duckdb-go-bindings/linux-arm64 v0.1.12 // indirect
github.com/duckdb/duckdb-go-bindings/windows-amd64 v0.1.12 // indirect
github.com/marcboeker/go-duckdb/arrowmapping v0.0.10 // indirect
github.com/marcboeker/go-duckdb/mapping v0.0.11 // indirect
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
gonum.org/v1/gonum v0.16.0 // indirect
)
require (
cel.dev/expr v0.24.0 // indirect
cloud.google.com/go v0.121.2 // indirect
cloud.google.com/go v0.121.4 // indirect
cloud.google.com/go/alloydb v1.18.0 // indirect
cloud.google.com/go/auth v0.16.3 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
@@ -79,7 +72,6 @@ require (
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect
github.com/PuerkitoBio/goquery v1.10.3 // indirect
github.com/ajg/form v1.5.1 // indirect
github.com/apache/arrow-go/v18 v18.4.0 // indirect
github.com/apache/arrow/go/v15 v15.0.2 // indirect
github.com/cenkalti/backoff/v5 v5.0.2 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
@@ -100,7 +92,6 @@ require (
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-viper/mapstructure/v2 v2.3.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect
@@ -120,7 +111,6 @@ require (
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.11 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/marcboeker/go-duckdb/v2 v2.3.4
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
@@ -160,7 +150,7 @@ require (
golang.org/x/tools v0.34.0 // indirect
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 // indirect
google.golang.org/grpc v1.74.2 // indirect
google.golang.org/protobuf v1.36.6 // indirect

57
go.sum
View File

@@ -38,8 +38,8 @@ cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRY
cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM=
cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I=
cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY=
cloud.google.com/go v0.121.2 h1:v2qQpN6Dx9x2NmwrqlesOt3Ys4ol5/lFZ6Mg1B7OJCg=
cloud.google.com/go v0.121.2/go.mod h1:nRFlrHq39MNVWu+zESP2PosMWA0ryJw8KUBZ2iZpxbw=
cloud.google.com/go v0.121.4 h1:cVvUiY0sX0xwyxPwdSU2KsF9knOVmtRyAMt8xou0iTs=
cloud.google.com/go v0.121.4/go.mod h1:XEBchUiHFJbz4lKBZwYBDHV/rSyfFktk737TLDU089s=
cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4=
cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw=
cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E=
@@ -544,8 +544,8 @@ cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+
cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos=
cloud.google.com/go/spanner v1.44.0/go.mod h1:G8XIgYdOK+Fbcpbs7p2fiprDw4CaZX63whnSMLVBxjk=
cloud.google.com/go/spanner v1.45.0/go.mod h1:FIws5LowYz8YAE1J8fOS7DJup8ff7xJeetWEo5REA2M=
cloud.google.com/go/spanner v1.83.0 h1:AH3QIoSIa01l3WbeTppkwCEYFNK1AER6drcYhPmwhxY=
cloud.google.com/go/spanner v1.83.0/go.mod h1:QSWcjxszT0WRHNd8zyGI0WctrYA1N7j0yTFsWyol9Yw=
cloud.google.com/go/spanner v1.84.1 h1:ShH4Y3YeDtmHa55dFiSS3YtQ0dmCuP0okfAoHp/d68w=
cloud.google.com/go/spanner v1.84.1/go.mod h1:3GMEIjOcXINJSvb42H3M6TdlGCDzaCFpiiNQpjHPlCM=
cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM=
cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ=
cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0=
@@ -563,8 +563,8 @@ cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeL
cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s=
cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y=
cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4=
cloud.google.com/go/storage v1.53.0 h1:gg0ERZwL17pJ+Cz3cD2qS60w1WMDnwcm5YPAIQBHUAw=
cloud.google.com/go/storage v1.53.0/go.mod h1:7/eO2a/srr9ImZW9k5uufcNahT2+fPb8w5it1i5boaA=
cloud.google.com/go/storage v1.55.0 h1:NESjdAToN9u1tmhVqhXCaCwYBuvEhZLLv0gBr+2znf0=
cloud.google.com/go/storage v1.55.0/go.mod h1:ztSmTTwzsdXe5syLVS0YsbFxXuvEmEyZj7v7zChEmuY=
cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w=
cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I=
cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4=
@@ -674,20 +674,14 @@ github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM=
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/apache/arrow-go/v18 v18.4.0 h1:/RvkGqH517iY8bZKc4FD5/kkdwXJGjxf28JIXbJ/oB0=
github.com/apache/arrow-go/v18 v18.4.0/go.mod h1:Aawvwhj8x2jURIzD9Moy72cF0FyJXOpkYpdmGRHcw14=
github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0=
github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI=
github.com/apache/arrow/go/v15 v15.0.2 h1:60IliRbiyTWCWjERBCkO1W4Qun9svcYoZrSLcyOsMLE=
github.com/apache/arrow/go/v15 v15.0.2/go.mod h1:DGXsR3ajT524njufqf95822i+KTh+yea1jass9YXgjA=
github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU=
github.com/apache/thrift v0.22.0 h1:r7mTJdj51TMDe6RtcmNdQxgn9XcyfGDOzegMDRg47uc=
github.com/apache/thrift v0.22.0/go.mod h1:1e7J/O1Ae6ZQMTYdy9xa3w9k+XHWPfRvdPyJeynQ+/g=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
@@ -749,18 +743,6 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8Yc
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/duckdb/duckdb-go-bindings v0.1.17 h1:SjpRwrJ7v0vqnIvLeVFHlhuS72+Lp8xxQ5jIER2LZP4=
github.com/duckdb/duckdb-go-bindings v0.1.17/go.mod h1:pBnfviMzANT/9hi4bg+zW4ykRZZPCXlVuvBWEcZofkc=
github.com/duckdb/duckdb-go-bindings/darwin-amd64 v0.1.12 h1:8CLBnsq9YDhi2Gmt3sjSUeXxMzyMQAKefjqUy9zVPFk=
github.com/duckdb/duckdb-go-bindings/darwin-amd64 v0.1.12/go.mod h1:Ezo7IbAfB8NP7CqPIN8XEHKUg5xdRRQhcPPlCXImXYA=
github.com/duckdb/duckdb-go-bindings/darwin-arm64 v0.1.12 h1:wjO4I0GhMh2xIpiUgRpzuyOT4KxXLoUS/rjU7UUVvCE=
github.com/duckdb/duckdb-go-bindings/darwin-arm64 v0.1.12/go.mod h1:eS7m/mLnPQgVF4za1+xTyorKRBuK0/BA44Oy6DgrGXI=
github.com/duckdb/duckdb-go-bindings/linux-amd64 v0.1.12 h1:HzKQi2C+1jzmwANsPuYH6x9Sfw62SQTjNAEq3OySKFI=
github.com/duckdb/duckdb-go-bindings/linux-amd64 v0.1.12/go.mod h1:1GOuk1PixiESxLaCGFhag+oFi7aP+9W8byymRAvunBk=
github.com/duckdb/duckdb-go-bindings/linux-arm64 v0.1.12 h1:YGSR7AFLw2gJ7IbgLE6DkKYmgKv1LaRSd/ZKF1yh2oE=
github.com/duckdb/duckdb-go-bindings/linux-arm64 v0.1.12/go.mod h1:o7crKMpT2eOIi5/FY6HPqaXcvieeLSqdXXaXbruGX7w=
github.com/duckdb/duckdb-go-bindings/windows-amd64 v0.1.12 h1:2aduW6fnFnT2Q45PlIgHbatsPOxV9WSZ5B2HzFfxaxA=
github.com/duckdb/duckdb-go-bindings/windows-amd64 v0.1.12/go.mod h1:IlOhJdVKUJCAPj3QsDszUo8DVdvp1nBFp4TUJVdw99s=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
@@ -836,8 +818,6 @@ github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAu
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk=
github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
@@ -1021,7 +1001,6 @@ github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4=
github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE=
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
@@ -1046,12 +1025,6 @@ github.com/looker-open-source/sdk-codegen/go v0.25.10/go.mod h1:YM/IYSsTPk7I54j4
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=
github.com/marcboeker/go-duckdb/arrowmapping v0.0.10 h1:G1W+GVnUefR8uy7jHdNO+CRMsmFG5mFPIHVAespfFCA=
github.com/marcboeker/go-duckdb/arrowmapping v0.0.10/go.mod h1:jccUb8TYD0p5TsEEeN4SXuslNJHo23QaKOqKD+U6uFU=
github.com/marcboeker/go-duckdb/mapping v0.0.11 h1:fusN1b1l7Myxafifp596I6dNLNhN5Uv/rw31qAqBwqw=
github.com/marcboeker/go-duckdb/mapping v0.0.11/go.mod h1:aYBjFLgfKO0aJIbDtXPiaL5/avRQISveX/j9tMf9JhU=
github.com/marcboeker/go-duckdb/v2 v2.3.4 h1:o98wrefPbH0IdJRix4pF0+jZiXoFQ+FSR8InMsCUZD0=
github.com/marcboeker/go-duckdb/v2 v2.3.4/go.mod h1:8adNrftF4Ye29XMrpIl5NYNosTVsZu1mz3C82WdVvrk=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
@@ -1059,9 +1032,7 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/microsoft/go-mssqldb v1.9.2 h1:nY8TmFMQOHpm2qVWo6y4I2mAmVdZqlGiMGAYt64Ibbs=
github.com/microsoft/go-mssqldb v1.9.2/go.mod h1:GBbW9ASTiDC+mpgWDGKdm3FnFLTUsLYN3iFL90lQ+PA=
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 h1:AMFGa4R4MiIpspGNG7Z948v4n35fFGB3RR3G/ry4FWs=
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY=
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 h1:+n/aFZefKZp7spd8DFdX7uMikMLXX4oubIzJF4kv/wI=
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
@@ -1098,8 +1069,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
github.com/redis/go-redis/v9 v9.11.0 h1:E3S08Gl/nJNn5vkxd2i78wZxWAPNZgUNTp8WIJUAiIs=
github.com/redis/go-redis/v9 v9.11.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
github.com/redis/go-redis/v9 v9.12.0 h1:XlVPGlflh4nxfhsNXPA8Qp6EmEfTo0rp8oaBzPipXnU=
github.com/redis/go-redis/v9 v9.12.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
@@ -1141,8 +1112,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/thlib/go-timezone-local v0.0.7 h1:fX8zd3aJydqLlTs/TrROrIIdztzsdFV23OzOQx31jII=
github.com/thlib/go-timezone-local v0.0.7/go.mod h1:/Tnicc6m/lsJE0irFMA0LfIwTBo4QP7A8IfyIv4zZKI=
github.com/valkey-io/valkey-go v1.0.63 h1:LNlDTcUxy9jxrmGHSvd0s/NsgEmQbvREYvvBAHCIir0=
github.com/valkey-io/valkey-go v1.0.63/go.mod h1:bHmwjIEOrGq/ubOJfh5uMRs7Xj6mV3mQ/ZXUbmqpjqY=
github.com/valkey-io/valkey-go v1.0.64 h1:3u4+b6D6zs9JQs254TLy4LqitCMHHr9XorP9GGk7XY4=
github.com/valkey-io/valkey-go v1.0.64/go.mod h1:bHmwjIEOrGq/ubOJfh5uMRs7Xj6mV3mQ/ZXUbmqpjqY=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
@@ -1696,8 +1667,8 @@ google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/
google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI=
google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0=
google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg=
google.golang.org/api v0.244.0 h1:lpkP8wVibSKr++NCD36XzTk/IzeKJ3klj7vbj+XU5pE=
google.golang.org/api v0.244.0/go.mod h1:dMVhVcylamkirHdzEBAIQWUCgqY885ivNeZYd7VAVr8=
google.golang.org/api v0.246.0 h1:H0ODDs5PnMZVZAEtdLMn2Ul2eQi7QNjqM2DIFp8TlTM=
google.golang.org/api v0.246.0/go.mod h1:dMVhVcylamkirHdzEBAIQWUCgqY885ivNeZYd7VAVr8=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -1840,8 +1811,8 @@ google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOl
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s=
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0 h1:0UOBWO4dC+e51ui0NFKSPbkHHiQ4TmrEfEZMLDyRmY8=
google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0/go.mod h1:8ytArBbtOy2xfht+y2fqKd5DRDJRUQhqbyEnQ4bDChs=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 h1:MAKi5q709QWfnkkpNQ0M12hYJ1+e8qYVDyowc4U1XZM=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=

View File

@@ -72,26 +72,36 @@ tools:
SELECT
ti.schema_name,
ti.table_name AS object_name,
json_build_object(
'schema_name', ti.schema_name,
'object_name', ti.table_name,
'object_type', CASE ti.object_kind
WHEN 'r' THEN 'TABLE'
WHEN 'p' THEN 'PARTITIONED TABLE'
ELSE ti.object_kind::text -- Should not happen due to WHERE clause
END,
'owner', ti.table_owner,
'comment', ti.table_comment,
'columns', COALESCE((SELECT json_agg(json_build_object('column_name',ci.column_name,'data_type',ci.data_type,'ordinal_position',ci.column_ordinal_position,'is_not_nullable',ci.is_not_nullable,'column_default',ci.column_default,'column_comment',ci.column_comment) ORDER BY ci.column_ordinal_position) FROM columns_info ci WHERE ci.table_oid = ti.table_oid), '[]'::json),
'constraints', COALESCE((SELECT json_agg(json_build_object('constraint_name',cons.constraint_name,'constraint_type',cons.constraint_type,'constraint_definition',cons.constraint_definition,'constraint_columns',cons.constraint_columns,'foreign_key_referenced_table',cons.foreign_key_referenced_table,'foreign_key_referenced_columns',cons.foreign_key_referenced_columns)) FROM constraints_info cons WHERE cons.table_oid = ti.table_oid), '[]'::json),
'indexes', COALESCE((SELECT json_agg(json_build_object('index_name',ii.index_name,'index_definition',ii.index_definition,'is_unique',ii.is_unique,'is_primary',ii.is_primary,'index_method',ii.index_method,'index_columns',ii.index_columns)) FROM indexes_info ii WHERE ii.table_oid = ti.table_oid), '[]'::json),
'triggers', COALESCE((SELECT json_agg(json_build_object('trigger_name',tri.trigger_name,'trigger_definition',tri.trigger_definition,'trigger_enabled_state',tri.trigger_enabled_state)) FROM triggers_info tri WHERE tri.table_oid = ti.table_oid), '[]'::json)
) AS object_details
CASE
WHEN $2 = 'simple' THEN
-- IF format is 'simple', return basic JSON
json_build_object('name', ti.table_name)
ELSE
json_build_object(
'schema_name', ti.schema_name,
'object_name', ti.table_name,
'object_type', CASE ti.object_kind
WHEN 'r' THEN 'TABLE'
WHEN 'p' THEN 'PARTITIONED TABLE'
ELSE ti.object_kind::text -- Should not happen due to WHERE clause
END,
'owner', ti.table_owner,
'comment', ti.table_comment,
'columns', COALESCE((SELECT json_agg(json_build_object('column_name',ci.column_name,'data_type',ci.data_type,'ordinal_position',ci.column_ordinal_position,'is_not_nullable',ci.is_not_nullable,'column_default',ci.column_default,'column_comment',ci.column_comment) ORDER BY ci.column_ordinal_position) FROM columns_info ci WHERE ci.table_oid = ti.table_oid), '[]'::json),
'constraints', COALESCE((SELECT json_agg(json_build_object('constraint_name',cons.constraint_name,'constraint_type',cons.constraint_type,'constraint_definition',cons.constraint_definition,'constraint_columns',cons.constraint_columns,'foreign_key_referenced_table',cons.foreign_key_referenced_table,'foreign_key_referenced_columns',cons.foreign_key_referenced_columns)) FROM constraints_info cons WHERE cons.table_oid = ti.table_oid), '[]'::json),
'indexes', COALESCE((SELECT json_agg(json_build_object('index_name',ii.index_name,'index_definition',ii.index_definition,'is_unique',ii.is_unique,'is_primary',ii.is_primary,'index_method',ii.index_method,'index_columns',ii.index_columns)) FROM indexes_info ii WHERE ii.table_oid = ti.table_oid), '[]'::json),
'triggers', COALESCE((SELECT json_agg(json_build_object('trigger_name',tri.trigger_name,'trigger_definition',tri.trigger_definition,'trigger_enabled_state',tri.trigger_enabled_state)) FROM triggers_info tri WHERE tri.table_oid = ti.table_oid), '[]'::json)
)
END AS object_details
FROM table_info ti ORDER BY ti.schema_name, ti.table_name;
parameters:
- name: table_names
type: string
description: "Optional: A comma-separated list of table names. If empty, details for all tables in user-accessible schemas will be listed."
- name: output_format
type: string
description: "Optional: Use 'simple' to return table names only or use 'detailed' to return the full information schema."
default: "detailed"
toolsets:
cloud-sql-postgres-database-tools:

View File

@@ -123,7 +123,313 @@ tools:
This tool is used to generate the URL of a query in Looker.
The user can then explore the query further inside Looker.
The tool also returns the query_id and slug. The parameters
are the same as the query tool.
are the same as the query tool with an additional vis_config
parameter.
The vis_config is optional. If provided, it will be used to
control the default visualization for the query. These are
some sample vis_config settings.
A bar chart -
{{
"defaults_version": 1,
"label_density": 25,
"legend_position": "center",
"limit_displayed_rows": false,
"ordering": "none",
"plot_size_by_field": false,
"point_style": "none",
"show_null_labels": false,
"show_silhouette": false,
"show_totals_labels": false,
"show_value_labels": false,
"show_view_names": false,
"show_x_axis_label": true,
"show_x_axis_ticks": true,
"show_y_axis_labels": true,
"show_y_axis_ticks": true,
"stacking": "normal",
"totals_color": "#808080",
"trellis": "",
"type": "looker_bar",
"x_axis_gridlines": false,
"x_axis_reversed": false,
"x_axis_scale": "auto",
"x_axis_zoom": true,
"y_axis_combined": true,
"y_axis_gridlines": true,
"y_axis_reversed": false,
"y_axis_scale_mode": "linear",
"y_axis_tick_density": "default",
"y_axis_tick_density_custom": 5,
"y_axis_zoom": true
}}
A column chart with an option advanced_vis_config -
{{
"advanced_vis_config": "{ chart: { type: 'pie', spacingBottom: 50, spacingLeft: 50, spacingRight: 50, spacingTop: 50, }, legend: { enabled: false, }, plotOptions: { pie: { dataLabels: { enabled: true, format: '\u003cb\u003e{key}\u003c/b\u003e\u003cspan style=\"font-weight: normal\"\u003e - {percentage:.2f}%\u003c/span\u003e', }, showInLegend: false, }, }, series: [], }",
"colors": [
"grey"
],
"defaults_version": 1,
"hidden_fields": [],
"label_density": 25,
"legend_position": "center",
"limit_displayed_rows": false,
"note_display": "below",
"note_state": "collapsed",
"note_text": "Unsold inventory only",
"ordering": "none",
"plot_size_by_field": false,
"point_style": "none",
"series_colors": {},
"show_null_labels": false,
"show_silhouette": false,
"show_totals_labels": false,
"show_value_labels": true,
"show_view_names": false,
"show_x_axis_label": true,
"show_x_axis_ticks": true,
"show_y_axis_labels": true,
"show_y_axis_ticks": true,
"stacking": "normal",
"totals_color": "#808080",
"trellis": "",
"type": "looker_column",
"x_axis_gridlines": false,
"x_axis_reversed": false,
"x_axis_scale": "auto",
"x_axis_zoom": true,
"y_axes": [],
"y_axis_combined": true,
"y_axis_gridlines": true,
"y_axis_reversed": false,
"y_axis_scale_mode": "linear",
"y_axis_tick_density": "default",
"y_axis_tick_density_custom": 5,
"y_axis_zoom": true
}}
A line chart -
{{
"defaults_version": 1,
"hidden_pivots": {},
"hidden_series": [],
"interpolation": "linear",
"label_density": 25,
"legend_position": "center",
"limit_displayed_rows": false,
"plot_size_by_field": false,
"point_style": "none",
"series_types": {},
"show_null_points": true,
"show_value_labels": false,
"show_view_names": false,
"show_x_axis_label": true,
"show_x_axis_ticks": true,
"show_y_axis_labels": true,
"show_y_axis_ticks": true,
"stacking": "",
"trellis": "",
"type": "looker_line",
"x_axis_gridlines": false,
"x_axis_reversed": false,
"x_axis_scale": "auto",
"y_axis_combined": true,
"y_axis_gridlines": true,
"y_axis_reversed": false,
"y_axis_scale_mode": "linear",
"y_axis_tick_density": "default",
"y_axis_tick_density_custom": 5
}}
An area chart -
{{
"defaults_version": 1,
"interpolation": "linear",
"label_density": 25,
"legend_position": "center",
"limit_displayed_rows": false,
"plot_size_by_field": false,
"point_style": "none",
"series_types": {},
"show_null_points": true,
"show_silhouette": false,
"show_totals_labels": false,
"show_value_labels": false,
"show_view_names": false,
"show_x_axis_label": true,
"show_x_axis_ticks": true,
"show_y_axis_labels": true,
"show_y_axis_ticks": true,
"stacking": "normal",
"totals_color": "#808080",
"trellis": "",
"type": "looker_area",
"x_axis_gridlines": false,
"x_axis_reversed": false,
"x_axis_scale": "auto",
"x_axis_zoom": true,
"y_axis_combined": true,
"y_axis_gridlines": true,
"y_axis_reversed": false,
"y_axis_scale_mode": "linear",
"y_axis_tick_density": "default",
"y_axis_tick_density_custom": 5,
"y_axis_zoom": true
}}
A scatter plot -
{{
"cluster_points": false,
"custom_quadrant_point_x": 5,
"custom_quadrant_point_y": 5,
"custom_value_label_column": "",
"custom_x_column": "",
"custom_y_column": "",
"defaults_version": 1,
"hidden_fields": [],
"hidden_pivots": {},
"hidden_points_if_no": [],
"hidden_series": [],
"interpolation": "linear",
"label_density": 25,
"legend_position": "center",
"limit_displayed_rows": false,
"limit_displayed_rows_values": {
"first_last": "first",
"num_rows": 0,
"show_hide": "hide"
},
"plot_size_by_field": false,
"point_style": "circle",
"quadrant_properties": {
"0": {
"color": "",
"label": "Quadrant 1"
},
"1": {
"color": "",
"label": "Quadrant 2"
},
"2": {
"color": "",
"label": "Quadrant 3"
},
"3": {
"color": "",
"label": "Quadrant 4"
}
},
"quadrants_enabled": false,
"series_labels": {},
"series_types": {},
"show_null_points": false,
"show_value_labels": false,
"show_view_names": true,
"show_x_axis_label": true,
"show_x_axis_ticks": true,
"show_y_axis_labels": true,
"show_y_axis_ticks": true,
"size_by_field": "roi",
"stacking": "normal",
"swap_axes": true,
"trellis": "",
"type": "looker_scatter",
"x_axis_gridlines": false,
"x_axis_reversed": false,
"x_axis_scale": "auto",
"x_axis_zoom": true,
"y_axes": [
{
"label": "",
"orientation": "bottom",
"series": [
{
"axisId": "Channel_0 - average_of_roi_first",
"id": "Channel_0 - average_of_roi_first",
"name": "Channel_0"
},
{
"axisId": "Channel_1 - average_of_roi_first",
"id": "Channel_1 - average_of_roi_first",
"name": "Channel_1"
},
{
"axisId": "Channel_2 - average_of_roi_first",
"id": "Channel_2 - average_of_roi_first",
"name": "Channel_2"
},
{
"axisId": "Channel_3 - average_of_roi_first",
"id": "Channel_3 - average_of_roi_first",
"name": "Channel_3"
},
{
"axisId": "Channel_4 - average_of_roi_first",
"id": "Channel_4 - average_of_roi_first",
"name": "Channel_4"
}
],
"showLabels": true,
"showValues": true,
"tickDensity": "custom",
"tickDensityCustom": 100,
"type": "linear",
"unpinAxis": false
}
],
"y_axis_combined": true,
"y_axis_gridlines": true,
"y_axis_reversed": false,
"y_axis_scale_mode": "linear",
"y_axis_tick_density": "default",
"y_axis_tick_density_custom": 5,
"y_axis_zoom": true
}}
A single value visualization -
{{
"defaults_version": 1,
"show_view_names": false,
"type": "looker_single_record"
}}
A Pie chart -
{{
"defaults_version": 1,
"label_density": 25,
"label_type": "labPer",
"legend_position": "center",
"limit_displayed_rows": false,
"ordering": "none",
"plot_size_by_field": false,
"point_style": "none",
"series_types": {},
"show_null_labels": false,
"show_silhouette": false,
"show_totals_labels": false,
"show_value_labels": false,
"show_view_names": false,
"show_x_axis_label": true,
"show_x_axis_ticks": true,
"show_y_axis_labels": true,
"show_y_axis_ticks": true,
"stacking": "",
"totals_color": "#808080",
"trellis": "",
"type": "looker_pie",
"value_labels": "legend",
"x_axis_gridlines": false,
"x_axis_reversed": false,
"x_axis_scale": "auto",
"y_axis_combined": true,
"y_axis_gridlines": true,
"y_axis_reversed": false,
"y_axis_scale_mode": "linear",
"y_axis_tick_density": "default",
"y_axis_tick_density_custom": 5
}}
The result is a JSON object with the id, slug, the url, and
the long_url.
@@ -158,6 +464,25 @@ tools:
the data in a JSON structure. It accepts the look_id as the
parameter.
make_look:
kind: looker-make-look
source: looker-source
description: |
make_look Tool
This tool creates a new look in Looker, using the query
parameters and the vis_config specified.
Most of the parameters are the same as the query_url
tool. In addition, there is a title and a description
that must be provided.
The newly created look will be created in the user's
personal folder in looker. The look name must be unique.
The result is a json document with a link to the newly
created look.
toolsets:
looker-tools:
- get_models
@@ -171,3 +496,4 @@ toolsets:
- query_url
- get_looks
- run_look
- make_look

View File

@@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AuthServices View</title>
<link rel="stylesheet" href="/ui/css/style.css">
</head>
<body>
<div id="navbar-container" data-active-nav="/ui/authServices"></div>
<aside class="second-nav">
<h4>My Auth Services</h4>
<div id="secondary-panel-content">
<p>Fetching Auth Services...</p>
</div>
</aside>
<div id="main-content-container"></div>
<script type="module" src="/ui/js/authServices.js"></script>
<script src="/ui/js/navbar.js"></script>
<script src="/ui/js/mainContent.js"></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
const navbarContainer = document.getElementById('navbar-container');
const activeNav = navbarContainer.getAttribute('data-active-nav');
renderNavbar('navbar-container', activeNav);
renderMainContent('main-content-container', 'auth-service-info-area')
});
</script>
</body>
</html>

View File

@@ -4,6 +4,7 @@
--text-secondary-gray: #6e6e6e;
--button-primary: var(--toolbox-blue);
--button-secondary: #616161;
--section-border: #e0e0e0;
}
body {
@@ -161,7 +162,19 @@ body {
background-color: var(--button-secondary)
}
.tool-button {
.btn--setup-gis {
background-color: white;
color: var(--text-primary-gray);
border: 2px solid var(--text-primary-gray);
}
.btn--externalDocs {
background-color: var(--button-secondary);
text-decoration: none;
display: inline-flex;
}
.second-nav-choice {
display: flex;
align-items: center;
padding: 12px;
@@ -379,7 +392,7 @@ body {
}
.auth-param-input {
background-color: #e0e0e0;
background-color: var(--section-border);
cursor: not-allowed;
}
@@ -513,6 +526,89 @@ body {
}
}
.auth-method-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
}
.auth-method-label {
font-weight: 500;
color: var(--text-primary-gray);
word-break: break-word;
}
.auth-helper-section {
border: 1px solid var(--section-border);
background-color: transparent;
padding: 16px;
border-radius: 8px;
margin-top: 20px;
width: 80%;
}
.auth-method-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.auth-method-details {
padding: 16px;
border: 1px solid var(--section-border);
border-radius: 4px;
margin-bottom: 16px;
background-color: #fff;
overflow-x: auto;
}
.auth-controls {
display: flex;
flex-direction: column;
gap: 16px;
align-items: flex-start;
}
.auth-input-row {
display: flex;
flex-direction: column;
gap: 6px;
& label {
font-size: 14px;
color: var(--text-primary-gray);
margin-bottom: 4px;
}
}
.auth-input {
padding: 8px 8px;
border: 1px solid #bdc1c6;
border-radius: 4px;
font-size: 14px;
width: 100%;
box-sizing: border-box;
&:focus {
outline: none;
border-color: var(--toolbox-blue);
box-shadow: 0 0 0 1px #1a73e8;
}
}
.auth-method-actions {
display: flex;
align-items: center;
gap: 12px;
}
.auth-instructions {
font-size: 0.8em;
margin-top: 5px;
color: var(--text-secondary-gray);
}
.tool-response {
margin: 20px 0 0 0;
@@ -577,4 +673,119 @@ body {
}
}
.toggle-details-tab {
background-color: transparent;
color: var(--toolbox-blue);
border: none;
padding: 8px 12px;
border-radius: 4px;
cursor: pointer;
font-size: 1em;
font-weight: bold;
&:hover {
opacity: 0.8;
}
}
.resource-instructions {
font-family: inherit;
color: var(--text-primary-gray);
padding: 24px;
background-color: #ffffff;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
margin: 16px 0;
li {
margin-bottom: 10px;
color: var(--text-secondary-gray);
}
}
.resource-title {
color: var(--toolbox-blue);
font-size: 24px;
font-weight: bold;
margin-top: 0;
margin-bottom: 16px;
}
.resource-subtitle {
color: var(--text-primary-gray);
font-size: 20px;
font-weight: normal;
margin-top: 24px;
margin-bottom: 12px;
}
.resource-intro,
.resource-description {
font-size: 16px;
line-height: 1.6;
color: var(--text-secondary-gray);
margin-bottom: 16px;
code {
background-color: #e9e9e9;
color: #c0392b;
padding: 2px 4px;
border-radius: 4px;
font-family: monospace;
font-size: 90%;
vertical-align: baseline;
}
}
.item-details-box {
margin-top: 20px;
overflow-x: auto;
h5 {
color: var(--toolbox-blue);
margin-top: 0;
margin-bottom: 10px;
font-weight: bold;
}
}
.item-details-list {
margin: 0;
padding: 0;
list-style: none;
}
.item-detail-entry {
display: grid;
grid-template-columns: minmax(120px, auto) 1fr;
gap: 6px;
padding: 4px 4px;
border-bottom: 1px solid #f0f0f0;
align-items: start;
&:last-child {
border-bottom: none;
}
}
.item-detail-key {
font-weight: bolder;
color: var(--toolbox-blue);
padding: 4px 6px;
padding-right: 8px;
}
.item-detail-value {
color: var(--text-primary-gray);
padding: 4px 6px;
}
.item-details-box.error p {
color: #d93025;
font-style: italic;
}
dl {
margin-block-start: 0;
margin-block-end: 0;
}

View File

@@ -17,7 +17,7 @@
const navbarContainer = document.getElementById('navbar-container');
const activeNav = navbarContainer.getAttribute('data-active-nav');
renderNavbar('navbar-container', activeNav);
renderMainContent('main-content-container', '')
renderMainContent('main-content-container', 'homepage-info', getHomepageInstructions())
});
</script>
</body>

View File

@@ -0,0 +1,173 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* Renders the Google Sign-In button using the GIS library.
* @param {string} toolId The ID of the tool.
* @param {string} clientId The Google OAuth Client ID.
* @param {string} authProfileName The name of the auth service in tools file.
*/
function renderGoogleSignInButton(toolId, clientId, authProfileName) {
const UNIQUE_ID_BASE = `${toolId}-${authProfileName}`;
const GIS_CONTAINER_ID = `gisContainer-${UNIQUE_ID_BASE}`;
const gisContainer = document.getElementById(GIS_CONTAINER_ID);
const setupGisBtn = document.querySelector(`#google-auth-details-${UNIQUE_ID_BASE} .btn--setup-gis`);
if (!gisContainer) {
console.error('GIS container not found:', GIS_CONTAINER_ID);
return;
}
if (!clientId) {
alert('Please enter an OAuth Client ID first.');
return;
}
// hide the setup button and show the container for the GIS button
if (setupGisBtn) setupGisBtn.style.display = 'none';
gisContainer.innerHTML = '';
gisContainer.style.display = 'flex';
if (window.google && window.google.accounts && window.google.accounts.id) {
try {
const handleResponse = (response) => handleCredentialResponse(response, toolId, authProfileName);
window.google.accounts.id.initialize({
client_id: clientId,
callback: handleResponse,
auto_select: false
});
window.google.accounts.id.renderButton(
gisContainer,
{ theme: "outline", size: "large", text: "signin_with" }
);
} catch (error) {
console.error("Error initializing Google Sign-In:", error);
alert("Error initializing Google Sign-In. Check the Client ID and browser console.");
gisContainer.innerHTML = '<p style="color: red;">Error loading Sign-In button.</p>';
if (setupGisBtn) setupGisBtn.style.display = '';
}
} else {
console.error("GIS library not fully loaded yet.");
alert("Google Identity Services library not ready. Please try again in a moment.");
gisContainer.innerHTML = '<p style="color: red;">GIS library not ready.</p>';
if (setupGisBtn) setupGisBtn.style.display = '';
}
}
/**
* Handles the credential response from the Google Sign-In library.
* @param {!CredentialResponse} response The credential response object from GIS.
* @param {string} toolId The ID of the tool.
* @param {string} authProfileName The name of the auth service in tools file.
*/
function handleCredentialResponse(response, toolId, authProfileName) {
console.debug("handleCredentialResponse called with:", { response, toolId, authProfileName });
const headersTextarea = document.getElementById(`headers-textarea-${toolId}`);
if (!headersTextarea) {
console.error('Headers textarea not found for toolId:', toolId);
return;
}
const UNIQUE_ID_BASE = `${toolId}-${authProfileName}`;
const setupGisBtn = document.querySelector(`#google-auth-details-${UNIQUE_ID_BASE} .setup-gis-btn`);
const gisContainer = document.getElementById(`gisContainer-${UNIQUE_ID_BASE}`);
if (response.credential) {
const idToken = response.credential;
try {
let currentHeaders = {};
if (headersTextarea.value) {
currentHeaders = JSON.parse(headersTextarea.value);
}
const HEADER_KEY = `${authProfileName}_token`;
currentHeaders[HEADER_KEY] = `${idToken}`;
headersTextarea.value = JSON.stringify(currentHeaders, null, 2);
if (gisContainer) gisContainer.style.display = 'none';
if (setupGisBtn) setupGisBtn.style.display = '';
} catch (e) {
alert('Headers are not valid JSON. Please correct and try again.');
console.error("Header JSON parse error:", e);
}
} else {
console.error("Error: No credential in response", response);
alert('Error: No ID Token received. Check console for details.');
if (gisContainer) gisContainer.style.display = 'none';
if (setupGisBtn) setupGisBtn.style.display = '';
}
}
// creates the Google Auth method dropdown
export function createGoogleAuthMethodItem(toolId, authProfileName) {
const UNIQUE_ID_BASE = `${toolId}-${authProfileName}`;
const item = document.createElement('div');
item.className = 'auth-method-item';
item.innerHTML = `
<div class="auth-method-header">
<span class="auth-method-label">Google ID Token (${authProfileName})</span>
<button class="toggle-details-tab">Auto Setup</button>
</div>
<div class="auth-method-details" id="google-auth-details-${UNIQUE_ID_BASE}" style="display: none;">
<div class="auth-controls">
<div class="auth-input-row">
<label for="clientIdInput-${UNIQUE_ID_BASE}">OAuth Client ID:</label>
<input type="text" id="clientIdInput-${UNIQUE_ID_BASE}" placeholder="Enter Client ID" class="auth-input">
</div>
<div class="auth-instructions">
<strong>Action Required:</strong> Add this URL (e.g., http://localhost:PORT) to the Client ID's <strong>Authorized JavaScript origins</strong> to avoid a 401 error. If using Google OAuth,
navigate to <a href="https://console.cloud.google.com/apis/credentials" target="_blank">Google Cloud Console</a> for this setting.
</div>
<div class="auth-method-actions">
<button class="btn btn--setup-gis">Continue</button>
<div id="gisContainer-${UNIQUE_ID_BASE}" class="auth-interactive-element gis-container" style="display: none;"></div>
</div>
</div>
</div>
`;
const toggleBtn = item.querySelector('.toggle-details-tab');
const detailsDiv = item.querySelector(`#google-auth-details-${UNIQUE_ID_BASE}`);
const setupGisBtn = item.querySelector('.btn--setup-gis');
const clientIdInput = item.querySelector(`#clientIdInput-${UNIQUE_ID_BASE}`);
const gisContainer = item.querySelector(`#gisContainer-${UNIQUE_ID_BASE}`);
toggleBtn.addEventListener('click', () => {
const isVisible = detailsDiv.style.display === 'flex';
detailsDiv.style.display = isVisible ? 'none' : 'flex';
toggleBtn.textContent = isVisible ? 'Auto Setup' : 'Close';
if (!isVisible) {
if (gisContainer) {
gisContainer.innerHTML = '';
gisContainer.style.display = 'none';
}
if (setupGisBtn) {
setupGisBtn.style.display = '';
}
}
});
setupGisBtn.addEventListener('click', () => {
const clientId = clientIdInput.value;
if (!clientId) {
alert('Please enter an OAuth Client ID first.');
return;
}
renderGoogleSignInButton(toolId, clientId, authProfileName);
});
return item;
}

View File

@@ -0,0 +1,27 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { loadItems } from './resourceDisplay.js';
document.addEventListener('DOMContentLoaded', () => {
const authServiceListArea = document.getElementById('secondary-panel-content');
const authServiceInfoArea = document.getElementById('auth-service-info-area');
if (!authServiceListArea || !authServiceInfoArea) {
console.error('Required DOM elements not found.');
return;
}
loadItems('authservices', authServiceListArea, authServiceInfoArea);
});

View File

@@ -67,7 +67,7 @@ function renderToolList(apiResponse, secondNavContent, toolDisplayArea) {
const button = document.createElement('button');
button.textContent = toolName;
button.dataset.toolname = toolName;
button.classList.add('tool-button');
button.classList.add('second-nav-choice');
button.addEventListener('click', (event) => handleToolClick(event, secondNavContent, toolDisplayArea));
li.appendChild(button);
ul.appendChild(li);
@@ -84,7 +84,7 @@ function renderToolList(apiResponse, secondNavContent, toolDisplayArea) {
function handleToolClick(event, secondNavContent, toolDisplayArea) {
const toolName = event.target.dataset.toolname;
if (toolName) {
const currentActive = secondNavContent.querySelector('.tool-button.active');
const currentActive = secondNavContent.querySelector('.second-nav-choice.active');
if (currentActive) {
currentActive.classList.remove('active');
}
@@ -128,6 +128,7 @@ async function fetchToolDetails(toolName, toolDisplayArea) {
id: toolName,
name: toolName,
description: toolObject.description || "No description provided.",
authRequired: toolObject.authRequired || [],
parameters: (toolObject.parameters || []).map(param => {
let inputType = 'text';
const apiType = param.type ? param.type.toLowerCase() : 'string';

View File

@@ -17,7 +17,7 @@
* @param {string} containerId The ID of the DOM element to inject the content into.
* @param {string} idString The id of the item inside the main content area.
*/
function renderMainContent(containerId, idString) {
function renderMainContent(containerId, idString, instructionContent) {
const mainContentContainer = document.getElementById(containerId);
if (!mainContentContainer) {
console.error(`Content container with ID "${containerId}" not found.`);
@@ -30,11 +30,51 @@ function renderMainContent(containerId, idString) {
<div class="top-bar">
</div>
<main class="content" ${idAttribute}">
<h1>Welcome to MCP Toolbox UI</h1>
<p>This is the main content area. Click a tab on the left to navigate.</p>
${instructionContent}
</main>
</div>
`;
mainContentContainer.innerHTML = contentHTML;
}
function getHomepageInstructions() {
return `
<div class="resource-instructions">
<h1 class="resource-title">Welcome to Toolbox UI</h1>
<p class="resource-intro">Toolbox UI is a built-in web interface that allows users to visually inspect and test out configured resources such as tools and toolsets. To get started, select a resource from the navigation tab to the left.</p>
<a href="https://googleapis.github.io/genai-toolbox/how-to/use-toolbox-ui/" class="btn btn--externalDocs" target="_blank" rel="noopener noreferrer">Toolbox UI Documentation</a>
</div>
`;
}
function getToolInstructions() {
return `
<div class="resource-instructions">
<h1 class="resource-title">Tools</h1>
<p class="resource-intro">To inspect and test a tool, please click on one of your tools to the left.</p>
<h2 class="resource-subtitle">What are Tools?</h2>
<p class="resource-description">
Tools define actions an agent can take, such as running a SQL statement or interacting with a source.
You can define Tools as a map in the <code>tools</code> section of your <code>tools.yaml</code> file. <br><br>
Some tools also use <strong>parameters</strong>. Parameters for each Tool will define what inputs the agent will need to provide to invoke them.
</p>
<a href="https://googleapis.github.io/genai-toolbox/resources/tools/" class="btn btn--externalDocs" target="_blank" rel="noopener noreferrer">Tools Documentation</a>
</div>
`;
}
function getToolsetInstructions() {
return `
<div class="resource-instructions">
<h1 class="resource-title">Toolsets</h1>
<p class="resource-intro">To inspect a specific toolset, please enter the name of a toolset and press search.</p>
<h2 class="resource-subtitle">What are Toolsets?</h2>
<p class="resource-description">
Toolsets define groups of tools an agent can access. You can define Toolsets as a map in the <code>toolsets</code> section of your <code>tools.yaml</code> file. Toolsets may
only include valid tools that are also defined in your <code>tools.yaml</code> file.
</p>
<a href="https://googleapis.github.io/genai-toolbox/getting-started/configure/#toolsets" class="btn btn--externalDocs" target="_blank" rel="noopener noreferrer">Toolsets Documentation</a>
</div>
`;
}

View File

@@ -30,8 +30,8 @@ function renderNavbar(containerId, activePath) {
<img src="/ui/assets/mcptoolboxlogo.png" alt="App Logo">
</div>
<ul>
<!--<li><a href="/ui/sources">Sources</a></li>-->
<!--<li><a href="/ui/authservices">Auth Services</a></li>-->
<li><a href="/ui/sources">Sources</a></li>
<li><a href="/ui/authservices">Auth Services</a></li>
<li><a href="/ui/tools">Tools</a></li>
<li><a href="/ui/toolsets">Toolsets</a></li>
</ul>

View File

@@ -0,0 +1,271 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
const MOCK_SOURCE_MANIFEST = {
"sources": {
"my-alloydb-pg-source": {
kind: "alloydb-postgres",
project: "my-project-id",
region: "us-central1",
cluster: "my-cluster",
instance: "my-instance",
database: "my_db",
ipType: "public"
},
"my-bigtable-source": {
kind: "bigtable",
project: "my-project-id",
instance: "test-instance"
},
}
};
async function getMockSourceListData() {
console.warn(`[MOCK] Returning mock data for all sources`);
return MOCK_SOURCE_MANIFEST;
}
async function getMockSourceDetailsData(sourceName) {
console.warn(`[MOCK] Returning mock data for source details: ${sourceName}`);
const SOURCE_DATA = MOCK_SOURCE_MANIFEST.sources[sourceName];
if (SOURCE_DATA) {
return { "sources": { [sourceName]: SOURCE_DATA } };
} else {
throw new Error(`Mock Source "${sourceName}" not found`);
}
}
const MOCK_AUTH_SERVICE_MANIFEST = {
"authservices": {
"my-auth-service-1": {
kind: "google oauth2",
},
"another-auth": {
kind: "other",
}
}
};
async function getMockAuthServiceListData() {
console.warn(`[MOCK] Returning mock data for all authservices`);
return MOCK_AUTH_SERVICE_MANIFEST;
}
async function getMockAuthServiceDetailsData(authServiceName) {
console.warn(`[MOCK] Returning mock data for auth service details: ${authServiceName}`);
const SERVICE_DATA = MOCK_AUTH_SERVICE_MANIFEST.authservices[authServiceName];
if (SERVICE_DATA) {
return { "authservices": { [authServiceName]: SERVICE_DATA } };
} else {
throw new Error(`Mock Auth Service "${authServiceName}" not found`);
}
}
const itemConfigs = {
sources: {
getListData: getMockSourceListData, // replace with real API call to GET all sources
getDetailsData: getMockSourceDetailsData, // replace with real API call to GET specific source
listKey: 'sources',
nameAttribute: 'data-itemname',
displayName: 'Source',
singularName: 'source',
},
authservices: {
getListData: getMockAuthServiceListData, // replace with real API call to GET all auth services
getDetailsData: getMockAuthServiceDetailsData, //replace with real API call to get specific auth service
listKey: 'authservices',
nameAttribute: 'data-itemname',
displayName: 'Auth Service',
singularName: 'authservice',
}
};
/**
* Renders the details of an item.
* @param {string} itemName - The name of the item.
* @param {!Object<string, *>} itemDetails - The object containing the item's attributes.
* @param {!HTMLElement} displayArea - The HTML element to render the details into.
* @param {string} displayName - The display name of the item type.
*/
function displayItemDetails(itemName, itemDetails, displayArea) {
displayArea.innerHTML = '';
const container = document.createElement('div');
container.className = 'tool-box item-details-box';
const dl = document.createElement('dl');
dl.classList.add('item-details-list');
// add the name (key in the original json response) as the first key-value pair
const nameEntryDiv = document.createElement('div');
nameEntryDiv.classList.add('item-detail-entry');
const nameDt = document.createElement('dt');
nameDt.classList.add('item-detail-key');
nameDt.textContent = 'name';
nameEntryDiv.appendChild(nameDt);
const nameDd = document.createElement('dd');
nameDd.classList.add('item-detail-value');
nameDd.textContent = itemName;
nameEntryDiv.appendChild(nameDd);
dl.appendChild(nameEntryDiv);
const entries = Object.entries(itemDetails);
if (entries.length === 0 && !itemName) {
const para = document.createElement('p');
para.textContent = 'No details available.';
container.appendChild(para);
displayArea.appendChild(container);
return;
}
for (const [key, value] of entries) {
const entryDiv = document.createElement('div');
entryDiv.classList.add('item-detail-entry');
const dt = document.createElement('dt');
dt.classList.add('item-detail-key');
dt.textContent = key;
entryDiv.appendChild(dt);
const dd = document.createElement('dd');
dd.classList.add('item-detail-value');
dd.textContent = String(value);
entryDiv.appendChild(dd);
dl.appendChild(entryDiv);
}
container.appendChild(dl);
displayArea.appendChild(container);
}
/**
* Fetches and displays details for a specific item.
* @param {string} itemType - 'sources' or 'authservices'.
* @param {string} itemName - The name of the item.
* @param {!HTMLElement} displayArea - The element to render details into.
* @param {!Object} config - The configuration object for the item type.
*/
async function fetchItemDetails(itemType, itemName, displayArea, config) {
displayArea.innerHTML = `<p>Loading details for ${itemName}...</p>`;
try {
const apiResponse = await config.getDetailsData(itemName);
const itemDetails = apiResponse && apiResponse[config.listKey] ? apiResponse[config.listKey][itemName] : null;
if (!itemDetails) {
throw new Error(`${config.displayName} "${itemName}" data not found in API response.`);
}
displayItemDetails(itemName, itemDetails, displayArea);
console.log(`${config.displayName} details:`, itemDetails);
} catch (error) {
console.error(`Failed to load details for ${config.singularName} "${itemName}":`, error);
displayArea.innerHTML = `<p class="error">Failed to load details for ${itemName}: ${error.message}</p>`;
}
}
/**
* Handles the click event on an item button.
* @param {!Event} event - The click event object.
* @param {string} itemType - 'sources' or 'authservices'.
* @param {!HTMLElement} listContainer - The parent element containing the item buttons.
* @param {!HTMLElement} displayArea - The HTML element where item details will be shown.
* @param {!Object} config - The configuration object for the item type.
*/
function handleItemClick(event, itemType, listContainer, displayArea, config) {
const itemName = event.target.getAttribute(config.nameAttribute);
if (itemName) {
const currentActive = listContainer.querySelector('.second-nav-choice.active');
if (currentActive) currentActive.classList.remove('active');
event.target.classList.add('active');
fetchItemDetails(itemType, itemName, displayArea, config);
}
}
/**
* Renders the list of items as buttons.
* @param {string} itemType - 'sources' or 'authservices'.
* @param {?Object} apiResponse - The API response object.
* @param {!HTMLElement} listContainer - The element to render the list into.
* @param {!HTMLElement} displayArea - The element for displaying item details.
* @param {!Object} config - The configuration object for the item type.
*/
function renderList(itemType, apiResponse, listContainer, displayArea, config) {
listContainer.innerHTML = '';
const itemsObject = apiResponse ? apiResponse[config.listKey] : null;
if (!itemsObject || typeof itemsObject !== 'object' || itemsObject === null) {
console.error(`Error: Expected an object with a "${config.listKey}" property, but received:`, apiResponse);
listContainer.textContent = `Error: Invalid response format from ${config.displayName} API.`;
return;
}
const itemNames = Object.keys(itemsObject);
if (itemNames.length === 0) {
listContainer.textContent = `No ${config.displayName}s found.`;
return;
}
const ul = document.createElement('ul');
itemNames.forEach(itemName => {
const li = document.createElement('li');
const button = document.createElement('button');
button.textContent = itemName;
button.setAttribute(config.nameAttribute, itemName);
button.classList.add('second-nav-choice');
button.addEventListener('click', (event) => handleItemClick(event, itemType, listContainer, displayArea, config));
li.appendChild(button);
ul.appendChild(li);
});
listContainer.appendChild(ul);
}
/**
* Fetches and renders a list of items (sources or authservices).
* @param {string} itemType - 'sources' or 'authservices'.
* @param {!HTMLElement} listContainer - Element to render the list into.
* @param {!HTMLElement} displayArea - Element to display item details.
* @returns {!Promise<void>}
*/
async function loadItems(itemType, listContainer, displayArea) {
const config = itemConfigs[itemType];
if (!config) {
console.error(`Unknown item type: ${itemType}`);
if (listContainer) {
listContainer.innerHTML = `<p class="error">Configuration error.</p>`;
}
return;
}
if (!listContainer || !displayArea) {
console.error("List container or display area not provided to loadItems");
return;
}
listContainer.innerHTML = `<p>Fetching ${config.displayName}s...</p>`;
try {
const apiResponse = await config.getListData();
renderList(itemType, apiResponse, listContainer, displayArea, config);
} catch (error) {
console.error(`Failed to load ${config.displayName}s:`, error);
listContainer.innerHTML = `<p class="error">Failed to load ${config.displayName}s: <pre><code>${error}</code></pre></p>`;
}
}
export { loadItems };

View File

@@ -0,0 +1,27 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { loadItems } from './resourceDisplay.js';
document.addEventListener('DOMContentLoaded', () => {
const sourceListArea = document.getElementById('secondary-panel-content');
const sourceInfoArea = document.getElementById('source-info-area');
if (!sourceListArea || !sourceInfoArea) {
console.error('Required DOM elements not found.');
return;
}
loadItems('sources', sourceListArea, sourceInfoArea);
});

View File

@@ -13,6 +13,7 @@
// limitations under the License.
import { handleRunTool, displayResults } from './runTool.js';
import { createGoogleAuthMethodItem } from './auth.js'
/**
* Helper function to create form inputs for parameters.
@@ -151,7 +152,7 @@ function createParamInput(param, toolId) {
* parsed. The function receives the updated headers object as its argument.
* @return {!HTMLDivElement} The outermost div element of the created modal.
*/
function createHeaderEditorModal(toolId, currentHeaders, saveCallback) {
function createHeaderEditorModal(toolId, currentHeaders, toolParameters, authRequired, saveCallback) {
const MODAL_ID = `header-modal-${toolId}`;
let modal = document.getElementById(MODAL_ID);
@@ -174,9 +175,37 @@ function createHeaderEditorModal(toolId, currentHeaders, saveCallback) {
headersTextarea.rows = 10;
headersTextarea.value = JSON.stringify(currentHeaders, null, 2);
// handle authenticated params
const authProfileNames = new Set();
toolParameters.forEach(param => {
const isAuthParam = param.authServices && param.authServices.length > 0;
if (isAuthParam && param.authServices) {
param.authServices.forEach(name => authProfileNames.add(name));
}
});
// handle authorized invocations
if (authRequired && authRequired.length > 0) {
authRequired.forEach(name => authProfileNames.add(name));
}
modalContent.appendChild(modalHeader);
modalContent.appendChild(headersTextarea);
if (authProfileNames.size > 0 || authRequired.length > 0) {
const authHelperSection = document.createElement('div');
authHelperSection.className = 'auth-helper-section';
const authList = document.createElement('div');
authList.className = 'auth-method-list';
authProfileNames.forEach(profileName => {
const authItem = createGoogleAuthMethodItem(toolId, profileName);
authList.appendChild(authItem);
});
authHelperSection.appendChild(authList);
modalContent.appendChild(authHelperSection);
}
const modalActions = document.createElement('div');
const closeButton = document.createElement('button');
const saveButton = document.createElement('button');
@@ -205,13 +234,6 @@ function createHeaderEditorModal(toolId, currentHeaders, saveCallback) {
modalContent.appendChild(authTokenDropdown);
modal.appendChild(modalContent);
// Close modal if clicked outside
window.addEventListener('click', (event) => {
if (event.target === modal) {
closeHeaderEditor(toolId);
}
});
return modal;
}
@@ -246,7 +268,7 @@ function createAuthTokenInfoDropdown() {
details.className = 'auth-token-details';
details.appendChild(summary);
summary.textContent = 'How to extract Google OAuth ID Token';
summary.textContent = 'How to extract Google OAuth ID Token manually';
content.className = 'auth-token-content';
// auth instruction dropdown
@@ -321,10 +343,9 @@ export function renderToolInterface(tool, containerElement) {
const updateLastResults = (newResults) => {
lastResults = newResults;
};
const updateCurrentHeaders = (newHeaders) => {
currentHeaders = newHeaders;
const newModal = createHeaderEditorModal(TOOL_ID, currentHeaders, updateCurrentHeaders);
const newModal = createHeaderEditorModal(TOOL_ID, currentHeaders, tool.parameters, tool.authRequired, updateCurrentHeaders);
containerElement.appendChild(newModal);
};
@@ -428,7 +449,7 @@ export function renderToolInterface(tool, containerElement) {
containerElement.appendChild(responseContainer);
// create and append the header editor modal
const headerModal = createHeaderEditorModal(TOOL_ID, currentHeaders, updateCurrentHeaders);
const headerModal = createHeaderEditorModal(TOOL_ID, currentHeaders, tool.parameters, tool.authRequired, updateCurrentHeaders);
containerElement.appendChild(headerModal);
prettifyCheckbox.addEventListener('change', () => {

View File

@@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sources View</title>
<link rel="stylesheet" href="/ui/css/style.css">
</head>
<body>
<div id="navbar-container" data-active-nav="/ui/sources"></div>
<aside class="second-nav">
<h4>My Sources</h4>
<div id="secondary-panel-content">
<p>Fetching sources...</p>
</div>
</aside>
<div id="main-content-container"></div>
<script type="module" src="/ui/js/sources.js"></script>
<script src="/ui/js/navbar.js"></script>
<script src="/ui/js/mainContent.js"></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
const navbarContainer = document.getElementById('navbar-container');
const activeNav = navbarContainer.getAttribute('data-active-nav');
renderNavbar('navbar-container', activeNav);
renderMainContent('main-content-container', 'source-info-area')
});
</script>
</body>
</html>

View File

@@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tools View</title>
<link rel="stylesheet" href="/ui/css/style.css">
<script src="https://accounts.google.com/gsi/client" async defer></script>
</head>
<body>
<div id="navbar-container" data-active-nav="/ui/tools"></div>
@@ -26,7 +27,7 @@
const navbarContainer = document.getElementById('navbar-container');
const activeNav = navbarContainer.getAttribute('data-active-nav');
renderNavbar('navbar-container', activeNav);
renderMainContent('main-content-container', 'tool-display-area')
renderMainContent('main-content-container', 'tool-display-area', getToolInstructions())
});
</script>
</body>

View File

@@ -6,7 +6,7 @@
<title>Toolsets View</title>
<link rel="stylesheet" href="/ui/css/style.css">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<script src="https://accounts.google.com/gsi/client" async defer></script>
</head>
<body>
<div id="navbar-container" data-active-nav="/ui/toolsets"></div>
@@ -34,7 +34,7 @@
const navbarContainer = document.getElementById('navbar-container');
const activeNav = navbarContainer.getAttribute('data-active-nav');
renderNavbar('navbar-container', activeNav);
renderMainContent('main-content-container', 'tool-display-area');
renderMainContent('main-content-container', 'tool-display-area', getToolsetInstructions());
});
</script>
</body>

View File

@@ -22,6 +22,8 @@ func webRouter() (chi.Router, error) {
// direct routes for html pages to provide clean URLs
r.Get("/", func(w http.ResponseWriter, r *http.Request) { serveHTML(w, r, "static/index.html") })
r.Get("/sources", func(w http.ResponseWriter, r *http.Request) { serveHTML(w, r, "static/sources.html") })
r.Get("/authservices", func(w http.ResponseWriter, r *http.Request) { serveHTML(w, r, "static/authServices.html") })
r.Get("/tools", func(w http.ResponseWriter, r *http.Request) { serveHTML(w, r, "static/tools.html") })
r.Get("/toolsets", func(w http.ResponseWriter, r *http.Request) { serveHTML(w, r, "static/toolsets.html") })

View File

@@ -73,6 +73,34 @@ func TestWebEndpoint(t *testing.T) {
wantContentType: "text/html",
wantPageTitle: "Toolsets View",
},
{
name: "web sources page",
path: "/ui/sources",
wantStatus: http.StatusOK,
wantContentType: "text/html",
wantPageTitle: "Sources View",
},
{
name: "web sources page with trailing slash",
path: "/ui/sources/",
wantStatus: http.StatusOK,
wantContentType: "text/html",
wantPageTitle: "Sources View",
},
{
name: "web auth services page",
path: "/ui/authservices",
wantStatus: http.StatusOK,
wantContentType: "text/html",
wantPageTitle: "AuthServices View",
},
{
name: "web auth services page with trailing slash",
path: "/ui/authservices/",
wantStatus: http.StatusOK,
wantContentType: "text/html",
wantPageTitle: "AuthServices View",
},
}
for _, tc := range testCases {

View File

@@ -1,128 +0,0 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package duckdb
import (
"context"
"database/sql"
"fmt"
"net/url"
"strings"
"github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
_ "github.com/marcboeker/go-duckdb/v2"
"go.opentelemetry.io/otel/trace"
)
const SourceKind string = "duckdb"
func init() {
if !sources.Register(SourceKind, newConfig) {
panic(fmt.Sprintf("source kind %q already registered", SourceKind))
}
}
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) {
actual := Config{Name: name}
if err := decoder.DecodeContext(ctx, &actual); err != nil {
return nil, err
}
return actual, nil
}
type Source struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
Db *sql.DB
}
// SourceKind implements sources.Source.
func (s *Source) SourceKind() string {
return SourceKind
}
func (s *Source) DuckDb() *sql.DB {
return s.Db
}
// validate Source
var _ sources.Source = &Source{}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
DatabaseFile string `yaml:"dbFilePath,omitempty"`
Configuration map[string]string `yaml:"configuration,omitempty"`
}
func (r Config) SourceConfigKind() string {
return SourceKind
}
func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) {
db, err := initDuckDbConnection(ctx, tracer, r.Name, r.DatabaseFile, r.Configuration)
if err != nil {
return nil, fmt.Errorf("unable to create db connection: %w", err)
}
err = db.PingContext(context.Background())
if err != nil {
return nil, fmt.Errorf("unable to connect sucessfully: %w", err)
}
s := &Source{
Name: r.Name,
Kind: r.Kind,
Db: db,
}
return s, nil
}
// validate interface
var _ sources.SourceConfig = Config{}
func initDuckDbConnection(ctx context.Context, tracer trace.Tracer, name string, dbFilePath string, duckdbConfiguration map[string]string) (*sql.DB, error) {
//nolint:all // Reassigned ctx
ctx, span := sources.InitConnectionSpan(ctx, tracer, SourceKind, name)
defer span.End()
var configStr = getDuckDbConfiguration(dbFilePath, duckdbConfiguration)
//Open database connection
db, err := sql.Open("duckdb", configStr)
if err != nil {
return nil, fmt.Errorf("unable to open duckdb connection: %w", err)
}
return db, nil
}
func getDuckDbConfiguration(dbFilePath string, duckdbConfiguration map[string]string) string {
if len(duckdbConfiguration) == 0 {
return dbFilePath
}
params := url.Values{}
for key, value := range duckdbConfiguration {
params.Set(key, value)
}
var configStr strings.Builder
configStr.WriteString(dbFilePath)
configStr.WriteString("?")
configStr.WriteString(params.Encode())
return configStr.String()
}

View File

@@ -1,84 +0,0 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package duckdb_test
import (
"testing"
yaml "github.com/goccy/go-yaml"
"github.com/google/go-cmp/cmp"
"github.com/googleapis/genai-toolbox/internal/server"
"github.com/googleapis/genai-toolbox/internal/sources/duckdb"
"github.com/googleapis/genai-toolbox/internal/testutils"
)
func TestParserFromYamlDuckDb(t *testing.T) {
config := make(map[string]string)
config["access_mode"] = "READ_ONLY"
config["threads"] = "4"
tcs := []struct {
desc string
in string
want server.SourceConfigs
}{
{
desc: "basic example",
in: `
sources:
my-duckdb:
kind: duckdb
`,
want: server.SourceConfigs{
"my-duckdb": duckdb.Config{
Name: "my-duckdb",
Kind: duckdb.SourceKind,
},
},
},
{
desc: "with custom configuration",
in: `
sources:
my-duckdb:
kind: duckdb
configuration:
access_mode: READ_ONLY
threads: 4
`,
want: server.SourceConfigs{
"my-duckdb": duckdb.Config{
Name: "my-duckdb",
Kind: duckdb.SourceKind,
Configuration: config,
},
},
},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
got := struct {
Sources server.SourceConfigs `yaml:"sources"`
}{}
// Parse contents
err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
if err != nil {
t.Fatalf("unable to unmarshal: %s", err)
}
if !cmp.Equal(tc.want, got.Sources) {
t.Fatalf("incorrect parse: want %v, got %v", tc.want, got.Sources)
}
})
}
}

View File

@@ -16,6 +16,7 @@ package bigqueryexecutesql
import (
"context"
"encoding/json"
"fmt"
bigqueryapi "cloud.google.com/go/bigquery"
@@ -83,7 +84,13 @@ func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error)
}
sqlParameter := tools.NewStringParameter("sql", "The sql to execute.")
parameters := tools.Parameters{sqlParameter}
dryRunParameter := tools.NewBooleanParameterWithDefault(
"dry_run",
false,
"If set to true, the query will be validated and information about the execution "+
"will be returned without running the query. Defaults to false.",
)
parameters := tools.Parameters{sqlParameter, dryRunParameter}
mcpManifest := tools.McpManifest{
Name: cfg.Name,
@@ -120,10 +127,14 @@ type Tool struct {
}
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues) (any, error) {
sliceParams := params.AsSlice()
sql, ok := sliceParams[0].(string)
paramsMap := params.AsMap()
sql, ok := paramsMap["sql"].(string)
if !ok {
return nil, fmt.Errorf("unable to get cast %s", sliceParams[0])
return nil, fmt.Errorf("unable to cast sql parameter %s", paramsMap["sql"])
}
dryRun, ok := paramsMap["dry_run"].(bool)
if !ok {
return nil, fmt.Errorf("unable to cast dry_run parameter %s", paramsMap["dry_run"])
}
dryRunJob, err := dryRunQuery(ctx, t.RestService, t.Client.Project(), t.Client.Location, sql)
@@ -131,6 +142,18 @@ func (t Tool) Invoke(ctx context.Context, params tools.ParamValues) (any, error)
return nil, fmt.Errorf("query validation failed during dry run: %w", err)
}
if dryRun {
if dryRunJob != nil {
jobJSON, err := json.MarshalIndent(dryRunJob, "", " ")
if err != nil {
return nil, fmt.Errorf("failed to marshal dry run job to JSON: %w", err)
}
return string(jobJSON), nil
}
// This case should not be reached, but as a fallback, we return a message.
return "Dry run was requested, but no job information was returned.", nil
}
statementType := dryRunJob.Statistics.Query.StatementType
// JobStatistics.QueryStatistics.StatementType
query := t.Client.Query(sql)

View File

@@ -1,210 +0,0 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package duckdbsql
import (
"context"
"database/sql"
"fmt"
"github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
"github.com/googleapis/genai-toolbox/internal/sources/duckdb"
"github.com/googleapis/genai-toolbox/internal/tools"
)
const kind string = "duckdb-sql"
func init() {
if !tools.Register(kind, newConfig) {
panic(fmt.Sprintf("tool kind %q already registered", kind))
}
}
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
actual := Config{Name: name}
if err := decoder.DecodeContext(ctx, &actual); err != nil {
return nil, err
}
return actual, nil
}
type compatibleSource interface {
DuckDb() *sql.DB
}
// validate compatible sources are still compatible
var _ compatibleSource = &duckdb.Source{}
var compatibleSources = [...]string{duckdb.SourceKind}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
Statement string `yaml:"statement" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
Parameters tools.Parameters `yaml:"parameters"`
TemplateParameters tools.Parameters `yaml:"templateParameters"`
}
// Initialize implements tools.ToolConfig.
func (c Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
// verify source exists
rawS, ok := srcs[c.Source]
if !ok {
return nil, fmt.Errorf("no source named %q configured", c.Source)
}
// verify the source is compatible
s, ok := rawS.(compatibleSource)
if !ok {
return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
}
allParameters, paramManifest, paramMcpManifest := tools.ProcessParameters(c.TemplateParameters, c.Parameters)
mcpManifest := tools.McpManifest{
Name: c.Name,
Description: c.Description,
InputSchema: paramMcpManifest,
}
// finish tool setup
t := Tool{
Name: c.Name,
Kind: kind,
Parameters: c.Parameters,
TemplateParameters: c.TemplateParameters,
AllParams: allParameters,
Statement: c.Statement,
AuthRequired: c.AuthRequired,
Db: s.DuckDb(),
manifest: tools.Manifest{Description: c.Description, Parameters: paramManifest, AuthRequired: c.AuthRequired},
mcpManifest: mcpManifest,
}
return t, nil
}
// ToolConfigKind implements tools.ToolConfig.
func (c Config) ToolConfigKind() string {
return kind
}
var _ tools.ToolConfig = Config{}
type Tool struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
AuthRequired []string `yaml:"authRequired"`
Parameters tools.Parameters `yaml:"parameters"`
TemplateParameters tools.Parameters `yaml:"templateParameters"`
AllParams tools.Parameters `yaml:"allParams"`
Db *sql.DB
Statement string `yaml:"statement"`
manifest tools.Manifest
mcpManifest tools.McpManifest
}
// Authorized implements tools.Tool.
func (t Tool) Authorized(verifiedAuthSources []string) bool {
return tools.IsAuthorized(t.AuthRequired, verifiedAuthSources)
}
// Invoke implements tools.Tool.
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues) (any, error) {
paramsMap := params.AsMap()
newStatement, err := tools.ResolveTemplateParams(t.TemplateParameters, t.Statement, paramsMap)
if err != nil {
return nil, fmt.Errorf("unable to extract template params %w", err)
}
newParams, err := tools.GetParams(t.Parameters, paramsMap)
if err != nil {
return nil, fmt.Errorf("unable to extract standard params %w", err)
}
sliceParams := newParams.AsSlice()
// Execute the SQL query with parameters
rows, err := t.Db.QueryContext(ctx, newStatement, sliceParams...)
if err != nil {
return nil, fmt.Errorf("unable to execute query: %w", err)
}
defer rows.Close()
// Get column names
cols, err := rows.Columns()
if err != nil {
return nil, fmt.Errorf("unable to get column names: %w", err)
}
values := make([]any, len(cols))
valuePtrs := make([]any, len(cols))
for i := range values {
valuePtrs[i] = &values[i]
}
// Prepare the result slice
var result []any
// Iterate through the rows
for rows.Next() {
// Scan the row into the value pointers
if err := rows.Scan(valuePtrs...); err != nil {
return nil, fmt.Errorf("unable to scan row: %w", err)
}
// Create a map for this row
rowMap := make(map[string]interface{})
for i, col := range cols {
val := values[i]
// Handle nil values
if val == nil {
rowMap[col] = nil
continue
}
// Store the value in the map
rowMap[col] = val
}
result = append(result, rowMap)
}
if err = rows.Close(); err != nil {
return nil, fmt.Errorf("unable to close rows: %w", err)
}
if err = rows.Err(); err != nil {
return nil, fmt.Errorf("error iterating rows: %w", err)
}
return result, nil
}
// Manifest implements tools.Tool.
func (t Tool) Manifest() tools.Manifest {
return t.manifest
}
// McpManifest implements tools.Tool.
func (t Tool) McpManifest() tools.McpManifest {
return t.mcpManifest
}
// ParseParams implements tools.Tool.
func (t Tool) ParseParams(data map[string]any, claimsMap map[string]map[string]any) (tools.ParamValues, error) {
return tools.ParseParams(t.AllParams, data, claimsMap)
}
var _ tools.Tool = Tool{}

View File

@@ -1,87 +0,0 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package duckdbsql_test
import (
"testing"
"github.com/googleapis/genai-toolbox/internal/tools/duckdbsql"
yaml "github.com/goccy/go-yaml"
"github.com/google/go-cmp/cmp"
"github.com/googleapis/genai-toolbox/internal/server"
"github.com/googleapis/genai-toolbox/internal/testutils"
"github.com/googleapis/genai-toolbox/internal/tools"
)
func TestParseFromYamlDuckDb(t *testing.T) {
tcs := []struct {
desc string
in string
want server.ToolConfigs
}{
{
desc: "basic example",
in: `
tools:
example_tool:
kind: duckdb-sql
source: my-duckdb-instance
description: some description
statement: |
select * from hotel WHERE name = $hotel;
parameters:
- name: hotel
type: string
description: hotel parameter description
`,
want: server.ToolConfigs{
"example_tool": duckdbsql.Config{
Name: "example_tool",
Kind: "duckdb-sql",
Source: "my-duckdb-instance",
Description: "some description",
Statement: "select * from hotel WHERE name = $hotel;\n",
AuthRequired: []string{},
Parameters: []tools.Parameter{
tools.NewStringParameter("hotel", "hotel parameter description"),
},
},
},
},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
got := struct {
Tools server.ToolConfigs `yaml:"tools"`
}{}
// Create a context with a logger
ctx, err := testutils.ContextWithNewLogger()
if err != nil {
t.Fatalf("unable to create context with logger: %s", err)
}
// Parse contents with context
err = yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
if err != nil {
t.Fatalf("unable to unmarshal: %s", err)
}
if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
t.Fatalf("incorrect parse: diff %v", diff)
}
})
}
}

View File

@@ -45,13 +45,10 @@ func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.T
}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
// AuthRequired specifies the authentication services required for this tool.
// Currently, this field is not actively used for authorization checks within the tool itself,
// as the Authorized method always returns true. It is included for potential future extensibility.
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
}
@@ -161,7 +158,5 @@ func (t Tool) McpManifest() tools.McpManifest {
}
func (t Tool) Authorized(verifiedAuthServices []string) bool {
// Currently, all Looker tools are considered authorized if the source is correctly configured.
// The AuthRequired field in the Config struct is reserved for future, more granular authorization.
return true
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
}

View File

@@ -44,13 +44,10 @@ func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.T
}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
// AuthRequired specifies the authentication services required for this tool.
// Currently, this field is not actively used for authorization checks within the tool itself,
// as the Authorized method always returns true. It is included for potential future extensibility.
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
}
@@ -164,7 +161,5 @@ func (t Tool) McpManifest() tools.McpManifest {
}
func (t Tool) Authorized(verifiedAuthServices []string) bool {
// Currently, all Looker tools are considered authorized if the source is correctly configured.
// The AuthRequired field in the Config struct is reserved for future, more granular authorization.
return true
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
}

View File

@@ -45,13 +45,10 @@ func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.T
}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
// AuthRequired specifies the authentication services required for this tool.
// Currently, this field is not actively used for authorization checks within the tool itself,
// as the Authorized method always returns true. It is included for potential future extensibility.
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
}
@@ -161,7 +158,5 @@ func (t Tool) McpManifest() tools.McpManifest {
}
func (t Tool) Authorized(verifiedAuthServices []string) bool {
// Currently, all Looker tools are considered authorized if the source is correctly configured.
// The AuthRequired field in the Config struct is reserved for future, more granular authorization.
return true
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
}

View File

@@ -44,13 +44,10 @@ func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.T
}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
// AuthRequired specifies the authentication services required for this tool.
// Currently, this field is not actively used for authorization checks within the tool itself,
// as the Authorized method always returns true. It is included for potential future extensibility.
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
}
@@ -187,7 +184,5 @@ func (t Tool) McpManifest() tools.McpManifest {
}
func (t Tool) Authorized(verifiedAuthServices []string) bool {
// Currently, all Looker tools are considered authorized if the source is correctly configured.
// The AuthRequired field in the Config struct is reserved for future, more granular authorization.
return true
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
}

View File

@@ -45,13 +45,10 @@ func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.T
}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
// AuthRequired specifies the authentication services required for this tool.
// Currently, this field is not actively used for authorization checks within the tool itself,
// as the Authorized method always returns true. It is included for potential future extensibility.
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
}
@@ -161,7 +158,5 @@ func (t Tool) McpManifest() tools.McpManifest {
}
func (t Tool) Authorized(verifiedAuthServices []string) bool {
// Currently, all Looker tools are considered authorized if the source is correctly configured.
// The AuthRequired field in the Config struct is reserved for future, more granular authorization.
return true
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
}

View File

@@ -44,13 +44,10 @@ func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.T
}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
// AuthRequired specifies the authentication services required for this tool.
// Currently, this field is not actively used for authorization checks within the tool itself,
// as the Authorized method always returns true. It is included for potential future extensibility.
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
}
@@ -161,7 +158,5 @@ func (t Tool) McpManifest() tools.McpManifest {
}
func (t Tool) Authorized(verifiedAuthServices []string) bool {
// Currently, all Looker tools are considered authorized if the source is correctly configured.
// The AuthRequired field in the Config struct is reserved for future, more granular authorization.
return true
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
}

View File

@@ -45,13 +45,10 @@ func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.T
}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
// AuthRequired specifies the authentication services required for this tool.
// Currently, this field is not actively used for authorization checks within the tool itself,
// as the Authorized method always returns true. It is included for potential future extensibility.
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
}
@@ -161,7 +158,5 @@ func (t Tool) McpManifest() tools.McpManifest {
}
func (t Tool) Authorized(verifiedAuthServices []string) bool {
// Currently, all Looker tools are considered authorized if the source is correctly configured.
// The AuthRequired field in the Config struct is reserved for future, more granular authorization.
return true
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
}

View File

@@ -0,0 +1,219 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package lookermakelook
import (
"context"
"encoding/json"
"fmt"
"slices"
yaml "github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
lookersrc "github.com/googleapis/genai-toolbox/internal/sources/looker"
"github.com/googleapis/genai-toolbox/internal/tools"
"github.com/googleapis/genai-toolbox/internal/tools/looker/lookercommon"
"github.com/googleapis/genai-toolbox/internal/util"
"github.com/looker-open-source/sdk-codegen/go/rtl"
v4 "github.com/looker-open-source/sdk-codegen/go/sdk/v4"
)
const kind string = "looker-make-look"
func init() {
if !tools.Register(kind, newConfig) {
panic(fmt.Sprintf("tool kind %q already registered", kind))
}
}
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
actual := Config{Name: name}
if err := decoder.DecodeContext(ctx, &actual); err != nil {
return nil, err
}
return actual, nil
}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
}
// validate interface
var _ tools.ToolConfig = Config{}
func (cfg Config) ToolConfigKind() string {
return kind
}
func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
// verify source exists
rawS, ok := srcs[cfg.Source]
if !ok {
return nil, fmt.Errorf("no source named %q configured", cfg.Source)
}
// verify the source is compatible
s, ok := rawS.(*lookersrc.Source)
if !ok {
return nil, fmt.Errorf("invalid source for %q tool: source kind must be `looker`", kind)
}
parameters := lookercommon.GetQueryParameters()
titleParameter := tools.NewStringParameter("title", "The title of the Look")
parameters = append(parameters, titleParameter)
descParameter := tools.NewStringParameterWithDefault("description", "", "The description of the Look")
parameters = append(parameters, descParameter)
vizParameter := tools.NewMapParameterWithDefault("vis_config",
map[string]any{},
"The visualization config for the query",
"",
)
parameters = append(parameters, vizParameter)
mcpManifest := tools.McpManifest{
Name: cfg.Name,
Description: cfg.Description,
InputSchema: parameters.McpManifest(),
}
// finish tool setup
return Tool{
Name: cfg.Name,
Kind: kind,
Parameters: parameters,
AuthRequired: cfg.AuthRequired,
Client: s.Client,
ApiSettings: s.ApiSettings,
manifest: tools.Manifest{
Description: cfg.Description,
Parameters: parameters.Manifest(),
AuthRequired: cfg.AuthRequired,
},
mcpManifest: mcpManifest,
}, nil
}
// validate interface
var _ tools.Tool = Tool{}
type Tool struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
Client *v4.LookerSDK
ApiSettings *rtl.ApiSettings
AuthRequired []string `yaml:"authRequired"`
Parameters tools.Parameters `yaml:"parameters"`
manifest tools.Manifest
mcpManifest tools.McpManifest
}
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues) (any, error) {
logger, err := util.LoggerFromContext(ctx)
if err != nil {
return nil, fmt.Errorf("unable to get logger from ctx: %s", err)
}
logger.DebugContext(ctx, "params = ", params)
wq, err := lookercommon.ProcessQueryArgs(ctx, params)
if err != nil {
return nil, fmt.Errorf("error building query request: %w", err)
}
mrespFields := "id,personal_folder_id"
mresp, err := t.Client.Me(mrespFields, t.ApiSettings)
if err != nil {
return nil, fmt.Errorf("error making me request: %s", err)
}
paramsMap := params.AsMap()
title := paramsMap["title"].(string)
description := paramsMap["description"].(string)
looks, err := t.Client.FolderLooks(*mresp.PersonalFolderId, "title", t.ApiSettings)
if err != nil {
return nil, fmt.Errorf("error getting existing looks in folder: %s", err)
}
lookTitles := []string{}
for _, look := range looks {
lookTitles = append(lookTitles, *look.Title)
}
if slices.Contains(lookTitles, title) {
lt, _ := json.Marshal(lookTitles)
return nil, fmt.Errorf("title %s already used in user's folder. Currently used titles are %v. Make the call again with a unique title", title, string(lt))
}
visConfig := paramsMap["vis_config"].(map[string]any)
wq.VisConfig = &visConfig
qrespFields := "id"
qresp, err := t.Client.CreateQuery(*wq, qrespFields, t.ApiSettings)
if err != nil {
return nil, fmt.Errorf("error making create query request: %s", err)
}
wlwq := v4.WriteLookWithQuery{
Title: &title,
UserId: mresp.Id,
Description: &description,
QueryId: qresp.Id,
FolderId: mresp.PersonalFolderId,
}
resp, err := t.Client.CreateLook(wlwq, "", t.ApiSettings)
if err != nil {
return nil, fmt.Errorf("error making create look request: %s", err)
}
logger.DebugContext(ctx, "resp = %v", resp)
setting, err := t.Client.GetSetting("host_url", t.ApiSettings)
if err != nil {
logger.ErrorContext(ctx, "error getting settings: %s", err)
}
data := make(map[string]any)
if resp.Id != nil {
data["id"] = *resp.Id
}
if resp.ShortUrl != nil {
if setting.HostUrl != nil {
data["short_url"] = *setting.HostUrl + *resp.ShortUrl
} else {
data["short_url"] = *resp.ShortUrl
}
}
logger.DebugContext(ctx, "data = %v", data)
return data, nil
}
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
return tools.ParseParams(t.Parameters, data, claims)
}
func (t Tool) Manifest() tools.Manifest {
return t.manifest
}
func (t Tool) McpManifest() tools.McpManifest {
return t.mcpManifest
}
func (t Tool) Authorized(verifiedAuthServices []string) bool {
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
}

View File

@@ -0,0 +1,116 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package lookermakelook_test
import (
"strings"
"testing"
yaml "github.com/goccy/go-yaml"
"github.com/google/go-cmp/cmp"
"github.com/googleapis/genai-toolbox/internal/server"
"github.com/googleapis/genai-toolbox/internal/testutils"
lkr "github.com/googleapis/genai-toolbox/internal/tools/looker/lookermakelook"
)
func TestParseFromYamlLookerQuery(t *testing.T) {
ctx, err := testutils.ContextWithNewLogger()
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
tcs := []struct {
desc string
in string
want server.ToolConfigs
}{
{
desc: "basic example",
in: `
tools:
example_tool:
kind: looker-make-look
source: my-instance
description: some description
`,
want: server.ToolConfigs{
"example_tool": lkr.Config{
Name: "example_tool",
Kind: "looker-make-look",
Source: "my-instance",
Description: "some description",
AuthRequired: []string{},
},
},
},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
got := struct {
Tools server.ToolConfigs `yaml:"tools"`
}{}
// Parse contents
err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
if err != nil {
t.Fatalf("unable to unmarshal: %s", err)
}
if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
t.Fatalf("incorrect parse: diff %v", diff)
}
})
}
}
func TestFailParseFromYamlLookerQuery(t *testing.T) {
ctx, err := testutils.ContextWithNewLogger()
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
tcs := []struct {
desc string
in string
err string
}{
{
desc: "Invalid method",
in: `
tools:
example_tool:
kind: looker-make-look
source: my-instance
method: GOT
description: some description
`,
err: "unable to parse tool \"example_tool\" as kind \"looker-make-look\": [4:1] unknown field \"method\"\n 1 | authRequired: []\n 2 | description: some description\n 3 | kind: looker-make-look\n> 4 | method: GOT\n ^\n 5 | source: my-instance",
},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
got := struct {
Tools server.ToolConfigs `yaml:"tools"`
}{}
// Parse contents
err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
if err == nil {
t.Fatalf("expect parsing to fail")
}
errStr := err.Error()
if !strings.Contains(errStr, tc.err) {
t.Fatalf("unexpected error string: got %q, want substring %q", errStr, tc.err)
}
})
}
}

View File

@@ -46,13 +46,10 @@ func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.T
}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
// AuthRequired specifies the authentication services required for this tool.
// Currently, this field is not actively used for authorization checks within the tool itself,
// as the Authorized method always returns true. It is included for potential future extensibility.
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
}
@@ -158,7 +155,5 @@ func (t Tool) McpManifest() tools.McpManifest {
}
func (t Tool) Authorized(verifiedAuthServices []string) bool {
// Currently, all Looker tools are considered authorized if the source is correctly configured.
// The AuthRequired field in the Config struct is reserved for future, more granular authorization.
return true
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
}

View File

@@ -45,13 +45,10 @@ func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.T
}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
// AuthRequired specifies the authentication services required for this tool.
// Currently, this field is not actively used for authorization checks within the tool itself,
// as the Authorized method always returns true. It is included for potential future extensibility.
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
}
@@ -149,7 +146,5 @@ func (t Tool) McpManifest() tools.McpManifest {
}
func (t Tool) Authorized(verifiedAuthServices []string) bool {
// Currently, all Looker tools are considered authorized if the source is correctly configured.
// The AuthRequired field in the Config struct is reserved for future, more granular authorization.
return true
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
}

View File

@@ -45,13 +45,10 @@ func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.T
}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
// AuthRequired specifies the authentication services required for this tool.
// Currently, this field is not actively used for authorization checks within the tool itself,
// as the Authorized method always returns true. It is included for potential future extensibility.
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
}
@@ -77,6 +74,13 @@ func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error)
parameters := lookercommon.GetQueryParameters()
vizParameter := tools.NewMapParameterWithDefault("vis_config",
map[string]any{},
"The visualization config for the query",
"",
)
parameters = append(parameters, vizParameter)
mcpManifest := tools.McpManifest{
Name: cfg.Name,
Description: cfg.Description,
@@ -124,6 +128,11 @@ func (t Tool) Invoke(ctx context.Context, params tools.ParamValues) (any, error)
if err != nil {
return nil, fmt.Errorf("error building query request: %w", err)
}
paramsMap := params.AsMap()
visConfig := paramsMap["vis_config"].(map[string]any)
wq.VisConfig = &visConfig
respFields := "id,slug,share_url,expanded_share_url"
resp, err := t.Client.CreateQuery(*wq, respFields, t.ApiSettings)
if err != nil {
@@ -162,7 +171,5 @@ func (t Tool) McpManifest() tools.McpManifest {
}
func (t Tool) Authorized(verifiedAuthServices []string) bool {
// Currently, all Looker tools are considered authorized if the source is correctly configured.
// The AuthRequired field in the Config struct is reserved for future, more granular authorization.
return true
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
}

View File

@@ -45,13 +45,10 @@ func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.T
}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
// AuthRequired specifies the authentication services required for this tool.
// Currently, this field is not actively used for authorization checks within the tool itself,
// as the Authorized method always returns true. It is included for potential future extensibility.
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
}
@@ -166,7 +163,5 @@ func (t Tool) McpManifest() tools.McpManifest {
}
func (t Tool) Authorized(verifiedAuthServices []string) bool {
// Currently, all Looker tools are considered authorized if the source is correctly configured.
// The AuthRequired field in the Config struct is reserved for future, more granular authorization.
return true
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
}

View File

@@ -118,10 +118,10 @@ type Tool struct {
}
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues) (any, error) {
sliceParams := params.AsSlice()
sql, ok := sliceParams[0].(string)
paramsMap := params.AsMap()
sql, ok := paramsMap["sql"].(string)
if !ok {
return nil, fmt.Errorf("unable to get cast %s", sliceParams[0])
return nil, fmt.Errorf("unable to get cast %s", paramsMap["sql"])
}
// Log the query executed for debugging.

View File

@@ -118,10 +118,10 @@ type Tool struct {
}
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues) (any, error) {
sliceParams := params.AsSlice()
sql, ok := sliceParams[0].(string)
paramsMap := params.AsMap()
sql, ok := paramsMap["sql"].(string)
if !ok {
return nil, fmt.Errorf("unable to get cast %s", sliceParams[0])
return nil, fmt.Errorf("unable to get cast %s", paramsMap["sql"])
}
// Log the query executed for debugging.

View File

@@ -124,10 +124,10 @@ type Tool struct {
}
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues) (any, error) {
sliceParams := params.AsSlice()
cypherStr, ok := sliceParams[0].(string)
paramsMap := params.AsMap()
cypherStr, ok := paramsMap["cypher"].(string)
if !ok {
return nil, fmt.Errorf("unable to get cast %s", sliceParams[0])
return nil, fmt.Errorf("unable to get cast %s", paramsMap["cypher"])
}
if cypherStr == "" {
@@ -145,7 +145,7 @@ func (t Tool) Invoke(ctx context.Context, params tools.ParamValues) (any, error)
}
config := neo4j.ExecuteQueryWithDatabase(t.Database)
results, err := neo4j.ExecuteQuery[*neo4j.EagerResult](ctx, t.Driver, cypherStr, nil,
results, err := neo4j.ExecuteQuery(ctx, t.Driver, cypherStr, nil,
neo4j.EagerResultTransformer, config)
if err != nil {
return nil, fmt.Errorf("unable to execute query: %w", err)

View File

@@ -120,10 +120,10 @@ type Tool struct {
}
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues) (any, error) {
sliceParams := params.AsSlice()
sql, ok := sliceParams[0].(string)
paramsMap := params.AsMap()
sql, ok := paramsMap["sql"].(string)
if !ok {
return nil, fmt.Errorf("unable to get cast %s", sliceParams[0])
return nil, fmt.Errorf("unable to get cast %s", paramsMap["sql"])
}
// Log the query executed for debugging.
logger, err := util.LoggerFromContext(ctx)

View File

@@ -146,10 +146,10 @@ func processRows(iter *spanner.RowIterator) ([]any, error) {
}
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues) (any, error) {
sliceParams := params.AsSlice()
sql, ok := sliceParams[0].(string)
paramsMap := params.AsMap()
sql, ok := paramsMap["sql"].(string)
if !ok {
return nil, fmt.Errorf("unable to get cast %s", sliceParams[0])
return nil, fmt.Errorf("unable to get cast %s", paramsMap["sql"])
}
// Log the query executed for debugging.

View File

@@ -116,10 +116,10 @@ type Tool struct {
}
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues) (any, error) {
sliceParams := params.AsSlice()
sql, ok := sliceParams[0].(string)
paramsMap := params.AsMap()
sql, ok := paramsMap["sql"].(string)
if !ok {
return nil, fmt.Errorf("unable to get cast %s", sliceParams[0])
return nil, fmt.Errorf("unable to get cast %s", paramsMap["sql"])
}
// Log the query executed for debugging.

View File

@@ -162,6 +162,7 @@ func TestBigQueryToolEndpoints(t *testing.T) {
tests.RunToolInvokeWithTemplateParameters(t, tableNameTemplateParam, templateParamTestConfig)
runBigQueryExecuteSqlToolInvokeTest(t, select1Want, invokeParamWant, tableNameParam, ddlWant)
runBigQueryExecuteSqlToolInvokeDryRunTest(t, datasetName)
runBigQueryDataTypeTests(t)
runBigQueryListDatasetToolInvokeTest(t, datasetName)
runBigQueryGetDatasetInfoToolInvokeTest(t, datasetName, datasetInfoWant)
@@ -556,6 +557,115 @@ func runBigQueryExecuteSqlToolInvokeTest(t *testing.T, select1Want, invokeParamW
}
}
func runBigQueryExecuteSqlToolInvokeDryRunTest(t *testing.T, datasetName string) {
// Get ID token
idToken, err := tests.GetGoogleIdToken(tests.ClientId)
if err != nil {
t.Fatalf("error getting Google ID token: %s", err)
}
newTableName := fmt.Sprintf("%s.new_dry_run_table_%s", datasetName, strings.ReplaceAll(uuid.New().String(), "-", ""))
// Test tool invoke endpoint
invokeTcs := []struct {
name string
api string
requestHeader map[string]string
requestBody io.Reader
want string
isErr bool
}{
{
name: "invoke my-exec-sql-tool with dryRun",
api: "http://127.0.0.1:5000/api/tool/my-exec-sql-tool/invoke",
requestHeader: map[string]string{},
requestBody: bytes.NewBuffer([]byte(`{"sql":"SELECT 1", "dry_run": true}`)),
want: `\"statementType\": \"SELECT\"`,
isErr: false,
},
{
name: "invoke my-exec-sql-tool with dryRun create table",
api: "http://127.0.0.1:5000/api/tool/my-exec-sql-tool/invoke",
requestHeader: map[string]string{},
requestBody: bytes.NewBuffer([]byte(fmt.Sprintf(`{"sql":"CREATE TABLE %s (id INT64, name STRING)", "dry_run": true}`, newTableName))),
want: `\"statementType\": \"CREATE_TABLE\"`,
isErr: false,
},
{
name: "invoke my-exec-sql-tool with dryRun execute immediate",
api: "http://127.0.0.1:5000/api/tool/my-exec-sql-tool/invoke",
requestHeader: map[string]string{},
requestBody: bytes.NewBuffer([]byte(fmt.Sprintf(`{"sql":"EXECUTE IMMEDIATE \"CREATE TABLE %s (id INT64, name STRING)\"", "dry_run": true}`, newTableName))),
want: `\"statementType\": \"SCRIPT\"`,
isErr: false,
},
{
name: "Invoke my-auth-exec-sql-tool with dryRun and auth token",
api: "http://127.0.0.1:5000/api/tool/my-auth-exec-sql-tool/invoke",
requestHeader: map[string]string{"my-google-auth_token": idToken},
requestBody: bytes.NewBuffer([]byte(`{"sql":"SELECT 1", "dry_run": true}`)),
isErr: false,
want: `\"statementType\": \"SELECT\"`,
},
{
name: "Invoke my-auth-exec-sql-tool with dryRun and invalid auth token",
api: "http://127.0.0.1:5000/api/tool/my-auth-exec-sql-tool/invoke",
requestHeader: map[string]string{"my-google-auth_token": "INVALID_TOKEN"},
requestBody: bytes.NewBuffer([]byte(`{"sql":"SELECT 1","dry_run": true}`)),
isErr: true,
},
{
name: "Invoke my-auth-exec-sql-tool with dryRun and without auth token",
api: "http://127.0.0.1:5000/api/tool/my-auth-exec-sql-tool/invoke",
requestHeader: map[string]string{},
requestBody: bytes.NewBuffer([]byte(`{"sql":"SELECT 1", "dry_run": true}`)),
isErr: true,
},
}
for _, tc := range invokeTcs {
t.Run(tc.name, func(t *testing.T) {
// Send Tool invocation request
req, err := http.NewRequest(http.MethodPost, tc.api, tc.requestBody)
if err != nil {
t.Fatalf("unable to create request: %s", err)
}
req.Header.Add("Content-type", "application/json")
for k, v := range tc.requestHeader {
req.Header.Add(k, v)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("unable to send request: %s", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
if tc.isErr {
return
}
bodyBytes, _ := io.ReadAll(resp.Body)
t.Fatalf("response status code is not 200, got %d: %s", resp.StatusCode, string(bodyBytes))
}
// Check response body
var body map[string]interface{}
err = json.NewDecoder(resp.Body).Decode(&body)
if err != nil {
t.Fatalf("error parsing response body")
}
got, ok := body["result"].(string)
if !ok {
t.Fatalf("unable to find result in response body")
}
if !strings.Contains(got, tc.want) {
t.Fatalf("expected %q to contain %q, but it did not", got, tc.want)
}
})
}
}
func runBigQueryDataTypeTests(t *testing.T) {
// Test tool invoke endpoint
invokeTcs := []struct {

View File

@@ -1,152 +0,0 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package duckdb
import (
"context"
"database/sql"
"fmt"
"os"
"regexp"
"strings"
"testing"
"time"
"github.com/google/uuid"
"github.com/googleapis/genai-toolbox/internal/testutils"
"github.com/googleapis/genai-toolbox/tests"
)
var (
DuckDbKind = "duckdb-sql"
dbPath = "/tmp/users.db"
)
func getDuckDbVars() map[string]any {
return map[string]any{
"kind": "duckdb",
"dbFilePath": dbPath,
"configuration": map[string]any{
"access_mode": "READ_WRITE",
},
}
}
func setupDuckDb(t *testing.T, createParamStmt, insertParamStmt, createAuthStmt, insertAuthStmt string, params []any, authparams []any) {
// Remove any existing database file to ensure a clean state
os.Remove(dbPath)
// Open a connection to DuckDB
db, err := sql.Open("duckdb", dbPath)
if err != nil {
t.Fatalf("Failed to open DuckDB connection: %v", err)
}
defer db.Close()
_, err = db.Exec(createParamStmt, params...)
if err != nil {
t.Fatalf("Failed to create table: %v", err)
}
_, err = db.Exec(createAuthStmt, authparams...)
if err != nil {
t.Fatalf("Failed to create table: %v", err)
}
_, err = db.Exec(insertParamStmt, params...)
if err != nil {
t.Fatalf("Failed to insert initial data: %v", err)
}
_, err = db.Exec(insertAuthStmt, authparams...)
if err != nil {
t.Fatalf("Failed to create table: %v", err)
}
}
func TestDuckDb(t *testing.T) {
sourceConfig := getDuckDbVars()
var args []string
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
tableNameParam := "param_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
tableNameAuth := "auth_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
tableNameTemplateParam := "template_param_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
createParamTableStmt, insertParamTableStmt, paramToolStmt, idParamToolStmt, paramToolStmt2, arrayToolStmt, paramTestParams := GetDuckDbParamToolInfo(tableNameParam)
createAuthTableStmt, insertAuthTableStmt, authToolStmt, authTestParams := GetDuckDbAuthToolInfo(tableNameAuth)
setupDuckDb(t, createParamTableStmt, insertParamTableStmt, createAuthTableStmt, insertAuthTableStmt, paramTestParams, authTestParams)
toolsFile := tests.GetToolsConfig(sourceConfig, DuckDbKind, paramToolStmt, idParamToolStmt, paramToolStmt2, arrayToolStmt, authToolStmt)
tmplSelectCombined, tmplSelectFilterCombined := tests.GetPostgresSQLTmplToolStatement()
toolsFile = tests.AddTemplateParamConfig(t, toolsFile, DuckDbKind, tmplSelectCombined, tmplSelectFilterCombined, "")
defer os.Remove(dbPath)
cmd, cleanup, err := tests.StartCmd(ctx, toolsFile, args...)
if err != nil {
t.Fatalf("command initialization returned an error: %s", err)
}
defer cleanup()
waitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
out, err := testutils.WaitForString(waitCtx, regexp.MustCompile(`Server ready to serve`), cmd.Out)
if err != nil {
t.Logf("toolbox command logs: \n%s", out)
t.Fatalf("toolbox didn't start successfully: %s", err)
}
tests.RunToolGetTest(t)
select1Want, failInvocationWant, _ := GetDuckDbWants()
_, invokeParamWantNull, nullWant, _ := tests.GetNonSpannerInvokeParamWant()
invokeParamWant := "[{\"name\":\"Alice\"},{\"name\":\"Sid\"}]"
mcpInvokeParamWant := "{\"jsonrpc\":\"2.0\",\"id\":\"my-tool\",\"result\":{\"content\":[{\"type\":\"text\",\"text\":\"{\\\"name\\\":\\\"Alice\\\"}\"},{\"type\":\"text\",\"text\":\"{\\\"name\\\":\\\"Sid\\\"}\"}]}}"
tests.RunToolInvokeTest(t, select1Want, invokeParamWant, invokeParamWantNull, nullWant, true, true)
tests.RunMCPToolCallMethod(t, mcpInvokeParamWant, failInvocationWant)
templateParamTestConfig := tests.NewTemplateParameterTestConfig(
tests.WithSelectAllWant("[{\"age\":21,\"id\":1,\"name\":\"Alex\"},{\"age\":100,\"id\":2,\"name\":\"Alice\"}]"),
tests.WithSelect1Want("[{\"age\":21,\"id\":1,\"name\":\"Alex\"}]"),
tests.WithReplaceNameFieldArray(`["name"]`),
tests.WithReplaceNameColFilter("name"),
tests.WithCreateColArray(`["id INT","name VARCHAR(20)","age INT"]`),
tests.WithInsert1Want("[{\"Count\":1}]"),
)
tests.RunToolInvokeWithTemplateParameters(t, tableNameTemplateParam, templateParamTestConfig)
}
func GetDuckDbParamToolInfo(tableName string) (string, string, string, string, string, string, []any) {
createStatement := fmt.Sprintf("CREATE TABLE %s (id INTEGER PRIMARY KEY, name TEXT);", tableName)
insertStatement := fmt.Sprintf("INSERT INTO %s (id, name) VALUES (1, $1), (2, $2), (3, $3), (4, $4);", tableName)
toolStatement := fmt.Sprintf("SELECT * EXCLUDE (id) FROM %s WHERE id = $1 OR name = $2 order by id;", tableName)
idParamStatement := fmt.Sprintf("SELECT * FROM %s WHERE id IN (SELECT unnest(list_value($1)) AS id);", tableName)
toolStatement2 := fmt.Sprintf("SELECT name FROM %s WHERE id = list_extract(list_value($1), 1);", tableName)
arrayToolStatement := fmt.Sprintf("SELECT name FROM %s WHERE id = ANY($1) AND name = ANY($2) order by name;", tableName)
params := []any{"Alice", "Jane", "Sid", nil}
return createStatement, insertStatement, toolStatement, idParamStatement, toolStatement2, arrayToolStatement, params
}
// GetDuckDbAuthToolInfo returns statements and param of my-auth-tool for duckdb-sql kind
func GetDuckDbAuthToolInfo(tableName string) (string, string, string, []any) {
createStatement := fmt.Sprintf("CREATE TABLE %s (id INTEGER PRIMARY KEY, name TEXT, email TEXT);", tableName)
insertStatement := fmt.Sprintf("INSERT INTO %s (id, name, email) VALUES (1, $1, $2), (2, $3, $4)", tableName)
toolStatement := fmt.Sprintf("SELECT name FROM %s WHERE email = $1;", tableName)
params := []any{"Alice", tests.ServiceAccountEmail, "Jane", "janedoe@gmail.com"}
return createStatement, insertStatement, toolStatement, params
}
func GetDuckDbWants() (string, string, string) {
select1Want := "[{\"1\":1}]"
failInvocationWant := `{"jsonrpc":"2.0","id":"invoke-fail-tool","result":{"content":[{"type":"text","text":"unable to execute query: Parser Error: syntax error at or near \"SELEC\""}],"isError":true}}`
createTableStatement := `"CREATE TABLE t (id SERIAL PRIMARY KEY, name TEXT)"`
return select1Want, failInvocationWant, createTableStatement
}