#
tokens: 49631/50000 16/807 files (page 20/47)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 20 of 47. Use http://codebase.md/googleapis/genai-toolbox?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .ci
│   ├── continuous.release.cloudbuild.yaml
│   ├── generate_release_table.sh
│   ├── integration.cloudbuild.yaml
│   ├── quickstart_test
│   │   ├── go.integration.cloudbuild.yaml
│   │   ├── js.integration.cloudbuild.yaml
│   │   ├── py.integration.cloudbuild.yaml
│   │   ├── run_go_tests.sh
│   │   ├── run_js_tests.sh
│   │   ├── run_py_tests.sh
│   │   └── setup_hotels_sample.sql
│   ├── test_with_coverage.sh
│   └── versioned.release.cloudbuild.yaml
├── .github
│   ├── auto-label.yaml
│   ├── blunderbuss.yml
│   ├── CODEOWNERS
│   ├── header-checker-lint.yml
│   ├── ISSUE_TEMPLATE
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   ├── feature_request.yml
│   │   └── question.yml
│   ├── label-sync.yml
│   ├── labels.yaml
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── release-please.yml
│   ├── renovate.json5
│   ├── sync-repo-settings.yaml
│   └── workflows
│       ├── cloud_build_failure_reporter.yml
│       ├── deploy_dev_docs.yaml
│       ├── deploy_previous_version_docs.yaml
│       ├── deploy_versioned_docs.yaml
│       ├── docs_deploy.yaml
│       ├── docs_preview_clean.yaml
│       ├── docs_preview_deploy.yaml
│       ├── lint.yaml
│       ├── schedule_reporter.yml
│       ├── sync-labels.yaml
│       └── tests.yaml
├── .gitignore
├── .gitmodules
├── .golangci.yaml
├── .hugo
│   ├── archetypes
│   │   └── default.md
│   ├── assets
│   │   ├── icons
│   │   │   └── logo.svg
│   │   └── scss
│   │       ├── _styles_project.scss
│   │       └── _variables_project.scss
│   ├── go.mod
│   ├── go.sum
│   ├── hugo.toml
│   ├── layouts
│   │   ├── _default
│   │   │   └── home.releases.releases
│   │   ├── index.llms-full.txt
│   │   ├── index.llms.txt
│   │   ├── partials
│   │   │   ├── hooks
│   │   │   │   └── head-end.html
│   │   │   ├── navbar-version-selector.html
│   │   │   ├── page-meta-links.html
│   │   │   └── td
│   │   │       └── render-heading.html
│   │   ├── robot.txt
│   │   └── shortcodes
│   │       ├── include.html
│   │       ├── ipynb.html
│   │       └── regionInclude.html
│   ├── package-lock.json
│   ├── package.json
│   └── static
│       ├── favicons
│       │   ├── android-chrome-192x192.png
│       │   ├── android-chrome-512x512.png
│       │   ├── apple-touch-icon.png
│       │   ├── favicon-16x16.png
│       │   ├── favicon-32x32.png
│       │   └── favicon.ico
│       └── js
│           └── w3.js
├── CHANGELOG.md
├── cmd
│   ├── options_test.go
│   ├── options.go
│   ├── root_test.go
│   ├── root.go
│   └── version.txt
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── DEVELOPER.md
├── Dockerfile
├── docs
│   └── en
│       ├── _index.md
│       ├── about
│       │   ├── _index.md
│       │   └── faq.md
│       ├── concepts
│       │   ├── _index.md
│       │   └── telemetry
│       │       ├── index.md
│       │       ├── telemetry_flow.png
│       │       └── telemetry_traces.png
│       ├── getting-started
│       │   ├── _index.md
│       │   ├── colab_quickstart.ipynb
│       │   ├── configure.md
│       │   ├── introduction
│       │   │   ├── _index.md
│       │   │   └── architecture.png
│       │   ├── local_quickstart_go.md
│       │   ├── local_quickstart_js.md
│       │   ├── local_quickstart.md
│       │   ├── mcp_quickstart
│       │   │   ├── _index.md
│       │   │   ├── inspector_tools.png
│       │   │   └── inspector.png
│       │   └── quickstart
│       │       ├── go
│       │       │   ├── genAI
│       │       │   │   ├── go.mod
│       │       │   │   ├── go.sum
│       │       │   │   └── quickstart.go
│       │       │   ├── genkit
│       │       │   │   ├── go.mod
│       │       │   │   ├── go.sum
│       │       │   │   └── quickstart.go
│       │       │   ├── langchain
│       │       │   │   ├── go.mod
│       │       │   │   ├── go.sum
│       │       │   │   └── quickstart.go
│       │       │   ├── openAI
│       │       │   │   ├── go.mod
│       │       │   │   ├── go.sum
│       │       │   │   └── quickstart.go
│       │       │   └── quickstart_test.go
│       │       ├── golden.txt
│       │       ├── js
│       │       │   ├── genAI
│       │       │   │   ├── package-lock.json
│       │       │   │   ├── package.json
│       │       │   │   └── quickstart.js
│       │       │   ├── genkit
│       │       │   │   ├── package-lock.json
│       │       │   │   ├── package.json
│       │       │   │   └── quickstart.js
│       │       │   ├── langchain
│       │       │   │   ├── package-lock.json
│       │       │   │   ├── package.json
│       │       │   │   └── quickstart.js
│       │       │   ├── llamaindex
│       │       │   │   ├── package-lock.json
│       │       │   │   ├── package.json
│       │       │   │   └── quickstart.js
│       │       │   └── quickstart.test.js
│       │       ├── python
│       │       │   ├── __init__.py
│       │       │   ├── adk
│       │       │   │   ├── quickstart.py
│       │       │   │   └── requirements.txt
│       │       │   ├── core
│       │       │   │   ├── quickstart.py
│       │       │   │   └── requirements.txt
│       │       │   ├── langchain
│       │       │   │   ├── quickstart.py
│       │       │   │   └── requirements.txt
│       │       │   ├── llamaindex
│       │       │   │   ├── quickstart.py
│       │       │   │   └── requirements.txt
│       │       │   └── quickstart_test.py
│       │       └── shared
│       │           ├── cloud_setup.md
│       │           ├── configure_toolbox.md
│       │           └── database_setup.md
│       ├── how-to
│       │   ├── _index.md
│       │   ├── connect_via_geminicli.md
│       │   ├── connect_via_mcp.md
│       │   ├── connect-ide
│       │   │   ├── _index.md
│       │   │   ├── alloydb_pg_admin_mcp.md
│       │   │   ├── alloydb_pg_mcp.md
│       │   │   ├── bigquery_mcp.md
│       │   │   ├── cloud_sql_mssql_admin_mcp.md
│       │   │   ├── cloud_sql_mssql_mcp.md
│       │   │   ├── cloud_sql_mysql_admin_mcp.md
│       │   │   ├── cloud_sql_mysql_mcp.md
│       │   │   ├── cloud_sql_pg_admin_mcp.md
│       │   │   ├── cloud_sql_pg_mcp.md
│       │   │   ├── firestore_mcp.md
│       │   │   ├── looker_mcp.md
│       │   │   ├── mssql_mcp.md
│       │   │   ├── mysql_mcp.md
│       │   │   ├── neo4j_mcp.md
│       │   │   ├── postgres_mcp.md
│       │   │   ├── spanner_mcp.md
│       │   │   └── sqlite_mcp.md
│       │   ├── deploy_docker.md
│       │   ├── deploy_gke.md
│       │   ├── deploy_toolbox.md
│       │   ├── export_telemetry.md
│       │   └── toolbox-ui
│       │       ├── edit-headers.gif
│       │       ├── edit-headers.png
│       │       ├── index.md
│       │       ├── optional-param-checked.png
│       │       ├── optional-param-unchecked.png
│       │       ├── run-tool.gif
│       │       ├── tools.png
│       │       └── toolsets.png
│       ├── reference
│       │   ├── _index.md
│       │   ├── cli.md
│       │   └── prebuilt-tools.md
│       ├── resources
│       │   ├── _index.md
│       │   ├── authServices
│       │   │   ├── _index.md
│       │   │   └── google.md
│       │   ├── sources
│       │   │   ├── _index.md
│       │   │   ├── alloydb-admin.md
│       │   │   ├── alloydb-pg.md
│       │   │   ├── bigquery.md
│       │   │   ├── bigtable.md
│       │   │   ├── cassandra.md
│       │   │   ├── clickhouse.md
│       │   │   ├── cloud-monitoring.md
│       │   │   ├── cloud-sql-admin.md
│       │   │   ├── cloud-sql-mssql.md
│       │   │   ├── cloud-sql-mysql.md
│       │   │   ├── cloud-sql-pg.md
│       │   │   ├── couchbase.md
│       │   │   ├── dataplex.md
│       │   │   ├── dgraph.md
│       │   │   ├── firebird.md
│       │   │   ├── firestore.md
│       │   │   ├── http.md
│       │   │   ├── looker.md
│       │   │   ├── mongodb.md
│       │   │   ├── mssql.md
│       │   │   ├── mysql.md
│       │   │   ├── neo4j.md
│       │   │   ├── oceanbase.md
│       │   │   ├── oracle.md
│       │   │   ├── postgres.md
│       │   │   ├── redis.md
│       │   │   ├── spanner.md
│       │   │   ├── sqlite.md
│       │   │   ├── tidb.md
│       │   │   ├── trino.md
│       │   │   ├── valkey.md
│       │   │   └── yugabytedb.md
│       │   └── tools
│       │       ├── _index.md
│       │       ├── alloydb
│       │       │   ├── _index.md
│       │       │   ├── alloydb-create-cluster.md
│       │       │   ├── alloydb-create-instance.md
│       │       │   ├── alloydb-create-user.md
│       │       │   ├── alloydb-get-cluster.md
│       │       │   ├── alloydb-get-instance.md
│       │       │   ├── alloydb-get-user.md
│       │       │   ├── alloydb-list-clusters.md
│       │       │   ├── alloydb-list-instances.md
│       │       │   ├── alloydb-list-users.md
│       │       │   └── alloydb-wait-for-operation.md
│       │       ├── alloydbainl
│       │       │   ├── _index.md
│       │       │   └── alloydb-ai-nl.md
│       │       ├── bigquery
│       │       │   ├── _index.md
│       │       │   ├── bigquery-analyze-contribution.md
│       │       │   ├── bigquery-conversational-analytics.md
│       │       │   ├── bigquery-execute-sql.md
│       │       │   ├── bigquery-forecast.md
│       │       │   ├── bigquery-get-dataset-info.md
│       │       │   ├── bigquery-get-table-info.md
│       │       │   ├── bigquery-list-dataset-ids.md
│       │       │   ├── bigquery-list-table-ids.md
│       │       │   ├── bigquery-search-catalog.md
│       │       │   └── bigquery-sql.md
│       │       ├── bigtable
│       │       │   ├── _index.md
│       │       │   └── bigtable-sql.md
│       │       ├── cassandra
│       │       │   ├── _index.md
│       │       │   └── cassandra-cql.md
│       │       ├── clickhouse
│       │       │   ├── _index.md
│       │       │   ├── clickhouse-execute-sql.md
│       │       │   ├── clickhouse-list-databases.md
│       │       │   ├── clickhouse-list-tables.md
│       │       │   └── clickhouse-sql.md
│       │       ├── cloudmonitoring
│       │       │   ├── _index.md
│       │       │   └── cloud-monitoring-query-prometheus.md
│       │       ├── cloudsql
│       │       │   ├── _index.md
│       │       │   ├── cloudsqlcreatedatabase.md
│       │       │   ├── cloudsqlcreateusers.md
│       │       │   ├── cloudsqlgetinstances.md
│       │       │   ├── cloudsqllistdatabases.md
│       │       │   ├── cloudsqllistinstances.md
│       │       │   ├── cloudsqlmssqlcreateinstance.md
│       │       │   ├── cloudsqlmysqlcreateinstance.md
│       │       │   ├── cloudsqlpgcreateinstances.md
│       │       │   └── cloudsqlwaitforoperation.md
│       │       ├── couchbase
│       │       │   ├── _index.md
│       │       │   └── couchbase-sql.md
│       │       ├── dataform
│       │       │   ├── _index.md
│       │       │   └── dataform-compile-local.md
│       │       ├── dataplex
│       │       │   ├── _index.md
│       │       │   ├── dataplex-lookup-entry.md
│       │       │   ├── dataplex-search-aspect-types.md
│       │       │   └── dataplex-search-entries.md
│       │       ├── dgraph
│       │       │   ├── _index.md
│       │       │   └── dgraph-dql.md
│       │       ├── firebird
│       │       │   ├── _index.md
│       │       │   ├── firebird-execute-sql.md
│       │       │   └── firebird-sql.md
│       │       ├── firestore
│       │       │   ├── _index.md
│       │       │   ├── firestore-add-documents.md
│       │       │   ├── firestore-delete-documents.md
│       │       │   ├── firestore-get-documents.md
│       │       │   ├── firestore-get-rules.md
│       │       │   ├── firestore-list-collections.md
│       │       │   ├── firestore-query-collection.md
│       │       │   ├── firestore-query.md
│       │       │   ├── firestore-update-document.md
│       │       │   └── firestore-validate-rules.md
│       │       ├── http
│       │       │   ├── _index.md
│       │       │   └── http.md
│       │       ├── looker
│       │       │   ├── _index.md
│       │       │   ├── looker-add-dashboard-element.md
│       │       │   ├── looker-conversational-analytics.md
│       │       │   ├── looker-create-project-file.md
│       │       │   ├── looker-delete-project-file.md
│       │       │   ├── looker-dev-mode.md
│       │       │   ├── looker-get-dashboards.md
│       │       │   ├── looker-get-dimensions.md
│       │       │   ├── looker-get-explores.md
│       │       │   ├── looker-get-filters.md
│       │       │   ├── looker-get-looks.md
│       │       │   ├── looker-get-measures.md
│       │       │   ├── looker-get-models.md
│       │       │   ├── looker-get-parameters.md
│       │       │   ├── looker-get-project-file.md
│       │       │   ├── looker-get-project-files.md
│       │       │   ├── looker-get-projects.md
│       │       │   ├── looker-health-analyze.md
│       │       │   ├── looker-health-pulse.md
│       │       │   ├── looker-health-vacuum.md
│       │       │   ├── looker-make-dashboard.md
│       │       │   ├── looker-make-look.md
│       │       │   ├── looker-query-sql.md
│       │       │   ├── looker-query-url.md
│       │       │   ├── looker-query.md
│       │       │   ├── looker-run-look.md
│       │       │   └── looker-update-project-file.md
│       │       ├── mongodb
│       │       │   ├── _index.md
│       │       │   ├── mongodb-aggregate.md
│       │       │   ├── mongodb-delete-many.md
│       │       │   ├── mongodb-delete-one.md
│       │       │   ├── mongodb-find-one.md
│       │       │   ├── mongodb-find.md
│       │       │   ├── mongodb-insert-many.md
│       │       │   ├── mongodb-insert-one.md
│       │       │   ├── mongodb-update-many.md
│       │       │   └── mongodb-update-one.md
│       │       ├── mssql
│       │       │   ├── _index.md
│       │       │   ├── mssql-execute-sql.md
│       │       │   ├── mssql-list-tables.md
│       │       │   └── mssql-sql.md
│       │       ├── mysql
│       │       │   ├── _index.md
│       │       │   ├── mysql-execute-sql.md
│       │       │   ├── mysql-list-active-queries.md
│       │       │   ├── mysql-list-table-fragmentation.md
│       │       │   ├── mysql-list-tables-missing-unique-indexes.md
│       │       │   ├── mysql-list-tables.md
│       │       │   └── mysql-sql.md
│       │       ├── neo4j
│       │       │   ├── _index.md
│       │       │   ├── neo4j-cypher.md
│       │       │   ├── neo4j-execute-cypher.md
│       │       │   └── neo4j-schema.md
│       │       ├── oceanbase
│       │       │   ├── _index.md
│       │       │   ├── oceanbase-execute-sql.md
│       │       │   └── oceanbase-sql.md
│       │       ├── oracle
│       │       │   ├── _index.md
│       │       │   ├── oracle-execute-sql.md
│       │       │   └── oracle-sql.md
│       │       ├── postgres
│       │       │   ├── _index.md
│       │       │   ├── postgres-execute-sql.md
│       │       │   ├── postgres-list-active-queries.md
│       │       │   ├── postgres-list-available-extensions.md
│       │       │   ├── postgres-list-installed-extensions.md
│       │       │   ├── postgres-list-tables.md
│       │       │   └── postgres-sql.md
│       │       ├── redis
│       │       │   ├── _index.md
│       │       │   └── redis.md
│       │       ├── spanner
│       │       │   ├── _index.md
│       │       │   ├── spanner-execute-sql.md
│       │       │   ├── spanner-list-tables.md
│       │       │   └── spanner-sql.md
│       │       ├── sqlite
│       │       │   ├── _index.md
│       │       │   ├── sqlite-execute-sql.md
│       │       │   └── sqlite-sql.md
│       │       ├── tidb
│       │       │   ├── _index.md
│       │       │   ├── tidb-execute-sql.md
│       │       │   └── tidb-sql.md
│       │       ├── trino
│       │       │   ├── _index.md
│       │       │   ├── trino-execute-sql.md
│       │       │   └── trino-sql.md
│       │       ├── utility
│       │       │   ├── _index.md
│       │       │   └── wait.md
│       │       ├── valkey
│       │       │   ├── _index.md
│       │       │   └── valkey.md
│       │       └── yuagbytedb
│       │           ├── _index.md
│       │           └── yugabytedb-sql.md
│       ├── samples
│       │   ├── _index.md
│       │   ├── alloydb
│       │   │   ├── _index.md
│       │   │   ├── ai-nl
│       │   │   │   ├── alloydb_ai_nl.ipynb
│       │   │   │   └── index.md
│       │   │   └── mcp_quickstart.md
│       │   ├── bigquery
│       │   │   ├── _index.md
│       │   │   ├── colab_quickstart_bigquery.ipynb
│       │   │   ├── local_quickstart.md
│       │   │   └── mcp_quickstart
│       │   │       ├── _index.md
│       │   │       ├── inspector_tools.png
│       │   │       └── inspector.png
│       │   └── looker
│       │       ├── _index.md
│       │       ├── looker_gemini_oauth
│       │       │   ├── _index.md
│       │       │   ├── authenticated.png
│       │       │   ├── authorize.png
│       │       │   └── registration.png
│       │       ├── looker_gemini.md
│       │       └── looker_mcp_inspector
│       │           ├── _index.md
│       │           ├── inspector_tools.png
│       │           └── inspector.png
│       └── sdks
│           ├── _index.md
│           ├── go-sdk.md
│           ├── js-sdk.md
│           └── python-sdk.md
├── gemini-extension.json
├── go.mod
├── go.sum
├── internal
│   ├── auth
│   │   ├── auth.go
│   │   └── google
│   │       └── google.go
│   ├── log
│   │   ├── handler.go
│   │   ├── log_test.go
│   │   ├── log.go
│   │   └── logger.go
│   ├── prebuiltconfigs
│   │   ├── prebuiltconfigs_test.go
│   │   ├── prebuiltconfigs.go
│   │   └── tools
│   │       ├── alloydb-postgres-admin.yaml
│   │       ├── alloydb-postgres-observability.yaml
│   │       ├── alloydb-postgres.yaml
│   │       ├── bigquery.yaml
│   │       ├── clickhouse.yaml
│   │       ├── cloud-sql-mssql-admin.yaml
│   │       ├── cloud-sql-mssql-observability.yaml
│   │       ├── cloud-sql-mssql.yaml
│   │       ├── cloud-sql-mysql-admin.yaml
│   │       ├── cloud-sql-mysql-observability.yaml
│   │       ├── cloud-sql-mysql.yaml
│   │       ├── cloud-sql-postgres-admin.yaml
│   │       ├── cloud-sql-postgres-observability.yaml
│   │       ├── cloud-sql-postgres.yaml
│   │       ├── dataplex.yaml
│   │       ├── firestore.yaml
│   │       ├── looker-conversational-analytics.yaml
│   │       ├── looker.yaml
│   │       ├── mssql.yaml
│   │       ├── mysql.yaml
│   │       ├── neo4j.yaml
│   │       ├── oceanbase.yaml
│   │       ├── postgres.yaml
│   │       ├── spanner-postgres.yaml
│   │       ├── spanner.yaml
│   │       └── sqlite.yaml
│   ├── server
│   │   ├── api_test.go
│   │   ├── api.go
│   │   ├── common_test.go
│   │   ├── config.go
│   │   ├── mcp
│   │   │   ├── jsonrpc
│   │   │   │   ├── jsonrpc_test.go
│   │   │   │   └── jsonrpc.go
│   │   │   ├── mcp.go
│   │   │   ├── util
│   │   │   │   └── lifecycle.go
│   │   │   ├── v20241105
│   │   │   │   ├── method.go
│   │   │   │   └── types.go
│   │   │   ├── v20250326
│   │   │   │   ├── method.go
│   │   │   │   └── types.go
│   │   │   └── v20250618
│   │   │       ├── method.go
│   │   │       └── types.go
│   │   ├── mcp_test.go
│   │   ├── mcp.go
│   │   ├── server_test.go
│   │   ├── server.go
│   │   ├── static
│   │   │   ├── assets
│   │   │   │   └── mcptoolboxlogo.png
│   │   │   ├── css
│   │   │   │   └── style.css
│   │   │   ├── index.html
│   │   │   ├── js
│   │   │   │   ├── auth.js
│   │   │   │   ├── loadTools.js
│   │   │   │   ├── mainContent.js
│   │   │   │   ├── navbar.js
│   │   │   │   ├── runTool.js
│   │   │   │   ├── toolDisplay.js
│   │   │   │   ├── tools.js
│   │   │   │   └── toolsets.js
│   │   │   ├── tools.html
│   │   │   └── toolsets.html
│   │   ├── web_test.go
│   │   └── web.go
│   ├── sources
│   │   ├── alloydbadmin
│   │   │   ├── alloydbadmin_test.go
│   │   │   └── alloydbadmin.go
│   │   ├── alloydbpg
│   │   │   ├── alloydb_pg_test.go
│   │   │   └── alloydb_pg.go
│   │   ├── bigquery
│   │   │   ├── bigquery_test.go
│   │   │   └── bigquery.go
│   │   ├── bigtable
│   │   │   ├── bigtable_test.go
│   │   │   └── bigtable.go
│   │   ├── cassandra
│   │   │   ├── cassandra_test.go
│   │   │   └── cassandra.go
│   │   ├── clickhouse
│   │   │   ├── clickhouse_test.go
│   │   │   └── clickhouse.go
│   │   ├── cloudmonitoring
│   │   │   ├── cloud_monitoring_test.go
│   │   │   └── cloud_monitoring.go
│   │   ├── cloudsqladmin
│   │   │   ├── cloud_sql_admin_test.go
│   │   │   └── cloud_sql_admin.go
│   │   ├── cloudsqlmssql
│   │   │   ├── cloud_sql_mssql_test.go
│   │   │   └── cloud_sql_mssql.go
│   │   ├── cloudsqlmysql
│   │   │   ├── cloud_sql_mysql_test.go
│   │   │   └── cloud_sql_mysql.go
│   │   ├── cloudsqlpg
│   │   │   ├── cloud_sql_pg_test.go
│   │   │   └── cloud_sql_pg.go
│   │   ├── couchbase
│   │   │   ├── couchbase_test.go
│   │   │   └── couchbase.go
│   │   ├── dataplex
│   │   │   ├── dataplex_test.go
│   │   │   └── dataplex.go
│   │   ├── dgraph
│   │   │   ├── dgraph_test.go
│   │   │   └── dgraph.go
│   │   ├── dialect.go
│   │   ├── firebird
│   │   │   ├── firebird_test.go
│   │   │   └── firebird.go
│   │   ├── firestore
│   │   │   ├── firestore_test.go
│   │   │   └── firestore.go
│   │   ├── http
│   │   │   ├── http_test.go
│   │   │   └── http.go
│   │   ├── ip_type.go
│   │   ├── looker
│   │   │   ├── looker_test.go
│   │   │   └── looker.go
│   │   ├── mongodb
│   │   │   ├── mongodb_test.go
│   │   │   └── mongodb.go
│   │   ├── mssql
│   │   │   ├── mssql_test.go
│   │   │   └── mssql.go
│   │   ├── mysql
│   │   │   ├── mysql_test.go
│   │   │   └── mysql.go
│   │   ├── neo4j
│   │   │   ├── neo4j_test.go
│   │   │   └── neo4j.go
│   │   ├── oceanbase
│   │   │   ├── oceanbase_test.go
│   │   │   └── oceanbase.go
│   │   ├── oracle
│   │   │   └── oracle.go
│   │   ├── postgres
│   │   │   ├── postgres_test.go
│   │   │   └── postgres.go
│   │   ├── redis
│   │   │   ├── redis_test.go
│   │   │   └── redis.go
│   │   ├── sources.go
│   │   ├── spanner
│   │   │   ├── spanner_test.go
│   │   │   └── spanner.go
│   │   ├── sqlite
│   │   │   ├── sqlite_test.go
│   │   │   └── sqlite.go
│   │   ├── tidb
│   │   │   ├── tidb_test.go
│   │   │   └── tidb.go
│   │   ├── trino
│   │   │   ├── trino_test.go
│   │   │   └── trino.go
│   │   ├── util.go
│   │   ├── valkey
│   │   │   ├── valkey_test.go
│   │   │   └── valkey.go
│   │   └── yugabytedb
│   │       ├── yugabytedb_test.go
│   │       └── yugabytedb.go
│   ├── telemetry
│   │   ├── instrumentation.go
│   │   └── telemetry.go
│   ├── testutils
│   │   └── testutils.go
│   ├── tools
│   │   ├── alloydb
│   │   │   ├── alloydbcreatecluster
│   │   │   │   ├── alloydbcreatecluster_test.go
│   │   │   │   └── alloydbcreatecluster.go
│   │   │   ├── alloydbcreateinstance
│   │   │   │   ├── alloydbcreateinstance_test.go
│   │   │   │   └── alloydbcreateinstance.go
│   │   │   ├── alloydbcreateuser
│   │   │   │   ├── alloydbcreateuser_test.go
│   │   │   │   └── alloydbcreateuser.go
│   │   │   ├── alloydbgetcluster
│   │   │   │   ├── alloydbgetcluster_test.go
│   │   │   │   └── alloydbgetcluster.go
│   │   │   ├── alloydbgetinstance
│   │   │   │   ├── alloydbgetinstance_test.go
│   │   │   │   └── alloydbgetinstance.go
│   │   │   ├── alloydbgetuser
│   │   │   │   ├── alloydbgetuser_test.go
│   │   │   │   └── alloydbgetuser.go
│   │   │   ├── alloydblistclusters
│   │   │   │   ├── alloydblistclusters_test.go
│   │   │   │   └── alloydblistclusters.go
│   │   │   ├── alloydblistinstances
│   │   │   │   ├── alloydblistinstances_test.go
│   │   │   │   └── alloydblistinstances.go
│   │   │   ├── alloydblistusers
│   │   │   │   ├── alloydblistusers_test.go
│   │   │   │   └── alloydblistusers.go
│   │   │   └── alloydbwaitforoperation
│   │   │       ├── alloydbwaitforoperation_test.go
│   │   │       └── alloydbwaitforoperation.go
│   │   ├── alloydbainl
│   │   │   ├── alloydbainl_test.go
│   │   │   └── alloydbainl.go
│   │   ├── bigquery
│   │   │   ├── bigqueryanalyzecontribution
│   │   │   │   ├── bigqueryanalyzecontribution_test.go
│   │   │   │   └── bigqueryanalyzecontribution.go
│   │   │   ├── bigquerycommon
│   │   │   │   ├── table_name_parser_test.go
│   │   │   │   ├── table_name_parser.go
│   │   │   │   └── util.go
│   │   │   ├── bigqueryconversationalanalytics
│   │   │   │   ├── bigqueryconversationalanalytics_test.go
│   │   │   │   └── bigqueryconversationalanalytics.go
│   │   │   ├── bigqueryexecutesql
│   │   │   │   ├── bigqueryexecutesql_test.go
│   │   │   │   └── bigqueryexecutesql.go
│   │   │   ├── bigqueryforecast
│   │   │   │   ├── bigqueryforecast_test.go
│   │   │   │   └── bigqueryforecast.go
│   │   │   ├── bigquerygetdatasetinfo
│   │   │   │   ├── bigquerygetdatasetinfo_test.go
│   │   │   │   └── bigquerygetdatasetinfo.go
│   │   │   ├── bigquerygettableinfo
│   │   │   │   ├── bigquerygettableinfo_test.go
│   │   │   │   └── bigquerygettableinfo.go
│   │   │   ├── bigquerylistdatasetids
│   │   │   │   ├── bigquerylistdatasetids_test.go
│   │   │   │   └── bigquerylistdatasetids.go
│   │   │   ├── bigquerylisttableids
│   │   │   │   ├── bigquerylisttableids_test.go
│   │   │   │   └── bigquerylisttableids.go
│   │   │   ├── bigquerysearchcatalog
│   │   │   │   ├── bigquerysearchcatalog_test.go
│   │   │   │   └── bigquerysearchcatalog.go
│   │   │   └── bigquerysql
│   │   │       ├── bigquerysql_test.go
│   │   │       └── bigquerysql.go
│   │   ├── bigtable
│   │   │   ├── bigtable_test.go
│   │   │   └── bigtable.go
│   │   ├── cassandra
│   │   │   └── cassandracql
│   │   │       ├── cassandracql_test.go
│   │   │       └── cassandracql.go
│   │   ├── clickhouse
│   │   │   ├── clickhouseexecutesql
│   │   │   │   ├── clickhouseexecutesql_test.go
│   │   │   │   └── clickhouseexecutesql.go
│   │   │   ├── clickhouselistdatabases
│   │   │   │   ├── clickhouselistdatabases_test.go
│   │   │   │   └── clickhouselistdatabases.go
│   │   │   ├── clickhouselisttables
│   │   │   │   ├── clickhouselisttables_test.go
│   │   │   │   └── clickhouselisttables.go
│   │   │   └── clickhousesql
│   │   │       ├── clickhousesql_test.go
│   │   │       └── clickhousesql.go
│   │   ├── cloudmonitoring
│   │   │   ├── cloudmonitoring_test.go
│   │   │   └── cloudmonitoring.go
│   │   ├── cloudsql
│   │   │   ├── cloudsqlcreatedatabase
│   │   │   │   ├── cloudsqlcreatedatabase_test.go
│   │   │   │   └── cloudsqlcreatedatabase.go
│   │   │   ├── cloudsqlcreateusers
│   │   │   │   ├── cloudsqlcreateusers_test.go
│   │   │   │   └── cloudsqlcreateusers.go
│   │   │   ├── cloudsqlgetinstances
│   │   │   │   ├── cloudsqlgetinstances_test.go
│   │   │   │   └── cloudsqlgetinstances.go
│   │   │   ├── cloudsqllistdatabases
│   │   │   │   ├── cloudsqllistdatabases_test.go
│   │   │   │   └── cloudsqllistdatabases.go
│   │   │   ├── cloudsqllistinstances
│   │   │   │   ├── cloudsqllistinstances_test.go
│   │   │   │   └── cloudsqllistinstances.go
│   │   │   └── cloudsqlwaitforoperation
│   │   │       ├── cloudsqlwaitforoperation_test.go
│   │   │       └── cloudsqlwaitforoperation.go
│   │   ├── cloudsqlmssql
│   │   │   └── cloudsqlmssqlcreateinstance
│   │   │       ├── cloudsqlmssqlcreateinstance_test.go
│   │   │       └── cloudsqlmssqlcreateinstance.go
│   │   ├── cloudsqlmysql
│   │   │   └── cloudsqlmysqlcreateinstance
│   │   │       ├── cloudsqlmysqlcreateinstance_test.go
│   │   │       └── cloudsqlmysqlcreateinstance.go
│   │   ├── cloudsqlpg
│   │   │   └── cloudsqlpgcreateinstances
│   │   │       ├── cloudsqlpgcreateinstances_test.go
│   │   │       └── cloudsqlpgcreateinstances.go
│   │   ├── common_test.go
│   │   ├── common.go
│   │   ├── couchbase
│   │   │   ├── couchbase_test.go
│   │   │   └── couchbase.go
│   │   ├── dataform
│   │   │   └── dataformcompilelocal
│   │   │       ├── dataformcompilelocal_test.go
│   │   │       └── dataformcompilelocal.go
│   │   ├── dataplex
│   │   │   ├── dataplexlookupentry
│   │   │   │   ├── dataplexlookupentry_test.go
│   │   │   │   └── dataplexlookupentry.go
│   │   │   ├── dataplexsearchaspecttypes
│   │   │   │   ├── dataplexsearchaspecttypes_test.go
│   │   │   │   └── dataplexsearchaspecttypes.go
│   │   │   └── dataplexsearchentries
│   │   │       ├── dataplexsearchentries_test.go
│   │   │       └── dataplexsearchentries.go
│   │   ├── dgraph
│   │   │   ├── dgraph_test.go
│   │   │   └── dgraph.go
│   │   ├── firebird
│   │   │   ├── firebirdexecutesql
│   │   │   │   ├── firebirdexecutesql_test.go
│   │   │   │   └── firebirdexecutesql.go
│   │   │   └── firebirdsql
│   │   │       ├── firebirdsql_test.go
│   │   │       └── firebirdsql.go
│   │   ├── firestore
│   │   │   ├── firestoreadddocuments
│   │   │   │   ├── firestoreadddocuments_test.go
│   │   │   │   └── firestoreadddocuments.go
│   │   │   ├── firestoredeletedocuments
│   │   │   │   ├── firestoredeletedocuments_test.go
│   │   │   │   └── firestoredeletedocuments.go
│   │   │   ├── firestoregetdocuments
│   │   │   │   ├── firestoregetdocuments_test.go
│   │   │   │   └── firestoregetdocuments.go
│   │   │   ├── firestoregetrules
│   │   │   │   ├── firestoregetrules_test.go
│   │   │   │   └── firestoregetrules.go
│   │   │   ├── firestorelistcollections
│   │   │   │   ├── firestorelistcollections_test.go
│   │   │   │   └── firestorelistcollections.go
│   │   │   ├── firestorequery
│   │   │   │   ├── firestorequery_test.go
│   │   │   │   └── firestorequery.go
│   │   │   ├── firestorequerycollection
│   │   │   │   ├── firestorequerycollection_test.go
│   │   │   │   └── firestorequerycollection.go
│   │   │   ├── firestoreupdatedocument
│   │   │   │   ├── firestoreupdatedocument_test.go
│   │   │   │   └── firestoreupdatedocument.go
│   │   │   ├── firestorevalidaterules
│   │   │   │   ├── firestorevalidaterules_test.go
│   │   │   │   └── firestorevalidaterules.go
│   │   │   └── util
│   │   │       ├── converter_test.go
│   │   │       ├── converter.go
│   │   │       ├── validator_test.go
│   │   │       └── validator.go
│   │   ├── http
│   │   │   ├── http_test.go
│   │   │   └── http.go
│   │   ├── http_method.go
│   │   ├── looker
│   │   │   ├── lookeradddashboardelement
│   │   │   │   ├── lookeradddashboardelement_test.go
│   │   │   │   └── lookeradddashboardelement.go
│   │   │   ├── lookercommon
│   │   │   │   ├── lookercommon_test.go
│   │   │   │   └── lookercommon.go
│   │   │   ├── lookerconversationalanalytics
│   │   │   │   ├── lookerconversationalanalytics_test.go
│   │   │   │   └── lookerconversationalanalytics.go
│   │   │   ├── lookercreateprojectfile
│   │   │   │   ├── lookercreateprojectfile_test.go
│   │   │   │   └── lookercreateprojectfile.go
│   │   │   ├── lookerdeleteprojectfile
│   │   │   │   ├── lookerdeleteprojectfile_test.go
│   │   │   │   └── lookerdeleteprojectfile.go
│   │   │   ├── lookerdevmode
│   │   │   │   ├── lookerdevmode_test.go
│   │   │   │   └── lookerdevmode.go
│   │   │   ├── lookergetdashboards
│   │   │   │   ├── lookergetdashboards_test.go
│   │   │   │   └── lookergetdashboards.go
│   │   │   ├── lookergetdimensions
│   │   │   │   ├── lookergetdimensions_test.go
│   │   │   │   └── lookergetdimensions.go
│   │   │   ├── lookergetexplores
│   │   │   │   ├── lookergetexplores_test.go
│   │   │   │   └── lookergetexplores.go
│   │   │   ├── lookergetfilters
│   │   │   │   ├── lookergetfilters_test.go
│   │   │   │   └── lookergetfilters.go
│   │   │   ├── lookergetlooks
│   │   │   │   ├── lookergetlooks_test.go
│   │   │   │   └── lookergetlooks.go
│   │   │   ├── lookergetmeasures
│   │   │   │   ├── lookergetmeasures_test.go
│   │   │   │   └── lookergetmeasures.go
│   │   │   ├── lookergetmodels
│   │   │   │   ├── lookergetmodels_test.go
│   │   │   │   └── lookergetmodels.go
│   │   │   ├── lookergetparameters
│   │   │   │   ├── lookergetparameters_test.go
│   │   │   │   └── lookergetparameters.go
│   │   │   ├── lookergetprojectfile
│   │   │   │   ├── lookergetprojectfile_test.go
│   │   │   │   └── lookergetprojectfile.go
│   │   │   ├── lookergetprojectfiles
│   │   │   │   ├── lookergetprojectfiles_test.go
│   │   │   │   └── lookergetprojectfiles.go
│   │   │   ├── lookergetprojects
│   │   │   │   ├── lookergetprojects_test.go
│   │   │   │   └── lookergetprojects.go
│   │   │   ├── lookerhealthanalyze
│   │   │   │   ├── lookerhealthanalyze_test.go
│   │   │   │   └── lookerhealthanalyze.go
│   │   │   ├── lookerhealthpulse
│   │   │   │   ├── lookerhealthpulse_test.go
│   │   │   │   └── lookerhealthpulse.go
│   │   │   ├── lookerhealthvacuum
│   │   │   │   ├── lookerhealthvacuum_test.go
│   │   │   │   └── lookerhealthvacuum.go
│   │   │   ├── lookermakedashboard
│   │   │   │   ├── lookermakedashboard_test.go
│   │   │   │   └── lookermakedashboard.go
│   │   │   ├── lookermakelook
│   │   │   │   ├── lookermakelook_test.go
│   │   │   │   └── lookermakelook.go
│   │   │   ├── lookerquery
│   │   │   │   ├── lookerquery_test.go
│   │   │   │   └── lookerquery.go
│   │   │   ├── lookerquerysql
│   │   │   │   ├── lookerquerysql_test.go
│   │   │   │   └── lookerquerysql.go
│   │   │   ├── lookerqueryurl
│   │   │   │   ├── lookerqueryurl_test.go
│   │   │   │   └── lookerqueryurl.go
│   │   │   ├── lookerrunlook
│   │   │   │   ├── lookerrunlook_test.go
│   │   │   │   └── lookerrunlook.go
│   │   │   └── lookerupdateprojectfile
│   │   │       ├── lookerupdateprojectfile_test.go
│   │   │       └── lookerupdateprojectfile.go
│   │   ├── mongodb
│   │   │   ├── mongodbaggregate
│   │   │   │   ├── mongodbaggregate_test.go
│   │   │   │   └── mongodbaggregate.go
│   │   │   ├── mongodbdeletemany
│   │   │   │   ├── mongodbdeletemany_test.go
│   │   │   │   └── mongodbdeletemany.go
│   │   │   ├── mongodbdeleteone
│   │   │   │   ├── mongodbdeleteone_test.go
│   │   │   │   └── mongodbdeleteone.go
│   │   │   ├── mongodbfind
│   │   │   │   ├── mongodbfind_test.go
│   │   │   │   └── mongodbfind.go
│   │   │   ├── mongodbfindone
│   │   │   │   ├── mongodbfindone_test.go
│   │   │   │   └── mongodbfindone.go
│   │   │   ├── mongodbinsertmany
│   │   │   │   ├── mongodbinsertmany_test.go
│   │   │   │   └── mongodbinsertmany.go
│   │   │   ├── mongodbinsertone
│   │   │   │   ├── mongodbinsertone_test.go
│   │   │   │   └── mongodbinsertone.go
│   │   │   ├── mongodbupdatemany
│   │   │   │   ├── mongodbupdatemany_test.go
│   │   │   │   └── mongodbupdatemany.go
│   │   │   └── mongodbupdateone
│   │   │       ├── mongodbupdateone_test.go
│   │   │       └── mongodbupdateone.go
│   │   ├── mssql
│   │   │   ├── mssqlexecutesql
│   │   │   │   ├── mssqlexecutesql_test.go
│   │   │   │   └── mssqlexecutesql.go
│   │   │   ├── mssqllisttables
│   │   │   │   ├── mssqllisttables_test.go
│   │   │   │   └── mssqllisttables.go
│   │   │   └── mssqlsql
│   │   │       ├── mssqlsql_test.go
│   │   │       └── mssqlsql.go
│   │   ├── mysql
│   │   │   ├── mysqlcommon
│   │   │   │   └── mysqlcommon.go
│   │   │   ├── mysqlexecutesql
│   │   │   │   ├── mysqlexecutesql_test.go
│   │   │   │   └── mysqlexecutesql.go
│   │   │   ├── mysqllistactivequeries
│   │   │   │   ├── mysqllistactivequeries_test.go
│   │   │   │   └── mysqllistactivequeries.go
│   │   │   ├── mysqllisttablefragmentation
│   │   │   │   ├── mysqllisttablefragmentation_test.go
│   │   │   │   └── mysqllisttablefragmentation.go
│   │   │   ├── mysqllisttables
│   │   │   │   ├── mysqllisttables_test.go
│   │   │   │   └── mysqllisttables.go
│   │   │   ├── mysqllisttablesmissinguniqueindexes
│   │   │   │   ├── mysqllisttablesmissinguniqueindexes_test.go
│   │   │   │   └── mysqllisttablesmissinguniqueindexes.go
│   │   │   └── mysqlsql
│   │   │       ├── mysqlsql_test.go
│   │   │       └── mysqlsql.go
│   │   ├── neo4j
│   │   │   ├── neo4jcypher
│   │   │   │   ├── neo4jcypher_test.go
│   │   │   │   └── neo4jcypher.go
│   │   │   ├── neo4jexecutecypher
│   │   │   │   ├── classifier
│   │   │   │   │   ├── classifier_test.go
│   │   │   │   │   └── classifier.go
│   │   │   │   ├── neo4jexecutecypher_test.go
│   │   │   │   └── neo4jexecutecypher.go
│   │   │   └── neo4jschema
│   │   │       ├── cache
│   │   │       │   ├── cache_test.go
│   │   │       │   └── cache.go
│   │   │       ├── helpers
│   │   │       │   ├── helpers_test.go
│   │   │       │   └── helpers.go
│   │   │       ├── neo4jschema_test.go
│   │   │       ├── neo4jschema.go
│   │   │       └── types
│   │   │           └── types.go
│   │   ├── oceanbase
│   │   │   ├── oceanbaseexecutesql
│   │   │   │   ├── oceanbaseexecutesql_test.go
│   │   │   │   └── oceanbaseexecutesql.go
│   │   │   └── oceanbasesql
│   │   │       ├── oceanbasesql_test.go
│   │   │       └── oceanbasesql.go
│   │   ├── oracle
│   │   │   ├── oracleexecutesql
│   │   │   │   └── oracleexecutesql.go
│   │   │   └── oraclesql
│   │   │       └── oraclesql.go
│   │   ├── parameters_test.go
│   │   ├── parameters.go
│   │   ├── postgres
│   │   │   ├── postgresexecutesql
│   │   │   │   ├── postgresexecutesql_test.go
│   │   │   │   └── postgresexecutesql.go
│   │   │   ├── postgreslistactivequeries
│   │   │   │   ├── postgreslistactivequeries_test.go
│   │   │   │   └── postgreslistactivequeries.go
│   │   │   ├── postgreslistavailableextensions
│   │   │   │   ├── postgreslistavailableextensions_test.go
│   │   │   │   └── postgreslistavailableextensions.go
│   │   │   ├── postgreslistinstalledextensions
│   │   │   │   ├── postgreslistinstalledextensions_test.go
│   │   │   │   └── postgreslistinstalledextensions.go
│   │   │   ├── postgreslisttables
│   │   │   │   ├── postgreslisttables_test.go
│   │   │   │   └── postgreslisttables.go
│   │   │   └── postgressql
│   │   │       ├── postgressql_test.go
│   │   │       └── postgressql.go
│   │   ├── redis
│   │   │   ├── redis_test.go
│   │   │   └── redis.go
│   │   ├── spanner
│   │   │   ├── spannerexecutesql
│   │   │   │   ├── spannerexecutesql_test.go
│   │   │   │   └── spannerexecutesql.go
│   │   │   ├── spannerlisttables
│   │   │   │   ├── spannerlisttables_test.go
│   │   │   │   └── spannerlisttables.go
│   │   │   └── spannersql
│   │   │       ├── spanner_test.go
│   │   │       └── spannersql.go
│   │   ├── sqlite
│   │   │   ├── sqliteexecutesql
│   │   │   │   ├── sqliteexecutesql_test.go
│   │   │   │   └── sqliteexecutesql.go
│   │   │   └── sqlitesql
│   │   │       ├── sqlitesql_test.go
│   │   │       └── sqlitesql.go
│   │   ├── tidb
│   │   │   ├── tidbexecutesql
│   │   │   │   ├── tidbexecutesql_test.go
│   │   │   │   └── tidbexecutesql.go
│   │   │   └── tidbsql
│   │   │       ├── tidbsql_test.go
│   │   │       └── tidbsql.go
│   │   ├── tools_test.go
│   │   ├── tools.go
│   │   ├── toolsets.go
│   │   ├── trino
│   │   │   ├── trinoexecutesql
│   │   │   │   ├── trinoexecutesql_test.go
│   │   │   │   └── trinoexecutesql.go
│   │   │   └── trinosql
│   │   │       ├── trinosql_test.go
│   │   │       └── trinosql.go
│   │   ├── utility
│   │   │   └── wait
│   │   │       ├── wait_test.go
│   │   │       └── wait.go
│   │   ├── valkey
│   │   │   ├── valkey_test.go
│   │   │   └── valkey.go
│   │   └── yugabytedbsql
│   │       ├── yugabytedbsql_test.go
│   │       └── yugabytedbsql.go
│   └── util
│       └── util.go
├── LICENSE
├── logo.png
├── main.go
├── MCP-TOOLBOX-EXTENSION.md
├── README.md
└── tests
    ├── alloydb
    │   ├── alloydb_integration_test.go
    │   └── alloydb_wait_for_operation_test.go
    ├── alloydbainl
    │   └── alloydb_ai_nl_integration_test.go
    ├── alloydbpg
    │   └── alloydb_pg_integration_test.go
    ├── auth.go
    ├── bigquery
    │   └── bigquery_integration_test.go
    ├── bigtable
    │   └── bigtable_integration_test.go
    ├── cassandra
    │   └── cassandra_integration_test.go
    ├── clickhouse
    │   └── clickhouse_integration_test.go
    ├── cloudmonitoring
    │   └── cloud_monitoring_integration_test.go
    ├── cloudsql
    │   ├── cloud_sql_create_database_test.go
    │   ├── cloud_sql_create_users_test.go
    │   ├── cloud_sql_get_instances_test.go
    │   ├── cloud_sql_list_databases_test.go
    │   ├── cloudsql_list_instances_test.go
    │   └── cloudsql_wait_for_operation_test.go
    ├── cloudsqlmssql
    │   ├── cloud_sql_mssql_create_instance_integration_test.go
    │   └── cloud_sql_mssql_integration_test.go
    ├── cloudsqlmysql
    │   ├── cloud_sql_mysql_create_instance_integration_test.go
    │   └── cloud_sql_mysql_integration_test.go
    ├── cloudsqlpg
    │   ├── cloud_sql_pg_create_instances_test.go
    │   └── cloud_sql_pg_integration_test.go
    ├── common.go
    ├── couchbase
    │   └── couchbase_integration_test.go
    ├── dataform
    │   └── dataform_integration_test.go
    ├── dataplex
    │   └── dataplex_integration_test.go
    ├── dgraph
    │   └── dgraph_integration_test.go
    ├── firebird
    │   └── firebird_integration_test.go
    ├── firestore
    │   └── firestore_integration_test.go
    ├── http
    │   └── http_integration_test.go
    ├── looker
    │   └── looker_integration_test.go
    ├── mongodb
    │   └── mongodb_integration_test.go
    ├── mssql
    │   └── mssql_integration_test.go
    ├── mysql
    │   └── mysql_integration_test.go
    ├── neo4j
    │   └── neo4j_integration_test.go
    ├── oceanbase
    │   └── oceanbase_integration_test.go
    ├── option.go
    ├── oracle
    │   └── oracle_integration_test.go
    ├── postgres
    │   └── postgres_integration_test.go
    ├── redis
    │   └── redis_test.go
    ├── server.go
    ├── source.go
    ├── spanner
    │   └── spanner_integration_test.go
    ├── sqlite
    │   └── sqlite_integration_test.go
    ├── tidb
    │   └── tidb_integration_test.go
    ├── tool.go
    ├── trino
    │   └── trino_integration_test.go
    ├── utility
    │   └── wait_integration_test.go
    ├── valkey
    │   └── valkey_test.go
    └── yugabytedb
        └── yugabytedb_integration_test.go
```

# Files

--------------------------------------------------------------------------------
/internal/tools/mssql/mssqlsql/mssqlsql.go:
--------------------------------------------------------------------------------

```go
  1 | // Copyright 2025 Google LLC
  2 | //
  3 | // Licensed under the Apache License, Version 2.0 (the "License");
  4 | // you may not use this file except in compliance with the License.
  5 | // You may obtain a copy of the License at
  6 | //
  7 | //     http://www.apache.org/licenses/LICENSE-2.0
  8 | //
  9 | // Unless required by applicable law or agreed to in writing, software
 10 | // distributed under the License is distributed on an "AS IS" BASIS,
 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12 | // See the License for the specific language governing permissions and
 13 | // limitations under the License.
 14 | 
 15 | package mssqlsql
 16 | 
 17 | import (
 18 | 	"context"
 19 | 	"database/sql"
 20 | 	"fmt"
 21 | 	"strings"
 22 | 
 23 | 	yaml "github.com/goccy/go-yaml"
 24 | 	"github.com/googleapis/genai-toolbox/internal/sources"
 25 | 	"github.com/googleapis/genai-toolbox/internal/sources/cloudsqlmssql"
 26 | 	"github.com/googleapis/genai-toolbox/internal/sources/mssql"
 27 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 28 | )
 29 | 
 30 | const kind string = "mssql-sql"
 31 | 
 32 | func init() {
 33 | 	if !tools.Register(kind, newConfig) {
 34 | 		panic(fmt.Sprintf("tool kind %q already registered", kind))
 35 | 	}
 36 | }
 37 | 
 38 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
 39 | 	actual := Config{Name: name}
 40 | 	if err := decoder.DecodeContext(ctx, &actual); err != nil {
 41 | 		return nil, err
 42 | 	}
 43 | 	return actual, nil
 44 | }
 45 | 
 46 | type compatibleSource interface {
 47 | 	MSSQLDB() *sql.DB
 48 | }
 49 | 
 50 | // validate compatible sources are still compatible
 51 | var _ compatibleSource = &cloudsqlmssql.Source{}
 52 | var _ compatibleSource = &mssql.Source{}
 53 | 
 54 | var compatibleSources = [...]string{cloudsqlmssql.SourceKind, mssql.SourceKind}
 55 | 
 56 | type Config struct {
 57 | 	Name               string           `yaml:"name" validate:"required"`
 58 | 	Kind               string           `yaml:"kind" validate:"required"`
 59 | 	Source             string           `yaml:"source" validate:"required"`
 60 | 	Description        string           `yaml:"description" validate:"required"`
 61 | 	Statement          string           `yaml:"statement" validate:"required"`
 62 | 	AuthRequired       []string         `yaml:"authRequired"`
 63 | 	Parameters         tools.Parameters `yaml:"parameters"`
 64 | 	TemplateParameters tools.Parameters `yaml:"templateParameters"`
 65 | }
 66 | 
 67 | // validate interface
 68 | var _ tools.ToolConfig = Config{}
 69 | 
 70 | func (cfg Config) ToolConfigKind() string {
 71 | 	return kind
 72 | }
 73 | 
 74 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
 75 | 	// verify source exists
 76 | 	rawS, ok := srcs[cfg.Source]
 77 | 	if !ok {
 78 | 		return nil, fmt.Errorf("no source named %q configured", cfg.Source)
 79 | 	}
 80 | 
 81 | 	// verify the source is compatible
 82 | 	s, ok := rawS.(compatibleSource)
 83 | 	if !ok {
 84 | 		return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
 85 | 	}
 86 | 
 87 | 	allParameters, paramManifest, err := tools.ProcessParameters(cfg.TemplateParameters, cfg.Parameters)
 88 | 	if err != nil {
 89 | 		return nil, err
 90 | 	}
 91 | 
 92 | 	mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, allParameters)
 93 | 
 94 | 	// finish tool setup
 95 | 	t := Tool{
 96 | 		Name:               cfg.Name,
 97 | 		Kind:               kind,
 98 | 		Parameters:         cfg.Parameters,
 99 | 		TemplateParameters: cfg.TemplateParameters,
100 | 		AllParams:          allParameters,
101 | 		Statement:          cfg.Statement,
102 | 		AuthRequired:       cfg.AuthRequired,
103 | 		Db:                 s.MSSQLDB(),
104 | 		manifest:           tools.Manifest{Description: cfg.Description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired},
105 | 		mcpManifest:        mcpManifest,
106 | 	}
107 | 	return t, nil
108 | }
109 | 
110 | // validate interface
111 | var _ tools.Tool = Tool{}
112 | 
113 | type Tool struct {
114 | 	Name               string           `yaml:"name"`
115 | 	Kind               string           `yaml:"kind"`
116 | 	AuthRequired       []string         `yaml:"authRequired"`
117 | 	Parameters         tools.Parameters `yaml:"parameters"`
118 | 	TemplateParameters tools.Parameters `yaml:"templateParameters"`
119 | 	AllParams          tools.Parameters `yaml:"allParams"`
120 | 
121 | 	Db          *sql.DB
122 | 	Statement   string
123 | 	manifest    tools.Manifest
124 | 	mcpManifest tools.McpManifest
125 | }
126 | 
127 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
128 | 	paramsMap := params.AsMap()
129 | 	newStatement, err := tools.ResolveTemplateParams(t.TemplateParameters, t.Statement, paramsMap)
130 | 	if err != nil {
131 | 		return nil, fmt.Errorf("unable to extract template params %w", err)
132 | 	}
133 | 
134 | 	newParams, err := tools.GetParams(t.Parameters, paramsMap)
135 | 	if err != nil {
136 | 		return nil, fmt.Errorf("unable to extract standard params %w", err)
137 | 	}
138 | 
139 | 	namedArgs := make([]any, 0, len(newParams))
140 | 	// To support both named args (e.g @id) and positional args (e.g @p1), check
141 | 	// if arg name is contained in the statement.
142 | 	for _, p := range t.Parameters {
143 | 		name := p.GetName()
144 | 		value := paramsMap[name]
145 | 		if strings.Contains(newStatement, "@"+name) {
146 | 			namedArgs = append(namedArgs, sql.Named(name, value))
147 | 		} else {
148 | 			namedArgs = append(namedArgs, value)
149 | 		}
150 | 	}
151 | 
152 | 	rows, err := t.Db.QueryContext(ctx, newStatement, namedArgs...)
153 | 	if err != nil {
154 | 		return nil, fmt.Errorf("unable to execute query: %w", err)
155 | 	}
156 | 
157 | 	cols, err := rows.Columns()
158 | 	if err != nil {
159 | 		return nil, fmt.Errorf("unable to fetch column types: %w", err)
160 | 	}
161 | 
162 | 	// create an array of values for each column, which can be re-used to scan each row
163 | 	rawValues := make([]any, len(cols))
164 | 	values := make([]any, len(cols))
165 | 	for i := range rawValues {
166 | 		values[i] = &rawValues[i]
167 | 	}
168 | 
169 | 	var out []any
170 | 	for rows.Next() {
171 | 		err = rows.Scan(values...)
172 | 		if err != nil {
173 | 			return nil, fmt.Errorf("unable to parse row: %w", err)
174 | 		}
175 | 		vMap := make(map[string]any)
176 | 		for i, name := range cols {
177 | 			vMap[name] = rawValues[i]
178 | 		}
179 | 		out = append(out, vMap)
180 | 	}
181 | 	err = rows.Close()
182 | 	if err != nil {
183 | 		return nil, fmt.Errorf("unable to close rows: %w", err)
184 | 	}
185 | 
186 | 	// Check if error occurred during iteration
187 | 	if err := rows.Err(); err != nil {
188 | 		return nil, err
189 | 	}
190 | 
191 | 	return out, nil
192 | }
193 | 
194 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
195 | 	return tools.ParseParams(t.AllParams, data, claims)
196 | }
197 | 
198 | func (t Tool) Manifest() tools.Manifest {
199 | 	return t.manifest
200 | }
201 | 
202 | func (t Tool) McpManifest() tools.McpManifest {
203 | 	return t.mcpManifest
204 | }
205 | 
206 | func (t Tool) Authorized(verifiedAuthServices []string) bool {
207 | 	return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
208 | }
209 | 
210 | func (t Tool) RequiresClientAuthorization() bool {
211 | 	return false
212 | }
213 | 
```

--------------------------------------------------------------------------------
/internal/tools/firebird/firebirdsql/firebirdsql.go:
--------------------------------------------------------------------------------

```go
  1 | // Copyright 2025 Google LLC
  2 | //
  3 | // Licensed under the Apache License, Version 2.0 (the "License");
  4 | // you may not use this file except in compliance with the License.
  5 | // You may obtain a copy of the License at
  6 | //
  7 | //     http://www.apache.org/licenses/LICENSE-2.0
  8 | //
  9 | // Unless required by applicable law or agreed to in writing, software
 10 | // distributed under the License is distributed on an "AS IS" BASIS,
 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12 | // See the License for the specific language governing permissions and
 13 | // limitations under the License.
 14 | 
 15 | package firebirdsql
 16 | 
 17 | import (
 18 | 	"context"
 19 | 	"database/sql"
 20 | 	"fmt"
 21 | 	"strings"
 22 | 
 23 | 	"github.com/goccy/go-yaml"
 24 | 	"github.com/googleapis/genai-toolbox/internal/sources"
 25 | 	"github.com/googleapis/genai-toolbox/internal/sources/firebird"
 26 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 27 | )
 28 | 
 29 | const kind string = "firebird-sql"
 30 | 
 31 | func init() {
 32 | 	if !tools.Register(kind, newConfig) {
 33 | 		panic(fmt.Sprintf("tool kind %q already registered", kind))
 34 | 	}
 35 | }
 36 | 
 37 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
 38 | 	actual := Config{Name: name}
 39 | 	if err := decoder.DecodeContext(ctx, &actual); err != nil {
 40 | 		return nil, err
 41 | 	}
 42 | 	return actual, nil
 43 | }
 44 | 
 45 | type compatibleSource interface {
 46 | 	FirebirdDB() *sql.DB
 47 | }
 48 | 
 49 | // validate compatible sources are still compatible
 50 | var _ compatibleSource = &firebird.Source{}
 51 | 
 52 | var compatibleSources = [...]string{firebird.SourceKind}
 53 | 
 54 | type Config struct {
 55 | 	Name               string           `yaml:"name" validate:"required"`
 56 | 	Kind               string           `yaml:"kind" validate:"required"`
 57 | 	Source             string           `yaml:"source" validate:"required"`
 58 | 	Description        string           `yaml:"description" validate:"required"`
 59 | 	Statement          string           `yaml:"statement" validate:"required"`
 60 | 	AuthRequired       []string         `yaml:"authRequired"`
 61 | 	Parameters         tools.Parameters `yaml:"parameters"`
 62 | 	TemplateParameters tools.Parameters `yaml:"templateParameters"`
 63 | }
 64 | 
 65 | // validate interface
 66 | var _ tools.ToolConfig = Config{}
 67 | 
 68 | func (cfg Config) ToolConfigKind() string {
 69 | 	return kind
 70 | }
 71 | 
 72 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
 73 | 	// verify source exists
 74 | 	rawS, ok := srcs[cfg.Source]
 75 | 	if !ok {
 76 | 		return nil, fmt.Errorf("no source named %q configured", cfg.Source)
 77 | 	}
 78 | 
 79 | 	// verify the source is compatible
 80 | 	s, ok := rawS.(compatibleSource)
 81 | 	if !ok {
 82 | 		return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
 83 | 	}
 84 | 
 85 | 	allParameters, paramManifest, err := tools.ProcessParameters(cfg.TemplateParameters, cfg.Parameters)
 86 | 	if err != nil {
 87 | 		return nil, err
 88 | 	}
 89 | 
 90 | 	mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, allParameters)
 91 | 
 92 | 	// finish tool setup
 93 | 	t := &Tool{
 94 | 		Name:               cfg.Name,
 95 | 		Kind:               kind,
 96 | 		Parameters:         cfg.Parameters,
 97 | 		TemplateParameters: cfg.TemplateParameters,
 98 | 		AllParams:          allParameters,
 99 | 		Statement:          cfg.Statement,
100 | 		AuthRequired:       cfg.AuthRequired,
101 | 		Db:                 s.FirebirdDB(),
102 | 		manifest:           tools.Manifest{Description: cfg.Description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired},
103 | 		mcpManifest:        mcpManifest,
104 | 	}
105 | 	return t, nil
106 | }
107 | 
108 | // validate interface
109 | var _ tools.Tool = &Tool{}
110 | 
111 | type Tool struct {
112 | 	Name               string           `yaml:"name"`
113 | 	Kind               string           `yaml:"kind"`
114 | 	AuthRequired       []string         `yaml:"authRequired"`
115 | 	Parameters         tools.Parameters `yaml:"parameters"`
116 | 	TemplateParameters tools.Parameters `yaml:"templateParameters"`
117 | 	AllParams          tools.Parameters `yaml:"allParams"`
118 | 
119 | 	Db          *sql.DB
120 | 	Statement   string
121 | 	manifest    tools.Manifest
122 | 	mcpManifest tools.McpManifest
123 | }
124 | 
125 | func (t *Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
126 | 	paramsMap := params.AsMap()
127 | 	statement, err := tools.ResolveTemplateParams(t.TemplateParameters, t.Statement, paramsMap)
128 | 	if err != nil {
129 | 		return nil, fmt.Errorf("unable to extract template params: %w", err)
130 | 	}
131 | 
132 | 	newParams, err := tools.GetParams(t.Parameters, paramsMap)
133 | 	if err != nil {
134 | 		return nil, fmt.Errorf("unable to extract standard params: %w", err)
135 | 	}
136 | 
137 | 	namedArgs := make([]any, 0, len(newParams))
138 | 	// To support both named args (e.g :id) and positional args (e.g ?), check
139 | 	// if arg name is contained in the statement.
140 | 	for _, p := range t.Parameters {
141 | 		name := p.GetName()
142 | 		value := paramsMap[name]
143 | 		if strings.Contains(statement, ":"+name) {
144 | 			namedArgs = append(namedArgs, sql.Named(name, value))
145 | 		} else {
146 | 			namedArgs = append(namedArgs, value)
147 | 		}
148 | 	}
149 | 
150 | 	rows, err := t.Db.QueryContext(ctx, statement, namedArgs...)
151 | 	if err != nil {
152 | 		return nil, fmt.Errorf("unable to execute query: %w", err)
153 | 	}
154 | 	defer rows.Close()
155 | 
156 | 	cols, err := rows.Columns()
157 | 	if err != nil {
158 | 		return nil, fmt.Errorf("unable to get columns: %w", err)
159 | 	}
160 | 
161 | 	values := make([]any, len(cols))
162 | 	scanArgs := make([]any, len(values))
163 | 	for i := range values {
164 | 		scanArgs[i] = &values[i]
165 | 	}
166 | 
167 | 	var out []any
168 | 	for rows.Next() {
169 | 
170 | 		err = rows.Scan(scanArgs...)
171 | 		if err != nil {
172 | 			return nil, fmt.Errorf("unable to parse row: %w", err)
173 | 		}
174 | 
175 | 		vMap := make(map[string]any)
176 | 		for i, col := range cols {
177 | 			if b, ok := values[i].([]byte); ok {
178 | 				vMap[col] = string(b)
179 | 			} else {
180 | 				vMap[col] = values[i]
181 | 			}
182 | 		}
183 | 		out = append(out, vMap)
184 | 	}
185 | 
186 | 	if err := rows.Err(); err != nil {
187 | 		return nil, fmt.Errorf("error iterating rows: %w", err)
188 | 	}
189 | 
190 | 	// In most cases, DML/DDL statements like INSERT, UPDATE, CREATE, etc. might return no rows
191 | 	// However, it is also possible that this was a query that was expected to return rows
192 | 	// but returned none, a case that we cannot distinguish here.
193 | 	return out, nil
194 | }
195 | 
196 | func (t *Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
197 | 	return tools.ParseParams(t.AllParams, data, claims)
198 | }
199 | 
200 | func (t *Tool) Manifest() tools.Manifest {
201 | 	return t.manifest
202 | }
203 | 
204 | func (t *Tool) McpManifest() tools.McpManifest {
205 | 	return t.mcpManifest
206 | }
207 | 
208 | func (t *Tool) Authorized(verifiedAuthServices []string) bool {
209 | 	return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
210 | }
211 | 
212 | func (t Tool) RequiresClientAuthorization() bool {
213 | 	return false
214 | }
215 | 
```

--------------------------------------------------------------------------------
/internal/server/static/js/runTool.js:
--------------------------------------------------------------------------------

```javascript
  1 | // Copyright 2025 Google LLC
  2 | //
  3 | // Licensed under the Apache License, Version 2.0 (the "License");
  4 | // you may not use this file except in compliance with the License.
  5 | // You may obtain a copy of the License at
  6 | //
  7 | //     http://www.apache.org/licenses/LICENSE-2.0
  8 | //
  9 | // Unless required by applicable law or agreed to in writing, software
 10 | // distributed under the License is distributed on an "AS IS" BASIS,
 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12 | // See the License for the specific language governing permissions and
 13 | // limitations under the License.
 14 | 
 15 | import { isParamIncluded } from "./toolDisplay.js";
 16 | 
 17 | /**
 18 |  * Runs a specific tool using the /api/tools/toolName/invoke endpoint
 19 |  * @param {string} toolId The unique identifier for the tool.
 20 |  * @param {!HTMLFormElement} form The form element containing parameter inputs.
 21 |  * @param {!HTMLTextAreaElement} responseArea The textarea to display results or errors.
 22 |  * @param {!Array<!Object>} parameters An array of parameter definition objects
 23 |  * @param {!HTMLInputElement} prettifyCheckbox The checkbox to control JSON formatting.
 24 |  * @param {function(?Object): void} updateLastResults Callback to store the last results.
 25 |  */
 26 | export async function handleRunTool(toolId, form, responseArea, parameters, prettifyCheckbox, updateLastResults, headers) {
 27 |     const formData = new FormData(form);
 28 |     const typedParams = {};
 29 |     responseArea.value = 'Running tool...';
 30 |     updateLastResults(null);
 31 | 
 32 |     for (const param of parameters) {
 33 |         const NAME = param.name;
 34 |         const VALUE_TYPE = param.valueType;
 35 |         const RAW_VALUE = formData.get(NAME);
 36 |         const INCLUDE_CHECKED = isParamIncluded(toolId, NAME)
 37 | 
 38 |         try {
 39 |             if (!INCLUDE_CHECKED) {
 40 |                 console.debug(`Param ${NAME} was intentionally skipped.`)
 41 |                 // if param was purposely unchecked, don't include it in body
 42 |                 continue;
 43 |             }
 44 | 
 45 |             if (VALUE_TYPE === 'boolean') {
 46 |                 typedParams[NAME] = RAW_VALUE !== null;
 47 |                 console.debug(`Parameter ${NAME} (boolean) set to: ${typedParams[NAME]}`);
 48 |                 continue; 
 49 |             }
 50 | 
 51 |             // process remaining types
 52 |             if (VALUE_TYPE && VALUE_TYPE.startsWith('array<')) {
 53 |                 typedParams[NAME] = parseArrayParameter(RAW_VALUE, VALUE_TYPE, NAME);
 54 |             } else {
 55 |                 switch (VALUE_TYPE) {
 56 |                     case 'number':
 57 |                         if (RAW_VALUE === "") {
 58 |                             console.debug(`Param ${NAME} was empty, setting to empty string.`)
 59 |                             typedParams[NAME] = "";
 60 |                         } else {
 61 |                             const num = Number(RAW_VALUE);
 62 |                             if (isNaN(num)) {
 63 |                                 throw new Error(`Invalid number input for ${NAME}: ${RAW_VALUE}`);
 64 |                             }
 65 |                             typedParams[NAME] = num;
 66 |                         }
 67 |                         break;
 68 |                     case 'string':
 69 |                     default:
 70 |                         typedParams[NAME] = RAW_VALUE;
 71 |                         break;
 72 |                 }
 73 |             }
 74 |         } catch (error) {
 75 |             console.error('Error processing parameter:', NAME, error);
 76 |             responseArea.value = `Error for ${NAME}: ${error.message}`;
 77 |             return; 
 78 |         }
 79 |     }
 80 | 
 81 |     console.debug('Running tool:', toolId, 'with typed params:', typedParams);
 82 |     try {
 83 |         const response = await fetch(`/api/tool/${toolId}/invoke`, {
 84 |             method: 'POST',
 85 |             headers: headers,
 86 |             body: JSON.stringify(typedParams)
 87 |         });
 88 |         if (!response.ok) {
 89 |             const errorBody = await response.text();
 90 |             throw new Error(`HTTP error ${response.status}: ${errorBody}`);
 91 |         }
 92 |         const results = await response.json();
 93 |         updateLastResults(results);
 94 |         displayResults(results, responseArea, prettifyCheckbox.checked);
 95 |     } catch (error) {
 96 |         console.error('Error running tool:', error);
 97 |         responseArea.value = `Error: ${error.message}`;
 98 |         updateLastResults(null);
 99 |     }
100 | }
101 | 
102 | /**
103 |  * Parses and validates a single array parameter from a raw string value.
104 |  * @param {string} rawValue The raw string value from FormData.
105 |  * @param {string} valueType The full array type string (e.g., "array<number>").
106 |  * @param {string} paramName The name of the parameter for error messaging.
107 |  * @return {!Array<*>} The parsed array.
108 |  * @throws {Error} If parsing or type validation fails.
109 |  */
110 | function parseArrayParameter(rawValue, valueType, paramName) {
111 |     const ELEMENT_TYPE = valueType.substring(6, valueType.length - 1);
112 |     let parsedArray;
113 |     try {
114 |         parsedArray = JSON.parse(rawValue);
115 |     } catch (e) {
116 |         throw new Error(`Invalid JSON format for ${paramName}. Expected an array. ${e.message}`);
117 |     }
118 | 
119 |     if (!Array.isArray(parsedArray)) {
120 |         throw new Error(`Input for ${paramName} must be a JSON array (e.g., ["a", "b"]).`);
121 |     }
122 | 
123 |     return parsedArray.map((item, index) => {
124 |         switch (ELEMENT_TYPE) {
125 |             case 'number':
126 |                 const NUM = Number(item);
127 |                 if (isNaN(NUM)) {
128 |                     throw new Error(`Invalid number "${item}" found in array for ${paramName} at index ${index}.`);
129 |                 }
130 |                 return NUM;
131 |             case 'boolean':
132 |                 return item === true || String(item).toLowerCase() === 'true';
133 |             case 'string':
134 |             default:
135 |                 return item;
136 |         }
137 |     });
138 | }
139 | 
140 | /**
141 |  * Displays the results from the tool run in the response area.
142 |  */
143 | export function displayResults(results, responseArea, prettify) {
144 |     if (results === null || results === undefined) {
145 |         return;
146 |     }
147 |     try {
148 |         const resultJson = JSON.parse(results.result);
149 |         if (prettify) {
150 |             responseArea.value = JSON.stringify(resultJson, null, 2);
151 |         } else {
152 |             responseArea.value = JSON.stringify(resultJson);
153 |         }
154 |     } catch (error) {
155 |         console.error("Error parsing or stringifying results:", error);
156 |         if (typeof results.result === 'string') {
157 |             responseArea.value = results.result;
158 |         } else {
159 |             responseArea.value = "Error displaying results. Invalid format.";
160 |         }
161 |     }
162 | }
163 | 
```

--------------------------------------------------------------------------------
/internal/tools/alloydb/alloydbcreatecluster/alloydbcreatecluster.go:
--------------------------------------------------------------------------------

```go
  1 | // Copyright 2025 Google LLC
  2 | //
  3 | // Licensed under the Apache License, Version 2.0 (the "License");
  4 | // you may not use this file except in compliance with the License.
  5 | // You may obtain a copy of the License at
  6 | //
  7 | //      http://www.apache.org/licenses/LICENSE-2.0
  8 | //
  9 | // Unless required by applicable law or agreed to in writing, software
 10 | // distributed under the License is distributed on an "AS IS" BASIS,
 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12 | // See the License for the specific language governing permissions and
 13 | // limitations under the License.
 14 | 
 15 | package alloydbcreatecluster
 16 | 
 17 | import (
 18 | 	"context"
 19 | 	"fmt"
 20 | 
 21 | 	yaml "github.com/goccy/go-yaml"
 22 | 	"github.com/googleapis/genai-toolbox/internal/sources"
 23 | 	alloydbadmin "github.com/googleapis/genai-toolbox/internal/sources/alloydbadmin"
 24 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 25 | 	"google.golang.org/api/alloydb/v1"
 26 | )
 27 | 
 28 | const kind string = "alloydb-create-cluster"
 29 | 
 30 | func init() {
 31 | 	if !tools.Register(kind, newConfig) {
 32 | 		panic(fmt.Sprintf("tool kind %q already registered", kind))
 33 | 	}
 34 | }
 35 | 
 36 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
 37 | 	actual := Config{Name: name}
 38 | 	if err := decoder.DecodeContext(ctx, &actual); err != nil {
 39 | 		return nil, err
 40 | 	}
 41 | 	return actual, nil
 42 | }
 43 | 
 44 | // Configuration for the create-cluster tool.
 45 | type Config struct {
 46 | 	Name         string   `yaml:"name" validate:"required"`
 47 | 	Kind         string   `yaml:"kind" validate:"required"`
 48 | 	Source       string   `yaml:"source" validate:"required"`
 49 | 	Description  string   `yaml:"description"`
 50 | 	AuthRequired []string `yaml:"authRequired"`
 51 | }
 52 | 
 53 | // validate interface
 54 | var _ tools.ToolConfig = Config{}
 55 | 
 56 | // ToolConfigKind returns the kind of the tool.
 57 | func (cfg Config) ToolConfigKind() string {
 58 | 	return kind
 59 | }
 60 | 
 61 | // Initialize initializes the tool from the configuration.
 62 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
 63 | 	rawS, ok := srcs[cfg.Source]
 64 | 	if !ok {
 65 | 		return nil, fmt.Errorf("source %q not found", cfg.Source)
 66 | 	}
 67 | 
 68 | 	s, ok := rawS.(*alloydbadmin.Source)
 69 | 	if !ok {
 70 | 		return nil, fmt.Errorf("invalid source for %q tool: source kind must be `alloydb-admin`", kind)
 71 | 	}
 72 | 
 73 | 	allParameters := tools.Parameters{
 74 | 		tools.NewStringParameter("project", "The GCP project ID."),
 75 | 		tools.NewStringParameterWithDefault("location", "us-central1", "The location to create the cluster in. The default value is us-central1. If quota is exhausted then use other regions."),
 76 | 		tools.NewStringParameter("cluster", "A unique ID for the AlloyDB cluster."),
 77 | 		tools.NewStringParameter("password", "A secure password for the initial user."),
 78 | 		tools.NewStringParameterWithDefault("network", "default", "The name of the VPC network to connect the cluster to (e.g., 'default')."),
 79 | 		tools.NewStringParameterWithDefault("user", "postgres", "The name for the initial superuser. Defaults to 'postgres' if not provided."),
 80 | 	}
 81 | 	paramManifest := allParameters.Manifest()
 82 | 
 83 | 	description := cfg.Description
 84 | 	if description == "" {
 85 | 		description = "Creates a new AlloyDB cluster. This is a long-running operation, but the API call returns quickly. This will return operation id to be used by get operations tool. Take all parameters from user in one go."
 86 | 	}
 87 | 	mcpManifest := tools.GetMcpManifest(cfg.Name, description, cfg.AuthRequired, allParameters)
 88 | 
 89 | 	return Tool{
 90 | 		Name:        cfg.Name,
 91 | 		Kind:        kind,
 92 | 		Source:      s,
 93 | 		AllParams:   allParameters,
 94 | 		manifest:    tools.Manifest{Description: description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired},
 95 | 		mcpManifest: mcpManifest,
 96 | 	}, nil
 97 | }
 98 | 
 99 | // Tool represents the create-cluster tool.
100 | type Tool struct {
101 | 	Name        string `yaml:"name"`
102 | 	Kind        string `yaml:"kind"`
103 | 	Description string `yaml:"description"`
104 | 
105 | 	Source    *alloydbadmin.Source
106 | 	AllParams tools.Parameters `yaml:"allParams"`
107 | 
108 | 	manifest    tools.Manifest
109 | 	mcpManifest tools.McpManifest
110 | }
111 | 
112 | // Invoke executes the tool's logic.
113 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
114 | 	paramsMap := params.AsMap()
115 | 	project, ok := paramsMap["project"].(string)
116 | 	if !ok || project == "" {
117 | 		return nil, fmt.Errorf("invalid or missing 'project' parameter; expected a non-empty string")
118 | 	}
119 | 
120 | 	location, ok := paramsMap["location"].(string)
121 | 	if !ok {
122 | 		return nil, fmt.Errorf("invalid 'location' parameter; expected a string")
123 | 	}
124 | 
125 | 	clusterID, ok := paramsMap["cluster"].(string)
126 | 	if !ok || clusterID == "" {
127 | 		return nil, fmt.Errorf("invalid or missing 'cluster' parameter; expected a non-empty string")
128 | 	}
129 | 
130 | 	password, ok := paramsMap["password"].(string)
131 | 	if !ok || password == "" {
132 | 		return nil, fmt.Errorf("invalid or missing 'password' parameter; expected a non-empty string")
133 | 	}
134 | 
135 | 	network, ok := paramsMap["network"].(string)
136 | 	if !ok {
137 | 		return nil, fmt.Errorf("invalid 'network' parameter; expected a string")
138 | 	}
139 | 
140 | 	user, ok := paramsMap["user"].(string)
141 | 	if !ok {
142 | 		return nil, fmt.Errorf("invalid 'user' parameter; expected a string")
143 | 	}
144 | 
145 | 	service, err := t.Source.GetService(ctx, string(accessToken))
146 | 	if err != nil {
147 | 		return nil, err
148 | 	}
149 | 
150 | 	urlString := fmt.Sprintf("projects/%s/locations/%s", project, location)
151 | 
152 | 	// Build the request body using the type-safe Cluster struct.
153 | 	clusterBody := &alloydb.Cluster{
154 | 		NetworkConfig: &alloydb.NetworkConfig{
155 | 			Network: fmt.Sprintf("projects/%s/global/networks/%s", project, network),
156 | 		},
157 | 		InitialUser: &alloydb.UserPassword{
158 | 			User:     user,
159 | 			Password: password,
160 | 		},
161 | 	}
162 | 
163 | 	// The Create API returns a long-running operation.
164 | 	resp, err := service.Projects.Locations.Clusters.Create(urlString, clusterBody).ClusterId(clusterID).Do()
165 | 	if err != nil {
166 | 		return nil, fmt.Errorf("error creating AlloyDB cluster: %w", err)
167 | 	}
168 | 
169 | 	return resp, nil
170 | }
171 | 
172 | // ParseParams parses the parameters for the tool.
173 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
174 | 	return tools.ParseParams(t.AllParams, data, claims)
175 | }
176 | 
177 | // Manifest returns the tool's manifest.
178 | func (t Tool) Manifest() tools.Manifest {
179 | 	return t.manifest
180 | }
181 | 
182 | // McpManifest returns the tool's MCP manifest.
183 | func (t Tool) McpManifest() tools.McpManifest {
184 | 	return t.mcpManifest
185 | }
186 | 
187 | // Authorized checks if the tool is authorized.
188 | func (t Tool) Authorized(verifiedAuthServices []string) bool {
189 | 	return true
190 | }
191 | 
192 | func (t Tool) RequiresClientAuthorization() bool {
193 | 	return t.Source.UseClientAuthorization()
194 | }
195 | 
```

--------------------------------------------------------------------------------
/internal/tools/mongodb/mongodbupdatemany/mongodbupdatemany.go:
--------------------------------------------------------------------------------

```go
  1 | // Copyright 2025 Google LLC
  2 | //
  3 | // Licensed under the Apache License, Version 2.0 (the "License");
  4 | // you may not use this file except in compliance with the License.
  5 | // You may obtain a copy of the License at
  6 | //
  7 | //	http://www.apache.org/licenses/LICENSE-2.0
  8 | //
  9 | // Unless required by applicable law or agreed to in writing, software
 10 | // distributed under the License is distributed on an "AS IS" BASIS,
 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12 | // See the License for the specific language governing permissions and
 13 | // limitations under the License.
 14 | package mongodbupdatemany
 15 | 
 16 | import (
 17 | 	"context"
 18 | 	"fmt"
 19 | 	"slices"
 20 | 
 21 | 	"github.com/goccy/go-yaml"
 22 | 	"github.com/googleapis/genai-toolbox/internal/sources"
 23 | 	mongosrc "github.com/googleapis/genai-toolbox/internal/sources/mongodb"
 24 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 25 | 	"go.mongodb.org/mongo-driver/bson"
 26 | 	"go.mongodb.org/mongo-driver/mongo"
 27 | 	"go.mongodb.org/mongo-driver/mongo/options"
 28 | )
 29 | 
 30 | const kind string = "mongodb-update-many"
 31 | 
 32 | func init() {
 33 | 	if !tools.Register(kind, newConfig) {
 34 | 		panic(fmt.Sprintf("tool kind %q already registered", kind))
 35 | 	}
 36 | }
 37 | 
 38 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
 39 | 	actual := Config{Name: name}
 40 | 	if err := decoder.DecodeContext(ctx, &actual); err != nil {
 41 | 		return nil, err
 42 | 	}
 43 | 	return actual, nil
 44 | }
 45 | 
 46 | type Config struct {
 47 | 	Name          string           `yaml:"name" validate:"required"`
 48 | 	Kind          string           `yaml:"kind" validate:"required"`
 49 | 	Source        string           `yaml:"source" validate:"required"`
 50 | 	AuthRequired  []string         `yaml:"authRequired" validate:"required"`
 51 | 	Description   string           `yaml:"description" validate:"required"`
 52 | 	Database      string           `yaml:"database" validate:"required"`
 53 | 	Collection    string           `yaml:"collection" validate:"required"`
 54 | 	FilterPayload string           `yaml:"filterPayload" validate:"required"`
 55 | 	FilterParams  tools.Parameters `yaml:"filterParams" validate:"required"`
 56 | 	UpdatePayload string           `yaml:"updatePayload" validate:"required"`
 57 | 	UpdateParams  tools.Parameters `yaml:"updateParams" validate:"required"`
 58 | 	Canonical     bool             `yaml:"canonical" validate:"required"`
 59 | 	Upsert        bool             `yaml:"upsert"`
 60 | }
 61 | 
 62 | // validate interface
 63 | var _ tools.ToolConfig = Config{}
 64 | 
 65 | func (cfg Config) ToolConfigKind() string {
 66 | 	return kind
 67 | }
 68 | 
 69 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
 70 | 	// verify source exists
 71 | 	rawS, ok := srcs[cfg.Source]
 72 | 	if !ok {
 73 | 		return nil, fmt.Errorf("no source named %q configured", cfg.Source)
 74 | 	}
 75 | 
 76 | 	// verify the source is compatible
 77 | 	s, ok := rawS.(*mongosrc.Source)
 78 | 	if !ok {
 79 | 		return nil, fmt.Errorf("invalid source for %q tool: source kind must be `mongodb`", kind)
 80 | 	}
 81 | 
 82 | 	// Create a slice for all parameters
 83 | 	allParameters := slices.Concat(cfg.FilterParams, cfg.UpdateParams)
 84 | 
 85 | 	// Verify no duplicate parameter names
 86 | 	err := tools.CheckDuplicateParameters(allParameters)
 87 | 	if err != nil {
 88 | 		return nil, err
 89 | 	}
 90 | 
 91 | 	// Create Toolbox manifest
 92 | 	paramManifest := allParameters.Manifest()
 93 | 
 94 | 	if paramManifest == nil {
 95 | 		paramManifest = make([]tools.ParameterManifest, 0)
 96 | 	}
 97 | 
 98 | 	// Create MCP manifest
 99 | 	mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, allParameters)
100 | 
101 | 	// finish tool setup
102 | 	return Tool{
103 | 		Name:          cfg.Name,
104 | 		Kind:          kind,
105 | 		AuthRequired:  cfg.AuthRequired,
106 | 		Collection:    cfg.Collection,
107 | 		Canonical:     cfg.Canonical,
108 | 		Upsert:        cfg.Upsert,
109 | 		FilterPayload: cfg.FilterPayload,
110 | 		FilterParams:  cfg.FilterParams,
111 | 		UpdatePayload: cfg.UpdatePayload,
112 | 		UpdateParams:  cfg.UpdateParams,
113 | 		AllParams:     allParameters,
114 | 		database:      s.Client.Database(cfg.Database),
115 | 		manifest:      tools.Manifest{Description: cfg.Description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired},
116 | 		mcpManifest:   mcpManifest,
117 | 	}, nil
118 | }
119 | 
120 | // validate interface
121 | var _ tools.Tool = Tool{}
122 | 
123 | type Tool struct {
124 | 	Name          string           `yaml:"name"`
125 | 	Kind          string           `yaml:"kind"`
126 | 	AuthRequired  []string         `yaml:"authRequired"`
127 | 	Description   string           `yaml:"description"`
128 | 	Collection    string           `yaml:"collection"`
129 | 	FilterPayload string           `yaml:"filterPayload" validate:"required"`
130 | 	FilterParams  tools.Parameters `yaml:"filterParams" validate:"required"`
131 | 	UpdatePayload string           `yaml:"updatePayload" validate:"required"`
132 | 	UpdateParams  tools.Parameters `yaml:"updateParams" validate:"required"`
133 | 	AllParams     tools.Parameters `yaml:"allParams"`
134 | 	Canonical     bool             `yaml:"canonical" validation:"required"`
135 | 	Upsert        bool             `yaml:"upsert"`
136 | 
137 | 	database    *mongo.Database
138 | 	manifest    tools.Manifest
139 | 	mcpManifest tools.McpManifest
140 | }
141 | 
142 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
143 | 	paramsMap := params.AsMap()
144 | 
145 | 	filterString, err := tools.PopulateTemplateWithJSON("MongoDBUpdateManyFilter", t.FilterPayload, paramsMap)
146 | 	if err != nil {
147 | 		return nil, fmt.Errorf("error populating filter: %s", err)
148 | 	}
149 | 
150 | 	var filter = bson.D{}
151 | 	err = bson.UnmarshalExtJSON([]byte(filterString), t.Canonical, &filter)
152 | 	if err != nil {
153 | 		return nil, fmt.Errorf("unable to unmarshal filter string: %w", err)
154 | 	}
155 | 
156 | 	updateString, err := tools.PopulateTemplateWithJSON("MongoDBUpdateMany", t.UpdatePayload, paramsMap)
157 | 	if err != nil {
158 | 		return nil, fmt.Errorf("unable to get update: %w", err)
159 | 	}
160 | 
161 | 	var update = bson.D{}
162 | 	err = bson.UnmarshalExtJSON([]byte(updateString), false, &update)
163 | 	if err != nil {
164 | 		return nil, fmt.Errorf("unable to unmarshal update string: %w", err)
165 | 	}
166 | 
167 | 	res, err := t.database.Collection(t.Collection).UpdateMany(ctx, filter, update, options.Update().SetUpsert(t.Upsert))
168 | 	if err != nil {
169 | 		return nil, fmt.Errorf("error updating collection: %w", err)
170 | 	}
171 | 
172 | 	return []any{res.ModifiedCount, res.UpsertedCount, res.MatchedCount}, nil
173 | }
174 | 
175 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
176 | 	return tools.ParseParams(t.AllParams, data, claims)
177 | }
178 | 
179 | func (t Tool) Manifest() tools.Manifest {
180 | 	return t.manifest
181 | }
182 | 
183 | func (t Tool) McpManifest() tools.McpManifest {
184 | 	return t.mcpManifest
185 | }
186 | 
187 | func (t Tool) Authorized(verifiedAuthServices []string) bool {
188 | 	return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
189 | }
190 | 
191 | func (t Tool) RequiresClientAuthorization() bool {
192 | 	return false
193 | }
194 | 
```

--------------------------------------------------------------------------------
/internal/server/mcp/v20250618/types.go:
--------------------------------------------------------------------------------

```go
  1 | // Copyright 2025 Google LLC
  2 | //
  3 | // Licensed under the Apache License, Version 2.0 (the "License");
  4 | // you may not use this file except in compliance with the License.
  5 | // You may obtain a copy of the License at
  6 | //
  7 | //     http://www.apache.org/licenses/LICENSE-2.0
  8 | //
  9 | // Unless required by applicable law or agreed to in writing, software
 10 | // distributed under the License is distributed on an "AS IS" BASIS,
 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12 | // See the License for the specific language governing permissions and
 13 | // limitations under the License.
 14 | 
 15 | package v20250618
 16 | 
 17 | import (
 18 | 	"github.com/googleapis/genai-toolbox/internal/server/mcp/jsonrpc"
 19 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 20 | )
 21 | 
 22 | // SERVER_NAME is the server name used in Implementation.
 23 | const SERVER_NAME = "Toolbox"
 24 | 
 25 | // PROTOCOL_VERSION is the version of the MCP protocol in this package.
 26 | const PROTOCOL_VERSION = "2025-06-18"
 27 | 
 28 | // methods that are supported.
 29 | const (
 30 | 	PING       = "ping"
 31 | 	TOOLS_LIST = "tools/list"
 32 | 	TOOLS_CALL = "tools/call"
 33 | )
 34 | 
 35 | /* Empty result */
 36 | 
 37 | // EmptyResult represents a response that indicates success but carries no data.
 38 | type EmptyResult jsonrpc.Result
 39 | 
 40 | /* Pagination */
 41 | 
 42 | // Cursor is an opaque token used to represent a cursor for pagination.
 43 | type Cursor string
 44 | 
 45 | type PaginatedRequest struct {
 46 | 	jsonrpc.Request
 47 | 	Params struct {
 48 | 		// An opaque token representing the current pagination position.
 49 | 		// If provided, the server should return results starting after this cursor.
 50 | 		Cursor Cursor `json:"cursor,omitempty"`
 51 | 	} `json:"params,omitempty"`
 52 | }
 53 | 
 54 | type PaginatedResult struct {
 55 | 	jsonrpc.Result
 56 | 	// An opaque token representing the pagination position after the last returned result.
 57 | 	// If present, there may be more results available.
 58 | 	NextCursor Cursor `json:"nextCursor,omitempty"`
 59 | }
 60 | 
 61 | /* Tools */
 62 | 
 63 | // Sent from the client to request a list of tools the server has.
 64 | type ListToolsRequest struct {
 65 | 	PaginatedRequest
 66 | }
 67 | 
 68 | // The server's response to a tools/list request from the client.
 69 | type ListToolsResult struct {
 70 | 	PaginatedResult
 71 | 	Tools []tools.McpManifest `json:"tools"`
 72 | }
 73 | 
 74 | // Used by the client to invoke a tool provided by the server.
 75 | type CallToolRequest struct {
 76 | 	jsonrpc.Request
 77 | 	Params struct {
 78 | 		Name      string         `json:"name"`
 79 | 		Arguments map[string]any `json:"arguments,omitempty"`
 80 | 	} `json:"params,omitempty"`
 81 | }
 82 | 
 83 | // The sender or recipient of messages and data in a conversation.
 84 | type Role string
 85 | 
 86 | const (
 87 | 	RoleUser      Role = "user"
 88 | 	RoleAssistant Role = "assistant"
 89 | )
 90 | 
 91 | // Base for objects that include optional annotations for the client.
 92 | // The client can use annotations to inform how objects are used or displayed
 93 | type Annotated struct {
 94 | 	Annotations *struct {
 95 | 		// Describes who the intended customer of this object or data is.
 96 | 		// It can include multiple entries to indicate content useful for multiple
 97 | 		// audiences (e.g., `["user", "assistant"]`).
 98 | 		Audience []Role `json:"audience,omitempty"`
 99 | 		// Describes how important this data is for operating the server.
100 | 		//
101 | 		// A value of 1 means "most important," and indicates that the data is
102 | 		// effectively required, while 0 means "least important," and indicates that
103 | 		// the data is entirely optional.
104 | 		//
105 | 		// @TJS-type number
106 | 		// @minimum 0
107 | 		// @maximum 1
108 | 		Priority float64 `json:"priority,omitempty"`
109 | 	} `json:"annotations,omitempty"`
110 | }
111 | 
112 | // TextContent represents text provided to or from an LLM.
113 | type TextContent struct {
114 | 	Annotated
115 | 	Type string `json:"type"`
116 | 	// The text content of the message.
117 | 	Text string `json:"text"`
118 | }
119 | 
120 | // The server's response to a tool call.
121 | //
122 | // Any errors that originate from the tool SHOULD be reported inside the result
123 | // object, with `isError` set to true, _not_ as an MCP protocol-level error
124 | // response. Otherwise, the LLM would not be able to see that an error occurred
125 | // and self-correct.
126 | //
127 | // However, any errors in _finding_ the tool, an error indicating that the
128 | // server does not support tool calls, or any other exceptional conditions,
129 | // should be reported as an MCP error response.
130 | type CallToolResult struct {
131 | 	jsonrpc.Result
132 | 	// Could be either a TextContent, ImageContent, or EmbeddedResources
133 | 	// For Toolbox, we will only be sending TextContent
134 | 	Content []TextContent `json:"content"`
135 | 	// Whether the tool call ended in an error.
136 | 	// If not set, this is assumed to be false (the call was successful).
137 | 	//
138 | 	// Any errors that originate from the tool SHOULD be reported inside the result
139 | 	// object, with `isError` set to true, _not_ as an MCP protocol-level error
140 | 	// response. Otherwise, the LLM would not be able to see that an error occurred
141 | 	// and self-correct.
142 | 	//
143 | 	// However, any errors in _finding_ the tool, an error indicating that the
144 | 	// server does not support tool calls, or any other exceptional conditions,
145 | 	// should be reported as an MCP error response.
146 | 	IsError bool `json:"isError,omitempty"`
147 | 	// An optional JSON object that represents the structured result of the tool call.
148 | 	StructuredContent map[string]any `json:"structuredContent,omitempty"`
149 | }
150 | 
151 | // Additional properties describing a Tool to clients.
152 | //
153 | // NOTE: all properties in ToolAnnotations are **hints**.
154 | // They are not guaranteed to provide a faithful description of
155 | // tool behavior (including descriptive properties like `title`).
156 | //
157 | // Clients should never make tool use decisions based on ToolAnnotations
158 | // received from untrusted servers.
159 | type ToolAnnotations struct {
160 | 	// A human-readable title for the tool.
161 | 	Title string `json:"title,omitempty"`
162 | 	// If true, the tool does not modify its environment.
163 | 	// Default: false
164 | 	ReadOnlyHint bool `json:"readOnlyHint,omitempty"`
165 | 	// If true, the tool may perform destructive updates to its environment.
166 | 	// If false, the tool performs only additive updates.
167 | 	// (This property is meaningful only when `readOnlyHint == false`)
168 | 	// Default: true
169 | 	DestructiveHint bool `json:"destructiveHint,omitempty"`
170 | 	// If true, calling the tool repeatedly with the same arguments
171 | 	// will have no additional effect on the its environment.
172 | 	// (This property is meaningful only when `readOnlyHint == false`)
173 | 	// Default: false
174 | 	IdempotentHint bool `json:"idempotentHint,omitempty"`
175 | 	// If true, this tool may interact with an "open world" of external
176 | 	// entities. If false, the tool's domain of interaction is closed.
177 | 	// For example, the world of a web search tool is open, whereas that
178 | 	// of a memory tool is not.
179 | 	// Default: true
180 | 	OpenWorldHint bool `json:"openWorldHint,omitempty"`
181 | }
182 | 
```

--------------------------------------------------------------------------------
/docs/en/resources/tools/spanner/spanner-list-tables.md:
--------------------------------------------------------------------------------

```markdown
  1 | ---
  2 | title: "spanner-list-tables"
  3 | type: docs
  4 | weight: 3
  5 | description: >
  6 |   A "spanner-list-tables" tool retrieves schema information about tables in a
  7 |   Google Cloud Spanner database.
  8 | ---
  9 | 
 10 | ## About
 11 | 
 12 | A `spanner-list-tables` tool retrieves comprehensive schema information about
 13 | tables in a Cloud Spanner database. It automatically adapts to the database
 14 | dialect (GoogleSQL or PostgreSQL) and returns detailed metadata including
 15 | columns, constraints, and indexes. It's compatible with:
 16 | 
 17 | - [spanner](../../sources/spanner.md)
 18 | 
 19 | This tool is read-only and executes pre-defined SQL queries against the
 20 | `INFORMATION_SCHEMA` tables to gather metadata. The tool automatically detects
 21 | the database dialect from the source configuration and uses the appropriate SQL
 22 | syntax.
 23 | 
 24 | ## Features
 25 | 
 26 | - **Automatic Dialect Detection**: Adapts queries based on whether the database
 27 |   uses GoogleSQL or PostgreSQL dialect
 28 | - **Comprehensive Schema Information**: Returns columns, data types, constraints,
 29 |   indexes, and table relationships
 30 | - **Flexible Filtering**: Can list all tables or filter by specific table names
 31 | - **Output Format Options**: Choose between simple (table names only) or detailed
 32 |   (full schema information) output
 33 | 
 34 | ## Example
 35 | 
 36 | ### Basic Usage - List All Tables
 37 | 
 38 | ```yaml
 39 | sources:
 40 |   my-spanner-db:
 41 |     kind: spanner
 42 |     project: ${SPANNER_PROJECT}
 43 |     instance: ${SPANNER_INSTANCE}
 44 |     database: ${SPANNER_DATABASE}
 45 |     dialect: googlesql  # or postgresql
 46 | 
 47 | tools:
 48 |   list_all_tables:
 49 |     kind: spanner-list-tables
 50 |     source: my-spanner-db
 51 |     description: Lists all tables with their complete schema information
 52 | ```
 53 | 
 54 | ### List Specific Tables
 55 | 
 56 | ```yaml
 57 | tools:
 58 |   list_specific_tables:
 59 |     kind: spanner-list-tables
 60 |     source: my-spanner-db
 61 |     description: |
 62 |       Lists schema information for specific tables.
 63 |       Example usage:
 64 |       {
 65 |         "table_names": "users,orders,products",
 66 |         "output_format": "detailed"
 67 |       }
 68 | ```
 69 | 
 70 | ## Parameters
 71 | 
 72 | The tool accepts two optional parameters:
 73 | 
 74 | | **parameter** | **type** | **default** | **description**                                                                                      |
 75 | |---------------|:--------:|:-----------:|------------------------------------------------------------------------------------------------------|
 76 | | table_names   |  string  |     ""      | Comma-separated list of table names to filter. If empty, lists all tables in user-accessible schemas |
 77 | | output_format |  string  | "detailed"  | Output format: "simple" returns only table names, "detailed" returns full schema information         |
 78 | 
 79 | ## Output Format
 80 | 
 81 | ### Simple Format
 82 | 
 83 | When `output_format` is set to "simple", the tool returns a minimal JSON structure:
 84 | 
 85 | ```json
 86 | [
 87 |   {
 88 |     "schema_name": "public",
 89 |     "object_name": "users",
 90 |     "object_details": "{\"name\":\"users\"}"
 91 |   },
 92 |   {
 93 |     "schema_name": "public",
 94 |     "object_name": "orders",
 95 |     "object_details": "{\"name\":\"orders\"}"
 96 |   }
 97 | ]
 98 | ```
 99 | 
100 | ### Detailed Format
101 | 
102 | When `output_format` is set to "detailed" (default), the tool returns
103 | comprehensive schema information:
104 | 
105 | ```json
106 | [
107 |   {
108 |     "schema_name": "public",
109 |     "object_name": "users",
110 |     "object_details": "{
111 |       \"schema_name\": \"public\",
112 |       \"object_name\": \"users\",
113 |       \"object_type\": \"BASE TABLE\",
114 |       \"columns\": [
115 |         {
116 |           \"column_name\": \"id\",
117 |           \"data_type\": \"INT64\",
118 |           \"ordinal_position\": 1,
119 |           \"is_not_nullable\": true,
120 |           \"column_default\": null
121 |         },
122 |         {
123 |           \"column_name\": \"email\",
124 |           \"data_type\": \"STRING(255)\",
125 |           \"ordinal_position\": 2,
126 |           \"is_not_nullable\": true,
127 |           \"column_default\": null
128 |         }
129 |       ],
130 |       \"constraints\": [
131 |         {
132 |           \"constraint_name\": \"PK_users\",
133 |           \"constraint_type\": \"PRIMARY KEY\",
134 |           \"constraint_definition\": \"PRIMARY KEY (id)\",
135 |           \"constraint_columns\": [\"id\"],
136 |           \"foreign_key_referenced_table\": null,
137 |           \"foreign_key_referenced_columns\": []
138 |         }
139 |       ],
140 |       \"indexes\": [
141 |         {
142 |           \"index_name\": \"idx_users_email\",
143 |           \"index_type\": \"INDEX\",
144 |           \"is_unique\": true,
145 |           \"is_null_filtered\": false,
146 |           \"interleaved_in_table\": null,
147 |           \"index_key_columns\": [
148 |             {\"column_name\": \"email\", \"ordering\": \"ASC\"}
149 |           ],
150 |           \"storing_columns\": []
151 |         }
152 |       ]
153 |     }"
154 |   }
155 | ]
156 | ```
157 | 
158 | ## Use Cases
159 | 
160 | 1. **Database Documentation**: Generate comprehensive documentation of your
161 |    database schema
162 | 2. **Schema Validation**: Verify that expected tables and columns exist
163 | 3. **Migration Planning**: Understand the current schema before making changes
164 | 4. **Development Tools**: Build tools that need to understand database structure
165 | 5. **Audit and Compliance**: Track schema changes and ensure compliance with
166 |    data governance policies
167 | 
168 | ## Example with Agent Integration
169 | 
170 | ```yaml
171 | sources:
172 |   spanner-db:
173 |     kind: spanner
174 |     project: my-project
175 |     instance: my-instance
176 |     database: my-database
177 |     dialect: googlesql
178 | 
179 | tools:
180 |   schema_inspector:
181 |     kind: spanner-list-tables
182 |     source: spanner-db
183 |     description: |
184 |       Use this tool to inspect database schema information.
185 |       You can:
186 |       - List all tables by leaving table_names empty
187 |       - Get specific table schemas by providing comma-separated table names
188 |       - Choose between simple (names only) or detailed (full schema) output
189 |       
190 |       Examples:
191 |       1. List all tables with details: {"output_format": "detailed"}
192 |       2. Get specific tables: {"table_names": "users,orders", "output_format": "detailed"}
193 |       3. Just get table names: {"output_format": "simple"}
194 | ```
195 | 
196 | ## Reference
197 | 
198 | | **field**    | **type** | **required** | **description**                                    |
199 | |--------------|:--------:|:------------:|----------------------------------------------------|
200 | | kind         |  string  |     true     | Must be "spanner-list-tables"                      |
201 | | source       |  string  |     true     | Name of the Spanner source to query                |
202 | | description  |  string  |    false     | Description of the tool that is passed to the LLM  |
203 | | authRequired | string[] |    false     | List of auth services required to invoke this tool |
204 | 
205 | ## Notes
206 | 
207 | - This tool is read-only and does not modify any data
208 | - The tool automatically handles both GoogleSQL and PostgreSQL dialects
209 | - Large databases with many tables may take longer to query
210 | 
```

--------------------------------------------------------------------------------
/docs/en/resources/sources/cloud-sql-pg.md:
--------------------------------------------------------------------------------

```markdown
  1 | ---
  2 | title: "Cloud SQL for PostgreSQL"
  3 | linkTitle: "Cloud SQL (Postgres)"
  4 | type: docs
  5 | weight: 1
  6 | description: >
  7 |   Cloud SQL for PostgreSQL is a fully-managed database service for Postgres.
  8 | 
  9 | ---
 10 | 
 11 | ## About
 12 | 
 13 | [Cloud SQL for PostgreSQL][csql-pg-docs] is a fully-managed database service
 14 | that helps you set up, maintain, manage, and administer your PostgreSQL
 15 | relational databases on Google Cloud Platform.
 16 | 
 17 | If you are new to Cloud SQL for PostgreSQL, you can try [creating and connecting
 18 | to a database by following these instructions][csql-pg-quickstart].
 19 | 
 20 | [csql-pg-docs]: https://cloud.google.com/sql/docs/postgres
 21 | [csql-pg-quickstart]:
 22 |     https://cloud.google.com/sql/docs/postgres/connect-instance-local-computer
 23 | 
 24 | ## Available Tools
 25 | 
 26 | - [`postgres-sql`](../tools/postgres/postgres-sql.md)
 27 |   Execute SQL queries as prepared statements in PostgreSQL.
 28 | 
 29 | - [`postgres-execute-sql`](../tools/postgres/postgres-execute-sql.md)
 30 |   Run parameterized SQL statements in PostgreSQL.
 31 | 
 32 | - [`postgres-list-tables`](../tools/postgres/postgres-list-tables.md)
 33 |   List tables in a PostgreSQL database.
 34 | 
 35 | - [`postgres-list-active-queries`](../tools/postgres/postgres-list-active-queries.md)
 36 |   List active queries in a PostgreSQL database.
 37 | 
 38 | - [`postgres-list-available-extensions`](../tools/postgres/postgres-list-available-extensions.md)
 39 |   List available extensions for installation in a PostgreSQL database.
 40 | 
 41 | - [`postgres-list-installed-extensions`](../tools/postgres/postgres-list-installed-extensions.md)
 42 |   List installed extensions in a PostgreSQL database.
 43 | 
 44 | ### Pre-built Configurations
 45 | 
 46 | - [Cloud SQL for Postgres using
 47 |   MCP](https://googleapis.github.io/genai-toolbox/how-to/connect-ide/cloud_sql_pg_mcp/)
 48 | Connect your IDE to Cloud SQL for Postgres using Toolbox.
 49 | 
 50 | 
 51 | ## Requirements
 52 | 
 53 | ### IAM Permissions
 54 | 
 55 | By default, this source uses the [Cloud SQL Go Connector][csql-go-conn] to
 56 | authorize and establish mTLS connections to your Cloud SQL instance. The Go
 57 | connector uses your [Application Default Credentials (ADC)][adc] to authorize
 58 | your connection to Cloud SQL.
 59 | 
 60 | In addition to [setting the ADC for your server][set-adc], you need to ensure
 61 | the IAM identity has been given the following IAM roles (or corresponding
 62 | permissions):
 63 | 
 64 | - `roles/cloudsql.client`
 65 | 
 66 | {{< notice tip >}}
 67 | If you are connecting from Compute Engine, make sure your VM
 68 | also has the [proper
 69 | scope](https://cloud.google.com/compute/docs/access/service-accounts#accesscopesiam)
 70 | to connect using the Cloud SQL Admin API.
 71 | {{< /notice >}}
 72 | 
 73 | [csql-go-conn]: <https://github.com/GoogleCloudPlatform/cloud-sql-go-connector>
 74 | [adc]: <https://cloud.google.com/docs/authentication#adc>
 75 | [set-adc]: <https://cloud.google.com/docs/authentication/provide-credentials-adc>
 76 | 
 77 | ### Networking
 78 | 
 79 | Cloud SQL supports connecting over both from external networks via the internet
 80 | ([public IP][public-ip]), and internal networks ([private IP][private-ip]).
 81 | For more information on choosing between the two options, see the Cloud SQL page
 82 | [Connection overview][conn-overview].
 83 | 
 84 | You can configure the `ipType` parameter in your source configuration to
 85 | `public` or `private` to match your cluster's configuration. Regardless of which
 86 | you choose, all connections use IAM-based authorization and are encrypted with
 87 | mTLS.
 88 | 
 89 | [private-ip]: https://cloud.google.com/sql/docs/postgres/configure-private-ip
 90 | [public-ip]: https://cloud.google.com/sql/docs/postgres/configure-ip
 91 | [conn-overview]: https://cloud.google.com/sql/docs/postgres/connect-overview
 92 | 
 93 | ### Authentication
 94 | 
 95 | This source supports both password-based authentication and IAM
 96 | authentication (using your [Application Default Credentials][adc]).
 97 | 
 98 | #### Standard Authentication
 99 | 
100 | To connect using user/password, [create
101 | a PostgreSQL user][cloudsql-users] and input your credentials in the `user` and
102 | `password` fields.
103 | 
104 | ```yaml
105 | user: ${USER_NAME}
106 | password: ${PASSWORD}
107 | ```
108 | 
109 | #### IAM Authentication
110 | 
111 | To connect using IAM authentication:
112 | 
113 | 1. Prepare your database instance and user following this [guide][iam-guide].
114 | 2. You could choose one of the two ways to log in:
115 |     - Specify your IAM email as the `user`.
116 |     - Leave your `user` field blank. Toolbox will fetch the [ADC][adc]
117 |       automatically and log in using the email associated with it.
118 | 
119 | 3. Leave the `password` field blank.
120 | 
121 | [iam-guide]: https://cloud.google.com/sql/docs/postgres/iam-logins
122 | [cloudsql-users]: https://cloud.google.com/sql/docs/postgres/create-manage-users
123 | 
124 | ## Example
125 | 
126 | ```yaml
127 | sources:
128 |     my-cloud-sql-pg-source:
129 |         kind: cloud-sql-postgres
130 |         project: my-project-id
131 |         region: us-central1
132 |         instance: my-instance
133 |         database: my_db
134 |         user: ${USER_NAME}
135 |         password: ${PASSWORD}
136 |         # ipType: "private"
137 | ```
138 | 
139 | {{< notice tip >}}
140 | Use environment variable replacement with the format ${ENV_NAME}
141 | instead of hardcoding your secrets into the configuration file.
142 | {{< /notice >}}
143 | 
144 | ## Reference
145 | 
146 | | **field** | **type** | **required** | **description**                                                                                                          |
147 | |-----------|:--------:|:------------:|--------------------------------------------------------------------------------------------------------------------------|
148 | | kind      |  string  |     true     | Must be "cloud-sql-postgres".                                                                                            |
149 | | project   |  string  |     true     | Id of the GCP project that the cluster was created in (e.g. "my-project-id").                                            |
150 | | region    |  string  |     true     | Name of the GCP region that the cluster was created in (e.g. "us-central1").                                             |
151 | | instance  |  string  |     true     | Name of the Cloud SQL instance within the cluster (e.g. "my-instance").                                                  |
152 | | database  |  string  |     true     | Name of the Postgres database to connect to (e.g. "my_db").                                                              |
153 | | 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. |
154 | | password  |  string  |    false     | Password of the Postgres user (e.g. "my-password"). Defaults to attempting IAM authentication if unspecified.            |
155 | | ipType    |  string  |    false     | IP Type of the Cloud SQL instance; must be one of `public`, `private`, or `psc`. Default: `public`.                      |
156 | 
```

--------------------------------------------------------------------------------
/internal/tools/oracle/oracleexecutesql/oracleexecutesql.go:
--------------------------------------------------------------------------------

```go
  1 | // Copyright © 2025, Oracle and/or its affiliates.
  2 | 
  3 | package oracleexecutesql
  4 | 
  5 | import (
  6 | 	"context"
  7 | 	"database/sql"
  8 | 	"encoding/json"
  9 | 	"fmt"
 10 | 	"strings"
 11 | 
 12 | 	yaml "github.com/goccy/go-yaml"
 13 | 	"github.com/googleapis/genai-toolbox/internal/sources"
 14 | 	"github.com/googleapis/genai-toolbox/internal/sources/oracle"
 15 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 16 | 	"github.com/googleapis/genai-toolbox/internal/util"
 17 | )
 18 | 
 19 | const kind string = "oracle-execute-sql"
 20 | 
 21 | func init() {
 22 | 	if !tools.Register(kind, newConfig) {
 23 | 		panic(fmt.Sprintf("tool kind %q already registered", kind))
 24 | 	}
 25 | }
 26 | 
 27 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
 28 | 	actual := Config{Name: name}
 29 | 	if err := decoder.DecodeContext(ctx, &actual); err != nil {
 30 | 		return nil, err
 31 | 	}
 32 | 	return actual, nil
 33 | }
 34 | 
 35 | type compatibleSource interface {
 36 | 	OracleDB() *sql.DB
 37 | }
 38 | 
 39 | // validate compatible sources are still compatible
 40 | var _ compatibleSource = &oracle.Source{}
 41 | 
 42 | var compatibleSources = [...]string{oracle.SourceKind}
 43 | 
 44 | type Config struct {
 45 | 	Name         string   `yaml:"name" validate:"required"`
 46 | 	Kind         string   `yaml:"kind" validate:"required"`
 47 | 	Source       string   `yaml:"source" validate:"required"`
 48 | 	Description  string   `yaml:"description" validate:"required"`
 49 | 	AuthRequired []string `yaml:"authRequired"`
 50 | }
 51 | 
 52 | // validate interface
 53 | var _ tools.ToolConfig = Config{}
 54 | 
 55 | func (cfg Config) ToolConfigKind() string {
 56 | 	return kind
 57 | }
 58 | 
 59 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
 60 | 	// verify source exists
 61 | 	rawS, ok := srcs[cfg.Source]
 62 | 	if !ok {
 63 | 		return nil, fmt.Errorf("no source named %q configured", cfg.Source)
 64 | 	}
 65 | 
 66 | 	// verify the source is compatible
 67 | 	s, ok := rawS.(compatibleSource)
 68 | 	if !ok {
 69 | 		return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
 70 | 	}
 71 | 
 72 | 	sqlParameter := tools.NewStringParameter("sql", "The SQL to execute.")
 73 | 	parameters := tools.Parameters{sqlParameter}
 74 | 
 75 | 	mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
 76 | 
 77 | 	// finish tool setup
 78 | 	t := Tool{
 79 | 		Name:         cfg.Name,
 80 | 		Kind:         kind,
 81 | 		Parameters:   parameters,
 82 | 		AuthRequired: cfg.AuthRequired,
 83 | 		Pool:         s.OracleDB(),
 84 | 		manifest:     tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired},
 85 | 		mcpManifest:  mcpManifest,
 86 | 	}
 87 | 	return t, nil
 88 | }
 89 | 
 90 | // validate interface
 91 | var _ tools.Tool = Tool{}
 92 | 
 93 | type Tool struct {
 94 | 	Name         string           `yaml:"name"`
 95 | 	Kind         string           `yaml:"kind"`
 96 | 	AuthRequired []string         `yaml:"authRequired"`
 97 | 	Parameters   tools.Parameters `yaml:"parameters"`
 98 | 
 99 | 	Pool        *sql.DB
100 | 	manifest    tools.Manifest
101 | 	mcpManifest tools.McpManifest
102 | }
103 | 
104 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
105 | 	paramsMap := params.AsMap()
106 | 	sqlParam, ok := paramsMap["sql"].(string)
107 | 	if !ok {
108 | 		return nil, fmt.Errorf("unable to get cast %s", paramsMap["sql"])
109 | 	}
110 | 
111 | 	// Log the query executed for debugging.
112 | 	logger, err := util.LoggerFromContext(ctx)
113 | 	if err != nil {
114 | 		return nil, fmt.Errorf("error getting logger: %s", err)
115 | 	}
116 | 	logger.DebugContext(ctx, "executing `%s` tool query: %s", kind, sqlParam)
117 | 
118 | 	results, err := t.Pool.QueryContext(ctx, sqlParam)
119 | 	if err != nil {
120 | 		return nil, fmt.Errorf("unable to execute query: %w", err)
121 | 	}
122 | 	defer results.Close()
123 | 
124 | 	// If Columns() errors, it might be a DDL/DML without an OUTPUT clause.
125 | 	// We proceed, and results.Err() will catch actual query execution errors.
126 | 	// 'out' will remain nil if cols is empty or err is not nil here.
127 | 	cols, _ := results.Columns()
128 | 
129 | 	// Get Column types
130 | 	colTypes, err := results.ColumnTypes()
131 | 	if err != nil {
132 | 		if err := results.Err(); err != nil {
133 | 			return nil, fmt.Errorf("query execution error: %w", err)
134 | 		}
135 | 		return []any{}, nil
136 | 	}
137 | 
138 | 	var out []any
139 | 	for results.Next() {
140 | 		// Create slice to hold values
141 | 		values := make([]any, len(cols))
142 | 		for i, colType := range colTypes {
143 | 			// Based on the database type, we prepare a pointer to a Go type.
144 | 			switch strings.ToUpper(colType.DatabaseTypeName()) {
145 | 			case "NUMBER", "FLOAT", "BINARY_FLOAT", "BINARY_DOUBLE":
146 | 				if _, scale, ok := colType.DecimalSize(); ok && scale == 0 {
147 | 					// Scale is 0, treat as an integer.
148 | 					values[i] = new(sql.NullInt64)
149 | 				} else {
150 | 					// Scale is non-zero or unknown, treat as a float.
151 | 					values[i] = new(sql.NullFloat64)
152 | 				}
153 | 			case "DATE", "TIMESTAMP", "TIMESTAMP WITH TIME ZONE", "TIMESTAMP WITH LOCAL TIME ZONE":
154 | 				values[i] = new(sql.NullTime)
155 | 			case "JSON":
156 | 				values[i] = new(sql.RawBytes)
157 | 			default:
158 | 				values[i] = new(sql.NullString)
159 | 			}
160 | 		}
161 | 
162 | 		if err := results.Scan(values...); err != nil {
163 | 			return nil, fmt.Errorf("unable to scan row: %w", err)
164 | 		}
165 | 
166 | 		vMap := make(map[string]any)
167 | 		for i, col := range cols {
168 | 			receiver := values[i]
169 | 
170 | 			// Dereference the pointer and check for validity (not NULL).
171 | 			switch v := receiver.(type) {
172 | 			case *sql.NullInt64:
173 | 				if v.Valid {
174 | 					vMap[col] = v.Int64
175 | 				} else {
176 | 					vMap[col] = nil
177 | 				}
178 | 			case *sql.NullFloat64:
179 | 				if v.Valid {
180 | 					vMap[col] = v.Float64
181 | 				} else {
182 | 					vMap[col] = nil
183 | 				}
184 | 			case *sql.NullString:
185 | 				if v.Valid {
186 | 					vMap[col] = v.String
187 | 				} else {
188 | 					vMap[col] = nil
189 | 				}
190 | 			case *sql.NullTime:
191 | 				if v.Valid {
192 | 					vMap[col] = v.Time
193 | 				} else {
194 | 					vMap[col] = nil
195 | 				}
196 | 			case *sql.RawBytes:
197 | 				if *v != nil {
198 | 					var unmarshaledData any
199 | 					if err := json.Unmarshal(*v, &unmarshaledData); err != nil {
200 | 						return nil, fmt.Errorf("unable to unmarshal json data for column %s", col)
201 | 					}
202 | 					vMap[col] = unmarshaledData
203 | 				} else {
204 | 					vMap[col] = nil
205 | 				}
206 | 			default:
207 | 				return nil, fmt.Errorf("unexpected receiver type: %T", v)
208 | 			}
209 | 		}
210 | 		out = append(out, vMap)
211 | 	}
212 | 
213 | 	if err := results.Err(); err != nil {
214 | 		return nil, fmt.Errorf("errors encountered during query execution or row processing: %w", err)
215 | 	}
216 | 
217 | 	return out, nil
218 | }
219 | 
220 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
221 | 	return tools.ParseParams(t.Parameters, data, claims)
222 | }
223 | 
224 | func (t Tool) Manifest() tools.Manifest {
225 | 	return t.manifest
226 | }
227 | 
228 | func (t Tool) McpManifest() tools.McpManifest {
229 | 	return t.mcpManifest
230 | }
231 | 
232 | func (t Tool) Authorized(verifiedAuthServices []string) bool {
233 | 	return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
234 | }
235 | 
236 | func (t Tool) RequiresClientAuthorization() bool {
237 | 	return false
238 | }
239 | 
```

--------------------------------------------------------------------------------
/internal/tools/dataplex/dataplexsearchaspecttypes/dataplexsearchaspecttypes.go:
--------------------------------------------------------------------------------

```go
  1 | // Copyright 2025 Google LLC
  2 | //
  3 | // Licensed under the Apache License, Version 2.0 (the "License");
  4 | // you may not use this file except in compliance with the License.
  5 | // You may obtain a copy of the License at
  6 | //
  7 | //     http://www.apache.org/licenses/LICENSE-2.0
  8 | //
  9 | // Unless required by applicable law or agreed to in writing, software
 10 | // distributed under the License is distributed on an "AS IS" BASIS,
 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12 | // See the License for the specific language governing permissions and
 13 | // limitations under the License.
 14 | 
 15 | package dataplexsearchaspecttypes
 16 | 
 17 | import (
 18 | 	"context"
 19 | 	"fmt"
 20 | 
 21 | 	dataplexapi "cloud.google.com/go/dataplex/apiv1"
 22 | 	dataplexpb "cloud.google.com/go/dataplex/apiv1/dataplexpb"
 23 | 	"github.com/cenkalti/backoff/v5"
 24 | 	"github.com/goccy/go-yaml"
 25 | 	"github.com/googleapis/genai-toolbox/internal/sources"
 26 | 	dataplexds "github.com/googleapis/genai-toolbox/internal/sources/dataplex"
 27 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 28 | )
 29 | 
 30 | const kind string = "dataplex-search-aspect-types"
 31 | 
 32 | func init() {
 33 | 	if !tools.Register(kind, newConfig) {
 34 | 		panic(fmt.Sprintf("tool kind %q already registered", kind))
 35 | 	}
 36 | }
 37 | 
 38 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
 39 | 	actual := Config{Name: name}
 40 | 	if err := decoder.DecodeContext(ctx, &actual); err != nil {
 41 | 		return nil, err
 42 | 	}
 43 | 	return actual, nil
 44 | }
 45 | 
 46 | type compatibleSource interface {
 47 | 	CatalogClient() *dataplexapi.CatalogClient
 48 | 	ProjectID() string
 49 | }
 50 | 
 51 | // validate compatible sources are still compatible
 52 | var _ compatibleSource = &dataplexds.Source{}
 53 | 
 54 | var compatibleSources = [...]string{dataplexds.SourceKind}
 55 | 
 56 | type Config struct {
 57 | 	Name         string   `yaml:"name" validate:"required"`
 58 | 	Kind         string   `yaml:"kind" validate:"required"`
 59 | 	Source       string   `yaml:"source" validate:"required"`
 60 | 	Description  string   `yaml:"description"`
 61 | 	AuthRequired []string `yaml:"authRequired"`
 62 | }
 63 | 
 64 | // validate interface
 65 | var _ tools.ToolConfig = Config{}
 66 | 
 67 | func (cfg Config) ToolConfigKind() string {
 68 | 	return kind
 69 | }
 70 | 
 71 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
 72 | 	// Initialize the search configuration with the provided sources
 73 | 	rawS, ok := srcs[cfg.Source]
 74 | 	if !ok {
 75 | 		return nil, fmt.Errorf("no source named %q configured", cfg.Source)
 76 | 	}
 77 | 	// verify the source is compatible
 78 | 	s, ok := rawS.(compatibleSource)
 79 | 	if !ok {
 80 | 		return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
 81 | 	}
 82 | 
 83 | 	query := tools.NewStringParameter("query", "The query against which aspect type should be matched.")
 84 | 	pageSize := tools.NewIntParameterWithDefault("pageSize", 5, "Number of returned aspect types in the search page.")
 85 | 	orderBy := tools.NewStringParameterWithDefault("orderBy", "relevance", "Specifies the ordering of results. Supported values are: relevance, last_modified_timestamp, last_modified_timestamp asc")
 86 | 	parameters := tools.Parameters{query, pageSize, orderBy}
 87 | 
 88 | 	mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
 89 | 
 90 | 	t := Tool{
 91 | 		Name:          cfg.Name,
 92 | 		Kind:          kind,
 93 | 		Parameters:    parameters,
 94 | 		AuthRequired:  cfg.AuthRequired,
 95 | 		CatalogClient: s.CatalogClient(),
 96 | 		ProjectID:     s.ProjectID(),
 97 | 		manifest: tools.Manifest{
 98 | 			Description:  cfg.Description,
 99 | 			Parameters:   parameters.Manifest(),
100 | 			AuthRequired: cfg.AuthRequired,
101 | 		},
102 | 		mcpManifest: mcpManifest,
103 | 	}
104 | 	return t, nil
105 | }
106 | 
107 | type Tool struct {
108 | 	Name          string
109 | 	Kind          string
110 | 	Parameters    tools.Parameters
111 | 	AuthRequired  []string
112 | 	CatalogClient *dataplexapi.CatalogClient
113 | 	ProjectID     string
114 | 	manifest      tools.Manifest
115 | 	mcpManifest   tools.McpManifest
116 | }
117 | 
118 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
119 | 	// Invoke the tool with the provided parameters
120 | 	paramsMap := params.AsMap()
121 | 	query, _ := paramsMap["query"].(string)
122 | 	pageSize := int32(paramsMap["pageSize"].(int))
123 | 	orderBy, _ := paramsMap["orderBy"].(string)
124 | 
125 | 	// Create SearchEntriesRequest with the provided parameters
126 | 	req := &dataplexpb.SearchEntriesRequest{
127 | 		Query:          query + " type=projects/dataplex-types/locations/global/entryTypes/aspecttype",
128 | 		Name:           fmt.Sprintf("projects/%s/locations/global", t.ProjectID),
129 | 		PageSize:       pageSize,
130 | 		OrderBy:        orderBy,
131 | 		SemanticSearch: true,
132 | 	}
133 | 
134 | 	// Perform the search using the CatalogClient - this will return an iterator
135 | 	it := t.CatalogClient.SearchEntries(ctx, req)
136 | 	if it == nil {
137 | 		return nil, fmt.Errorf("failed to create search entries iterator for project %q", t.ProjectID)
138 | 	}
139 | 
140 | 	// Create an instance of exponential backoff with default values for retrying GetAspectType calls
141 | 	// InitialInterval, RandomizationFactor, Multiplier, MaxInterval = 500 ms, 0.5, 1.5, 60 s
142 | 	getAspectBackOff := backoff.NewExponentialBackOff()
143 | 
144 | 	// Iterate through the search results and call GetAspectType for each result using the resource name
145 | 	var results []*dataplexpb.AspectType
146 | 	for {
147 | 		entry, err := it.Next()
148 | 		if err != nil {
149 | 			break
150 | 		}
151 | 		resourceName := entry.DataplexEntry.GetEntrySource().Resource
152 | 		getAspectTypeReq := &dataplexpb.GetAspectTypeRequest{
153 | 			Name: resourceName,
154 | 		}
155 | 
156 | 		operation := func() (*dataplexpb.AspectType, error) {
157 | 			aspectType, err := t.CatalogClient.GetAspectType(ctx, getAspectTypeReq)
158 | 			if err != nil {
159 | 				return nil, fmt.Errorf("failed to get aspect type for entry %q: %w", resourceName, err)
160 | 			}
161 | 			return aspectType, nil
162 | 		}
163 | 
164 | 		// Retry the GetAspectType operation with exponential backoff
165 | 		aspectType, err := backoff.Retry(ctx, operation, backoff.WithBackOff(getAspectBackOff))
166 | 		if err != nil {
167 | 			return nil, fmt.Errorf("failed to get aspect type after retries for entry %q: %w", resourceName, err)
168 | 		}
169 | 
170 | 		results = append(results, aspectType)
171 | 	}
172 | 	return results, nil
173 | }
174 | 
175 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
176 | 	// Parse parameters from the provided data
177 | 	return tools.ParseParams(t.Parameters, data, claims)
178 | }
179 | 
180 | func (t Tool) Manifest() tools.Manifest {
181 | 	// Returns the tool manifest
182 | 	return t.manifest
183 | }
184 | 
185 | func (t Tool) McpManifest() tools.McpManifest {
186 | 	// Returns the tool MCP manifest
187 | 	return t.mcpManifest
188 | }
189 | 
190 | func (t Tool) Authorized(verifiedAuthServices []string) bool {
191 | 	return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
192 | }
193 | 
194 | func (t Tool) RequiresClientAuthorization() bool {
195 | 	return false
196 | }
197 | 
```

--------------------------------------------------------------------------------
/internal/tools/tidb/tidbsql/tidbsql.go:
--------------------------------------------------------------------------------

```go
  1 | // Copyright 2025 Google LLC
  2 | //
  3 | // Licensed under the Apache License, Version 2.0 (the "License");
  4 | // you may not use this file except in compliance with the License.
  5 | // You may obtain a copy of the License at
  6 | //
  7 | //     http://www.apache.org/licenses/LICENSE-2.0
  8 | //
  9 | // Unless required by applicable law or agreed to in writing, software
 10 | // distributed under the License is distributed on an "AS IS" BASIS,
 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12 | // See the License for the specific language governing permissions and
 13 | // limitations under the License.
 14 | 
 15 | package tidbsql
 16 | 
 17 | import (
 18 | 	"context"
 19 | 	"database/sql"
 20 | 	"encoding/json"
 21 | 	"fmt"
 22 | 
 23 | 	yaml "github.com/goccy/go-yaml"
 24 | 	"github.com/googleapis/genai-toolbox/internal/sources"
 25 | 	"github.com/googleapis/genai-toolbox/internal/sources/tidb"
 26 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 27 | )
 28 | 
 29 | const kind string = "tidb-sql"
 30 | 
 31 | func init() {
 32 | 	if !tools.Register(kind, newConfig) {
 33 | 		panic(fmt.Sprintf("tool kind %q already registered", kind))
 34 | 	}
 35 | }
 36 | 
 37 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
 38 | 	actual := Config{Name: name}
 39 | 	if err := decoder.DecodeContext(ctx, &actual); err != nil {
 40 | 		return nil, err
 41 | 	}
 42 | 	return actual, nil
 43 | }
 44 | 
 45 | type compatibleSource interface {
 46 | 	TiDBPool() *sql.DB
 47 | }
 48 | 
 49 | // validate compatible sources are still compatible
 50 | var _ compatibleSource = &tidb.Source{}
 51 | 
 52 | var compatibleSources = [...]string{tidb.SourceKind}
 53 | 
 54 | type Config struct {
 55 | 	Name               string           `yaml:"name" validate:"required"`
 56 | 	Kind               string           `yaml:"kind" validate:"required"`
 57 | 	Source             string           `yaml:"source" validate:"required"`
 58 | 	Description        string           `yaml:"description" validate:"required"`
 59 | 	Statement          string           `yaml:"statement" validate:"required"`
 60 | 	AuthRequired       []string         `yaml:"authRequired"`
 61 | 	Parameters         tools.Parameters `yaml:"parameters"`
 62 | 	TemplateParameters tools.Parameters `yaml:"templateParameters"`
 63 | }
 64 | 
 65 | // validate interface
 66 | var _ tools.ToolConfig = Config{}
 67 | 
 68 | func (cfg Config) ToolConfigKind() string {
 69 | 	return kind
 70 | }
 71 | 
 72 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
 73 | 	// verify source exists
 74 | 	rawS, ok := srcs[cfg.Source]
 75 | 	if !ok {
 76 | 		return nil, fmt.Errorf("no source named %q configured", cfg.Source)
 77 | 	}
 78 | 
 79 | 	// verify the source is compatible
 80 | 	s, ok := rawS.(compatibleSource)
 81 | 	if !ok {
 82 | 		return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
 83 | 	}
 84 | 
 85 | 	allParameters, paramManifest, err := tools.ProcessParameters(cfg.TemplateParameters, cfg.Parameters)
 86 | 	if err != nil {
 87 | 		return nil, err
 88 | 	}
 89 | 
 90 | 	mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, allParameters)
 91 | 
 92 | 	// finish tool setup
 93 | 	t := Tool{
 94 | 		Name:               cfg.Name,
 95 | 		Kind:               kind,
 96 | 		Parameters:         cfg.Parameters,
 97 | 		TemplateParameters: cfg.TemplateParameters,
 98 | 		AllParams:          allParameters,
 99 | 		Statement:          cfg.Statement,
100 | 		AuthRequired:       cfg.AuthRequired,
101 | 		Pool:               s.TiDBPool(),
102 | 		manifest:           tools.Manifest{Description: cfg.Description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired},
103 | 		mcpManifest:        mcpManifest,
104 | 	}
105 | 	return t, nil
106 | }
107 | 
108 | // validate interface
109 | var _ tools.Tool = Tool{}
110 | 
111 | type Tool struct {
112 | 	Name               string           `yaml:"name"`
113 | 	Kind               string           `yaml:"kind"`
114 | 	AuthRequired       []string         `yaml:"authRequired"`
115 | 	Parameters         tools.Parameters `yaml:"parameters"`
116 | 	TemplateParameters tools.Parameters `yaml:"templateParameters"`
117 | 	AllParams          tools.Parameters `yaml:"allParams"`
118 | 
119 | 	Pool        *sql.DB
120 | 	Statement   string
121 | 	manifest    tools.Manifest
122 | 	mcpManifest tools.McpManifest
123 | }
124 | 
125 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
126 | 	paramsMap := params.AsMap()
127 | 	newStatement, err := tools.ResolveTemplateParams(t.TemplateParameters, t.Statement, paramsMap)
128 | 	if err != nil {
129 | 		return nil, fmt.Errorf("unable to extract template params %w", err)
130 | 	}
131 | 
132 | 	newParams, err := tools.GetParams(t.Parameters, paramsMap)
133 | 	if err != nil {
134 | 		return nil, fmt.Errorf("unable to extract standard params %w", err)
135 | 	}
136 | 
137 | 	sliceParams := newParams.AsSlice()
138 | 	results, err := t.Pool.QueryContext(ctx, newStatement, sliceParams...)
139 | 	if err != nil {
140 | 		return nil, fmt.Errorf("unable to execute query: %w", err)
141 | 	}
142 | 
143 | 	cols, err := results.Columns()
144 | 	if err != nil {
145 | 		return nil, fmt.Errorf("unable to retrieve rows column name: %w", err)
146 | 	}
147 | 
148 | 	// create an array of values for each column, which can be re-used to scan each row
149 | 	rawValues := make([]any, len(cols))
150 | 	values := make([]any, len(cols))
151 | 	for i := range rawValues {
152 | 		values[i] = &rawValues[i]
153 | 	}
154 | 	defer results.Close()
155 | 
156 | 	colTypes, err := results.ColumnTypes()
157 | 	if err != nil {
158 | 		return nil, fmt.Errorf("unable to get column types: %w", err)
159 | 	}
160 | 
161 | 	var out []any
162 | 	for results.Next() {
163 | 		err := results.Scan(values...)
164 | 		if err != nil {
165 | 			return nil, fmt.Errorf("unable to parse row: %w", err)
166 | 		}
167 | 		vMap := make(map[string]any)
168 | 		for i, name := range cols {
169 | 			val := rawValues[i]
170 | 			if val == nil {
171 | 				vMap[name] = nil
172 | 				continue
173 | 			}
174 | 
175 | 			// mysql driver return []uint8 type for "TEXT", "VARCHAR", and "NVARCHAR"
176 | 			// we'll need to cast it back to string
177 | 			switch colTypes[i].DatabaseTypeName() {
178 | 			case "JSON":
179 | 				// unmarshal JSON data before storing to prevent double marshaling
180 | 				var unmarshaledData any
181 | 				err := json.Unmarshal(val.([]byte), &unmarshaledData)
182 | 				if err != nil {
183 | 					return nil, fmt.Errorf("unable to unmarshal json data %s", val)
184 | 				}
185 | 				vMap[name] = unmarshaledData
186 | 			case "TEXT", "VARCHAR", "NVARCHAR":
187 | 				vMap[name] = string(val.([]byte))
188 | 			default:
189 | 				vMap[name] = val
190 | 			}
191 | 		}
192 | 		out = append(out, vMap)
193 | 	}
194 | 
195 | 	if err := results.Err(); err != nil {
196 | 		return nil, fmt.Errorf("errors encountered during row iteration: %w", err)
197 | 	}
198 | 
199 | 	return out, nil
200 | }
201 | 
202 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
203 | 	return tools.ParseParams(t.AllParams, data, claims)
204 | }
205 | 
206 | func (t Tool) Manifest() tools.Manifest {
207 | 	return t.manifest
208 | }
209 | 
210 | func (t Tool) McpManifest() tools.McpManifest {
211 | 	return t.mcpManifest
212 | }
213 | 
214 | func (t Tool) Authorized(verifiedAuthServices []string) bool {
215 | 	return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
216 | }
217 | 
218 | func (t Tool) RequiresClientAuthorization() bool {
219 | 	return false
220 | }
221 | 
```

--------------------------------------------------------------------------------
/tests/cloudsql/cloud_sql_get_instances_test.go:
--------------------------------------------------------------------------------

```go
  1 | // Copyright 2025 Google LLC
  2 | //
  3 | // Licensed under the Apache License, Version 2.0 (the "License");
  4 | // you may not use this file except in compliance with the License.
  5 | // You may obtain a copy of the License at
  6 | //
  7 | //      http://www.apache.org/licenses/LICENSE-2.0
  8 | //
  9 | // Unless required by applicable law or agreed to in writing, software
 10 | // distributed under the License is distributed on an "AS IS" BASIS,
 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12 | // See the License for the specific language governing permissions and
 13 | // limitations under the License.
 14 | 
 15 | package cloudsql
 16 | 
 17 | import (
 18 | 	"bytes"
 19 | 	"context"
 20 | 	"encoding/json"
 21 | 	"fmt"
 22 | 	"io"
 23 | 	"net/http"
 24 | 	"net/http/httptest"
 25 | 	"net/url"
 26 | 	"reflect"
 27 | 	"regexp"
 28 | 	"strings"
 29 | 	"sync"
 30 | 	"testing"
 31 | 	"time"
 32 | 
 33 | 	"github.com/googleapis/genai-toolbox/internal/testutils"
 34 | 	"github.com/googleapis/genai-toolbox/tests"
 35 | )
 36 | 
 37 | var (
 38 | 	getInstancesToolKind = "cloud-sql-get-instance"
 39 | )
 40 | 
 41 | type getInstancesTransport struct {
 42 | 	transport http.RoundTripper
 43 | 	url       *url.URL
 44 | }
 45 | 
 46 | func (t *getInstancesTransport) RoundTrip(req *http.Request) (*http.Response, error) {
 47 | 	if strings.HasPrefix(req.URL.String(), "https://sqladmin.googleapis.com") {
 48 | 		req.URL.Scheme = t.url.Scheme
 49 | 		req.URL.Host = t.url.Host
 50 | 	}
 51 | 	return t.transport.RoundTrip(req)
 52 | }
 53 | 
 54 | type instance struct {
 55 | 	Name string `json:"name"`
 56 | 	Kind string `json:"kind"`
 57 | }
 58 | 
 59 | type handler struct {
 60 | 	mu        sync.Mutex
 61 | 	instances map[string]*instance
 62 | 	t         *testing.T
 63 | }
 64 | 
 65 | func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 66 | 	h.mu.Lock()
 67 | 	defer h.mu.Unlock()
 68 | 
 69 | 	if !strings.Contains(r.UserAgent(), "genai-toolbox/") {
 70 | 		h.t.Errorf("User-Agent header not found")
 71 | 	}
 72 | 
 73 | 	if !strings.HasPrefix(r.URL.Path, "/v1/projects/") {
 74 | 		http.Error(w, "unexpected path", http.StatusBadRequest)
 75 | 		return
 76 | 	}
 77 | 
 78 | 	// The format is /v1/projects/{project}/instances/{instance_name}
 79 | 	// We only care about the instance_name for the test
 80 | 	parts := regexp.MustCompile("/").Split(r.URL.Path, -1)
 81 | 	instanceName := parts[len(parts)-1]
 82 | 
 83 | 	inst, ok := h.instances[instanceName]
 84 | 	if !ok {
 85 | 		http.NotFound(w, r)
 86 | 		return
 87 | 	}
 88 | 
 89 | 	w.Header().Set("Content-Type", "application/json")
 90 | 	if err := json.NewEncoder(w).Encode(inst); err != nil {
 91 | 		http.Error(w, err.Error(), http.StatusInternalServerError)
 92 | 	}
 93 | }
 94 | 
 95 | func TestGetInstancesToolEndpoints(t *testing.T) {
 96 | 	h := &handler{
 97 | 		instances: map[string]*instance{
 98 | 			"instance-1": {Name: "instance-1", Kind: "sql#instance"},
 99 | 		},
100 | 		t: t,
101 | 	}
102 | 	server := httptest.NewServer(h)
103 | 	defer server.Close()
104 | 
105 | 	serverURL, err := url.Parse(server.URL)
106 | 	if err != nil {
107 | 		t.Fatalf("failed to parse server URL: %v", err)
108 | 	}
109 | 
110 | 	originalTransport := http.DefaultClient.Transport
111 | 	if originalTransport == nil {
112 | 		originalTransport = http.DefaultTransport
113 | 	}
114 | 	http.DefaultClient.Transport = &getInstancesTransport{
115 | 		transport: originalTransport,
116 | 		url:       serverURL,
117 | 	}
118 | 	t.Cleanup(func() {
119 | 		http.DefaultClient.Transport = originalTransport
120 | 	})
121 | 
122 | 	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
123 | 	defer cancel()
124 | 
125 | 	var args []string
126 | 
127 | 	toolsFile := getToolsConfig()
128 | 	cmd, cleanup, err := tests.StartCmd(ctx, toolsFile, args...)
129 | 	if err != nil {
130 | 		t.Fatalf("command initialization returned an error: %s", err)
131 | 	}
132 | 	defer cleanup()
133 | 
134 | 	waitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
135 | 	defer cancel()
136 | 	out, err := testutils.WaitForString(waitCtx, regexp.MustCompile(`Server ready to serve`), cmd.Out)
137 | 	if err != nil {
138 | 		t.Logf("toolbox command logs: \n%s", out)
139 | 		t.Fatalf("toolbox didn't start successfully: %s", err)
140 | 	}
141 | 
142 | 	tcs := []struct {
143 | 		name          string
144 | 		toolName      string
145 | 		body          string
146 | 		want          string
147 | 		expectError   bool
148 | 		wantSubstring bool
149 | 	}{
150 | 		{
151 | 			name:     "successful get instance",
152 | 			toolName: "get-instance-1",
153 | 			body:     `{"projectId": "p1", "instanceId": "instance-1"}`,
154 | 			want:     `{"name":"instance-1","kind":"sql#instance"}`,
155 | 		},
156 | 		{
157 | 			name:        "failed get instance",
158 | 			toolName:    "get-instance-2",
159 | 			body:        `{"projectId": "p1", "instanceId": "instance-2"}`,
160 | 			expectError: true,
161 | 		},
162 | 	}
163 | 
164 | 	for _, tc := range tcs {
165 | 		t.Run(tc.name, func(t *testing.T) {
166 | 			api := fmt.Sprintf("http://127.0.0.1:5000/api/tool/%s/invoke", tc.toolName)
167 | 			req, err := http.NewRequest(http.MethodPost, api, bytes.NewBufferString(tc.body))
168 | 			if err != nil {
169 | 				t.Fatalf("unable to create request: %s", err)
170 | 			}
171 | 			req.Header.Add("Content-type", "application/json")
172 | 			resp, err := http.DefaultClient.Do(req)
173 | 			if err != nil {
174 | 				t.Fatalf("unable to send request: %s", err)
175 | 			}
176 | 			defer resp.Body.Close()
177 | 
178 | 			if tc.expectError {
179 | 				if resp.StatusCode == http.StatusOK {
180 | 					t.Fatal("expected error but got status 200")
181 | 				}
182 | 				return
183 | 			}
184 | 
185 | 			if resp.StatusCode != http.StatusOK {
186 | 				bodyBytes, _ := io.ReadAll(resp.Body)
187 | 				t.Fatalf("response status code is not 200, got %d: %s", resp.StatusCode, string(bodyBytes))
188 | 			}
189 | 
190 | 			var result struct {
191 | 				Result any `json:"result"`
192 | 			}
193 | 			if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
194 | 				t.Fatalf("failed to decode response: %v", err)
195 | 			}
196 | 
197 | 			var gotBytes []byte
198 | 			if s, ok := result.Result.(string); ok {
199 | 				gotBytes = []byte(s)
200 | 			} else {
201 | 				var err error
202 | 				gotBytes, err = json.Marshal(result.Result)
203 | 				if err != nil {
204 | 					t.Fatalf("failed to marshal result: %v", err)
205 | 				}
206 | 			}
207 | 
208 | 			if tc.wantSubstring {
209 | 				if !bytes.Contains(gotBytes, []byte(tc.want)) {
210 | 					t.Fatalf("unexpected result: got %q, want substring %q", string(gotBytes), tc.want)
211 | 				}
212 | 				return
213 | 			}
214 | 
215 | 			var got, want map[string]any
216 | 			if err := json.Unmarshal(gotBytes, &got); err != nil {
217 | 				t.Fatalf("failed to unmarshal result: %v", err)
218 | 			}
219 | 			if err := json.Unmarshal([]byte(tc.want), &want); err != nil {
220 | 				t.Fatalf("failed to unmarshal want: %v", err)
221 | 			}
222 | 
223 | 			if !reflect.DeepEqual(got, want) {
224 | 				t.Fatalf("unexpected result: got %+v, want %+v", got, want)
225 | 			}
226 | 		})
227 | 	}
228 | }
229 | 
230 | func getToolsConfig() map[string]any {
231 | 	return map[string]any{
232 | 		"sources": map[string]any{
233 | 			"my-cloud-sql-source": map[string]any{
234 | 				"kind": "cloud-sql-admin",
235 | 			},
236 | 			"my-invalid-cloud-sql-source": map[string]any{
237 | 				"kind":           "cloud-sql-admin",
238 | 				"useClientOAuth": true,
239 | 			},
240 | 		},
241 | 		"tools": map[string]any{
242 | 			"get-instance-1": map[string]any{
243 | 				"kind":        getInstancesToolKind,
244 | 				"description": "get instance 1",
245 | 				"source":      "my-cloud-sql-source",
246 | 			},
247 | 			"get-instance-2": map[string]any{
248 | 				"kind":        getInstancesToolKind,
249 | 				"description": "get instance 2",
250 | 				"source":      "my-invalid-cloud-sql-source",
251 | 			},
252 | 		},
253 | 	}
254 | }
255 | 
```

--------------------------------------------------------------------------------
/internal/tools/oceanbase/oceanbasesql/oceanbasesql.go:
--------------------------------------------------------------------------------

```go
  1 | // Copyright 2025 Google LLC
  2 | //
  3 | // Licensed under the Apache License, Version 2.0 (the "License");
  4 | // you may not use this file except in compliance with the License.
  5 | // You may obtain a copy of the License at
  6 | //
  7 | //     http://www.apache.org/licenses/LICENSE-2.0
  8 | //
  9 | // Unless required by applicable law or agreed to in writing, software
 10 | // distributed under the License is distributed on an "AS IS" BASIS,
 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12 | // See the License for the specific language governing permissions and
 13 | // limitations under the License.
 14 | 
 15 | package oceanbasesql
 16 | 
 17 | import (
 18 | 	"context"
 19 | 	"database/sql"
 20 | 	"fmt"
 21 | 
 22 | 	yaml "github.com/goccy/go-yaml"
 23 | 	"github.com/googleapis/genai-toolbox/internal/sources"
 24 | 	"github.com/googleapis/genai-toolbox/internal/sources/oceanbase"
 25 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 26 | 	"github.com/googleapis/genai-toolbox/internal/tools/mysql/mysqlcommon"
 27 | )
 28 | 
 29 | const kind string = "oceanbase-sql"
 30 | 
 31 | func init() {
 32 | 	if !tools.Register(kind, newConfig) {
 33 | 		panic(fmt.Sprintf("tool kind %q already registered", kind))
 34 | 	}
 35 | }
 36 | 
 37 | type compatibleSource interface {
 38 | 	OceanBasePool() *sql.DB
 39 | }
 40 | 
 41 | // validate compatible sources are still compatible
 42 | var _ compatibleSource = &oceanbase.Source{}
 43 | 
 44 | var compatibleSources = [...]string{oceanbase.SourceKind}
 45 | 
 46 | type Config struct {
 47 | 	Name               string           `yaml:"name" validate:"required"`
 48 | 	Kind               string           `yaml:"kind" validate:"required"`
 49 | 	Source             string           `yaml:"source" validate:"required"`
 50 | 	Description        string           `yaml:"description" validate:"required"`
 51 | 	Statement          string           `yaml:"statement" validate:"required"`
 52 | 	AuthRequired       []string         `yaml:"authRequired"`
 53 | 	Parameters         tools.Parameters `yaml:"parameters"`
 54 | 	TemplateParameters tools.Parameters `yaml:"templateParameters"`
 55 | }
 56 | 
 57 | // validate interface
 58 | var _ tools.ToolConfig = Config{}
 59 | 
 60 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
 61 | 	actual := Config{Name: name}
 62 | 	if err := decoder.DecodeContext(ctx, &actual); err != nil {
 63 | 		return nil, err
 64 | 	}
 65 | 	return actual, nil
 66 | }
 67 | 
 68 | func (cfg Config) ToolConfigKind() string {
 69 | 	return kind
 70 | }
 71 | 
 72 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
 73 | 	// verify source exists
 74 | 	rawS, ok := srcs[cfg.Source]
 75 | 	if !ok {
 76 | 		return nil, fmt.Errorf("no source named %q configured", cfg.Source)
 77 | 	}
 78 | 
 79 | 	// verify the source is compatible
 80 | 	s, ok := rawS.(compatibleSource)
 81 | 	if !ok {
 82 | 		return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
 83 | 	}
 84 | 
 85 | 	allParameters, paramManifest, err := tools.ProcessParameters(cfg.TemplateParameters, cfg.Parameters)
 86 | 	if err != nil {
 87 | 		return nil, fmt.Errorf("unable to process parameters: %w", err)
 88 | 	}
 89 | 
 90 | 	mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, allParameters)
 91 | 
 92 | 	// finish tool setup
 93 | 	t := Tool{
 94 | 		Name:               cfg.Name,
 95 | 		Kind:               kind,
 96 | 		Parameters:         cfg.Parameters,
 97 | 		TemplateParameters: cfg.TemplateParameters,
 98 | 		AllParams:          allParameters,
 99 | 		Statement:          cfg.Statement,
100 | 		AuthRequired:       cfg.AuthRequired,
101 | 		Pool:               s.OceanBasePool(),
102 | 		manifest:           tools.Manifest{Description: cfg.Description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired},
103 | 		mcpManifest:        mcpManifest,
104 | 	}
105 | 	return t, nil
106 | }
107 | 
108 | // validate interface
109 | var _ tools.Tool = Tool{}
110 | 
111 | type Tool struct {
112 | 	Name               string           `yaml:"name"`
113 | 	Kind               string           `yaml:"kind"`
114 | 	AuthRequired       []string         `yaml:"authRequired"`
115 | 	Parameters         tools.Parameters `yaml:"parameters"`
116 | 	TemplateParameters tools.Parameters `yaml:"templateParameters"`
117 | 	AllParams          tools.Parameters `yaml:"allParams"`
118 | 
119 | 	Pool        *sql.DB
120 | 	Statement   string
121 | 	manifest    tools.Manifest
122 | 	mcpManifest tools.McpManifest
123 | }
124 | 
125 | // Invoke executes the SQL statement with the provided parameters.
126 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
127 | 	paramsMap := params.AsMap()
128 | 	newStatement, err := tools.ResolveTemplateParams(t.TemplateParameters, t.Statement, paramsMap)
129 | 	if err != nil {
130 | 		return nil, fmt.Errorf("unable to extract template params %w", err)
131 | 	}
132 | 
133 | 	newParams, err := tools.GetParams(t.Parameters, paramsMap)
134 | 	if err != nil {
135 | 		return nil, fmt.Errorf("unable to extract standard params %w", err)
136 | 	}
137 | 
138 | 	sliceParams := newParams.AsSlice()
139 | 	results, err := t.Pool.QueryContext(ctx, newStatement, sliceParams...)
140 | 	if err != nil {
141 | 		return nil, fmt.Errorf("unable to execute query: %w", err)
142 | 	}
143 | 
144 | 	cols, err := results.Columns()
145 | 	if err != nil {
146 | 		return nil, fmt.Errorf("unable to retrieve rows column name: %w", err)
147 | 	}
148 | 
149 | 	// create an array of values for each column, which can be re-used to scan each row
150 | 	rawValues := make([]any, len(cols))
151 | 	values := make([]any, len(cols))
152 | 	for i := range rawValues {
153 | 		values[i] = &rawValues[i]
154 | 	}
155 | 	defer results.Close()
156 | 
157 | 	colTypes, err := results.ColumnTypes()
158 | 	if err != nil {
159 | 		return nil, fmt.Errorf("unable to get column types: %w", err)
160 | 	}
161 | 
162 | 	var out []any
163 | 	for results.Next() {
164 | 		err := results.Scan(values...)
165 | 		if err != nil {
166 | 			return nil, fmt.Errorf("unable to parse row: %w", err)
167 | 		}
168 | 		vMap := make(map[string]any)
169 | 		for i, name := range cols {
170 | 			val := rawValues[i]
171 | 			if val == nil {
172 | 				vMap[name] = nil
173 | 				continue
174 | 			}
175 | 
176 | 			// oceanbase uses mysql driver
177 | 			vMap[name], err = mysqlcommon.ConvertToType(colTypes[i], val)
178 | 			if err != nil {
179 | 				return nil, fmt.Errorf("errors encountered when converting values: %w", err)
180 | 			}
181 | 		}
182 | 		out = append(out, vMap)
183 | 	}
184 | 
185 | 	if err := results.Err(); err != nil {
186 | 		return nil, fmt.Errorf("errors encountered during row iteration: %w", err)
187 | 	}
188 | 
189 | 	return out, nil
190 | }
191 | 
192 | // ParseParams parses the input parameters for the tool.
193 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
194 | 	return tools.ParseParams(t.AllParams, data, claims)
195 | }
196 | 
197 | // Manifest returns the tool manifest.
198 | func (t Tool) Manifest() tools.Manifest {
199 | 	return t.manifest
200 | }
201 | 
202 | // McpManifest returns the MCP manifest for the tool.
203 | func (t Tool) McpManifest() tools.McpManifest {
204 | 	return t.mcpManifest
205 | }
206 | 
207 | // Authorized checks if the tool is authorized for the given auth services.
208 | func (t Tool) Authorized(verifiedAuthServices []string) bool {
209 | 	return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
210 | }
211 | 
212 | func (t Tool) RequiresClientAuthorization() bool {
213 | 	return false
214 | }
215 | 
```

--------------------------------------------------------------------------------
/internal/tools/bigquery/bigquerylisttableids/bigquerylisttableids.go:
--------------------------------------------------------------------------------

```go
  1 | // Copyright 2025 Google LLC
  2 | //
  3 | // Licensed under the Apache License, Version 2.0 (the "License");
  4 | // you may not use this file except in compliance with the License.
  5 | // You may obtain a copy of the License at
  6 | //
  7 | //     http://www.apache.org/licenses/LICENSE-2.0
  8 | //
  9 | // Unless required by applicable law or agreed to in writing, software
 10 | // distributed under the License is distributed on an "AS IS" BASIS,
 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12 | // See the License for the specific language governing permissions and
 13 | // limitations under the License.
 14 | 
 15 | package bigquerylisttableids
 16 | 
 17 | import (
 18 | 	"context"
 19 | 	"fmt"
 20 | 
 21 | 	bigqueryapi "cloud.google.com/go/bigquery"
 22 | 	yaml "github.com/goccy/go-yaml"
 23 | 	"github.com/googleapis/genai-toolbox/internal/sources"
 24 | 	bigqueryds "github.com/googleapis/genai-toolbox/internal/sources/bigquery"
 25 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 26 | 	bqutil "github.com/googleapis/genai-toolbox/internal/tools/bigquery/bigquerycommon"
 27 | 	"google.golang.org/api/iterator"
 28 | )
 29 | 
 30 | const kind string = "bigquery-list-table-ids"
 31 | const projectKey string = "project"
 32 | const datasetKey string = "dataset"
 33 | 
 34 | func init() {
 35 | 	if !tools.Register(kind, newConfig) {
 36 | 		panic(fmt.Sprintf("tool kind %q already registered", kind))
 37 | 	}
 38 | }
 39 | 
 40 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
 41 | 	actual := Config{Name: name}
 42 | 	if err := decoder.DecodeContext(ctx, &actual); err != nil {
 43 | 		return nil, err
 44 | 	}
 45 | 	return actual, nil
 46 | }
 47 | 
 48 | type compatibleSource interface {
 49 | 	BigQueryClient() *bigqueryapi.Client
 50 | 	BigQueryClientCreator() bigqueryds.BigqueryClientCreator
 51 | 	BigQueryProject() string
 52 | 	UseClientAuthorization() bool
 53 | 	IsDatasetAllowed(projectID, datasetID string) bool
 54 | 	BigQueryAllowedDatasets() []string
 55 | }
 56 | 
 57 | // validate compatible sources are still compatible
 58 | var _ compatibleSource = &bigqueryds.Source{}
 59 | 
 60 | var compatibleSources = [...]string{bigqueryds.SourceKind}
 61 | 
 62 | type Config struct {
 63 | 	Name         string   `yaml:"name" validate:"required"`
 64 | 	Kind         string   `yaml:"kind" validate:"required"`
 65 | 	Source       string   `yaml:"source" validate:"required"`
 66 | 	Description  string   `yaml:"description" validate:"required"`
 67 | 	AuthRequired []string `yaml:"authRequired"`
 68 | }
 69 | 
 70 | // validate interface
 71 | var _ tools.ToolConfig = Config{}
 72 | 
 73 | func (cfg Config) ToolConfigKind() string {
 74 | 	return kind
 75 | }
 76 | 
 77 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
 78 | 	// verify source exists
 79 | 	rawS, ok := srcs[cfg.Source]
 80 | 	if !ok {
 81 | 		return nil, fmt.Errorf("no source named %q configured", cfg.Source)
 82 | 	}
 83 | 
 84 | 	// verify the source is compatible
 85 | 	s, ok := rawS.(compatibleSource)
 86 | 	if !ok {
 87 | 		return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
 88 | 	}
 89 | 
 90 | 	defaultProjectID := s.BigQueryProject()
 91 | 	projectDescription := "The Google Cloud project ID containing the dataset."
 92 | 	datasetDescription := "The dataset to list table ids."
 93 | 	var datasetParameter tools.Parameter
 94 | 	var projectParameter tools.Parameter
 95 | 
 96 | 	projectParameter, datasetParameter = bqutil.InitializeDatasetParameters(
 97 | 		s.BigQueryAllowedDatasets(),
 98 | 		defaultProjectID,
 99 | 		projectKey, datasetKey,
100 | 		projectDescription, datasetDescription,
101 | 	)
102 | 
103 | 	parameters := tools.Parameters{projectParameter, datasetParameter}
104 | 
105 | 	mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
106 | 
107 | 	// finish tool setup
108 | 	t := Tool{
109 | 		Name:             cfg.Name,
110 | 		Kind:             kind,
111 | 		Parameters:       parameters,
112 | 		AuthRequired:     cfg.AuthRequired,
113 | 		UseClientOAuth:   s.UseClientAuthorization(),
114 | 		ClientCreator:    s.BigQueryClientCreator(),
115 | 		Client:           s.BigQueryClient(),
116 | 		IsDatasetAllowed: s.IsDatasetAllowed,
117 | 		manifest:         tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired},
118 | 		mcpManifest:      mcpManifest,
119 | 	}
120 | 	return t, nil
121 | }
122 | 
123 | // validate interface
124 | var _ tools.Tool = Tool{}
125 | 
126 | type Tool struct {
127 | 	Name           string           `yaml:"name"`
128 | 	Kind           string           `yaml:"kind"`
129 | 	AuthRequired   []string         `yaml:"authRequired"`
130 | 	UseClientOAuth bool             `yaml:"useClientOAuth"`
131 | 	Parameters     tools.Parameters `yaml:"parameters"`
132 | 
133 | 	Client           *bigqueryapi.Client
134 | 	ClientCreator    bigqueryds.BigqueryClientCreator
135 | 	IsDatasetAllowed func(projectID, datasetID string) bool
136 | 	Statement        string
137 | 	manifest         tools.Manifest
138 | 	mcpManifest      tools.McpManifest
139 | }
140 | 
141 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
142 | 	mapParams := params.AsMap()
143 | 	projectId, ok := mapParams[projectKey].(string)
144 | 	if !ok {
145 | 		return nil, fmt.Errorf("invalid or missing '%s' parameter; expected a string", projectKey)
146 | 	}
147 | 
148 | 	datasetId, ok := mapParams[datasetKey].(string)
149 | 	if !ok {
150 | 		return nil, fmt.Errorf("invalid or missing '%s' parameter; expected a string", datasetKey)
151 | 	}
152 | 
153 | 	if !t.IsDatasetAllowed(projectId, datasetId) {
154 | 		return nil, fmt.Errorf("access denied to dataset '%s' because it is not in the configured list of allowed datasets for project '%s'", datasetId, projectId)
155 | 	}
156 | 
157 | 	bqClient := t.Client
158 | 	// Initialize new client if using user OAuth token
159 | 	if t.UseClientOAuth {
160 | 		tokenStr, err := accessToken.ParseBearerToken()
161 | 		if err != nil {
162 | 			return nil, fmt.Errorf("error parsing access token: %w", err)
163 | 		}
164 | 		bqClient, _, err = t.ClientCreator(tokenStr, false)
165 | 		if err != nil {
166 | 			return nil, fmt.Errorf("error creating client from OAuth access token: %w", err)
167 | 		}
168 | 	}
169 | 
170 | 	dsHandle := bqClient.DatasetInProject(projectId, datasetId)
171 | 
172 | 	var tableIds []any
173 | 	tableIterator := dsHandle.Tables(ctx)
174 | 	for {
175 | 		table, err := tableIterator.Next()
176 | 		if err == iterator.Done {
177 | 			break
178 | 		}
179 | 		if err != nil {
180 | 			return nil, fmt.Errorf("failed to iterate through tables in dataset %s.%s: %w", projectId, datasetId, err)
181 | 		}
182 | 
183 | 		// Remove leading and trailing quotes
184 | 		id := table.TableID
185 | 		if len(id) >= 2 && id[0] == '"' && id[len(id)-1] == '"' {
186 | 			id = id[1 : len(id)-1]
187 | 		}
188 | 		tableIds = append(tableIds, id)
189 | 	}
190 | 
191 | 	return tableIds, nil
192 | }
193 | 
194 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
195 | 	return tools.ParseParams(t.Parameters, data, claims)
196 | }
197 | 
198 | func (t Tool) Manifest() tools.Manifest {
199 | 	return t.manifest
200 | }
201 | 
202 | func (t Tool) McpManifest() tools.McpManifest {
203 | 	return t.mcpManifest
204 | }
205 | 
206 | func (t Tool) Authorized(verifiedAuthServices []string) bool {
207 | 	return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
208 | }
209 | 
210 | func (t Tool) RequiresClientAuthorization() bool {
211 | 	return t.UseClientOAuth
212 | }
213 | 
```

--------------------------------------------------------------------------------
/internal/tools/bigquery/bigquerygettableinfo/bigquerygettableinfo.go:
--------------------------------------------------------------------------------

```go
  1 | // Copyright 2025 Google LLC
  2 | //
  3 | // Licensed under the Apache License, Version 2.0 (the "License");
  4 | // you may not use this file except in compliance with the License.
  5 | // You may obtain a copy of the License at
  6 | //
  7 | //     http://www.apache.org/licenses/LICENSE-2.0
  8 | //
  9 | // Unless required by applicable law or agreed to in writing, software
 10 | // distributed under the License is distributed on an "AS IS" BASIS,
 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12 | // See the License for the specific language governing permissions and
 13 | // limitations under the License.
 14 | 
 15 | package bigquerygettableinfo
 16 | 
 17 | import (
 18 | 	"context"
 19 | 	"fmt"
 20 | 
 21 | 	bigqueryapi "cloud.google.com/go/bigquery"
 22 | 	yaml "github.com/goccy/go-yaml"
 23 | 	"github.com/googleapis/genai-toolbox/internal/sources"
 24 | 	bigqueryds "github.com/googleapis/genai-toolbox/internal/sources/bigquery"
 25 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 26 | 	bqutil "github.com/googleapis/genai-toolbox/internal/tools/bigquery/bigquerycommon"
 27 | )
 28 | 
 29 | const kind string = "bigquery-get-table-info"
 30 | const projectKey string = "project"
 31 | const datasetKey string = "dataset"
 32 | const tableKey string = "table"
 33 | 
 34 | func init() {
 35 | 	if !tools.Register(kind, newConfig) {
 36 | 		panic(fmt.Sprintf("tool kind %q already registered", kind))
 37 | 	}
 38 | }
 39 | 
 40 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
 41 | 	actual := Config{Name: name}
 42 | 	if err := decoder.DecodeContext(ctx, &actual); err != nil {
 43 | 		return nil, err
 44 | 	}
 45 | 	return actual, nil
 46 | }
 47 | 
 48 | type compatibleSource interface {
 49 | 	BigQueryProject() string
 50 | 	BigQueryClient() *bigqueryapi.Client
 51 | 	BigQueryClientCreator() bigqueryds.BigqueryClientCreator
 52 | 	UseClientAuthorization() bool
 53 | 	IsDatasetAllowed(projectID, datasetID string) bool
 54 | 	BigQueryAllowedDatasets() []string
 55 | }
 56 | 
 57 | // validate compatible sources are still compatible
 58 | var _ compatibleSource = &bigqueryds.Source{}
 59 | 
 60 | var compatibleSources = [...]string{bigqueryds.SourceKind}
 61 | 
 62 | type Config struct {
 63 | 	Name         string   `yaml:"name" validate:"required"`
 64 | 	Kind         string   `yaml:"kind" validate:"required"`
 65 | 	Source       string   `yaml:"source" validate:"required"`
 66 | 	Description  string   `yaml:"description" validate:"required"`
 67 | 	AuthRequired []string `yaml:"authRequired"`
 68 | }
 69 | 
 70 | // validate interface
 71 | var _ tools.ToolConfig = Config{}
 72 | 
 73 | func (cfg Config) ToolConfigKind() string {
 74 | 	return kind
 75 | }
 76 | 
 77 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
 78 | 	// verify source exists
 79 | 	rawS, ok := srcs[cfg.Source]
 80 | 	if !ok {
 81 | 		return nil, fmt.Errorf("no source named %q configured", cfg.Source)
 82 | 	}
 83 | 
 84 | 	// verify the source is compatible
 85 | 	s, ok := rawS.(compatibleSource)
 86 | 	if !ok {
 87 | 		return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
 88 | 	}
 89 | 
 90 | 	defaultProjectID := s.BigQueryProject()
 91 | 	projectDescription := "The Google Cloud project ID containing the dataset and table."
 92 | 	datasetDescription := "The table's parent dataset."
 93 | 	var datasetParameter tools.Parameter
 94 | 	var projectParameter tools.Parameter
 95 | 
 96 | 	projectParameter, datasetParameter = bqutil.InitializeDatasetParameters(
 97 | 		s.BigQueryAllowedDatasets(),
 98 | 		defaultProjectID,
 99 | 		projectKey, datasetKey,
100 | 		projectDescription, datasetDescription,
101 | 	)
102 | 
103 | 	tableParameter := tools.NewStringParameter(tableKey, "The table to get metadata information.")
104 | 	parameters := tools.Parameters{projectParameter, datasetParameter, tableParameter}
105 | 
106 | 	mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
107 | 
108 | 	// finish tool setup
109 | 	t := Tool{
110 | 		Name:             cfg.Name,
111 | 		Kind:             kind,
112 | 		Parameters:       parameters,
113 | 		AuthRequired:     cfg.AuthRequired,
114 | 		UseClientOAuth:   s.UseClientAuthorization(),
115 | 		ClientCreator:    s.BigQueryClientCreator(),
116 | 		Client:           s.BigQueryClient(),
117 | 		IsDatasetAllowed: s.IsDatasetAllowed,
118 | 		manifest:         tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired},
119 | 		mcpManifest:      mcpManifest,
120 | 	}
121 | 	return t, nil
122 | }
123 | 
124 | // validate interface
125 | var _ tools.Tool = Tool{}
126 | 
127 | type Tool struct {
128 | 	Name           string           `yaml:"name"`
129 | 	Kind           string           `yaml:"kind"`
130 | 	AuthRequired   []string         `yaml:"authRequired"`
131 | 	UseClientOAuth bool             `yaml:"useClientOAuth"`
132 | 	Parameters     tools.Parameters `yaml:"parameters"`
133 | 
134 | 	Client           *bigqueryapi.Client
135 | 	ClientCreator    bigqueryds.BigqueryClientCreator
136 | 	Statement        string
137 | 	IsDatasetAllowed func(projectID, datasetID string) bool
138 | 	manifest         tools.Manifest
139 | 	mcpManifest      tools.McpManifest
140 | }
141 | 
142 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
143 | 	mapParams := params.AsMap()
144 | 	projectId, ok := mapParams[projectKey].(string)
145 | 	if !ok {
146 | 		return nil, fmt.Errorf("invalid or missing '%s' parameter; expected a string", projectKey)
147 | 	}
148 | 
149 | 	datasetId, ok := mapParams[datasetKey].(string)
150 | 	if !ok {
151 | 		return nil, fmt.Errorf("invalid or missing '%s' parameter; expected a string", datasetKey)
152 | 	}
153 | 
154 | 	tableId, ok := mapParams[tableKey].(string)
155 | 	if !ok {
156 | 		return nil, fmt.Errorf("invalid or missing '%s' parameter; expected a string", tableKey)
157 | 	}
158 | 
159 | 	if !t.IsDatasetAllowed(projectId, datasetId) {
160 | 		return nil, fmt.Errorf("access denied to dataset '%s' because it is not in the configured list of allowed datasets for project '%s'", datasetId, projectId)
161 | 	}
162 | 
163 | 	bqClient := t.Client
164 | 
165 | 	var err error
166 | 	// Initialize new client if using user OAuth token
167 | 	if t.UseClientOAuth {
168 | 		tokenStr, err := accessToken.ParseBearerToken()
169 | 		if err != nil {
170 | 			return nil, fmt.Errorf("error parsing access token: %w", err)
171 | 		}
172 | 		bqClient, _, err = t.ClientCreator(tokenStr, false)
173 | 		if err != nil {
174 | 			return nil, fmt.Errorf("error creating client from OAuth access token: %w", err)
175 | 		}
176 | 	}
177 | 
178 | 	dsHandle := bqClient.DatasetInProject(projectId, datasetId)
179 | 	tableHandle := dsHandle.Table(tableId)
180 | 
181 | 	metadata, err := tableHandle.Metadata(ctx)
182 | 	if err != nil {
183 | 		return nil, fmt.Errorf("failed to get metadata for table %s.%s.%s: %w", projectId, datasetId, tableId, err)
184 | 	}
185 | 
186 | 	return metadata, nil
187 | }
188 | 
189 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
190 | 	return tools.ParseParams(t.Parameters, data, claims)
191 | }
192 | 
193 | func (t Tool) Manifest() tools.Manifest {
194 | 	return t.manifest
195 | }
196 | 
197 | func (t Tool) McpManifest() tools.McpManifest {
198 | 	return t.mcpManifest
199 | }
200 | 
201 | func (t Tool) Authorized(verifiedAuthServices []string) bool {
202 | 	return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
203 | }
204 | 
205 | func (t Tool) RequiresClientAuthorization() bool {
206 | 	return t.UseClientOAuth
207 | }
208 | 
```

--------------------------------------------------------------------------------
/internal/tools/alloydbainl/alloydbainl.go:
--------------------------------------------------------------------------------

```go
  1 | // Copyright 2025 Google LLC
  2 | //
  3 | // Licensed under the Apache License, Version 2.0 (the "License");
  4 | // you may not use this file except in compliance with the License.
  5 | // You may obtain a copy of the License at
  6 | //
  7 | //     http://www.apache.org/licenses/LICENSE-2.0
  8 | //
  9 | // Unless required by applicable law or agreed to in writing, software
 10 | // distributed under the License is distributed on an "AS IS" BASIS,
 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12 | // See the License for the specific language governing permissions and
 13 | // limitations under the License.
 14 | 
 15 | package alloydbainl
 16 | 
 17 | import (
 18 | 	"context"
 19 | 	"fmt"
 20 | 	"strings"
 21 | 
 22 | 	yaml "github.com/goccy/go-yaml"
 23 | 	"github.com/googleapis/genai-toolbox/internal/sources"
 24 | 	"github.com/googleapis/genai-toolbox/internal/sources/alloydbpg"
 25 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 26 | 	"github.com/jackc/pgx/v5/pgxpool"
 27 | )
 28 | 
 29 | const kind string = "alloydb-ai-nl"
 30 | 
 31 | func init() {
 32 | 	if !tools.Register(kind, newConfig) {
 33 | 		panic(fmt.Sprintf("tool kind %q already registered", kind))
 34 | 	}
 35 | }
 36 | 
 37 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
 38 | 	actual := Config{Name: name}
 39 | 	if err := decoder.DecodeContext(ctx, &actual); err != nil {
 40 | 		return nil, err
 41 | 	}
 42 | 	return actual, nil
 43 | }
 44 | 
 45 | type compatibleSource interface {
 46 | 	PostgresPool() *pgxpool.Pool
 47 | }
 48 | 
 49 | // validate compatible sources are still compatible
 50 | var _ compatibleSource = &alloydbpg.Source{}
 51 | 
 52 | var compatibleSources = [...]string{alloydbpg.SourceKind}
 53 | 
 54 | type Config struct {
 55 | 	Name               string           `yaml:"name" validate:"required"`
 56 | 	Kind               string           `yaml:"kind" validate:"required"`
 57 | 	Source             string           `yaml:"source" validate:"required"`
 58 | 	Description        string           `yaml:"description" validate:"required"`
 59 | 	NLConfig           string           `yaml:"nlConfig" validate:"required"`
 60 | 	AuthRequired       []string         `yaml:"authRequired"`
 61 | 	NLConfigParameters tools.Parameters `yaml:"nlConfigParameters"`
 62 | }
 63 | 
 64 | // validate interface
 65 | var _ tools.ToolConfig = Config{}
 66 | 
 67 | func (cfg Config) ToolConfigKind() string {
 68 | 	return kind
 69 | }
 70 | 
 71 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
 72 | 	// verify source exists
 73 | 	rawS, ok := srcs[cfg.Source]
 74 | 	if !ok {
 75 | 		return nil, fmt.Errorf("no source named %q configured", cfg.Source)
 76 | 	}
 77 | 
 78 | 	// verify the source is compatible
 79 | 	s, ok := rawS.(compatibleSource)
 80 | 	if !ok {
 81 | 		return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
 82 | 	}
 83 | 
 84 | 	numParams := len(cfg.NLConfigParameters)
 85 | 	quotedNameParts := make([]string, 0, numParams)
 86 | 	placeholderParts := make([]string, 0, numParams)
 87 | 
 88 | 	for i, paramDef := range cfg.NLConfigParameters {
 89 | 		name := paramDef.GetName()
 90 | 		escapedName := strings.ReplaceAll(name, "'", "''") // Escape for SQL literal
 91 | 		quotedNameParts = append(quotedNameParts, fmt.Sprintf("'%s'", escapedName))
 92 | 		placeholderParts = append(placeholderParts, fmt.Sprintf("$%d", i+3)) // $1, $2 reserved
 93 | 	}
 94 | 
 95 | 	var paramNamesSQL string
 96 | 	var paramValuesSQL string
 97 | 
 98 | 	if numParams > 0 {
 99 | 		paramNamesSQL = fmt.Sprintf("ARRAY[%s]", strings.Join(quotedNameParts, ", "))
100 | 		paramValuesSQL = fmt.Sprintf("ARRAY[%s]", strings.Join(placeholderParts, ", "))
101 | 	} else {
102 | 		paramNamesSQL = "ARRAY[]::TEXT[]"
103 | 		paramValuesSQL = "ARRAY[]::TEXT[]"
104 | 	}
105 | 
106 | 	// execute_nl_query is the AlloyDB AI function that executes the natural language query
107 | 	// The first parameter is the natural language query, which is passed as $1
108 | 	// The second parameter is the NLConfig, which is passed as a $2
109 | 	// The following params are the list of PSV values passed to the NLConfig
110 | 	// Example SQL statement being executed:
111 | 	// SELECT alloydb_ai_nl.execute_nl_query('How many tickets do I have?', 'cymbal_air_nl_config', param_names => ARRAY ['user_email'], param_values => ARRAY ['[email protected]']);
112 | 	stmtFormat := "SELECT alloydb_ai_nl.execute_nl_query($1, $2, param_names => %s, param_values => %s);"
113 | 	stmt := fmt.Sprintf(stmtFormat, paramNamesSQL, paramValuesSQL)
114 | 
115 | 	newQuestionParam := tools.NewStringParameter(
116 | 		"question",                              // name
117 | 		"The natural language question to ask.", // description
118 | 	)
119 | 
120 | 	cfg.NLConfigParameters = append([]tools.Parameter{newQuestionParam}, cfg.NLConfigParameters...)
121 | 
122 | 	mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, cfg.NLConfigParameters)
123 | 
124 | 	t := Tool{
125 | 		Name:         cfg.Name,
126 | 		Kind:         kind,
127 | 		Parameters:   cfg.NLConfigParameters,
128 | 		Statement:    stmt,
129 | 		NLConfig:     cfg.NLConfig,
130 | 		AuthRequired: cfg.AuthRequired,
131 | 		Pool:         s.PostgresPool(),
132 | 		manifest:     tools.Manifest{Description: cfg.Description, Parameters: cfg.NLConfigParameters.Manifest(), AuthRequired: cfg.AuthRequired},
133 | 		mcpManifest:  mcpManifest,
134 | 	}
135 | 
136 | 	return t, nil
137 | }
138 | 
139 | // validate interface
140 | var _ tools.Tool = Tool{}
141 | 
142 | type Tool struct {
143 | 	Name         string           `yaml:"name"`
144 | 	Kind         string           `yaml:"kind"`
145 | 	AuthRequired []string         `yaml:"authRequired"`
146 | 	Parameters   tools.Parameters `yaml:"parameters"`
147 | 
148 | 	Pool        *pgxpool.Pool
149 | 	Statement   string
150 | 	NLConfig    string
151 | 	manifest    tools.Manifest
152 | 	mcpManifest tools.McpManifest
153 | }
154 | 
155 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
156 | 	sliceParams := params.AsSlice()
157 | 	allParamValues := make([]any, len(sliceParams)+1)
158 | 	allParamValues[0] = fmt.Sprintf("%s", sliceParams[0]) // nl_question
159 | 	allParamValues[1] = t.NLConfig                        // nl_config
160 | 	for i, param := range sliceParams[1:] {
161 | 		allParamValues[i+2] = fmt.Sprintf("%s", param)
162 | 	}
163 | 
164 | 	results, err := t.Pool.Query(ctx, t.Statement, allParamValues...)
165 | 	if err != nil {
166 | 		return nil, fmt.Errorf("unable to execute query: %w. Query: %v , Values: %v", err, t.Statement, allParamValues)
167 | 	}
168 | 
169 | 	fields := results.FieldDescriptions()
170 | 
171 | 	var out []any
172 | 	for results.Next() {
173 | 		v, err := results.Values()
174 | 		if err != nil {
175 | 			return nil, fmt.Errorf("unable to parse row: %w", err)
176 | 		}
177 | 		vMap := make(map[string]any)
178 | 		for i, f := range fields {
179 | 			vMap[f.Name] = v[i]
180 | 		}
181 | 		out = append(out, vMap)
182 | 	}
183 | 
184 | 	return out, nil
185 | }
186 | 
187 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
188 | 	return tools.ParseParams(t.Parameters, data, claims)
189 | }
190 | 
191 | func (t Tool) Manifest() tools.Manifest {
192 | 	return t.manifest
193 | }
194 | 
195 | func (t Tool) McpManifest() tools.McpManifest {
196 | 	return t.mcpManifest
197 | }
198 | 
199 | func (t Tool) Authorized(verifiedAuthServices []string) bool {
200 | 	return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
201 | }
202 | 
203 | func (t Tool) RequiresClientAuthorization() bool {
204 | 	return false
205 | }
206 | 
```
Page 20/47FirstPrevNextLast