#
tokens: 49657/50000 19/816 files (page 17/35)
lines: off (toggle) GitHub
raw markdown copy
This is page 17 of 35. Use http://codebase.md/googleapis/genai-toolbox?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
│       │   │   ├── serverless-spark.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
│       │       ├── serverless-spark
│       │       │   ├── _index.md
│       │       │   └── serverless-spark-list-batches.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
│   │       ├── serverless-spark.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
│   │   ├── serverlessspark
│   │   │   ├── serverlessspark_test.go
│   │   │   └── serverlessspark.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
│   │   ├── serverlessspark
│   │   │   └── serverlesssparklistbatches
│   │   │       ├── serverlesssparklistbatches_test.go
│   │   │       └── serverlesssparklistbatches.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
    ├── serverlessspark
    │   └── serverless_spark_integration_test.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/server/mcp/v20241105/method.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package v20241105

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"strings"

	"github.com/googleapis/genai-toolbox/internal/auth"
	"github.com/googleapis/genai-toolbox/internal/server/mcp/jsonrpc"
	"github.com/googleapis/genai-toolbox/internal/tools"
	"github.com/googleapis/genai-toolbox/internal/util"
)

// ProcessMethod returns a response for the request.
func ProcessMethod(ctx context.Context, id jsonrpc.RequestId, method string, toolset tools.Toolset, tools map[string]tools.Tool, authServices map[string]auth.AuthService, body []byte, header http.Header) (any, error) {
	switch method {
	case PING:
		return pingHandler(id)
	case TOOLS_LIST:
		return toolsListHandler(id, toolset, body)
	case TOOLS_CALL:
		return toolsCallHandler(ctx, id, tools, authServices, body, header)
	default:
		err := fmt.Errorf("invalid method %s", method)
		return jsonrpc.NewError(id, jsonrpc.METHOD_NOT_FOUND, err.Error(), nil), err
	}
}

// pingHandler handles the "ping" method by returning an empty response.
func pingHandler(id jsonrpc.RequestId) (any, error) {
	return jsonrpc.JSONRPCResponse{
		Jsonrpc: jsonrpc.JSONRPC_VERSION,
		Id:      id,
		Result:  struct{}{},
	}, nil
}

func toolsListHandler(id jsonrpc.RequestId, toolset tools.Toolset, body []byte) (any, error) {
	var req ListToolsRequest
	if err := json.Unmarshal(body, &req); err != nil {
		err = fmt.Errorf("invalid mcp tools list request: %w", err)
		return jsonrpc.NewError(id, jsonrpc.INVALID_REQUEST, err.Error(), nil), err
	}

	result := ListToolsResult{
		Tools: toolset.McpManifest,
	}
	return jsonrpc.JSONRPCResponse{
		Jsonrpc: jsonrpc.JSONRPC_VERSION,
		Id:      id,
		Result:  result,
	}, nil
}

// toolsCallHandler generate a response for tools call.
func toolsCallHandler(ctx context.Context, id jsonrpc.RequestId, toolsMap map[string]tools.Tool, authServices map[string]auth.AuthService, body []byte, header http.Header) (any, error) {
	// retrieve logger from context
	logger, err := util.LoggerFromContext(ctx)
	if err != nil {
		return jsonrpc.NewError(id, jsonrpc.INTERNAL_ERROR, err.Error(), nil), err
	}

	var req CallToolRequest
	if err = json.Unmarshal(body, &req); err != nil {
		err = fmt.Errorf("invalid mcp tools call request: %w", err)
		return jsonrpc.NewError(id, jsonrpc.INVALID_REQUEST, err.Error(), nil), err
	}

	toolName := req.Params.Name
	toolArgument := req.Params.Arguments
	logger.DebugContext(ctx, fmt.Sprintf("tool name: %s", toolName))
	tool, ok := toolsMap[toolName]
	if !ok {
		err = fmt.Errorf("invalid tool name: tool with name %q does not exist", toolName)
		return jsonrpc.NewError(id, jsonrpc.INVALID_PARAMS, err.Error(), nil), err
	}

	// Get access token
	accessToken := tools.AccessToken(header.Get("Authorization"))

	// Check if this specific tool requires the standard authorization header
	if tool.RequiresClientAuthorization() {
		if accessToken == "" {
			return jsonrpc.NewError(id, jsonrpc.INVALID_REQUEST, "missing access token in the 'Authorization' header", nil), tools.ErrUnauthorized
		}
	}

	// marshal arguments and decode it using decodeJSON instead to prevent loss between floats/int.
	aMarshal, err := json.Marshal(toolArgument)
	if err != nil {
		err = fmt.Errorf("unable to marshal tools argument: %w", err)
		return jsonrpc.NewError(id, jsonrpc.INTERNAL_ERROR, err.Error(), nil), err
	}

	var data map[string]any
	if err = util.DecodeJSON(bytes.NewBuffer(aMarshal), &data); err != nil {
		err = fmt.Errorf("unable to decode tools argument: %w", err)
		return jsonrpc.NewError(id, jsonrpc.INTERNAL_ERROR, err.Error(), nil), err
	}

	// Tool authentication
	// claimsFromAuth maps the name of the authservice to the claims retrieved from it.
	claimsFromAuth := make(map[string]map[string]any)

	// if using stdio, header will be nil and auth will not be supported
	if header != nil {
		for _, aS := range authServices {
			claims, err := aS.GetClaimsFromHeader(ctx, header)
			if err != nil {
				logger.DebugContext(ctx, err.Error())
				continue
			}
			if claims == nil {
				// authService not present in header
				continue
			}
			claimsFromAuth[aS.GetName()] = claims
		}
	}

	// Tool authorization check
	verifiedAuthServices := make([]string, len(claimsFromAuth))
	i := 0
	for k := range claimsFromAuth {
		verifiedAuthServices[i] = k
		i++
	}

	// Check if any of the specified auth services is verified
	isAuthorized := tool.Authorized(verifiedAuthServices)
	if !isAuthorized {
		err = fmt.Errorf("unauthorized Tool call: Please make sure your specify correct auth headers: %w", tools.ErrUnauthorized)
		return jsonrpc.NewError(id, jsonrpc.INVALID_REQUEST, err.Error(), nil), err
	}
	logger.DebugContext(ctx, "tool invocation authorized")

	params, err := tool.ParseParams(data, claimsFromAuth)
	if err != nil {
		err = fmt.Errorf("provided parameters were invalid: %w", err)
		return jsonrpc.NewError(id, jsonrpc.INVALID_PARAMS, err.Error(), nil), err
	}
	logger.DebugContext(ctx, fmt.Sprintf("invocation params: %s", params))

	// run tool invocation and generate response.
	results, err := tool.Invoke(ctx, params, accessToken)
	if err != nil {
		errStr := err.Error()
		// Missing authService tokens.
		if errors.Is(err, tools.ErrUnauthorized) {
			return jsonrpc.NewError(id, jsonrpc.INVALID_REQUEST, err.Error(), nil), err
		}
		// Upstream auth error
		if strings.Contains(errStr, "Error 401") || strings.Contains(errStr, "Error 403") {
			if tool.RequiresClientAuthorization() {
				// Error with client credentials should pass down to the client
				return jsonrpc.NewError(id, jsonrpc.INVALID_REQUEST, err.Error(), nil), err
			}
			// Auth error with ADC should raise internal 500 error
			return jsonrpc.NewError(id, jsonrpc.INTERNAL_ERROR, err.Error(), nil), err
		}

		text := TextContent{
			Type: "text",
			Text: err.Error(),
		}
		return jsonrpc.JSONRPCResponse{
			Jsonrpc: jsonrpc.JSONRPC_VERSION,
			Id:      id,
			Result:  CallToolResult{Content: []TextContent{text}, IsError: true},
		}, nil
	}

	content := make([]TextContent, 0)

	sliceRes, ok := results.([]any)
	if !ok {
		sliceRes = []any{results}
	}

	for _, d := range sliceRes {
		text := TextContent{Type: "text"}
		dM, err := json.Marshal(d)
		if err != nil {
			text.Text = fmt.Sprintf("fail to marshal: %s, result: %s", err, d)
		} else {
			text.Text = string(dM)
		}
		content = append(content, text)
	}

	return jsonrpc.JSONRPCResponse{
		Jsonrpc: jsonrpc.JSONRPC_VERSION,
		Id:      id,
		Result:  CallToolResult{Content: content},
	}, nil
}

```

--------------------------------------------------------------------------------
/internal/prebuiltconfigs/tools/postgres.yaml:
--------------------------------------------------------------------------------

```yaml
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

sources:
    postgresql-source:
        kind: postgres
        host: ${POSTGRES_HOST}
        port: ${POSTGRES_PORT}
        database: ${POSTGRES_DATABASE}
        user: ${POSTGRES_USER}
        password: ${POSTGRES_PASSWORD}
        queryParams: ${POSTGRES_QUERY_PARAMS:}

tools:
    execute_sql:
        kind: postgres-execute-sql
        source: postgresql-source
        description: Use this tool to execute SQL.

    list_tables:
        kind: postgres-list-tables
        source: postgresql-source
        description: "Lists detailed schema information (object type, columns, constraints, indexes, triggers, owner, comment) as JSON for user-created tables (ordinary or partitioned). Filters by a comma-separated list of names. If names are omitted, lists all tables in user schemas."

    list_active_queries:
        kind: postgres-list-active-queries
        source: postgresql-source
        description: "List the top N (default 50) currently running queries (state='active') from pg_stat_activity, ordered by longest-running first. Returns pid, user, database, application_name, client_addr, state, wait_event_type/wait_event, backend/xact/query start times, computed query_duration, and the SQL text."

    list_available_extensions:
        kind: postgres-list-available-extensions
        source: postgresql-source
        description: "Discover all PostgreSQL extensions available for installation on this server, returning name, default_version, and description."

    list_installed_extensions:
        kind: postgres-list-installed-extensions
        source: postgresql-source
        description: "List all installed PostgreSQL extensions with their name, version, schema, owner, and description."

    list_autovacuum_configurations:
        kind: postgres-sql
        source: postgresql-source
        description: "List PostgreSQL autovacuum-related configurations (name and current setting) from pg_settings."
        statement: |
            SELECT name,
                setting
            FROM   pg_settings
            WHERE  category = 'Autovacuum';

    list_memory_configurations:
        kind: postgres-sql
        source: postgresql-source
        description: "List PostgreSQL memory-related configurations (name and current setting) from pg_settings."
        statement: |
            (
                SELECT
                name,
                pg_size_pretty((setting::bigint * 1024)::bigint) setting
                FROM pg_settings
                WHERE name IN ('work_mem', 'maintenance_work_mem')
            )
            UNION ALL
            (
                SELECT
                name,
                pg_size_pretty((((setting::bigint) * 8) * 1024)::bigint)
                FROM pg_settings
                WHERE name IN ('shared_buffers', 'wal_buffers', 'effective_cache_size', 'temp_buffers')
            )
            ORDER BY 1 DESC;

    list_top_bloated_tables:
        kind: postgres-sql
        source: postgresql-source
        description: |
            List the top tables by dead-tuple (approximate bloat signal), returning schema, table, live/dead tuples, percentage, and last vacuum/analyze times.
        statement: |
            SELECT
                schemaname AS schema_name,
                relname AS relation_name,
                n_live_tup AS live_tuples,
                n_dead_tup AS dead_tuples,
                TRUNC((n_dead_tup::NUMERIC / NULLIF(n_live_tup + n_dead_tup, 0)) * 100, 2) AS dead_tuple_percentage,
                last_vacuum,
                last_autovacuum,
                last_analyze,
                last_autoanalyze
            FROM pg_stat_user_tables
            ORDER BY n_dead_tup DESC
            LIMIT COALESCE($1::int, 50);
        parameters:
            - name: limit
              description: "The maximum number of results to return."
              type: integer
              default: 50

    list_replication_slots:
        kind: postgres-sql
        source: postgresql-source
        description: "List key details for all PostgreSQL replication slots (e.g., type, database, active status) and calculates the size of the outstanding WAL that is being prevented from removal by the slot."
        statement: |
            SELECT
            slot_name,
            slot_type,
            plugin,
            database,
            temporary,
            active,
            restart_lsn,
            confirmed_flush_lsn,
            xmin,
            catalog_xmin,
            pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained_wal
            FROM pg_replication_slots;

    list_invalid_indexes:
        kind: postgres-sql
        source: postgresql-source
        description: "Lists all invalid PostgreSQL indexes which are taking up disk space but are unusable by the query planner. Typically created by failed CREATE INDEX CONCURRENTLY operations."
        statement: |
            SELECT
                nspname AS schema_name,
                indexrelid::regclass AS index_name,
                indrelid::regclass AS table_name,
                pg_size_pretty(pg_total_relation_size(indexrelid)) AS index_size,
                indisready,
                indisvalid,
                pg_get_indexdef(pg_class.oid) AS index_def
            FROM pg_index
            JOIN pg_class ON pg_class.oid = pg_index.indexrelid
            JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace
            WHERE indisvalid = FALSE;

    get_query_plan:
        kind: postgres-sql
        source: postgresql-source
        description: "Generate a PostgreSQL EXPLAIN plan in JSON format for a single SQL statement—without executing it. This returns the optimizer's estimated plan, costs, and rows (no ANALYZE, no extra options). Use in production safely for plan inspection, regression checks, and query tuning workflows."
        statement: |
            EXPLAIN (FORMAT JSON) {{.query}};
        templateParameters:
            - name: query
              type: string
              description: "The SQL statement for which you want to generate plan (omit the EXPLAIN keyword)."
              required: true

toolsets:
    postgres_database_tools:
        - execute_sql
        - list_tables
        - list_active_queries
        - list_available_extensions
        - list_installed_extensions
        - list_autovacuum_configurations
        - list_memory_configurations
        - list_top_bloated_tables
        - list_replication_slots
        - list_invalid_indexes
        - get_query_plan

```

--------------------------------------------------------------------------------
/docs/en/resources/tools/spanner/spanner-sql.md:
--------------------------------------------------------------------------------

```markdown
---
title: "spanner-sql"
type: docs
weight: 1
description: >
  A "spanner-sql" tool executes a pre-defined SQL statement against a Google
  Cloud Spanner database.
aliases:
- /resources/tools/spanner-sql
---

## About

A `spanner-sql` tool executes a pre-defined SQL statement (either `googlesql` or
`postgresql`) against a Cloud Spanner database. It's compatible with any of the
following sources:

- [spanner](../../sources/spanner.md)

### GoogleSQL

For the `googlesql` dialect, the specified SQL statement is executed as a [data
manipulation language (DML)][gsql-dml] statements, and specified parameters will
inserted according to their name: e.g. `@name`.

> **Note:** This tool uses parameterized queries to prevent SQL injections.
> Query parameters can be used as substitutes for arbitrary expressions.
> Parameters cannot be used as substitutes for identifiers, column names, table
> names, or other parts of the query.

[gsql-dml]:
    https://cloud.google.com/spanner/docs/reference/standard-sql/dml-syntax

### PostgreSQL

For the `postgresql` dialect, the specified SQL statement is executed as a [prepared
statement][pg-prepare], and specified parameters will be inserted according to
their position: e.g. `$1` will be the first parameter specified, `$2` will be
the second parameter, and so on.

[pg-prepare]: https://www.postgresql.org/docs/current/sql-prepare.html

## Example

> **Note:** This tool uses parameterized queries to prevent SQL injections.
> Query parameters can be used as substitutes for arbitrary expressions.
> Parameters cannot be used as substitutes for identifiers, column names, table
> names, or other parts of the query.

{{< tabpane persist="header" >}}
{{< tab header="GoogleSQL" lang="yaml" >}}

tools:
 search_flights_by_number:
    kind: spanner-sql
    source: my-spanner-instance
    statement: |
      SELECT * FROM flights
      WHERE airline = @airline
      AND flight_number = @flight_number
      LIMIT 10
    description: |
      Use this tool to get information for a specific flight.
      Takes an airline code and flight number and returns info on the flight.
      Do NOT use this tool with a flight id. Do NOT guess an airline code or flight number.
      A airline code is a code for an airline service consisting of two-character
      airline designator and followed by flight number, which is 1 to 4 digit number.
      For example, if given CY 0123, the airline is "CY", and flight_number is "123".
      Another example for this is DL 1234, the airline is "DL", and flight_number is "1234".
      If the tool returns more than one option choose the date closes to today.
      Example:
      {{
          "airline": "CY",
          "flight_number": "888",
      }}
      Example:
      {{
          "airline": "DL",
          "flight_number": "1234",
      }}
    parameters:
      - name: airline
        type: string
        description: Airline unique 2 letter identifier
      - name: flight_number
        type: string
        description: 1 to 4 digit number

{{< /tab >}}
{{< tab header="PostgreSQL" lang="yaml" >}}

tools:
 search_flights_by_number:
    kind: spanner
    source: my-spanner-instance
    statement: |
      SELECT * FROM flights
      WHERE airline = $1
      AND flight_number = $2
      LIMIT 10
    description: |
      Use this tool to get information for a specific flight.
      Takes an airline code and flight number and returns info on the flight.
      Do NOT use this tool with a flight id. Do NOT guess an airline code or flight number.
      A airline code is a code for an airline service consisting of two-character
      airline designator and followed by flight number, which is 1 to 4 digit number.
      For example, if given CY 0123, the airline is "CY", and flight_number is "123".
      Another example for this is DL 1234, the airline is "DL", and flight_number is "1234".
      If the tool returns more than one option choose the date closes to today.
      Example:
      {{
          "airline": "CY",
          "flight_number": "888",
      }}
      Example:
      {{
          "airline": "DL",
          "flight_number": "1234",
      }}
    parameters:
      - name: airline
        type: string
        description: Airline unique 2 letter identifier
      - name: flight_number
        type: string
        description: 1 to 4 digit number

{{< /tab >}}
{{< /tabpane >}}

### Example with Template Parameters

> **Note:** This tool allows direct modifications to the SQL statement,
> including identifiers, column names, and table names. **This makes it more
> vulnerable to SQL injections**. Using basic parameters only (see above) is
> recommended for performance and safety reasons. For more details, please check
> [templateParameters](..#template-parameters).

```yaml
tools:
 list_table:
    kind: spanner
    source: my-spanner-instance
    statement: |
      SELECT * FROM {{.tableName}};
    description: |
      Use this tool to list all information from a specific table.
      Example:
      {{
          "tableName": "flights",
      }}
    templateParameters:
      - name: tableName
        type: string
        description: Table to select from
```

## Reference

| **field**          |                   **type**                   | **required** | **description**                                                                                                                        |
|--------------------|:--------------------------------------------:|:------------:|----------------------------------------------------------------------------------------------------------------------------------------|
| kind               |                    string                    |     true     | Must be "spanner-sql".                                                                                                                 |
| source             |                    string                    |     true     | Name of the source the SQL should execute on.                                                                                          |
| description        |                    string                    |     true     | Description of the tool that is passed to the LLM.                                                                                     |
| statement          |                    string                    |     true     | SQL statement to execute on.                                                                                                           |
| parameters         |   [parameters](../#specifying-parameters)    |    false     | List of [parameters](../#specifying-parameters) that will be inserted into the SQL statement.                                          |
| readOnly           |                     bool                     |    false     | When set to `true`, the `statement` is run as a read-only transaction. Default: `false`.                                               |
| templateParameters | [templateParameters](..#template-parameters) |    false     | List of [templateParameters](..#template-parameters) that will be inserted into the SQL statement before executing prepared statement. |

```

--------------------------------------------------------------------------------
/internal/prebuiltconfigs/tools/cloud-sql-postgres.yaml:
--------------------------------------------------------------------------------

```yaml
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

sources:
    cloudsql-pg-source:
        kind: cloud-sql-postgres
        project: ${CLOUD_SQL_POSTGRES_PROJECT}
        region: ${CLOUD_SQL_POSTGRES_REGION}
        instance: ${CLOUD_SQL_POSTGRES_INSTANCE}
        database: ${CLOUD_SQL_POSTGRES_DATABASE}
        user: ${CLOUD_SQL_POSTGRES_USER:}
        password: ${CLOUD_SQL_POSTGRES_PASSWORD:}
        ipType: ${CLOUD_SQL_POSTGRES_IP_TYPE:public}

tools:
    execute_sql:
        kind: postgres-execute-sql
        source: cloudsql-pg-source
        description: Use this tool to execute sql.

    list_tables:
        kind: postgres-list-tables
        source: cloudsql-pg-source
        description: "Lists detailed schema information (object type, columns, constraints, indexes, triggers, owner, comment) as JSON for user-created tables (ordinary or partitioned). Filters by a comma-separated list of names. If names are omitted, lists all tables in user schemas."

    list_active_queries:
        kind: postgres-list-active-queries
        source: cloudsql-pg-source
        description: "List the top N (default 50) currently running queries (state='active') from pg_stat_activity, ordered by longest-running first. Returns pid, user, database, application_name, client_addr, state, wait_event_type/wait_event, backend/xact/query start times, computed query_duration, and the SQL text."

    list_available_extensions:
        kind: postgres-list-available-extensions
        source: cloudsql-pg-source
        description: "Discover all PostgreSQL extensions available for installation on this server, returning name, default_version, and description."

    list_installed_extensions:
        kind: postgres-list-installed-extensions
        source: cloudsql-pg-source
        description: "List all installed PostgreSQL extensions with their name, version, schema, owner, and description."

    list_autovacuum_configurations:
        kind: postgres-sql
        source: cloudsql-pg-source
        description: "List PostgreSQL autovacuum-related configurations (name and current setting) from pg_settings."
        statement: |
            SELECT name,
                setting
            FROM   pg_settings
            WHERE  category = 'Autovacuum';

    list_memory_configurations:
        kind: postgres-sql
        source: cloudsql-pg-source
        description: "List PostgreSQL memory-related configurations (name and current setting) from pg_settings."
        statement: |
            (
                SELECT
                name,
                pg_size_pretty((setting::bigint * 1024)::bigint) setting
                FROM pg_settings
                WHERE name IN ('work_mem', 'maintenance_work_mem')
            )
            UNION ALL
            (
                SELECT
                name,
                pg_size_pretty((((setting::bigint) * 8) * 1024)::bigint)
                FROM pg_settings
                WHERE name IN ('shared_buffers', 'wal_buffers', 'effective_cache_size', 'temp_buffers')
            )
            ORDER BY 1 DESC;

    list_top_bloated_tables:
        kind: postgres-sql
        source: cloudsql-pg-source
        description: |
            List the top tables by dead-tuple (approximate bloat signal), returning schema, table, live/dead tuples, percentage, and last vacuum/analyze times.
        statement: |
            SELECT
                schemaname AS schema_name,
                relname AS relation_name,
                n_live_tup AS live_tuples,
                n_dead_tup AS dead_tuples,
                TRUNC((n_dead_tup::NUMERIC / NULLIF(n_live_tup + n_dead_tup, 0)) * 100, 2) AS dead_tuple_percentage,
                last_vacuum,
                last_autovacuum,
                last_analyze,
                last_autoanalyze
            FROM pg_stat_user_tables
            ORDER BY n_dead_tup DESC
            LIMIT COALESCE($1::int, 50);
        parameters:
            - name: limit
              description: "The maximum number of results to return."
              type: integer
              default: 50

    list_replication_slots:
        kind: postgres-sql
        source: cloudsql-pg-source
        description: "List key details for all PostgreSQL replication slots (e.g., type, database, active status) and calculates the size of the outstanding WAL that is being prevented from removal by the slot."
        statement: |
            SELECT
            slot_name,
            slot_type,
            plugin,
            database,
            temporary,
            active,
            restart_lsn,
            confirmed_flush_lsn,
            xmin,
            catalog_xmin,
            pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained_wal
            FROM pg_replication_slots;

    list_invalid_indexes:
        kind: postgres-sql
        source: cloudsql-pg-source
        description: "Lists all invalid PostgreSQL indexes which are taking up disk space but are unusable by the query planner. Typically created by failed CREATE INDEX CONCURRENTLY operations."
        statement: |
            SELECT
                nspname AS schema_name,
                indexrelid::regclass AS index_name,
                indrelid::regclass AS table_name,
                pg_size_pretty(pg_total_relation_size(indexrelid)) AS index_size,
                indisready,
                indisvalid,
                pg_get_indexdef(pg_class.oid) AS index_def
            FROM pg_index
            JOIN pg_class ON pg_class.oid = pg_index.indexrelid
            JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace
            WHERE indisvalid = FALSE;

    get_query_plan:
        kind: postgres-sql
        source: cloudsql-pg-source
        description: "Generate a PostgreSQL EXPLAIN plan in JSON format for a single SQL statement—without executing it. This returns the optimizer's estimated plan, costs, and rows (no ANALYZE, no extra options). Use in production safely for plan inspection, regression checks, and query tuning workflows."
        statement: |
            EXPLAIN (FORMAT JSON) {{.query}};
        templateParameters:
            - name: query
              type: string
              description: "The SQL statement for which you want to generate plan (omit the EXPLAIN keyword)."
              required: true

toolsets:
    cloud_sql_postgres_database_tools:
        - execute_sql
        - list_tables
        - list_active_queries
        - list_available_extensions
        - list_installed_extensions
        - list_autovacuum_configurations
        - list_memory_configurations
        - list_top_bloated_tables
        - list_replication_slots
        - list_invalid_indexes
        - get_query_plan

```

--------------------------------------------------------------------------------
/internal/prebuiltconfigs/tools/alloydb-postgres.yaml:
--------------------------------------------------------------------------------

```yaml
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

sources:
    alloydb-pg-source:
        kind: "alloydb-postgres"
        project: ${ALLOYDB_POSTGRES_PROJECT}
        region: ${ALLOYDB_POSTGRES_REGION}
        cluster: ${ALLOYDB_POSTGRES_CLUSTER}
        instance: ${ALLOYDB_POSTGRES_INSTANCE}
        database: ${ALLOYDB_POSTGRES_DATABASE}
        user: ${ALLOYDB_POSTGRES_USER:}
        password: ${ALLOYDB_POSTGRES_PASSWORD:}
        ipType: ${ALLOYDB_POSTGRES_IP_TYPE:public}

tools:
    execute_sql:
        kind: postgres-execute-sql
        source: alloydb-pg-source
        description: Use this tool to execute sql.

    list_tables:
        kind: postgres-list-tables
        source: alloydb-pg-source
        description: "Lists detailed schema information (object type, columns, constraints, indexes, triggers, owner, comment) as JSON for user-created tables (ordinary or partitioned). Filters by a comma-separated list of names. If names are omitted, lists all tables in user schemas."

    list_active_queries:
        kind: postgres-list-active-queries
        source: alloydb-pg-source
        description: "List the top N (default 50) currently running queries (state='active') from pg_stat_activity, ordered by longest-running first. Returns pid, user, database, application_name, client_addr, state, wait_event_type/wait_event, backend/xact/query start times, computed query_duration, and the SQL text."

    list_available_extensions:
        kind: postgres-list-available-extensions
        source: alloydb-pg-source
        description: "Discover all PostgreSQL extensions available for installation on this server, returning name, default_version, and description."

    list_installed_extensions:
        kind: postgres-list-installed-extensions
        source: alloydb-pg-source
        description: "List all installed PostgreSQL extensions with their name, version, schema, owner, and description."

    list_autovacuum_configurations:
        kind: postgres-sql
        source: alloydb-pg-source
        description: "List PostgreSQL autovacuum-related configurations (name and current setting) from pg_settings."
        statement: |
            SELECT name,
                setting
            FROM   pg_settings
            WHERE  category = 'Autovacuum';

    list_memory_configurations:
        kind: postgres-sql
        source: alloydb-pg-source
        description: "List PostgreSQL memory-related configurations (name and current setting) from pg_settings."
        statement: |
            (
                SELECT
                name,
                pg_size_pretty((setting::bigint * 1024)::bigint) setting
                FROM pg_settings
                WHERE name IN ('work_mem', 'maintenance_work_mem')
            )
            UNION ALL
            (
                SELECT
                name,
                pg_size_pretty((((setting::bigint) * 8) * 1024)::bigint)
                FROM pg_settings
                WHERE name IN ('shared_buffers', 'wal_buffers', 'effective_cache_size', 'temp_buffers')
            )
            ORDER BY 1 DESC;

    list_top_bloated_tables:
        kind: postgres-sql
        source: alloydb-pg-source
        description: |
            List the top tables by dead-tuple (approximate bloat signal), returning schema, table, live/dead tuples, percentage, and last vacuum/analyze times.
        statement: |
            SELECT
                schemaname AS schema_name,
                relname AS relation_name,
                n_live_tup AS live_tuples,
                n_dead_tup AS dead_tuples,
                TRUNC((n_dead_tup::NUMERIC / NULLIF(n_live_tup + n_dead_tup, 0)) * 100, 2) AS dead_tuple_percentage,
                last_vacuum,
                last_autovacuum,
                last_analyze,
                last_autoanalyze
            FROM pg_stat_user_tables
            ORDER BY n_dead_tup DESC
            LIMIT COALESCE($1::int, 50);
        parameters:
            - name: limit
              description: "The maximum number of results to return."
              type: integer
              default: 50

    list_replication_slots:
        kind: postgres-sql
        source: alloydb-pg-source
        description: "List key details for all PostgreSQL replication slots (e.g., type, database, active status) and calculates the size of the outstanding WAL that is being prevented from removal by the slot."
        statement: |
            SELECT
            slot_name,
            slot_type,
            plugin,
            database,
            temporary,
            active,
            restart_lsn,
            confirmed_flush_lsn,
            xmin,
            catalog_xmin,
            pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS retained_wal
            FROM pg_replication_slots;

    list_invalid_indexes:
        kind: postgres-sql
        source: alloydb-pg-source
        description: "Lists all invalid PostgreSQL indexes which are taking up disk space but are unusable by the query planner. Typically created by failed CREATE INDEX CONCURRENTLY operations."
        statement: |
            SELECT
                nspname AS schema_name,
                indexrelid::regclass AS index_name,
                indrelid::regclass AS table_name,
                pg_size_pretty(pg_total_relation_size(indexrelid)) AS index_size,
                indisready,
                indisvalid,
                pg_get_indexdef(pg_class.oid) AS index_def
            FROM pg_index
            JOIN pg_class ON pg_class.oid = pg_index.indexrelid
            JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace
            WHERE indisvalid = FALSE;

    get_query_plan:
        kind: postgres-sql
        source: alloydb-pg-source
        description: "Generate a PostgreSQL EXPLAIN plan in JSON format for a single SQL statement—without executing it. This returns the optimizer's estimated plan, costs, and rows (no ANALYZE, no extra options). Use in production safely for plan inspection, regression checks, and query tuning workflows."
        statement: |
            EXPLAIN (FORMAT JSON) {{.query}};
        templateParameters:
            - name: query
              type: string
              description: "The SQL statement for which you want to generate plan (omit the EXPLAIN keyword)."
              required: true

toolsets:
    alloydb_postgres_database_tools:
        - execute_sql
        - list_tables
        - list_active_queries
        - list_available_extensions
        - list_installed_extensions
        - list_autovacuum_configurations
        - list_memory_configurations
        - list_top_bloated_tables
        - list_replication_slots
        - list_invalid_indexes
        - get_query_plan

```

--------------------------------------------------------------------------------
/internal/tools/firestore/firestoreadddocuments/firestoreadddocuments.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package firestoreadddocuments

import (
	"context"
	"fmt"

	firestoreapi "cloud.google.com/go/firestore"
	yaml "github.com/goccy/go-yaml"
	"github.com/googleapis/genai-toolbox/internal/sources"
	firestoreds "github.com/googleapis/genai-toolbox/internal/sources/firestore"
	"github.com/googleapis/genai-toolbox/internal/tools"
	"github.com/googleapis/genai-toolbox/internal/tools/firestore/util"
)

const kind string = "firestore-add-documents"
const collectionPathKey string = "collectionPath"
const documentDataKey string = "documentData"
const returnDocumentDataKey string = "returnData"

func init() {
	if !tools.Register(kind, newConfig) {
		panic(fmt.Sprintf("tool kind %q already registered", kind))
	}
}

func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
	actual := Config{Name: name}
	if err := decoder.DecodeContext(ctx, &actual); err != nil {
		return nil, err
	}
	return actual, nil
}

type compatibleSource interface {
	FirestoreClient() *firestoreapi.Client
}

// validate compatible sources are still compatible
var _ compatibleSource = &firestoreds.Source{}

var compatibleSources = [...]string{firestoreds.SourceKind}

type Config struct {
	Name         string   `yaml:"name" validate:"required"`
	Kind         string   `yaml:"kind" validate:"required"`
	Source       string   `yaml:"source" validate:"required"`
	Description  string   `yaml:"description" validate:"required"`
	AuthRequired []string `yaml:"authRequired"`
}

// validate interface
var _ tools.ToolConfig = Config{}

func (cfg Config) ToolConfigKind() string {
	return kind
}

func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
	// verify source exists
	rawS, ok := srcs[cfg.Source]
	if !ok {
		return nil, fmt.Errorf("no source named %q configured", cfg.Source)
	}

	// verify the source is compatible
	s, ok := rawS.(compatibleSource)
	if !ok {
		return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
	}

	// Create parameters
	collectionPathParameter := tools.NewStringParameter(
		collectionPathKey,
		"The relative path of the collection where the document will be added to (e.g., 'users' or 'users/userId/posts'). Note: This is a relative path, NOT an absolute path like 'projects/{project_id}/databases/{database_id}/documents/...'",
	)

	documentDataParameter := tools.NewMapParameter(
		documentDataKey,
		`The document data in Firestore's native JSON format. Each field must be wrapped with a type indicator:
- Strings: {"stringValue": "text"}
- Integers: {"integerValue": "123"} or {"integerValue": 123}
- Doubles: {"doubleValue": 123.45}
- Booleans: {"booleanValue": true}
- Timestamps: {"timestampValue": "2025-01-07T10:00:00Z"}
- GeoPoints: {"geoPointValue": {"latitude": 34.05, "longitude": -118.24}}
- Arrays: {"arrayValue": {"values": [{"stringValue": "item1"}, {"integerValue": "2"}]}}
- Maps: {"mapValue": {"fields": {"key1": {"stringValue": "value1"}, "key2": {"booleanValue": true}}}}
- Null: {"nullValue": null}
- Bytes: {"bytesValue": "base64EncodedString"}
- References: {"referenceValue": "collection/document"}`,
		"", // Empty string for generic map that accepts any value type
	)

	returnDataParameter := tools.NewBooleanParameterWithDefault(
		returnDocumentDataKey,
		false,
		"If set to true the output will have the data of the created document. This flag if set to false will help avoid overloading the context of the agent.",
	)

	parameters := tools.Parameters{
		collectionPathParameter,
		documentDataParameter,
		returnDataParameter,
	}

	mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)

	// finish tool setup
	t := Tool{
		Name:         cfg.Name,
		Kind:         kind,
		Parameters:   parameters,
		AuthRequired: cfg.AuthRequired,
		Client:       s.FirestoreClient(),
		manifest:     tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired},
		mcpManifest:  mcpManifest,
	}
	return t, nil
}

// validate interface
var _ tools.Tool = Tool{}

type Tool struct {
	Name         string           `yaml:"name"`
	Kind         string           `yaml:"kind"`
	AuthRequired []string         `yaml:"authRequired"`
	Parameters   tools.Parameters `yaml:"parameters"`

	Client      *firestoreapi.Client
	manifest    tools.Manifest
	mcpManifest tools.McpManifest
}

func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
	mapParams := params.AsMap()

	// Get collection path
	collectionPath, ok := mapParams[collectionPathKey].(string)
	if !ok || collectionPath == "" {
		return nil, fmt.Errorf("invalid or missing '%s' parameter", collectionPathKey)
	}

	// Validate collection path
	if err := util.ValidateCollectionPath(collectionPath); err != nil {
		return nil, fmt.Errorf("invalid collection path: %w", err)
	}

	// Get document data
	documentDataRaw, ok := mapParams[documentDataKey]
	if !ok {
		return nil, fmt.Errorf("invalid or missing '%s' parameter", documentDataKey)
	}

	// Convert the document data from JSON format to Firestore format
	// The client is passed to handle referenceValue types
	documentData, err := util.JSONToFirestoreValue(documentDataRaw, t.Client)
	if err != nil {
		return nil, fmt.Errorf("failed to convert document data: %w", err)
	}

	// Get return document data flag
	returnData := false
	if val, ok := mapParams[returnDocumentDataKey].(bool); ok {
		returnData = val
	}

	// Get the collection reference
	collection := t.Client.Collection(collectionPath)

	// Add the document to the collection
	docRef, writeResult, err := collection.Add(ctx, documentData)
	if err != nil {
		return nil, fmt.Errorf("failed to add document: %w", err)
	}

	// Build the response
	response := map[string]any{
		"documentPath": docRef.Path,
		"createTime":   writeResult.UpdateTime.Format("2006-01-02T15:04:05.999999999Z"),
	}

	// Add document data if requested
	if returnData {
		// Convert the document data back to simple JSON format
		simplifiedData := util.FirestoreValueToJSON(documentData)
		response["documentData"] = simplifiedData
	}

	return response, nil
}

func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
	return tools.ParseParams(t.Parameters, data, claims)
}

func (t Tool) Manifest() tools.Manifest {
	return t.manifest
}

func (t Tool) McpManifest() tools.McpManifest {
	return t.mcpManifest
}

func (t Tool) Authorized(verifiedAuthServices []string) bool {
	return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
}

func (t Tool) RequiresClientAuthorization() bool {
	return false
}

```

--------------------------------------------------------------------------------
/MCP-TOOLBOX-EXTENSION.md:
--------------------------------------------------------------------------------

```markdown
This document helps you find and install the right Gemini CLI extension to interact with your databases.

## How to Install an Extension

To install any of the extensions listed below, use the `gemini extensions install` command followed by the extension's GitHub repository URL.

For complete instructions on finding, installing, and managing extensions, please see the [official Gemini CLI extensions documentation](https://github.com/google-gemini/gemini-cli/blob/main/docs/extensions/index.md).

**Example Installation Command:**

```bash
gemini extensions install https://github.com/gemini-cli-extensions/EXTENSION_NAME
```

Make sure the user knows:
* These commands are not supported from within the CLI
* These commands will only be reflected in active CLI sessions on restart
* Extensions require Application Default Credentials in your environment. See [Set up ADC for a local development environment](https://cloud.google.com/docs/authentication/set-up-adc-local-dev-environment) to learn how you can provide either your user credentials or service account credentials to ADC in a local development environment.
* Most extensions require you to set environment variables to connect to a database. If there is a link provided for the configuration, fetch the web page and return the configuration.

-----

## Find Your Database Extension

Find your database or service in the list below to get the correct installation command.

**Note on Observability:** Extensions with `-observability` in their name are designed to help you understand the health and performance of your database instances, often by analyzing metrics and logs.

### Google Cloud Managed Databases

#### BigQuery

  * For data analytics and querying:
    ```bash
    gemini extensions install https://github.com/gemini-cli-extensions/bigquery-data-analytics
    ```

    Configuration: https://github.com/gemini-cli-extensions/bigquery-data-analytics/tree/main?tab=readme-ov-file#configuration

  * For conversational analytics (using natural language):
    ```bash
    gemini extensions install https://github.com/gemini-cli-extensions/bigquery-conversational-analytics
    ```
    
    Configuration: https://github.com/gemini-cli-extensions/bigquery-conversational-analytics/tree/main?tab=readme-ov-file#configuration

#### Cloud SQL for MySQL

  * Main Extension:
    ```bash
    gemini extensions install https://github.com/gemini-cli-extensions/cloud-sql-mysql
    ```
    Configuration: https://github.com/gemini-cli-extensions/cloud-sql-mysql/tree/main?tab=readme-ov-file#configuration

  * Observability:
    ```bash
    gemini extensions install https://github.com/gemini-cli-extensions/cloud-sql-mysql-observability
    ```

    If you are looking for self-hosted MySQL, consider the `mysql` extension.

#### Cloud SQL for PostgreSQL

  * Main Extension:
    ```bash
    gemini extensions install https://github.com/gemini-cli-extensions/cloud-sql-postgresql
    ```
    Configuration: https://github.com/gemini-cli-extensions/cloud-sql-postgresql/tree/main?tab=readme-ov-file#configuration

  * Observability:
    ```bash
    gemini extensions install https://github.com/gemini-cli-extensions/cloud-sql-postgresql-observability
    ```

    If you are looking for other PostgreSQL options, consider the `postgres` extension for self-hosted instances, or the `alloydb` extension for AlloyDB for PostgreSQL.

#### Cloud SQL for SQL Server

  * Main Extension:
    ```bash
    gemini extensions install https://github.com/gemini-cli-extensions/cloud-sql-sqlserver
    ```
    
    Configuration: https://github.com/gemini-cli-extensions/cloud-sql-sqlserver/tree/main?tab=readme-ov-file#configuration

  * Observability:
    ```bash
    gemini extensions install https://github.com/gemini-cli-extensions/cloud-sql-sqlserver-observability
    ```

    If you are looking for self-hosted SQL Server, consider the `sql-server` extension.

#### AlloyDB for PostgreSQL

  * Main Extension:
    ```bash
    gemini extensions install https://github.com/gemini-cli-extensions/alloydb
    ```
    
    Configuration: https://github.com/gemini-cli-extensions/alloydb/tree/main?tab=readme-ov-file#configuration

  * Observability:
    ```bash
    gemini extensions install https://github.com/gemini-cli-extensions/alloydb-observability
    ```

    If you are looking for other PostgreSQL options, consider the `postgres` extension for self-hosted instances, or the `cloud-sql-postgresql` extension for Cloud SQL for PostgreSQL.

#### Spanner

  * For querying Spanner databases:
    ```bash
    gemini extensions install https://github.com/gemini-cli-extensions/spanner
    ```
    
    Configuration: https://github.com/gemini-cli-extensions/spanner/tree/main?tab=readme-ov-file#configuration

#### Firestore

  * For querying Firestore in Native Mode:
    ```bash
    gemini extensions install https://github.com/gemini-cli-extensions/firestore-native
    ```

    Configuration: https://github.com/gemini-cli-extensions/firestore-native/tree/main?tab=readme-ov-file#configuration

### Other Google Cloud Data Services

#### Dataplex

  * For interacting with Dataplex data lakes and assets:
    ```bash
    gemini extensions install https://github.com/gemini-cli-extensions/dataplex
    ```

    Configuration: https://github.com/gemini-cli-extensions/dataplex/tree/main?tab=readme-ov-file#configuration

#### Looker

  * For querying Looker instances:
    ```bash
    gemini extensions install https://github.com/gemini-cli-extensions/looker
    ```

    Configuration: https://github.com/gemini-cli-extensions/looker/tree/main?tab=readme-ov-file#configuration

### Other Database Engines

These extensions are for connecting to database instances not managed by Cloud SQL (e.g., self-hosted on-prem, on a VM, or in another cloud).

  * MySQL:
    ```bash
    gemini extensions install https://github.com/gemini-cli-extensions/mysql
    ```
    
    Configuration: https://github.com/gemini-cli-extensions/mysql/tree/main?tab=readme-ov-file#configuration

    If you are looking for Google Cloud managed MySQL, consider the `cloud-sql-mysql` extension.

  * PostgreSQL:
    ```bash
    gemini extensions install https://github.com/gemini-cli-extensions/postgres
    ```
    
    Configuration: https://github.com/gemini-cli-extensions/postgres/tree/main?tab=readme-ov-file#configuration

    If you are looking for Google Cloud managed PostgreSQL, consider the `cloud-sql-postgresql` or `alloydb` extensions.

  * SQL Server:
    ```bash
    gemini extensions install https://github.com/gemini-cli-extensions/sql-server
    ```

    Configuration: https://github.com/gemini-cli-extensions/sql-server/tree/main?tab=readme-ov-file#configuration

    If you are looking for Google Cloud managed SQL Server, consider the `cloud-sql-sqlserver` extension.

### Custom Tools

#### MCP Toolbox

  * For connecting to MCP Toolbox servers:

    This extension can be used with any Google Cloud database to build custom tools. For more information, see the [MCP Toolbox documentation](https://googleapis.github.io/genai-toolbox/getting-started/introduction/).
    ```bash
    gemini extensions install https://github.com/gemini-cli-extensions/mcp-toolbox
    ```

    Configuration: https://github.com/gemini-cli-extensions/mcp-toolbox/tree/main?tab=readme-ov-file#configuration

```

--------------------------------------------------------------------------------
/internal/tools/spanner/spannersql/spannersql.go:
--------------------------------------------------------------------------------

```go
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package spannersql

import (
	"context"
	"fmt"
	"strings"

	"cloud.google.com/go/spanner"
	yaml "github.com/goccy/go-yaml"
	"github.com/googleapis/genai-toolbox/internal/sources"
	spannerdb "github.com/googleapis/genai-toolbox/internal/sources/spanner"
	"github.com/googleapis/genai-toolbox/internal/tools"
	"google.golang.org/api/iterator"
)

const kind string = "spanner-sql"

func init() {
	if !tools.Register(kind, newConfig) {
		panic(fmt.Sprintf("tool kind %q already registered", kind))
	}
}

func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
	actual := Config{Name: name}
	if err := decoder.DecodeContext(ctx, &actual); err != nil {
		return nil, err
	}
	return actual, nil
}

type compatibleSource interface {
	SpannerClient() *spanner.Client
	DatabaseDialect() string
}

// validate compatible sources are still compatible
var _ compatibleSource = &spannerdb.Source{}

var compatibleSources = [...]string{spannerdb.SourceKind}

type Config struct {
	Name               string           `yaml:"name" validate:"required"`
	Kind               string           `yaml:"kind" validate:"required"`
	Source             string           `yaml:"source" validate:"required"`
	Description        string           `yaml:"description" validate:"required"`
	Statement          string           `yaml:"statement" validate:"required"`
	ReadOnly           bool             `yaml:"readOnly"`
	AuthRequired       []string         `yaml:"authRequired"`
	Parameters         tools.Parameters `yaml:"parameters"`
	TemplateParameters tools.Parameters `yaml:"templateParameters"`
}

// validate interface
var _ tools.ToolConfig = Config{}

func (cfg Config) ToolConfigKind() string {
	return kind
}

func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
	// verify source exists
	rawS, ok := srcs[cfg.Source]
	if !ok {
		return nil, fmt.Errorf("no source named %q configured", cfg.Source)
	}

	// verify the source is compatible
	s, ok := rawS.(compatibleSource)
	if !ok {
		return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
	}

	allParameters, paramManifest, err := tools.ProcessParameters(cfg.TemplateParameters, cfg.Parameters)
	if err != nil {
		return nil, err
	}

	mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, allParameters)

	// finish tool setup
	t := Tool{
		Name:               cfg.Name,
		Kind:               kind,
		Parameters:         cfg.Parameters,
		TemplateParameters: cfg.TemplateParameters,
		AllParams:          allParameters,
		Statement:          cfg.Statement,
		AuthRequired:       cfg.AuthRequired,
		ReadOnly:           cfg.ReadOnly,
		Client:             s.SpannerClient(),
		dialect:            s.DatabaseDialect(),
		manifest:           tools.Manifest{Description: cfg.Description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired},
		mcpManifest:        mcpManifest,
	}
	return t, nil
}

// validate interface
var _ tools.Tool = Tool{}

type Tool struct {
	Name               string           `yaml:"name"`
	Kind               string           `yaml:"kind"`
	AuthRequired       []string         `yaml:"authRequired"`
	Parameters         tools.Parameters `yaml:"parameters"`
	TemplateParameters tools.Parameters `yaml:"templateParameters"`
	AllParams          tools.Parameters `yaml:"allParams"`
	ReadOnly           bool             `yaml:"readOnly"`
	Client             *spanner.Client
	dialect            string
	Statement          string
	manifest           tools.Manifest
	mcpManifest        tools.McpManifest
}

func getMapParams(params tools.ParamValues, dialect string) (map[string]interface{}, error) {
	switch strings.ToLower(dialect) {
	case "googlesql":
		return params.AsMap(), nil
	case "postgresql":
		return params.AsMapByOrderedKeys(), nil
	default:
		return nil, fmt.Errorf("invalid dialect %s", dialect)
	}
}

// processRows iterates over the spanner.RowIterator and converts each row to a map[string]any.
func processRows(iter *spanner.RowIterator) ([]any, error) {
	var out []any
	defer iter.Stop()

	for {
		row, err := iter.Next()
		if err == iterator.Done {
			break
		}
		if err != nil {
			return nil, fmt.Errorf("unable to parse row: %w", err)
		}

		vMap := make(map[string]any)
		cols := row.ColumnNames()
		for i, c := range cols {
			vMap[c] = row.ColumnValue(i)
		}
		out = append(out, vMap)
	}
	return out, nil
}

func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
	paramsMap := params.AsMap()
	newStatement, err := tools.ResolveTemplateParams(t.TemplateParameters, t.Statement, paramsMap)
	if err != nil {
		return nil, fmt.Errorf("unable to extract template params %w", err)
	}

	newParams, err := tools.GetParams(t.Parameters, paramsMap)
	if err != nil {
		return nil, fmt.Errorf("unable to extract standard params %w", err)
	}

	for i, p := range t.Parameters {
		name := p.GetName()
		value := newParams[i].Value

		// Spanner only accepts typed slices as input
		// This checks if the param is an array.
		// If yes, convert []any to typed slice (e.g []string, []int)
		switch arrayParam := p.(type) {
		case *tools.ArrayParameter:
			arrayParamValue, ok := value.([]any)
			if !ok {
				return nil, fmt.Errorf("unable to convert parameter `%s` to []any %w", name, err)
			}
			itemType := arrayParam.GetItems().GetType()
			var err error
			value, err = tools.ConvertAnySliceToTyped(arrayParamValue, itemType)
			if err != nil {
				return nil, fmt.Errorf("unable to convert parameter `%s` from []any to typed slice: %w", name, err)
			}
		}
		newParams[i] = tools.ParamValue{Name: name, Value: value}
	}

	mapParams, err := getMapParams(newParams, t.dialect)
	if err != nil {
		return nil, fmt.Errorf("fail to get map params: %w", err)
	}

	var results []any
	var opErr error
	stmt := spanner.Statement{
		SQL:    newStatement,
		Params: mapParams,
	}

	if t.ReadOnly {
		iter := t.Client.Single().Query(ctx, stmt)
		results, opErr = processRows(iter)
	} else {
		_, opErr = t.Client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
			iter := txn.Query(ctx, stmt)
			results, err = processRows(iter)
			if err != nil {
				return err
			}
			return nil
		})
	}

	if opErr != nil {
		return nil, fmt.Errorf("unable to execute client: %w", opErr)
	}

	return results, nil
}

func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
	return tools.ParseParams(t.AllParams, data, claims)
}

func (t Tool) Manifest() tools.Manifest {
	return t.manifest
}

func (t Tool) McpManifest() tools.McpManifest {
	return t.mcpManifest
}

func (t Tool) Authorized(verifiedAuthServices []string) bool {
	return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
}

func (t Tool) RequiresClientAuthorization() bool {
	return false
}

```

--------------------------------------------------------------------------------
/internal/tools/mysql/mysqllisttablefragmentation/mysqllisttablefragmentation.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package mysqllisttablefragmentation

import (
	"context"
	"database/sql"
	"fmt"

	yaml "github.com/goccy/go-yaml"
	"github.com/googleapis/genai-toolbox/internal/sources"
	"github.com/googleapis/genai-toolbox/internal/sources/cloudsqlmysql"
	"github.com/googleapis/genai-toolbox/internal/sources/mysql"
	"github.com/googleapis/genai-toolbox/internal/tools"
	"github.com/googleapis/genai-toolbox/internal/tools/mysql/mysqlcommon"
	"github.com/googleapis/genai-toolbox/internal/util"
)

const kind string = "mysql-list-table-fragmentation"

const listTableFragmentationStatement = `
	SELECT
		table_schema,
		table_name,
		data_length AS data_size,
		index_length AS index_size,
		data_free AS data_free,
		ROUND((data_free / (data_length + index_length)) * 100, 2) AS fragmentation_percentage
	FROM
		information_schema.tables
	WHERE
		table_schema NOT IN ('sys', 'performance_schema', 'mysql', 'information_schema')
		AND (COALESCE(?, '') = '' OR table_schema = ?)
		AND (COALESCE(?, '') = '' OR table_name = ?)
		AND data_free >= ?
	ORDER BY
		fragmentation_percentage DESC,
		table_schema,
		table_name
	LIMIT ?;
`

func init() {
	if !tools.Register(kind, newConfig) {
		panic(fmt.Sprintf("tool kind %q already registered", kind))
	}
}

func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
	actual := Config{Name: name}
	if err := decoder.DecodeContext(ctx, &actual); err != nil {
		return nil, err
	}
	return actual, nil
}

type compatibleSource interface {
	MySQLPool() *sql.DB
}

// validate compatible sources are still compatible
var _ compatibleSource = &mysql.Source{}
var _ compatibleSource = &cloudsqlmysql.Source{}

var compatibleSources = [...]string{mysql.SourceKind, cloudsqlmysql.SourceKind}

type Config struct {
	Name         string   `yaml:"name" validate:"required"`
	Kind         string   `yaml:"kind" validate:"required"`
	Source       string   `yaml:"source" validate:"required"`
	Description  string   `yaml:"description" validate:"required"`
	AuthRequired []string `yaml:"authRequired"`
}

// validate interface
var _ tools.ToolConfig = Config{}

func (cfg Config) ToolConfigKind() string {
	return kind
}

func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
	// verify source exists
	rawS, ok := srcs[cfg.Source]
	if !ok {
		return nil, fmt.Errorf("no source named %q configured", cfg.Source)
	}

	// verify the source is compatible
	s, ok := rawS.(compatibleSource)
	if !ok {
		return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
	}

	allParameters := tools.Parameters{
		tools.NewStringParameterWithDefault("table_schema", "", "(Optional) The database where fragmentation check is to be executed. Check all tables visible to the current user if not specified"),
		tools.NewStringParameterWithDefault("table_name", "", "(Optional) Name of the table to be checked. Check all tables visible to the current user if not specified."),
		tools.NewIntParameterWithDefault("data_free_threshold_bytes", 1, "(Optional) Only show tables with at least this much free space in bytes. Default is 1"),
		tools.NewIntParameterWithDefault("limit", 10, "(Optional) Max rows to return, default is 10"),
	}
	mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, allParameters)

	// finish tool setup
	t := Tool{
		Name:         cfg.Name,
		Kind:         kind,
		AuthRequired: cfg.AuthRequired,
		Pool:         s.MySQLPool(),
		allParams:    allParameters,
		manifest:     tools.Manifest{Description: cfg.Description, Parameters: allParameters.Manifest(), AuthRequired: cfg.AuthRequired},
		mcpManifest:  mcpManifest,
	}
	return t, nil
}

// validate interface
var _ tools.Tool = Tool{}

type Tool struct {
	Name         string           `yaml:"name"`
	Kind         string           `yaml:"kind"`
	AuthRequired []string         `yaml:"authRequired"`
	allParams    tools.Parameters `yaml:"parameters"`
	Pool         *sql.DB
	manifest     tools.Manifest
	mcpManifest  tools.McpManifest
}

func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
	paramsMap := params.AsMap()

	table_schema, ok := paramsMap["table_schema"].(string)
	if !ok {
		return nil, fmt.Errorf("invalid 'table_schema' parameter; expected a string")
	}
	table_name, ok := paramsMap["table_name"].(string)
	if !ok {
		return nil, fmt.Errorf("invalid 'table_name' parameter; expected a string")
	}
	data_free_threshold_bytes, ok := paramsMap["data_free_threshold_bytes"].(int)
	if !ok {
		return nil, fmt.Errorf("invalid 'data_free_threshold_bytes' parameter; expected an integer")
	}
	limit, ok := paramsMap["limit"].(int)
	if !ok {
		return nil, fmt.Errorf("invalid 'limit' parameter; expected an integer")
	}

	// Log the query executed for debugging.
	logger, err := util.LoggerFromContext(ctx)
	if err != nil {
		return nil, fmt.Errorf("error getting logger: %s", err)
	}
	logger.DebugContext(ctx, "executing `%s` tool query: %s", kind, listTableFragmentationStatement)

	results, err := t.Pool.QueryContext(ctx, listTableFragmentationStatement, table_schema, table_schema, table_name, table_name, data_free_threshold_bytes, limit)
	if err != nil {
		return nil, fmt.Errorf("unable to execute query: %w", err)
	}
	defer results.Close()

	cols, err := results.Columns()
	if err != nil {
		return nil, fmt.Errorf("unable to retrieve rows column name: %w", err)
	}

	// create an array of values for each column, which can be re-used to scan each row
	rawValues := make([]any, len(cols))
	values := make([]any, len(cols))
	for i := range rawValues {
		values[i] = &rawValues[i]
	}

	colTypes, err := results.ColumnTypes()
	if err != nil {
		return nil, fmt.Errorf("unable to get column types: %w", err)
	}

	var out []any
	for results.Next() {
		err := results.Scan(values...)
		if err != nil {
			return nil, fmt.Errorf("unable to parse row: %w", err)
		}
		vMap := make(map[string]any)
		for i, name := range cols {
			val := rawValues[i]
			if val == nil {
				vMap[name] = nil
				continue
			}

			vMap[name], err = mysqlcommon.ConvertToType(colTypes[i], val)
			if err != nil {
				return nil, fmt.Errorf("errors encountered when converting values: %w", err)
			}
		}
		out = append(out, vMap)
	}

	if err := results.Err(); err != nil {
		return nil, fmt.Errorf("errors encountered during row iteration: %w", err)
	}

	return out, nil
}

func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
	return tools.ParseParams(t.allParams, data, claims)
}

func (t Tool) Manifest() tools.Manifest {
	return t.manifest
}

func (t Tool) McpManifest() tools.McpManifest {
	return t.mcpManifest
}

func (t Tool) Authorized(verifiedAuthServices []string) bool {
	return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
}

func (t Tool) RequiresClientAuthorization() bool {
	return false
}

```

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

```javascript
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import { renderToolInterface } from "./toolDisplay.js";

let toolDetailsAbortController = null;

/**
 * Fetches a toolset from the /api/toolset endpoint and initiates creating the tool list.
 * @param {!HTMLElement} secondNavContent The HTML element where the tool list will be rendered.
 * @param {!HTMLElement} toolDisplayArea The HTML element where the details of a selected tool will be displayed.
 * @param {string} toolsetName The name of the toolset to load (empty string loads all tools).
 * @returns {!Promise<void>} A promise that resolves when the tools are loaded and rendered, or rejects on error.
 */
export async function loadTools(secondNavContent, toolDisplayArea, toolsetName) {
    secondNavContent.innerHTML = '<p>Fetching tools...</p>';
    try {
        const response = await fetch(`/api/toolset/${toolsetName}`);
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        const apiResponse = await response.json();
        renderToolList(apiResponse, secondNavContent, toolDisplayArea);
    } catch (error) {
        console.error('Failed to load tools:', error);
        secondNavContent.innerHTML = `<p class="error">Failed to load tools: <pre><code>${error}</code></pre></p>`;
    }
}

/**
 * Renders the list of tools as buttons within the provided HTML element.
 * @param {?{tools: ?Object<string,*>} } apiResponse The API response object containing the tools.
 * @param {!HTMLElement} secondNavContent The HTML element to render the tool list into.
 * @param {!HTMLElement} toolDisplayArea The HTML element for displaying tool details (passed to event handlers).
 */
function renderToolList(apiResponse, secondNavContent, toolDisplayArea) {
    secondNavContent.innerHTML = '';

    if (!apiResponse || typeof apiResponse.tools !== 'object' || apiResponse.tools === null) {
        console.error('Error: Expected an object with a "tools" property, but received:', apiResponse);
        secondNavContent.textContent = 'Error: Invalid response format from toolset API.';
        return;
    }

    const toolsObject = apiResponse.tools;
    const toolNames = Object.keys(toolsObject);

    if (toolNames.length === 0) {
        secondNavContent.textContent = 'No tools found.';
        return;
    }

    const ul = document.createElement('ul');
    toolNames.forEach(toolName => {
        const li = document.createElement('li');
        const button = document.createElement('button');
        button.textContent = toolName;
        button.dataset.toolname = toolName;
        button.classList.add('tool-button');
        button.addEventListener('click', (event) => handleToolClick(event, secondNavContent, toolDisplayArea));
        li.appendChild(button);
        ul.appendChild(li);
    });
    secondNavContent.appendChild(ul);
}

/**
 * Handles the click event on a tool button. 
 * @param {!Event} event The click event object.
 * @param {!HTMLElement} secondNavContent The parent element containing the tool buttons.
 * @param {!HTMLElement} toolDisplayArea The HTML element where tool details will be shown.
 */
function handleToolClick(event, secondNavContent, toolDisplayArea) {
    const toolName = event.target.dataset.toolname;
    if (toolName) {
        const currentActive = secondNavContent.querySelector('.tool-button.active');
        if (currentActive) {
            currentActive.classList.remove('active');
        }
        event.target.classList.add('active');
        fetchToolDetails(toolName, toolDisplayArea);
    }
}

/**
 * Fetches details for a specific tool /api/tool endpoint.
 * It aborts any previous in-flight request for tool details to stop race condition.
 * @param {string} toolName The name of the tool to fetch details for.
 * @param {!HTMLElement} toolDisplayArea The HTML element to display the tool interface in.
 * @returns {!Promise<void>} A promise that resolves when the tool details are fetched and rendered, or rejects on error.
 */
async function fetchToolDetails(toolName, toolDisplayArea) {
    if (toolDetailsAbortController) {
        toolDetailsAbortController.abort();
        console.debug("Aborted previous tool fetch.");
    }

    toolDetailsAbortController = new AbortController();
    const signal = toolDetailsAbortController.signal;

    toolDisplayArea.innerHTML = '<p>Loading tool details...</p>';

    try {
        const response = await fetch(`/api/tool/${encodeURIComponent(toolName)}`, { signal });
        if (!response.ok) {
                throw new Error(`HTTP error! status: ${response.status}`);
        }
        const apiResponse = await response.json();

        if (!apiResponse.tools || !apiResponse.tools[toolName]) {
            throw new Error(`Tool "${toolName}" data not found in API response.`);
        }
        const toolObject = apiResponse.tools[toolName];
        console.debug("Received tool object: ", toolObject)

        const toolInterfaceData = {
            id: toolName,
            name: toolName,
            description: toolObject.description || "No description provided.",
            authRequired: toolObject.authRequired || [],
            parameters: (toolObject.parameters || []).map(param => {
                let inputType = 'text'; 
                const apiType = param.type ? param.type.toLowerCase() : 'string';
                let valueType = 'string'; 
                let label = param.description || param.name;

                if (apiType === 'integer' || apiType === 'float') {
                    inputType = 'number';
                    valueType = 'number';
                } else if (apiType === 'boolean') {
                    inputType = 'checkbox';
                    valueType = 'boolean';
                } else if (apiType === 'array') {
                    inputType = 'textarea'; 
                    const itemType = param.items && param.items.type ? param.items.type.toLowerCase() : 'string';
                    valueType = `array<${itemType}>`;
                    label += ' (Array)';
                }

                return {
                    name: param.name,
                    type: inputType,    
                    valueType: valueType, 
                    label: label,
                    authServices: param.authSources,
                    required: param.required || false,
                    // defaultValue: param.default, can't do this yet bc tool manifest doesn't have default
                };
            })
        };

        console.debug("Transformed toolInterfaceData:", toolInterfaceData);

        renderToolInterface(toolInterfaceData, toolDisplayArea);
    } catch (error) {
        if (error.name === 'AbortError') {
            console.debug("Previous fetch was aborted, expected behavior.");
        } else {
            console.error(`Failed to load details for tool "${toolName}":`, error);
            toolDisplayArea.innerHTML = `<p class="error">Failed to load details for ${toolName}. ${error.message}</p>`;
        }
    }
}
```

--------------------------------------------------------------------------------
/internal/sources/yugabytedb/yugabytedb_test.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package yugabytedb_test

import (
	"testing"

	"strings"

	yaml "github.com/goccy/go-yaml"
	"github.com/google/go-cmp/cmp"
	"github.com/googleapis/genai-toolbox/internal/server"
	"github.com/googleapis/genai-toolbox/internal/sources/yugabytedb"
	"github.com/googleapis/genai-toolbox/internal/testutils"
)

// Basic config parse
func TestParseFromYamlYugabyteDB(t *testing.T) {
	tcs := []struct {
		desc string
		in   string
		want server.SourceConfigs
	}{
		{
			desc: "only required fields",
			in: `
			sources:
				my-yb-instance:
					kind: yugabytedb
					name: my-yb-instance
					host: yb-host
					port: yb-port
					user: yb_user
					password: yb_pass
					database: yb_db
			`,
			want: server.SourceConfigs{
				"my-yb-instance": yugabytedb.Config{
					Name:     "my-yb-instance",
					Kind:     "yugabytedb",
					Host:     "yb-host",
					Port:     "yb-port",
					User:     "yb_user",
					Password: "yb_pass",
					Database: "yb_db",
				},
			},
		},
		{
			desc: "with loadBalance only",
			in: `
			sources:
				my-yb-instance:
					kind: yugabytedb
					name: my-yb-instance
					host: yb-host
					port: yb-port
					user: yb_user
					password: yb_pass
					database: yb_db
					loadBalance: true
			`,
			want: server.SourceConfigs{
				"my-yb-instance": yugabytedb.Config{
					Name:        "my-yb-instance",
					Kind:        "yugabytedb",
					Host:        "yb-host",
					Port:        "yb-port",
					User:        "yb_user",
					Password:    "yb_pass",
					Database:    "yb_db",
					LoadBalance: "true",
				},
			},
		},
		{
			desc: "loadBalance with topologyKeys",
			in: `
			sources:
				my-yb-instance:
					kind: yugabytedb
					name: my-yb-instance
					host: yb-host
					port: yb-port
					user: yb_user
					password: yb_pass
					database: yb_db
					loadBalance: true
					topologyKeys: zone1,zone2
			`,
			want: server.SourceConfigs{
				"my-yb-instance": yugabytedb.Config{
					Name:         "my-yb-instance",
					Kind:         "yugabytedb",
					Host:         "yb-host",
					Port:         "yb-port",
					User:         "yb_user",
					Password:     "yb_pass",
					Database:     "yb_db",
					LoadBalance:  "true",
					TopologyKeys: "zone1,zone2",
				},
			},
		},
		{
			desc: "with fallback only",
			in: `
			sources:
				my-yb-instance:
					kind: yugabytedb
					name: my-yb-instance
					host: yb-host
					port: yb-port
					user: yb_user
					password: yb_pass
					database: yb_db
					loadBalance: true
					topologyKeys: zone1
					fallbackToTopologyKeysOnly: true
			`,
			want: server.SourceConfigs{
				"my-yb-instance": yugabytedb.Config{
					Name:                       "my-yb-instance",
					Kind:                       "yugabytedb",
					Host:                       "yb-host",
					Port:                       "yb-port",
					User:                       "yb_user",
					Password:                   "yb_pass",
					Database:                   "yb_db",
					LoadBalance:                "true",
					TopologyKeys:               "zone1",
					FallBackToTopologyKeysOnly: "true",
				},
			},
		},
		{
			desc: "with refresh interval and reconnect delay",
			in: `
			sources:
				my-yb-instance:
					kind: yugabytedb
					name: my-yb-instance
					host: yb-host
					port: yb-port
					user: yb_user
					password: yb_pass
					database: yb_db
					loadBalance: true
					ybServersRefreshInterval: 20
					failedHostReconnectDelaySecs: 5
			`,
			want: server.SourceConfigs{
				"my-yb-instance": yugabytedb.Config{
					Name:                            "my-yb-instance",
					Kind:                            "yugabytedb",
					Host:                            "yb-host",
					Port:                            "yb-port",
					User:                            "yb_user",
					Password:                        "yb_pass",
					Database:                        "yb_db",
					LoadBalance:                     "true",
					YBServersRefreshInterval:        "20",
					FailedHostReconnectDelaySeconds: "5",
				},
			},
		},
		{
			desc: "all fields set",
			in: `
			sources:
				my-yb-instance:
					kind: yugabytedb
					name: my-yb-instance
					host: yb-host
					port: yb-port
					user: yb_user
					password: yb_pass
					database: yb_db
					loadBalance: true
					topologyKeys: zone1,zone2
					fallbackToTopologyKeysOnly: true
					ybServersRefreshInterval: 30
					failedHostReconnectDelaySecs: 10
			`,
			want: server.SourceConfigs{
				"my-yb-instance": yugabytedb.Config{
					Name:                            "my-yb-instance",
					Kind:                            "yugabytedb",
					Host:                            "yb-host",
					Port:                            "yb-port",
					User:                            "yb_user",
					Password:                        "yb_pass",
					Database:                        "yb_db",
					LoadBalance:                     "true",
					TopologyKeys:                    "zone1,zone2",
					FallBackToTopologyKeysOnly:      "true",
					YBServersRefreshInterval:        "30",
					FailedHostReconnectDelaySeconds: "10",
				},
			},
		},
	}

	for _, tc := range tcs {
		t.Run(tc.desc, func(t *testing.T) {
			got := struct {
				Sources server.SourceConfigs `yaml:"sources"`
			}{}

			err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
			if err != nil {
				t.Fatalf("unable to unmarshal: %s", err)
			}
			if !cmp.Equal(tc.want, got.Sources) {
				t.Fatalf("incorrect parse (-want +got):\n%s", cmp.Diff(tc.want, got.Sources))
			}
		})
	}
}

func TestFailParseFromYamlYugabyteDB(t *testing.T) {
	tcs := []struct {
		desc string
		in   string
		err  string
	}{
		{
			desc: "extra field",
			in: `
			sources:
				my-yb-source:
					kind: yugabytedb
					name: my-yb-source
					host: yb-host
					port: yb-port
					database: yb_db
					user: yb_user
					password: yb_pass
					foo: bar
			`,
			err: "unable to parse source \"my-yb-source\" as \"yugabytedb\": [2:1] unknown field \"foo\"",
		},
		{
			desc: "missing required field (password)",
			in: `
			sources:
				my-yb-source:
					kind: yugabytedb
					name: my-yb-source
					host: yb-host
					port: yb-port
					database: yb_db
					user: yb_user
			`,
			err: "unable to parse source \"my-yb-source\" as \"yugabytedb\": Key: 'Config.Password' Error:Field validation for 'Password' failed on the 'required' tag",
		},
		{
			desc: "missing required field (host)",
			in: `
			sources:
				my-yb-source:
					kind: yugabytedb
					name: my-yb-source
					port: yb-port
					database: yb_db
					user: yb_user
					password: yb_pass
			`,
			err: "unable to parse source \"my-yb-source\" as \"yugabytedb\": Key: 'Config.Host' Error:Field validation for 'Host' failed on the 'required' tag",
		},
	}
	for _, tc := range tcs {
		t.Run(tc.desc, func(t *testing.T) {
			got := struct {
				Sources server.SourceConfigs `yaml:"sources"`
			}{}
			err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
			if err == nil {
				t.Fatalf("expected parsing to fail")
			}
			errStr := err.Error()
			if !strings.Contains(errStr, tc.err) {
				t.Fatalf("unexpected error:\nGot:  %q\nWant: %q", errStr, tc.err)
			}
		})
	}
}

```

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

```go
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//	http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package mongodbfind

import (
	"context"
	"encoding/json"
	"fmt"
	"slices"

	"github.com/goccy/go-yaml"
	mongosrc "github.com/googleapis/genai-toolbox/internal/sources/mongodb"
	"github.com/googleapis/genai-toolbox/internal/util"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"

	"github.com/googleapis/genai-toolbox/internal/sources"
	"github.com/googleapis/genai-toolbox/internal/tools"
)

const kind string = "mongodb-find"

func init() {
	if !tools.Register(kind, newConfig) {
		panic(fmt.Sprintf("tool kind %q already registered", kind))
	}
}

func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
	actual := Config{Name: name}
	if err := decoder.DecodeContext(ctx, &actual); err != nil {
		return nil, err
	}
	return actual, nil
}

type Config struct {
	Name           string           `yaml:"name" validate:"required"`
	Kind           string           `yaml:"kind" validate:"required"`
	Source         string           `yaml:"source" validate:"required"`
	AuthRequired   []string         `yaml:"authRequired" validate:"required"`
	Description    string           `yaml:"description" validate:"required"`
	Database       string           `yaml:"database" validate:"required"`
	Collection     string           `yaml:"collection" validate:"required"`
	FilterPayload  string           `yaml:"filterPayload" validate:"required"`
	FilterParams   tools.Parameters `yaml:"filterParams"`
	ProjectPayload string           `yaml:"projectPayload"`
	ProjectParams  tools.Parameters `yaml:"projectParams"`
	SortPayload    string           `yaml:"sortPayload"`
	SortParams     tools.Parameters `yaml:"sortParams"`
	Limit          int64            `yaml:"limit"`
}

// validate interface
var _ tools.ToolConfig = Config{}

func (cfg Config) ToolConfigKind() string {
	return kind
}

func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
	// verify source exists
	rawS, ok := srcs[cfg.Source]
	if !ok {
		return nil, fmt.Errorf("no source named %q configured", cfg.Source)
	}

	// verify the source is compatible
	s, ok := rawS.(*mongosrc.Source)
	if !ok {
		return nil, fmt.Errorf("invalid source for %q tool: source kind must be `mongodb`", kind)
	}

	// Create a slice for all parameters
	allParameters := slices.Concat(cfg.FilterParams, cfg.ProjectParams, cfg.SortParams)

	// Verify no duplicate parameter names
	err := tools.CheckDuplicateParameters(allParameters)
	if err != nil {
		return nil, err
	}

	// Verify 'limit' value
	if cfg.Limit <= 0 {
		return nil, fmt.Errorf("limit must be a positive number, but got %d", cfg.Limit)
	}

	// Create Toolbox manifest
	paramManifest := allParameters.Manifest()
	if paramManifest == nil {
		paramManifest = make([]tools.ParameterManifest, 0)
	}

	// Create MCP manifest
	mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, allParameters)

	// finish tool setup
	return Tool{
		Name:           cfg.Name,
		Kind:           kind,
		AuthRequired:   cfg.AuthRequired,
		Collection:     cfg.Collection,
		FilterPayload:  cfg.FilterPayload,
		FilterParams:   cfg.FilterParams,
		ProjectPayload: cfg.ProjectPayload,
		ProjectParams:  cfg.ProjectParams,
		SortPayload:    cfg.SortPayload,
		SortParams:     cfg.SortParams,
		Limit:          cfg.Limit,
		AllParams:      allParameters,
		database:       s.Client.Database(cfg.Database),
		manifest:       tools.Manifest{Description: cfg.Description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired},
		mcpManifest:    mcpManifest,
	}, nil
}

// validate interface
var _ tools.Tool = Tool{}

type Tool struct {
	Name           string           `yaml:"name"`
	Kind           string           `yaml:"kind"`
	Description    string           `yaml:"description"`
	AuthRequired   []string         `yaml:"authRequired"`
	Collection     string           `yaml:"collection"`
	FilterPayload  string           `yaml:"filterPayload"`
	FilterParams   tools.Parameters `yaml:"filterParams"`
	ProjectPayload string           `yaml:"projectPayload"`
	ProjectParams  tools.Parameters `yaml:"projectParams"`
	SortPayload    string           `yaml:"sortPayload"`
	SortParams     tools.Parameters `yaml:"sortParams"`
	Limit          int64            `yaml:"limit"`
	AllParams      tools.Parameters `yaml:"allParams"`

	database    *mongo.Database
	manifest    tools.Manifest
	mcpManifest tools.McpManifest
}

func getOptions(ctx context.Context, sortParameters tools.Parameters, projectPayload string, limit int64, paramsMap map[string]any) (*options.FindOptions, error) {
	logger, err := util.LoggerFromContext(ctx)
	if err != nil {
		panic(err)
	}

	opts := options.Find()

	sort := bson.M{}
	for _, p := range sortParameters {
		sort[p.GetName()] = paramsMap[p.GetName()]
	}
	opts = opts.SetSort(sort)

	if len(projectPayload) > 0 {

		result, err := tools.PopulateTemplateWithJSON("MongoDBFindProjectString", projectPayload, paramsMap)

		if err != nil {
			return nil, fmt.Errorf("error populating project payload: %s", err)
		}

		var projection any
		err = bson.UnmarshalExtJSON([]byte(result), false, &projection)
		if err != nil {
			return nil, fmt.Errorf("error unmarshalling projection: %s", err)
		}

		opts = opts.SetProjection(projection)
		logger.DebugContext(ctx, "Projection is set to %v", projection)
	}

	if limit > 0 {
		opts = opts.SetLimit(limit)
		logger.DebugContext(ctx, "Limit is being set to %d", limit)
	}
	return opts, nil
}

func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
	paramsMap := params.AsMap()

	filterString, err := tools.PopulateTemplateWithJSON("MongoDBFindFilterString", t.FilterPayload, paramsMap)

	if err != nil {
		return nil, fmt.Errorf("error populating filter: %s", err)
	}

	opts, err := getOptions(ctx, t.SortParams, t.ProjectPayload, t.Limit, paramsMap)
	if err != nil {
		return nil, fmt.Errorf("error populating options: %s", err)
	}

	var filter = bson.D{}
	err = bson.UnmarshalExtJSON([]byte(filterString), false, &filter)
	if err != nil {
		return nil, err
	}

	cur, err := t.database.Collection(t.Collection).Find(ctx, filter, opts)
	if err != nil {
		return nil, err
	}
	defer cur.Close(ctx)

	var data = []any{}
	err = cur.All(context.TODO(), &data)
	if err != nil {
		return nil, err
	}

	var final []any
	for _, item := range data {
		tmp, _ := bson.MarshalExtJSON(item, false, false)
		var tmp2 any
		err = json.Unmarshal(tmp, &tmp2)
		if err != nil {
			return nil, err
		}
		final = append(final, tmp2)
	}

	return final, err
}

func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
	return tools.ParseParams(t.AllParams, data, claims)
}

func (t Tool) Manifest() tools.Manifest {
	return t.manifest
}

func (t Tool) McpManifest() tools.McpManifest {
	return t.mcpManifest
}

func (t Tool) Authorized(verifiedAuthServices []string) bool {
	return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
}

func (t Tool) RequiresClientAuthorization() bool {
	return false
}

```

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

```javascript
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * Renders the Google Sign-In button using the GIS library.
 * @param {string} toolId The ID of the tool.
 * @param {string} clientId The Google OAuth Client ID.
 * @param {string} authProfileName The name of the auth service in tools file.
 */
function renderGoogleSignInButton(toolId, clientId, authProfileName) { 
    const UNIQUE_ID_BASE = `${toolId}-${authProfileName}`;
    const GIS_CONTAINER_ID = `gisContainer-${UNIQUE_ID_BASE}`;
    const gisContainer = document.getElementById(GIS_CONTAINER_ID);
    const setupGisBtn = document.querySelector(`#google-auth-details-${UNIQUE_ID_BASE} .btn--setup-gis`);

    if (!gisContainer) {
        console.error('GIS container not found:', GIS_CONTAINER_ID);
        return;
    }

    if (!clientId) {
        alert('Please enter an OAuth Client ID first.');
        return;
    }

    // hide the setup button and show the container for the GIS button
    if (setupGisBtn) setupGisBtn.style.display = 'none';
    gisContainer.innerHTML = ''; 
    gisContainer.style.display = 'flex'; 
    if (window.google && window.google.accounts && window.google.accounts.id) {
        try {
            const handleResponse = (response) => handleCredentialResponse(response, toolId, authProfileName);
            window.google.accounts.id.initialize({
                client_id: clientId,
                callback: handleResponse,
                auto_select: false
            });
            window.google.accounts.id.renderButton(
                gisContainer,
                { theme: "outline", size: "large", text: "signin_with" }
            );
        } catch (error) {
            console.error("Error initializing Google Sign-In:", error);
            alert("Error initializing Google Sign-In. Check the Client ID and browser console.");
            gisContainer.innerHTML = '<p style="color: red;">Error loading Sign-In button.</p>';
            if (setupGisBtn) setupGisBtn.style.display = ''; 
        }
    } else {
        console.error("GIS library not fully loaded yet.");
        alert("Google Identity Services library not ready. Please try again in a moment.");
        gisContainer.innerHTML = '<p style="color: red;">GIS library not ready.</p>';
        if (setupGisBtn) setupGisBtn.style.display = ''; 
    }
}

/**
 * Handles the credential response from the Google Sign-In library.
 * @param {!CredentialResponse} response The credential response object from GIS.
 * @param {string} toolId The ID of the tool.
 * @param {string} authProfileName The name of the auth service in tools file.
 */
function handleCredentialResponse(response, toolId, authProfileName) { 
    console.debug("handleCredentialResponse called with:", { response, toolId, authProfileName });
    const headersTextarea = document.getElementById(`headers-textarea-${toolId}`);
    if (!headersTextarea) {
        console.error('Headers textarea not found for toolId:', toolId);
        return;
    }

    const UNIQUE_ID_BASE = `${toolId}-${authProfileName}`;
    const setupGisBtn = document.querySelector(`#google-auth-details-${UNIQUE_ID_BASE} .setup-gis-btn`);
    const gisContainer = document.getElementById(`gisContainer-${UNIQUE_ID_BASE}`);

    if (response.credential) {
        const idToken = response.credential;

        try {
            let currentHeaders = {};
            if (headersTextarea.value) {
                currentHeaders = JSON.parse(headersTextarea.value);
            }
            const HEADER_KEY = `${authProfileName}_token`; 
            currentHeaders[HEADER_KEY] = `${idToken}`;
            headersTextarea.value = JSON.stringify(currentHeaders, null, 2);

            if (gisContainer) gisContainer.style.display = 'none';
            if (setupGisBtn) setupGisBtn.style.display = '';

        } catch (e) {
            alert('Headers are not valid JSON. Please correct and try again.');
            console.error("Header JSON parse error:", e);
        }
    } else {
        console.error("Error: No credential in response", response);
        alert('Error: No ID Token received. Check console for details.');
        
        if (gisContainer) gisContainer.style.display = 'none';
        if (setupGisBtn) setupGisBtn.style.display = '';
    }
}

// creates the Google Auth method dropdown
export function createGoogleAuthMethodItem(toolId, authProfileName) { 
    const UNIQUE_ID_BASE = `${toolId}-${authProfileName}`;
    const item = document.createElement('div');

    item.className = 'auth-method-item';
    item.innerHTML = `
        <div class="auth-method-header">
            <span class="auth-method-label">Google ID Token (${authProfileName})</span>
            <button class="toggle-details-tab">Auto Setup</button>
        </div>
        <div class="auth-method-details" id="google-auth-details-${UNIQUE_ID_BASE}" style="display: none;">
            <div class="auth-controls">
                <div class="auth-input-row">
                    <label for="clientIdInput-${UNIQUE_ID_BASE}">OAuth Client ID:</label>
                    <input type="text" id="clientIdInput-${UNIQUE_ID_BASE}" placeholder="Enter Client ID" class="auth-input">
                </div>
                <div class="auth-instructions">
                    <strong>Action Required:</strong> Add this URL (e.g., http://localhost:PORT) to the Client ID's <strong>Authorized JavaScript origins</strong> to avoid a 401 error. If using Google OAuth, 
                    navigate to <a href="https://console.cloud.google.com/apis/credentials" target="_blank">Google Cloud Console</a> for this setting.
                </div>
                <div class="auth-method-actions">
                    <button class="btn btn--setup-gis">Continue</button>
                    <div id="gisContainer-${UNIQUE_ID_BASE}" class="auth-interactive-element gis-container" style="display: none;"></div>
                </div>
            </div>
        </div>
    `;

    const toggleBtn = item.querySelector('.toggle-details-tab');
    const detailsDiv = item.querySelector(`#google-auth-details-${UNIQUE_ID_BASE}`);
    const setupGisBtn = item.querySelector('.btn--setup-gis');
    const clientIdInput = item.querySelector(`#clientIdInput-${UNIQUE_ID_BASE}`);
    const gisContainer = item.querySelector(`#gisContainer-${UNIQUE_ID_BASE}`);

    toggleBtn.addEventListener('click', () => {
        const isVisible = detailsDiv.style.display === 'flex'; 
        detailsDiv.style.display = isVisible ? 'none' : 'flex'; 
        toggleBtn.textContent = isVisible ? 'Auto Setup' : 'Close';
        if (!isVisible) { 
            if (gisContainer) {
                gisContainer.innerHTML = '';
                gisContainer.style.display = 'none';
            }
            if (setupGisBtn) {
                setupGisBtn.style.display = ''; 
            }
        }
    });

    setupGisBtn.addEventListener('click', () => {
        const clientId = clientIdInput.value;
        if (!clientId) {
            alert('Please enter an OAuth Client ID first.');
            return;
        }
        renderGoogleSignInButton(toolId, clientId, authProfileName);
    });

    return item;
}

```

--------------------------------------------------------------------------------
/internal/sources/clickhouse/clickhouse_test.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package clickhouse

import (
	"context"
	"strings"
	"testing"

	"github.com/goccy/go-yaml"
	"github.com/google/go-cmp/cmp"
	"github.com/googleapis/genai-toolbox/internal/testutils"
	"go.opentelemetry.io/otel"
)

func TestConfigSourceConfigKind(t *testing.T) {
	config := Config{}
	if config.SourceConfigKind() != SourceKind {
		t.Errorf("Expected %s, got %s", SourceKind, config.SourceConfigKind())
	}
}

func TestNewConfig(t *testing.T) {
	tests := []struct {
		name     string
		yaml     string
		expected Config
	}{
		{
			name: "all fields specified",
			yaml: `
				name: test-clickhouse
				kind: clickhouse
				host: localhost
				port: "8443"
				user: default
				password: "mypass"
				database: mydb
				protocol: https
				secure: true
			`,
			expected: Config{
				Name:     "test-clickhouse",
				Kind:     "clickhouse",
				Host:     "localhost",
				Port:     "8443",
				User:     "default",
				Password: "mypass",
				Database: "mydb",
				Protocol: "https",
				Secure:   true,
			},
		},
		{
			name: "minimal configuration with defaults",
			yaml: `
				name: minimal-clickhouse
				kind: clickhouse
				host: 127.0.0.1
				port: "8123"
				user: testuser
				database: testdb
			`,
			expected: Config{
				Name:     "minimal-clickhouse",
				Kind:     "clickhouse",
				Host:     "127.0.0.1",
				Port:     "8123",
				User:     "testuser",
				Password: "",
				Database: "testdb",
				Protocol: "",
				Secure:   false,
			},
		},
		{
			name: "http protocol",
			yaml: `
				name: http-clickhouse
				kind: clickhouse
				host: clickhouse.example.com
				port: "8123"
				user: analytics
				password: "securepass"
				database: analytics_db
				protocol: http
				secure: false
			`,
			expected: Config{
				Name:     "http-clickhouse",
				Kind:     "clickhouse",
				Host:     "clickhouse.example.com",
				Port:     "8123",
				User:     "analytics",
				Password: "securepass",
				Database: "analytics_db",
				Protocol: "http",
				Secure:   false,
			},
		},
		{
			name: "https with secure connection",
			yaml: `
				name: secure-clickhouse
				kind: clickhouse
				host: secure.clickhouse.io
				port: "8443"
				user: secureuser
				password: "verysecure"
				database: production
				protocol: https
				secure: true
			`,
			expected: Config{
				Name:     "secure-clickhouse",
				Kind:     "clickhouse",
				Host:     "secure.clickhouse.io",
				Port:     "8443",
				User:     "secureuser",
				Password: "verysecure",
				Database: "production",
				Protocol: "https",
				Secure:   true,
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			decoder := yaml.NewDecoder(strings.NewReader(string(testutils.FormatYaml(tt.yaml))))
			config, err := newConfig(context.Background(), tt.expected.Name, decoder)
			if err != nil {
				t.Fatalf("Failed to create config: %v", err)
			}

			clickhouseConfig, ok := config.(Config)
			if !ok {
				t.Fatalf("Expected Config type, got %T", config)
			}

			if diff := cmp.Diff(tt.expected, clickhouseConfig); diff != "" {
				t.Errorf("Config mismatch (-want +got):\n%s", diff)
			}
		})
	}
}

func TestNewConfigInvalidYAML(t *testing.T) {
	tests := []struct {
		name        string
		yaml        string
		expectError bool
	}{
		{
			name: "invalid yaml syntax",
			yaml: `
				name: test-clickhouse
				kind: clickhouse
				host: [invalid
			`,
			expectError: true,
		},
		{
			name: "missing required fields",
			yaml: `
				name: test-clickhouse
				kind: clickhouse
			`,
			expectError: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			decoder := yaml.NewDecoder(strings.NewReader(string(testutils.FormatYaml(tt.yaml))))
			_, err := newConfig(context.Background(), "test-clickhouse", decoder)
			if tt.expectError && err == nil {
				t.Errorf("Expected error but got none")
			}
			if !tt.expectError && err != nil {
				t.Errorf("Expected no error but got: %v", err)
			}
		})
	}
}

func TestSource_SourceKind(t *testing.T) {
	source := &Source{}
	if source.SourceKind() != SourceKind {
		t.Errorf("Expected %s, got %s", SourceKind, source.SourceKind())
	}
}

func TestValidateConfig(t *testing.T) {
	tests := []struct {
		name        string
		protocol    string
		expectError bool
	}{
		{
			name:        "valid https protocol",
			protocol:    "https",
			expectError: false,
		},
		{
			name:        "valid http protocol",
			protocol:    "http",
			expectError: false,
		},
		{
			name:        "invalid protocol",
			protocol:    "invalid",
			expectError: true,
		},
		{
			name:        "invalid protocol - native not supported",
			protocol:    "native",
			expectError: true,
		},
		{
			name:        "empty values use defaults",
			protocol:    "",
			expectError: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := validateConfig(tt.protocol)
			if tt.expectError && err == nil {
				t.Errorf("Expected error but got none")
			}
			if !tt.expectError && err != nil {
				t.Errorf("Expected no error but got: %v", err)
			}
		})
	}
}

func TestInitClickHouseConnectionPoolDSNGeneration(t *testing.T) {
	tracer := otel.Tracer("test")
	ctx := context.Background()

	tests := []struct {
		name      string
		host      string
		port      string
		user      string
		pass      string
		dbname    string
		protocol  string
		secure    bool
		shouldErr bool
	}{
		{
			name:      "http protocol with defaults",
			host:      "localhost",
			port:      "8123",
			user:      "default",
			pass:      "",
			dbname:    "default",
			protocol:  "http",
			secure:    false,
			shouldErr: true,
		},
		{
			name:      "https protocol with secure",
			host:      "localhost",
			port:      "8443",
			user:      "default",
			pass:      "",
			dbname:    "default",
			protocol:  "https",
			secure:    true,
			shouldErr: true,
		},
		{
			name:      "special characters in password",
			host:      "localhost",
			port:      "8443",
			user:      "test@user",
			pass:      "pass@word:with/special&chars",
			dbname:    "default",
			protocol:  "https",
			secure:    true,
			shouldErr: true,
		},
		{
			name:      "invalid protocol should fail",
			host:      "localhost",
			port:      "9000",
			user:      "default",
			pass:      "",
			dbname:    "default",
			protocol:  "invalid",
			secure:    false,
			shouldErr: true,
		},
		{
			name:      "empty protocol defaults to https",
			host:      "localhost",
			port:      "8443",
			user:      "user",
			pass:      "pass",
			dbname:    "testdb",
			protocol:  "",
			secure:    true,
			shouldErr: true,
		},
		{
			name:      "http with secure flag should upgrade to https",
			host:      "example.com",
			port:      "8443",
			user:      "user",
			pass:      "pass",
			dbname:    "db",
			protocol:  "http",
			secure:    true,
			shouldErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			pool, err := initClickHouseConnectionPool(ctx, tracer, "test", tt.host, tt.port, tt.user, tt.pass, tt.dbname, tt.protocol, tt.secure)

			if !tt.shouldErr && err != nil {
				t.Errorf("Expected no error, got: %v", err)
			}

			if pool != nil {
				pool.Close()
			}
		})
	}
}

```

--------------------------------------------------------------------------------
/docs/en/getting-started/mcp_quickstart/_index.md:
--------------------------------------------------------------------------------

```markdown
---
title: "Quickstart (MCP)"
type: docs
weight: 5
description: >
  How to get started running Toolbox locally with MCP Inspector.
---

## Overview

[Model Context Protocol](https://modelcontextprotocol.io) is an open protocol
that standardizes how applications provide context to LLMs. Check out this page
on how to [connect to Toolbox via MCP](../../how-to/connect_via_mcp.md).

## Step 1: Set up your database

In this section, we will create a database, insert some data that needs to be
access by our agent, and create a database user for Toolbox to connect with.

1. Connect to postgres using the `psql` command:

    ```bash
    psql -h 127.0.0.1 -U postgres
    ```

    Here, `postgres` denotes the default postgres superuser.

1. Create a new database and a new user:

    {{< notice tip >}}
  For a real application, it's best to follow the principle of least permission
  and only grant the privileges your application needs.
    {{< /notice >}}

    ```sql
      CREATE USER toolbox_user WITH PASSWORD 'my-password';

      CREATE DATABASE toolbox_db;
      GRANT ALL PRIVILEGES ON DATABASE toolbox_db TO toolbox_user;

      ALTER DATABASE toolbox_db OWNER TO toolbox_user;
    ```

1. End the database session:

    ```bash
    \q
    ```

1. Connect to your database with your new user:

    ```bash
    psql -h 127.0.0.1 -U toolbox_user -d toolbox_db
    ```

1. Create a table using the following command:

    ```sql
    CREATE TABLE hotels(
      id            INTEGER NOT NULL PRIMARY KEY,
      name          VARCHAR NOT NULL,
      location      VARCHAR NOT NULL,
      price_tier    VARCHAR NOT NULL,
      checkin_date  DATE    NOT NULL,
      checkout_date DATE    NOT NULL,
      booked        BIT     NOT NULL
    );
    ```

1. Insert data into the table.

    ```sql
    INSERT INTO hotels(id, name, location, price_tier, checkin_date, checkout_date, booked)
    VALUES
      (1, 'Hilton Basel', 'Basel', 'Luxury', '2024-04-22', '2024-04-20', B'0'),
      (2, 'Marriott Zurich', 'Zurich', 'Upscale', '2024-04-14', '2024-04-21', B'0'),
      (3, 'Hyatt Regency Basel', 'Basel', 'Upper Upscale', '2024-04-02', '2024-04-20', B'0'),
      (4, 'Radisson Blu Lucerne', 'Lucerne', 'Midscale', '2024-04-24', '2024-04-05', B'0'),
      (5, 'Best Western Bern', 'Bern', 'Upper Midscale', '2024-04-23', '2024-04-01', B'0'),
      (6, 'InterContinental Geneva', 'Geneva', 'Luxury', '2024-04-23', '2024-04-28', B'0'),
      (7, 'Sheraton Zurich', 'Zurich', 'Upper Upscale', '2024-04-27', '2024-04-02', B'0'),
      (8, 'Holiday Inn Basel', 'Basel', 'Upper Midscale', '2024-04-24', '2024-04-09', B'0'),
      (9, 'Courtyard Zurich', 'Zurich', 'Upscale', '2024-04-03', '2024-04-13', B'0'),
      (10, 'Comfort Inn Bern', 'Bern', 'Midscale', '2024-04-04', '2024-04-16', B'0');
    ```

1. End the database session:

    ```bash
    \q
    ```

## Step 2: Install and configure Toolbox

In this section, we will download Toolbox, configure our tools in a
`tools.yaml`, and then run the Toolbox server.

1. Download the latest version of Toolbox as a binary:

    {{< notice tip >}}
  Select the
  [correct binary](https://github.com/googleapis/genai-toolbox/releases)
  corresponding to your OS and CPU architecture.
    {{< /notice >}}
    <!-- {x-release-please-start-version} -->
    ```bash
    export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
    curl -O https://storage.googleapis.com/genai-toolbox/v0.18.0/$OS/toolbox
    ```
    <!-- {x-release-please-end} -->

1. Make the binary executable:

    ```bash
    chmod +x toolbox
    ```

1. Write the following into a `tools.yaml` file. Be sure to update any fields
   such as `user`, `password`, or `database` that you may have customized in the
   previous step.

    {{< notice tip >}}
  In practice, use environment variable replacement with the format ${ENV_NAME}
  instead of hardcoding your secrets into the configuration file.
    {{< /notice >}}

    ```yaml
    sources:
      my-pg-source:
        kind: postgres
        host: 127.0.0.1
        port: 5432
        database: toolbox_db
        user: toolbox_user
        password: my-password
    tools:
      search-hotels-by-name:
        kind: postgres-sql
        source: my-pg-source
        description: Search for hotels based on name.
        parameters:
          - name: name
            type: string
            description: The name of the hotel.
        statement: SELECT * FROM hotels WHERE name ILIKE '%' || $1 || '%';
      search-hotels-by-location:
        kind: postgres-sql
        source: my-pg-source
        description: Search for hotels based on location.
        parameters:
          - name: location
            type: string
            description: The location of the hotel.
        statement: SELECT * FROM hotels WHERE location ILIKE '%' || $1 || '%';
      book-hotel:
        kind: postgres-sql
        source: my-pg-source
        description: >-
           Book a hotel by its ID. If the hotel is successfully booked, returns a NULL, raises an error if not.
        parameters:
          - name: hotel_id
            type: string
            description: The ID of the hotel to book.
        statement: UPDATE hotels SET booked = B'1' WHERE id = $1;
      update-hotel:
        kind: postgres-sql
        source: my-pg-source
        description: >-
          Update a hotel's check-in and check-out dates by its ID. Returns a message
          indicating  whether the hotel was successfully updated or not.
        parameters:
          - name: hotel_id
            type: string
            description: The ID of the hotel to update.
          - name: checkin_date
            type: string
            description: The new check-in date of the hotel.
          - name: checkout_date
            type: string
            description: The new check-out date of the hotel.
        statement: >-
          UPDATE hotels SET checkin_date = CAST($2 as date), checkout_date = CAST($3
          as date) WHERE id = $1;
      cancel-hotel:
        kind: postgres-sql
        source: my-pg-source
        description: Cancel a hotel by its ID.
        parameters:
          - name: hotel_id
            type: string
            description: The ID of the hotel to cancel.
        statement: UPDATE hotels SET booked = B'0' WHERE id = $1;
   toolsets:
      my-toolset:
        - search-hotels-by-name
        - search-hotels-by-location
        - book-hotel
        - update-hotel
        - cancel-hotel
    ```

    For more info on tools, check out the
    [Tools](../../resources/tools/) section.

1. Run the Toolbox server, pointing to the `tools.yaml` file created earlier:

    ```bash
    ./toolbox --tools-file "tools.yaml"
    ```

## Step 3: Connect to MCP Inspector

1. Run the MCP Inspector:

    ```bash
    npx @modelcontextprotocol/inspector
    ```

1. Type `y` when it asks to install the inspector package.

1. It should show the following when the MCP Inspector is up and running (please
   take note of `<YOUR_SESSION_TOKEN>`):

    ```bash
    Starting MCP inspector...
    ⚙️ Proxy server listening on localhost:6277
    🔑 Session token: <YOUR_SESSION_TOKEN>
       Use this token to authenticate requests or set DANGEROUSLY_OMIT_AUTH=true to disable auth

    🚀 MCP Inspector is up and running at:
       http://localhost:6274/?MCP_PROXY_AUTH_TOKEN=<YOUR_SESSION_TOKEN>
    ```

1. Open the above link in your browser.

1. For `Transport Type`, select `Streamable HTTP`.

1. For `URL`, type in `http://127.0.0.1:5000/mcp`.

1. For `Configuration` -> `Proxy Session Token`, make sure
   `<YOUR_SESSION_TOKEN>` is present.

1. Click Connect.

    ![inspector](./inspector.png)

1. Select `List Tools`, you will see a list of tools configured in `tools.yaml`.

    ![inspector_tools](./inspector_tools.png)

1. Test out your tools here!

```

--------------------------------------------------------------------------------
/.github/workflows/cloud_build_failure_reporter.yml:
--------------------------------------------------------------------------------

```yaml
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

name: Cloud Build Failure Reporter

on:
  workflow_call:
    inputs:
      trigger_names:
          required: true
          type: string
  workflow_dispatch:
    inputs:
      trigger_names:
        description: 'Cloud Build trigger names separated by comma.'
        required: true
        default: ''

jobs:
  report:

    permissions:
      issues: 'write'
      checks: 'read'

    runs-on: 'ubuntu-latest'

    steps:
      - uses: 'actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd' # v8
        with:
          script: |-
                  // parse test names
                  const testNameSubstring = '${{ inputs.trigger_names }}';
                  const testNameFound = new Map(); //keeps track of whether each test is found
                  testNameSubstring.split(',').forEach(testName => {
                    testNameFound.set(testName, false); 
                  });
                  
                  // label for all issues opened by reporter
                  const periodicLabel = 'periodic-failure';

                  // check if any reporter opened any issues previously
                  const prevIssues = await github.paginate(github.rest.issues.listForRepo, {
                    ...context.repo,
                    state: 'open',
                    creator: 'github-actions[bot]',
                    labels: [periodicLabel]
                  });

                  // createOrCommentIssue creates a new issue or comments on an existing issue.
                  const createOrCommentIssue = async function (title, txt) {
                    if (prevIssues.length < 1) {
                      console.log('no previous issues found, creating one');
                      await github.rest.issues.create({
                        ...context.repo,
                        title: title,
                        body: txt,
                        labels: [periodicLabel]
                      });
                      return;
                    }
                    // only comment on issue related to the current test
                    for (const prevIssue of prevIssues) {
                      if (prevIssue.title.includes(title)){
                          console.log(
                          `found previous issue ${prevIssue.html_url}, adding comment`
                        );

                        await github.rest.issues.createComment({
                          ...context.repo,
                          issue_number: prevIssue.number,
                          body: txt
                        });
                        return;
                      }
                    }
                  };

                  // updateIssues comments on any existing issues. No-op if no issue exists.
                  const updateIssues = async function (checkName, txt) {
                    if (prevIssues.length < 1) {
                      console.log('no previous issues found.');
                      return;
                    }
                    // only comment on issue related to the current test
                    for (const prevIssue of prevIssues) {
                      if (prevIssue.title.includes(checkName)){
                        console.log(`found previous issue ${prevIssue.html_url}, adding comment`);
                        await github.rest.issues.createComment({
                          ...context.repo,
                          issue_number: prevIssue.number,
                          body: txt
                        });
                      }
                    }
                  };

                  // Find status of check runs.
                  // We will find check runs for each commit and then filter for the periodic.
                  // Checks API only allows for ref and if we use main there could be edge cases where
                  // the check run happened on a SHA that is different from head.
                  const commits = await github.paginate(github.rest.repos.listCommits, {
                    ...context.repo
                  });

                  const relevantChecks = new Map();
                  for (const commit of commits) {
                    console.log(
                      `checking runs at ${commit.html_url}: ${commit.commit.message}`
                    );
                    const checks = await github.rest.checks.listForRef({
                      ...context.repo,
                      ref: commit.sha
                    });

                    // Iterate through each check and find matching names
                    for (const check of checks.data.check_runs) {
                      console.log(`Handling test name ${check.name}`);
                      for (const testName of testNameFound.keys()) {
                        if (testNameFound.get(testName) === true){
                          //skip if a check is already found for this name
                          continue; 
                        }
                        if (check.name.includes(testName)) {
                          relevantChecks.set(check, commit);
                          testNameFound.set(testName, true);
                        }
                      }
                    }
                    // Break out of the loop early if all tests are found
                    const allTestsFound = Array.from(testNameFound.values()).every(value => value === true);
                    if (allTestsFound){
                      break;
                    }
                  }

                  // Handle each relevant check
                  relevantChecks.forEach((commit, check) => {
                    if (
                        check.status === 'completed' &&
                        check.conclusion === 'success'
                    ) {
                        updateIssues(
                            check.name,
                            `[Tests are passing](${check.html_url}) for commit [${commit.sha}](${commit.html_url}).`
                        );
                    } else if (check.status === 'in_progress') {
                        console.log(
                            `Check is pending ${check.html_url} for ${commit.html_url}. Retry again later.`
                        );
                    } else {
                        createOrCommentIssue(
                            `Cloud Build Failure Reporter: ${check.name} failed`,
                            `Cloud Build Failure Reporter found test failure for [**${check.name}** ](${check.html_url}) at [${commit.sha}](${commit.html_url}). Please fix the error and then close the issue after the **${check.name}** test passes.`
                        );
                    }
                  });

                  // no periodic checks found across all commits, report it
                  const noTestFound = Array.from(testNameFound.values()).every(value => value === false);
                  if (noTestFound){
                    createOrCommentIssue(
                      'Missing periodic tests: ${{ inputs.trigger_names }}',
                      `No periodic test is found for triggers: ${{ inputs.trigger_names }}. Last checked from ${
                        commits[0].html_url
                      } to ${commits[commits.length - 1].html_url}.`
                    );
                  }

```

--------------------------------------------------------------------------------
/internal/server/api_test.go:
--------------------------------------------------------------------------------

```go
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package server

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"strings"
	"testing"

	"github.com/googleapis/genai-toolbox/internal/tools"
)

func TestToolsetEndpoint(t *testing.T) {
	mockTools := []MockTool{tool1, tool2}
	toolsMap, toolsets := setUpResources(t, mockTools)
	r, shutdown := setUpServer(t, "api", toolsMap, toolsets)
	defer shutdown()
	ts := runServer(r, false)
	defer ts.Close()

	// wantResponse is a struct for checks against test cases
	type wantResponse struct {
		statusCode int
		isErr      bool
		version    string
		tools      []string
	}

	testCases := []struct {
		name        string
		toolsetName string
		want        wantResponse
	}{
		{
			name:        "'default' manifest",
			toolsetName: "",
			want: wantResponse{
				statusCode: http.StatusOK,
				version:    fakeVersionString,
				tools:      []string{tool1.Name, tool2.Name},
			},
		},
		{
			name:        "invalid toolset name",
			toolsetName: "some_imaginary_toolset",
			want: wantResponse{
				statusCode: http.StatusNotFound,
				isErr:      true,
			},
		},
		{
			name:        "single toolset 1",
			toolsetName: "tool1_only",
			want: wantResponse{
				statusCode: http.StatusOK,
				version:    fakeVersionString,
				tools:      []string{tool1.Name},
			},
		},
		{
			name:        "single toolset 2",
			toolsetName: "tool2_only",
			want: wantResponse{
				statusCode: http.StatusOK,
				version:    fakeVersionString,
				tools:      []string{tool2.Name},
			},
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			resp, body, err := runRequest(ts, http.MethodGet, fmt.Sprintf("/toolset/%s", tc.toolsetName), nil, nil)
			if err != nil {
				t.Fatalf("unexpected error during request: %s", err)
			}

			if contentType := resp.Header.Get("Content-type"); contentType != "application/json" {
				t.Fatalf("unexpected content-type header: want %s, got %s", "application/json", contentType)
			}

			if resp.StatusCode != tc.want.statusCode {
				t.Logf("response body: %s", body)
				t.Fatalf("unexpected status code: want %d, got %d", tc.want.statusCode, resp.StatusCode)
			}
			if tc.want.isErr {
				// skip the rest of the checks if this is an error case
				return
			}
			var m tools.ToolsetManifest
			err = json.Unmarshal(body, &m)
			if err != nil {
				t.Fatalf("unable to parse ToolsetManifest: %s", err)
			}
			// Check the version is correct
			if m.ServerVersion != tc.want.version {
				t.Fatalf("unexpected ServerVersion: want %q, got %q", tc.want.version, m.ServerVersion)
			}
			// validate that the tools in the toolset are correct
			for _, name := range tc.want.tools {
				_, ok := m.ToolsManifest[name]
				if !ok {
					t.Errorf("%q tool not found in manifest", name)
				}
			}
		})
	}
}

func TestToolGetEndpoint(t *testing.T) {
	mockTools := []MockTool{tool1, tool2}
	toolsMap, toolsets := setUpResources(t, mockTools)
	r, shutdown := setUpServer(t, "api", toolsMap, toolsets)
	defer shutdown()
	ts := runServer(r, false)
	defer ts.Close()

	// wantResponse is a struct for checks against test cases
	type wantResponse struct {
		statusCode int
		isErr      bool
		version    string
		tools      []string
	}

	testCases := []struct {
		name     string
		toolName string
		want     wantResponse
	}{
		{
			name:     "tool1",
			toolName: tool1.Name,
			want: wantResponse{
				statusCode: http.StatusOK,
				version:    fakeVersionString,
				tools:      []string{tool1.Name},
			},
		},
		{
			name:     "tool2",
			toolName: tool2.Name,
			want: wantResponse{
				statusCode: http.StatusOK,
				version:    fakeVersionString,
				tools:      []string{tool2.Name},
			},
		},
		{
			name:     "invalid tool",
			toolName: "some_imaginary_tool",
			want: wantResponse{
				statusCode: http.StatusNotFound,
				isErr:      true,
			},
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			resp, body, err := runRequest(ts, http.MethodGet, fmt.Sprintf("/tool/%s", tc.toolName), nil, nil)
			if err != nil {
				t.Fatalf("unexpected error during request: %s", err)
			}

			if contentType := resp.Header.Get("Content-type"); contentType != "application/json" {
				t.Fatalf("unexpected content-type header: want %s, got %s", "application/json", contentType)
			}

			if resp.StatusCode != tc.want.statusCode {
				t.Logf("response body: %s", body)
				t.Fatalf("unexpected status code: want %d, got %d", tc.want.statusCode, resp.StatusCode)
			}
			if tc.want.isErr {
				// skip the rest of the checks if this is an error case
				return
			}
			var m tools.ToolsetManifest
			err = json.Unmarshal(body, &m)
			if err != nil {
				t.Fatalf("unable to parse ToolsetManifest: %s", err)
			}
			// Check the version is correct
			if m.ServerVersion != tc.want.version {
				t.Fatalf("unexpected ServerVersion: want %q, got %q", tc.want.version, m.ServerVersion)
			}
			// validate that the tools in the toolset are correct
			for _, name := range tc.want.tools {
				_, ok := m.ToolsManifest[name]
				if !ok {
					t.Errorf("%q tool not found in manifest", name)
				}
			}
		})
	}
}

func TestToolInvokeEndpoint(t *testing.T) {
	mockTools := []MockTool{tool1, tool2, tool4, tool5}
	toolsMap, toolsets := setUpResources(t, mockTools)
	r, shutdown := setUpServer(t, "api", toolsMap, toolsets)
	defer shutdown()
	ts := runServer(r, false)
	defer ts.Close()

	testCases := []struct {
		name        string
		toolName    string
		requestBody io.Reader
		want        string
		isErr       bool
	}{
		{
			name:        "tool1",
			toolName:    tool1.Name,
			requestBody: bytes.NewBuffer([]byte(`{}`)),
			want:        "{result:[no_params]}\n",
			isErr:       false,
		},
		{
			name:        "tool2",
			toolName:    tool2.Name,
			requestBody: bytes.NewBuffer([]byte(`{"param1": 1, "param2": 2}`)),
			want:        "{result:[some_params]}\n",
			isErr:       false,
		},
		{
			name:        "invalid tool",
			toolName:    "some_imaginary_tool",
			requestBody: bytes.NewBuffer([]byte(`{}`)),
			want:        "",
			isErr:       true,
		},
		{
			name:        "tool4",
			toolName:    tool4.Name,
			requestBody: bytes.NewBuffer([]byte(`{}`)),
			want:        "",
			isErr:       true,
		},
		{
			name:        "tool5",
			toolName:    tool5.Name,
			requestBody: bytes.NewBuffer([]byte(`{}`)),
			want:        "",
			isErr:       true,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			resp, body, err := runRequest(ts, http.MethodPost, fmt.Sprintf("/tool/%s/invoke", tc.toolName), tc.requestBody, nil)
			if err != nil {
				t.Fatalf("unexpected error during request: %s", err)
			}

			if contentType := resp.Header.Get("Content-type"); contentType != "application/json" {
				t.Fatalf("unexpected content-type header: want %s, got %s", "application/json", contentType)
			}

			if resp.StatusCode != http.StatusOK {
				if tc.isErr == true {
					return
				}
				t.Fatalf("response status code is not 200, got %d, %s", resp.StatusCode, string(body))
			}

			got := string(body)

			// Remove `\` and `"` for string comparison
			got = strings.ReplaceAll(got, "\\", "")
			want := strings.ReplaceAll(tc.want, "\\", "")
			got = strings.ReplaceAll(got, "\"", "")
			want = strings.ReplaceAll(want, "\"", "")

			if got != want {
				t.Fatalf("unexpected value: got %q, want %q", got, tc.want)
			}
		})
	}
}

```

--------------------------------------------------------------------------------
/internal/server/config.go:
--------------------------------------------------------------------------------

```go
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//	http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server

import (
	"context"
	"fmt"
	"strings"

	yaml "github.com/goccy/go-yaml"
	"github.com/googleapis/genai-toolbox/internal/auth"
	"github.com/googleapis/genai-toolbox/internal/auth/google"
	"github.com/googleapis/genai-toolbox/internal/sources"
	"github.com/googleapis/genai-toolbox/internal/tools"
	"github.com/googleapis/genai-toolbox/internal/util"
)

type ServerConfig struct {
	// Server version
	Version string
	// Address is the address of the interface the server will listen on.
	Address string
	// Port is the port the server will listen on.
	Port int
	// SourceConfigs defines what sources of data are available for tools.
	SourceConfigs SourceConfigs
	// AuthServiceConfigs defines what sources of authentication are available for tools.
	AuthServiceConfigs AuthServiceConfigs
	// ToolConfigs defines what tools are available.
	ToolConfigs ToolConfigs
	// ToolsetConfigs defines what tools are available.
	ToolsetConfigs ToolsetConfigs
	// LoggingFormat defines whether structured loggings are used.
	LoggingFormat logFormat
	// LogLevel defines the levels to log.
	LogLevel StringLevel
	// TelemetryGCP defines whether GCP exporter is used.
	TelemetryGCP bool
	// TelemetryOTLP defines OTLP collector url for telemetry exports.
	TelemetryOTLP string
	// TelemetryServiceName defines the value of service.name resource attribute.
	TelemetryServiceName string
	// Stdio indicates if Toolbox is listening via MCP stdio.
	Stdio bool
	// DisableReload indicates if the user has disabled dynamic reloading for Toolbox.
	DisableReload bool
	// UI indicates if Toolbox UI endpoints (/ui) are available
	UI bool
}

type logFormat string

// String is used by both fmt.Print and by Cobra in help text
func (f *logFormat) String() string {
	if string(*f) != "" {
		return strings.ToLower(string(*f))
	}
	return "standard"
}

// validate logging format flag
func (f *logFormat) Set(v string) error {
	switch strings.ToLower(v) {
	case "standard", "json":
		*f = logFormat(v)
		return nil
	default:
		return fmt.Errorf(`log format must be one of "standard", or "json"`)
	}
}

// Type is used in Cobra help text
func (f *logFormat) Type() string {
	return "logFormat"
}

type StringLevel string

// String is used by both fmt.Print and by Cobra in help text
func (s *StringLevel) String() string {
	if string(*s) != "" {
		return strings.ToLower(string(*s))
	}
	return "info"
}

// validate log level flag
func (s *StringLevel) Set(v string) error {
	switch strings.ToLower(v) {
	case "debug", "info", "warn", "error":
		*s = StringLevel(v)
		return nil
	default:
		return fmt.Errorf(`log level must be one of "debug", "info", "warn", or "error"`)
	}
}

// Type is used in Cobra help text
func (s *StringLevel) Type() string {
	return "stringLevel"
}

// SourceConfigs is a type used to allow unmarshal of the data source config map
type SourceConfigs map[string]sources.SourceConfig

// validate interface
var _ yaml.InterfaceUnmarshalerContext = &SourceConfigs{}

func (c *SourceConfigs) UnmarshalYAML(ctx context.Context, unmarshal func(interface{}) error) error {
	*c = make(SourceConfigs)
	// Parse the 'kind' fields for each source
	var raw map[string]util.DelayedUnmarshaler
	if err := unmarshal(&raw); err != nil {
		return err
	}

	for name, u := range raw {
		// Unmarshal to a general type that ensure it capture all fields
		var v map[string]any
		if err := u.Unmarshal(&v); err != nil {
			return fmt.Errorf("unable to unmarshal %q: %w", name, err)
		}

		kind, ok := v["kind"]
		if !ok {
			return fmt.Errorf("missing 'kind' field for source %q", name)
		}
		kindStr, ok := kind.(string)
		if !ok {
			return fmt.Errorf("invalid 'kind' field for source %q (must be a string)", name)
		}

		yamlDecoder, err := util.NewStrictDecoder(v)
		if err != nil {
			return fmt.Errorf("error creating YAML decoder for source %q: %w", name, err)
		}

		sourceConfig, err := sources.DecodeConfig(ctx, kindStr, name, yamlDecoder)
		if err != nil {
			return err
		}
		(*c)[name] = sourceConfig
	}
	return nil
}

// AuthServiceConfigs is a type used to allow unmarshal of the data authService config map
type AuthServiceConfigs map[string]auth.AuthServiceConfig

// validate interface
var _ yaml.InterfaceUnmarshalerContext = &AuthServiceConfigs{}

func (c *AuthServiceConfigs) UnmarshalYAML(ctx context.Context, unmarshal func(interface{}) error) error {
	*c = make(AuthServiceConfigs)
	// Parse the 'kind' fields for each authService
	var raw map[string]util.DelayedUnmarshaler
	if err := unmarshal(&raw); err != nil {
		return err
	}

	for name, u := range raw {
		var v map[string]any
		if err := u.Unmarshal(&v); err != nil {
			return fmt.Errorf("unable to unmarshal %q: %w", name, err)
		}

		kind, ok := v["kind"]
		if !ok {
			return fmt.Errorf("missing 'kind' field for %q", name)
		}

		dec, err := util.NewStrictDecoder(v)
		if err != nil {
			return fmt.Errorf("error creating decoder: %w", err)
		}
		switch kind {
		case google.AuthServiceKind:
			actual := google.Config{Name: name}
			if err := dec.DecodeContext(ctx, &actual); err != nil {
				return fmt.Errorf("unable to parse as %q: %w", kind, err)
			}
			(*c)[name] = actual
		default:
			return fmt.Errorf("%q is not a valid kind of auth source", kind)
		}
	}
	return nil
}

// ToolConfigs is a type used to allow unmarshal of the tool configs
type ToolConfigs map[string]tools.ToolConfig

// validate interface
var _ yaml.InterfaceUnmarshalerContext = &ToolConfigs{}

func (c *ToolConfigs) UnmarshalYAML(ctx context.Context, unmarshal func(interface{}) error) error {
	*c = make(ToolConfigs)
	// Parse the 'kind' fields for each source
	var raw map[string]util.DelayedUnmarshaler
	if err := unmarshal(&raw); err != nil {
		return err
	}

	for name, u := range raw {
		var v map[string]any
		if err := u.Unmarshal(&v); err != nil {
			return fmt.Errorf("unable to unmarshal %q: %w", name, err)
		}

		// `authRequired` and `useClientOAuth` cannot be specified together
		if v["authRequired"] != nil && v["useClientOAuth"] == true {
			return fmt.Errorf("`authRequired` and `useClientOAuth` are mutually exclusive. Choose only one authentication method")
		}

		// Make `authRequired` an empty list instead of nil for Tool manifest
		if v["authRequired"] == nil {
			v["authRequired"] = []string{}
		}

		kindVal, ok := v["kind"]
		if !ok {
			return fmt.Errorf("missing 'kind' field for tool %q", name)
		}
		kindStr, ok := kindVal.(string)
		if !ok {
			return fmt.Errorf("invalid 'kind' field for tool %q (must be a string)", name)
		}

		yamlDecoder, err := util.NewStrictDecoder(v)
		if err != nil {
			return fmt.Errorf("error creating YAML decoder for tool %q: %w", name, err)
		}

		toolCfg, err := tools.DecodeConfig(ctx, kindStr, name, yamlDecoder)
		if err != nil {
			return err
		}
		(*c)[name] = toolCfg
	}
	return nil
}

// ToolConfigs is a type used to allow unmarshal of the toolset configs
type ToolsetConfigs map[string]tools.ToolsetConfig

// validate interface
var _ yaml.InterfaceUnmarshalerContext = &ToolsetConfigs{}

func (c *ToolsetConfigs) UnmarshalYAML(ctx context.Context, unmarshal func(interface{}) error) error {
	*c = make(ToolsetConfigs)

	var raw map[string][]string
	if err := unmarshal(&raw); err != nil {
		return err
	}

	for name, toolList := range raw {
		(*c)[name] = tools.ToolsetConfig{Name: name, ToolNames: toolList}
	}
	return nil
}

```

--------------------------------------------------------------------------------
/internal/tools/sqlite/sqliteexecutesql/sqliteexecutesql_test.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package sqliteexecutesql_test

import (
	"context"
	"database/sql"
	"reflect"
	"testing"

	yaml "github.com/goccy/go-yaml"
	"github.com/google/go-cmp/cmp"
	"github.com/googleapis/genai-toolbox/internal/server"
	"github.com/googleapis/genai-toolbox/internal/testutils"
	"github.com/googleapis/genai-toolbox/internal/tools"
	"github.com/googleapis/genai-toolbox/internal/tools/sqlite/sqliteexecutesql"
	_ "modernc.org/sqlite"
)

func TestParseFromYamlExecuteSql(t *testing.T) {
	ctx, err := testutils.ContextWithNewLogger()
	if err != nil {
		t.Fatalf("unexpected error: %s", err)
	}
	tcs := []struct {
		desc string
		in   string
		want server.ToolConfigs
	}{
		{
			desc: "basic example",
			in: `
			tools:
				example_tool:
					kind: sqlite-execute-sql
					source: my-instance
					description: some description
					authRequired:
						- my-google-auth-service
						- other-auth-service
			`,
			want: server.ToolConfigs{
				"example_tool": sqliteexecutesql.Config{
					Name:         "example_tool",
					Kind:         "sqlite-execute-sql",
					Source:       "my-instance",
					Description:  "some description",
					AuthRequired: []string{"my-google-auth-service", "other-auth-service"},
				},
			},
		},
	}
	for _, tc := range tcs {
		t.Run(tc.desc, func(t *testing.T) {
			got := struct {
				Tools server.ToolConfigs `yaml:"tools"`
			}{}
			// Parse contents
			err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
			if err != nil {
				t.Fatalf("unable to unmarshal: %s", err)
			}
			if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
				t.Fatalf("incorrect parse: diff %v", diff)
			}
		})
	}

}

func setupTestDB(t *testing.T) *sql.DB {
	db, err := sql.Open("sqlite", ":memory:")
	if err != nil {
		t.Fatalf("Failed to open in-memory database: %v", err)
	}
	return db
}

func TestTool_Invoke(t *testing.T) {
	ctx, err := testutils.ContextWithNewLogger()
	if err != nil {
		t.Fatalf("unexpected error: %s", err)
	}

	type fields struct {
		Name         string
		Kind         string
		AuthRequired []string
		Parameters   tools.Parameters
		DB           *sql.DB
	}
	type args struct {
		ctx         context.Context
		params      tools.ParamValues
		accessToken tools.AccessToken
	}
	tests := []struct {
		name    string
		fields  fields
		args    args
		want    any
		wantErr bool
	}{
		{
			name: "create table",
			fields: fields{
				DB: setupTestDB(t),
			},
			args: args{
				ctx: ctx,
				params: []tools.ParamValue{
					{Name: "sql", Value: "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)"},
				},
			},
			want:    nil,
			wantErr: false,
		},
		{
			name: "insert data",
			fields: fields{
				DB: setupTestDB(t),
			},
			args: args{
				ctx: ctx,
				params: []tools.ParamValue{
					{Name: "sql", Value: "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER); INSERT INTO users (id, name, age) VALUES (1, 'Alice', 30), (2, 'Bob', 25)"},
				},
			},
			want:    nil,
			wantErr: false,
		},
		{
			name: "select data",
			fields: fields{
				DB: func() *sql.DB {
					db := setupTestDB(t)
					if _, err := db.Exec("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER); INSERT INTO users (id, name, age) VALUES (1, 'Alice', 30), (2, 'Bob', 25)"); err != nil {
						t.Fatalf("Failed to set up database for select: %v", err)
					}
					return db
				}(),
			},
			args: args{
				ctx: ctx,
				params: []tools.ParamValue{
					{Name: "sql", Value: "SELECT * FROM users"},
				},
			},
			want: []any{
				map[string]any{"id": int64(1), "name": "Alice", "age": int64(30)},
				map[string]any{"id": int64(2), "name": "Bob", "age": int64(25)},
			},
			wantErr: false,
		},
		{
			name: "drop table",
			fields: fields{
				DB: func() *sql.DB {
					db := setupTestDB(t)
					if _, err := db.Exec("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)"); err != nil {
						t.Fatalf("Failed to set up database for drop: %v", err)
					}
					return db
				}(),
			},
			args: args{
				ctx: ctx,
				params: []tools.ParamValue{
					{Name: "sql", Value: "DROP TABLE users"},
				},
			},
			want:    nil,
			wantErr: false,
		},
		{
			name: "invalid sql",
			fields: fields{
				DB: setupTestDB(t),
			},
			args: args{
				ctx: ctx,
				params: []tools.ParamValue{
					{Name: "sql", Value: "SELECT * FROM non_existent_table"},
				},
			},
			want:    nil,
			wantErr: true,
		},
		{
			name: "empty sql",
			fields: fields{
				DB: setupTestDB(t),
			},
			args: args{
				ctx: ctx,
				params: []tools.ParamValue{
					{Name: "sql", Value: ""},
				},
			},
			want:    nil,
			wantErr: true,
		},
		{
			name: "data types",
			fields: fields{
				DB: func() *sql.DB {
					db := setupTestDB(t)
					if _, err := db.Exec("CREATE TABLE data_types (id INTEGER PRIMARY KEY, null_col TEXT, blob_col BLOB)"); err != nil {
						t.Fatalf("Failed to set up database for data types: %v", err)
					}
					if _, err := db.Exec("INSERT INTO data_types (id, null_col, blob_col) VALUES (1, NULL, ?)", []byte{1, 2, 3}); err != nil {
						t.Fatalf("Failed to insert data for data types: %v", err)
					}
					return db
				}(),
			},
			args: args{
				ctx: ctx,
				params: []tools.ParamValue{
					{Name: "sql", Value: "SELECT * FROM data_types"},
				},
			},
			want: []any{
				map[string]any{"id": int64(1), "null_col": nil, "blob_col": []byte{1, 2, 3}},
			},
			wantErr: false,
		},
		{
			name: "join operation",
			fields: fields{
				DB: func() *sql.DB {
					db := setupTestDB(t)
					if _, err := db.Exec("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)"); err != nil {
						t.Fatalf("Failed to set up database for join: %v", err)
					}
					if _, err := db.Exec("INSERT INTO users (id, name, age) VALUES (1, 'Alice', 30), (2, 'Bob', 25)"); err != nil {
						t.Fatalf("Failed to insert data for join: %v", err)
					}
					if _, err := db.Exec("CREATE TABLE orders (id INTEGER PRIMARY KEY, user_id INTEGER, item TEXT)"); err != nil {
						t.Fatalf("Failed to set up database for join: %v", err)
					}
					if _, err := db.Exec("INSERT INTO orders (id, user_id, item) VALUES (1, 1, 'Laptop'), (2, 2, 'Keyboard')"); err != nil {
						t.Fatalf("Failed to insert data for join: %v", err)
					}
					return db
				}(),
			},
			args: args{
				ctx: ctx,
				params: []tools.ParamValue{
					{Name: "sql", Value: "SELECT u.name, o.item FROM users u JOIN orders o ON u.id = o.user_id"},
				},
			},
			want: []any{
				map[string]any{"name": "Alice", "item": "Laptop"},
				map[string]any{"name": "Bob", "item": "Keyboard"},
			},
			wantErr: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tr := &sqliteexecutesql.Tool{
				Name:         tt.fields.Name,
				Kind:         tt.fields.Kind,
				AuthRequired: tt.fields.AuthRequired,
				Parameters:   tt.fields.Parameters,
				DB:           tt.fields.DB,
			}
			got, err := tr.Invoke(tt.args.ctx, tt.args.params, tt.args.accessToken)
			if (err != nil) != tt.wantErr {
				t.Errorf("Tool.Invoke() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			isEqual := false
			if got != nil && len(got.([]any)) == 0 && len(tt.want.([]any)) == 0 {
				isEqual = true // Special case for empty slices, since DeepEqual returns false
			} else {
				isEqual = reflect.DeepEqual(got, tt.want)
			}

			if !isEqual {
				t.Errorf("Tool.Invoke() = %v, want %v", got, tt.want)
			}
		})
	}
}

```
Page 17/35FirstPrevNextLast