Compare commits

...

28 Commits

Author SHA1 Message Date
Yuan Teoh
eea22e5d68 fix: fix spanner integration test 2025-07-03 23:53:36 -06:00
Yuan
9b2dfcc553 chore: update int test variable name to be consistent (#766)
Update `_` variables to camelcase.
2025-07-04 05:21:39 +00:00
Twisha Bansal
cb514209b6 docs: add JS SDK to readme (#776)
Co-authored-by: Kurtis Van Gent <31518063+kurtisvg@users.noreply.github.com>
2025-07-03 22:35:13 +05:30
Twisha Bansal
0a93b0482c fix: fix docs preview deployment pipeline (#787)
The code for the preview build is now sourced from the target branch,
whereas it was previously sourced from the main branch.
2025-07-03 21:54:10 +05:30
release-please[bot]
f13e9635ba chore(main): release 0.8.0 (#689)
🤖 I have created a release *beep* *boop*
---


##
[0.8.0](https://github.com/googleapis/genai-toolbox/compare/v0.7.0...v0.8.0)
(2025-07-02)


### ⚠ BREAKING CHANGES

* **postgres,mssql,cloudsqlmssql:** encode source connection url for
sources ([#727](https://github.com/googleapis/genai-toolbox/issues/727))

### Features

* Add support for multiple YAML configuration files
([#760](https://github.com/googleapis/genai-toolbox/issues/760))
([40679d7](40679d700e))
* Add support for optional parameters
([#617](https://github.com/googleapis/genai-toolbox/issues/617))
([4827771](4827771b78)),
closes [#475](https://github.com/googleapis/genai-toolbox/issues/475)
* **mcp:** Support MCP version 2025-03-26
([#755](https://github.com/googleapis/genai-toolbox/issues/755))
([474df57](474df57d62))
* **sources/http:** Support disable SSL verification for HTTP Source
([#674](https://github.com/googleapis/genai-toolbox/issues/674))
([4055b0c](4055b0c356))
* **tools/bigquery:** Add templateParameters field for bigquery
([#699](https://github.com/googleapis/genai-toolbox/issues/699))
([f5f771b](f5f771b0f3))
* **tools/bigtable:** Add templateParameters field for bigtable
([#692](https://github.com/googleapis/genai-toolbox/issues/692))
([1c06771](1c067715fa))
* **tools/couchbase:** Add templateParameters field for couchbase
([#723](https://github.com/googleapis/genai-toolbox/issues/723))
([9197186](9197186b8b))
* **tools/http:** Add support for HTTP Tool pathParams
([#726](https://github.com/googleapis/genai-toolbox/issues/726))
([fd300dc](fd300dc606))
* **tools/redis:** Add Redis Source and Tool
([#519](https://github.com/googleapis/genai-toolbox/issues/519))
([f0aef29](f0aef29b0c))
* **tools/spanner:** Add templateParameters field for spanner
([#691](https://github.com/googleapis/genai-toolbox/issues/691))
([075dfa4](075dfa47e1))
* **tools/sqlitesql:** Add templateParameters field for sqlitesql
([#687](https://github.com/googleapis/genai-toolbox/issues/687))
([75e254c](75e254c0a4))
* **tools/valkey:** Add Valkey Source and Tool
([#532](https://github.com/googleapis/genai-toolbox/issues/532))
([054ec19](054ec198b9))


### Bug Fixes

* **bigquery,mssql:** Fix panic on tools with array param
([#722](https://github.com/googleapis/genai-toolbox/issues/722))
([7a6644c](7a6644cf0c))
* **postgres,mssql,cloudsqlmssql:** Encode source connection url for
sources ([#727](https://github.com/googleapis/genai-toolbox/issues/727))
([67964d9](67964d939f)),
closes [#717](https://github.com/googleapis/genai-toolbox/issues/717)
* Set default value to field's type during unmarshalling
([#774](https://github.com/googleapis/genai-toolbox/issues/774))
([fafed24](fafed24858)),
closes [#771](https://github.com/googleapis/genai-toolbox/issues/771)
* **server/mcp:** Do not listen from port for stdio
([#719](https://github.com/googleapis/genai-toolbox/issues/719))
([d51dbc7](d51dbc759b)),
closes [#711](https://github.com/googleapis/genai-toolbox/issues/711)
* **tools/mysqlexecutesql:** Handle nil panic and connection leak in
Invoke ([#757](https://github.com/googleapis/genai-toolbox/issues/757))
([7badba4](7badba42ee))
* **tools/mysqlsql:** Handle nil panic and connection leak in invoke
([#758](https://github.com/googleapis/genai-toolbox/issues/758))
([cbb4a33](cbb4a33351))

---
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 <45984206+Yuan325@users.noreply.github.com>
2025-07-02 09:30:33 -06:00
Yuan
fafed24858 fix: set default value to field's type during unmarshalling (#774)
When go-yaml decode into CommonParameter with Default being an any type,
int will be converted into []uint64.
It will fail the Parse() when the value is being used since it does not
belong to either of the int types.

Unmarshal `default` value into each field's type directly.

Fixes #771
2025-07-02 14:58:42 +00:00
Mend Renovate
6337434623 chore(deps): update module github.com/go-playground/validator/v10 to v10.27.0 (#775)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
|
[github.com/go-playground/validator/v10](https://redirect.github.com/go-playground/validator)
| `v10.26.0` -> `v10.27.0` |
[![age](https://developer.mend.io/api/mc/badges/age/go/github.com%2fgo-playground%2fvalidator%2fv10/v10.27.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/go/github.com%2fgo-playground%2fvalidator%2fv10/v10.27.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/go/github.com%2fgo-playground%2fvalidator%2fv10/v10.26.0/v10.27.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/github.com%2fgo-playground%2fvalidator%2fv10/v10.26.0/v10.27.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>go-playground/validator
(github.com/go-playground/validator/v10)</summary>

###
[`v10.27.0`](https://redirect.github.com/go-playground/validator/releases/tag/v10.27.0):
Release 10.27.0

[Compare
Source](https://redirect.github.com/go-playground/validator/compare/v10.26.0...v10.27.0)

#### What's Changed

- Fix Release version badge on README page by
[@&#8203;nodivbyzero](https://redirect.github.com/nodivbyzero) in
[https://github.com/go-playground/validator/pull/1406](https://redirect.github.com/go-playground/validator/pull/1406)
- fix russian E.164 error message by
[@&#8203;prigornitskiy](https://redirect.github.com/prigornitskiy) in
[https://github.com/go-playground/validator/pull/1349](https://redirect.github.com/go-playground/validator/pull/1349)
- chore: remove unnecessary statement by
[@&#8203;qshuai](https://redirect.github.com/qshuai) in
[https://github.com/go-playground/validator/pull/1200](https://redirect.github.com/go-playground/validator/pull/1200)
- Re-enable several linters by
[@&#8203;nodivbyzero](https://redirect.github.com/nodivbyzero) in
[https://github.com/go-playground/validator/pull/1412](https://redirect.github.com/go-playground/validator/pull/1412)
- add support to tag validateFn by
[@&#8203;peczenyj](https://redirect.github.com/peczenyj) in
[https://github.com/go-playground/validator/pull/1363](https://redirect.github.com/go-playground/validator/pull/1363)
- Bump golang.org/x/crypto from 0.33.0 to 0.35.0 in
/\_examples/validate\_fn by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/go-playground/validator/pull/1418](https://redirect.github.com/go-playground/validator/pull/1418)
- Bump golang.org/x/net from 0.34.0 to 0.38.0 in
/\_examples/validate\_fn by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/go-playground/validator/pull/1419](https://redirect.github.com/go-playground/validator/pull/1419)
- Align required\_without with the contract stated in the documentation
by [@&#8203;jmfrees](https://redirect.github.com/jmfrees) in
[https://github.com/go-playground/validator/pull/1422](https://redirect.github.com/go-playground/validator/pull/1422)
- Add translation example by
[@&#8203;cxlblm](https://redirect.github.com/cxlblm) in
[https://github.com/go-playground/validator/pull/1394](https://redirect.github.com/go-playground/validator/pull/1394)
- doc(errors): mention RegisterTagNameFunc for FieldError.Field by
[@&#8203;khan-ajamal](https://redirect.github.com/khan-ajamal) in
[https://github.com/go-playground/validator/pull/1358](https://redirect.github.com/go-playground/validator/pull/1358)
- Bump golangci/golangci-lint-action from 7 to 8 by
[@&#8203;dependabot](https://redirect.github.com/dependabot) in
[https://github.com/go-playground/validator/pull/1425](https://redirect.github.com/go-playground/validator/pull/1425)
- feat(translation): add en translation for urn\_rfc2141 by
[@&#8203;ryanmalesic](https://redirect.github.com/ryanmalesic) in
[https://github.com/go-playground/validator/pull/1431](https://redirect.github.com/go-playground/validator/pull/1431)
- fix: panics when private field is validated by
[@&#8203;ykalchevskiy](https://redirect.github.com/ykalchevskiy) in
[https://github.com/go-playground/validator/pull/1423](https://redirect.github.com/go-playground/validator/pull/1423)
- Fix: support validation for map values with struct types by
[@&#8203;JunaidIslam2105](https://redirect.github.com/JunaidIslam2105)
in
[https://github.com/go-playground/validator/pull/1433](https://redirect.github.com/go-playground/validator/pull/1433)
- Omitzero does not work with slice and map bug by
[@&#8203;JunaidIslam2105](https://redirect.github.com/JunaidIslam2105)
in
[https://github.com/go-playground/validator/pull/1436](https://redirect.github.com/go-playground/validator/pull/1436)
- Fix: Validator panics when 'nil' is used along with required if for
slices and maps by
[@&#8203;JunaidIslam2105](https://redirect.github.com/JunaidIslam2105)
in
[https://github.com/go-playground/validator/pull/1442](https://redirect.github.com/go-playground/validator/pull/1442)
- docs: typos by [@&#8203;eqsdxr](https://redirect.github.com/eqsdxr) in
[https://github.com/go-playground/validator/pull/1440](https://redirect.github.com/go-playground/validator/pull/1440)
- fix: make "file://" fail `url` validation by
[@&#8203;bfabio](https://redirect.github.com/bfabio) in
[https://github.com/go-playground/validator/pull/1444](https://redirect.github.com/go-playground/validator/pull/1444)
- disable way too aggressive and disagreeable linters by
[@&#8203;deankarn](https://redirect.github.com/deankarn) in
[https://github.com/go-playground/validator/pull/1445](https://redirect.github.com/go-playground/validator/pull/1445)
- use golangci lint file for disables by
[@&#8203;deankarn](https://redirect.github.com/deankarn) in
[https://github.com/go-playground/validator/pull/1447](https://redirect.github.com/go-playground/validator/pull/1447)

#### New Contributors

- [@&#8203;prigornitskiy](https://redirect.github.com/prigornitskiy)
made their first contribution in
[https://github.com/go-playground/validator/pull/1349](https://redirect.github.com/go-playground/validator/pull/1349)
- [@&#8203;qshuai](https://redirect.github.com/qshuai) made their first
contribution in
[https://github.com/go-playground/validator/pull/1200](https://redirect.github.com/go-playground/validator/pull/1200)
- [@&#8203;peczenyj](https://redirect.github.com/peczenyj) made their
first contribution in
[https://github.com/go-playground/validator/pull/1363](https://redirect.github.com/go-playground/validator/pull/1363)
- [@&#8203;jmfrees](https://redirect.github.com/jmfrees) made their
first contribution in
[https://github.com/go-playground/validator/pull/1422](https://redirect.github.com/go-playground/validator/pull/1422)
- [@&#8203;cxlblm](https://redirect.github.com/cxlblm) made their first
contribution in
[https://github.com/go-playground/validator/pull/1394](https://redirect.github.com/go-playground/validator/pull/1394)
- [@&#8203;khan-ajamal](https://redirect.github.com/khan-ajamal) made
their first contribution in
[https://github.com/go-playground/validator/pull/1358](https://redirect.github.com/go-playground/validator/pull/1358)
- [@&#8203;ryanmalesic](https://redirect.github.com/ryanmalesic) made
their first contribution in
[https://github.com/go-playground/validator/pull/1431](https://redirect.github.com/go-playground/validator/pull/1431)
- [@&#8203;ykalchevskiy](https://redirect.github.com/ykalchevskiy) made
their first contribution in
[https://github.com/go-playground/validator/pull/1423](https://redirect.github.com/go-playground/validator/pull/1423)
- [@&#8203;JunaidIslam2105](https://redirect.github.com/JunaidIslam2105)
made their first contribution in
[https://github.com/go-playground/validator/pull/1433](https://redirect.github.com/go-playground/validator/pull/1433)
- [@&#8203;eqsdxr](https://redirect.github.com/eqsdxr) made their first
contribution in
[https://github.com/go-playground/validator/pull/1440](https://redirect.github.com/go-playground/validator/pull/1440)
- [@&#8203;bfabio](https://redirect.github.com/bfabio) made their first
contribution in
[https://github.com/go-playground/validator/pull/1444](https://redirect.github.com/go-playground/validator/pull/1444)

**Full Changelog**:
https://github.com/go-playground/validator/compare/v10.26.0...v10.27.0

</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:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->
2025-07-02 05:32:35 +00:00
Mend Renovate
822708afaa chore(deps): update module cloud.google.com/go/bigtable to v1.38.0 (#773)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
|
[cloud.google.com/go/bigtable](https://redirect.github.com/googleapis/google-cloud-go)
| `v1.37.0` -> `v1.38.0` |
[![age](https://developer.mend.io/api/mc/badges/age/go/cloud.google.com%2fgo%2fbigtable/v1.38.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/go/cloud.google.com%2fgo%2fbigtable/v1.38.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/go/cloud.google.com%2fgo%2fbigtable/v1.37.0/v1.38.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/cloud.google.com%2fgo%2fbigtable/v1.37.0/v1.38.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:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->
2025-07-02 05:14:59 +00:00
Yuan
010c278cbf chore: release 0.8.0 (#769)
Release-As: 0.8.0
2025-06-30 18:32:48 +00:00
Mohd Mujtaba
40679d700e feat: add support for multiple YAML configuration files (#760)
# Add Multiple YAML Configuration File Support

## 🎯 Overview

This PR introduces support for loading and merging multiple YAML
configuration files in Toolbox, addressing the need for modular
configuration management in complex deployments.

##  New Features

### 1. Multiple Files Support (`--tools-files`)
- **Usage**: `--tools-files=file1.yaml,file2.yaml,file3.yaml`
- Load and intelligently merge multiple YAML configuration files
- Comma-separated file paths for maximum flexibility

### 2. Directory Support (`--tools-folder`)
- **Usage**: `--tools-folder=config-directory`
- Automatically discover and load all `.yaml` and `.yml` files from a
directory
- Simplifies configuration management for organized deployments

### 3. Smart Merging Logic
- **Sources/AuthServices/Tools**: Later files override earlier files
with same names
- **Toolsets**: Tools from same-named toolsets are combined without
duplicates
- Preserves all existing functionality while enabling composition

## 🔒 Safety & Validation

- **Mutual Exclusivity**: Prevents simultaneous use of `--tools-file`,
`--tools-files`, `--tools-folder`, and `--prebuilt`
- **Clear Error Messages**: Descriptive validation errors guide users to
correct usage
- **Comprehensive Error Handling**: Proper handling of missing files,
directories, and parsing errors
- **Full Backward Compatibility**: Existing configurations continue to
work unchanged

## 🏗️ Implementation Details

### Core Functions Added
- `mergeToolsFiles()` - Smart merging with configurable override rules
- `loadAndMergeToolsFiles()` - Multi-file loading and processing
- `loadAndMergeToolsFolder()` - Directory scanning and batch loading

### Command Structure Updates
- New `tools_files []string` field for multiple file paths
- New `tools_folder string` field for directory path
- Enhanced validation logic in `run()` function
- Updated flag definitions with proper descriptions

## 📋 Use Cases

### Organizational Benefits
- **Modular Configuration**: Separate database, API, and auth
configurations
- **Team Collaboration**: Multiple developers can work on different
config files
- **Environment Management**: Easy configuration swapping for different
environments
- **Scalability**: Large configurations can be broken into manageable
chunks

### Example Usage Patterns
```bash
# Multiple specific files
./toolbox --tools-files=database.yaml,apis.yaml,auth.yaml

# Directory-based loading
./toolbox --tools-folder=./production-configs

# Error case (properly handled)
./toolbox --tools-file=single.yaml --tools-folder=configs
# ERROR: --tools-file, --tools-files, and --tools-folder flags cannot be used simultaneously

---------

Co-authored-by: Wenxin Du <117315983+duwenxin99@users.noreply.github.com>
2025-06-30 14:00:49 -04:00
Mend Renovate
5fb056ee43 chore(deps): update module github.com/microsoft/go-mssqldb to v1.9.2 (#767)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
|
[github.com/microsoft/go-mssqldb](https://redirect.github.com/microsoft/go-mssqldb)
| `v1.9.1` -> `v1.9.2` |
[![age](https://developer.mend.io/api/mc/badges/age/go/github.com%2fmicrosoft%2fgo-mssqldb/v1.9.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/go/github.com%2fmicrosoft%2fgo-mssqldb/v1.9.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/go/github.com%2fmicrosoft%2fgo-mssqldb/v1.9.1/v1.9.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/github.com%2fmicrosoft%2fgo-mssqldb/v1.9.1/v1.9.2?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>microsoft/go-mssqldb
(github.com/microsoft/go-mssqldb)</summary>

###
[`v1.9.2`](https://redirect.github.com/microsoft/go-mssqldb/compare/v1.9.1...v1.9.2)

[Compare
Source](https://redirect.github.com/microsoft/go-mssqldb/compare/v1.9.1...v1.9.2)

</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:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->
2025-06-30 17:06:28 +00:00
Wenxin Du
a1b60100c2 chore: Group tools by type (#743)
Group tools of the same type into the same folder so that they are more
discoverable and our tools are more organized as the number grows.
2025-06-30 11:37:48 -04:00
Mend Renovate
cb92883330 chore(deps): update module cloud.google.com/go/spanner to v1.83.0 (#763)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
|
[cloud.google.com/go/spanner](https://redirect.github.com/googleapis/google-cloud-go)
| `v1.82.0` -> `v1.83.0` |
[![age](https://developer.mend.io/api/mc/badges/age/go/cloud.google.com%2fgo%2fspanner/v1.83.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/go/cloud.google.com%2fgo%2fspanner/v1.83.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/go/cloud.google.com%2fgo%2fspanner/v1.82.0/v1.83.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.82.0/v1.83.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:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->
2025-06-27 19:06:39 +00:00
Mend Renovate
bd2f1956bd chore(deps): update module github.com/valkey-io/valkey-go to v1.0.62 (#762)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
|
[github.com/valkey-io/valkey-go](https://redirect.github.com/valkey-io/valkey-go)
| `v1.0.61` -> `v1.0.62` |
[![age](https://developer.mend.io/api/mc/badges/age/go/github.com%2fvalkey-io%2fvalkey-go/v1.0.62?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/go/github.com%2fvalkey-io%2fvalkey-go/v1.0.62?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/go/github.com%2fvalkey-io%2fvalkey-go/v1.0.61/v1.0.62?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.61/v1.0.62?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.62`](https://redirect.github.com/valkey-io/valkey-go/releases/tag/v1.0.62):
1.0.62

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

### Changes

- feat: support the SendToReplicas option in the Sentinel client.
- feat: deterministic SendToReplicas routing in the Cluster client.
- perf: changed atomic.Value to atomic.Pointer in the pipe.
- docs: fix typos and spellings.

#### Contributors

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

[@&#8203;PingXie](https://redirect.github.com/PingXie),
[@&#8203;jsoref](https://redirect.github.com/jsoref),
[@&#8203;nithinputhenveettil](https://redirect.github.com/nithinputhenveettil),
[@&#8203;proost](https://redirect.github.com/proost) and
[@&#8203;rueian](https://redirect.github.com/rueian)

</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:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->
2025-06-27 12:32:36 -04:00
Yuan
cbb4a33351 fix(tools/mysqlsql): Handle nil panic and connection leak in invoke (#758)
Copy fix from #757. 

The Invoke function had two bugs:

A panic would occur when scanning a row containing a NULL value in a
TEXT or VARCHAR column. The code did not check for nil before attempting
a type assertion on the scanned value.

The *sql.Rows result was not being closed on all code paths, leading to
connection leaks that could exhaust the database connection pool.

This change corrects both issues. A guard clause now checks for nil
values before processing, and rows.Close() is deferred to guarantee the
connection is released.
2025-06-26 20:54:59 +00:00
megatron0000
7badba42ee fix(tools/mysqlexecutesql): Handle nil panic and connection leak in Invoke (#757)
The Invoke function had two bugs:

1. A panic would occur when scanning a row containing a NULL value in a
TEXT or VARCHAR column. The code did not check for nil before attempting
a type assertion on the scanned value.

2. The *sql.Rows result was not being closed on all code paths, leading
to connection leaks that could exhaust the database connection pool.

This change corrects both issues. A guard clause now checks for nil
values before processing, and rows.Close() is deferred to guarantee the
connection is released.

Co-authored-by: Yuan <45984206+Yuan325@users.noreply.github.com>
2025-06-26 13:12:45 -07:00
Twisha Bansal
f72e426314 docs: fix grammar (#751) 2025-06-26 10:40:15 +05:30
Wenxin Du
7a6644cf0c fix(bigquery,mssql): fix panic on tools with array param (#722)
Fix: https://github.com/googleapis/genai-toolbox/issues/701

Things done:
1. Replace the `AsReversedMap()` helper with `AsMap()`
2. BigQuery's QueryParameter only accepts typed slices as input, but our
arrays are passed in as []any. Therefore, add a logic to convert []any
to a typed array based on the item type.

Tested on MCP inspector:
<img width="409" alt="Screenshot 2025-06-16 at 5 15 55 PM"
src="https://github.com/user-attachments/assets/8053cad5-270e-4d82-b97c-856238c42154"
/>

---------

Co-authored-by: Kurtis Van Gent <31518063+kurtisvg@users.noreply.github.com>
2025-06-25 22:54:26 -04:00
Mend Renovate
184c681797 chore(deps): update module google.golang.org/api to v0.239.0 (#754)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
|
[google.golang.org/api](https://redirect.github.com/googleapis/google-api-go-client)
| `v0.238.0` -> `v0.239.0` |
[![age](https://developer.mend.io/api/mc/badges/age/go/google.golang.org%2fapi/v0.239.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/go/google.golang.org%2fapi/v0.239.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/go/google.golang.org%2fapi/v0.238.0/v0.239.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/google.golang.org%2fapi/v0.238.0/v0.239.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.239.0`](https://redirect.github.com/googleapis/google-api-go-client/releases/tag/v0.239.0)

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

##### Features

- **all:** Auto-regenerate discovery clients
([#&#8203;3199](https://redirect.github.com/googleapis/google-api-go-client/issues/3199))
([2bdd042](2bdd042ac9))
- **all:** Auto-regenerate discovery clients
([#&#8203;3201](https://redirect.github.com/googleapis/google-api-go-client/issues/3201))
([8eff56f](8eff56f43f))
- **all:** Auto-regenerate discovery clients
([#&#8203;3202](https://redirect.github.com/googleapis/google-api-go-client/issues/3202))
([f7c299e](f7c299e9c0))
- **all:** Auto-regenerate discovery clients
([#&#8203;3203](https://redirect.github.com/googleapis/google-api-go-client/issues/3203))
([459c5a8](459c5a8db5))
- **all:** Auto-regenerate discovery clients
([#&#8203;3205](https://redirect.github.com/googleapis/google-api-go-client/issues/3205))
([ca610d5](ca610d5390))
- **all:** Auto-regenerate discovery clients
([#&#8203;3206](https://redirect.github.com/googleapis/google-api-go-client/issues/3206))
([98b7398](98b739881e))
- **all:** Auto-regenerate discovery clients
([#&#8203;3207](https://redirect.github.com/googleapis/google-api-go-client/issues/3207))
([71fe287](71fe287d9c))
- **all:** Auto-regenerate discovery clients
([#&#8203;3209](https://redirect.github.com/googleapis/google-api-go-client/issues/3209))
([27d1aa4](27d1aa43d1))

</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:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: Yuan <45984206+Yuan325@users.noreply.github.com>
2025-06-26 01:26:08 +00:00
Yuan
474df57d62 feat: support MCP version 2025-03-26 (#755)
This feature includes the following:
* Implement initialize lifecycle (including version negotiation)
* Add the v20250326 schema
* Supporting the `DELETE` and `GET` endpoint for MCP.
* Supporting streamable HTTP (without SSE).
* Terminating sessions after timeout (default = 10 minutes from last
active).
* Toolbox do not support batch request. Will response with `Invalid
requests` if batch requests is received.
2025-06-26 00:34:37 +00:00
Wenxin Du
fc1a3813ea ci: Add integration test coverage by source (#742)
Add a script for checking coverage for each source package with its
compatible tools.
Fail with coverage under 50%
2025-06-25 15:07:00 -04:00
Yuan
c7fe3c7f38 docs: fix linting in docs (#749)
Fix long lines and table column width lints in docs.
2025-06-25 17:03:42 +00:00
Anubhav Dhawan
dc2690bd39 docs: Document correct syntax for array parameters in SQL queries (#750)
## Problem

Users attempting to filter results in a SQL query based on an array
parameter from a tool may intuitively write a `statement` using the `IN`
clause, like so:
```sql
SELECT * FROM flights WHERE preferred_airlines IN ($1);
```
When this query is executed with an array argument (e.g., `["Delta",
"United"]`), it fails with a cryptic error message from the database
driver:
```
Exception: error while invoking tool: unable to execute query: failed to encode args[0]: unable to encode []interface {}{"Delta", "United"} into text format for text (OID 25): cannot find encode plan
```
This error occurs because the driver does not automatically expand the
single `$1` placeholder into a list of values `('Delta', 'United')`.
Instead, it tries to encode the entire Go slice `[]interface{}` as a
single text value, which fails. This creates a point of friction, as the
correct syntax is not immediately obvious and can lead to user
frustration and debugging time.

## Solution
This PR updates our documentation and example usage to demonstrate the
correct SQL syntax for handling array parameters. The proper way to
check for a value's existence in an array parameter is by using
PostgreSQL's `ANY()` operator:

```sql
SELECT * FROM flights WHERE preferred_airlines = ANY($1);
```
When this syntax is used, the database driver correctly interprets the
Go slice passed as `$1` as a PostgreSQL array, and the query executes as
intended.

## Impact
Saves developers time they would otherwise spend troubleshooting a
non-obvious database driver behavior.
2025-06-25 20:32:30 +05:30
Mend Renovate
b78f7480cf chore(deps): update module github.com/microsoft/go-mssqldb to v1.9.1 (#746)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
|
[github.com/microsoft/go-mssqldb](https://redirect.github.com/microsoft/go-mssqldb)
| `v1.8.2` -> `v1.9.1` |
[![age](https://developer.mend.io/api/mc/badges/age/go/github.com%2fmicrosoft%2fgo-mssqldb/v1.9.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/go/github.com%2fmicrosoft%2fgo-mssqldb/v1.9.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/go/github.com%2fmicrosoft%2fgo-mssqldb/v1.8.2/v1.9.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/go/github.com%2fmicrosoft%2fgo-mssqldb/v1.8.2/v1.9.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>microsoft/go-mssqldb
(github.com/microsoft/go-mssqldb)</summary>

###
[`v1.9.1`](https://redirect.github.com/microsoft/go-mssqldb/compare/v1.9.0...v1.9.1)

[Compare
Source](https://redirect.github.com/microsoft/go-mssqldb/compare/v1.9.0...v1.9.1)

###
[`v1.9.0`](https://redirect.github.com/microsoft/go-mssqldb/compare/v1.8.2...v1.9.0)

[Compare
Source](https://redirect.github.com/microsoft/go-mssqldb/compare/v1.8.2...v1.9.0)

</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:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: Yuan <45984206+Yuan325@users.noreply.github.com>
2025-06-24 23:38:49 +00:00
Mend Renovate
ffe9b74211 chore(deps): update module github.com/redis/go-redis/v9 to v9.11.0 (#745)
This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
|
[github.com/redis/go-redis/v9](https://redirect.github.com/redis/go-redis)
| `v9.10.0` -> `v9.11.0` |
[![age](https://developer.mend.io/api/mc/badges/age/go/github.com%2fredis%2fgo-redis%2fv9/v9.11.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/go/github.com%2fredis%2fgo-redis%2fv9/v9.11.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/go/github.com%2fredis%2fgo-redis%2fv9/v9.10.0/v9.11.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.10.0/v9.11.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

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

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

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

#### 🚀 Highlights

Fixes `TxPipeline` to work correctly in cluster scenarios, allowing
execution of commands
only in the same slot for a given transaction.

### Changes

#### 🚀 New Features

- Set cluster slot for `scan` commands, rather than random
([#&#8203;2623](https://redirect.github.com/redis/go-redis/pull/2623))
- Add CredentialsProvider field to UniversalOptions
([#&#8203;2927](https://redirect.github.com/redis/go-redis/pull/2927))
- feat(redisotel): add WithCallerEnabled option
([#&#8203;3415](https://redirect.github.com/redis/go-redis/pull/3415))

#### 🐛 Bug Fixes

- fix(txpipeline): keyless commands should take the slot of the keyed
([#&#8203;3411](https://redirect.github.com/redis/go-redis/pull/3411))
- fix(loading): cache the loaded flag for slave nodes
([#&#8203;3410](https://redirect.github.com/redis/go-redis/pull/3410))
- fix(txpipeline): should return error on multi/exec on multiple slots
([#&#8203;3408](https://redirect.github.com/redis/go-redis/pull/3408))
- fix: check if the shard exists to avoid returning nil
([#&#8203;3396](https://redirect.github.com/redis/go-redis/pull/3396))

#### 🧰 Maintenance

- feat: optimize connection pool waitTurn
([#&#8203;3412](https://redirect.github.com/redis/go-redis/pull/3412))
- chore(ci): update CI redis builds
([#&#8203;3407](https://redirect.github.com/redis/go-redis/pull/3407))
- chore: remove a redundant method from `Ring`, `Client` and
`ClusterClient`
([#&#8203;3401](https://redirect.github.com/redis/go-redis/pull/3401))
- test: refactor TestBasicCredentials using table-driven tests
([#&#8203;3406](https://redirect.github.com/redis/go-redis/pull/3406))
- perf: reduce unnecessary memory allocation operations
([#&#8203;3399](https://redirect.github.com/redis/go-redis/pull/3399))
- fix: insert entry during iterating over a map
([#&#8203;3398](https://redirect.github.com/redis/go-redis/pull/3398))
- DOC-5229 probabilistic data type examples
([#&#8203;3413](https://redirect.github.com/redis/go-redis/pull/3413))
- chore(deps): bump rojopolis/spellcheck-github-actions from 0.49.0 to
0.51.0
([#&#8203;3414](https://redirect.github.com/redis/go-redis/pull/3414))

#### 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;boekkooi-impossiblecloud](https://redirect.github.com/boekkooi-impossiblecloud),
[@&#8203;cxljs](https://redirect.github.com/cxljs),
[@&#8203;dcherubini](https://redirect.github.com/dcherubini),
[@&#8203;iamamirsalehi](https://redirect.github.com/iamamirsalehi),
[@&#8203;ndyakov](https://redirect.github.com/ndyakov),
[@&#8203;pete-woods](https://redirect.github.com/pete-woods),
[@&#8203;twz915](https://redirect.github.com/twz915)

</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:eyJjcmVhdGVkSW5WZXIiOiI0MC42Mi4xIiwidXBkYXRlZEluVmVyIjoiNDAuNjIuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->

Co-authored-by: Yuan <45984206+Yuan325@users.noreply.github.com>
2025-06-24 23:27:34 +00:00
Averi Kitsch
e1355660d4 chore: Update Developer and Contributing docs (#738)
Co-authored-by: Wenxin Du <117315983+duwenxin99@users.noreply.github.com>
2025-06-24 13:58:47 -07:00
Twisha Bansal
d8e2abe2dd fix: fix adk quickstart (#741) 2025-06-24 12:04:11 +05:30
Twisha Bansal
7b3539e9ff chore: reorder quickstart (#740)
Reorder quickstart.
Order from
`GoogleGenAI -> ADK -> Langchain -> Llamaindex`

to
`ADK -> Langchain -> Llamaindex -> GoogleGenAI`
2025-06-24 11:55:01 +05:30
142 changed files with 4406 additions and 2238 deletions

View File

@@ -33,7 +33,9 @@ steps:
- name: "go"
path: "/gopath"
script: |
go test -c -race ./tests/...
go test -c -race -cover \
-coverpkg=./internal/sources/...,./internal/tools/... ./tests/...
chmod +x .ci/test_with_coverage.sh
- id: "cloud-sql-pg"
name: golang:1
@@ -54,7 +56,12 @@ steps:
args:
- -c
- |
./cloudsqlpg.test -test.v
.ci/test_with_coverage.sh \
"Cloud SQL Postgres" \
cloudsqlpg \
postgressql \
postgresexecutesql
- id: "alloydb-pg"
name: golang:1
@@ -75,7 +82,11 @@ steps:
args:
- -c
- |
./alloydbpg.test -test.v
.ci/test_with_coverage.sh \
"AlloyDB Postgres" \
alloydbpg \
postgressql \
postgresexecutesql
- id: "alloydb-ai-nl"
name: golang:1
@@ -96,7 +107,10 @@ steps:
args:
- -c
- |
./alloydbainl.test -test.v
.ci/test_with_coverage.sh \
"AlloyDB AI NL" \
alloydbainl \
alloydbainl
- id: "bigtable"
name: golang:1
@@ -115,7 +129,10 @@ steps:
args:
- -c
- |
./bigtable.test -test.v
.ci/test_with_coverage.sh \
"Bigtable" \
bigtable \
bigtable
- id: "bigquery"
name: golang:1
@@ -132,7 +149,10 @@ steps:
args:
- -c
- |
./bigquery.test -test.v
.ci/test_with_coverage.sh \
"BigQuery" \
bigquery \
bigquery
- id: "postgres"
name: golang:1
@@ -151,7 +171,11 @@ steps:
args:
- -c
- |
./postgres.test -test.v
.ci/test_with_coverage.sh \
"Postgres" \
postgres \
postgressql \
postgresexecutesql
- id: "spanner"
name: golang:1
@@ -170,7 +194,10 @@ steps:
args:
- -c
- |
./spanner.test -test.v
.ci/test_with_coverage.sh \
"Spanner" \
spanner \
spanner
- id: "neo4j"
name: golang:1
@@ -187,7 +214,10 @@ steps:
args:
- -c
- |
./neo4j.test -test.v
.ci/test_with_coverage.sh \
"Neo4j" \
neo4j \
neo4j
- id: "cloud-sql-mssql"
name: golang:1
@@ -208,7 +238,10 @@ steps:
args:
- -c
- |
./cloudsqlmssql.test -test.v
.ci/test_with_coverage.sh \
"Cloud SQL MSSQL" \
cloudsqlmssql \
mssql
- id: "cloud-sql-mysql"
name: golang:1
@@ -229,7 +262,10 @@ steps:
args:
- -c
- |
./cloudsqlmysql.test -test.v
.ci/test_with_coverage.sh \
"Cloud SQL MySQL" \
cloudsqlmysql \
mysql
- id: "mysql"
name: golang:1
@@ -248,7 +284,10 @@ steps:
args:
- -c
- |
./mysql.test -test.v
.ci/test_with_coverage.sh \
"MySQL" \
mysql \
mysql
- id: "mssql"
name: golang:1
@@ -267,7 +306,10 @@ steps:
args:
- -c
- |
./mssql.test -test.v
.ci/test_with_coverage.sh \
"MSSQL" \
mssql \
mssql
- id: "dgraph"
name: golang:1
@@ -282,7 +324,10 @@ steps:
args:
- -c
- |
./dgraph.test -test.v
.ci/test_with_coverage.sh \
"Dgraph" \
dgraph \
dgraph
- id: "http"
name: golang:1
@@ -297,7 +342,10 @@ steps:
args:
- -c
- |
./http.test -test.v
.ci/test_with_coverage.sh \
"HTTP" \
http \
http
- id: "sqlite"
name: golang:1
@@ -313,7 +361,10 @@ steps:
args:
- -c
- |
./sqlite.test -test.v
.ci/test_with_coverage.sh \
"SQLite" \
sqlite \
sqlite
- id: "couchbase"
name : golang:1
@@ -331,7 +382,10 @@ steps:
args:
- -c
- |
./couchbase.test -test.v
.ci/test_with_coverage.sh \
"Couchbase" \
couchbase \
couchbase
- id: "redis"
name : golang:1
@@ -347,7 +401,10 @@ steps:
args:
- -c
- |
./redis.test -test.v
.ci/test_with_coverage.sh \
"Redis" \
redis \
redis
- id: "valkey"
name : golang:1
@@ -364,7 +421,10 @@ steps:
args:
- -c
- |
./valkey.test -test.v
.ci/test_with_coverage.sh \
"Valkey" \
valkey \
valkey
availableSecrets:
@@ -404,7 +464,7 @@ availableSecrets:
- versionName: projects/$PROJECT_ID/secrets/mysql_pass/versions/latest
env: MYSQL_PASS
- versionName: projects/$PROJECT_ID/secrets/mssql_user/versions/latest
env: MSSQL_USER
env: MSSQL_USER
- versionName: projects/$PROJECT_ID/secrets/mssql_pass/versions/latest
env: MSSQL_PASS
- versionName: projects/$PROJECT_ID/secrets/couchbase_connection/versions/latest

60
.ci/test_with_coverage.sh Executable file
View File

@@ -0,0 +1,60 @@
#!/bin/bash
# Arguments:
# $1: Display name for logs (e.g., "Cloud SQL Postgres")
# $2: Source package name (e.g., cloudsqlpg)
# $3, $4, ...: Tool package names for grep (e.g., postgressql)
DISPLAY_NAME="$1"
SOURCE_PACKAGE_NAME="$2"
# Construct the test binary name
TEST_BINARY="${SOURCE_PACKAGE_NAME}.test"
# Construct the full source path
SOURCE_PATH="sources/${SOURCE_PACKAGE_NAME}/"
# Shift arguments so that $3 and onwards become the list of tool package names
shift 2
TOOL_PACKAGE_NAMES=("$@")
COVERAGE_FILE="${TEST_BINARY%.test}_coverage.out"
FILTERED_COVERAGE_FILE="${TEST_BINARY%.test}_filtered_coverage.out"
export path="github.com/googleapis/genai-toolbox/internal/"
GREP_PATTERN="^mode:|${path}${SOURCE_PATH}"
# Add each tool package path to the grep pattern
for tool_name in "${TOOL_PACKAGE_NAMES[@]}"; do
if [ -n "$tool_name" ]; then
full_tool_path="tools/${tool_name}/"
GREP_PATTERN="${GREP_PATTERN}|${path}${full_tool_path}"
fi
done
# Run integration test
if ! ./"${TEST_BINARY}" -test.v -test.coverprofile="${COVERAGE_FILE}"; then
echo "Error: Tests for ${DISPLAY_NAME} failed. Exiting."
exit 1
fi
# Filter source/tool packages
if ! grep -E "${GREP_PATTERN}" "${COVERAGE_FILE}" > "${FILTERED_COVERAGE_FILE}"; then
echo "Warning: Could not filter coverage for ${DISPLAY_NAME}. Filtered file might be empty or invalid."
fi
# Calculate coverage
echo "Calculating coverage for ${DISPLAY_NAME}..."
total_coverage=$(go tool cover -func="${FILTERED_COVERAGE_FILE}" 2>/dev/null | grep "total:" | awk '{print $3}')
echo "${DISPLAY_NAME} total coverage: $total_coverage"
coverage_numeric=$(echo "$total_coverage" | sed 's/%//')
# Check coverage threshold
if awk -v coverage="$coverage_numeric" 'BEGIN {exit !(coverage < 50)}'; then
echo "Coverage failure: ${DISPLAY_NAME} total coverage($total_coverage) is below 50%."
exit 1
else
echo "Coverage for ${DISPLAY_NAME} is sufficient."
fi

View File

@@ -51,6 +51,7 @@ jobs:
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod
- name: Setup Hugo

View File

@@ -1,5 +1,37 @@
# Changelog
## [0.8.0](https://github.com/googleapis/genai-toolbox/compare/v0.7.0...v0.8.0) (2025-07-02)
### ⚠ BREAKING CHANGES
* **postgres,mssql,cloudsqlmssql:** encode source connection url for sources ([#727](https://github.com/googleapis/genai-toolbox/issues/727))
### Features
* Add support for multiple YAML configuration files ([#760](https://github.com/googleapis/genai-toolbox/issues/760)) ([40679d7](https://github.com/googleapis/genai-toolbox/commit/40679d700eded50d19569923e2a71c51e907a8bf))
* Add support for optional parameters ([#617](https://github.com/googleapis/genai-toolbox/issues/617)) ([4827771](https://github.com/googleapis/genai-toolbox/commit/4827771b78dee9a1284a898b749509b472061527)), closes [#475](https://github.com/googleapis/genai-toolbox/issues/475)
* **mcp:** Support MCP version 2025-03-26 ([#755](https://github.com/googleapis/genai-toolbox/issues/755)) ([474df57](https://github.com/googleapis/genai-toolbox/commit/474df57d62de683079f8d12c31db53396a545fd1))
* **sources/http:** Support disable SSL verification for HTTP Source ([#674](https://github.com/googleapis/genai-toolbox/issues/674)) ([4055b0c](https://github.com/googleapis/genai-toolbox/commit/4055b0c3569c527560d7ad34262963b3dd4e282d))
* **tools/bigquery:** Add templateParameters field for bigquery ([#699](https://github.com/googleapis/genai-toolbox/issues/699)) ([f5f771b](https://github.com/googleapis/genai-toolbox/commit/f5f771b0f3d159630ff602ff55c6c66b61981446))
* **tools/bigtable:** Add templateParameters field for bigtable ([#692](https://github.com/googleapis/genai-toolbox/issues/692)) ([1c06771](https://github.com/googleapis/genai-toolbox/commit/1c067715fac06479eb0060d7067b73dba099ed92))
* **tools/couchbase:** Add templateParameters field for couchbase ([#723](https://github.com/googleapis/genai-toolbox/issues/723)) ([9197186](https://github.com/googleapis/genai-toolbox/commit/9197186b8bea1ac4ec1b39c9c5c110807c8b2ba9))
* **tools/http:** Add support for HTTP Tool pathParams ([#726](https://github.com/googleapis/genai-toolbox/issues/726)) ([fd300dc](https://github.com/googleapis/genai-toolbox/commit/fd300dc606d88bf9f7bba689e2cee4e3565537dd))
* **tools/redis:** Add Redis Source and Tool ([#519](https://github.com/googleapis/genai-toolbox/issues/519)) ([f0aef29](https://github.com/googleapis/genai-toolbox/commit/f0aef29b0c2563e2a00277fbe2784f39f16d2835))
* **tools/spanner:** Add templateParameters field for spanner ([#691](https://github.com/googleapis/genai-toolbox/issues/691)) ([075dfa4](https://github.com/googleapis/genai-toolbox/commit/075dfa47e1fd92be4847bd0aec63296146b66455))
* **tools/sqlitesql:** Add templateParameters field for sqlitesql ([#687](https://github.com/googleapis/genai-toolbox/issues/687)) ([75e254c](https://github.com/googleapis/genai-toolbox/commit/75e254c0a4ce690ca5fa4d1741550ce54734b226))
* **tools/valkey:** Add Valkey Source and Tool ([#532](https://github.com/googleapis/genai-toolbox/issues/532)) ([054ec19](https://github.com/googleapis/genai-toolbox/commit/054ec198b97ba9f36f67dd12b2eff0cc6bc4d080))
### Bug Fixes
* **bigquery,mssql:** Fix panic on tools with array param ([#722](https://github.com/googleapis/genai-toolbox/issues/722)) ([7a6644c](https://github.com/googleapis/genai-toolbox/commit/7a6644cf0c5413e5c803955c88a2cfd0a2233ed3))
* **postgres,mssql,cloudsqlmssql:** Encode source connection url for sources ([#727](https://github.com/googleapis/genai-toolbox/issues/727)) ([67964d9](https://github.com/googleapis/genai-toolbox/commit/67964d939f27320b63b5759f4b3f3fdaa0c76fbf)), closes [#717](https://github.com/googleapis/genai-toolbox/issues/717)
* Set default value to field's type during unmarshalling ([#774](https://github.com/googleapis/genai-toolbox/issues/774)) ([fafed24](https://github.com/googleapis/genai-toolbox/commit/fafed2485839cf1acc1350e8a24103d2e6356ee0)), closes [#771](https://github.com/googleapis/genai-toolbox/issues/771)
* **server/mcp:** Do not listen from port for stdio ([#719](https://github.com/googleapis/genai-toolbox/issues/719)) ([d51dbc7](https://github.com/googleapis/genai-toolbox/commit/d51dbc759ba493021d3ec6f5417fc04c21f7044f)), closes [#711](https://github.com/googleapis/genai-toolbox/issues/711)
* **tools/mysqlexecutesql:** Handle nil panic and connection leak in Invoke ([#757](https://github.com/googleapis/genai-toolbox/issues/757)) ([7badba4](https://github.com/googleapis/genai-toolbox/commit/7badba42eefb34252be77b852a57d6bd78dd267d))
* **tools/mysqlsql:** Handle nil panic and connection leak in invoke ([#758](https://github.com/googleapis/genai-toolbox/issues/758)) ([cbb4a33](https://github.com/googleapis/genai-toolbox/commit/cbb4a333517313744800d148840312e56340f3fd))
## [0.7.0](https://github.com/googleapis/genai-toolbox/compare/v0.6.0...v0.7.0) (2025-06-10)

View File

@@ -14,21 +14,21 @@ race, religion, or sexual identity and orientation.
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
@@ -75,7 +75,7 @@ receive and address reported violations of the code of conduct. They will then
work with a committee consisting of representatives from the Open Source
Programs Office and the Google Open Source Strategy team. If for any reason you
are uncomfortable reaching out to the Project Steward, please email
opensource@google.com.
<opensource@google.com>.
We will investigate every complaint, but you may not receive a direct response.
We will use our discretion in determining when and how to follow up on reported
@@ -90,4 +90,4 @@ harassment or threats to anyone's safety, we may take action without notice.
This Code of Conduct is adapted from the Contributor Covenant, version 1.4,
available at
https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
<https://www.contributor-covenant.org/version/1/4/code-of-conduct.html>

View File

@@ -30,4 +30,153 @@ This project follows
All submissions, including submissions by project members, require review. We
use GitHub pull requests for this purpose. Consult
[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
information on using pull requests.
information on using pull requests.
Within 2-5 days, a reviewer will review your PR. They may approve it, or request
changes. When requesting changes, reviewers should self-assign the PR to ensure
they are aware of any updates.
If additional changes are needed, push additional commits to your PR branch -
this helps the reviewer know which parts of the PR have changed. Commits will be
squashed when merged.
Please follow up with changes promptly. If a PR is awaiting changes by the
author for more than 10 days, maintainers may mark that PR as Draft. PRs that
are inactive for more than 30 days may be closed.
### Adding a New Database Source and Tool
We recommend creating an
[issue](https://github.com/googleapis/genai-toolbox/issues) before
implementation to ensure we can accept the contribution and no duplicated work.
If you have any questions, reach out on our
[Discord](https://discord.gg/Dmm69peqjh) to chat directly with the team. New
contributions should be added with both unit tests and integration tests.
#### 1. Implement the New Data Source
We recommend looking at an [example source
implementation](https://github.com/googleapis/genai-toolbox/blob/main/internal/sources/postgres/postgres.go).
* **Create a new directory** under `internal/sources` for your database type
(e.g., `internal/sources/newdb`).
* **Define a configuration struct** for your data source in a file named
`newdb.go`. Create a `Config` struct to include all the necessary parameters
for connecting to the database (e.g., host, port, username, password, database
name) and a `Source` struct to store necessary parameters for tools (e.g.,
Name, Kind, connection object, additional config).
* **Implement the
[`SourceConfig`](https://github.com/googleapis/genai-toolbox/blob/fd300dc606d88bf9f7bba689e2cee4e3565537dd/internal/sources/sources.go#L57)
interface**. This interface requires two methods:
* `SourceConfigKind() string`: Returns a unique string identifier for your
data source (e.g., `"newdb"`).
* `Initialize(ctx context.Context, tracer trace.Tracer) (Source, error)`:
Creates a new instance of your data source and establishes a connection to
the database.
* **Implement the
[`Source`](https://github.com/googleapis/genai-toolbox/blob/fd300dc606d88bf9f7bba689e2cee4e3565537dd/internal/sources/sources.go#L63)
interface**. This interface requires one method:
* `SourceKind() string`: Returns the same string identifier as `SourceConfigKind()`.
* **Implement `init()`** to register the new Source.
* **Implement Unit Tests** in a file named `newdb_test.go`.
#### 2. Implement the New Tool
We recommend looking at an [example tool
implementation](https://github.com/googleapis/genai-toolbox/tree/main/internal/tools/postgressql).
* **Create a new directory** under `internal/tools` for your tool type (e.g.,
`internal/tools/newdb` or `internal/tools/newdb<tool_name>`).
* **Define a configuration struct** for your tool in a file named `newdbtool.go`.
Create a `Config` struct and a `Tool` struct to store necessary parameters for
tools.
* **Implement the
[`ToolConfig`](https://github.com/googleapis/genai-toolbox/blob/fd300dc606d88bf9f7bba689e2cee4e3565537dd/internal/tools/tools.go#L61)
interface**. This interface requires one method:
* `ToolConfigKind() string`: Returns a unique string identifier for your tool
(e.g., `"newdb"`).
* `Initialize(sources map[string]Source) (Tool, error)`: Creates a new
instance of your tool and validates that it can connect to the specified
data source.
* **Implement the `Tool` interface**. This interface requires the following
methods:
* `Invoke(ctx context.Context, params map[string]any) ([]any, error)`:
Executes the operation on the database using the provided parameters.
* `ParseParams(data map[string]any, claims map[string]map[string]any)
(ParamValues, error)`: Parses and validates the input parameters.
* `Manifest() Manifest`: Returns a manifest describing the tool's capabilities
and parameters.
* `McpManifest() McpManifest`: Returns an MCP manifest describing the tool for
use with the Model Context Protocol.
* `Authorized(services []string) bool`: Checks if the tool is authorized to
run based on the provided authentication services.
* **Implement `init()`** to register the new Tool.
* **Implement Unit Tests** in a file named `newdb_test.go`.
#### 3. Add Integration Tests
* **Add a test file** under a new directory `tests/newdb`.
* **Add pre-defined integration test suites** in the
`/tests/newdb/newdb_test.go` that are **required** to be run as long as your
code contains related features:
1. [RunToolGetTest][tool-get]: tests for the `GET` endpoint that returns the
tool's manifest.
2. [RunToolInvokeTest][tool-call]: tests for tool calling through the native
Toolbox endpoints.
3. [RunMCPToolCallMethod][mcp-call]: tests tool calling through the MCP
endpoints.
4. (Optional) [RunExecuteSqlToolInvokeTest][execute-sql]: tests an
`execute-sql` tool for any source. Only run this test if you are adding an
`execute-sql` tool.
5. (Optional) [RunToolInvokeWithTemplateParameters][temp-param]: tests for [template
parameters][temp-param-doc]. Only run this test if template
parameters apply to your tool.
* **Add the new database to the test config** in
[integration.cloudbuild.yaml](.ci/integration.cloudbuild.yaml).
[tool-get]:
https://github.com/googleapis/genai-toolbox/blob/fd300dc606d88bf9f7bba689e2cee4e3565537dd/tests/tool.go#L31
[tool-call]:
<https://github.com/googleapis/genai-toolbox/blob/fd300dc606d88bf9f7bba689e2cee4e3565537dd/tests/tool.go#L79>
[mcp-call]:
https://github.com/googleapis/genai-toolbox/blob/fd300dc606d88bf9f7bba689e2cee4e3565537dd/tests/tool.go#L554
[execute-sql]:
<https://github.com/googleapis/genai-toolbox/blob/fd300dc606d88bf9f7bba689e2cee4e3565537dd/tests/tool.go#L431>
[temp-param]:
<https://github.com/googleapis/genai-toolbox/blob/fd300dc606d88bf9f7bba689e2cee4e3565537dd/tests/tool.go#L297>
[temp-param-doc]:
https://googleapis.github.io/genai-toolbox/resources/tools/#template-parameters
#### 4. Add Documentation
* **Update the documentation** to include information about your new data source
and tool. This includes:
* Adding a new page to the `docs/en/resources/sources` directory for your data
source.
* Adding a new page to the `docs/en/resources/tools` directory for your tool.
* **(Optional) Add samples** to the `docs/en/samples/<newdb>` directory.
#### (Optional) 5. Add Prebuilt Tools
You can provide developers with a set of "build-time" tools to aid common
software development user journeys like viewing and creating tables/collections
and data.
* **Create a set of prebuilt tools** by defining a new `tools.yaml` and adding
it to `internal/tools`. Make sure the file name matches the source (i.e. for
source "alloydb-postgres" create a file named "alloydb-postgres.yaml").
* **Update `cmd/root.go`** to add new source to the `prebuilt` flag.
* **Add tests** in
[internal/prebuiltconfigs/prebuiltconfigs_test.go](internal/prebuiltconfigs/prebuiltconfigs_test.go)
and [cmd/root_test.go](cmd/root_test.go).
#### 6. Submit a Pull Request
* **Submit a pull request** to the repository with your changes. Be sure to
include a detailed description of your changes and any requests for long term
testing resources.

View File

@@ -1,12 +1,16 @@
# DEVELOPER.md
## Before you begin
This document provides instructions for setting up your development environment
and contributing to the Toolbox project.
1. Make sure you've setup your databases.
## Prerequisites
1. Install the latest version of [Go](https://go.dev/doc/install).
Before you begin, ensure you have the following:
1. Locate and download dependencies:
1. **Databases:** Set up the necessary databases for your development
environment.
1. **Go:** Install the latest version of [Go](https://go.dev/doc/install).
1. **Dependencies:** Download and manage project dependencies:
```bash
go get
@@ -15,196 +19,224 @@
## Developing Toolbox
### Run Toolbox from local source
### Running from Local Source
1. Create a `tools.yaml` file with your [sources and tools configurations](./README.md#Configuration).
1. You can specify flags for the Toolbox server. Execute the following to list the possible CLI flags:
1. **Configuration:** Create a `tools.yaml` file to configure your sources and
tools. See the [Configuration section in the
README](./README.md#Configuration) for details.
1. **CLI Flags:** List available command-line flags for the Toolbox server:
```bash
go run . --help
```
1. To run the server, execute the following (with any flags, if applicable):
1. **Running the Server:** Start the Toolbox server with optional flags. The
server listens on port 5000 by default.
```bash
go run .
```
The server will listen on port 5000 (by default).
1. Test endpoint using the following:
1. **Testing the Endpoint:** Verify the server is running by sending a request
to the endpoint:
```bash
curl http://127.0.0.1:5000
```
### Testing
## Testing
#### Writing Tests
### Infrastructure
New contributions should be added with both unit tests and integration tests.
Toolbox uses both GitHub Actions and Cloud Build to run test workflows. Cloud
Build is used when Google credentials are required. Cloud Build uses test
project "toolbox-testing-438616".
- Unit tests are in the same package as the source code.
### Linting
- Integration tests are in the `/tests` directory, under its dedicated package
named after its source. We have several pre-defined integration test suites
in the `/tests/tool.go` that are **required** to be run as long as your code contains related
features:
Run the lint check to ensure code quality:
1. [RunToolGetTest][tool-get]: tests for the `GET` endpoint that returns the
tool's manifest.
2. [RunToolInvokeTest][tool-call]: tests for tool calling through the native
Toolbox endpoints.
```bash
golangci-lint run --fix
```
3. [RunMCPToolCallMethod][mcp-call]: tests tool calling through the MCP
endpoints.
4. (Optional) [RunExecuteSqlToolInvokeTest][execute-sql]: tests an
`execute-sql` tool for any source. Only run this test if you are adding an
`execute-sql` tool.
### Unit Tests
5. (Optional) [RunToolInvokeWithTemplateParameters][temp-param]: tests for [template
parameters][temp-param-doc]. Only run this test if template parameters apply to your tool.
[tool-get]:
https://github.com/googleapis/genai-toolbox/blob/fd300dc606d88bf9f7bba689e2cee4e3565537dd/tests/tool.go#L31
[tool-call]:
<https://github.com/googleapis/genai-toolbox/blob/fd300dc606d88bf9f7bba689e2cee4e3565537dd/tests/tool.go#L79>
[mcp-call]:
https://github.com/googleapis/genai-toolbox/blob/fd300dc606d88bf9f7bba689e2cee4e3565537dd/tests/tool.go#L554
[execute-sql]:
<https://github.com/googleapis/genai-toolbox/blob/fd300dc606d88bf9f7bba689e2cee4e3565537dd/tests/tool.go#L431>
[temp-param]:
<https://github.com/googleapis/genai-toolbox/blob/fd300dc606d88bf9f7bba689e2cee4e3565537dd/tests/tool.go#L297>
[temp-param-doc]:
https://googleapis.github.io/genai-toolbox/resources/tools/#template-parameters
Execute unit tests locally:
#### Running Tests
```bash
go test -race -v ./...
```
- Run the lint check:
### Integration Tests
```bash
golangci-lint run --fix
#### Running Locally
1. **Environment Variables:** Set the required environment variables. Refer to
the [Cloud Build testing configuration](./.ci/integration.cloudbuild.yaml)
for a complete list of variables for each source.
* `SERVICE_ACCOUNT_EMAIL`: Use your own GCP email.
* `CLIENT_ID`: Use the Google Cloud SDK application Client ID. Contact
Toolbox maintainers if you don't have it.
1. **Running Tests:** Run the integration test for your target source. Specify
the required Go build tags at the top of each integration test file.
```shell
go test -race -v ./tests/<YOUR_TEST_DIR>
```
- Run unit tests locally:
For example, to run the AlloyDB integration test:
```bash
go test -race -v ./...
```shell
go test -race -v ./tests/alloydbpg
```
- Run integration tests locally:
1. Set required environment variables. For a complete lists of required
vairables for each source, check out the [Cloud Build testing
configuration](./.ci/integration.cloudbuild.yaml).
- Use your own GCP email as the `SERVICE_ACCOUNT_EMAIL`.
- Use the Google Cloud SDK application Client ID as the `CLIENT_ID`. Ask the
Toolbox maintainers if you don't know it already.
#### Running on Pull Requests
2. Run the integration test for your target source with the required Go
build tags specified at the top of each integration test file:
* **Internal Contributors:** Testing workflows should trigger automatically.
* **External Contributors:** Request Toolbox maintainers to trigger the testing
workflows on your PR.
```shell
go test -race -v ./tests/<YOUR_TEST_DIR>
```
#### Test Resources
For example, to run the AlloyDB integration test, run:
The following databases have been added as test resources. To add a new database
to test against, please contact the Toolbox maintainer team via an issue or PR.
Refer to the [Cloud Build testing
configuration](./.ci/integration.cloudbuild.yaml) for a complete list of
variables for each source.
```shell
go test -race -v ./tests/alloydbpg
```
* AlloyDB - setup in the test project
* AI Natural Language ([setup
instructions](https://cloud.google.com/alloydb/docs/ai/use-natural-language-generate-sql-queries))
has been configured for `alloydb-a`-nl` tool tests
* The Cloud Build service account is a user
* Bigtable - setup in the test project
* The Cloud Build service account is a user
* BigQuery - setup in the test project
* The Cloud Build service account is a user
* Cloud SQL Postgres - setup in the test project
* The Cloud Build service account is a user
* Cloud SQL MySQL - setup in the test project
* The Cloud Build service account is a user
* Cloud SQL SQL Server - setup in the test project
* The Cloud Build service account is a user
* Couchbase - setup in the test project via the Marketplace
* DGraph - using the public dgraph interface <https://play.dgraph.io> for
testing
* Memorystore Redis - setup in the test project using a Memorystore for Redis
standalone instance
* Memorystore Redis Cluster, Memorystore Valkey standalone, and Memorystore
Valkey Cluster instances all require PSC connections, which requires extra
security setup to connect from Cloud Build. Memorystore Redis standalone is
the only one allowing PSA connection.
* The Cloud Build service account is a user
* Memorystore Valkey - setup in the test project using a Memorystore for Redis
standalone instance
* The Cloud Build service account is a user
* MySQL - setup in the test project using a Cloud SQL instance
* Neo4j - setup in the test project on a GCE VM
* Postgres - setup in the test project using an AlloyDB instance
* Spanner - setup in the test project
* The Cloud Build service account is a user
* SQL Server - setup in the test project using a Cloud SQL instance
* SQLite - setup in the integration test, where we create a temporary database
file
- Run integration tests on your PR:
### Other GitHub Checks
For internal contributors, the testing workflows should trigger
automatically. For external contributors, ask the Toolbox
maintainers to trigger the testing workflows on your PR.
* License header check (`.github/header-checker-lint.yml`) - Ensures files have
the appropriate license
* CLA/google - Ensures the developer has signed the CLA:
<https://cla.developers.google.com/>
* conventionalcommits.org - Ensures the commit messages are in the correct
format. This repository uses tool [Release
Please](https://github.com/googleapis/release-please) to create GitHub
releases. It does so by parsing your git history, looking for [Conventional
Commit messages](https://www.conventionalcommits.org/), and creating release
PRs. Learn more by reading [How should I write my
commits?](https://github.com/googleapis/release-please?tab=readme-ov-file#how-should-i-write-my-commits)
## Developing Documentation
Follow these steps to run a Hugo server for local preview:
### Running a Local Hugo Server
1. [Install Hugo](https://gohugo.io/installation/macos/) version 0.146.0+.
1. Move into the `.hugo` directory
Follow these steps to preview documentation changes locally using a Hugo server:
1. **Install Hugo:** Ensure you have
[Hugo](https://gohugo.io/installation/macos/) extended edition version
0.146.0 or later installed.
1. **Navigate to the Hugo Directory:**
```bash
cd .hugo
```
1. Install dependencies
1. **Install Dependencies:**
```bash
npm ci
```
1. Run the server
1. **Start the Server:**
```bash
hugo server
```
### PR documentation preview
### Previewing Documentation on Pull Requests
- For contributors:
#### Contributors
- Ask a repo owner to run the preview deployment workflow on your PR. The preview link
will be commented under your PR automatically.
Request a repo owner to run the preview deployment workflow on your PR. A
preview link will be automatically added as a comment to your PR.
- For maintainers:
#### Maintainers
- Inspect the proposed changes in the PR and ensure that it does not contain
malicious code changes. You should be especially alert to any proposed changes in the
`.github/workflows/` directory that affect workflow files.
- After you make sure the changes are safe, apply the `docs: deploy-preview`
label to the PR to deploy documentation preview.
1. **Inspect Changes:** Review the proposed changes in the PR to ensure they are
safe and do not contain malicious code. Pay close attention to changes in the
`.github/workflows/` directory.
1. **Deploy Preview:** Apply the `docs: deploy-preview` label to the PR to
deploy a documentation preview.
## Compile the app locally
## Building Toolbox
### Compile Toolbox binary
### Building the Binary
1. Run build to compile binary:
1. **Build Command:** Compile the Toolbox binary:
```bash
go build -o toolbox
```
1. You can specify flags for the Toolbox server. Execute the following to list the possible CLI flags:
```bash
./toolbox --help
```
1. To run the binary, execute the following (with any flags, if applicable):
1. **Running the Binary:** Execute the compiled binary with optional flags. The
server listens on port 5000 by default:
```bash
./toolbox
```
The server will listen on port 5000 (by default).
1. Test endpoint using the following:
1. **Testing the Endpoint:** Verify the server is running by sending a request
to the endpoint:
```bash
curl http://127.0.0.1:5000
```
### Compile Toolbox container images
### Building Container Images
1. Run build to compile container image:
1. **Build Command:** Build the Toolbox container image:
```bash
docker build -t toolbox:dev .
```
1. Execute the following to view image:
1. **View Image:** List available Docker images to confirm the build:
```bash
docker images
```
1. Run container image with Docker:
1. **Run Container:** Run the Toolbox container image using Docker:
```bash
docker run -d toolbox:dev
@@ -212,56 +244,118 @@ Follow these steps to run a Hugo server for local preview:
## Developing Toolbox SDKs
Please refer to the [SDK developer guide](https://github.com/googleapis/mcp-toolbox-sdk-python/blob/main/DEVELOPER.md)
Refer to the [SDK developer
guide](https://github.com/googleapis/mcp-toolbox-sdk-python/blob/main/DEVELOPER.md)
for instructions on developing Toolbox SDKs.
## (Optional) Maintainer Information
## Maintainer Information
### Team
Team, `@googleapis/senseai-eco`, has been set as
[CODEOWNERS](.github/CODEOWNERS). The GitHub TeamSync tool is used to create
this team from MDB Group, `senseai-eco`.
### Releasing
There are two types of release for Toolbox, including a versioned release and continuous release.
Toolbox has two types of releases: versioned and continuous. It uses Google
Cloud project, `database-toolbox`.
- Versioned release: Official supported distributions with the `latest` tag. The release process for versioned release is in [versioned.release.cloudbuild.yaml](https://github.com/googleapis/genai-toolbox/blob/main/versioned.release.cloudbuild.yaml).
- Continuous release: Used for early testing features between official supported releases and end-to-end testings.
* **Versioned Release:** Official, supported distributions tagged as `latest`.
The release process is defined in
[versioned.release.cloudbuild.yaml](.ci/versioned.release.cloudbuild.yaml).
* **Continuous Release:** Used for early testing of features between official
releases and for end-to-end testing. The release process is defined in
[continuous.release.cloudbuild.yaml](.ci/continuous.release.cloudbuild.yaml).
* **GitHub Release:** `.github/release-please.yml` automatically creates GitHub
Releases and release PRs.
#### Supported OS and Architecture binaries
### How-to Release a new Version
The following OS and computer architecture is supported within the binary releases.
1. [Optional] If you want to override the version number, send a
[PR](https://github.com/googleapis/genai-toolbox/pull/31) to trigger
[release-please](https://github.com/googleapis/release-please?tab=readme-ov-file#how-do-i-change-the-version-number).
You can generate a commit with the following line: `git commit -m "chore:
release 0.1.0" -m "Release-As: 0.1.0" --allow-empty`
1. [Optional] If you want to edit the changelog, send commits to the release PR
1. Approve and merge the PR with the title “[chore(main): release
x.x.x](https://github.com/googleapis/genai-toolbox/pull/16)”
1. The
[trigger](https://pantheon.corp.google.com/cloud-build/triggers;region=us-central1/edit/27bd0d21-264a-4446-b2d7-0df4e9915fb3?e=13802955&inv=1&invt=AbhU8A&mods=logs_tg_staging&project=database-toolbox)
should automatically run when a new tag is pushed. You can view [triggered
builds here to check the
status](https://pantheon.corp.google.com/cloud-build/builds;region=us-central1?query=trigger_id%3D%2227bd0d21-264a-4446-b2d7-0df4e9915fb3%22&e=13802955&inv=1&invt=AbhU8A&mods=logs_tg_staging&project=database-toolbox)
1. Update the Github release notes to include the following table:
1. Run the following command (from the root directory):
- linux/amd64
- darwin/arm64
- darwin/amd64
- windows/amd64
```
export VERSION="v0.0.0"
.ci/generate_release_table.sh
```
#### Supported container images
1. Copy the table output
1. In the GitHub UI, navigate to Releases and click the `edit` button.
1. Paste the table at the bottom of release note and click `Update release`.
1. Post release in internal chat and on Discord.
The following base container images is supported within the container image releases.
#### Supported Binaries
- distroless
The following operating systems and architectures are supported for binary
releases:
### Automated tests
* linux/amd64
* darwin/arm64
* darwin/amd64
* windows/amd64
Integration and unit tests are automatically triggered via CloudBuild during each PR creation.
#### Supported Container Images
The following base container images are supported for container image releases:
* distroless
### Automated Tests
Integration and unit tests are automatically triggered via Cloud Build on each
pull request. Integration tests run on merge and nightly.
#### Failure notifications
On-merge and nightly tests that fail have notification setup via Cloud Build
Failure Reporter [GitHub Actions
Workflow](.github/workflows/schedule_reporter.yml).
#### Trigger Setup
Create a Cloud Build trigger via the UI or `gcloud` with the following specs:
Configure a Cloud Build trigger using the UI or `gcloud` with the following
settings:
- Event: Pull request
- Region:
- global - for default worker pools
- Source:
- Generation: 1st gen
- Repo: googleapis/genai-toolbox (GitHub App)
- Base branch: `^main$`
- Comment control: Required except for owners and collaborators
- Filters: add directory filter
- Config: Cloud Build configuration file
- Location: Repository (add path to file)
- Service account: set for demo service to enable ID token creation to use to authenticated services
* **Event:** Pull request
* **Region:** global (for default worker pools)
* **Source:**
* Generation: 1st gen
* Repo: googleapis/genai-toolbox (GitHub App)
* Base branch: `^main$`
* **Comment control:** Required except for owners and collaborators
* **Filters:** Add directory filter
* **Config:** Cloud Build configuration file
* Location: Repository (add path to file)
* **Service account:** Set for demo service to enable ID token creation for
authenticated services
### Trigger
### Triggering Tests
Trigger the PR tests on PRs from external contributors:
Trigger pull request tests for external contributors by:
- Cloud Build tests: comment `/gcbrun`
- Unit tests: add `tests:run` label
* **Cloud Build tests:** Comment `/gcbrun`
* **Unit tests:** Add the `tests:run` label
## Repo Setup & Automation
* .github/blunderbuss.yml - Auto-assign issues and PRs from GitHub teams
* .github/renovate.json5 - Tooling for dependency updates. Dependabot is built
into the GitHub repo for GitHub security warnings
* go/github-issue-mirror - GitHub issues are automatically mirrored into buganizer
* (Suspended) .github/sync-repo-settings.yaml - configure repo settings
* .github/release-please.yml - Creates GitHub releases
* .github/ISSUE_TEMPLATE - templates for GitHub issues

212
README.md
View File

@@ -16,7 +16,6 @@ such as connection pooling, authentication, and more.
This README provides a brief overview. For comprehensive details, see the [full
documentation](https://googleapis.github.io/genai-toolbox/).
> [!NOTE]
> This solution was originally named “Gen AI Toolbox for Databases” as
> its initial development predated MCP, but was renamed to align with recently
@@ -30,23 +29,23 @@ documentation](https://googleapis.github.io/genai-toolbox/).
- [Why Toolbox?](#why-toolbox)
- [General Architecture](#general-architecture)
- [Getting Started](#getting-started)
- [Installing the server](#installing-the-server)
- [Running the server](#running-the-server)
- [Integrating your application](#integrating-your-application)
- [Installing the server](#installing-the-server)
- [Running the server](#running-the-server)
- [Integrating your application](#integrating-your-application)
- [Configuration](#configuration)
- [Sources](#sources)
- [Tools](#tools)
- [Toolsets](#toolsets)
- [Sources](#sources)
- [Tools](#tools)
- [Toolsets](#toolsets)
- [Versioning](#versioning)
- [Contributing](#contributing)
<!-- /TOC -->
## Why Toolbox?
## Why Toolbox?
Toolbox helps you build Gen AI tools that let your agents access data in your
database. Toolbox provides:
- **Simplified development**: Integrate tools to your agent in less than 10
lines of code, reuse tools between multiple agents or frameworks, and deploy
new versions of tools more easily.
@@ -56,17 +55,29 @@ database. Toolbox provides:
- **End-to-end observability**: Out of the box metrics and tracing with built-in
support for OpenTelemetry.
**⚡ Supercharge Your Workflow with an AI Database Assistant ⚡**
Stop context-switching and let your AI assistant become a true co-developer. By [connecting your IDE to your databases with MCP Toolbox][connect-ide], you can delegate complex and time-consuming database tasks, allowing you to build faster and focus on what matters. This isn't just about code completion; it's about giving your AI the context it needs to handle the entire development lifecycle.
Stop context-switching and let your AI assistant become a true co-developer. By
[connecting your IDE to your databases with MCP Toolbox][connect-ide], you can
delegate complex and time-consuming database tasks, allowing you to build faster
and focus on what matters. This isn't just about code completion; it's about
giving your AI the context it needs to handle the entire development lifecycle.
Heres how it will save you time:
* **Query in Plain English**: Interact with your data using natural language right from your IDE. Ask complex questions like, *"How many orders were delivered in 2024, and what items were in them?"* without writing any SQL.
* **Automate Database Management**: Simply describe your data needs, and let the AI assistant manage your database for you. It can handle generating queries, creating tables, adding indexes, and more.
* **Generate Context-Aware Code**: Empower your AI assistant to generate application code and tests with a deep understanding of your real-time database schema. This accelerates the development cycle by ensuring the generated code is directly usable.
* **Slash Development Overhead**: Radically reduce the time spent on manual setup and boilerplate. MCP Toolbox helps streamline lengthy database configurations, repetitive code, and error-prone schema migrations.
- **Query in Plain English**: Interact with your data using natural language
right from your IDE. Ask complex questions like, *"How many orders were
delivered in 2024, and what items were in them?"* without writing any SQL.
- **Automate Database Management**: Simply describe your data needs, and let the
AI assistant manage your database for you. It can handle generating queries,
creating tables, adding indexes, and more.
- **Generate Context-Aware Code**: Empower your AI assistant to generate
application code and tests with a deep understanding of your real-time
database schema. This accelerates the development cycle by ensuring the
generated code is directly usable.
- **Slash Development Overhead**: Radically reduce the time spent on manual
setup and boilerplate. MCP Toolbox helps streamline lengthy database
configurations, repetitive code, and error-prone schema migrations.
Learn [how to connect your AI tools (IDEs) to Toolbox using MCP][connect-ide].
@@ -86,6 +97,7 @@ redeploying your application.
## Getting Started
### Installing the server
For the latest version, check the [releases page][releases] and use the
following instructions for your OS and CPU architecture.
@@ -99,7 +111,7 @@ To install Toolbox as a binary:
<!-- {x-release-please-start-version} -->
```sh
# see releases page for other versions
export VERSION=0.7.0
export VERSION=0.8.0
curl -O https://storage.googleapis.com/genai-toolbox/v$VERSION/linux/amd64/toolbox
chmod +x toolbox
```
@@ -112,7 +124,7 @@ You can also install Toolbox as a container:
```sh
# see releases page for other versions
export VERSION=0.7.0
export VERSION=0.8.0
docker pull us-central1-docker.pkg.dev/database-toolbox/toolbox/toolbox:$VERSION
```
@@ -125,7 +137,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.7.0
go install github.com/googleapis/genai-toolbox@v0.8.0
```
<!-- {x-release-please-end} -->
@@ -153,13 +165,21 @@ Once your server is up and running, you can load the tools into your
application. See below the list of Client SDKs for using various frameworks:
<details open>
<summary>Core</summary>
<summary>Python</summary>
<br>
<blockquote>
<details open>
<summary>Core</summary>
1. Install [Toolbox Core SDK][toolbox-core]:
```bash
pip install toolbox-core
```
1. Load tools:
```python
from toolbox_core import ToolboxClient
@@ -176,15 +196,18 @@ For more detailed instructions on using the Toolbox Core SDK, see the
[toolbox-core]: https://pypi.org/project/toolbox-core/
[toolbox-core-readme]: https://github.com/googleapis/mcp-toolbox-sdk-python/tree/main/packages/toolbox-core/README.md
</details>
<details>
<summary>LangChain / LangGraph</summary>
</details>
<details>
<summary>LangChain / LangGraph</summary>
1. Install [Toolbox LangChain SDK][toolbox-langchain]:
```bash
pip install toolbox-langchain
```
1. Load tools:
```python
from toolbox_langchain import ToolboxClient
@@ -195,22 +218,24 @@ For more detailed instructions on using the Toolbox Core SDK, see the
tools = client.load_toolset()
```
For more detailed instructions on using the Toolbox LangChain SDK, see the
[project's README][toolbox-langchain-readme].
For more detailed instructions on using the Toolbox LangChain SDK, see the
[project's README][toolbox-langchain-readme].
[toolbox-langchain]: https://pypi.org/project/toolbox-langchain/
[toolbox-langchain-readme]: https://github.com/googleapis/mcp-toolbox-sdk-python/blob/main/packages/toolbox-langchain/README.md
[toolbox-langchain]: https://pypi.org/project/toolbox-langchain/
[toolbox-langchain-readme]: https://github.com/googleapis/mcp-toolbox-sdk-python/blob/main/packages/toolbox-langchain/README.md
</details>
<details>
<summary>LlamaIndex</summary>
</details>
<details>
<summary>LlamaIndex</summary>
1. Install [Toolbox Llamaindex SDK][toolbox-llamaindex]:
```bash
pip install toolbox-llamaindex
```
1. Load tools:
```python
from toolbox_llamaindex import ToolboxClient
@@ -221,12 +246,129 @@ For more detailed instructions on using the Toolbox LangChain SDK, see the
tools = client.load_toolset()
```
For more detailed instructions on using the Toolbox Llamaindex SDK, see the
[project's README][toolbox-llamaindex-readme].
For more detailed instructions on using the Toolbox Llamaindex SDK, see the
[project's README][toolbox-llamaindex-readme].
[toolbox-llamaindex]: https://pypi.org/project/toolbox-llamaindex/
[toolbox-llamaindex-readme]: https://github.com/googleapis/genai-toolbox-llamaindex-python/blob/main/README.md
[toolbox-llamaindex]: https://pypi.org/project/toolbox-llamaindex/
[toolbox-llamaindex-readme]: https://github.com/googleapis/genai-toolbox-llamaindex-python/blob/main/README.md
</details>
</details>
</blockquote>
<details>
<summary>Javascript/Typescript</summary>
<br>
<blockquote>
<details open>
<summary>Core</summary>
1. Install [Toolbox Core SDK][toolbox-core-js]:
```bash
npm install @toolbox-sdk/core
```
1. Load tools:
```javascript
import { ToolboxClient } from '@toolbox-sdk/core';
// update the url to point to your server
const URL = 'http://127.0.0.1:5000';
let client = new ToolboxClient(URL);
// these tools can be passed to your application!
const tools = await client.loadToolset('toolsetName');
```
For more detailed instructions on using the Toolbox Core SDK, see the
[project's README][toolbox-core-js-readme].
[toolbox-core-js]: https://www.npmjs.com/package/@toolbox-sdk/core
[toolbox-core-js-readme]: https://github.com/googleapis/mcp-toolbox-sdk-js/blob/main/packages/toolbox-core/README.md
</details>
<details>
<summary>LangChain / LangGraph</summary>
1. Install [Toolbox Core SDK][toolbox-core-js]:
```bash
npm install @toolbox-sdk/core
```
2. Load tools:
```javascript
import { ToolboxClient } from '@toolbox-sdk/core';
// update the url to point to your server
const URL = 'http://127.0.0.1:5000';
let client = new ToolboxClient(URL);
// these tools can be passed to your application!
const toolboxTools = await client.loadToolset('toolsetName');
// Define the basics of the tool: name, description, schema and core logic
const getTool = (toolboxTool) => tool(currTool, {
name: toolboxTool.getName(),
description: toolboxTool.getDescription(),
schema: toolboxTool.getParamSchema()
});
// Use these tools in your Langchain/Langraph applications
const tools = toolboxTools.map(getTool);
```
</details>
<details>
<summary>Genkit</summary>
1. Install [Toolbox Core SDK][toolbox-core-js]:
```bash
npm install @toolbox-sdk/core
```
2. Load tools:
```javascript
import { ToolboxClient } from '@toolbox-sdk/core';
import { genkit } from 'genkit';
// Initialise genkit
const ai = genkit({
plugins: [
googleAI({
apiKey: process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY
})
],
model: googleAI.model('gemini-2.0-flash'),
});
// update the url to point to your server
const URL = 'http://127.0.0.1:5000';
let client = new ToolboxClient(URL);
// these tools can be passed to your application!
const toolboxTools = await client.loadToolset('toolsetName');
// Define the basics of the tool: name, description, schema and core logic
const getTool = (toolboxTool) => ai.defineTool({
name: toolboxTool.getName(),
description: toolboxTool.getDescription(),
schema: toolboxTool.getParamSchema()
}, toolboxTool)
// Use these tools in your Genkit applications
const tools = toolboxTools.map(getTool);
```
</details>
</details>
</blockquote>
</details>
## Configuration
@@ -237,6 +379,7 @@ tools.yaml` flag.
You can find more detailed reference documentation to all resource types in the
[Resources](https://googleapis.github.io/genai-toolbox/resources/).
### Sources
The `sources` section of your `tools.yaml` defines what data sources your
@@ -278,7 +421,6 @@ tools:
For more details on configuring different types of tools, see the
[Tools](https://googleapis.github.io/genai-toolbox/resources/tools).
### Toolsets
The `toolsets` section of your `tools.yaml` allows you to define groups of tools

View File

@@ -21,6 +21,7 @@ import (
"io"
"os"
"os/signal"
"path/filepath"
"regexp"
"runtime"
"strings"
@@ -36,26 +37,26 @@ import (
// Import tool packages for side effect of registration
_ "github.com/googleapis/genai-toolbox/internal/tools/alloydbainl"
_ "github.com/googleapis/genai-toolbox/internal/tools/bigquery"
_ "github.com/googleapis/genai-toolbox/internal/tools/bigqueryexecutesql"
_ "github.com/googleapis/genai-toolbox/internal/tools/bigquerygetdatasetinfo"
_ "github.com/googleapis/genai-toolbox/internal/tools/bigquerygettableinfo"
_ "github.com/googleapis/genai-toolbox/internal/tools/bigquerylistdatasetids"
_ "github.com/googleapis/genai-toolbox/internal/tools/bigquerylisttableids"
_ "github.com/googleapis/genai-toolbox/internal/tools/bigquery/bigqueryexecutesql"
_ "github.com/googleapis/genai-toolbox/internal/tools/bigquery/bigquerygetdatasetinfo"
_ "github.com/googleapis/genai-toolbox/internal/tools/bigquery/bigquerygettableinfo"
_ "github.com/googleapis/genai-toolbox/internal/tools/bigquery/bigquerylistdatasetids"
_ "github.com/googleapis/genai-toolbox/internal/tools/bigquery/bigquerylisttableids"
_ "github.com/googleapis/genai-toolbox/internal/tools/bigquery/bigquerysql"
_ "github.com/googleapis/genai-toolbox/internal/tools/bigtable"
_ "github.com/googleapis/genai-toolbox/internal/tools/couchbase"
_ "github.com/googleapis/genai-toolbox/internal/tools/dgraph"
_ "github.com/googleapis/genai-toolbox/internal/tools/http"
_ "github.com/googleapis/genai-toolbox/internal/tools/mssqlexecutesql"
_ "github.com/googleapis/genai-toolbox/internal/tools/mssqlsql"
_ "github.com/googleapis/genai-toolbox/internal/tools/mysqlexecutesql"
_ "github.com/googleapis/genai-toolbox/internal/tools/mysqlsql"
_ "github.com/googleapis/genai-toolbox/internal/tools/mssql/mssqlexecutesql"
_ "github.com/googleapis/genai-toolbox/internal/tools/mssql/mssqlsql"
_ "github.com/googleapis/genai-toolbox/internal/tools/mysql/mysqlexecutesql"
_ "github.com/googleapis/genai-toolbox/internal/tools/mysql/mysqlsql"
_ "github.com/googleapis/genai-toolbox/internal/tools/neo4j"
_ "github.com/googleapis/genai-toolbox/internal/tools/postgresexecutesql"
_ "github.com/googleapis/genai-toolbox/internal/tools/postgressql"
_ "github.com/googleapis/genai-toolbox/internal/tools/postgres/postgresexecutesql"
_ "github.com/googleapis/genai-toolbox/internal/tools/postgres/postgressql"
_ "github.com/googleapis/genai-toolbox/internal/tools/redis"
_ "github.com/googleapis/genai-toolbox/internal/tools/spanner"
_ "github.com/googleapis/genai-toolbox/internal/tools/spannerexecutesql"
_ "github.com/googleapis/genai-toolbox/internal/tools/spanner/spannerexecutesql"
_ "github.com/googleapis/genai-toolbox/internal/tools/spanner/spannersql"
_ "github.com/googleapis/genai-toolbox/internal/tools/sqlitesql"
_ "github.com/googleapis/genai-toolbox/internal/tools/valkey"
@@ -122,6 +123,8 @@ type Command struct {
cfg server.ServerConfig
logger log.Logger
tools_file string
tools_files []string
tools_folder string
prebuiltConfig string
inStream io.Reader
outStream io.Writer
@@ -165,7 +168,9 @@ func NewCommand(opts ...Option) *Command {
flags.StringVar(&cmd.tools_file, "tools_file", "", "File path specifying the tool configuration. Cannot be used with --prebuilt.")
// deprecate tools_file
_ = flags.MarkDeprecated("tools_file", "please use --tools-file instead")
flags.StringVar(&cmd.tools_file, "tools-file", "", "File path specifying the tool configuration. Cannot be used with --prebuilt.")
flags.StringVar(&cmd.tools_file, "tools-file", "", "File path specifying the tool configuration. Cannot be used with --prebuilt, --tools-files, or --tools-folder.")
flags.StringSliceVar(&cmd.tools_files, "tools-files", []string{}, "Multiple file paths specifying tool configurations. Files will be merged. Cannot be used with --prebuilt, --tools-file, or --tools-folder.")
flags.StringVar(&cmd.tools_folder, "tools-folder", "", "Directory path containing YAML tool configuration files. All .yaml and .yml files in the directory will be loaded and merged. Cannot be used with --prebuilt, --tools-file, or --tools-files.")
flags.Var(&cmd.cfg.LogLevel, "log-level", "Specify the minimum level logged. Allowed: 'DEBUG', 'INFO', 'WARN', 'ERROR'.")
flags.Var(&cmd.cfg.LoggingFormat, "logging-format", "Specify logging format to use. Allowed: 'standard' or 'JSON'.")
flags.BoolVar(&cmd.cfg.TelemetryGCP, "telemetry-gcp", false, "Enable exporting directly to Google Cloud Monitoring.")
@@ -221,6 +226,136 @@ func parseToolsFile(ctx context.Context, raw []byte) (ToolsFile, error) {
return toolsFile, nil
}
// mergeToolsFiles merges multiple ToolsFile structs into one.
// Detects and raises errors for resource conflicts in sources, authServices, tools, and toolsets.
// All resource names (sources, authServices, tools, toolsets) must be unique across all files.
func mergeToolsFiles(files ...ToolsFile) (ToolsFile, error) {
merged := ToolsFile{
Sources: make(server.SourceConfigs),
AuthServices: make(server.AuthServiceConfigs),
Tools: make(server.ToolConfigs),
Toolsets: make(server.ToolsetConfigs),
}
var conflicts []string
for fileIndex, file := range files {
// Check for conflicts and merge sources
for name, source := range file.Sources {
if _, exists := merged.Sources[name]; exists {
conflicts = append(conflicts, fmt.Sprintf("source '%s' (file #%d)", name, fileIndex+1))
} else {
merged.Sources[name] = source
}
}
// Check for conflicts and merge authSources (deprecated, but still support)
for name, authSource := range file.AuthSources {
if _, exists := merged.AuthSources[name]; exists {
conflicts = append(conflicts, fmt.Sprintf("authSource '%s' (file #%d)", name, fileIndex+1))
} else {
merged.AuthSources[name] = authSource
}
}
// Check for conflicts and merge authServices
for name, authService := range file.AuthServices {
if _, exists := merged.AuthServices[name]; exists {
conflicts = append(conflicts, fmt.Sprintf("authService '%s' (file #%d)", name, fileIndex+1))
} else {
merged.AuthServices[name] = authService
}
}
// Check for conflicts and merge tools
for name, tool := range file.Tools {
if _, exists := merged.Tools[name]; exists {
conflicts = append(conflicts, fmt.Sprintf("tool '%s' (file #%d)", name, fileIndex+1))
} else {
merged.Tools[name] = tool
}
}
// Check for conflicts and merge toolsets
for name, toolset := range file.Toolsets {
if _, exists := merged.Toolsets[name]; exists {
conflicts = append(conflicts, fmt.Sprintf("toolset '%s' (file #%d)", name, fileIndex+1))
} else {
merged.Toolsets[name] = toolset
}
}
}
// If conflicts were detected, return an error
if len(conflicts) > 0 {
return ToolsFile{}, fmt.Errorf("resource conflicts detected:\n - %s\n\nPlease ensure each source, authService, tool, and toolset has a unique name across all files", strings.Join(conflicts, "\n - "))
}
return merged, nil
}
// loadAndMergeToolsFiles loads multiple YAML files and merges them
func loadAndMergeToolsFiles(ctx context.Context, filePaths []string) (ToolsFile, error) {
var toolsFiles []ToolsFile
for _, filePath := range filePaths {
buf, err := os.ReadFile(filePath)
if err != nil {
return ToolsFile{}, fmt.Errorf("unable to read tool file at %q: %w", filePath, err)
}
toolsFile, err := parseToolsFile(ctx, buf)
if err != nil {
return ToolsFile{}, fmt.Errorf("unable to parse tool file at %q: %w", filePath, err)
}
toolsFiles = append(toolsFiles, toolsFile)
}
mergedFile, err := mergeToolsFiles(toolsFiles...)
if err != nil {
return ToolsFile{}, fmt.Errorf("unable to merge tools files: %w", err)
}
return mergedFile, nil
}
// loadAndMergeToolsFolder loads all YAML files from a directory and merges them
func loadAndMergeToolsFolder(ctx context.Context, folderPath string) (ToolsFile, error) {
// Check if directory exists
info, err := os.Stat(folderPath)
if err != nil {
return ToolsFile{}, fmt.Errorf("unable to access tools folder at %q: %w", folderPath, err)
}
if !info.IsDir() {
return ToolsFile{}, fmt.Errorf("path %q is not a directory", folderPath)
}
// Find all YAML files in the directory
pattern := filepath.Join(folderPath, "*.yaml")
yamlFiles, err := filepath.Glob(pattern)
if err != nil {
return ToolsFile{}, fmt.Errorf("error finding YAML files in %q: %w", folderPath, err)
}
// Also find .yml files
ymlPattern := filepath.Join(folderPath, "*.yml")
ymlFiles, err := filepath.Glob(ymlPattern)
if err != nil {
return ToolsFile{}, fmt.Errorf("error finding YML files in %q: %w", folderPath, err)
}
// Combine both file lists
allFiles := append(yamlFiles, ymlFiles...)
if len(allFiles) == 0 {
return ToolsFile{}, fmt.Errorf("no YAML files found in directory %q", folderPath)
}
// Use existing loadAndMergeToolsFiles function
return loadAndMergeToolsFiles(ctx, allFiles)
}
// updateLogLevel checks if Toolbox have to update the existing log level set by users.
// stdio doesn't support "debug" and "info" logs.
func updateLogLevel(stdio bool, logLevel string) bool {
@@ -298,17 +433,17 @@ func run(cmd *Command) error {
}
}()
var buf []byte
var toolsFile ToolsFile
if cmd.prebuiltConfig != "" {
// Make sure --prebuilt and --tools-file flags are mutually exclusive
if cmd.tools_file != "" {
errMsg := fmt.Errorf("--prebuilt and --tools-file flags cannot be used simultaneously")
// Make sure --prebuilt and --tools-file/--tools-files/--tools-folder flags are mutually exclusive
if cmd.tools_file != "" || len(cmd.tools_files) > 0 || cmd.tools_folder != "" {
errMsg := fmt.Errorf("--prebuilt and --tools-file/--tools-files/--tools-folder flags cannot be used simultaneously")
cmd.logger.ErrorContext(ctx, errMsg.Error())
return errMsg
}
// Use prebuilt tools
buf, err = prebuiltconfigs.Get(cmd.prebuiltConfig)
buf, err := prebuiltconfigs.Get(cmd.prebuiltConfig)
if err != nil {
cmd.logger.ErrorContext(ctx, err.Error())
return err
@@ -317,32 +452,70 @@ func run(cmd *Command) error {
cmd.logger.InfoContext(ctx, logMsg)
// Append prebuilt.source to Version string for the User Agent
cmd.cfg.Version += "+prebuilt." + cmd.prebuiltConfig
toolsFile, err = parseToolsFile(ctx, buf)
if err != nil {
errMsg := fmt.Errorf("unable to parse prebuilt tool configuration: %w", err)
cmd.logger.ErrorContext(ctx, errMsg.Error())
return errMsg
}
} else if len(cmd.tools_files) > 0 {
// Make sure --tools-file, --tools-files, and --tools-folder flags are mutually exclusive
if cmd.tools_file != "" || cmd.tools_folder != "" {
errMsg := fmt.Errorf("--tools-file, --tools-files, and --tools-folder flags cannot be used simultaneously")
cmd.logger.ErrorContext(ctx, errMsg.Error())
return errMsg
}
// Use multiple tools files
cmd.logger.InfoContext(ctx, fmt.Sprintf("Loading and merging %d tool configuration files", len(cmd.tools_files)))
var err error
toolsFile, err = loadAndMergeToolsFiles(ctx, cmd.tools_files)
if err != nil {
cmd.logger.ErrorContext(ctx, err.Error())
return err
}
} else if cmd.tools_folder != "" {
// Make sure --tools-folder and other flags are mutually exclusive
if cmd.tools_file != "" || len(cmd.tools_files) > 0 {
errMsg := fmt.Errorf("--tools-file, --tools-files, and --tools-folder flags cannot be used simultaneously")
cmd.logger.ErrorContext(ctx, errMsg.Error())
return errMsg
}
// Use tools folder
cmd.logger.InfoContext(ctx, fmt.Sprintf("Loading and merging all YAML files from directory: %s", cmd.tools_folder))
var err error
toolsFile, err = loadAndMergeToolsFolder(ctx, cmd.tools_folder)
if err != nil {
cmd.logger.ErrorContext(ctx, err.Error())
return err
}
} else {
// Set default value of tools-file flag to tools.yaml
if cmd.tools_file == "" {
cmd.tools_file = "tools.yaml"
}
// Read tool file contents
buf, err = os.ReadFile(cmd.tools_file)
// Read single tool file contents
buf, err := os.ReadFile(cmd.tools_file)
if err != nil {
errMsg := fmt.Errorf("unable to read tool file at %q: %w", cmd.tools_file, err)
cmd.logger.ErrorContext(ctx, errMsg.Error())
return errMsg
}
toolsFile, err = parseToolsFile(ctx, buf)
if err != nil {
errMsg := fmt.Errorf("unable to parse tool file at %q: %w", cmd.tools_file, err)
cmd.logger.ErrorContext(ctx, errMsg.Error())
return errMsg
}
}
toolsFile, err := parseToolsFile(ctx, buf)
cmd.cfg.SourceConfigs, cmd.cfg.AuthServiceConfigs, cmd.cfg.ToolConfigs, cmd.cfg.ToolsetConfigs = toolsFile.Sources, toolsFile.AuthServices, toolsFile.Tools, toolsFile.Toolsets
authSourceConfigs := toolsFile.AuthSources
if authSourceConfigs != nil {
cmd.logger.WarnContext(ctx, "`authSources` is deprecated, use `authServices` instead")
cmd.cfg.AuthServiceConfigs = authSourceConfigs
}
if err != nil {
errMsg := fmt.Errorf("unable to parse tool file at %q: %w", cmd.tools_file, err)
cmd.logger.ErrorContext(ctx, errMsg.Error())
return errMsg
}
// start server
s, err := server.NewServer(ctx, cmd.cfg, cmd.logger)

View File

@@ -32,7 +32,7 @@ import (
"github.com/googleapis/genai-toolbox/internal/testutils"
"github.com/googleapis/genai-toolbox/internal/tools"
"github.com/googleapis/genai-toolbox/internal/tools/http"
"github.com/googleapis/genai-toolbox/internal/tools/postgressql"
"github.com/googleapis/genai-toolbox/internal/tools/postgres/postgressql"
"github.com/spf13/cobra"
)
@@ -229,6 +229,71 @@ func TestToolFileFlag(t *testing.T) {
}
}
func TestToolsFilesFlag(t *testing.T) {
tcs := []struct {
desc string
args []string
want []string
}{
{
desc: "no value",
args: []string{},
want: []string{},
},
{
desc: "single file",
args: []string{"--tools-files", "foo.yaml"},
want: []string{"foo.yaml"},
},
{
desc: "multiple files",
args: []string{"--tools-files", "foo.yaml,bar.yaml"},
want: []string{"foo.yaml", "bar.yaml"},
},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
c, _, err := invokeCommand(tc.args)
if err != nil {
t.Fatalf("unexpected error invoking command: %s", err)
}
if diff := cmp.Diff(c.tools_files, tc.want); diff != "" {
t.Fatalf("got %v, want %v", c.tools_files, tc.want)
}
})
}
}
func TestToolsFolderFlag(t *testing.T) {
tcs := []struct {
desc string
args []string
want string
}{
{
desc: "no value",
args: []string{},
want: "",
},
{
desc: "folder set",
args: []string{"--tools-folder", "test-folder"},
want: "test-folder",
},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
c, _, err := invokeCommand(tc.args)
if err != nil {
t.Fatalf("unexpected error invoking command: %s", err)
}
if c.tools_folder != tc.want {
t.Fatalf("got %v, want %v", c.tools_folder, tc.want)
}
})
}
}
func TestPrebuiltFlag(t *testing.T) {
tcs := []struct {
desc string

View File

@@ -1 +1 @@
0.7.0
0.8.0

View File

@@ -9,22 +9,22 @@ description: Frequently asked questions about Toolbox.
MCP Toolbox for Databases is open-source and can be ran or deployed to a
multitude of environments. For convenience, we release [compiled binaries and
docker images][release-notes] (but you can always compile yourself as well!).
docker images][release-notes] (but you can always compile yourself as well!).
For detailed instructions, check our these resources:
- [Quickstart: How to Run Locally](../getting-started/local_quickstart.md)
- [Deploy to Cloud Run](../how-to/deploy_toolbox.md)
[release-notes]: https://github.com/googleapis/genai-toolbox/releases/
## Do I need a Google Cloud account/project to get started with Toolbox?
Nope! While some of the sources Toolbox connects to may require GCP credentials,
Toolbox doesn't require them and can connect to a bunch of different resources
that don't.
that don't.
## Does Toolbox take contributions from external users?
## Does Toolbox take contributions from external users?
Absolutely! Please check out our [DEVELOPER.md][] for instructions on how to get
started developing _on_ Toolbox instead of with it, and the [CONTRIBUTING.md][]
@@ -33,17 +33,16 @@ for instructions on completing the CLA and getting a PR accepted.
[DEVELOPER.md]: https://github.com/googleapis/genai-toolbox/blob/main/DEVELOPER.md
[CONTRIBUTING.MD]: https://github.com/googleapis/genai-toolbox/blob/main/CONTRIBUTING.md
## Can Toolbox support a feature to let me do _$FOO_?
## Can Toolbox support a feature to let me do _$FOO_?
Maybe? The best place to start is by [opening an issue][github-issue] for
discussion (or seeing if there is already one open), so we can better understand
your use case and the best way to solve it. Generally we aim to prioritize the
most popular issues, so make sure to +1 ones you are the most interested in.
most popular issues, so make sure to +1 ones you are the most interested in.
[github-issue]: https://github.com/googleapis/genai-toolbox/issues
## Can Toolbox be used for non-database tools?
## Can Toolbox be used for non-database tools?
Currently, Toolbox is primarily focused on making it easier to create and
develop tools focused on interacting with Databases. We believe that there are a
@@ -55,21 +54,21 @@ GRPC tools might be helpful in assisting with migrating to Toolbox or in
accomplishing more complicated workflows. We're looking into what that might
best look like in Toolbox.
## Can I use _$BAR_ orchestration framework to use tools from Toolbox?
## Can I use _$BAR_ orchestration framework to use tools from Toolbox?
Currently, Toolbox only supports a limited number of client SDKs at our initial
launch. We are investigating support for more frameworks as well as more general
approaches for users without a framework -- look forward to seeing an update
soon.
## Why does Toolbox use a server-client architecture pattern?
## Why does Toolbox use a server-client architecture pattern?
Toolbox's server-client architecture allows us to more easily support a wide
variety of languages and frameworks with a centralized implementation. It also
allows us to tackle problems like connection pooling, auth, or caching more
completely than entirely client-side solutions.
## Why was Toolbox written in Go?
## Why was Toolbox written in Go?
While a large part of the Gen AI Ecosystem is predominately Python, we opted to
use Go. We chose Go because it's still easy and simple to use, but also easier
@@ -80,8 +79,9 @@ to be able to use Toolbox on the serving path of mission critical applications.
It's easier to build the needed robustness, performance and scalability in Go
than in Python.
## Is Toolbox compatible with Model Context Protocol (MCP)?
## Is Toolbox compatible with Model Context Protocol (MCP)?
Yes! Toolbox is compatible with [Anthropic's Model Context Protocol (MCP)](https://modelcontextprotocol.io/). Please checkout [Connect via MCP](../how-to/connect_via_mcp.md) on how to
connect to Toolbox with an MCP client.
Yes! Toolbox is compatible with [Anthropic's Model Context Protocol
(MCP)](https://modelcontextprotocol.io/). Please checkout [Connect via
MCP](../how-to/connect_via_mcp.md) on how to connect to Toolbox with an MCP
client.

View File

@@ -17,7 +17,6 @@ through [OpenTelemetry](https://opentelemetry.io/). Additional flags can be
passed to Toolbox to enable different logging behavior, or to export metrics
through a specific [exporter](#exporter).
## Logging
The following flags can be used to customize Toolbox logging:
@@ -27,7 +26,8 @@ The following flags can be used to customize Toolbox logging:
| `--log-level` | Preferred log level, allowed values: `debug`, `info`, `warn`, `error`. Default: `info`. |
| `--logging-format` | Preferred logging format, allowed values: `standard`, `json`. Default: `standard`. |
__Example:__
**Example:**
```bash
./toolbox --tools-file "tools.yaml" --log-level warn --logging-format json
```
@@ -35,6 +35,7 @@ __Example:__
### Level
Toolbox supports the following log levels, including:
| **Log level** | **Description** |
|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Debug | Debug logs typically contain information that is only useful during the debugging phase and may be of little value during production. |
@@ -46,17 +47,18 @@ Toolbox will only output logs that are equal or more severe to the
level that it is set. Below are the log levels that Toolbox supports in the
order of severity.
### Format
Toolbox supports both standard and structured logging format.
The standard logging outputs log as string:
```
2024-11-12T15:08:11.451377-08:00 INFO "Initialized 0 sources.\n"
```
The structured logging outputs log as JSON:
```
{
"timestamp":"2024-11-04T16:45:11.987299-08:00",
@@ -66,9 +68,9 @@ The structured logging outputs log as JSON:
}
```
{{< notice tip >}}
{{< notice tip >}}
`logging.googleapis.com/sourceLocation` shows the source code
location information associated with the log entry, if any.
location information associated with the log entry, if any.
{{< /notice >}}
## Telemetry
@@ -125,7 +127,6 @@ unified [resource][resource]. The list of resource attributes included are:
| `service.name` | Open telemetry service name. Defaulted to `toolbox`. User can set the service name via flag mentioned above to distinguish between different toolbox service. |
| `service.version` | The version of Toolbox used. |
[resource]: https://opentelemetry.io/docs/languages/go/resources/
### Exporter
@@ -151,9 +152,10 @@ Exporter][gcp-trace-exporter].
[gcp-trace-exporter]:
https://github.com/GoogleCloudPlatform/opentelemetry-operations-go/tree/main/exporter/trace
{{< notice note >}}
If you're using Google Cloud Monitoring, the following APIs will need to be
{{< notice note >}}
If you're using Google Cloud Monitoring, the following APIs will need to be
enabled:
- [Cloud Logging API](https://cloud.google.com/logging/docs/api/enable-api)
- [Cloud Monitoring API](https://cloud.google.com/monitoring/api/enable-api)
- [Cloud Trace API](https://cloud.google.com/apis/enableflow?apiid=cloudtrace.googleapis.com)
@@ -184,7 +186,7 @@ The following flags are used to determine Toolbox's telemetry configuration:
| **flag** | **type** | **description** |
|----------------------------|----------|----------------------------------------------------------------------------------------------------------------|
| `--telemetry-gcp` | bool | Enable exporting directly to Google Cloud Monitoring. Default is `false`. |
| `--telemetry-otlp` | string | Enable exporting using OpenTelemetry Protocol (OTLP) to the specified endpoint (e.g. "http://127.0.0.1:4318"). |
| `--telemetry-otlp` | string | Enable exporting using OpenTelemetry Protocol (OTLP) to the specified endpoint (e.g. "<http://127.0.0.1:4318>"). |
| `--telemetry-service-name` | string | Sets the value of the `service.name` resource attribute. Default is `toolbox`. |
In addition to the flags noted above, you can also make additional configuration
@@ -194,14 +196,16 @@ environmental variables.
[sdk-configuration]:
https://opentelemetry.io/docs/languages/sdk-configuration/general/
__Examples:__
**Examples:**
To enable Google Cloud Exporter:
```bash
./toolbox --telemetry-gcp
```
To enable OTLP Exporter, provide Collector endpoint:
```bash
./toolbox --telemetry-otlp="http://127.0.0.1:4553"
```

View File

@@ -222,7 +222,7 @@
},
"outputs": [],
"source": [
"version = \"0.7.0\" # x-release-please-version\n",
"version = \"0.8.0\" # x-release-please-version\n",
"! curl -O https://storage.googleapis.com/genai-toolbox/v{version}/linux/amd64/toolbox\n",
"\n",
"# Make the binary executable\n",
@@ -474,165 +474,12 @@
"source": [
"> You can either use LangGraph or LlamaIndex to develop a Toolbox based\n",
"> application. Run one of the sections below\n",
"> - [Connect using Google GenAI](#scrollTo=Rwgv1LDdNKSn)\n",
"> - [Connect using Google GenAI](#scrollTo=Fv2-uT4mvYtp)\n",
"> - [Connect using ADK](#scrollTo=QqRlWqvYNKSo)\n",
"> - [Connect Using LangGraph](#scrollTo=pbapNMhhL33S)\n",
"> - [Connect using LlamaIndex](#scrollTo=04iysrm_L_7v)\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "Rwgv1LDdNKSn"
},
"source": [
"### Connect Using Google GenAI"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "HY23RMk4NKSn"
},
"outputs": [],
"source": [
"# Install the Toolbox Core package\n",
"!pip install toolbox-core --quiet\n",
"\n",
"# Install the Google GenAI package\n",
"!pip install google-genai --quiet"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "9F1u566sNKSn"
},
"source": [
"Create a Google GenAI Application which can Search, Book and Cancel hotels."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "LAuBIOXvNKSn"
},
"outputs": [],
"source": [
"import asyncio\n",
"\n",
"from google import genai\n",
"from google.genai.types import (\n",
" Content,\n",
" FunctionDeclaration,\n",
" GenerateContentConfig,\n",
" Part,\n",
" Tool,\n",
")\n",
"\n",
"from toolbox_core import ToolboxClient\n",
"\n",
"prompt = \"\"\"\n",
" You're a helpful hotel assistant. You handle hotel searching, booking and\n",
" cancellations. When the user searches for a hotel, mention it's name, id,\n",
" location and price tier. Always mention hotel id while performing any\n",
" searches. This is very important for any operations. For any bookings or\n",
" cancellations, please provide the appropriate confirmation. Be sure to\n",
" update checkin or checkout dates if mentioned by the user.\n",
" Don't ask for confirmations from the user.\n",
"\"\"\"\n",
"\n",
"queries = [\n",
" \"Find hotels in Basel with Basel in it's name.\",\n",
" \"Please book the hotel Hilton Basel for me.\",\n",
" \"This is too expensive. Please cancel it.\",\n",
" \"Please book Hyatt Regency for me\",\n",
" \"My check in dates for my booking would be from April 10, 2024 to April 19, 2024.\",\n",
"]\n",
"\n",
"\n",
"async def run_application():\n",
" toolbox_client = ToolboxClient(\"http://127.0.0.1:5000\")\n",
"\n",
" # The toolbox_tools list contains Python callables (functions/methods) designed for LLM tool-use\n",
" # integration. While this example uses Google's genai client, these callables can be adapted for\n",
" # various function-calling or agent frameworks. For easier integration with supported frameworks\n",
" # (https://github.com/googleapis/mcp-toolbox-python-sdk/tree/main/packages), use the\n",
" # provided wrapper packages, which handle framework-specific boilerplate.\n",
" toolbox_tools = await toolbox_client.load_toolset(\"my-toolset\")\n",
" genai_client = genai.Client(\n",
" vertexai=True, project=project_id, location=\"us-central1\"\n",
" )\n",
"\n",
" genai_tools = [\n",
" Tool(\n",
" function_declarations=[\n",
" FunctionDeclaration.from_callable_with_api_option(callable=tool)\n",
" ]\n",
" )\n",
" for tool in toolbox_tools\n",
" ]\n",
" history = []\n",
" for query in queries:\n",
" user_prompt_content = Content(\n",
" role=\"user\",\n",
" parts=[Part.from_text(text=query)],\n",
" )\n",
" history.append(user_prompt_content)\n",
"\n",
" response = genai_client.models.generate_content(\n",
" model=\"gemini-2.0-flash-001\",\n",
" contents=history,\n",
" config=GenerateContentConfig(\n",
" system_instruction=prompt,\n",
" tools=genai_tools,\n",
" ),\n",
" )\n",
" history.append(response.candidates[0].content)\n",
" function_response_parts = []\n",
" for function_call in response.function_calls:\n",
" fn_name = function_call.name\n",
" # The tools are sorted alphabetically\n",
" if fn_name == \"search-hotels-by-name\":\n",
" function_result = await toolbox_tools[3](**function_call.args)\n",
" elif fn_name == \"search-hotels-by-location\":\n",
" function_result = await toolbox_tools[2](**function_call.args)\n",
" elif fn_name == \"book-hotel\":\n",
" function_result = await toolbox_tools[0](**function_call.args)\n",
" elif fn_name == \"update-hotel\":\n",
" function_result = await toolbox_tools[4](**function_call.args)\n",
" elif fn_name == \"cancel-hotel\":\n",
" function_result = await toolbox_tools[1](**function_call.args)\n",
" else:\n",
" raise ValueError(\"Function name not present.\")\n",
" function_response = {\"result\": function_result}\n",
" function_response_part = Part.from_function_response(\n",
" name=function_call.name,\n",
" response=function_response,\n",
" )\n",
" function_response_parts.append(function_response_part)\n",
"\n",
" if function_response_parts:\n",
" tool_response_content = Content(role=\"tool\", parts=function_response_parts)\n",
" history.append(tool_response_content)\n",
"\n",
" response2 = genai_client.models.generate_content(\n",
" model=\"gemini-2.0-flash-001\",\n",
" contents=history,\n",
" config=GenerateContentConfig(\n",
" tools=genai_tools,\n",
" ),\n",
" )\n",
" final_model_response_content = response2.candidates[0].content\n",
" history.append(final_model_response_content)\n",
" print(response2.text)\n",
"\n",
"\n",
"asyncio.run(run_application())"
]
},
{
"cell_type": "markdown",
"metadata": {
@@ -695,7 +542,7 @@
"\n",
"session_service = InMemorySessionService()\n",
"artifacts_service = InMemoryArtifactService()\n",
"session = session_service.create_session(\n",
"session = await session_service.create_session(\n",
" state={}, app_name='hotel_agent', user_id='123'\n",
")\n",
"runner = Runner(\n",
@@ -931,6 +778,159 @@
"await run_application()"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "Fv2-uT4mvYtp"
},
"source": [
"### Connect Using Google GenAI"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "mHSvk5_AvYtu"
},
"outputs": [],
"source": [
"# Install the Toolbox Core package\n",
"!pip install toolbox-core --quiet\n",
"\n",
"# Install the Google GenAI package\n",
"!pip install google-genai --quiet"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "sO_7FGSYvYtu"
},
"source": [
"Create a Google GenAI Application which can Search, Book and Cancel hotels."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "-NVVBiLnvYtu"
},
"outputs": [],
"source": [
"import asyncio\n",
"\n",
"from google import genai\n",
"from google.genai.types import (\n",
" Content,\n",
" FunctionDeclaration,\n",
" GenerateContentConfig,\n",
" Part,\n",
" Tool,\n",
")\n",
"\n",
"from toolbox_core import ToolboxClient\n",
"\n",
"prompt = \"\"\"\n",
" You're a helpful hotel assistant. You handle hotel searching, booking and\n",
" cancellations. When the user searches for a hotel, mention it's name, id,\n",
" location and price tier. Always mention hotel id while performing any\n",
" searches. This is very important for any operations. For any bookings or\n",
" cancellations, please provide the appropriate confirmation. Be sure to\n",
" update checkin or checkout dates if mentioned by the user.\n",
" Don't ask for confirmations from the user.\n",
"\"\"\"\n",
"\n",
"queries = [\n",
" \"Find hotels in Basel with Basel in it's name.\",\n",
" \"Please book the hotel Hilton Basel for me.\",\n",
" \"This is too expensive. Please cancel it.\",\n",
" \"Please book Hyatt Regency for me\",\n",
" \"My check in dates for my booking would be from April 10, 2024 to April 19, 2024.\",\n",
"]\n",
"\n",
"\n",
"async def run_application():\n",
" toolbox_client = ToolboxClient(\"http://127.0.0.1:5000\")\n",
"\n",
" # The toolbox_tools list contains Python callables (functions/methods) designed for LLM tool-use\n",
" # integration. While this example uses Google's genai client, these callables can be adapted for\n",
" # various function-calling or agent frameworks. For easier integration with supported frameworks\n",
" # (https://github.com/googleapis/mcp-toolbox-python-sdk/tree/main/packages), use the\n",
" # provided wrapper packages, which handle framework-specific boilerplate.\n",
" toolbox_tools = await toolbox_client.load_toolset(\"my-toolset\")\n",
" genai_client = genai.Client(\n",
" vertexai=True, project=project_id, location=\"us-central1\"\n",
" )\n",
"\n",
" genai_tools = [\n",
" Tool(\n",
" function_declarations=[\n",
" FunctionDeclaration.from_callable_with_api_option(callable=tool)\n",
" ]\n",
" )\n",
" for tool in toolbox_tools\n",
" ]\n",
" history = []\n",
" for query in queries:\n",
" user_prompt_content = Content(\n",
" role=\"user\",\n",
" parts=[Part.from_text(text=query)],\n",
" )\n",
" history.append(user_prompt_content)\n",
"\n",
" response = genai_client.models.generate_content(\n",
" model=\"gemini-2.0-flash-001\",\n",
" contents=history,\n",
" config=GenerateContentConfig(\n",
" system_instruction=prompt,\n",
" tools=genai_tools,\n",
" ),\n",
" )\n",
" history.append(response.candidates[0].content)\n",
" function_response_parts = []\n",
" for function_call in response.function_calls:\n",
" fn_name = function_call.name\n",
" # The tools are sorted alphabetically\n",
" if fn_name == \"search-hotels-by-name\":\n",
" function_result = await toolbox_tools[3](**function_call.args)\n",
" elif fn_name == \"search-hotels-by-location\":\n",
" function_result = await toolbox_tools[2](**function_call.args)\n",
" elif fn_name == \"book-hotel\":\n",
" function_result = await toolbox_tools[0](**function_call.args)\n",
" elif fn_name == \"update-hotel\":\n",
" function_result = await toolbox_tools[4](**function_call.args)\n",
" elif fn_name == \"cancel-hotel\":\n",
" function_result = await toolbox_tools[1](**function_call.args)\n",
" else:\n",
" raise ValueError(\"Function name not present.\")\n",
" function_response = {\"result\": function_result}\n",
" function_response_part = Part.from_function_response(\n",
" name=function_call.name,\n",
" response=function_response,\n",
" )\n",
" function_response_parts.append(function_response_part)\n",
"\n",
" if function_response_parts:\n",
" tool_response_content = Content(role=\"tool\", parts=function_response_parts)\n",
" history.append(tool_response_content)\n",
"\n",
" response2 = genai_client.models.generate_content(\n",
" model=\"gemini-2.0-flash-001\",\n",
" contents=history,\n",
" config=GenerateContentConfig(\n",
" tools=genai_tools,\n",
" ),\n",
" )\n",
" final_model_response_content = response2.candidates[0].content\n",
" history.append(final_model_response_content)\n",
" print(response2.text)\n",
"\n",
"\n",
"asyncio.run(run_application())"
]
},
{
"cell_type": "markdown",
"metadata": {

View File

@@ -10,17 +10,17 @@ MCP Toolbox for Databases is an open source MCP server for databases. It enables
you to develop tools easier, faster, and more securely by handling the complexities
such as connection pooling, authentication, and more.
{{< notice note >}}
This solution was originally named “Gen AI Toolbox for
Databases” as its initial development predated MCP, but was renamed to align
with recently added MCP compatibility.
{{< /notice >}}
## Why Toolbox?
## Why Toolbox?
Toolbox helps you build Gen AI tools that let your agents access data in your
database. Toolbox provides:
- **Simplified development**: Integrate tools to your agent in less than 10
lines of code, reuse tools between multiple agents or frameworks, and deploy
new versions of tools more easily.
@@ -30,17 +30,29 @@ database. Toolbox provides:
- **End-to-end observability**: Out of the box metrics and tracing with built-in
support for OpenTelemetry.
**⚡ Supercharge Your Workflow with an AI Database Assistant ⚡**
Stop context-switching and let your AI assistant become a true co-developer. By [connecting your IDE to your databases with MCP Toolbox][connect-ide], you can delegate complex and time-consuming database tasks, allowing you to build faster and focus on what matters. This isn't just about code completion; it's about giving your AI the context it needs to handle the entire development lifecycle.
Stop context-switching and let your AI assistant become a true co-developer. By
[connecting your IDE to your databases with MCP Toolbox][connect-ide], you can
delegate complex and time-consuming database tasks, allowing you to build faster
and focus on what matters. This isn't just about code completion; it's about
giving your AI the context it needs to handle the entire development lifecycle.
Heres how it will save you time:
* **Query in Plain English**: Interact with your data using natural language right from your IDE. Ask complex questions like, *"How many orders were delivered in 2024, and what items were in them?"* without writing any SQL.
* **Automate Database Management**: Simply describe your data needs, and let the AI assistant manage your database for you. It can handle generating queries, creating tables, adding indexes, and more.
* **Generate Context-Aware Code**: Empower your AI assistant to generate application code and tests with a deep understanding of your real-time database schema. This accelerates the development cycle by ensuring the generated code is directly usable.
* **Slash Development Overhead**: Radically reduce the time spent on manual setup and boilerplate. MCP Toolbox helps streamline lengthy database configurations, repetitive code, and error-prone schema migrations.
- **Query in Plain English**: Interact with your data using natural language
right from your IDE. Ask complex questions like, *"How many orders were
delivered in 2024, and what items were in them?"* without writing any SQL.
- **Automate Database Management**: Simply describe your data needs, and let the
AI assistant manage your database for you. It can handle generating queries,
creating tables, adding indexes, and more.
- **Generate Context-Aware Code**: Empower your AI assistant to generate
application code and tests with a deep understanding of your real-time
database schema. This accelerates the development cycle by ensuring the
generated code is directly usable.
- **Slash Development Overhead**: Radically reduce the time spent on manual
setup and boilerplate. MCP Toolbox helps streamline lengthy database
configurations, repetitive code, and error-prone schema migrations.
Learn [how to connect your AI tools (IDEs) to Toolbox using MCP][connect-ide].
@@ -60,6 +72,7 @@ redeploying your application.
## Getting Started
### Installing the server
For the latest version, check the [releases page][releases] and use the
following instructions for your OS and CPU architecture.
@@ -73,7 +86,7 @@ To install Toolbox as a binary:
```sh
# see releases page for other versions
export VERSION=0.7.0
export VERSION=0.8.0
curl -O https://storage.googleapis.com/genai-toolbox/v$VERSION/linux/amd64/toolbox
chmod +x toolbox
```
@@ -84,7 +97,7 @@ You can also install Toolbox as a container:
```sh
# see releases page for other versions
export VERSION=0.7.0
export VERSION=0.8.0
docker pull us-central1-docker.pkg.dev/database-toolbox/toolbox/toolbox:$VERSION
```
@@ -95,7 +108,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.7.0
go install github.com/googleapis/genai-toolbox@v0.8.0
```
{{% /tab %}}
@@ -133,7 +146,8 @@ tools:
from toolbox_core import ToolboxClient
# update the url to point to your server
async with ToolboxClient("http://127.0.0.1:5000") as client:
async with ToolboxClient("<http://127.0.0.1:5000>") as client:
# these tools can be passed to your application!
tools = await client.load_toolset("toolset_name")
@@ -153,7 +167,8 @@ tools:
from toolbox_langchain import ToolboxClient
# update the url to point to your server
async with ToolboxClient("http://127.0.0.1:5000") as client:
async with ToolboxClient("<http://127.0.0.1:5000>") as client:
# these tools can be passed to your application!
tools = client.load_toolset()
@@ -173,9 +188,11 @@ tools:
from toolbox_llamaindex import ToolboxClient
# update the url to point to your server
async with ToolboxClient("http://127.0.0.1:5000") as client:
# these tools can be passed to your application!
async with ToolboxClient("<http://127.0.0.1:5000>") as client:
# these tools can be passed to your application
tools = client.load_toolset()
{{< /highlight >}}

View File

@@ -3,9 +3,8 @@ title: "Quickstart (Local)"
type: docs
weight: 2
description: >
How to get started running Toolbox locally with Python, PostgreSQL, and
[GoogleGenAI](https://pypi.org/project/google-genai/),
[LangGraph](https://www.langchain.com/langgraph), [LlamaIndex](https://www.llamaindex.ai/) or [Agent Development Kit](https://google.github.io/adk-docs/).
How to get started running Toolbox locally with Python, PostgreSQL, and [Agent Development Kit](https://google.github.io/adk-docs/),
[LangGraph](https://www.langchain.com/langgraph), [LlamaIndex](https://www.llamaindex.ai/) or [GoogleGenAI](https://pypi.org/project/google-genai/).
---
[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/googleapis/genai-toolbox/blob/main/docs/en/getting-started/colab_quickstart.ipynb)
@@ -37,24 +36,40 @@ accessed by our agent, and create a database user for Toolbox to connect with.
Here, `postgres` denotes the default postgres superuser.
{{< notice info >}}
#### **Having trouble connecting?**
* **Password Prompt:** If you are prompted for a password for the `postgres` user and do not know it (or a blank password doesn't work), your PostgreSQL installation might require a password or a different authentication method.
* **`FATAL: role "postgres" does not exist`:** This error means the default `postgres` superuser role isn't available under that name on your system.
* **`Connection refused`:** Ensure your PostgreSQL server is actually running. You can typically check with `sudo systemctl status postgresql` and start it with `sudo systemctl start postgresql` on Linux systems.
* **Password Prompt:** If you are prompted for a password for the `postgres`
user and do not know it (or a blank password doesn't work), your PostgreSQL
installation might require a password or a different authentication method.
* **`FATAL: role "postgres" does not exist`:** This error means the default
`postgres` superuser role isn't available under that name on your system.
* **`Connection refused`:** Ensure your PostgreSQL server is actually running.
You can typically check with `sudo systemctl status postgresql` and start it
with `sudo systemctl start postgresql` on Linux systems.
<br/>
#### **Common Solution**
For password issues or if the `postgres` role seems inaccessible directly, try switching to the `postgres` operating system user first. This user often has permission to connect without a password for local connections (this is called peer authentication).
For password issues or if the `postgres` role seems inaccessible directly, try
switching to the `postgres` operating system user first. This user often has
permission to connect without a password for local connections (this is called
peer authentication).
```bash
sudo -i -u postgres
psql -h 127.0.0.1
```
Once you are in the `psql` shell using this method, you can proceed with the database creation steps below. Afterwards, type `\q` to exit `psql`, and then `exit` to return to your normal user shell.
If desired, once connected to `psql` as the `postgres` OS user, you can set a password for the `postgres` *database* user using: `ALTER USER postgres WITH PASSWORD 'your_chosen_password';`. This would allow direct connection with `-U postgres` and a password next time.
Once you are in the `psql` shell using this method, you can proceed with the
database creation steps below. Afterwards, type `\q` to exit `psql`, and then
`exit` to return to your normal user shell.
If desired, once connected to `psql` as the `postgres` OS user, you can set a
password for the `postgres` *database* user using: `ALTER USER postgres WITH
PASSWORD 'your_chosen_password';`. This would allow direct connection with `-U
postgres` and a password next time.
{{< /notice >}}
1. Create a new database and a new user:
@@ -78,7 +93,10 @@ If desired, once connected to `psql` as the `postgres` OS user, you can set a pa
```bash
\q
```
(If you used `sudo -i -u postgres` and then `psql`, remember you might also need to type `exit` after `\q` to leave the `postgres` user's shell session.)
(If you used `sudo -i -u postgres` and then `psql`, remember you might also
need to type `exit` after `\q` to leave the `postgres` user's shell
session.)
1. Connect to your database with your new user:
@@ -138,7 +156,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.7.0/$OS/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.8.0/$OS/toolbox
```
<!-- {x-release-please-end} -->
@@ -253,10 +271,6 @@ you can connect to a
1. In a new terminal, install the SDK package.
{{< tabpane persist=header >}}
{{< tab header="Core" lang="bash" >}}
pip install toolbox-core
{{< /tab >}}
{{< tab header="ADK" lang="bash" >}}
pip install toolbox-core
@@ -269,15 +283,15 @@ pip install toolbox-langchain
pip install toolbox-llamaindex
{{< /tab >}}
{{< tab header="Core" lang="bash" >}}
pip install toolbox-core
{{< /tab >}}
{{< /tabpane >}}
1. Install other required dependencies:
{{< tabpane persist=header >}}
{{< tab header="Core" lang="bash" >}}
pip install google-genai
{{< /tab >}}
{{< tab header="ADK" lang="bash" >}}
pip install google-adk
@@ -301,123 +315,16 @@ pip install llama-index-llms-google-genai
# pip install llama-index-llms-anthropic
{{< /tab >}}
{{< tab header="Core" lang="bash" >}}
pip install google-genai
{{< /tab >}}
{{< /tabpane >}}
1. Create a new file named `hotel_agent.py` and copy the following
code to create an agent:
{{< tabpane persist=header >}}
{{< tab header="Core" lang="python" >}}
import asyncio
from google import genai
from google.genai.types import (
Content,
FunctionDeclaration,
GenerateContentConfig,
Part,
Tool,
)
from toolbox_core import ToolboxClient
prompt = """
You're a helpful hotel assistant. You handle hotel searching, booking and
cancellations. When the user searches for a hotel, mention it's name, id,
location and price tier. Always mention hotel id while performing any
searches. This is very important for any operations. For any bookings or
cancellations, please provide the appropriate confirmation. Be sure to
update checkin or checkout dates if mentioned by the user.
Don't ask for confirmations from the user.
"""
queries = [
"Find hotels in Basel with Basel in it's name.",
"Please book the hotel Hilton Basel for me.",
"This is too expensive. Please cancel it.",
"Please book Hyatt Regency for me",
"My check in dates for my booking would be from April 10, 2024 to April 19, 2024.",
]
async def run_application():
async with ToolboxClient("http://127.0.0.1:5000") as toolbox_client:
# The toolbox_tools list contains Python callables (functions/methods) designed for LLM tool-use
# integration. While this example uses Google's genai client, these callables can be adapted for
# various function-calling or agent frameworks. For easier integration with supported frameworks
# (https://github.com/googleapis/mcp-toolbox-python-sdk/tree/main/packages), use the
# provided wrapper packages, which handle framework-specific boilerplate.
toolbox_tools = await toolbox_client.load_toolset("my-toolset")
genai_client = genai.Client(
vertexai=True, project="project-id", location="us-central1"
)
genai_tools = [
Tool(
function_declarations=[
FunctionDeclaration.from_callable_with_api_option(callable=tool)
]
)
for tool in toolbox_tools
]
history = []
for query in queries:
user_prompt_content = Content(
role="user",
parts=[Part.from_text(text=query)],
)
history.append(user_prompt_content)
response = genai_client.models.generate_content(
model="gemini-2.0-flash-001",
contents=history,
config=GenerateContentConfig(
system_instruction=prompt,
tools=genai_tools,
),
)
history.append(response.candidates[0].content)
function_response_parts = []
for function_call in response.function_calls:
fn_name = function_call.name
# The tools are sorted alphabetically
if fn_name == "search-hotels-by-name":
function_result = await toolbox_tools[3](**function_call.args)
elif fn_name == "search-hotels-by-location":
function_result = await toolbox_tools[2](**function_call.args)
elif fn_name == "book-hotel":
function_result = await toolbox_tools[0](**function_call.args)
elif fn_name == "update-hotel":
function_result = await toolbox_tools[4](**function_call.args)
elif fn_name == "cancel-hotel":
function_result = await toolbox_tools[1](**function_call.args)
else:
raise ValueError("Function name not present.")
function_response = {"result": function_result}
function_response_part = Part.from_function_response(
name=function_call.name,
response=function_response,
)
function_response_parts.append(function_response_part)
if function_response_parts:
tool_response_content = Content(role="tool", parts=function_response_parts)
history.append(tool_response_content)
response2 = genai_client.models.generate_content(
model="gemini-2.0-flash-001",
contents=history,
config=GenerateContentConfig(
tools=genai_tools,
),
)
final_model_response_content = response2.candidates[0].content
history.append(final_model_response_content)
print(response2.text)
asyncio.run(run_application())
{{< /tab >}}
{{< tab header="ADK" lang="python" >}}
from google.adk.agents import Agent
from google.adk.runners import Runner
@@ -426,65 +333,69 @@ from google.adk.artifacts.in_memory_artifact_service import InMemoryArtifactServ
from google.genai import types
from toolbox_core import ToolboxSyncClient
import asyncio
import os
# TODO(developer): replace this with your Google API key
os.environ['GOOGLE_API_KEY'] = 'your-api-key'
with ToolboxSyncClient("http://127.0.0.1:5000") as toolbox_client:
async def main():
with ToolboxSyncClient("<http://127.0.0.1:5000>") as toolbox_client:
prompt = """
You're a helpful hotel assistant. You handle hotel searching, booking and
cancellations. When the user searches for a hotel, mention it's name, id,
location and price tier. Always mention hotel ids while performing any
searches. This is very important for any operations. For any bookings or
cancellations, please provide the appropriate confirmation. Be sure to
update checkin or checkout dates if mentioned by the user.
Don't ask for confirmations from the user.
"""
prompt = """
You're a helpful hotel assistant. You handle hotel searching, booking and
cancellations. When the user searches for a hotel, mention it's name, id,
location and price tier. Always mention hotel ids while performing any
searches. This is very important for any operations. For any bookings or
cancellations, please provide the appropriate confirmation. Be sure to
update checkin or checkout dates if mentioned by the user.
Don't ask for confirmations from the user.
"""
root_agent = Agent(
model='gemini-2.0-flash-001',
name='hotel_agent',
description='A helpful AI assistant.',
instruction=prompt,
tools=toolbox_client.load_toolset("my-toolset"),
)
root_agent = Agent(
model='gemini-2.0-flash-001',
name='hotel_agent',
description='A helpful AI assistant.',
instruction=prompt,
tools=toolbox_client.load_toolset("my-toolset"),
)
session_service = InMemorySessionService()
artifacts_service = InMemoryArtifactService()
session = session_service.create_session(
state={}, app_name='hotel_agent', user_id='123'
)
runner = Runner(
app_name='hotel_agent',
agent=root_agent,
artifact_service=artifacts_service,
session_service=session_service,
)
session_service = InMemorySessionService()
artifacts_service = InMemoryArtifactService()
session = await session_service.create_session(
state={}, app_name='hotel_agent', user_id='123'
)
runner = Runner(
app_name='hotel_agent',
agent=root_agent,
artifact_service=artifacts_service,
session_service=session_service,
)
queries = [
"Find hotels in Basel with Basel in it's name.",
"Can you book the Hilton Basel for me?",
"Oh wait, this is too expensive. Please cancel it and book the Hyatt Regency instead.",
"My check in dates would be from April 10, 2024 to April 19, 2024.",
]
queries = [
"Find hotels in Basel with Basel in it's name.",
"Can you book the Hilton Basel for me?",
"Oh wait, this is too expensive. Please cancel it and book the Hyatt Regency instead.",
"My check in dates would be from April 10, 2024 to April 19, 2024.",
]
for query in queries:
content = types.Content(role='user', parts=[types.Part(text=query)])
events = runner.run(session_id=session.id,
user_id='123', new_message=content)
for query in queries:
content = types.Content(role='user', parts=[types.Part(text=query)])
events = runner.run(session_id=session.id,
user_id='123', new_message=content)
responses = (
part.text
for event in events
for part in event.content.parts
if part.text is not None
)
responses = (
part.text
for event in events
for part in event.content.parts
if part.text is not None
)
for text in responses:
print(text)
for text in responses:
print(text)
asyncio.run(main())
{{< /tab >}}
{{< tab header="LangChain" lang="python" >}}
import asyncio
@@ -604,23 +515,138 @@ async def run_application():
print(str(response))
asyncio.run(run_application())
{{< /tab >}}
{{< tab header="Core" lang="python" >}}
import asyncio
from google import genai
from google.genai.types import (
Content,
FunctionDeclaration,
GenerateContentConfig,
Part,
Tool,
)
from toolbox_core import ToolboxClient
prompt = """
You're a helpful hotel assistant. You handle hotel searching, booking and
cancellations. When the user searches for a hotel, mention it's name, id,
location and price tier. Always mention hotel id while performing any
searches. This is very important for any operations. For any bookings or
cancellations, please provide the appropriate confirmation. Be sure to
update checkin or checkout dates if mentioned by the user.
Don't ask for confirmations from the user.
"""
queries = [
"Find hotels in Basel with Basel in it's name.",
"Please book the hotel Hilton Basel for me.",
"This is too expensive. Please cancel it.",
"Please book Hyatt Regency for me",
"My check in dates for my booking would be from April 10, 2024 to April 19, 2024.",
]
async def run_application():
async with ToolboxClient("<http://127.0.0.1:5000>") as toolbox_client:
# The toolbox_tools list contains Python callables (functions/methods) designed for LLM tool-use
# integration. While this example uses Google's genai client, these callables can be adapted for
# various function-calling or agent frameworks. For easier integration with supported frameworks
# (https://github.com/googleapis/mcp-toolbox-python-sdk/tree/main/packages), use the
# provided wrapper packages, which handle framework-specific boilerplate.
toolbox_tools = await toolbox_client.load_toolset("my-toolset")
genai_client = genai.Client(
vertexai=True, project="project-id", location="us-central1"
)
genai_tools = [
Tool(
function_declarations=[
FunctionDeclaration.from_callable_with_api_option(callable=tool)
]
)
for tool in toolbox_tools
]
history = []
for query in queries:
user_prompt_content = Content(
role="user",
parts=[Part.from_text(text=query)],
)
history.append(user_prompt_content)
response = genai_client.models.generate_content(
model="gemini-2.0-flash-001",
contents=history,
config=GenerateContentConfig(
system_instruction=prompt,
tools=genai_tools,
),
)
history.append(response.candidates[0].content)
function_response_parts = []
for function_call in response.function_calls:
fn_name = function_call.name
# The tools are sorted alphabetically
if fn_name == "search-hotels-by-name":
function_result = await toolbox_tools[3](**function_call.args)
elif fn_name == "search-hotels-by-location":
function_result = await toolbox_tools[2](**function_call.args)
elif fn_name == "book-hotel":
function_result = await toolbox_tools[0](**function_call.args)
elif fn_name == "update-hotel":
function_result = await toolbox_tools[4](**function_call.args)
elif fn_name == "cancel-hotel":
function_result = await toolbox_tools[1](**function_call.args)
else:
raise ValueError("Function name not present.")
function_response = {"result": function_result}
function_response_part = Part.from_function_response(
name=function_call.name,
response=function_response,
)
function_response_parts.append(function_response_part)
if function_response_parts:
tool_response_content = Content(role="tool", parts=function_response_parts)
history.append(tool_response_content)
response2 = genai_client.models.generate_content(
model="gemini-2.0-flash-001",
contents=history,
config=GenerateContentConfig(
tools=genai_tools,
),
)
final_model_response_content = response2.candidates[0].content
history.append(final_model_response_content)
print(response2.text)
asyncio.run(run_application())
{{< /tab >}}
{{< /tabpane >}}
{{< tabpane text=true persist=header >}}
{{% tab header="Core" lang="en" %}}
To learn more about tool calling with Google GenAI, check out the
[Google GenAI Documentation](https://github.com/googleapis/python-genai?tab=readme-ov-file#manually-declare-and-invoke-a-function-for-function-calling).
{{% /tab %}}
{{< tabpane text=true persist=header >}}
{{% tab header="ADK" lang="en" %}}
To learn more about Agent Development Kit, check out the [ADK documentation.](https://google.github.io/adk-docs/)
To learn more about Agent Development Kit, check out the [ADK
documentation.](https://google.github.io/adk-docs/)
{{% /tab %}}
{{% tab header="Langchain" lang="en" %}}
To learn more about Agents in LangChain, check out the [LangGraph Agent documentation.](https://langchain-ai.github.io/langgraph/reference/prebuilt/#langgraph.prebuilt.chat_agent_executor.create_react_agent)
To learn more about Agents in LangChain, check out the [LangGraph Agent
documentation.](https://langchain-ai.github.io/langgraph/reference/prebuilt/#langgraph.prebuilt.chat_agent_executor.create_react_agent)
{{% /tab %}}
{{% tab header="LlamaIndex" lang="en" %}}
To learn more about Agents in LlamaIndex, check out the
[LlamaIndex AgentWorkflow documentation.](https://docs.llamaindex.ai/en/stable/examples/agent/agent_workflow_basic/)
To learn more about Agents in LlamaIndex, check out the [LlamaIndex
AgentWorkflow
documentation.](https://docs.llamaindex.ai/en/stable/examples/agent/agent_workflow_basic/)
{{% /tab %}}
{{% tab header="Core" lang="en" %}}
To learn more about tool calling with Google GenAI, check out the
[Google GenAI
Documentation](https://github.com/googleapis/python-genai?tab=readme-ov-file#manually-declare-and-invoke-a-function-for-function-calling).
{{% /tab %}}
{{< /tabpane >}}

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.7.0/$OS/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.8.0/$OS/toolbox
```
<!-- {x-release-please-end} -->
@@ -199,7 +199,8 @@ In this section, we will download Toolbox, configure our tools in a
- cancel-hotel
```
For more info on tools, check out the [Tools](../../resources/tools/_index.md) section.
For more info on tools, check out the
[Tools](../../resources/tools/_index.md) section.
1. Run the Toolbox server, pointing to the `tools.yaml` file created earlier:

View File

@@ -6,11 +6,14 @@ description: >
Connect your IDE to PostgreSQL using Toolbox.
---
[Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) is an open protocol for connecting Large Language Models (LLMs) to data sources like Postgres. This guide covers how to use [MCP Toolbox for Databases][toolbox] to expose your developer assistant tools to a Postgres instance:
[Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) is
an open protocol for connecting Large Language Models (LLMs) to data sources
like Postgres. This guide covers how to use [MCP Toolbox for Databases][toolbox]
to expose your developer assistant tools to a Postgres instance:
* [Cursor][cursor]
* [Windsurf][windsurf] (Codium)
* [Visual Studio Code ][vscode] (Copilot)
* [Visual Studio Code][vscode] (Copilot)
* [Cline][cline] (VS Code extension)
* [Claude desktop][claudedesktop]
* [Claude code][claudecode]
@@ -24,7 +27,8 @@ description: >
[claudecode]: #configure-your-mcp-client
{{< notice tip >}}
This guide can be used with [AlloyDB Omni](https://cloud.google.com/alloydb/omni/current/docs/overview).
This guide can be used with [AlloyDB
Omni](https://cloud.google.com/alloydb/omni/current/docs/overview).
{{< /notice >}}
## Set up the database
@@ -34,34 +38,37 @@ This guide can be used with [AlloyDB Omni](https://cloud.google.com/alloydb/omni
* [Install PostgreSQL locally](https://www.postgresql.org/download/)
* [Install AlloyDB Omni](https://cloud.google.com/alloydb/omni/current/docs/quickstart)
1. Create or reuse [a database user](https://cloud.google.com/alloydb/omni/current/docs/database-users/manage-users) and have the username and password ready.
1. Create or reuse [a database
user](https://cloud.google.com/alloydb/omni/current/docs/database-users/manage-users)
and have the username and password ready.
## 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.6.0+:
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.6.0+:
<!-- {x-release-please-start-version} -->
{{< tabpane persist=header >}}
{{< tab header="linux/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/linux/amd64/toolbox
curl -O <https://storage.googleapis.com/genai-toolbox/v0.8.0/linux/amd64/toolbox>
{{< /tab >}}
{{< tab header="darwin/arm64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/darwin/arm64/toolbox
curl -O <https://storage.googleapis.com/genai-toolbox/v0.8.0/darwin/arm64/toolbox>
{{< /tab >}}
{{< tab header="darwin/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/darwin/amd64/toolbox
curl -O <https://storage.googleapis.com/genai-toolbox/v0.8.0/darwin/amd64/toolbox>
{{< /tab >}}
{{< tab header="windows/amd64" lang="bash" >}}
curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/windows/amd64/toolbox
curl -O <https://storage.googleapis.com/genai-toolbox/v0.8.0/windows/amd64/toolbox>
{{< /tab >}}
{{< /tabpane >}}
<!-- {x-release-please-end} -->
1. Make the binary executable:
```bash
@@ -79,9 +86,11 @@ curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/windows/amd64/toolbo
{{< 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. 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:
1. Add the following configuration, replace the environment variables with your
values, and save:
```json
{
@@ -108,7 +117,8 @@ curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/windows/amd64/toolbo
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:
1. Add the following configuration, replace the environment variables with your
values, and save:
```json
{
@@ -129,14 +139,17 @@ curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/windows/amd64/toolbo
```
1. Restart Claude desktop.
1. From the new chat screen, you should see a hammer (MCP) icon appear with the new MCP server available.
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. 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:
1. Add the following configuration, replace the environment variables with your
values, and save:
```json
{
@@ -156,14 +169,16 @@ curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/windows/amd64/toolbo
}
```
1. You should see a green active status after the server is successfully connected.
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:
1. Add the following configuration, replace the environment variables with your
values, and save:
```json
{
@@ -183,14 +198,18 @@ curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/windows/amd64/toolbo
}
```
1. [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.
1. [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. 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:
1. Add the following configuration, replace the environment variables with your
values, and save:
```json
{
@@ -209,13 +228,16 @@ curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/windows/amd64/toolbo
}
}
```
{{% /tab %}}
{{% tab header="Windsurf" lang="en" %}}
1. Open [Windsurf](https://docs.codeium.com/windsurf) and navigate to the Cascade assistant.
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:
1. Add the following configuration, replace the environment variables with your
values, and save:
```json
{
@@ -235,12 +257,15 @@ curl -O https://storage.googleapis.com/genai-toolbox/v0.7.0/windows/amd64/toolbo
}
```
{{% /tab %}}
{{< /tabpane >}}
## Use Tools
Your AI tool is now connected to Postgres using MCP. Try asking your AI assistant to list tables, create a table, or define and execute other SQL statements.
Your AI tool is now connected to Postgres 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:
@@ -248,5 +273,6 @@ The following tools are available to the LLM:
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 >}}
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

@@ -7,36 +7,49 @@ description: >
---
## Toolbox SDKs vs Model Context Protocol (MCP)
Toolbox now supports connections via both the native Toolbox SDKs and via [Model Context Protocol (MCP)](https://modelcontextprotocol.io/). However, Toolbox has several features which are not supported in the MCP specification (such as Authenticated Parameters and Authorized invocation).
We recommend using the native SDKs over MCP clients to leverage these features. The native SDKs can be combined with MCP clients in many cases.
Toolbox now supports connections via both the native Toolbox SDKs and via [Model
Context Protocol (MCP)](https://modelcontextprotocol.io/). However, Toolbox has
several features which are not supported in the MCP specification (such as
Authenticated Parameters and Authorized invocation).
We recommend using the native SDKs over MCP clients to leverage these features.
The native SDKs can be combined with MCP clients in many cases.
### Protocol Versions
Toolbox currently supports the following versions of MCP specification:
* [2024-11-05](https://spec.modelcontextprotocol.io/specification/2024-11-05/)
### Features Not Supported by MCP
Toolbox has several features that are not yet supported in the MCP specification:
* **AuthZ/AuthN:** There are no auth implementation in the `2024-11-05` specification. This includes:
* **AuthZ/AuthN:** There are no auth implementation in the `2024-11-05`
specification. This includes:
* [Authenticated Parameters](../resources/tools/_index.md#authenticated-parameters)
* [Authorized Invocations](../resources/tools/_index.md#authorized-invocations)
* **Notifications:** Currently, editing Toolbox Tools requires a server restart. Clients should reload tools on disconnect to get the latest version.
* **Notifications:** Currently, editing Toolbox Tools requires a server restart.
Clients should reload tools on disconnect to get the latest version.
## Connecting to Toolbox with an MCP client
### Before you begin
{{< notice note >}}
{{< notice note >}}
MCP is only compatible with Toolbox version 0.3.0 and above.
{{< /notice >}}
1. [Install](../getting-started/introduction/_index.md#installing-the-server) Toolbox version 0.3.0+.
1. [Install](../getting-started/introduction/_index.md#installing-the-server)
Toolbox version 0.3.0+.
1. Make sure you've set up and initialized your database.
1. [Set up](../getting-started/configure.md) your `tools.yaml` file.
### Connecting via Standard Input/Output (stdio)
Toolbox supports the
[stdio](https://modelcontextprotocol.io/docs/concepts/transports#standard-input%2Foutput-stdio)
transport protocol. Users that wish to use stdio will have to include the
@@ -47,14 +60,16 @@ transport protocol. Users that wish to use stdio will have to include the
```
When running with stdio, Toolbox will listen via stdio instead of acting as a
remote HTTP server. Logs will be set to the `warn` level by default. `debug` and `info` logs are not
supported with stdio.
remote HTTP server. Logs will be set to the `warn` level by default. `debug` and
`info` logs are not supported with stdio.
### Connecting via HTTP
Toolbox supports the HTTP transport protocol with and without SSE.
{{< tabpane text=true >}} {{% tab header="HTTP with SSE" lang="en" %}}
Add the following configuration to your MCP client configuration:
```bash
{
"mcpServers": {
@@ -66,11 +81,13 @@ Add the following configuration to your MCP client configuration:
}
```
If you would like to connect to a specific toolset, replace `url` with `"http://127.0.0.1:5000/mcp/{toolset_name}/sse"`.
If you would like to connect to a specific toolset, replace `url` with
`"http://127.0.0.1:5000/mcp/{toolset_name}/sse"`.
{{% /tab %}} {{% tab header="HTTP POST" lang="en" %}}
Connect to Toolbox HTTP POST via `http://127.0.0.1:5000/mcp`.
If you would like to connect to a specific toolset, connect via `http://127.0.0.1:5000/mcp/{toolset_name}`.
If you would like to connect to a specific toolset, connect via
`http://127.0.0.1:5000/mcp/{toolset_name}`.
{{% /tab %}} {{< /tabpane >}}
### Using the MCP Inspector with Toolbox
@@ -80,6 +97,7 @@ testing and debugging Toolbox server.
{{< tabpane text=true >}}
{{% tab header="STDIO" lang="en" %}}
1. Run Inspector with Toolbox as a subprocess:
```bash
@@ -88,7 +106,8 @@ testing and debugging Toolbox server.
1. For `Transport Type` dropdown menu, select `STDIO`.
1. In `Command`, make sure that it is set to :`./toolbox` (or the correct path to where the Toolbox binary is installed).
1. In `Command`, make sure that it is set to :`./toolbox` (or the correct path
to where the Toolbox binary is installed).
1. In `Arguments`, make sure that it's filled with `--stdio`.
@@ -117,8 +136,8 @@ testing and debugging Toolbox server.
| Client | SSE Works | MCP Config Docs |
|--------|--------|--------|
| Claude Desktop | ✅ | https://modelcontextprotocol.io/quickstart/user#1-download-claude-for-desktop |
| MCP Inspector | ✅ | https://github.com/modelcontextprotocol/inspector |
| Cursor | ✅ | https://docs.cursor.com/context/model-context-protocol |
| Windsurf | ✅ | https://docs.windsurf.com/windsurf/mcp |
| VS Code (Insiders) | ✅ | https://code.visualstudio.com/docs/copilot/chat/mcp-servers |
| Claude Desktop | ✅ | <https://modelcontextprotocol.io/quickstart/user#1-download-claude-for-desktop> |
| MCP Inspector | ✅ | <https://github.com/modelcontextprotocol/inspector> |
| Cursor | ✅ | <https://docs.cursor.com/context/model-context-protocol> |
| Windsurf | ✅ | <https://docs.windsurf.com/windsurf/mcp> |
| VS Code (Insiders) | ✅ | <https://code.visualstudio.com/docs/copilot/chat/mcp-servers> |

View File

@@ -8,7 +8,6 @@ description: >
<!-- Contributor: Sujith R Pillai <sujithrpillai@gmail.com> -->
## Before you begin
1. [Install Docker Compose.](https://docs.docker.com/compose/install/)
@@ -74,7 +73,6 @@ networks:
docker-compose up -d
```
{{< notice tip >}}
You can use this setup quickly set up Toolbox + Postgres to follow along in our
@@ -82,8 +80,6 @@ You can use this setup quickly set up Toolbox + Postgres to follow along in our
{{< /notice >}}
## Connecting with Toolbox Client SDK
Next, we will use Toolbox with the Client SDKs:
@@ -101,14 +97,14 @@ Next, we will use Toolbox with the Client SDKs:
from toolbox_langchain import ToolboxClient
# Replace with the cloud run service URL generated above
async with ToolboxClient("http://$YOUR_URL") as toolbox:
{{< /tab >}}
{{< tab header="Llamaindex" lang="Python" >}}
from toolbox_llamaindex import ToolboxClient
# Replace with the cloud run service URL generated above
async with ToolboxClient("http://$YOUR_URL") as toolbox:
{{< /tab >}}
{{< /tabpane >}}

View File

@@ -9,7 +9,6 @@ description: >
## Before you begin
1. Set the PROJECT_ID environment variable:
```bash
@@ -41,7 +40,6 @@ description: >
kubectl version --client
```
1. If needed, install `kubectl` component using the Google Cloud CLI:
```bash
@@ -62,7 +60,7 @@ description: >
gcloud iam service-accounts create $SA_NAME
```
1. Grant any IAM roles necessary to the IAM service account. Each source have a
1. Grant any IAM roles necessary to the IAM service account. Each source have a
list of necessary IAM permissions listed on it's page. The example below is
for cloud sql postgres source:
@@ -254,6 +252,7 @@ description: >
```
## Clean up resources
1. Delete secret.
```bash

View File

@@ -54,7 +54,6 @@ AlloyDB or Cloud SQL over private IP), make sure your Cloud Run service and the
database are in the same VPC network.
{{< /notice >}}
## Create a service account
1. Create a backend service account if you don't already have one:
@@ -63,7 +62,7 @@ database are in the same VPC network.
gcloud iam service-accounts create toolbox-identity
```
1. Grant permissions to use secret manager:
1. Grant permissions to use secret manager:
```bash
gcloud projects add-iam-policy-binding $PROJECT_ID \
@@ -71,7 +70,8 @@ database are in the same VPC network.
--role roles/secretmanager.secretAccessor
```
1. Grant additional permissions to the service account that are specific to the source, e.g.:
1. Grant additional permissions to the service account that are specific to the
source, e.g.:
- [AlloyDB for PostgreSQL](../resources/sources/alloydb-pg.md#iam-permissions)
- [Cloud SQL for PostgreSQL](../resources/sources/cloud-sql-pg.md#iam-permissions)
@@ -97,7 +97,8 @@ section.
gcloud secrets versions add tools --data-file=tools.yaml
```
1. Set an environment variable to the container image that you want to use for cloud run:
1. Set an environment variable to the container image that you want to use for
cloud run:
```bash
export IMAGE=us-central1-docker.pkg.dev/database-toolbox/toolbox/toolbox:latest
@@ -134,12 +135,14 @@ section.
You can connect to Toolbox Cloud Run instances directly through the SDK
1. [Set up `Cloud Run Invoker` role access](https://cloud.google.com/run/docs/securing/managing-access#service-add-principals) to your Cloud Run service.
1. [Set up `Cloud Run Invoker` role
access](https://cloud.google.com/run/docs/securing/managing-access#service-add-principals)
to your Cloud Run service.
1. Set up [Application Default
Credentials](https://cloud.google.com/docs/authentication/set-up-adc-local-dev-environment)
for the principle you set up the `Cloud Run Invoker` role access to.
{{< notice tip >}}
If you're working in some other environment than local, set up [environment
specific Default

View File

@@ -20,6 +20,7 @@ the need to run, operate, and maintain multiple agents/collectors.
To configure the collector, you will have to provide a configuration file. The
configuration file consists of four classes of pipeline component that access
telemetry data.
- `Receivers`
- `Processors`
- `Exporters`

View File

@@ -62,7 +62,9 @@ token you will provide a function (that returns an id). This function is called
when the tool is invoked. This allows you to cache and refresh the ID token as
needed.
The primary method for providing these getters is via the `auth_token_getters` parameter when loading tools, or the `add_auth_token_getter`() / `add_auth_token_getters()` methods on a loaded tool object.
The primary method for providing these getters is via the `auth_token_getters`
parameter when loading tools, or the `add_auth_token_getter`() /
`add_auth_token_getters()` methods on a loaded tool object.
### Specifying tokens during load
@@ -77,7 +79,7 @@ async def get_auth_token():
return "YOUR_ID_TOKEN" # Placeholder
async def main():
async with ToolboxClient("http://127.0.0.1:5000") as toolbox:
async with ToolboxClient("<http://127.0.0.1:5000>") as toolbox:
auth_tool = await toolbox.load_tool(
"get_sensitive_data",
auth_token_getters={"my_auth_app_1": get_auth_token}
@@ -85,7 +87,7 @@ async def main():
result = await auth_tool(param="value")
print(result)
if __name__ == "__main__":
if **name** == "**main**":
asyncio.run(main())
{{< /tab >}}
{{< tab header="LangChain" lang="Python" >}}
@@ -98,7 +100,7 @@ async def get_auth_token():
return "YOUR_ID_TOKEN" # Placeholder
async def main():
toolbox = ToolboxClient("http://127.0.0.1:5000")
toolbox = ToolboxClient("<http://127.0.0.1:5000>")
auth_tool = await toolbox.aload_tool(
"get_sensitive_data",
@@ -107,7 +109,7 @@ async def main():
result = await auth_tool.ainvoke({"param": "value"})
print(result)
if __name__ == "__main__":
if **name** == "**main**":
asyncio.run(main())
{{< /tab >}}
{{< tab header="Llamaindex" lang="Python" >}}
@@ -120,7 +122,7 @@ async def get_auth_token():
return "YOUR_ID_TOKEN" # Placeholder
async def main():
toolbox = ToolboxClient("http://127.0.0.1:5000")
toolbox = ToolboxClient("<http://127.0.0.1:5000>")
auth_tool = await toolbox.aload_tool(
"get_sensitive_data",
@@ -129,7 +131,7 @@ async def main():
# result = await auth_tool.acall(param="value")
# print(result.content)
if __name__ == "__main__":
if **name** == "**main**":
asyncio.run(main()){{< /tab >}}
{{< /tabpane >}}

View File

@@ -81,8 +81,8 @@ To connect using IAM authentication:
1. Prepare your database instance and user following this [guide][iam-guide].
2. You could choose one of the two ways to log in:
- Specify your IAM email as the `user`.
- Leave your `user` field blank. Toolbox
will fetch the [ADC][adc] automatically and log in using the email associated with it.
- Leave your `user` field blank. Toolbox will fetch the [ADC][adc]
automatically and log in using the email associated with it.
3. Leave the `password` field blank.
[iam-guide]: https://cloud.google.com/alloydb/docs/database-users/manage-iam-auth

View File

@@ -63,8 +63,8 @@ mTLS.
### Database User
Currently, this source only uses standard authentication. You will need to [create a
SQL Server user][cloud-sql-users] to login to the database with.
Currently, this source only uses standard authentication. You will need to
[create a SQL Server user][cloud-sql-users] to login to the database with.
[cloud-sql-users]: https://cloud.google.com/sql/docs/sqlserver/create-manage-users
@@ -96,7 +96,7 @@ instead of hardcoding your secrets into the configuration file.
| kind | string | true | Must be "cloud-sql-mssql". |
| project | string | true | Id of the GCP project that the cluster was created in (e.g. "my-project-id"). |
| region | string | true | Name of the GCP region that the cluster was created in (e.g. "us-central1"). |
| instance | string | true | Name of the Cloud SQL instance within the cluster (e.g. "my-instance"). |
| instance | string | true | Name of the Cloud SQL instance within the cluster (e.g. "my-instance"). |
| database | string | true | Name of the Cloud SQL database to connect to (e.g. "my_db"). |
| ipAddress | string | true | IP address of the Cloud SQL instance to connect to. |
| user | string | true | Name of the SQL Server user to connect as (e.g. "my-pg-user"). |

View File

@@ -85,8 +85,8 @@ To connect using IAM authentication:
1. Prepare your database instance and user following this [guide][iam-guide].
2. You could choose one of the two ways to log in:
- Specify your IAM email as the `user`.
- Leave your `user` field blank. Toolbox
will fetch the [ADC][adc] automatically and log in using the email associated with it.
- Leave your `user` field blank. Toolbox will fetch the [ADC][adc]
automatically and log in using the email associated with it.
3. Leave the `password` field blank.
@@ -115,13 +115,13 @@ instead of hardcoding your secrets into the configuration file.
## Reference
| **field** | **type** | **required** | **description** |
|-----------|:--------:|:------------:|---------------------------------------------------------------------------------------------|
| kind | string | true | Must be "cloud-sql-postgres". |
| project | string | true | Id of the GCP project that the cluster was created in (e.g. "my-project-id"). |
| region | string | true | Name of the GCP region that the cluster was created in (e.g. "us-central1"). |
| instance | string | true | Name of the Cloud SQL instance within the cluster (e.g. "my-instance"). |
| database | string | true | Name of the Postgres database to connect to (e.g. "my_db"). |
| user | string | false | Name of the Postgres user to connect as (e.g. "my-pg-user"). Defaults to IAM auth using [ADC][adc] email if unspecified. |
| password | string | false | Password of the Postgres user (e.g. "my-password"). Defaults to attempting IAM authentication if unspecified. |
| ipType | string | false | IP Type of the Cloud SQL instance; must be one of `public` or `private`. Default: `public`. |
| **field** | **type** | **required** | **description** |
|-----------|:--------:|:------------:|--------------------------------------------------------------------------------------------------------------------------|
| kind | string | true | Must be "cloud-sql-postgres". |
| project | string | true | Id of the GCP project that the cluster was created in (e.g. "my-project-id"). |
| region | string | true | Name of the GCP region that the cluster was created in (e.g. "us-central1"). |
| instance | string | true | Name of the Cloud SQL instance within the cluster (e.g. "my-instance"). |
| database | string | true | Name of the Postgres database to connect to (e.g. "my_db"). |
| user | string | false | Name of the Postgres user to connect as (e.g. "my-pg-user"). Defaults to IAM auth using [ADC][adc] email if unspecified. |
| password | string | false | Password of the Postgres user (e.g. "my-password"). Defaults to attempting IAM authentication if unspecified. |
| ipType | string | false | IP Type of the Cloud SQL instance; must be one of `public` or `private`. Default: `public`. |

View File

@@ -8,7 +8,8 @@ description: >
## About
A `couchbase` source establishes a connection to a Couchbase database cluster, allowing tools to execute SQL queries against it.
A `couchbase` source establishes a connection to a Couchbase database cluster,
allowing tools to execute SQL queries against it.
## Example
@@ -25,19 +26,19 @@ sources:
## Reference
| **field** | **type** | **required** | **description** |
|---------------------|:--------:|:------------:|-----------------------------------------------------------------------------------------------------------------------------|
| kind | string | true | Must be "couchbase". |
| connectionString | string | true | Connection string for the Couchbase cluster. |
| bucket | string | true | Name of the bucket to connect to. |
| scope | string | true | Name of the scope within the bucket. |
| username | string | false | Username for authentication. |
| password | string | false | Password for authentication. |
| clientCert | string | false | Path to client certificate file for TLS authentication. |
| clientCertPassword| string | false | Password for the client certificate. |
| clientKey | string | false | Path to client key file for TLS authentication. |
| clientKeyPassword | string | false | Password for the client key. |
| caCert | string | false | Path to CA certificate file. |
| noSslVerify | boolean | false | If true, skip server certificate verification. **Warning:** This option should only be used in development or testing environments. Disabling SSL verification poses significant security risks in production as it makes your connection vulnerable to man-in-the-middle attacks. |
| profile | string | false | Name of the connection profile to apply. |
| **field** | **type** | **required** | **description** |
|----------------------|:--------:|:------------:|---------------------------------------------------------|
| kind | string | true | Must be "couchbase". |
| connectionString | string | true | Connection string for the Couchbase cluster. |
| bucket | string | true | Name of the bucket to connect to. |
| scope | string | true | Name of the scope within the bucket. |
| username | string | false | Username for authentication. |
| password | string | false | Password for authentication. |
| clientCert | string | false | Path to client certificate file for TLS authentication. |
| clientCertPassword | string | false | Password for the client certificate. |
| clientKey | string | false | Path to client key file for TLS authentication. |
| clientKeyPassword | string | false | Password for the client key. |
| caCert | string | false | Path to CA certificate file. |
| noSslVerify | boolean | false | If true, skip server certificate verification. **Warning:** This option should only be used in development or testing environments. Disabling SSL verification poses significant security risks in production as it makes your connection vulnerable to man-in-the-middle attacks. |
| profile | string | false | Name of the connection profile to apply. |
| queryScanConsistency | integer | false | Query scan consistency. Controls the consistency guarantee for index scanning. Values: 1 for "not_bounded" (fastest option, but results may not include the most recent operations), 2 for "request_plus" (highest consistency level, includes all operations up until the query started, but incurs a performance penalty). If not specified, defaults to the Couchbase Go SDK default. |

View File

@@ -9,7 +9,10 @@ description: >
## About
[Dgraph][dgraph-docs] is an open-source graph database. It is designed for real-time workloads, horizontal scalability, and data flexibility. Implemented as a distributed system, Dgraph processes queries in parallel to deliver the fastest result.
[Dgraph][dgraph-docs] is an open-source graph database. It is designed for
real-time workloads, horizontal scalability, and data flexibility. Implemented
as a distributed system, Dgraph processes queries in parallel to deliver the
fastest result.
This source can connect to either a self-managed Dgraph cluster or one hosted on
Dgraph Cloud. If you're new to Dgraph, the fastest way to get started is to
@@ -52,7 +55,7 @@ instead of hardcoding your secrets into the configuration file.
| **Field** | **Type** | **Required** | **Description** |
|-------------|:--------:|:------------:|--------------------------------------------------------------------------------------------------|
| kind | string | true | Must be "dgraph". |
| dgraphUrl | string | true | Connection URI (e.g. "<https://xxx.cloud.dgraph.io>", "<https://localhost:8080>"). |
| dgraphUrl | string | true | Connection URI (e.g. "<https://xxx.cloud.dgraph.io>", "<https://localhost:8080>"). |
| user | string | false | Name of the Dgraph user to connect as (e.g., "groot"). |
| password | string | false | Password of the Dgraph user (e.g., "password"). |
| apiKey | string | false | API key to connect to a Dgraph Cloud instance. |

View File

@@ -10,15 +10,21 @@ description: >
## About
Redis is an open-source, in-memory data structure store, used as a database, cache, and message broker. It supports data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, and geospatial indexes with radius queries.
Redis is an open-source, in-memory data structure store, used as a database,
cache, and message broker. It supports data structures such as strings, hashes,
lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, and
geospatial indexes with radius queries.
If you are new to Redis, you can find installation and getting started guides on the [official Redis website](https://redis.io/docs/getting-started/).
If you are new to Redis, you can find installation and getting started guides on
the [official Redis website](https://redis.io/docs/getting-started/).
## Requirements
### Redis
[AUTH string][auth] is a password for connection to Redis. If you have the `requirepass` directive set in your Redis configuration, incoming client connections must authenticate in order to connect.
[AUTH string][auth] is a password for connection to Redis. If you have the
`requirepass` directive set in your Redis configuration, incoming client
connections must authenticate in order to connect.
Specify your AUTH string in the password field:
@@ -58,8 +64,8 @@ sources:
# clusterEnabled: false
```
Memorystore Redis Cluster supports IAM authentication instead. Grant your account the
required [IAM role][iam] and make sure to set `useGCPIAM` to `true`.
Memorystore Redis Cluster supports IAM authentication instead. Grant your
account the required [IAM role][iam] and make sure to set `useGCPIAM` to `true`.
Here is an example tools.yaml config for Memorystore Redis Cluster instances
using IAM authentication:

View File

@@ -15,6 +15,7 @@ database management system. The lite in SQLite means lightweight in terms of
setup, database administration, and required resources.
SQLite has the following notable characteristics:
- Self-contained with no external dependencies
- Serverless - the SQLite library accesses its storage files directly
- Single database file that can be easily copied or moved
@@ -26,6 +27,7 @@ SQLite has the following notable characteristics:
### Database File
You need a SQLite database file. This can be:
- An existing database file
- A path where a new database file should be created
- `:memory:` for an in-memory database
@@ -40,6 +42,7 @@ sources:
```
For an in-memory database:
```yaml
sources:
my-sqlite-memory-db:
@@ -51,13 +54,14 @@ sources:
### Configuration Fields
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| kind | string | Yes | Must be "sqlite" |
| database | string | Yes | Path to SQLite database file, or ":memory:" for an in-memory database |
| **field** | **type** | **required** | **description** |
|-----------|:--------:|:------------:|---------------------------------------------------------------------------------------------------------------------|
| kind | string | true | Must be "spanner". |
| database | string | true | Path to SQLite database file, or ":memory:" for an in-memory database. |
### Connection Properties
SQLite connections are configured with these defaults for optimal performance:
- `MaxOpenConns`: 1 (SQLite only supports one writer at a time)
- `MaxIdleConns`: 1
- `MaxIdleConns`: 1

View File

@@ -10,9 +10,14 @@ description: >
## About
Valkey is an open-source, in-memory data structure store that originated as a fork of Redis. It's designed to be used as a database, cache, and message broker, supporting a wide range of data structures like strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, and geospatial indexes with radius queries.
Valkey is an open-source, in-memory data structure store that originated as a
fork of Redis. It's designed to be used as a database, cache, and message
broker, supporting a wide range of data structures like strings, hashes, lists,
sets, sorted sets with range queries, bitmaps, hyperloglogs, and geospatial
indexes with radius queries.
If you're new to Valkey, you can find installation and getting started guides on the [official Valkey website](https://valkey.io/docs/getting-started/).
If you're new to Valkey, you can find installation and getting started guides on
the [official Valkey website](https://valkey.io/docs/getting-started/).
## Example

View File

@@ -11,7 +11,6 @@ A tool represents an action your agent can take, such as running a SQL
statement. You can define Tools as a map in the `tools` section of your
`tools.yaml` file. Typically, a tool will require a source to act on:
```yaml
tools:
search_flights_by_number:
@@ -50,7 +49,6 @@ tools:
description: 1 to 4 digit number
```
## Specifying Parameters
Parameters for each Tool will define what inputs the agent will need to provide
@@ -89,18 +87,20 @@ the parameter.
### Array Parameters
The `array` type is a list of items passed in as a single parameter.
To use the `array` type, you must also specify what kind of items are
To use the `array` type, you must also specify what kind of items are
in the list using the items field:
```yaml
parameters:
- name: preferred_airlines
type: array
description: A list of airline, ordered by preference.
description: A list of airline, ordered by preference.
items:
name: name
name: name
type: string
description: Name of the airline.
description: Name of the airline.
statement: |
SELECT * FROM airlines WHERE preferred_airlines = ANY($1);
```
| **field** | **type** | **required** | **description** |
@@ -118,11 +118,12 @@ Items in array should not have a default value. If provided, it will be ignored.
### Authenticated Parameters
Authenticated parameters are automatically populated with user
information decoded from [ID tokens](../authsources/#specifying-id-tokens-from-clients) that
are passed in request headers. They do not take input values in request bodies
like other parameters. To use authenticated parameters, you must configure
the tool to map the required [authServices](../authservices) to
specific claims within the user's ID token.
information decoded from [ID
tokens](../authsources/#specifying-id-tokens-from-clients) that are passed in
request headers. They do not take input values in request bodies like other
parameters. To use authenticated parameters, you must configure the tool to map
the required [authServices](../authservices) to specific claims within the
user's ID token.
```yaml
tools:
@@ -149,20 +150,22 @@ specific claims within the user's ID token.
### Template Parameters
Template parameters types include `string`, `integer`, `float`, `boolean` types. In
most cases, the description will be provided to the LLM as context on specifying
the parameter. Template parameters will be inserted into the SQL statement before
executing the prepared statement. They will be inserted without quotes, so to
insert a string using template parameters, quotes must be explicitly added within
the string.
Template parameters types include `string`, `integer`, `float`, `boolean` types.
In most cases, the description will be provided to the LLM as context on
specifying the parameter. Template parameters will be inserted into the SQL
statement before executing the prepared statement. They will be inserted without
quotes, so to insert a string using template parameters, quotes must be
explicitly added within the string.
Template parameter arrays can also be used similarly to basic parameters, and array
items must be strings. Once inserted into the SQL statement, the outer layer of quotes
will be removed. Therefore to insert strings into the SQL statement, a set of quotes
must be explicitly added within the string.
items must be strings. Once inserted into the SQL statement, the outer layer of
quotes will be removed. Therefore to insert strings into the SQL statement, a
set of quotes must be explicitly added within the string.
{{< notice warning >}}
Because template parameters can directly replace identifiers, column names, and table names, they are prone to SQL injections. Basic parameters are preferred for performance and safety reasons.
Because template parameters can directly replace identifiers, column names, and
table names, they are prone to SQL injections. Basic parameters are preferred
for performance and safety reasons.
{{< /notice >}}
```yaml

View File

@@ -0,0 +1,7 @@
---
title: "AlloyDB AI NL"
type: docs
weight: 1
description: >
AlloyDB AI NL Tool.
---

View File

@@ -7,6 +7,8 @@ description: >
[AlloyDB AI](https://cloud.google.com/alloydb/ai) next-generation Natural
Language support to provide the ability to query the database directly using
natural language.
aliases:
- /resources/tools/alloydb-ai-nl
---
## About
@@ -16,18 +18,20 @@ Language][alloydb-ai-nl-overview] support to allow an Agent the ability to query
the database directly using natural language. Natural language streamlines the
development of generative AI applications by transferring the complexity of
converting natural language to SQL from the application layer to the database
layer.
layer.
This tool is compatible with the following sources:
- [alloydb-postgres](../sources/alloydb-pg.md)
AlloyDB AI Natural Language delivers secure and accurate responses for
application end user natural language questions. Natural language streamlines
the development of generative AI applications by transferring the complexity
of converting natural language to SQL from the application layer to the
AlloyDB AI Natural Language delivers secure and accurate responses for
application end user natural language questions. Natural language streamlines
the development of generative AI applications by transferring the complexity
of converting natural language to SQL from the application layer to the
database layer.
## Requirements
{{< notice tip >}} AlloyDB AI natural language is currently in gated public
preview. For more information on availability and limitations, please see
[AlloyDB AI natural language overview](https://cloud.google.com/alloydb/docs/ai/natural-language-overview)
@@ -41,16 +45,16 @@ context for your application.
[alloydb-ai-nl-overview]: https://cloud.google.com/alloydb/docs/ai/natural-language-overview
[alloydb-ai-gen-nl]: https://cloud.google.com/alloydb/docs/ai/generate-sql-queries-natural-language
## Configuration
### Specifying an `nl_config`
A `nl_config` is a configuration that associates an application to schema
objects, examples and other contexts that can be used. A large application can
also use different configurations for different parts of the app, as long as the
correct configuration can be specified when a question is sent from that part of
the application.
Once you've followed the steps for configuring context, you can use the
`context` field when configuring a `alloydb-ai-nl` tool. When this tool is
invoked, the SQL will be generated and executed using this context.
@@ -58,9 +62,9 @@ invoked, the SQL will be generated and executed using this context.
### Specifying Parameters to PSV's
[Parameterized Secure Views (PSVs)][alloydb-psv] are a feature unique to AlloyDB
that allows you allow you to require one or more named parameter values passed
that allows you to require one or more named parameter values passed
to the view when querying it, somewhat like bind variables with ordinary
database queries.
database queries.
You can use the `nlConfigParameters` to list the parameters required for your
`nl_config`. You **must** supply all parameters required for all PSVs in the

View File

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

View File

@@ -4,6 +4,8 @@ type: docs
weight: 1
description: >
A "bigquery-execute-sql" tool executes a SQL statement against BigQuery.
aliases:
- /resources/tools/bigquery-execute-sql
---
## About
@@ -30,6 +32,6 @@ tools:
| **field** | **type** | **required** | **description** |
|-------------|:------------------------------------------:|:------------:|--------------------------------------------------------------------------------------------------|
| kind | string | true | Must be "bigquery-execute-sql". |
| kind | string | true | Must be "bigquery-execute-sql". |
| 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

@@ -4,6 +4,8 @@ type: docs
weight: 1
description: >
A "bigquery-get-dataset-info" tool retrieves metadata for a BigQuery dataset.
aliases:
- /resources/tools/bigquery-get-dataset-info
---
## About
@@ -13,7 +15,7 @@ It's compatible with the following sources:
- [bigquery](../sources/bigquery.md)
bigquery-get-dataset-info takes a dataset parameter to specify the dataset
bigquery-get-dataset-info takes a dataset parameter to specify the dataset
on the given source.
## Example
@@ -30,6 +32,6 @@ tools:
| **field** | **type** | **required** | **description** |
|-------------|:------------------------------------------:|:------------:|--------------------------------------------------------------------------------------------------|
| kind | string | true | Must be "bigquery-get-dataset-info". |
| kind | string | true | Must be "bigquery-get-dataset-info". |
| 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

@@ -4,6 +4,8 @@ type: docs
weight: 1
description: >
A "bigquery-get-table-info" tool retrieves metadata for a BigQuery table.
aliases:
- /resources/tools/bigquery-get-table-info
---
## About
@@ -13,7 +15,7 @@ It's compatible with the following sources:
- [bigquery](../sources/bigquery.md)
bigquery-get-table-info takes dataset and table parameters to specify
bigquery-get-table-info takes dataset and table parameters to specify
the target table.
## Example
@@ -30,6 +32,6 @@ tools:
| **field** | **type** | **required** | **description** |
|-------------|:------------------------------------------:|:------------:|--------------------------------------------------------------------------------------------------|
| kind | string | true | Must be "bigquery-get-table-info". |
| kind | string | true | Must be "bigquery-get-table-info". |
| 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

@@ -4,6 +4,8 @@ type: docs
weight: 1
description: >
A "bigquery-list-dataset-ids" tool returns all dataset IDs from the source.
aliases:
- /resources/tools/bigquery-list-dataset-ids
---
## About
@@ -13,7 +15,7 @@ It's compatible with the following sources:
- [bigquery](../sources/bigquery.md)
bigquery-list-dataset-ids requires no input parameters beyond the configured
bigquery-list-dataset-ids requires no input parameters beyond the configured
source.
## Example
@@ -30,6 +32,6 @@ tools:
| **field** | **type** | **required** | **description** |
|-------------|:------------------------------------------:|:------------:|--------------------------------------------------------------------------------------------------|
| kind | string | true | Must be "bigquery-list-dataset-ids". |
| kind | string | true | Must be "bigquery-list-dataset-ids". |
| 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

@@ -4,6 +4,8 @@ type: docs
weight: 1
description: >
A "bigquery-list-table-ids" tool returns table IDs in a given BigQuery dataset.
aliases:
- /resources/tools/bigquery-list-table-ids
---
## About
@@ -30,6 +32,6 @@ tools:
| **field** | **type** | **required** | **description** |
|-------------|:------------------------------------------:|:------------:|--------------------------------------------------------------------------------------------------|
| kind | string | true | Must be "bigquery-list-table-ids". |
| kind | string | true | Must be "bigquery-list-table-ids". |
| 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

@@ -4,10 +4,13 @@ type: docs
weight: 1
description: >
A "bigquery-sql" tool executes a pre-defined SQL statement.
aliases:
- /resources/tools/bigquery-sql
---
## About
A `bigquery-sql` tool executes a pre-defined SQL statement. It's compatible with
A `bigquery-sql` tool executes a pre-defined SQL statement. It's compatible with
the following sources:
- [bigquery](../sources/bigquery.md)
@@ -99,4 +102,4 @@ tools:
| description | string | true | Description of the tool that is passed to the LLM. |
| statement | string | true | The GoogleSQL statement to execute. |
| parameters | [parameters](_index#specifying-parameters) | false | List of [parameters](_index#specifying-parameters) that will be inserted into the SQL statement. |
| templateParameters | [templateParameters](_index#template-parameters) | false | List of [templateParameters](_index#template-parameters) that will be inserted into the SQL statement before executing prepared statement. |
| templateParameters | [templateParameters](_index#template-parameters) | false | List of [templateParameters](_index#template-parameters) that will be inserted into the SQL statement before executing prepared statement. |

View File

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

View File

@@ -5,6 +5,8 @@ weight: 1
description: >
A "bigtable-sql" tool executes a pre-defined SQL statement against a Google
Cloud Bigtable instance.
aliases:
- /resources/tools/bigtable-sql
---
## About

View File

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

View File

@@ -5,6 +5,8 @@ weight: 1
description: >
A "couchbase-sql" tool executes a pre-defined SQL statement against a Couchbase
database.
aliases:
- /resources/tools/couchbase-sql
---
## About
@@ -95,4 +97,4 @@ tools:
| statement | string | true | SQL statement to execute |
| parameters | [parameters](_index#specifying-parameters) | false | List of [parameters](_index#specifying-parameters) that will be used with the SQL statement. |
| templateParameters | [templateParameters](_index#template-parameters) | false | List of [templateParameters](_index#template-parameters) that will be inserted into the SQL statement before executing prepared statement. |
| authRequired | array[string] | false | List of auth services that are required to use this tool. |
| authRequired | array[string] | false | List of auth services that are required to use this tool. |

View File

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

View File

@@ -5,6 +5,8 @@ weight: 1
description: >
A "dgraph-dql" tool executes a pre-defined DQL statement against a Dgraph
database.
aliases:
- /resources/tools/dgraph-dql
---
## About
@@ -117,6 +119,6 @@ tools:
| source | string | true | Name of the source the dql query should execute on. |
| description | string | true | Description of the tool that is passed to the LLM. |
| statement | string | true | dql statement to execute |
| isQuery | boolean | false | To run statement as query set true otherwise false |
| timeout | string | false | To set timeout for query |
| isQuery | boolean | false | To run statement as query set true otherwise false |
| timeout | string | false | To set timeout for query |
| parameters | [parameters](_index#specifying-parameters) | false | List of [parameters](_index#specifying-parameters) that will be used with the dql statement. |

View File

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

View File

@@ -4,14 +4,18 @@ type: docs
weight: 1
description: >
A "http" tool sends out an HTTP request to an HTTP endpoint.
aliases:
- /resources/tools/http
---
## About
The `http` tool allows you to make HTTP requests to APIs to retrieve data.
An HTTP request is the method by which a client communicates with a server to retrieve or manipulate resources.
Toolbox allows you to configure the request URL, method, headers, query parameters, and the request body for an HTTP Tool.
An HTTP request is the method by which a client communicates with a server to
retrieve or manipulate resources.
Toolbox allows you to configure the request URL, method, headers, query
parameters, and the request body for an HTTP Tool.
### URL
@@ -20,9 +24,11 @@ Toolbox composes the request URL from 3 places:
1. The HTTP Source's `baseUrl`.
2. The HTTP Tool's `path` field.
3. The HTTP Tool's `pathParams` for dynamic path composed during Tool invocation.
3. The HTTP Tool's `pathParams` for dynamic path composed during Tool
invocation.
For example, the following config allows you to reach different paths of the same server using multiple Tools:
For example, the following config allows you to reach different paths of the
same server using multiple Tools:
```yaml
sources:
@@ -60,11 +66,15 @@ tools:
### Headers
An HTTP request header is a key-value pair sent by a client to a server, providing additional information about the request, such as the client's preferences, the request body content type, and other metadata.
Headers specified by the HTTP Tool are combined with its HTTP Source headers for the resulting HTTP request, and override the Source headers in case of conflict.
An HTTP request header is a key-value pair sent by a client to a server,
providing additional information about the request, such as the client's
preferences, the request body content type, and other metadata.
Headers specified by the HTTP Tool are combined with its HTTP Source headers for
the resulting HTTP request, and override the Source headers in case of conflict.
The HTTP Tool allows you to specify headers in two different ways:
- Static headers can be specified using the `headers` field, and will be the same for every invocation:
- Static headers can be specified using the `headers` field, and will be the
same for every invocation:
```yaml
my-http-tool:
@@ -78,7 +88,9 @@ my-http-tool:
Content-Type: application/json
```
- Dynamic headers can be specified as parameters in the `headerParams` field. The `name` of the `headerParams` will be used as the header key, and the value is determined by the LLM input upon Tool invocation:
- Dynamic headers can be specified as parameters in the `headerParams` field.
The `name` of the `headerParams` will be used as the header key, and the value
is determined by the LLM input upon Tool invocation:
```yaml
my-http-tool:
@@ -95,9 +107,12 @@ my-http-tool:
### Query parameters
Query parameters are key-value pairs appended to a URL after a question mark (?) to provide additional information to the server for processing the request, like filtering or sorting data.
Query parameters are key-value pairs appended to a URL after a question mark (?)
to provide additional information to the server for processing the request, like
filtering or sorting data.
- Static request query parameters should be specified in the `path` as part of the URL itself:
- Static request query parameters should be specified in the `path` as part of
the URL itself:
```yaml
my-http-tool:
@@ -108,7 +123,8 @@ my-http-tool:
description: Tool to search for item with ID 1 in English
```
- Dynamic request query parameters should be specified as parameters in the `queryParams` section:
- Dynamic request query parameters should be specified as parameters in the
`queryParams` section:
```yaml
my-http-tool:
@@ -125,8 +141,11 @@ my-http-tool:
### Request body
The request body payload is a string that supports parameter replacement following [Go template][go-template-doc]'s annotations.
The parameter names in the `requestBody` should be preceded by "." and enclosed by double curly brackets "{{}}". The values will be populated into the request body payload upon Tool invocation.
The request body payload is a string that supports parameter replacement
following [Go template][go-template-doc]'s annotations.
The parameter names in the `requestBody` should be preceded by "." and enclosed
by double curly brackets "{{}}". The values will be populated into the request
body payload upon Tool invocation.
Example:
@@ -153,14 +172,17 @@ my-http-tool:
#### Formatting Parameters
Some complex parameters (such as arrays) may require additional formatting to match the expected output. For convenience, you can specify one of the following pre-defined functions before the parameter name to format it:
Some complex parameters (such as arrays) may require additional formatting to
match the expected output. For convenience, you can specify one of the following
pre-defined functions before the parameter name to format it:
##### JSON
The `json` keyword converts a parameter into a JSON format.
{{< notice note >}}
Using JSON may add quotes to the variable name for certain types (such as strings).
Using JSON may add quotes to the variable name for certain types (such as
strings).
{{< /notice >}}
Example:

View File

@@ -0,0 +1,7 @@
---
title: "SQL Server"
type: docs
weight: 1
description: >
Tools that work with SQL Server Sources, such as CloudSQL for SQL Server.
---

View File

@@ -5,6 +5,8 @@ weight: 1
description: >
A "mssql-execute-sql" tool executes a SQL statement against a SQL Server
database.
aliases:
- /resources/tools/mssql-execute-sql
---
## About
@@ -30,6 +32,7 @@ tools:
source: my-mssql-instance
description: Use this tool to execute sql statement.
```
## Reference
| **field** | **type** | **required** | **description** |

View File

@@ -5,6 +5,8 @@ weight: 1
description: >
A "mssql-sql" tool executes a pre-defined SQL statement against a SQL Server
database.
aliases:
- /resources/tools/mssql-sql
---
## About
@@ -106,4 +108,4 @@ tools:
| description | string | true | Description of the tool that is passed to the LLM. |
| statement | string | true | SQL statement to execute. |
| parameters | [parameters](_index#specifying-parameters) | false | List of [parameters](_index#specifying-parameters) that will be inserted into the SQL statement. |
| templateParameters | [templateParameters](_index#template-parameters) | false | List of [templateParameters](_index#template-parameters) that will be inserted into the SQL statement before executing prepared statement. |
| templateParameters | [templateParameters](_index#template-parameters) | false | List of [templateParameters](_index#template-parameters) that will be inserted into the SQL statement before executing prepared statement. |

View File

@@ -0,0 +1,7 @@
---
title: "MySQL"
type: docs
weight: 1
description: >
Tools that work with MySQL Sources, such as Cloud SQL for MySQL.
---

View File

@@ -5,6 +5,8 @@ weight: 1
description: >
A "mysql-execute-sql" tool executes a SQL statement against a MySQL
database.
aliases:
- /resources/tools/mysql-execute-sql
---
## About
@@ -35,6 +37,6 @@ tools:
| **field** | **type** | **required** | **description** |
|-------------|:------------------------------------------:|:------------:|--------------------------------------------------------------------------------------------------|
| kind | string | true | Must be "mysql-execute-sql". |
| kind | string | true | Must be "mysql-execute-sql". |
| 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

@@ -5,6 +5,8 @@ weight: 1
description: >
A "mysql-sql" tool executes a pre-defined SQL statement against a MySQL
database.
aliases:
- /resources/tools/mysql-sql
---
## About
@@ -101,4 +103,4 @@ tools:
| description | string | true | Description of the tool that is passed to the LLM. |
| statement | string | true | SQL statement to execute on. |
| parameters | [parameters](_index#specifying-parameters) | false | List of [parameters](_index#specifying-parameters) that will be inserted into the SQL statement. |
| templateParameters | [templateParameters](_index#template-parameters) | false | List of [templateParameters](_index#template-parameters) that will be inserted into the SQL statement before executing prepared statement. |
| templateParameters | [templateParameters](_index#template-parameters) | false | List of [templateParameters](_index#template-parameters) that will be inserted into the SQL statement before executing prepared statement. |

View File

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

View File

@@ -5,6 +5,8 @@ weight: 1
description: >
A "neo4j-cypher" tool executes a pre-defined cypher statement against a Neo4j
database.
aliases:
- /resources/tools/neo4j-cypher
---
## About

View File

@@ -0,0 +1,7 @@
---
title: "Postgres"
type: docs
weight: 1
description: >
Tools that work with Postgres Sources, such as Cloud SQL for Postgres and AlloyDB.
---

View File

@@ -5,6 +5,8 @@ weight: 1
description: >
A "postgres-execute-sql" tool executes a SQL statement against a Postgres
database.
aliases:
- /resources/tools/postgres-execute-sql
---
## About
@@ -36,6 +38,6 @@ tools:
| **field** | **type** | **required** | **description** |
|-------------|:------------------------------------------:|:------------:|--------------------------------------------------------------------------------------------------|
| kind | string | true | Must be "postgres-execute-sql". |
| kind | string | true | Must be "postgres-execute-sql". |
| 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

@@ -5,6 +5,8 @@ weight: 1
description: >
A "postgres-sql" tool executes a pre-defined SQL statement against a Postgres
database.
aliases:
- /resources/tools/postgres-sql
---
## About

View File

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

View File

@@ -4,7 +4,8 @@ type: docs
weight: 1
description: >
A "redis" tool executes a set of pre-defined Redis commands against a Redis instance.
aliases:
- /resources/tools/redis
---
## About
@@ -13,8 +14,8 @@ A redis tool executes a series of pre-defined Redis commands against a
Redis source.
The specified Redis commands are executed sequentially. Each command is
represented as a string list, where the first element is the command name (e.g., SET,
GET, HGETALL) and subsequent elements are its arguments.
represented as a string list, where the first element is the command name (e.g.,
SET, GET, HGETALL) and subsequent elements are its arguments.
### Dynamic Command Parameters

View File

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

View File

@@ -5,6 +5,8 @@ weight: 1
description: >
A "spanner-execute-sql" tool executes a SQL statement against a Spanner
database.
aliases:
- /resources/tools/spanner-execute-sql
---
## About
@@ -34,7 +36,7 @@ tools:
| **field** | **type** | **required** | **description** |
|-------------|:------------------------------------------:|:------------:|--------------------------------------------------------------------------------------------------|
| kind | string | true | Must be "spanner-execute-sql". |
| kind | string | true | Must be "spanner-execute-sql". |
| 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. |
| readOnly | bool | false | When set to `true`, the `statement` is run as a read-only transaction. Default: `false`. |

View File

@@ -5,6 +5,8 @@ weight: 1
description: >
A "spanner-sql" tool executes a pre-defined SQL statement against a Google
Cloud Spanner database.
aliases:
- /resources/tools/spanner-sql
---
## About
@@ -162,4 +164,4 @@ tools:
| statement | string | true | SQL statement to execute on. |
| parameters | [parameters](_index#specifying-parameters) | false | List of [parameters](_index#specifying-parameters) that will be inserted into the SQL statement. |
| readOnly | bool | false | When set to `true`, the `statement` is run as a read-only transaction. Default: `false`. |
| templateParameters | [templateParameters](_index#template-parameters) | false | List of [templateParameters](_index#template-parameters) that will be inserted into the SQL statement before executing prepared statement. |
| templateParameters | [templateParameters](_index#template-parameters) | false | List of [templateParameters](_index#template-parameters) that will be inserted into the SQL statement before executing prepared statement. |

View File

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

View File

@@ -4,6 +4,8 @@ type: docs
weight: 1
description: >
Execute SQL statements against a SQLite database.
aliases:
- /resources/tools/sqlite-sql
---
## About
@@ -70,14 +72,13 @@ tools:
description: Table to select from
```
## Reference
| **field** | **type** | **required** | **description** |
|--------------------|:------------------------------------------------:|:------------:|--------------------------------------------------------------------------------------------------------------------------------------------|
| kind | string | true | Must be "sqlite-sql". |
| kind | string | true | Must be "sqlite-sql". |
| source | string | true | Name of the source the SQLite source configuration. |
| description | string | true | Description of the tool that is passed to the LLM. |
| statement | string | true | The SQL statement to execute. |
| statement | string | true | The SQL statement to execute. |
| parameters | [parameters](_index#specifying-parameters) | false | List of [parameters](_index#specifying-parameters) that will be inserted into the SQL statement. |
| templateParameters | [templateParameters](_index#template-parameters) | false | List of [templateParameters](_index#template-parameters) that will be inserted into the SQL statement before executing prepared statement. |
| templateParameters | [templateParameters](_index#template-parameters) | false | List of [templateParameters](_index#template-parameters) that will be inserted into the SQL statement before executing prepared statement. |

View File

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

View File

@@ -4,6 +4,8 @@ type: docs
weight: 1
description: >
A "valkey" tool executes a set of pre-defined Valkey commands against a Memorystore for Valkey instance.
aliases:
- /resources/tools/valkey
---
## About
@@ -12,8 +14,8 @@ A valkey tool executes a series of pre-defined Valkey commands against a
Memorystore for Valkey instance.
The specified Valkey commands are executed sequentially. Each command is
represented as a string array, where the first element is the command name (e.g., SET,
GET, HGETALL) and subsequent elements are its arguments.
represented as a string array, where the first element is the command name
(e.g., SET, GET, HGETALL) and subsequent elements are its arguments.
### Dynamic Command Parameters

View File

@@ -220,7 +220,7 @@
},
"outputs": [],
"source": [
"version = \"0.7.0\" # x-release-please-version\n",
"version = \"0.8.0\" # x-release-please-version\n",
"! curl -O https://storage.googleapis.com/genai-toolbox/v{version}/linux/amd64/toolbox\n",
"\n",
"# Make the binary executable\n",

View File

@@ -7,49 +7,69 @@ description: >
LangGraph, LlamaIndex, or ADK.
---
[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/googleapis/genai-toolbox/blob/main/docs/en/samples/bigquery/colab_quickstart_bigquery.ipynb)
[![Open In
Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/googleapis/genai-toolbox/blob/main/docs/en/samples/bigquery/colab_quickstart_bigquery.ipynb)
## Before you begin
This guide assumes you have already done the following:
1. Installed [Python 3.9+][install-python] (including [pip][install-pip] and
your preferred virtual environment tool for managing dependencies e.g. [venv][install-venv]).
1. Installed and configured the [Google Cloud SDK (gcloud CLI)][install-gcloud].
1. Authenticated with Google Cloud for Application Default Credentials (ADC):
1. Installed [Python 3.9+][install-python] (including [pip][install-pip] and
your preferred virtual environment tool for managing dependencies e.g.
[venv][install-venv]).
1. Installed and configured the [Google Cloud SDK (gcloud CLI)][install-gcloud].
1. Authenticated with Google Cloud for Application Default Credentials (ADC):
```bash
gcloud auth login --update-adc
```
1. Set your default Google Cloud project (replace `YOUR_PROJECT_ID` with your actual project ID):
1. Set your default Google Cloud project (replace `YOUR_PROJECT_ID` with your
actual project ID):
```bash
gcloud config set project YOUR_PROJECT_ID
export GOOGLE_CLOUD_PROJECT=YOUR_PROJECT_ID
```
Toolbox and the client libraries will use this project for BigQuery, unless overridden in configurations.
1. [Enabled the BigQuery API][enable-bq-api] in your Google Cloud project.
1. Installed the BigQuery client library for Python:
Toolbox and the client libraries will use this project for BigQuery, unless
overridden in configurations.
1. [Enabled the BigQuery API][enable-bq-api] in your Google Cloud project.
1. Installed the BigQuery client library for Python:
```bash
pip install google-cloud-bigquery
```
1. Completed setup for usage with an LLM model such as
{{< tabpane text=true persist=header >}}
{{% tab header="Core" lang="en" %}}
- [langchain-vertexai](https://python.langchain.com/docs/integrations/llms/google_vertex_ai_palm/#setup) package.
- [langchain-google-genai](https://python.langchain.com/docs/integrations/chat/google_generative_ai/#setup) package.
- [langchain-vertexai](https://python.langchain.com/docs/integrations/llms/google_vertex_ai_palm/#setup)
package.
- [langchain-anthropic](https://python.langchain.com/docs/integrations/chat/anthropic/#setup) package.
- [langchain-google-genai](https://python.langchain.com/docs/integrations/chat/google_generative_ai/#setup)
package.
- [langchain-anthropic](https://python.langchain.com/docs/integrations/chat/anthropic/#setup)
package.
{{% /tab %}}
{{% tab header="LangChain" lang="en" %}}
- [langchain-vertexai](https://python.langchain.com/docs/integrations/llms/google_vertex_ai_palm/#setup) package.
- [langchain-vertexai](https://python.langchain.com/docs/integrations/llms/google_vertex_ai_palm/#setup)
package.
- [langchain-google-genai](https://python.langchain.com/docs/integrations/chat/google_generative_ai/#setup) package.
- [langchain-google-genai](https://python.langchain.com/docs/integrations/chat/google_generative_ai/#setup)
package.
- [langchain-anthropic](https://python.langchain.com/docs/integrations/chat/anthropic/#setup) package.
- [langchain-anthropic](https://python.langchain.com/docs/integrations/chat/anthropic/#setup)
package.
{{% /tab %}}
{{% tab header="LlamaIndex" lang="en" %}}
- [llama-index-llms-google-genai](https://pypi.org/project/llama-index-llms-google-genai/) package.
- [llama-index-llms-google-genai](https://pypi.org/project/llama-index-llms-google-genai/)
package.
- [llama-index-llms-anthropic](https://docs.llamaindex.ai/en/stable/examples/llm/anthropic) package.
- [llama-index-llms-anthropic](https://docs.llamaindex.ai/en/stable/examples/llm/anthropic)
package.
{{% /tab %}}
{{% tab header="ADK" lang="en" %}}
- [google-adk](https://pypi.org/project/google-adk/) package.
@@ -58,15 +78,21 @@ This guide assumes you have already done the following:
[install-python]: https://wiki.python.org/moin/BeginnersGuide/Download
[install-pip]: https://pip.pypa.io/en/stable/installation/
[install-venv]: https://packaging.python.org/en/latest/tutorials/installing-packages/#creating-virtual-environments
[install-venv]:
https://packaging.python.org/en/latest/tutorials/installing-packages/#creating-virtual-environments
[install-gcloud]: https://cloud.google.com/sdk/docs/install
[enable-bq-api]: https://cloud.google.com/bigquery/docs/quickstarts/query-public-dataset-console#before-you-begin
[enable-bq-api]:
https://cloud.google.com/bigquery/docs/quickstarts/query-public-dataset-console#before-you-begin
## Step 1: Set up your BigQuery Dataset and Table
In this section, we will create a BigQuery dataset and a table, then insert some data that needs to be accessed by our agent. BigQuery operations are performed against your configured Google Cloud project.
In this section, we will create a BigQuery dataset and a table, then insert some
data that needs to be accessed by our agent. BigQuery operations are performed
against your configured Google Cloud project.
1. Create a new BigQuery dataset (replace `YOUR_DATASET_NAME` with your desired dataset name, e.g., `toolbox_ds`, and optionally specify a location like `US` or `EU`):
1. Create a new BigQuery dataset (replace `YOUR_DATASET_NAME` with your desired
dataset name, e.g., `toolbox_ds`, and optionally specify a location like `US`
or `EU`):
```bash
export BQ_DATASET_NAME="YOUR_DATASET_NAME" # e.g., toolbox_ds
@@ -74,13 +100,20 @@ In this section, we will create a BigQuery dataset and a table, then insert some
bq --location=$BQ_LOCATION mk $BQ_DATASET_NAME
```
You can also do this through the [Google Cloud Console](https://console.cloud.google.com/bigquery).
You can also do this through the [Google Cloud
Console](https://console.cloud.google.com/bigquery).
{{< notice tip >}}
For a real application, ensure that the service account or user running Toolbox has the necessary IAM permissions (e.g., BigQuery Data Editor, BigQuery User) on the dataset or project. For this local quickstart with user credentials, your own permissions will apply.
For a real application, ensure that the service account or user running Toolbox
has the necessary IAM permissions (e.g., BigQuery Data Editor, BigQuery User)
on the dataset or project. For this local quickstart with user credentials,
your own permissions will apply.
{{< /notice >}}
1. The hotels table needs to be defined in your new dataset for use with the bq query command. First, create a file named `create_hotels_table.sql` with the following content:
1. The hotels table needs to be defined in your new dataset for use with the bq
query command. First, create a file named `create_hotels_table.sql` with the
following content:
```sql
CREATE TABLE IF NOT EXISTS `YOUR_PROJECT_ID.YOUR_DATASET_NAME.hotels` (
@@ -93,15 +126,19 @@ In this section, we will create a BigQuery dataset and a table, then insert some
booked BOOLEAN NOT NULL
);
```
> **Note:** Replace `YOUR_PROJECT_ID` and `YOUR_DATASET_NAME` in the SQL with your actual project ID and dataset name.
> **Note:** Replace `YOUR_PROJECT_ID` and `YOUR_DATASET_NAME` in the SQL
> with your actual project ID and dataset name.
Then run the command below to execute the sql query:
```bash
bq query --project_id=$GOOGLE_CLOUD_PROJECT --dataset_id=$BQ_DATASET_NAME --use_legacy_sql=false < create_hotels_table.sql
```
1. Next, populate the hotels table with some initial data. To do this, create a file named `insert_hotels_data.sql` and add the following SQL INSERT statement to it.
1. Next, populate the hotels table with some initial data. To do this, create a
file named `insert_hotels_data.sql` and add the following SQL INSERT
statement to it.
```sql
INSERT INTO `YOUR_PROJECT_ID.YOUR_DATASET_NAME.hotels` (id, name, location, price_tier, checkin_date, checkout_date, booked)
@@ -117,18 +154,22 @@ In this section, we will create a BigQuery dataset and a table, then insert some
(9, 'Courtyard Zurich', 'Zurich', 'Upscale', '2024-04-03', '2024-04-13', FALSE),
(10, 'Comfort Inn Bern', 'Bern', 'Midscale', '2024-04-04', '2024-04-16', FALSE);
```
> **Note:** Replace `YOUR_PROJECT_ID` and `YOUR_DATASET_NAME` in the SQL with your actual project ID and dataset name.
> **Note:** Replace `YOUR_PROJECT_ID` and `YOUR_DATASET_NAME` in the SQL
> with your actual project ID and dataset name.
Then run the command below to execute the sql query:
```bash
bq query --project_id=$GOOGLE_CLOUD_PROJECT --dataset_id=$BQ_DATASET_NAME --use_legacy_sql=false < insert_hotels_data.sql
```
## Step 2: Install and configure Toolbox
In this section, we will download Toolbox, configure our tools in a `tools.yaml` to use BigQuery, and then run the Toolbox server.
In this section, we will download Toolbox, configure our tools in a `tools.yaml`
to use BigQuery, and then run the Toolbox server.
1. Download the latest version of Toolbox as a binary:
1. Download the latest version of Toolbox as a binary:
{{< notice tip >}}
Select the
@@ -138,20 +179,25 @@ In this section, we will download Toolbox, configure our tools in a `tools.yaml`
<!-- {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.7.0/$OS/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.8.0/$OS/toolbox
```
<!-- {x-release-please-end} -->
1. Make the binary executable:
1. Make the binary executable:
```bash
chmod +x toolbox
```
1. Write the following into a `tools.yaml` file. You must replace the `YOUR_PROJECT_ID` and `YOUR_DATASET_NAME` placeholder in the config with your actual BigQuery project and dataset name. The `location` field is optional; if not specified, it defaults to 'us'. The table name `hotels` is used directly in the statements.
1. Write the following into a `tools.yaml` file. You must replace the
`YOUR_PROJECT_ID` and `YOUR_DATASET_NAME` placeholder in the config with your
actual BigQuery project and dataset name. The `location` field is optional;
if not specified, it defaults to 'us'. The table name `hotels` is used
directly in the statements.
{{< notice tip >}}
Authentication with BigQuery is handled via Application Default Credentials (ADC). Ensure you have run `gcloud auth application-default login`.
Authentication with BigQuery is handled via Application Default Credentials
(ADC). Ensure you have run `gcloud auth application-default login`.
{{< /notice >}}
```yaml
@@ -217,7 +263,12 @@ In this section, we will download Toolbox, configure our tools in a `tools.yaml`
statement: UPDATE `YOUR_DATASET_NAME.hotels` SET booked = FALSE WHERE id = @hotel_id;
```
**Important Note on `toolsets`**: The `tools.yaml` content above does not include a `toolsets` section. The Python agent examples in Step 3 (e.g., `await toolbox_client.load_toolset("my-toolset")`) rely on a toolset named `my-toolset`. To make those examples work, you will need to add a `toolsets` section to your `tools.yaml` file, for example:
**Important Note on `toolsets`**: The `tools.yaml` content above does not
include a `toolsets` section. The Python agent examples in Step 3 (e.g.,
`await toolbox_client.load_toolset("my-toolset")`) rely on a toolset named
`my-toolset`. To make those examples work, you will need to add a `toolsets`
section to your `tools.yaml` file, for example:
```yaml
# Add this to your tools.yaml if using load_toolset("my-toolset")
# Ensure it's at the same indentation level as 'sources:' and 'tools:'
@@ -229,11 +280,14 @@ In this section, we will download Toolbox, configure our tools in a `tools.yaml`
- update-hotel
- cancel-hotel
```
Alternatively, you can modify the agent code to load tools individually (e.g., using `await toolbox_client.load_tool("search-hotels-by-name")`).
For more info on tools, check out the [Resources](../../resources/) section of the docs.
Alternatively, you can modify the agent code to load tools individually
(e.g., using `await toolbox_client.load_tool("search-hotels-by-name")`).
1. Run the Toolbox server, pointing to the `tools.yaml` file created earlier:
For more info on tools, check out the [Resources](../../resources/) section
of the docs.
1. Run the Toolbox server, pointing to the `tools.yaml` file created earlier:
```bash
./toolbox --tools-file "tools.yaml"
@@ -244,14 +298,13 @@ In this section, we will download Toolbox, configure our tools in a `tools.yaml`
In this section, we will write and run an agent that will load the Tools
from Toolbox.
{{< notice tip>}} If you prefer to experiment within a Google Colab environment,
you can connect to a
[local runtime](https://research.google.com/colaboratory/local-runtimes.html).
{{< notice tip>}} If you prefer to experiment within a Google Colab environment,
you can connect to a
[local runtime](https://research.google.com/colaboratory/local-runtimes.html).
{{< /notice >}}
1. In a new terminal, install the SDK package.
{{< tabpane persist=header >}}
{{< tab header="Core" lang="bash" >}}
@@ -278,28 +331,39 @@ pip install google-adk
{{< tab header="Core" lang="bash" >}}
# TODO(developer): replace with correct package if needed
pip install langgraph langchain-google-vertexai
# pip install langchain-google-genai
# pip install langchain-anthropic
{{< /tab >}}
{{< tab header="Langchain" lang="bash" >}}
# TODO(developer): replace with correct package if needed
pip install langgraph langchain-google-vertexai
# pip install langchain-google-genai
# pip install langchain-anthropic
{{< /tab >}}
{{< tab header="LlamaIndex" lang="bash" >}}
# TODO(developer): replace with correct package if needed
pip install llama-index-llms-google-genai
# pip install llama-index-llms-anthropic
{{< /tab >}}
{{< tab header="ADK" lang="bash" >}}
pip install toolbox-core
{{< /tab >}}
{{< /tabpane >}}
1. Create a new file named `hotel_agent.py` and copy the following
code to create an agent:
{{< tabpane persist=header >}}
@@ -337,7 +401,7 @@ queries = [
]
async def run_application():
async with ToolboxClient("http://127.0.0.1:5000") as toolbox_client:
async with ToolboxClient("<http://127.0.0.1:5000>") as toolbox_client:
# The toolbox_tools list contains Python callables (functions/methods) designed for LLM tool-use
# integration. While this example uses Google's genai client, these callables can be adapted for
@@ -418,20 +482,25 @@ asyncio.run(run_application())
import asyncio
from langgraph.prebuilt import create_react_agent
# TODO(developer): replace this with another import if needed
from langchain_google_vertexai import ChatVertexAI
# from langchain_google_genai import ChatGoogleGenerativeAI
# from langchain_anthropic import ChatAnthropic
from langgraph.checkpoint.memory import MemorySaver
from toolbox_langchain import ToolboxClient
prompt = """
You're a helpful hotel assistant. You handle hotel searching, booking and
cancellations. When the user searches for a hotel, mention it's name, id,
location and price tier. Always mention hotel ids while performing any
searches. This is very important for any operations. For any bookings or
cancellations, please provide the appropriate confirmation. Be sure to
cancellations. When the user searches for a hotel, mention it's name, id,
location and price tier. Always mention hotel ids while performing any
searches. This is very important for any operations. For any bookings or
cancellations, please provide the appropriate confirmation. Be sure to
update checkin or checkout dates if mentioned by the user.
Don't ask for confirmations from the user.
"""
@@ -448,7 +517,7 @@ async def main():
model = ChatVertexAI(model_name="gemini-2.0-flash-001")
# model = ChatGoogleGenerativeAI(model="gemini-2.0-flash-001")
# model = ChatAnthropic(model="claude-3-5-sonnet-20240620")
# Load the tools from the Toolbox server
client = ToolboxClient("http://127.0.0.1:5000")
tools = await client.aload_toolset()
@@ -471,18 +540,20 @@ from llama_index.core.agent.workflow import AgentWorkflow
from llama_index.core.workflow import Context
# TODO(developer): replace this with another import if needed
# TODO(developer): replace this with another import if needed
from llama_index.llms.google_genai import GoogleGenAI
# from llama_index.llms.anthropic import Anthropic
from toolbox_llamaindex import ToolboxClient
prompt = """
You're a helpful hotel assistant. You handle hotel searching, booking and
cancellations. When the user searches for a hotel, mention it's name, id,
location and price tier. Always mention hotel ids while performing any
searches. This is very important for any operations. For any bookings or
cancellations, please provide the appropriate confirmation. Be sure to
cancellations. When the user searches for a hotel, mention it's name, id,
location and price tier. Always mention hotel ids while performing any
searches. This is very important for any operations. For any bookings or
cancellations, please provide the appropriate confirmation. Be sure to
update checkin or checkout dates if mentioned by the user.
Don't ask for confirmations from the user.
"""
@@ -508,7 +579,7 @@ async def main():
# model="claude-3-7-sonnet-latest",
# api_key=os.getenv("ANTHROPIC_API_KEY")
# )
# Load the tools from the Toolbox server
client = ToolboxClient("http://127.0.0.1:5000")
tools = await client.aload_toolset()
@@ -536,14 +607,20 @@ from toolbox_core import ToolboxSyncClient
import os
os.environ['GOOGLE_GENAI_USE_VERTEXAI'] = 'True'
# TODO(developer): Replace 'YOUR_PROJECT_ID' with your Google Cloud Project ID.
# TODO(developer): Replace 'YOUR_PROJECT_ID' with your Google Cloud Project ID
os.environ['GOOGLE_CLOUD_PROJECT'] = 'YOUR_PROJECT_ID'
# TODO(developer): Replace 'us-central1' with your Google Cloud Location (region).
# TODO(developer): Replace 'us-central1' with your Google Cloud Location (region)
os.environ['GOOGLE_CLOUD_LOCATION'] = 'us-central1'
# --- Load Tools from Toolbox ---
# TODO(developer): Ensure the Toolbox server is running at http://127.0.0.1:5000
with ToolboxSyncClient("http://127.0.0.1:5000") as toolbox_client:
# TODO(developer): Ensure the Toolbox server is running at <http://127.0.0.1:5000>
with ToolboxSyncClient("<http://127.0.0.1:5000>") as toolbox_client:
# TODO(developer): Replace "my-toolset" with the actual ID of your toolset as configured in your MCP Toolbox server.
agent_toolset = toolbox_client.load_toolset("my-toolset")
@@ -607,21 +684,27 @@ with ToolboxSyncClient("http://127.0.0.1:5000") as toolbox_client:
print(text)
{{< /tab >}}
{{< /tabpane >}}
{{< tabpane text=true persist=header >}}
{{% tab header="Core" lang="en" %}}
To learn more about the Core SDK, check out the [Toolbox Core SDK documentation.](https://github.com/googleapis/genai-toolbox/tree/main/sdks/toolbox-core)
To learn more about the Core SDK, check out the [Toolbox Core SDK
documentation.](https://github.com/googleapis/genai-toolbox/tree/main/sdks/toolbox-core)
{{% /tab %}}
{{% tab header="Langchain" lang="en" %}}
To learn more about Agents in LangChain, check out the [LangGraph Agent documentation.](https://langchain-ai.github.io/langgraph/reference/prebuilt/#langgraph.prebuilt.chat_agent_executor.create_react_agent)
To learn more about Agents in LangChain, check out the [LangGraph Agent
documentation.](https://langchain-ai.github.io/langgraph/reference/prebuilt/#langgraph.prebuilt.chat_agent_executor.create_react_agent)
{{% /tab %}}
{{% tab header="LlamaIndex" lang="en" %}}
To learn more about Agents in LlamaIndex, check out the [LlamaIndex AgentWorkflow documentation.](https://docs.llamaindex.ai/en/stable/examples/agent/agent_workflow_basic/)
To learn more about Agents in LlamaIndex, check out the [LlamaIndex
AgentWorkflow
documentation.](https://docs.llamaindex.ai/en/stable/examples/agent/agent_workflow_basic/)
{{% /tab %}}
{{% tab header="ADK" lang="en" %}}
To learn more about Agents in ADK, check out the [ADK documentation.](https://google.github.io/adk-docs/)
To learn more about Agents in ADK, check out the [ADK
documentation.](https://google.github.io/adk-docs/)
{{% /tab %}}
{{< /tabpane >}}
1. Run your agent, and observe the results:
```sh

View File

@@ -14,9 +14,12 @@ on how to [connect to Toolbox via MCP](../../how-to/connect_via_mcp.md).
## Step 1: Set up your BigQuery Dataset and Table
In this section, we will create a BigQuery dataset and a table, then insert some data that needs to be accessed by our agent.
In this section, we will create a BigQuery dataset and a table, then insert some
data that needs to be accessed by our agent.
1. Create a new BigQuery dataset (replace `YOUR_DATASET_NAME` with your desired dataset name, e.g., `toolbox_mcp_ds`, and optionally specify a location like `US` or `EU`):
1. Create a new BigQuery dataset (replace `YOUR_DATASET_NAME` with your desired
dataset name, e.g., `toolbox_mcp_ds`, and optionally specify a location like
`US` or `EU`):
```bash
export BQ_DATASET_NAME="YOUR_DATASET_NAME"
@@ -24,10 +27,12 @@ In this section, we will create a BigQuery dataset and a table, then insert some
bq --location=$BQ_LOCATION mk $BQ_DATASET_NAME
```
You can also do this through the [Google Cloud Console](https://console.cloud.google.com/bigquery).
1. The `hotels` table needs to be defined in your new dataset. First, create a file named `create_hotels_table.sql` with the following content:
You can also do this through the [Google Cloud
Console](https://console.cloud.google.com/bigquery).
1. The `hotels` table needs to be defined in your new dataset. First, create a
file named `create_hotels_table.sql` with the following content:
```sql
CREATE TABLE IF NOT EXISTS `YOUR_PROJECT_ID.YOUR_DATASET_NAME.hotels` (
@@ -40,14 +45,19 @@ In this section, we will create a BigQuery dataset and a table, then insert some
booked BOOLEAN NOT NULL
);
```
> **Note:** Replace `YOUR_PROJECT_ID` and `YOUR_DATASET_NAME` in the SQL with your actual project ID and dataset name.
> **Note:** Replace `YOUR_PROJECT_ID` and `YOUR_DATASET_NAME` in the SQL
> with your actual project ID and dataset name.
Then run the command below to execute the sql query:
```bash
bq query --project_id=$GOOGLE_CLOUD_PROJECT --dataset_id=$BQ_DATASET_NAME --use_legacy_sql=false < create_hotels_table.sql
```
1. . Next, populate the hotels table with some initial data. To do this, create a file named `insert_hotels_data.sql` and add the following SQL INSERT statement to it.
1. . Next, populate the hotels table with some initial data. To do this, create
a file named `insert_hotels_data.sql` and add the following SQL INSERT
statement to it.
```sql
INSERT INTO `YOUR_PROJECT_ID.YOUR_DATASET_NAME.hotels` (id, name, location, price_tier, checkin_date, checkout_date, booked)
@@ -63,16 +73,20 @@ In this section, we will create a BigQuery dataset and a table, then insert some
(9, 'Courtyard Zurich', 'Zurich', 'Upscale', '2024-04-03', '2024-04-13', FALSE),
(10, 'Comfort Inn Bern', 'Bern', 'Midscale', '2024-04-04', '2024-04-16', FALSE);
```
> **Note:** Replace `YOUR_PROJECT_ID` and `YOUR_DATASET_NAME` in the SQL with your actual project ID and dataset name.
> **Note:** Replace `YOUR_PROJECT_ID` and `YOUR_DATASET_NAME` in the SQL
> with your actual project ID and dataset name.
Then run the command below to execute the sql query:
```bash
bq query --project_id=$GOOGLE_CLOUD_PROJECT --dataset_id=$BQ_DATASET_NAME --use_legacy_sql=false < insert_hotels_data.sql
```
## Step 2: Install and configure Toolbox
In this section, we will download Toolbox, configure our tools in a `tools.yaml`, and then run the Toolbox server.
In this section, we will download Toolbox, configure our tools in a
`tools.yaml`, and then run the Toolbox server.
1. Download the latest version of Toolbox as a binary:
@@ -84,7 +98,7 @@ In this section, we will download Toolbox, configure our tools in a `tools.yaml`
<!-- {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.7.0/$OS/toolbox
curl -O https://storage.googleapis.com/genai-toolbox/v0.8.0/$OS/toolbox
```
<!-- {x-release-please-end} -->
@@ -94,10 +108,15 @@ In this section, we will download Toolbox, configure our tools in a `tools.yaml`
chmod +x toolbox
```
1. Write the following into a `tools.yaml` file. You must replace the `YOUR_PROJECT_ID` and `YOUR_DATASET_NAME` placeholder in the config with your actual BigQuery project and dataset name. The `location` field is optional; if not specified, it defaults to 'us'. The table name `hotels` is used directly in the statements.
1. Write the following into a `tools.yaml` file. You must replace the
`YOUR_PROJECT_ID` and `YOUR_DATASET_NAME` placeholder in the config with your
actual BigQuery project and dataset name. The `location` field is optional;
if not specified, it defaults to 'us'. The table name `hotels` is used
directly in the statements.
{{< notice tip >}}
Authentication with BigQuery is handled via Application Default Credentials (ADC). Ensure you have run `gcloud auth application-default login`.
Authentication with BigQuery is handled via Application Default Credentials
(ADC). Ensure you have run `gcloud auth application-default login`.
{{< /notice >}}
```yaml
@@ -170,8 +189,9 @@ In this section, we will download Toolbox, configure our tools in a `tools.yaml`
- cancel-hotel
```
For more info on tools, check out the [Tools](../../resources/tools/_index.md) section.
For more info on tools, check out the
[Tools](../../resources/tools/_index.md) section.
1. Run the Toolbox server, pointing to the `tools.yaml` file created earlier:
```bash

22
go.mod
View File

@@ -7,9 +7,9 @@ toolchain go1.24.4
require (
cloud.google.com/go/alloydbconn v1.15.3
cloud.google.com/go/bigquery v1.69.0
cloud.google.com/go/bigtable v1.37.0
cloud.google.com/go/bigtable v1.38.0
cloud.google.com/go/cloudsqlconn v1.17.2
cloud.google.com/go/spanner v1.82.0
cloud.google.com/go/spanner v1.83.0
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.0
@@ -17,18 +17,18 @@ require (
github.com/go-chi/chi/v5 v5.2.2
github.com/go-chi/httplog/v2 v2.1.1
github.com/go-chi/render v1.0.3
github.com/go-playground/validator/v10 v10.26.0
github.com/go-playground/validator/v10 v10.27.0
github.com/go-sql-driver/mysql v1.9.3
github.com/goccy/go-yaml v1.18.0
github.com/google/go-cmp v0.7.0
github.com/google/uuid v1.6.0
github.com/jackc/pgx/v5 v5.7.5
github.com/json-iterator/go v1.1.12
github.com/microsoft/go-mssqldb v1.8.2
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.10.0
github.com/redis/go-redis/v9 v9.11.0
github.com/spf13/cobra v1.9.1
github.com/valkey-io/valkey-go v1.0.61
github.com/valkey-io/valkey-go v1.0.62
go.opentelemetry.io/contrib/propagators/autoprop v0.61.0
go.opentelemetry.io/otel v1.36.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.36.0
@@ -38,7 +38,7 @@ require (
go.opentelemetry.io/otel/sdk/metric v1.36.0
go.opentelemetry.io/otel/trace v1.36.0
golang.org/x/oauth2 v0.30.0
google.golang.org/api v0.238.0
google.golang.org/api v0.239.0
modernc.org/sqlite v1.38.0
)
@@ -46,7 +46,7 @@ require golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
require (
cel.dev/expr v0.23.0 // indirect
cloud.google.com/go v0.121.0 // indirect
cloud.google.com/go v0.121.2 // indirect
cloud.google.com/go/alloydb v1.16.1 // indirect
cloud.google.com/go/auth v0.16.2 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
@@ -56,7 +56,7 @@ require (
cloud.google.com/go/monitoring v1.24.2 // indirect
cloud.google.com/go/trace v1.11.6 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.2 // indirect
github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.3 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect
github.com/ajg/form v1.5.1 // indirect
@@ -113,7 +113,7 @@ require (
github.com/zeebo/xxh3 v1.0.2 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.35.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
go.opentelemetry.io/contrib/propagators/aws v1.36.0 // indirect
@@ -134,7 +134,7 @@ require (
golang.org/x/tools v0.33.0 // indirect
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/grpc v1.73.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect

68
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.0 h1:pgfwva8nGw7vivjZiRfrmglGWiCJBP+0OmDpenG/Fwg=
cloud.google.com/go v0.121.0/go.mod h1:rS7Kytwheu/y9buoDmu5EIpMMCI4Mb8ND4aeN4Vwj7Q=
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/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=
@@ -139,8 +139,8 @@ cloud.google.com/go/bigquery v1.49.0/go.mod h1:Sv8hMmTFFYBlt/ftw2uN6dFdQPzBlREY9
cloud.google.com/go/bigquery v1.50.0/go.mod h1:YrleYEh2pSEbgTBZYMJ5SuSr0ML3ypjRB1zgf7pvQLU=
cloud.google.com/go/bigquery v1.69.0 h1:rZvHnjSUs5sHK3F9awiuFk2PeOaB8suqNuim21GbaTc=
cloud.google.com/go/bigquery v1.69.0/go.mod h1:TdGLquA3h/mGg+McX+GsqG9afAzTAcldMjqhdjHTLew=
cloud.google.com/go/bigtable v1.37.0 h1:Q+x7y04lQ0B+WXp03wc1/FLhFt4CwcQdkwWT0M4Jp3w=
cloud.google.com/go/bigtable v1.37.0/go.mod h1:HXqddP6hduwzrtiTCqZPpj9ij4hGZb4Zy1WF/dT+yaU=
cloud.google.com/go/bigtable v1.38.0 h1:L/PnUXRtAzFfa7qMULJHt4cXa/O2dqPJEkzYNGA4hfo=
cloud.google.com/go/bigtable v1.38.0/go.mod h1:o/lntJarF3Y5C0XYLMJLjLYwxaRbcrtM0BiV57ymXbI=
cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY=
cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s=
cloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI=
@@ -540,8 +540,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.82.0 h1:w9uO8RqEoBooBLX4nqV1RtgudyU2ZX780KTLRgeVg60=
cloud.google.com/go/spanner v1.82.0/go.mod h1:BzybQHFQ/NqGxvE/M+/iU29xgutJf7Q85/4U9RWMto0=
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/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=
@@ -633,22 +633,22 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8=
git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 h1:E+OJmp2tPvt1W+amx48v1eqbjDYsgN+RzP4q16yV5eM=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 h1:U2rTu3Ef+7w9FHKIAXM6ZyqF3UOWJZ12zIm8zECAFfg=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 h1:jBQA3cKT4L2rWMpgE7Yt3Hwh2aUj8KXjIGLxjHeYNNo=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0/go.mod h1:4OG6tQ9EOP/MT0NMjDlRzWoVFxfu9rN9B2X+tlSVktg=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1 h1:MyVTgWR8qd/Jw1Le0NZebGBUCLbtak3bJ3z1OlqZBpw=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1/go.mod h1:GpPjLhVR9dnUoJMyHWSPy71xY9/lcmpzIPZXmF0FCVY=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h1:bTSOgj05NGRuHHhQwAdPnYr9TOdNmKlZTgGLL6nyAdI=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1 h1:Wgf5rZba3YZqeTNJPtvqZoBu1sBN/L4sry+u2U3Y75w=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1/go.mod h1:xxCBG/f/4Vbmh2XQJBsOmNdxWUY5j/s27jujKPbQf14=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 h1:bFWuoEKg+gImo7pvkiQEFAc8ocibADgXeiLAxWhWmkI=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1/go.mod h1:Vih/3yc6yac2JzU4hzpaDupBJP0Flaia9rXXrU8xyww=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.2 h1:DBjmt6/otSdULyJdVg2BlG0qGZO5tKL4VzOs0jpvw5Q=
github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.2/go.mod h1:dppbR7CwXD4pgtV9t3wD1812RaLDcBjtblcDF5f1vI0=
github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.3 h1:2afWGsMzkIcN8Qm4mgPJKZWyroE5QBszMiDMYEBrnfw=
github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.3/go.mod h1:dppbR7CwXD4pgtV9t3wD1812RaLDcBjtblcDF5f1vI0=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 h1:ErKg/3iS1AKcTkf3yixlZ54f9U1rljCkQyEXWUnIUxc=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 h1:owcC2UnmsZycprQ5RfRgjydWhuoxg71LUfyiQdijZuM=
@@ -801,8 +801,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
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=
@@ -1014,8 +1014,8 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/microsoft/go-mssqldb v1.8.2 h1:236sewazvC8FvG6Dr3bszrVhMkAl4KYImryLkRMCd0I=
github.com/microsoft/go-mssqldb v1.8.2/go.mod h1:vp38dT33FGfVotRiTmDo3bFyaHq+p3LektQrjTULowo=
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/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY=
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=
@@ -1051,8 +1051,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.10.0 h1:FxwK3eV8p/CQa0Ch276C7u2d0eNC9kCmAYQ7mCXCzVs=
github.com/redis/go-redis/v9 v9.10.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
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/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=
@@ -1092,8 +1092,8 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/valkey-io/valkey-go v1.0.61 h1:uz7gxSs4dKqLfaa8xKFo8wHaCWYSCD3lMhVL0OJifZA=
github.com/valkey-io/valkey-go v1.0.61/go.mod h1:bHmwjIEOrGq/ubOJfh5uMRs7Xj6mV3mQ/ZXUbmqpjqY=
github.com/valkey-io/valkey-go v1.0.62 h1:oQdPlQGRyxcQWL8fnu6J3SCaQwayc/hRZifjJIaJqu0=
github.com/valkey-io/valkey-go v1.0.62/go.mod h1:bHmwjIEOrGq/ubOJfh5uMRs7Xj6mV3mQ/ZXUbmqpjqY=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -1120,8 +1120,8 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/detectors/gcp v1.35.0 h1:bGvFt68+KTiAKFlacHW6AhA56GF2rS0bdD3aJYEnmzA=
go.opentelemetry.io/contrib/detectors/gcp v1.35.0/go.mod h1:qGWP8/+ILwMRIUf9uIVLloR1uo5ZYAslM4O6OqUi1DA=
go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw=
go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
@@ -1605,8 +1605,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.238.0 h1:+EldkglWIg/pWjkq97sd+XxH7PxakNYoe/rkSTbnvOs=
google.golang.org/api v0.238.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50=
google.golang.org/api v0.239.0 h1:2hZKUnFZEy81eugPs4e2XzIJ5SOwQg0G82bpXD65Puo=
google.golang.org/api v0.239.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50=
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=
@@ -1749,8 +1749,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-20250505200425-f936aa4a68b2 h1:1tXaIXCracvtsRxSBsYDiSBN0cuJvM7QYW+MrpIRY78=
google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:49MsLSx0oWMOZqcpB3uL8ZOkAh1+TndpJ8ONoCBWiZk=
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a h1:SGktgSolFCo75dnHJF2yMvnns6jCmHFJ0vE4Vn2JKvQ=
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a/go.mod h1:a77HrdMjoeKbnd2jmgcWdaS++ZLZAEq3orIOAEIKiVw=
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/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=

View File

@@ -17,13 +17,13 @@ package server
import (
"encoding/json"
"fmt"
"io"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/render"
"github.com/googleapis/genai-toolbox/internal/tools"
"github.com/googleapis/genai-toolbox/internal/util"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/metric"
@@ -199,7 +199,7 @@ func toolInvokeHandler(s *Server, w http.ResponseWriter, r *http.Request) {
s.logger.DebugContext(ctx, "tool invocation authorized")
var data map[string]any
if err = decodeJSON(r.Body, &data); err != nil {
if err = util.DecodeJSON(r.Body, &data); err != nil {
render.Status(r, http.StatusBadRequest)
err = fmt.Errorf("request body was invalid JSON: %w", err)
s.logger.DebugContext(ctx, err.Error())
@@ -274,13 +274,3 @@ func (e *errResponse) Render(w http.ResponseWriter, r *http.Request) error {
render.Status(r, e.HTTPStatusCode)
return nil
}
// decodeJSON decodes a given reader into an interface using the json decoder.
func decodeJSON(r io.Reader, v interface{}) error {
defer io.Copy(io.Discard, r) //nolint:errcheck
d := json.NewDecoder(r)
// specify JSON numbers should get parsed to json.Number instead of float64 by default.
// This prevents loss between floats/ints.
d.UseNumber()
return d.Decode(v)
}

View File

@@ -86,7 +86,7 @@ func TestToolsetEndpoint(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
resp, body, err := runRequest(ts, http.MethodGet, fmt.Sprintf("/toolset/%s", tc.toolsetName), nil)
resp, body, err := runRequest(ts, http.MethodGet, fmt.Sprintf("/toolset/%s", tc.toolsetName), nil, nil)
if err != nil {
t.Fatalf("unexpected error during request: %s", err)
}
@@ -174,7 +174,7 @@ func TestToolGetEndpoint(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
resp, body, err := runRequest(ts, http.MethodGet, fmt.Sprintf("/tool/%s", tc.toolName), nil)
resp, body, err := runRequest(ts, http.MethodGet, fmt.Sprintf("/tool/%s", tc.toolName), nil, nil)
if err != nil {
t.Fatalf("unexpected error during request: %s", err)
}
@@ -251,7 +251,7 @@ func TestToolInvokeEndpoint(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
resp, body, err := runRequest(ts, http.MethodPost, fmt.Sprintf("/tool/%s/invoke", tc.toolName), tc.requestBody)
resp, body, err := runRequest(ts, http.MethodPost, fmt.Sprintf("/tool/%s/invoke", tc.toolName), tc.requestBody, nil)
if err != nil {
t.Fatalf("unexpected error during request: %s", err)
}

View File

@@ -21,7 +21,6 @@ import (
"net/http"
"net/http/httptest"
"os"
"sync"
"testing"
"github.com/go-chi/chi/v5"
@@ -153,10 +152,7 @@ func setUpServer(t *testing.T, router string, tools map[string]tools.Tool, tools
t.Fatalf("unable to create custom metrics: %s", err)
}
sseManager := &sseManager{
mu: sync.RWMutex{},
sseSessions: make(map[string]*sseSession),
}
sseManager := newSseManager(ctx)
server := Server{version: fakeVersionString, logger: testLogger, instrumentation: instrumentation, sseManager: sseManager, tools: tools, toolsets: toolsets}
var r chi.Router
@@ -197,12 +193,17 @@ func runServer(r chi.Router, tls bool) *httptest.Server {
return ts
}
func runRequest(ts *httptest.Server, method, path string, body io.Reader) (*http.Response, []byte, error) {
func runRequest(ts *httptest.Server, method, path string, body io.Reader, header map[string]string) (*http.Response, []byte, error) {
req, err := http.NewRequest(method, ts.URL+path, body)
if err != nil {
return nil, nil, fmt.Errorf("unable to create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
for k, v := range header {
req.Header.Set(k, v)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, nil, fmt.Errorf("unable to send request: %w", err)

View File

@@ -23,12 +23,17 @@ import (
"io"
"net/http"
"sync"
"time"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/render"
"github.com/google/uuid"
"github.com/googleapis/genai-toolbox/internal/server/mcp"
"github.com/googleapis/genai-toolbox/internal/server/mcp/jsonrpc"
mcputil "github.com/googleapis/genai-toolbox/internal/server/mcp/util"
v20241105 "github.com/googleapis/genai-toolbox/internal/server/mcp/v20241105"
v20250326 "github.com/googleapis/genai-toolbox/internal/server/mcp/v20250326"
"github.com/googleapis/genai-toolbox/internal/util"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
@@ -36,30 +41,41 @@ import (
)
type sseSession struct {
sessionId string
writer http.ResponseWriter
flusher http.Flusher
done chan struct{}
eventQueue chan string
lastActive time.Time
}
// sseManager manages and control access to sse sessions
type sseManager struct {
mu sync.RWMutex
mu sync.Mutex
sseSessions map[string]*sseSession
}
func (m *sseManager) get(id string) (*sseSession, bool) {
m.mu.RLock()
defer m.mu.RUnlock()
m.mu.Lock()
defer m.mu.Unlock()
session, ok := m.sseSessions[id]
session.lastActive = time.Now()
return session, ok
}
func newSseManager(ctx context.Context) *sseManager {
sseM := &sseManager{
mu: sync.Mutex{},
sseSessions: make(map[string]*sseSession),
}
go sseM.cleanupRoutine(ctx)
return sseM
}
func (m *sseManager) add(id string, session *sseSession) {
m.mu.Lock()
defer m.mu.Unlock()
m.sseSessions[id] = session
m.mu.Unlock()
session.lastActive = time.Now()
}
func (m *sseManager) remove(id string) {
@@ -68,10 +84,35 @@ func (m *sseManager) remove(id string) {
m.mu.Unlock()
}
func (m *sseManager) cleanupRoutine(ctx context.Context) {
timeout := 10 * time.Minute
ticker := time.NewTicker(timeout)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
func() {
m.mu.Lock()
defer m.mu.Unlock()
now := time.Now()
for id, sess := range m.sseSessions {
if now.Sub(sess.lastActive) > timeout {
delete(m.sseSessions, id)
}
}
}()
}
}
}
type stdioSession struct {
server *Server
reader *bufio.Reader
writer io.Writer
protocol string
server *Server
reader *bufio.Reader
writer io.Writer
}
func NewStdioSession(s *Server, stdin io.Reader, stdout io.Writer) *stdioSession {
@@ -100,13 +141,15 @@ func (s *stdioSession) readInputStream(ctx context.Context) error {
}
return err
}
res, err := processMcpMessage(ctx, []byte(line), s.server, "")
v, res, err := processMcpMessage(ctx, []byte(line), s.server, s.protocol, "")
if err != nil {
// errors during the processing of message will generate a valid MCP Error response.
// server can continue to run.
s.server.logger.ErrorContext(ctx, err.Error())
}
if v != "" {
s.protocol = v
}
// no responses for notifications
if res != nil {
if err = s.write(ctx, res); err != nil {
@@ -176,11 +219,15 @@ func mcpRouter(s *Server) (chi.Router, error) {
r.Use(render.SetContentType(render.ContentTypeJSON))
r.Get("/sse", func(w http.ResponseWriter, r *http.Request) { sseHandler(s, w, r) })
r.Get("/", func(w http.ResponseWriter, r *http.Request) { methodNotAllowed(s, w, r) })
r.Post("/", func(w http.ResponseWriter, r *http.Request) { httpHandler(s, w, r) })
r.Delete("/", func(w http.ResponseWriter, r *http.Request) {})
r.Route("/{toolsetName}", func(r chi.Router) {
r.Get("/sse", func(w http.ResponseWriter, r *http.Request) { sseHandler(s, w, r) })
r.Get("/", func(w http.ResponseWriter, r *http.Request) { methodNotAllowed(s, w, r) })
r.Post("/", func(w http.ResponseWriter, r *http.Request) { httpHandler(s, w, r) })
r.Delete("/", func(w http.ResponseWriter, r *http.Request) {})
})
return r, nil
@@ -228,7 +275,6 @@ func sseHandler(s *Server, w http.ResponseWriter, r *http.Request) {
_ = render.Render(w, r, newErrResponse(err, http.StatusInternalServerError))
}
session := &sseSession{
sessionId: sessionId,
writer: w,
flusher: flusher,
done: make(chan struct{}),
@@ -274,19 +320,47 @@ func sseHandler(s *Server, w http.ResponseWriter, r *http.Request) {
}
}
// methodNotAllowed handles all mcp messages.
func methodNotAllowed(s *Server, w http.ResponseWriter, r *http.Request) {
err := fmt.Errorf("toolbox does not support streaming in streamable HTTP transport")
s.logger.DebugContext(r.Context(), err.Error())
_ = render.Render(w, r, newErrResponse(err, http.StatusMethodNotAllowed))
}
// httpHandler handles all mcp messages.
func httpHandler(s *Server, w http.ResponseWriter, r *http.Request) {
ctx, span := s.instrumentation.Tracer.Start(r.Context(), "toolbox/server/mcp")
r = r.WithContext(ctx)
ctx = util.WithLogger(r.Context(), s.logger)
var sessionId, protocolVersion string
var session *sseSession
// check if client connects via sse
// v2024-11-05 supports http with sse
paramSessionId := r.URL.Query().Get("sessionId")
if paramSessionId != "" {
sessionId = paramSessionId
protocolVersion = v20241105.PROTOCOL_VERSION
var ok bool
session, ok = s.sseManager.get(sessionId)
if !ok {
s.logger.DebugContext(ctx, "sse session not available")
}
}
// check if client have `Mcp-Session-Id` header
// if `Mcp-Session-Id` header is set, we are using v2025-03-26 since
// previous version doesn't use this header.
headerSessionId := r.Header.Get("Mcp-Session-Id")
if headerSessionId != "" {
protocolVersion = v20250326.PROTOCOL_VERSION
}
toolsetName := chi.URLParam(r, "toolsetName")
s.logger.DebugContext(ctx, fmt.Sprintf("toolset name: %s", toolsetName))
span.SetAttributes(attribute.String("toolset_name", toolsetName))
// retrieve sse session id, if applicable
sessionId := r.URL.Query().Get("sessionId")
var err error
defer func() {
if err != nil {
@@ -312,10 +386,10 @@ func httpHandler(s *Server, w http.ResponseWriter, r *http.Request) {
// Generate a new uuid if unable to decode
id := uuid.New().String()
s.logger.DebugContext(ctx, err.Error())
render.JSON(w, r, newJSONRPCError(id, mcp.PARSE_ERROR, err.Error(), nil))
render.JSON(w, r, jsonrpc.NewError(id, jsonrpc.PARSE_ERROR, err.Error(), nil))
}
res, err := processMcpMessage(ctx, body, s, toolsetName)
v, res, err := processMcpMessage(ctx, body, s, protocolVersion, toolsetName)
// notifications will return empty string
if res == nil {
// Notifications do not expect a response
@@ -327,11 +401,13 @@ func httpHandler(s *Server, w http.ResponseWriter, r *http.Request) {
s.logger.DebugContext(ctx, err.Error())
}
// retrieve sse session
session, ok := s.sseManager.get(sessionId)
if !ok {
s.logger.DebugContext(ctx, "sse session not available")
} else {
// for v20250326, add the `Mcp-Session-Id` header
if v == v20250326.PROTOCOL_VERSION {
sessionId = uuid.New().String()
w.Header().Set("Mcp-Session-Id", sessionId)
}
if session != nil {
// queue sse event
eventData, _ := json.Marshal(res)
select {
@@ -349,141 +425,66 @@ func httpHandler(s *Server, w http.ResponseWriter, r *http.Request) {
}
// processMcpMessage process the messages received from clients
func processMcpMessage(ctx context.Context, body []byte, s *Server, toolsetName string) (any, error) {
func processMcpMessage(ctx context.Context, body []byte, s *Server, protocolVersion string, toolsetName string) (string, any, error) {
logger, err := util.LoggerFromContext(ctx)
if err != nil {
return newJSONRPCError("", mcp.INTERNAL_ERROR, err.Error(), nil), err
return "", jsonrpc.NewError("", jsonrpc.INTERNAL_ERROR, err.Error(), nil), err
}
if protocolVersion == "" {
protocolVersion = v20241105.PROTOCOL_VERSION
}
// Generic baseMessage could either be a JSONRPCNotification or JSONRPCRequest
var baseMessage struct {
Jsonrpc string `json:"jsonrpc"`
Method string `json:"method"`
Id mcp.RequestId `json:"id,omitempty"`
}
if err = decodeJSON(bytes.NewBuffer(body), &baseMessage); err != nil {
var baseMessage jsonrpc.BaseMessage
if err = util.DecodeJSON(bytes.NewBuffer(body), &baseMessage); err != nil {
// Generate a new uuid if unable to decode
id := uuid.New().String()
return newJSONRPCError(id, mcp.PARSE_ERROR, err.Error(), nil), err
// check if user is sending a batch request
var a []any
unmarshalErr := json.Unmarshal(body, &a)
if unmarshalErr == nil {
err = fmt.Errorf("not supporting batch requests")
return "", jsonrpc.NewError(id, jsonrpc.INVALID_REQUEST, err.Error(), nil), err
}
return "", jsonrpc.NewError(id, jsonrpc.PARSE_ERROR, err.Error(), nil), err
}
// Check if method is present
if baseMessage.Method == "" {
err = fmt.Errorf("method not found")
return newJSONRPCError(baseMessage.Id, mcp.METHOD_NOT_FOUND, err.Error(), nil), err
return "", jsonrpc.NewError(baseMessage.Id, jsonrpc.METHOD_NOT_FOUND, err.Error(), nil), err
}
logger.DebugContext(ctx, fmt.Sprintf("method is: %s", baseMessage.Method))
// Check for JSON-RPC 2.0
if baseMessage.Jsonrpc != mcp.JSONRPC_VERSION {
if baseMessage.Jsonrpc != jsonrpc.JSONRPC_VERSION {
err = fmt.Errorf("invalid json-rpc version")
return newJSONRPCError(baseMessage.Id, mcp.INVALID_REQUEST, err.Error(), nil), err
return "", jsonrpc.NewError(baseMessage.Id, jsonrpc.INVALID_REQUEST, err.Error(), nil), err
}
// Check if message is a notification
if baseMessage.Id == nil {
var notification mcp.JSONRPCNotification
if err = json.Unmarshal(body, &notification); err != nil {
err = fmt.Errorf("invalid notification request: %w", err)
return nil, err
}
return nil, nil
err := mcp.NotificationHandler(ctx, body)
return "", nil, err
}
switch baseMessage.Method {
case "initialize":
var req mcp.InitializeRequest
if err = json.Unmarshal(body, &req); err != nil {
err = fmt.Errorf("invalid mcp initialize request: %w", err)
return newJSONRPCError(baseMessage.Id, mcp.INVALID_REQUEST, err.Error(), nil), err
}
result := mcp.Initialize(s.version)
return mcp.JSONRPCResponse{
Jsonrpc: mcp.JSONRPC_VERSION,
Id: baseMessage.Id,
Result: result,
}, nil
case "tools/list":
var req mcp.ListToolsRequest
if err = json.Unmarshal(body, &req); err != nil {
err = fmt.Errorf("invalid mcp tools list request: %w", err)
return newJSONRPCError(baseMessage.Id, mcp.INVALID_REQUEST, err.Error(), nil), err
case mcputil.INITIALIZE:
res, v, err := mcp.InitializeResponse(ctx, baseMessage.Id, body, s.version)
if err != nil {
return "", res, err
}
return v, res, err
default:
toolset, ok := s.toolsets[toolsetName]
if !ok {
err = fmt.Errorf("toolset does not exist")
return newJSONRPCError(baseMessage.Id, mcp.INVALID_REQUEST, err.Error(), nil), err
return "", jsonrpc.NewError(baseMessage.Id, jsonrpc.INVALID_REQUEST, err.Error(), nil), err
}
result := mcp.ToolsList(toolset)
return mcp.JSONRPCResponse{
Jsonrpc: mcp.JSONRPC_VERSION,
Id: baseMessage.Id,
Result: result,
}, nil
case "tools/call":
var req mcp.CallToolRequest
if err = json.Unmarshal(body, &req); err != nil {
err = fmt.Errorf("invalid mcp tools call request: %w", err)
return newJSONRPCError(baseMessage.Id, mcp.INVALID_REQUEST, err.Error(), nil), err
}
toolName := req.Params.Name
toolArgument := req.Params.Arguments
logger.DebugContext(ctx, fmt.Sprintf("tool name: %s", toolName))
tool, ok := s.tools[toolName]
if !ok {
err = fmt.Errorf("invalid tool name: tool with name %q does not exist", toolName)
return newJSONRPCError(baseMessage.Id, mcp.INVALID_PARAMS, err.Error(), nil), err
}
// marshal arguments and decode it using decodeJSON instead to prevent loss between floats/int.
aMarshal, err := json.Marshal(toolArgument)
if err != nil {
err = fmt.Errorf("unable to marshal tools argument: %w", err)
return newJSONRPCError(baseMessage.Id, mcp.INTERNAL_ERROR, err.Error(), nil), err
}
var data map[string]any
if err = decodeJSON(bytes.NewBuffer(aMarshal), &data); err != nil {
err = fmt.Errorf("unable to decode tools argument: %w", err)
return newJSONRPCError(baseMessage.Id, mcp.INTERNAL_ERROR, err.Error(), nil), err
}
// claimsFromAuth maps the name of the authservice to the claims retrieved from it.
// Since MCP doesn't support auth, an empty map will be use every time.
claimsFromAuth := make(map[string]map[string]any)
params, err := tool.ParseParams(data, claimsFromAuth)
if err != nil {
err = fmt.Errorf("provided parameters were invalid: %w", err)
return newJSONRPCError(baseMessage.Id, mcp.INVALID_PARAMS, err.Error(), nil), err
}
logger.DebugContext(ctx, fmt.Sprintf("invocation params: %s", params))
if !tool.Authorized([]string{}) {
err = fmt.Errorf("unauthorized Tool call: `authRequired` is set for the target Tool")
return newJSONRPCError(baseMessage.Id, mcp.INVALID_REQUEST, err.Error(), nil), err
}
result := mcp.ToolCall(ctx, tool, params)
return mcp.JSONRPCResponse{
Jsonrpc: mcp.JSONRPC_VERSION,
Id: baseMessage.Id,
Result: result,
}, nil
default:
err = fmt.Errorf("invalid method %s", baseMessage.Method)
return newJSONRPCError(baseMessage.Id, mcp.METHOD_NOT_FOUND, err.Error(), nil), err
}
}
// newJSONRPCError is the response sent back when an error has been encountered in mcp.
func newJSONRPCError(id mcp.RequestId, code int, message string, data any) mcp.JSONRPCError {
return mcp.JSONRPCError{
Jsonrpc: mcp.JSONRPC_VERSION,
Id: id,
Error: mcp.McpError{
Code: code,
Message: message,
Data: data,
},
res, err := mcp.ProcessMethod(ctx, protocolVersion, baseMessage.Id, baseMessage.Method, toolset, s.tools, body)
return "", res, err
}
}

View File

@@ -0,0 +1,125 @@
// 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 jsonrpc
// JSONRPC_VERSION is the version of JSON-RPC used by MCP.
const JSONRPC_VERSION = "2.0"
// Standard JSON-RPC error codes
const (
PARSE_ERROR = -32700
INVALID_REQUEST = -32600
METHOD_NOT_FOUND = -32601
INVALID_PARAMS = -32602
INTERNAL_ERROR = -32603
)
// ProgressToken is used to associate progress notifications with the original request.
type ProgressToken interface{}
// RequestId is a uniquely identifying ID for a request in JSON-RPC.
// It can be any JSON-serializable value, typically a number or string.
type RequestId interface{}
// Request represents a bidirectional message with method and parameters expecting a response.
type Request struct {
Method string `json:"method"`
Params struct {
Meta struct {
// If specified, the caller is requesting out-of-band progress
// notifications for this request (as represented by
// notifications/progress). The value of this parameter is an
// opaque token that will be attached to any subsequent
// notifications. The receiver is not obligated to provide these
// notifications.
ProgressToken ProgressToken `json:"progressToken,omitempty"`
} `json:"_meta,omitempty"`
} `json:"params,omitempty"`
}
// JSONRPCRequest represents a request that expects a response.
type JSONRPCRequest struct {
Jsonrpc string `json:"jsonrpc"`
Id RequestId `json:"id"`
Request
Params any `json:"params,omitempty"`
}
// Notification is a one-way message requiring no response.
type Notification struct {
Method string `json:"method"`
Params struct {
Meta map[string]interface{} `json:"_meta,omitempty"`
} `json:"params,omitempty"`
}
// JSONRPCNotification represents a notification which does not expect a response.
type JSONRPCNotification struct {
Jsonrpc string `json:"jsonrpc"`
Notification
}
// Result represents a response for the request query.
type Result struct {
// This result property is reserved by the protocol to allow clients and
// servers to attach additional metadata to their responses.
Meta map[string]interface{} `json:"_meta,omitempty"`
}
// JSONRPCResponse represents a successful (non-error) response to a request.
type JSONRPCResponse struct {
Jsonrpc string `json:"jsonrpc"`
Id RequestId `json:"id"`
Result interface{} `json:"result"`
}
// Error represents the error content.
type Error struct {
// The error type that occurred.
Code int `json:"code"`
// A short description of the error. The message SHOULD be limited
// to a concise single sentence.
Message string `json:"message"`
// Additional information about the error. The value of this member
// is defined by the sender (e.g. detailed error information, nested errors etc.).
Data interface{} `json:"data,omitempty"`
}
// JSONRPCError represents a non-successful (error) response to a request.
type JSONRPCError struct {
Jsonrpc string `json:"jsonrpc"`
Id RequestId `json:"id"`
Error Error `json:"error"`
}
// Generic baseMessage could either be a JSONRPCNotification or JSONRPCRequest
type BaseMessage struct {
Jsonrpc string `json:"jsonrpc"`
Method string `json:"method"`
Id RequestId `json:"id,omitempty"`
}
// NewError is the standard JSONRPC response sent back when an error has been encountered.
func NewError(id RequestId, code int, message string, data any) JSONRPCError {
return JSONRPCError{
Jsonrpc: JSONRPC_VERSION,
Id: id,
Error: Error{
Code: code,
Message: message,
Data: data,
},
}
}

View File

@@ -0,0 +1,99 @@
// 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 mcp
import (
"context"
"encoding/json"
"fmt"
"slices"
"github.com/googleapis/genai-toolbox/internal/server/mcp/jsonrpc"
mcputil "github.com/googleapis/genai-toolbox/internal/server/mcp/util"
v20241105 "github.com/googleapis/genai-toolbox/internal/server/mcp/v20241105"
v20250326 "github.com/googleapis/genai-toolbox/internal/server/mcp/v20250326"
"github.com/googleapis/genai-toolbox/internal/tools"
)
// LATEST_PROTOCOL_VERSION is the latest version of the MCP protocol supported.
// Update the version used in InitializeResponse when this value is updated.
const LATEST_PROTOCOL_VERSION = v20250326.PROTOCOL_VERSION
// SUPPORTED_PROTOCOL_VERSIONS is the MCP protocol versions that are supported.
var SUPPORTED_PROTOCOL_VERSIONS = []string{v20241105.PROTOCOL_VERSION, v20250326.PROTOCOL_VERSION}
// InitializeResponse runs capability negotiation and protocol version agreement.
// This is the Initialization phase of the lifecycle for MCP client-server connections.
// Always start with the latest protocol version supported.
func InitializeResponse(ctx context.Context, id jsonrpc.RequestId, body []byte, toolboxVersion string) (any, string, error) {
var req mcputil.InitializeRequest
if err := json.Unmarshal(body, &req); err != nil {
err = fmt.Errorf("invalid mcp initialize request: %w", err)
return jsonrpc.NewError(id, jsonrpc.INVALID_REQUEST, err.Error(), nil), "", err
}
var protocolVersion string
v := req.Params.ProtocolVersion
if slices.Contains(SUPPORTED_PROTOCOL_VERSIONS, v) {
protocolVersion = v
} else {
protocolVersion = LATEST_PROTOCOL_VERSION
}
toolsListChanged := false
result := mcputil.InitializeResult{
ProtocolVersion: protocolVersion,
Capabilities: mcputil.ServerCapabilities{
Tools: &mcputil.ListChanged{
ListChanged: &toolsListChanged,
},
},
ServerInfo: mcputil.Implementation{
Name: mcputil.SERVER_NAME,
Version: toolboxVersion,
},
}
res := jsonrpc.JSONRPCResponse{
Jsonrpc: jsonrpc.JSONRPC_VERSION,
Id: id,
Result: result,
}
return res, protocolVersion, nil
}
// NotificationHandler process notifications request. It MUST NOT send a response.
// Currently Toolbox does not process any notifications.
func NotificationHandler(ctx context.Context, body []byte) error {
var notification jsonrpc.JSONRPCNotification
if err := json.Unmarshal(body, &notification); err != nil {
return fmt.Errorf("invalid notification request: %w", err)
}
return nil
}
// ProcessMethod returns a response for the request.
// This is the Operation phase of the lifecycle for MCP client-server connections.
func ProcessMethod(ctx context.Context, mcpVersion string, id jsonrpc.RequestId, method string, toolset tools.Toolset, tools map[string]tools.Tool, body []byte) (any, error) {
switch mcpVersion {
case v20250326.PROTOCOL_VERSION:
return v20250326.ProcessMethod(ctx, id, method, toolset, tools, body)
case v20241105.PROTOCOL_VERSION:
return v20241105.ProcessMethod(ctx, id, method, toolset, tools, body)
default:
err := fmt.Errorf("invalid protocol version: %s", mcpVersion)
return jsonrpc.NewError(id, jsonrpc.INVALID_REQUEST, err.Error(), nil), err
}
}

View File

@@ -1,75 +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 mcp
import (
"context"
"encoding/json"
"fmt"
"github.com/googleapis/genai-toolbox/internal/tools"
)
func Initialize(version string) InitializeResult {
toolsListChanged := false
result := InitializeResult{
ProtocolVersion: LATEST_PROTOCOL_VERSION,
Capabilities: ServerCapabilities{
Tools: &ListChanged{
ListChanged: &toolsListChanged,
},
},
ServerInfo: Implementation{
Name: SERVER_NAME,
Version: version,
},
}
return result
}
// ToolsList return a ListToolsResult
func ToolsList(toolset tools.Toolset) ListToolsResult {
mcpManifest := toolset.McpManifest
result := ListToolsResult{
Tools: mcpManifest,
}
return result
}
// ToolCall runs tool invocation and return a CallToolResult
func ToolCall(ctx context.Context, tool tools.Tool, params tools.ParamValues) CallToolResult {
res, err := tool.Invoke(ctx, params)
if err != nil {
text := TextContent{
Type: "text",
Text: err.Error(),
}
return CallToolResult{Content: []TextContent{text}, IsError: true}
}
content := make([]TextContent, 0)
for _, d := range res {
text := TextContent{Type: "text"}
dM, err := json.Marshal(d)
if err != nil {
text.Text = fmt.Sprintf("fail to marshal: %s, result: %s", err, d)
} else {
text.Text = string(dM)
}
content = append(content, text)
}
return CallToolResult{Content: content}
}

View File

@@ -1,295 +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 mcp
import (
"github.com/googleapis/genai-toolbox/internal/tools"
)
// SERVER_NAME is the server name used in Implementation.
const SERVER_NAME = "Toolbox"
// LATEST_PROTOCOL_VERSION is the most recent version of the MCP protocol.
const LATEST_PROTOCOL_VERSION = "2024-11-05"
// JSONRPC_VERSION is the version of JSON-RPC used by MCP.
const JSONRPC_VERSION = "2.0"
// Standard JSON-RPC error codes
const (
PARSE_ERROR = -32700
INVALID_REQUEST = -32600
METHOD_NOT_FOUND = -32601
INVALID_PARAMS = -32602
INTERNAL_ERROR = -32603
)
// JSONRPCMessage represents either a JSONRPCRequest, JSONRPCNotification, JSONRPCResponse, or JSONRPCError.
type JSONRPCMessage interface{}
// ProgressToken is used to associate progress notifications with the original request.
type ProgressToken interface{}
// Request represents a bidirectional message with method and parameters expecting a response.
type Request struct {
Method string `json:"method"`
Params struct {
Meta struct {
// If specified, the caller is requesting out-of-band progress
// notifications for this request (as represented by
// notifications/progress). The value of this parameter is an
// opaque token that will be attached to any subsequent
// notifications. The receiver is not obligated to provide these
// notifications.
ProgressToken ProgressToken `json:"progressToken,omitempty"`
} `json:"_meta,omitempty"`
} `json:"params,omitempty"`
}
// Notification is a one-way message requiring no response.
type Notification struct {
Method string `json:"method"`
Params struct {
Meta map[string]interface{} `json:"_meta,omitempty"`
} `json:"params,omitempty"`
}
// Result represents a response for the request query.
type Result struct {
// This result property is reserved by the protocol to allow clients and
// servers to attach additional metadata to their responses.
Meta map[string]interface{} `json:"_meta,omitempty"`
}
// RequestId is a uniquely identifying ID for a request in JSON-RPC.
// It can be any JSON-serializable value, typically a number or string.
type RequestId interface{}
// JSONRPCRequest represents a request that expects a response.
type JSONRPCRequest struct {
Jsonrpc string `json:"jsonrpc"`
Id RequestId `json:"id"`
Request
Params any `json:"params,omitempty"`
}
// JSONRPCNotification represents a notification which does not expect a response.
type JSONRPCNotification struct {
Jsonrpc string `json:"jsonrpc"`
Notification
}
// JSONRPCResponse represents a successful (non-error) response to a request.
type JSONRPCResponse struct {
Jsonrpc string `json:"jsonrpc"`
Id RequestId `json:"id"`
Result interface{} `json:"result"`
}
// McpError represents the error content.
type McpError struct {
// The error type that occurred.
Code int `json:"code"`
// A short description of the error. The message SHOULD be limited
// to a concise single sentence.
Message string `json:"message"`
// Additional information about the error. The value of this member
// is defined by the sender (e.g. detailed error information, nested errors etc.).
Data interface{} `json:"data,omitempty"`
}
// JSONRPCError represents a non-successful (error) response to a request.
type JSONRPCError struct {
Jsonrpc string `json:"jsonrpc"`
Id RequestId `json:"id"`
Error McpError `json:"error"`
}
/* Empty result */
// EmptyResult represents a response that indicates success but carries no data.
type EmptyResult Result
/* Initialization */
// Params to define MCP Client during initialize request.
type InitializeParams struct {
// The latest version of the Model Context Protocol that the client supports.
// The client MAY decide to support older versions as well.
ProtocolVersion string `json:"protocolVersion"`
Capabilities ClientCapabilities `json:"capabilities"`
ClientInfo Implementation `json:"clientInfo"`
}
// InitializeRequest is sent from the client to the server when it first
// connects, asking it to begin initialization.
type InitializeRequest struct {
Request
Params InitializeParams `json:"params"`
}
// InitializeResult is sent after receiving an initialize request from the
// client.
type InitializeResult struct {
Result
// The version of the Model Context Protocol that the server wants to use.
// This may not match the version that the client requested. If the client cannot
// support this version, it MUST disconnect.
ProtocolVersion string `json:"protocolVersion"`
Capabilities ServerCapabilities `json:"capabilities"`
ServerInfo Implementation `json:"serverInfo"`
// Instructions describing how to use the server and its features.
//
// This can be used by clients to improve the LLM's understanding of
// available tools, resources, etc. It can be thought of like a "hint" to the model.
// For example, this information MAY be added to the system prompt.
Instructions string `json:"instructions,omitempty"`
}
// InitializedNotification is sent from the client to the server after
// initialization has finished.
type InitializedNotification struct {
Notification
}
// ListChange represents whether the server supports notification for changes to the capabilities.
type ListChanged struct {
ListChanged *bool `json:"listChanged,omitempty"`
}
// ClientCapabilities represents capabilities a client may support. Known
// capabilities are defined here, in this schema, but this is not a closed set: any
// client can define its own, additional capabilities.
type ClientCapabilities struct {
// Experimental, non-standard capabilities that the client supports.
Experimental map[string]interface{} `json:"experimental,omitempty"`
// Present if the client supports listing roots.
Roots *ListChanged `json:"roots,omitempty"`
// Present if the client supports sampling from an LLM.
Sampling struct{} `json:"sampling,omitempty"`
}
// ServerCapabilities represents capabilities that a server may support. Known
// capabilities are defined here, in this schema, but this is not a closed set: any
// server can define its own, additional capabilities.
type ServerCapabilities struct {
Tools *ListChanged `json:"tools,omitempty"`
}
// Implementation describes the name and version of an MCP implementation.
type Implementation struct {
Name string `json:"name"`
Version string `json:"version"`
}
/* Pagination */
// Cursor is an opaque token used to represent a cursor for pagination.
type Cursor string
type PaginatedRequest struct {
Request
Params struct {
// An opaque token representing the current pagination position.
// If provided, the server should return results starting after this cursor.
Cursor Cursor `json:"cursor,omitempty"`
} `json:"params,omitempty"`
}
type PaginatedResult struct {
Result
// An opaque token representing the pagination position after the last returned result.
// If present, there may be more results available.
NextCursor Cursor `json:"nextCursor,omitempty"`
}
/* Tools */
// Sent from the client to request a list of tools the server has.
type ListToolsRequest struct {
PaginatedRequest
}
// The server's response to a tools/list request from the client.
type ListToolsResult struct {
PaginatedResult
Tools []tools.McpManifest `json:"tools"`
}
// Used by the client to invoke a tool provided by the server.
type CallToolRequest struct {
Request
Params struct {
Name string `json:"name"`
Arguments map[string]any `json:"arguments,omitempty"`
} `json:"params,omitempty"`
}
// The sender or recipient of messages and data in a conversation.
type Role string
const (
RoleUser Role = "user"
RoleAssistant Role = "assistant"
)
// Base for objects that include optional annotations for the client.
// The client can use annotations to inform how objects are used or displayed
type Annotated struct {
Annotations *struct {
// Describes who the intended customer of this object or data is.
// It can include multiple entries to indicate content useful for multiple
// audiences (e.g., `["user", "assistant"]`).
Audience []Role `json:"audience,omitempty"`
// Describes how important this data is for operating the server.
//
// A value of 1 means "most important," and indicates that the data is
// effectively required, while 0 means "least important," and indicates that
// the data is entirely optional.
//
// @TJS-type number
// @minimum 0
// @maximum 1
Priority float64 `json:"priority,omitempty"`
} `json:"annotations,omitempty"`
}
// TextContent represents text provided to or from an LLM.
type TextContent struct {
Annotated
Type string `json:"type"`
// The text content of the message.
Text string `json:"text"`
}
// The server's response to a tool call.
//
// Any errors that originate from the tool SHOULD be reported inside the result
// object, with `isError` set to true, _not_ as an MCP protocol-level error
// response. Otherwise, the LLM would not be able to see that an error occurred
// and self-correct.
//
// However, any errors in _finding_ the tool, an error indicating that the
// server does not support tool calls, or any other exceptional conditions,
// should be reported as an MCP error response.
type CallToolResult struct {
Result
// Could be either a TextContent, ImageContent, or EmbeddedResources
// For Toolbox, we will only be sending TextContent
Content []TextContent `json:"content"`
// Whether the tool call ended in an error.
// If not set, this is assumed to be false (the call was successful).
IsError bool `json:"isError,omitempty"`
}

View File

@@ -0,0 +1,96 @@
// 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 util
import "github.com/googleapis/genai-toolbox/internal/server/mcp/jsonrpc"
const (
// SERVER_NAME is the server name used in Implementation.
SERVER_NAME = "Toolbox"
// methods that are supported
INITIALIZE = "initialize"
)
/* Initialization */
// Params to define MCP Client during initialize request.
type InitializeParams struct {
// The latest version of the Model Context Protocol that the client supports.
// The client MAY decide to support older versions as well.
ProtocolVersion string `json:"protocolVersion"`
Capabilities ClientCapabilities `json:"capabilities"`
ClientInfo Implementation `json:"clientInfo"`
}
// InitializeRequest is sent from the client to the server when it first
// connects, asking it to begin initialization.
type InitializeRequest struct {
jsonrpc.Request
Params InitializeParams `json:"params"`
}
// InitializeResult is sent after receiving an initialize request from the
// client.
type InitializeResult struct {
jsonrpc.Result
// The version of the Model Context Protocol that the server wants to use.
// This may not match the version that the client requested. If the client cannot
// support this version, it MUST disconnect.
ProtocolVersion string `json:"protocolVersion"`
Capabilities ServerCapabilities `json:"capabilities"`
ServerInfo Implementation `json:"serverInfo"`
// Instructions describing how to use the server and its features.
//
// This can be used by clients to improve the LLM's understanding of
// available tools, resources, etc. It can be thought of like a "hint" to the model.
// For example, this information MAY be added to the system prompt.
Instructions string `json:"instructions,omitempty"`
}
// InitializedNotification is sent from the client to the server after
// initialization has finished.
type InitializedNotification struct {
jsonrpc.Notification
}
// ListChange represents whether the server supports notification for changes to the capabilities.
type ListChanged struct {
ListChanged *bool `json:"listChanged,omitempty"`
}
// ClientCapabilities represents capabilities a client may support. Known
// capabilities are defined here, in this schema, but this is not a closed set: any
// client can define its own, additional capabilities.
type ClientCapabilities struct {
// Experimental, non-standard capabilities that the client supports.
Experimental map[string]interface{} `json:"experimental,omitempty"`
// Present if the client supports listing roots.
Roots *ListChanged `json:"roots,omitempty"`
// Present if the client supports sampling from an LLM.
Sampling struct{} `json:"sampling,omitempty"`
}
// ServerCapabilities represents capabilities that a server may support. Known
// capabilities are defined here, in this schema, but this is not a closed set: any
// server can define its own, additional capabilities.
type ServerCapabilities struct {
Tools *ListChanged `json:"tools,omitempty"`
}
// Implementation describes the name and version of an MCP implementation.
type Implementation struct {
Name string `json:"name"`
Version string `json:"version"`
}

View File

@@ -0,0 +1,141 @@
// 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 v20241105
import (
"bytes"
"context"
"encoding/json"
"fmt"
"github.com/googleapis/genai-toolbox/internal/server/mcp/jsonrpc"
"github.com/googleapis/genai-toolbox/internal/tools"
"github.com/googleapis/genai-toolbox/internal/util"
)
// ProcessMethod returns a response for the request.
func ProcessMethod(ctx context.Context, id jsonrpc.RequestId, method string, toolset tools.Toolset, tools map[string]tools.Tool, body []byte) (any, error) {
switch method {
case TOOLS_LIST:
return toolsListHandler(id, toolset, body)
case TOOLS_CALL:
return toolsCallHandler(ctx, id, tools, body)
default:
err := fmt.Errorf("invalid method %s", method)
return jsonrpc.NewError(id, jsonrpc.METHOD_NOT_FOUND, err.Error(), nil), err
}
}
func toolsListHandler(id jsonrpc.RequestId, toolset tools.Toolset, body []byte) (any, error) {
var req ListToolsRequest
if err := json.Unmarshal(body, &req); err != nil {
err = fmt.Errorf("invalid mcp tools list request: %w", err)
return jsonrpc.NewError(id, jsonrpc.INVALID_REQUEST, err.Error(), nil), err
}
result := ListToolsResult{
Tools: toolset.McpManifest,
}
return jsonrpc.JSONRPCResponse{
Jsonrpc: jsonrpc.JSONRPC_VERSION,
Id: id,
Result: result,
}, nil
}
// toolsCallHandler generate a response for tools call.
func toolsCallHandler(ctx context.Context, id jsonrpc.RequestId, tools map[string]tools.Tool, body []byte) (any, error) {
// retrieve logger from context
logger, err := util.LoggerFromContext(ctx)
if err != nil {
return jsonrpc.NewError(id, jsonrpc.INTERNAL_ERROR, err.Error(), nil), err
}
var req CallToolRequest
if err = json.Unmarshal(body, &req); err != nil {
err = fmt.Errorf("invalid mcp tools call request: %w", err)
return jsonrpc.NewError(id, jsonrpc.INVALID_REQUEST, err.Error(), nil), err
}
toolName := req.Params.Name
toolArgument := req.Params.Arguments
logger.DebugContext(ctx, fmt.Sprintf("tool name: %s", toolName))
tool, ok := tools[toolName]
if !ok {
err = fmt.Errorf("invalid tool name: tool with name %q does not exist", toolName)
return jsonrpc.NewError(id, jsonrpc.INVALID_PARAMS, err.Error(), nil), err
}
// marshal arguments and decode it using decodeJSON instead to prevent loss between floats/int.
aMarshal, err := json.Marshal(toolArgument)
if err != nil {
err = fmt.Errorf("unable to marshal tools argument: %w", err)
return jsonrpc.NewError(id, jsonrpc.INTERNAL_ERROR, err.Error(), nil), err
}
var data map[string]any
if err = util.DecodeJSON(bytes.NewBuffer(aMarshal), &data); err != nil {
err = fmt.Errorf("unable to decode tools argument: %w", err)
return jsonrpc.NewError(id, jsonrpc.INTERNAL_ERROR, err.Error(), nil), err
}
// claimsFromAuth maps the name of the authservice to the claims retrieved from it.
// Since MCP doesn't support auth, an empty map will be use every time.
claimsFromAuth := make(map[string]map[string]any)
params, err := tool.ParseParams(data, claimsFromAuth)
if err != nil {
err = fmt.Errorf("provided parameters were invalid: %w", err)
return jsonrpc.NewError(id, jsonrpc.INVALID_PARAMS, err.Error(), nil), err
}
logger.DebugContext(ctx, fmt.Sprintf("invocation params: %s", params))
if !tool.Authorized([]string{}) {
err = fmt.Errorf("unauthorized Tool call: `authRequired` is set for the target Tool")
return jsonrpc.NewError(id, jsonrpc.INVALID_REQUEST, err.Error(), nil), err
}
// run tool invocation and generate response.
results, err := tool.Invoke(ctx, params)
if err != nil {
text := TextContent{
Type: "text",
Text: err.Error(),
}
return jsonrpc.JSONRPCResponse{
Jsonrpc: jsonrpc.JSONRPC_VERSION,
Id: id,
Result: CallToolResult{Content: []TextContent{text}, IsError: true},
}, nil
}
content := make([]TextContent, 0)
for _, d := range results {
text := TextContent{Type: "text"}
dM, err := json.Marshal(d)
if err != nil {
text.Text = fmt.Sprintf("fail to marshal: %s, result: %s", err, d)
} else {
text.Text = string(dM)
}
content = append(content, text)
}
return jsonrpc.JSONRPCResponse{
Jsonrpc: jsonrpc.JSONRPC_VERSION,
Id: id,
Result: CallToolResult{Content: content},
}, nil
}

View File

@@ -0,0 +1,137 @@
// 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 v20241105
import (
"github.com/googleapis/genai-toolbox/internal/server/mcp/jsonrpc"
"github.com/googleapis/genai-toolbox/internal/tools"
)
// SERVER_NAME is the server name used in Implementation.
const SERVER_NAME = "Toolbox"
// PROTOCOL_VERSION is the version of the MCP protocol in this package.
const PROTOCOL_VERSION = "2024-11-05"
// methods that are supported.
const (
TOOLS_LIST = "tools/list"
TOOLS_CALL = "tools/call"
)
/* Empty result */
// EmptyResult represents a response that indicates success but carries no data.
type EmptyResult jsonrpc.Result
/* Pagination */
// Cursor is an opaque token used to represent a cursor for pagination.
type Cursor string
type PaginatedRequest struct {
jsonrpc.Request
Params struct {
// An opaque token representing the current pagination position.
// If provided, the server should return results starting after this cursor.
Cursor Cursor `json:"cursor,omitempty"`
} `json:"params,omitempty"`
}
type PaginatedResult struct {
jsonrpc.Result
// An opaque token representing the pagination position after the last returned result.
// If present, there may be more results available.
NextCursor Cursor `json:"nextCursor,omitempty"`
}
/* Tools */
// Sent from the client to request a list of tools the server has.
type ListToolsRequest struct {
PaginatedRequest
}
// The server's response to a tools/list request from the client.
type ListToolsResult struct {
PaginatedResult
Tools []tools.McpManifest `json:"tools"`
}
// Used by the client to invoke a tool provided by the server.
type CallToolRequest struct {
jsonrpc.Request
Params struct {
Name string `json:"name"`
Arguments map[string]any `json:"arguments,omitempty"`
} `json:"params,omitempty"`
}
// The sender or recipient of messages and data in a conversation.
type Role string
const (
RoleUser Role = "user"
RoleAssistant Role = "assistant"
)
// Base for objects that include optional annotations for the client.
// The client can use annotations to inform how objects are used or displayed
type Annotated struct {
Annotations *struct {
// Describes who the intended customer of this object or data is.
// It can include multiple entries to indicate content useful for multiple
// audiences (e.g., `["user", "assistant"]`).
Audience []Role `json:"audience,omitempty"`
// Describes how important this data is for operating the server.
//
// A value of 1 means "most important," and indicates that the data is
// effectively required, while 0 means "least important," and indicates that
// the data is entirely optional.
//
// @TJS-type number
// @minimum 0
// @maximum 1
Priority float64 `json:"priority,omitempty"`
} `json:"annotations,omitempty"`
}
// TextContent represents text provided to or from an LLM.
type TextContent struct {
Annotated
Type string `json:"type"`
// The text content of the message.
Text string `json:"text"`
}
// The server's response to a tool call.
//
// Any errors that originate from the tool SHOULD be reported inside the result
// object, with `isError` set to true, _not_ as an MCP protocol-level error
// response. Otherwise, the LLM would not be able to see that an error occurred
// and self-correct.
//
// However, any errors in _finding_ the tool, an error indicating that the
// server does not support tool calls, or any other exceptional conditions,
// should be reported as an MCP error response.
type CallToolResult struct {
jsonrpc.Result
// Could be either a TextContent, ImageContent, or EmbeddedResources
// For Toolbox, we will only be sending TextContent
Content []TextContent `json:"content"`
// Whether the tool call ended in an error.
// If not set, this is assumed to be false (the call was successful).
IsError bool `json:"isError,omitempty"`
}

View File

@@ -0,0 +1,141 @@
// 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 v20250326
import (
"bytes"
"context"
"encoding/json"
"fmt"
"github.com/googleapis/genai-toolbox/internal/server/mcp/jsonrpc"
"github.com/googleapis/genai-toolbox/internal/tools"
"github.com/googleapis/genai-toolbox/internal/util"
)
// ProcessMethod returns a response for the request.
func ProcessMethod(ctx context.Context, id jsonrpc.RequestId, method string, toolset tools.Toolset, tools map[string]tools.Tool, body []byte) (any, error) {
switch method {
case TOOLS_LIST:
return toolsListHandler(id, toolset, body)
case TOOLS_CALL:
return toolsCallHandler(ctx, id, tools, body)
default:
err := fmt.Errorf("invalid method %s", method)
return jsonrpc.NewError(id, jsonrpc.METHOD_NOT_FOUND, err.Error(), nil), err
}
}
func toolsListHandler(id jsonrpc.RequestId, toolset tools.Toolset, body []byte) (any, error) {
var req ListToolsRequest
if err := json.Unmarshal(body, &req); err != nil {
err = fmt.Errorf("invalid mcp tools list request: %w", err)
return jsonrpc.NewError(id, jsonrpc.INVALID_REQUEST, err.Error(), nil), err
}
result := ListToolsResult{
Tools: toolset.McpManifest,
}
return jsonrpc.JSONRPCResponse{
Jsonrpc: jsonrpc.JSONRPC_VERSION,
Id: id,
Result: result,
}, nil
}
// toolsCallHandler generate a response for tools call.
func toolsCallHandler(ctx context.Context, id jsonrpc.RequestId, tools map[string]tools.Tool, body []byte) (any, error) {
// retrieve logger from context
logger, err := util.LoggerFromContext(ctx)
if err != nil {
return jsonrpc.NewError(id, jsonrpc.INTERNAL_ERROR, err.Error(), nil), err
}
var req CallToolRequest
if err = json.Unmarshal(body, &req); err != nil {
err = fmt.Errorf("invalid mcp tools call request: %w", err)
return jsonrpc.NewError(id, jsonrpc.INVALID_REQUEST, err.Error(), nil), err
}
toolName := req.Params.Name
toolArgument := req.Params.Arguments
logger.DebugContext(ctx, fmt.Sprintf("tool name: %s", toolName))
tool, ok := tools[toolName]
if !ok {
err = fmt.Errorf("invalid tool name: tool with name %q does not exist", toolName)
return jsonrpc.NewError(id, jsonrpc.INVALID_PARAMS, err.Error(), nil), err
}
// marshal arguments and decode it using decodeJSON instead to prevent loss between floats/int.
aMarshal, err := json.Marshal(toolArgument)
if err != nil {
err = fmt.Errorf("unable to marshal tools argument: %w", err)
return jsonrpc.NewError(id, jsonrpc.INTERNAL_ERROR, err.Error(), nil), err
}
var data map[string]any
if err = util.DecodeJSON(bytes.NewBuffer(aMarshal), &data); err != nil {
err = fmt.Errorf("unable to decode tools argument: %w", err)
return jsonrpc.NewError(id, jsonrpc.INTERNAL_ERROR, err.Error(), nil), err
}
// claimsFromAuth maps the name of the authservice to the claims retrieved from it.
// Since MCP doesn't support auth, an empty map will be use every time.
claimsFromAuth := make(map[string]map[string]any)
params, err := tool.ParseParams(data, claimsFromAuth)
if err != nil {
err = fmt.Errorf("provided parameters were invalid: %w", err)
return jsonrpc.NewError(id, jsonrpc.INVALID_PARAMS, err.Error(), nil), err
}
logger.DebugContext(ctx, fmt.Sprintf("invocation params: %s", params))
if !tool.Authorized([]string{}) {
err = fmt.Errorf("unauthorized Tool call: `authRequired` is set for the target Tool")
return jsonrpc.NewError(id, jsonrpc.INVALID_REQUEST, err.Error(), nil), err
}
// run tool invocation and generate response.
results, err := tool.Invoke(ctx, params)
if err != nil {
text := TextContent{
Type: "text",
Text: err.Error(),
}
return jsonrpc.JSONRPCResponse{
Jsonrpc: jsonrpc.JSONRPC_VERSION,
Id: id,
Result: CallToolResult{Content: []TextContent{text}, IsError: true},
}, nil
}
content := make([]TextContent, 0)
for _, d := range results {
text := TextContent{Type: "text"}
dM, err := json.Marshal(d)
if err != nil {
text.Text = fmt.Sprintf("fail to marshal: %s, result: %s", err, d)
} else {
text.Text = string(dM)
}
content = append(content, text)
}
return jsonrpc.JSONRPCResponse{
Jsonrpc: jsonrpc.JSONRPC_VERSION,
Id: id,
Result: CallToolResult{Content: content},
}, nil
}

View File

@@ -0,0 +1,169 @@
// 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 v20250326
import (
"github.com/googleapis/genai-toolbox/internal/server/mcp/jsonrpc"
"github.com/googleapis/genai-toolbox/internal/tools"
)
// SERVER_NAME is the server name used in Implementation.
const SERVER_NAME = "Toolbox"
// PROTOCOL_VERSION is the version of the MCP protocol in this package.
const PROTOCOL_VERSION = "2025-03-26"
// methods that are supported.
const (
TOOLS_LIST = "tools/list"
TOOLS_CALL = "tools/call"
)
/* Empty result */
// EmptyResult represents a response that indicates success but carries no data.
type EmptyResult jsonrpc.Result
/* Pagination */
// Cursor is an opaque token used to represent a cursor for pagination.
type Cursor string
type PaginatedRequest struct {
jsonrpc.Request
Params struct {
// An opaque token representing the current pagination position.
// If provided, the server should return results starting after this cursor.
Cursor Cursor `json:"cursor,omitempty"`
} `json:"params,omitempty"`
}
type PaginatedResult struct {
jsonrpc.Result
// An opaque token representing the pagination position after the last returned result.
// If present, there may be more results available.
NextCursor Cursor `json:"nextCursor,omitempty"`
}
/* Tools */
// Sent from the client to request a list of tools the server has.
type ListToolsRequest struct {
PaginatedRequest
}
// The server's response to a tools/list request from the client.
type ListToolsResult struct {
PaginatedResult
Tools []tools.McpManifest `json:"tools"`
}
// Used by the client to invoke a tool provided by the server.
type CallToolRequest struct {
jsonrpc.Request
Params struct {
Name string `json:"name"`
Arguments map[string]any `json:"arguments,omitempty"`
} `json:"params,omitempty"`
}
// The sender or recipient of messages and data in a conversation.
type Role string
const (
RoleUser Role = "user"
RoleAssistant Role = "assistant"
)
// Base for objects that include optional annotations for the client.
// The client can use annotations to inform how objects are used or displayed
type Annotated struct {
Annotations *struct {
// Describes who the intended customer of this object or data is.
// It can include multiple entries to indicate content useful for multiple
// audiences (e.g., `["user", "assistant"]`).
Audience []Role `json:"audience,omitempty"`
// Describes how important this data is for operating the server.
//
// A value of 1 means "most important," and indicates that the data is
// effectively required, while 0 means "least important," and indicates that
// the data is entirely optional.
//
// @TJS-type number
// @minimum 0
// @maximum 1
Priority float64 `json:"priority,omitempty"`
} `json:"annotations,omitempty"`
}
// TextContent represents text provided to or from an LLM.
type TextContent struct {
Annotated
Type string `json:"type"`
// The text content of the message.
Text string `json:"text"`
}
// The server's response to a tool call.
//
// Any errors that originate from the tool SHOULD be reported inside the result
// object, with `isError` set to true, _not_ as an MCP protocol-level error
// response. Otherwise, the LLM would not be able to see that an error occurred
// and self-correct.
//
// However, any errors in _finding_ the tool, an error indicating that the
// server does not support tool calls, or any other exceptional conditions,
// should be reported as an MCP error response.
type CallToolResult struct {
jsonrpc.Result
// Could be either a TextContent, ImageContent, or EmbeddedResources
// For Toolbox, we will only be sending TextContent
Content []TextContent `json:"content"`
// Whether the tool call ended in an error.
// If not set, this is assumed to be false (the call was successful).
IsError bool `json:"isError,omitempty"`
}
// Additional properties describing a Tool to clients.
//
// NOTE: all properties in ToolAnnotations are **hints**.
// They are not guaranteed to provide a faithful description of
// tool behavior (including descriptive properties like `title`).
//
// Clients should never make tool use decisions based on ToolAnnotations
// received from untrusted servers.
type ToolAnnotations struct {
// A human-readable title for the tool.
Title string `json:"title,omitempty"`
// If true, the tool does not modify its environment.
// Default: false
ReadOnlyHint bool `json:"readOnlyHint,omitempty"`
// If true, the tool may perform destructive updates to its environment.
// If false, the tool performs only additive updates.
// (This property is meaningful only when `readOnlyHint == false`)
// Default: true
DestructiveHint bool `json:"destructiveHint,omitempty"`
// If true, calling the tool repeatedly with the same arguments
// will have no additional effect on the its environment.
// (This property is meaningful only when `readOnlyHint == false`)
// Default: false
IdempotentHint bool `json:"idempotentHint,omitempty"`
// If true, this tool may interact with an "open world" of external
// entities. If false, the tool's domain of interaction is closed.
// For example, the world of a web search tool is open, whereas that
// of a memory tool is not.
// Default: true
OpenWorldHint bool `json:"openWorldHint,omitempty"`
}

View File

@@ -25,16 +25,17 @@ import (
"os"
"reflect"
"strings"
"sync"
"testing"
"github.com/googleapis/genai-toolbox/internal/log"
"github.com/googleapis/genai-toolbox/internal/server/mcp"
"github.com/googleapis/genai-toolbox/internal/server/mcp/jsonrpc"
"github.com/googleapis/genai-toolbox/internal/telemetry"
"github.com/googleapis/genai-toolbox/internal/tools"
)
const jsonrpcVersion = "2.0"
const protocolVersion = "2024-11-05"
const protocolVersion20241105 = "2024-11-05"
const protocolVersion20250326 = "2025-03-26"
const serverName = "Toolbox"
var tool1InputSchema = map[string]any{
@@ -64,7 +65,7 @@ var tool3InputSchema = map[string]any{
"required": []any{"my_array"},
}
func TestMcpEndpoint(t *testing.T) {
func TestMcpEndpointWithoutInitialized(t *testing.T) {
mockTools := []MockTool{tool1, tool2, tool3}
toolsMap, toolsets := setUpResources(t, mockTools)
r, shutdown := setUpServer(t, "mcp", toolsMap, toolsets)
@@ -76,51 +77,20 @@ func TestMcpEndpoint(t *testing.T) {
name string
url string
isErr bool
body mcp.JSONRPCRequest
body jsonrpc.JSONRPCRequest
want map[string]any
}{
{
name: "initialize",
url: "/",
body: mcp.JSONRPCRequest{
Jsonrpc: jsonrpcVersion,
Id: "mcp-initialize",
Request: mcp.Request{
Method: "initialize",
},
},
want: map[string]any{
"jsonrpc": "2.0",
"id": "mcp-initialize",
"result": map[string]any{
"protocolVersion": protocolVersion,
"capabilities": map[string]any{
"tools": map[string]any{"listChanged": false},
},
"serverInfo": map[string]any{"name": serverName, "version": fakeVersionString},
},
},
},
{
name: "basic notification",
url: "/",
body: mcp.JSONRPCRequest{
Jsonrpc: jsonrpcVersion,
Request: mcp.Request{
Method: "notification",
},
},
},
{
name: "tools/list",
url: "/",
body: mcp.JSONRPCRequest{
body: jsonrpc.JSONRPCRequest{
Jsonrpc: jsonrpcVersion,
Id: "tools-list",
Request: mcp.Request{
Request: jsonrpc.Request{
Method: "tools/list",
},
},
isErr: false,
want: map[string]any{
"jsonrpc": "2.0",
"id": "tools-list",
@@ -143,57 +113,14 @@ func TestMcpEndpoint(t *testing.T) {
},
},
},
{
name: "tools/list on tool1_only",
url: "/tool1_only",
body: mcp.JSONRPCRequest{
Jsonrpc: jsonrpcVersion,
Id: "tools-list-tool1",
Request: mcp.Request{
Method: "tools/list",
},
},
want: map[string]any{
"jsonrpc": "2.0",
"id": "tools-list-tool1",
"result": map[string]any{
"tools": []any{
map[string]any{
"name": "no_params",
"inputSchema": tool1InputSchema,
},
},
},
},
},
{
name: "tools/list on invalid tool set",
url: "/foo",
isErr: true,
body: mcp.JSONRPCRequest{
Jsonrpc: jsonrpcVersion,
Id: "tools-list-invalid-toolset",
Request: mcp.Request{
Method: "tools/list",
},
},
want: map[string]any{
"jsonrpc": "2.0",
"id": "tools-list-invalid-toolset",
"error": map[string]any{
"code": -32600.0,
"message": "toolset does not exist",
},
},
},
{
name: "missing method",
url: "/",
isErr: true,
body: mcp.JSONRPCRequest{
body: jsonrpc.JSONRPCRequest{
Jsonrpc: jsonrpcVersion,
Id: "missing-method",
Request: mcp.Request{},
Request: jsonrpc.Request{},
},
want: map[string]any{
"jsonrpc": "2.0",
@@ -204,34 +131,14 @@ func TestMcpEndpoint(t *testing.T) {
},
},
},
{
name: "invalid method",
url: "/",
isErr: true,
body: mcp.JSONRPCRequest{
Jsonrpc: jsonrpcVersion,
Id: "invalid-method",
Request: mcp.Request{
Method: "foo",
},
},
want: map[string]any{
"jsonrpc": "2.0",
"id": "invalid-method",
"error": map[string]any{
"code": -32601.0,
"message": "invalid method foo",
},
},
},
{
name: "invalid jsonrpc version",
url: "/",
isErr: true,
body: mcp.JSONRPCRequest{
body: jsonrpc.JSONRPCRequest{
Jsonrpc: "1.0",
Id: "invalid-jsonrpc-version",
Request: mcp.Request{
Request: jsonrpc.Request{
Method: "foo",
},
},
@@ -252,7 +159,7 @@ func TestMcpEndpoint(t *testing.T) {
t.Fatalf("unexpected error during marshaling of body")
}
resp, body, err := runRequest(ts, http.MethodPost, tc.url, bytes.NewBuffer(reqMarshal))
resp, body, err := runRequest(ts, http.MethodPost, tc.url, bytes.NewBuffer(reqMarshal), nil)
if err != nil {
t.Fatalf("unexpected error during request: %s", err)
}
@@ -275,6 +182,378 @@ func TestMcpEndpoint(t *testing.T) {
}
}
func runInitializeLifecycle(t *testing.T, ts *httptest.Server, protocolVersion string, initializeWant map[string]any, idHeader bool) string {
initializeRequestBody := map[string]any{
"jsonrpc": jsonrpcVersion,
"id": "mcp-initialize",
"method": "initialize",
"params": map[string]any{
"protocolVersion": protocolVersion,
},
}
reqMarshal, err := json.Marshal(initializeRequestBody)
if err != nil {
t.Fatalf("unexpected error during marshaling of body")
}
resp, body, err := runRequest(ts, http.MethodPost, "/", bytes.NewBuffer(reqMarshal), nil)
if err != nil {
t.Fatalf("unexpected error during request: %s", err)
}
if contentType := resp.Header.Get("Content-type"); contentType != "application/json" {
t.Fatalf("unexpected content-type header: want %s, got %s", "application/json", contentType)
}
sessionId := resp.Header.Get("Mcp-Session-Id")
if idHeader && sessionId == "" {
t.Fatalf("Mcp-Session-Id header is expected")
}
var got map[string]any
if err := json.Unmarshal(body, &got); err != nil {
t.Fatalf("unexpected error unmarshalling body: %s", err)
}
if !reflect.DeepEqual(got, initializeWant) {
t.Fatalf("unexpected response: got %+v, want %+v", got, initializeWant)
}
header := map[string]string{}
if sessionId != "" {
header["Mcp-Session-Id"] = sessionId
}
initializeNotificationBody := map[string]any{
"jsonrpc": jsonrpcVersion,
"method": "notifications/initialized",
}
notiMarshal, err := json.Marshal(initializeNotificationBody)
if err != nil {
t.Fatalf("unexpected error during marshaling of notifications body")
}
_, _, err = runRequest(ts, http.MethodPost, "/", bytes.NewBuffer(notiMarshal), header)
if err != nil {
t.Fatalf("unexpected error during request: %s", err)
}
return sessionId
}
func TestMcpEndpoint(t *testing.T) {
mockTools := []MockTool{tool1, tool2, tool3}
toolsMap, toolsets := setUpResources(t, mockTools)
r, shutdown := setUpServer(t, "mcp", toolsMap, toolsets)
defer shutdown()
ts := runServer(r, false)
defer ts.Close()
versTestCases := []struct {
name string
protocol string
idHeader bool
initWant map[string]any
}{
{
name: "verson 2024-11-05",
protocol: protocolVersion20241105,
idHeader: false,
initWant: map[string]any{
"jsonrpc": "2.0",
"id": "mcp-initialize",
"result": map[string]any{
"protocolVersion": "2024-11-05",
"capabilities": map[string]any{
"tools": map[string]any{"listChanged": false},
},
"serverInfo": map[string]any{"name": serverName, "version": fakeVersionString},
},
},
},
{
name: "verson 2025-03-26",
protocol: protocolVersion20250326,
idHeader: true,
initWant: map[string]any{
"jsonrpc": "2.0",
"id": "mcp-initialize",
"result": map[string]any{
"protocolVersion": "2025-03-26",
"capabilities": map[string]any{
"tools": map[string]any{"listChanged": false},
},
"serverInfo": map[string]any{"name": serverName, "version": fakeVersionString},
},
},
},
}
for _, vtc := range versTestCases {
t.Run(vtc.name, func(t *testing.T) {
sessionId := runInitializeLifecycle(t, ts, vtc.protocol, vtc.initWant, vtc.idHeader)
header := map[string]string{}
if sessionId != "" {
header["Mcp-Session-Id"] = sessionId
}
testCases := []struct {
name string
url string
isErr bool
body any
want map[string]any
}{
{
name: "basic notification",
url: "/",
body: jsonrpc.JSONRPCRequest{
Jsonrpc: jsonrpcVersion,
Request: jsonrpc.Request{
Method: "notification",
},
},
},
{
name: "tools/list",
url: "/",
body: jsonrpc.JSONRPCRequest{
Jsonrpc: jsonrpcVersion,
Id: "tools-list",
Request: jsonrpc.Request{
Method: "tools/list",
},
},
want: map[string]any{
"jsonrpc": "2.0",
"id": "tools-list",
"result": map[string]any{
"tools": []any{
map[string]any{
"name": "no_params",
"inputSchema": tool1InputSchema,
},
map[string]any{
"name": "some_params",
"inputSchema": tool2InputSchema,
},
map[string]any{
"name": "array_param",
"description": "some description",
"inputSchema": tool3InputSchema,
},
},
},
},
},
{
name: "tools/list on tool1_only",
url: "/tool1_only",
body: jsonrpc.JSONRPCRequest{
Jsonrpc: jsonrpcVersion,
Id: "tools-list-tool1",
Request: jsonrpc.Request{
Method: "tools/list",
},
},
want: map[string]any{
"jsonrpc": "2.0",
"id": "tools-list-tool1",
"result": map[string]any{
"tools": []any{
map[string]any{
"name": "no_params",
"inputSchema": tool1InputSchema,
},
},
},
},
},
{
name: "tools/list on invalid tool set",
url: "/foo",
isErr: true,
body: jsonrpc.JSONRPCRequest{
Jsonrpc: jsonrpcVersion,
Id: "tools-list-invalid-toolset",
Request: jsonrpc.Request{
Method: "tools/list",
},
},
want: map[string]any{
"jsonrpc": "2.0",
"id": "tools-list-invalid-toolset",
"error": map[string]any{
"code": -32600.0,
"message": "toolset does not exist",
},
},
},
{
name: "missing method",
url: "/",
isErr: true,
body: jsonrpc.JSONRPCRequest{
Jsonrpc: jsonrpcVersion,
Id: "missing-method",
Request: jsonrpc.Request{},
},
want: map[string]any{
"jsonrpc": "2.0",
"id": "missing-method",
"error": map[string]any{
"code": -32601.0,
"message": "method not found",
},
},
},
{
name: "invalid method",
url: "/",
isErr: true,
body: jsonrpc.JSONRPCRequest{
Jsonrpc: jsonrpcVersion,
Id: "invalid-method",
Request: jsonrpc.Request{
Method: "foo",
},
},
want: map[string]any{
"jsonrpc": "2.0",
"id": "invalid-method",
"error": map[string]any{
"code": -32601.0,
"message": "invalid method foo",
},
},
},
{
name: "invalid jsonrpc version",
url: "/",
isErr: true,
body: jsonrpc.JSONRPCRequest{
Jsonrpc: "1.0",
Id: "invalid-jsonrpc-version",
Request: jsonrpc.Request{
Method: "foo",
},
},
want: map[string]any{
"jsonrpc": "2.0",
"id": "invalid-jsonrpc-version",
"error": map[string]any{
"code": -32600.0,
"message": "invalid json-rpc version",
},
},
},
{
name: "batch requests",
url: "/",
isErr: true,
body: []any{
jsonrpc.JSONRPCRequest{
Jsonrpc: "1.0",
Id: "batch-requests1",
Request: jsonrpc.Request{
Method: "foo",
},
},
jsonrpc.JSONRPCRequest{
Jsonrpc: jsonrpcVersion,
Id: "batch-requests2",
Request: jsonrpc.Request{
Method: "tools/list",
},
},
},
want: map[string]any{
"jsonrpc": "2.0",
"error": map[string]any{
"code": -32600.0,
"message": "not supporting batch requests",
},
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
reqMarshal, err := json.Marshal(tc.body)
if err != nil {
t.Fatalf("unexpected error during marshaling of body")
}
if vtc.protocol == protocolVersion20250326 && len(header) == 0 {
t.Fatalf("header is missing")
}
resp, body, err := runRequest(ts, http.MethodPost, tc.url, bytes.NewBuffer(reqMarshal), header)
if err != nil {
t.Fatalf("unexpected error during request: %s", err)
}
// Notifications don't expect a response.
if tc.want != nil {
if contentType := resp.Header.Get("Content-type"); contentType != "application/json" {
t.Fatalf("unexpected content-type header: want %s, got %s", "application/json", contentType)
}
var got map[string]any
if err := json.Unmarshal(body, &got); err != nil {
t.Fatalf("unexpected error unmarshalling body: %s", err)
}
// for decode failure, a random uuid is generated in server
if tc.want["id"] == nil {
tc.want["id"] = got["id"]
}
if !reflect.DeepEqual(got, tc.want) {
t.Fatalf("unexpected response: got %+v, want %+v", got, tc.want)
}
}
})
}
})
}
}
func TestDeleteEndpoint(t *testing.T) {
toolsMap, toolsets := map[string]tools.Tool{}, map[string]tools.Toolset{}
r, shutdown := setUpServer(t, "mcp", toolsMap, toolsets)
defer shutdown()
ts := runServer(r, false)
defer ts.Close()
resp, _, err := runRequest(ts, http.MethodDelete, "/", nil, nil)
if resp.Status != "200 OK" {
t.Fatalf("unexpected status: %s", resp.Status)
}
if err != nil {
t.Fatalf("unexpected error during request: %s", err)
}
}
func TestGetEndpoint(t *testing.T) {
toolsMap, toolsets := map[string]tools.Tool{}, map[string]tools.Toolset{}
r, shutdown := setUpServer(t, "mcp", toolsMap, toolsets)
defer shutdown()
ts := runServer(r, false)
defer ts.Close()
resp, body, err := runRequest(ts, http.MethodGet, "/", nil, nil)
if resp.Status != "405 Method Not Allowed" {
t.Fatalf("unexpected status: %s", resp.Status)
}
var got map[string]any
if err := json.Unmarshal(body, &got); err != nil {
t.Fatalf("unexpected error unmarshalling body: %s", err)
}
want := "toolbox does not support streaming in streamable HTTP transport"
if got["error"] != want {
t.Fatalf("unexpected error message: %s", got["error"])
}
if err != nil {
t.Fatalf("unexpected error during request: %s", err)
}
}
func TestSseEndpoint(t *testing.T) {
r, shutdown := setUpServer(t, "mcp", nil, nil)
defer shutdown()
@@ -419,10 +698,7 @@ func TestStdioSession(t *testing.T) {
t.Fatalf("unable to create custom metrics: %s", err)
}
sseManager := &sseManager{
mu: sync.RWMutex{},
sseSessions: make(map[string]*sseSession),
}
sseManager := newSseManager(ctx)
server := &Server{version: fakeVersionString, logger: testLogger, instrumentation: instrumentation, sseManager: sseManager, tools: toolsMap, toolsets: toolsets}

View File

@@ -21,7 +21,6 @@ import (
"net"
"net/http"
"strconv"
"sync"
"time"
"github.com/go-chi/chi/v5"
@@ -206,10 +205,7 @@ func NewServer(ctx context.Context, cfg ServerConfig, l log.Logger) (*Server, er
addr := net.JoinHostPort(cfg.Address, strconv.Itoa(cfg.Port))
srv := &http.Server{Addr: addr, Handler: r}
sseManager := &sseManager{
mu: sync.RWMutex{},
sseSessions: make(map[string]*sseSession),
}
sseManager := newSseManager(ctx)
s := &Server{
version: cfg.Version,

View File

@@ -21,7 +21,7 @@ import (
"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/bigqueryexecutesql"
"github.com/googleapis/genai-toolbox/internal/tools/bigquery/bigqueryexecutesql"
)
func TestParseFromYamlBigQueryExecuteSql(t *testing.T) {

View File

@@ -21,7 +21,7 @@ import (
"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/bigquerygetdatasetinfo"
"github.com/googleapis/genai-toolbox/internal/tools/bigquery/bigquerygetdatasetinfo"
)
func TestParseFromYamlBigQueryGetDatasetInfo(t *testing.T) {

View File

@@ -21,7 +21,7 @@ import (
"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/bigquerygettableinfo"
"github.com/googleapis/genai-toolbox/internal/tools/bigquery/bigquerygettableinfo"
)
func TestParseFromYamlBigQueryGetTableInfo(t *testing.T) {

View File

@@ -21,7 +21,7 @@ import (
"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/bigquerylistdatasetids"
"github.com/googleapis/genai-toolbox/internal/tools/bigquery/bigquerylistdatasetids"
)
func TestParseFromYamlBigQueryListDatasetIds(t *testing.T) {

View File

@@ -21,7 +21,7 @@ import (
"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/bigquerylisttableids"
"github.com/googleapis/genai-toolbox/internal/tools/bigquery/bigquerylisttableids"
)
func TestParseFromYamlBigQueryListTableIds(t *testing.T) {

Some files were not shown because too many files have changed in this diff Show More