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

# Directory Structure

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

# Files

--------------------------------------------------------------------------------
/internal/tools/common_test.go:
--------------------------------------------------------------------------------

```go
  1 | // Copyright 2025 Google LLC
  2 | //
  3 | // Licensed under the Apache License, Version 2.0 (the "License");
  4 | // you may not use this file except in compliance with the License.
  5 | // You may obtain a copy of the License at
  6 | //
  7 | //     http://www.apache.org/licenses/LICENSE-2.0
  8 | //
  9 | // Unless required by applicable law or agreed to in writing, software
 10 | // distributed under the License is distributed on an "AS IS" BASIS,
 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12 | // See the License for the specific language governing permissions and
 13 | // limitations under the License.
 14 | 
 15 | package tools_test
 16 | 
 17 | import (
 18 | 	"strings"
 19 | 	"testing"
 20 | 	"text/template"
 21 | 
 22 | 	"github.com/google/go-cmp/cmp"
 23 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 24 | )
 25 | 
 26 | func TestPopulateTemplate(t *testing.T) {
 27 | 	tcs := []struct {
 28 | 		name           string
 29 | 		templateName   string
 30 | 		templateString string
 31 | 		data           map[string]any
 32 | 		want           string
 33 | 		wantErr        bool
 34 | 	}{
 35 | 		{
 36 | 			name:           "simple string substitution",
 37 | 			templateName:   "test",
 38 | 			templateString: "Hello {{.name}}!",
 39 | 			data:           map[string]any{"name": "World"},
 40 | 			want:           "Hello World!",
 41 | 			wantErr:        false,
 42 | 		},
 43 | 		{
 44 | 			name:           "multiple substitutions",
 45 | 			templateName:   "test",
 46 | 			templateString: "{{.greeting}} {{.name}}, you are {{.age}} years old",
 47 | 			data:           map[string]any{"greeting": "Hello", "name": "Alice", "age": 30},
 48 | 			want:           "Hello Alice, you are 30 years old",
 49 | 			wantErr:        false,
 50 | 		},
 51 | 		{
 52 | 			name:           "empty template",
 53 | 			templateName:   "test",
 54 | 			templateString: "",
 55 | 			data:           map[string]any{},
 56 | 			want:           "",
 57 | 			wantErr:        false,
 58 | 		},
 59 | 		{
 60 | 			name:           "no substitutions",
 61 | 			templateName:   "test",
 62 | 			templateString: "Plain text without templates",
 63 | 			data:           map[string]any{},
 64 | 			want:           "Plain text without templates",
 65 | 			wantErr:        false,
 66 | 		},
 67 | 		{
 68 | 			name:           "invalid template syntax",
 69 | 			templateName:   "test",
 70 | 			templateString: "{{.name",
 71 | 			data:           map[string]any{"name": "World"},
 72 | 			want:           "",
 73 | 			wantErr:        true,
 74 | 		},
 75 | 		{
 76 | 			name:           "missing field",
 77 | 			templateName:   "test",
 78 | 			templateString: "{{.missing}}",
 79 | 			data:           map[string]any{"name": "World"},
 80 | 			want:           "<no value>",
 81 | 			wantErr:        false,
 82 | 		},
 83 | 		{
 84 | 			name:           "invalid function call",
 85 | 			templateName:   "test",
 86 | 			templateString: "{{.name.invalid}}",
 87 | 			data:           map[string]any{"name": "World"},
 88 | 			want:           "",
 89 | 			wantErr:        true,
 90 | 		},
 91 | 	}
 92 | 
 93 | 	for _, tc := range tcs {
 94 | 		t.Run(tc.name, func(t *testing.T) {
 95 | 			got, err := tools.PopulateTemplate(tc.templateName, tc.templateString, tc.data)
 96 | 			if tc.wantErr {
 97 | 				if err == nil {
 98 | 					t.Fatalf("expected error, got nil")
 99 | 				}
100 | 				return
101 | 			}
102 | 			if err != nil {
103 | 				t.Fatalf("unexpected error: %s", err)
104 | 			}
105 | 			if diff := cmp.Diff(tc.want, got); diff != "" {
106 | 				t.Fatalf("incorrect result (-want +got):\n%s", diff)
107 | 			}
108 | 		})
109 | 	}
110 | }
111 | 
112 | func TestPopulateTemplateWithFunc(t *testing.T) {
113 | 	// Custom function for testing
114 | 	customFuncs := template.FuncMap{
115 | 		"upper": strings.ToUpper,
116 | 		"add": func(a, b int) int {
117 | 			return a + b
118 | 		},
119 | 	}
120 | 
121 | 	tcs := []struct {
122 | 		name           string
123 | 		templateName   string
124 | 		templateString string
125 | 		data           map[string]any
126 | 		funcMap        template.FuncMap
127 | 		want           string
128 | 		wantErr        bool
129 | 	}{
130 | 		{
131 | 			name:           "with custom upper function",
132 | 			templateName:   "test",
133 | 			templateString: "{{upper .text}}",
134 | 			data:           map[string]any{"text": "hello"},
135 | 			funcMap:        customFuncs,
136 | 			want:           "HELLO",
137 | 			wantErr:        false,
138 | 		},
139 | 		{
140 | 			name:           "with custom add function",
141 | 			templateName:   "test",
142 | 			templateString: "Result: {{add .x .y}}",
143 | 			data:           map[string]any{"x": 5, "y": 3},
144 | 			funcMap:        customFuncs,
145 | 			want:           "Result: 8",
146 | 			wantErr:        false,
147 | 		},
148 | 		{
149 | 			name:           "nil funcMap",
150 | 			templateName:   "test",
151 | 			templateString: "Hello {{.name}}",
152 | 			data:           map[string]any{"name": "World"},
153 | 			funcMap:        nil,
154 | 			want:           "Hello World",
155 | 			wantErr:        false,
156 | 		},
157 | 		{
158 | 			name:           "combine custom function with regular substitution",
159 | 			templateName:   "test",
160 | 			templateString: "{{upper .greeting}} {{.name}}!",
161 | 			data:           map[string]any{"greeting": "hello", "name": "Alice"},
162 | 			funcMap:        customFuncs,
163 | 			want:           "HELLO Alice!",
164 | 			wantErr:        false,
165 | 		},
166 | 		{
167 | 			name:           "undefined function",
168 | 			templateName:   "test",
169 | 			templateString: "{{undefined .text}}",
170 | 			data:           map[string]any{"text": "hello"},
171 | 			funcMap:        nil,
172 | 			want:           "",
173 | 			wantErr:        true,
174 | 		},
175 | 		{
176 | 			name:           "wrong number of arguments",
177 | 			templateName:   "test",
178 | 			templateString: "{{upper}}",
179 | 			data:           map[string]any{},
180 | 			funcMap:        template.FuncMap{"upper": strings.ToUpper},
181 | 			want:           "",
182 | 			wantErr:        true,
183 | 		},
184 | 	}
185 | 
186 | 	for _, tc := range tcs {
187 | 		t.Run(tc.name, func(t *testing.T) {
188 | 			got, err := tools.PopulateTemplateWithFunc(tc.templateName, tc.templateString, tc.data, tc.funcMap)
189 | 			if tc.wantErr {
190 | 				if err == nil {
191 | 					t.Fatalf("expected error, got nil")
192 | 				}
193 | 				return
194 | 			}
195 | 			if err != nil {
196 | 				t.Fatalf("unexpected error: %s", err)
197 | 			}
198 | 			if diff := cmp.Diff(tc.want, got); diff != "" {
199 | 				t.Fatalf("incorrect result (-want +got):\n%s", diff)
200 | 			}
201 | 		})
202 | 	}
203 | }
204 | 
205 | func TestPopulateTemplateWithJSON(t *testing.T) {
206 | 	tcs := []struct {
207 | 		name           string
208 | 		templateName   string
209 | 		templateString string
210 | 		data           map[string]any
211 | 		want           string
212 | 		wantErr        bool
213 | 	}{
214 | 		{
215 | 			name:           "json string",
216 | 			templateName:   "test",
217 | 			templateString: "Data: {{json .value}}",
218 | 			data:           map[string]any{"value": "hello"},
219 | 			want:           `Data: "hello"`,
220 | 			wantErr:        false,
221 | 		},
222 | 		{
223 | 			name:           "json number",
224 | 			templateName:   "test",
225 | 			templateString: "Number: {{json .num}}",
226 | 			data:           map[string]any{"num": 42},
227 | 			want:           "Number: 42",
228 | 			wantErr:        false,
229 | 		},
230 | 		{
231 | 			name:           "json boolean",
232 | 			templateName:   "test",
233 | 			templateString: "Bool: {{json .flag}}",
234 | 			data:           map[string]any{"flag": true},
235 | 			want:           "Bool: true",
236 | 			wantErr:        false,
237 | 		},
238 | 		{
239 | 			name:           "json array",
240 | 			templateName:   "test",
241 | 			templateString: "Array: {{json .items}}",
242 | 			data:           map[string]any{"items": []any{"a", "b", "c"}},
243 | 			want:           `Array: ["a","b","c"]`,
244 | 			wantErr:        false,
245 | 		},
246 | 		{
247 | 			name:           "json object",
248 | 			templateName:   "test",
249 | 			templateString: "Object: {{json .obj}}",
250 | 			data:           map[string]any{"obj": map[string]any{"name": "Alice", "age": 30}},
251 | 			want:           `Object: {"age":30,"name":"Alice"}`,
252 | 			wantErr:        false,
253 | 		},
254 | 		{
255 | 			name:           "json null",
256 | 			templateName:   "test",
257 | 			templateString: "Null: {{json .nullValue}}",
258 | 			data:           map[string]any{"nullValue": nil},
259 | 			want:           "Null: null",
260 | 			wantErr:        false,
261 | 		},
262 | 		{
263 | 			name:           "combine json with regular substitution",
264 | 			templateName:   "test",
265 | 			templateString: "User {{.name}} has data: {{json .data}}",
266 | 			data:           map[string]any{"name": "Bob", "data": map[string]any{"id": 123}},
267 | 			want:           `User Bob has data: {"id":123}`,
268 | 			wantErr:        false,
269 | 		},
270 | 		{
271 | 			name:           "missing field for json",
272 | 			templateName:   "test",
273 | 			templateString: "{{json .missing}}",
274 | 			data:           map[string]any{},
275 | 			want:           "null",
276 | 			wantErr:        false,
277 | 		},
278 | 	}
279 | 
280 | 	for _, tc := range tcs {
281 | 		t.Run(tc.name, func(t *testing.T) {
282 | 			got, err := tools.PopulateTemplateWithJSON(tc.templateName, tc.templateString, tc.data)
283 | 			if tc.wantErr {
284 | 				if err == nil {
285 | 					t.Fatalf("expected error, got nil")
286 | 				}
287 | 				return
288 | 			}
289 | 			if err != nil {
290 | 				t.Fatalf("unexpected error: %s", err)
291 | 			}
292 | 			if diff := cmp.Diff(tc.want, got); diff != "" {
293 | 				t.Fatalf("incorrect result (-want +got):\n%s", diff)
294 | 			}
295 | 		})
296 | 	}
297 | }
298 | 
```

--------------------------------------------------------------------------------
/tests/cloudsqlmysql/cloud_sql_mysql_create_instance_integration_test.go:
--------------------------------------------------------------------------------

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

--------------------------------------------------------------------------------
/tests/cloudsqlmssql/cloud_sql_mssql_create_instance_integration_test.go:
--------------------------------------------------------------------------------

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

--------------------------------------------------------------------------------
/tests/cloudsqlpg/cloud_sql_pg_create_instances_test.go:
--------------------------------------------------------------------------------

```go
  1 | // Copyright 2025 Google LLC
  2 | //
  3 | // Licensed under the Apache License, Version 2.0 (the "License");
  4 | // you may not use this file except in compliance with the License.
  5 | // You may obtain a copy of the License at
  6 | //
  7 | //      http://www.apache.org/licenses/LICENSE-2.0
  8 | //
  9 | // Unless required by applicable law or agreed to in writing, software
 10 | // distributed under the License is distributed on an "AS IS" BASIS,
 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12 | // See the License for the specific language governing permissions and
 13 | // limitations under the License.
 14 | 
 15 | package cloudsqlpg
 16 | 
 17 | import (
 18 | 	"bytes"
 19 | 	"context"
 20 | 	"encoding/json"
 21 | 	"fmt"
 22 | 	"io"
 23 | 	"net/http"
 24 | 	"net/http/httptest"
 25 | 	"net/url"
 26 | 	"reflect"
 27 | 	"regexp"
 28 | 	"strings"
 29 | 	"testing"
 30 | 	"time"
 31 | 
 32 | 	"github.com/google/go-cmp/cmp"
 33 | 	"github.com/googleapis/genai-toolbox/internal/testutils"
 34 | 	"github.com/googleapis/genai-toolbox/tests"
 35 | 	"google.golang.org/api/sqladmin/v1"
 36 | )
 37 | 
 38 | var (
 39 | 	createInstanceToolKind = "cloud-sql-postgres-create-instance"
 40 | )
 41 | 
 42 | type createInstanceTransport struct {
 43 | 	transport http.RoundTripper
 44 | 	url       *url.URL
 45 | }
 46 | 
 47 | func (t *createInstanceTransport) RoundTrip(req *http.Request) (*http.Response, error) {
 48 | 	if strings.HasPrefix(req.URL.String(), "https://sqladmin.googleapis.com") {
 49 | 		req.URL.Scheme = t.url.Scheme
 50 | 		req.URL.Host = t.url.Host
 51 | 	}
 52 | 	return t.transport.RoundTrip(req)
 53 | }
 54 | 
 55 | type masterHandler struct {
 56 | 	t *testing.T
 57 | }
 58 | 
 59 | func (h *masterHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 60 | 	ua := r.Header.Get("User-Agent")
 61 | 	if !strings.Contains(ua, "genai-toolbox/") {
 62 | 		h.t.Errorf("User-Agent header not found in %q", ua)
 63 | 	}
 64 | 
 65 | 	var body sqladmin.DatabaseInstance
 66 | 	if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
 67 | 		h.t.Fatalf("failed to decode request body: %v", err)
 68 | 	}
 69 | 
 70 | 	instanceName := body.Name
 71 | 	if instanceName == "" {
 72 | 		http.Error(w, "missing instance name", http.StatusBadRequest)
 73 | 		return
 74 | 	}
 75 | 
 76 | 	var expectedBody sqladmin.DatabaseInstance
 77 | 	var response any
 78 | 	var statusCode int
 79 | 
 80 | 	switch instanceName {
 81 | 	case "instance1":
 82 | 		expectedBody = sqladmin.DatabaseInstance{
 83 | 			Project:         "p1",
 84 | 			Name:            "instance1",
 85 | 			DatabaseVersion: "POSTGRES_15",
 86 | 			RootPassword:    "password123",
 87 | 			Settings: &sqladmin.Settings{
 88 | 				AvailabilityType: "REGIONAL",
 89 | 				Edition:          "ENTERPRISE_PLUS",
 90 | 				Tier:             "db-perf-optimized-N-8",
 91 | 				DataDiskSizeGb:   250,
 92 | 				DataDiskType:     "PD_SSD",
 93 | 			},
 94 | 		}
 95 | 		response = map[string]any{"name": "op1", "status": "PENDING"}
 96 | 		statusCode = http.StatusOK
 97 | 	case "instance2":
 98 | 		expectedBody = sqladmin.DatabaseInstance{
 99 | 			Project:         "p2",
100 | 			Name:            "instance2",
101 | 			DatabaseVersion: "POSTGRES_17",
102 | 			RootPassword:    "password456",
103 | 			Settings: &sqladmin.Settings{
104 | 				AvailabilityType: "ZONAL",
105 | 				Edition:          "ENTERPRISE_PLUS",
106 | 				Tier:             "db-perf-optimized-N-2",
107 | 				DataDiskSizeGb:   100,
108 | 				DataDiskType:     "PD_SSD",
109 | 			},
110 | 		}
111 | 		response = map[string]any{"name": "op2", "status": "RUNNING"}
112 | 		statusCode = http.StatusOK
113 | 	default:
114 | 		http.Error(w, fmt.Sprintf("unhandled instance name: %s", instanceName), http.StatusInternalServerError)
115 | 		return
116 | 	}
117 | 
118 | 	if expectedBody.Project != body.Project {
119 | 		h.t.Errorf("unexpected project: got %q, want %q", body.Project, expectedBody.Project)
120 | 	}
121 | 	if expectedBody.Name != body.Name {
122 | 		h.t.Errorf("unexpected name: got %q, want %q", body.Name, expectedBody.Name)
123 | 	}
124 | 	if expectedBody.DatabaseVersion != body.DatabaseVersion {
125 | 		h.t.Errorf("unexpected databaseVersion: got %q, want %q", body.DatabaseVersion, expectedBody.DatabaseVersion)
126 | 	}
127 | 	if expectedBody.RootPassword != body.RootPassword {
128 | 		h.t.Errorf("unexpected rootPassword: got %q, want %q", body.RootPassword, expectedBody.RootPassword)
129 | 	}
130 | 	if diff := cmp.Diff(expectedBody.Settings, body.Settings); diff != "" {
131 | 		h.t.Errorf("unexpected request body settings (-want +got):\n%s", diff)
132 | 	}
133 | 
134 | 	w.Header().Set("Content-Type", "application/json")
135 | 	w.WriteHeader(statusCode)
136 | 	if err := json.NewEncoder(w).Encode(response); err != nil {
137 | 		http.Error(w, err.Error(), http.StatusInternalServerError)
138 | 	}
139 | }
140 | 
141 | func TestCreateInstanceToolEndpoints(t *testing.T) {
142 | 	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
143 | 	defer cancel()
144 | 
145 | 	handler := &masterHandler{t: t}
146 | 	server := httptest.NewServer(handler)
147 | 	defer server.Close()
148 | 
149 | 	serverURL, err := url.Parse(server.URL)
150 | 	if err != nil {
151 | 		t.Fatalf("failed to parse server URL: %v", err)
152 | 	}
153 | 
154 | 	originalTransport := http.DefaultClient.Transport
155 | 	if originalTransport == nil {
156 | 		originalTransport = http.DefaultTransport
157 | 	}
158 | 	http.DefaultClient.Transport = &createInstanceTransport{
159 | 		transport: originalTransport,
160 | 		url:       serverURL,
161 | 	}
162 | 	t.Cleanup(func() {
163 | 		http.DefaultClient.Transport = originalTransport
164 | 	})
165 | 
166 | 	var args []string
167 | 	toolsFile := getCreateInstanceToolsConfig()
168 | 	cmd, cleanup, err := tests.StartCmd(ctx, toolsFile, args...)
169 | 	if err != nil {
170 | 		t.Fatalf("command initialization returned an error: %s", err)
171 | 	}
172 | 	defer cleanup()
173 | 
174 | 	waitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
175 | 	defer cancel()
176 | 	out, err := testutils.WaitForString(waitCtx, regexp.MustCompile(`Server ready to serve`), cmd.Out)
177 | 	if err != nil {
178 | 		t.Logf("toolbox command logs: \n%s", out)
179 | 		t.Fatalf("toolbox didn't start successfully: %s", err)
180 | 	}
181 | 
182 | 	tcs := []struct {
183 | 		name        string
184 | 		toolName    string
185 | 		body        string
186 | 		want        string
187 | 		expectError bool
188 | 		errorStatus int
189 | 	}{
190 | 		{
191 | 			name:     "successful creation - production",
192 | 			toolName: "create-instance-prod",
193 | 			body:     `{"project": "p1", "name": "instance1", "databaseVersion": "POSTGRES_15", "rootPassword": "password123", "editionPreset": "Production"}`,
194 | 			want:     `{"name":"op1","status":"PENDING"}`,
195 | 		},
196 | 		{
197 | 			name:     "successful creation - development",
198 | 			toolName: "create-instance-dev",
199 | 			body:     `{"project": "p2", "name": "instance2", "rootPassword": "password456", "editionPreset": "Development"}`,
200 | 			want:     `{"name":"op2","status":"RUNNING"}`,
201 | 		},
202 | 		{
203 | 			name:        "missing required parameter",
204 | 			toolName:    "create-instance-prod",
205 | 			body:        `{"name": "instance1"}`,
206 | 			expectError: true,
207 | 			errorStatus: http.StatusBadRequest,
208 | 		},
209 | 	}
210 | 
211 | 	for _, tc := range tcs {
212 | 		tc := tc
213 | 		t.Run(tc.name, func(t *testing.T) {
214 | 			api := fmt.Sprintf("http://127.0.0.1:5000/api/tool/%s/invoke", tc.toolName)
215 | 			req, err := http.NewRequest(http.MethodPost, api, bytes.NewBufferString(tc.body))
216 | 			if err != nil {
217 | 				t.Fatalf("unable to create request: %s", err)
218 | 			}
219 | 			req.Header.Add("Content-type", "application/json")
220 | 			resp, err := http.DefaultClient.Do(req)
221 | 			if err != nil {
222 | 				t.Fatalf("unable to send request: %s", err)
223 | 			}
224 | 			defer resp.Body.Close()
225 | 
226 | 			if tc.expectError {
227 | 				if resp.StatusCode != tc.errorStatus {
228 | 					bodyBytes, _ := io.ReadAll(resp.Body)
229 | 					t.Fatalf("expected status %d but got %d: %s", tc.errorStatus, resp.StatusCode, string(bodyBytes))
230 | 				}
231 | 				return
232 | 			}
233 | 
234 | 			if resp.StatusCode != http.StatusOK {
235 | 				bodyBytes, _ := io.ReadAll(resp.Body)
236 | 				t.Fatalf("response status code is not 200, got %d: %s", resp.StatusCode, string(bodyBytes))
237 | 			}
238 | 
239 | 			var result struct {
240 | 				Result string `json:"result"`
241 | 			}
242 | 			if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
243 | 				t.Fatalf("failed to decode response: %v", err)
244 | 			}
245 | 
246 | 			var got, want map[string]any
247 | 			if err := json.Unmarshal([]byte(result.Result), &got); err != nil {
248 | 				t.Fatalf("failed to unmarshal result: %v", err)
249 | 			}
250 | 			if err := json.Unmarshal([]byte(tc.want), &want); err != nil {
251 | 				t.Fatalf("failed to unmarshal want: %v", err)
252 | 			}
253 | 
254 | 			if !reflect.DeepEqual(got, want) {
255 | 				t.Fatalf("unexpected result: got %+v, want %+v", got, want)
256 | 			}
257 | 		})
258 | 	}
259 | }
260 | 
261 | func getCreateInstanceToolsConfig() map[string]any {
262 | 	return map[string]any{
263 | 		"sources": map[string]any{
264 | 			"my-cloud-sql-source": map[string]any{
265 | 				"kind": "cloud-sql-admin",
266 | 			},
267 | 		},
268 | 		"tools": map[string]any{
269 | 			"create-instance-prod": map[string]any{
270 | 				"kind":   createInstanceToolKind,
271 | 				"source": "my-cloud-sql-source",
272 | 			},
273 | 			"create-instance-dev": map[string]any{
274 | 				"kind":   createInstanceToolKind,
275 | 				"source": "my-cloud-sql-source",
276 | 			},
277 | 		},
278 | 	}
279 | }
280 | 
```

--------------------------------------------------------------------------------
/docs/en/how-to/connect-ide/sqlite_mcp.md:
--------------------------------------------------------------------------------

```markdown
  1 | ---
  2 | title: SQLite using MCP
  3 | type: docs
  4 | weight: 2
  5 | description: "Connect your IDE to SQLite using Toolbox."
  6 | ---
  7 | 
  8 | [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) is
  9 | an open protocol for connecting Large Language Models (LLMs) to data sources
 10 | like SQLite. This guide covers how to use [MCP Toolbox for Databases][toolbox]
 11 | to expose your developer assistant tools to a SQLite instance:
 12 | 
 13 | * [Cursor][cursor]
 14 | * [Windsurf][windsurf] (Codium)
 15 | * [Visual Studio Code][vscode] (Copilot)
 16 | * [Cline][cline] (VS Code extension)
 17 | * [Claude desktop][claudedesktop]
 18 | * [Claude code][claudecode]
 19 | * [Gemini CLI][geminicli]
 20 | * [Gemini Code Assist][geminicodeassist]
 21 | 
 22 | [toolbox]: https://github.com/googleapis/genai-toolbox
 23 | [cursor]: #configure-your-mcp-client
 24 | [windsurf]: #configure-your-mcp-client
 25 | [vscode]: #configure-your-mcp-client
 26 | [cline]: #configure-your-mcp-client
 27 | [claudedesktop]: #configure-your-mcp-client
 28 | [claudecode]: #configure-your-mcp-client
 29 | [geminicli]: #configure-your-mcp-client
 30 | [geminicodeassist]: #configure-your-mcp-client
 31 | 
 32 | ## Set up the database
 33 | 
 34 | 1.  [Create or select a SQLite database file.](https://www.sqlite.org/download.html)
 35 | 
 36 | ## Install MCP Toolbox
 37 | 
 38 | 1. Download the latest version of Toolbox as a binary. Select the [correct
 39 |    binary](https://github.com/googleapis/genai-toolbox/releases) corresponding
 40 |    to your OS and CPU architecture. You are required to use Toolbox version
 41 |    V0.10.0+:
 42 | 
 43 |    <!-- {x-release-please-start-version} -->
 44 |    {{< tabpane persist=header >}}
 45 | {{< tab header="linux/amd64" lang="bash" >}}
 46 | curl -O https://storage.googleapis.com/genai-toolbox/v0.18.0/linux/amd64/toolbox
 47 | {{< /tab >}}
 48 | 
 49 | {{< tab header="darwin/arm64" lang="bash" >}}
 50 | curl -O https://storage.googleapis.com/genai-toolbox/v0.18.0/darwin/arm64/toolbox
 51 | {{< /tab >}}
 52 | 
 53 | {{< tab header="darwin/amd64" lang="bash" >}}
 54 | curl -O https://storage.googleapis.com/genai-toolbox/v0.18.0/darwin/amd64/toolbox
 55 | {{< /tab >}}
 56 | 
 57 | {{< tab header="windows/amd64" lang="bash" >}}
 58 | curl -O https://storage.googleapis.com/genai-toolbox/v0.18.0/windows/amd64/toolbox.exe
 59 | {{< /tab >}}
 60 | {{< /tabpane >}}
 61 |     <!-- {x-release-please-end} -->
 62 | 
 63 | 1. Make the binary executable:
 64 | 
 65 |     ```bash
 66 |     chmod +x toolbox
 67 |     ```
 68 | 
 69 | 1. Verify the installation:
 70 | 
 71 |     ```bash
 72 |     ./toolbox --version
 73 |     ```
 74 | 
 75 | ## Configure your MCP Client
 76 | 
 77 | {{< tabpane text=true >}}
 78 | {{% tab header="Claude code" lang="en" %}}
 79 | 
 80 | 1.  Install [Claude
 81 |     Code](https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/overview).
 82 | 1.  Create a `.mcp.json` file in your project root if it doesn't exist.
 83 | 1.  Add the following configuration, replace the environment variables with your
 84 |     values, and save:
 85 | 
 86 |     ```json
 87 |     {
 88 |       "mcpServers": {
 89 |         "sqlite": {
 90 |           "command": "./PATH/TO/toolbox",
 91 |           "args": ["--prebuilt", "sqlite", "--stdio"],
 92 |           "env": {
 93 |             "SQLITE_DATABASE": "./sample.db"
 94 |           }
 95 |         }
 96 |       }
 97 |     }
 98 |     ```
 99 | 
100 | 1.  Restart Claude code to apply the new configuration.
101 | {{% /tab %}}
102 | {{% tab header="Claude desktop" lang="en" %}}
103 | 
104 | 1.  Open [Claude desktop](https://claude.ai/download) and navigate to Settings.
105 | 1.  Under the Developer tab, tap Edit Config to open the configuration file.
106 | 1.  Add the following configuration, replace the environment variables with your
107 |     values, and save:
108 | 
109 |     ```json
110 |     {
111 |       "mcpServers": {
112 |         "sqlite": {
113 |           "command": "./PATH/TO/toolbox",
114 |           "args": ["--prebuilt", "sqlite", "--stdio"],
115 |           "env": {
116 |             "SQLITE_DATABASE": "./sample.db"
117 |           }
118 |         }
119 |       }
120 |     }
121 |     ```
122 | 
123 | 1.  Restart Claude desktop.
124 | 1.  From the new chat screen, you should see a hammer (MCP) icon appear with the
125 |     new MCP server available.
126 | {{% /tab %}}
127 | {{% tab header="Cline" lang="en" %}}
128 | 
129 | 1.  Open the [Cline](https://github.com/cline/cline) extension in VS Code and
130 |     tap the **MCP Servers** icon.
131 | 1.  Tap Configure MCP Servers to open the configuration file.
132 | 1.  Add the following configuration, replace the environment variables with your
133 |     values, and save:
134 | 
135 |     ```json
136 |     {
137 |       "mcpServers": {
138 |         "sqlite": {
139 |           "command": "./PATH/TO/toolbox",
140 |           "args": ["--prebuilt", "sqlite", "--stdio"],
141 |           "env": {
142 |             "SQLITE_DATABASE": "./sample.db"
143 |           }
144 |         }
145 |       }
146 |     }
147 |     ```
148 | 
149 | 1.  You should see a green active status after the server is successfully
150 |     connected.
151 | {{% /tab %}}
152 | {{% tab header="Cursor" lang="en" %}}
153 | 
154 | 1.  Create a `.cursor` directory in your project root if it doesn't exist.
155 | 1.  Create a `.cursor/mcp.json` file if it doesn't exist and open it.
156 | 1.  Add the following configuration, replace the environment variables with your
157 |     values, and save:
158 | 
159 |     ```json
160 |     {
161 |       "mcpServers": {
162 |         "sqlite": {
163 |           "command": "./PATH/TO/toolbox",
164 |           "args": ["--prebuilt", "sqlite", "--stdio"],
165 |           "env": {
166 |             "SQLITE_DATABASE": "./sample.db"
167 |           }
168 |         }
169 |       }
170 |     }
171 |     ```
172 | 
173 | 1.  Open [Cursor](https://www.cursor.com/) and navigate to **Settings > Cursor
174 |     Settings > MCP**. You should see a green active status after the server is
175 |     successfully connected.
176 | {{% /tab %}}
177 | {{% tab header="Visual Studio Code (Copilot)" lang="en" %}}
178 | 
179 | 1.  Open [VS Code](https://code.visualstudio.com/docs/copilot/overview) and
180 |     create a `.vscode` directory in your project root if it doesn't exist.
181 | 1.  Create a `.vscode/mcp.json` file if it doesn't exist and open it.
182 | 1.  Add the following configuration, replace the environment variables with your
183 |     values, and save:
184 | 
185 |     ```json
186 |     {
187 |       "servers": {
188 |         "sqlite": {
189 |           "command": "./PATH/TO/toolbox",
190 |           "args": ["--prebuilt","sqlite","--stdio"],
191 |           "env": {
192 |             "SQLITE_DATABASE": "./sample.db"
193 |           }
194 |         }
195 |       }
196 |     }
197 |     ```
198 | {{% /tab %}}
199 | {{% tab header="Windsurf" lang="en" %}}
200 | 
201 | 1.  Open [Windsurf](https://docs.codeium.com/windsurf) and navigate to the
202 |     Cascade assistant.
203 | 1.  Tap on the hammer (MCP) icon, then Configure to open the configuration file.
204 | 1.  Add the following configuration, replace the environment variables with your
205 |     values, and save:
206 | 
207 |     ```json
208 |     {
209 |       "mcpServers": {
210 |         "sqlite": {
211 |           "command": "./PATH/TO/toolbox",
212 |           "args": ["--prebuilt","sqlite","--stdio"],
213 |           "env": {
214 |             "SQLITE_DATABASE": "./sample.db"
215 |           }
216 |         }
217 |       }
218 |     }
219 |     ```
220 | {{% /tab %}}
221 | {{% tab header="Gemini CLI" lang="en" %}}
222 | 
223 | 1.  Install the [Gemini
224 |     CLI](https://github.com/google-gemini/gemini-cli?tab=readme-ov-file#quickstart).
225 | 1.  In your working directory, create a folder named `.gemini`. Within it,
226 |     create a `settings.json` file.
227 | 1.  Add the following configuration, replace the environment variables with your
228 |     values, and then save:
229 | 
230 |     ```json
231 |     {
232 |       "mcpServers": {
233 |         "sqlite": {
234 |           "command": "./PATH/TO/toolbox",
235 |           "args": ["--prebuilt","sqlite","--stdio"],
236 |           "env": {
237 |             "SQLITE_DATABASE": "./sample.db"
238 |           }
239 |         }
240 |       }
241 |     }
242 |     ```
243 | {{% /tab %}}
244 | {{% tab header="Gemini Code Assist" lang="en" %}}
245 | 
246 | 1.  Install the [Gemini Code
247 |     Assist](https://marketplace.visualstudio.com/items?itemName=Google.geminicodeassist)
248 |     extension in Visual Studio Code.
249 | 1.  Enable Agent Mode in Gemini Code Assist chat.
250 | 1.  In your working directory, create a folder named `.gemini`. Within it,
251 |     create a `settings.json` file.
252 | 1.  Add the following configuration, replace the environment variables with your
253 |     values, and then save:
254 | 
255 |     ```json
256 |     {
257 |       "mcpServers": {
258 |         "sqlite": {
259 |           "command": "./PATH/TO/toolbox",
260 |           "args": ["--prebuilt","sqlite","--stdio"],
261 |           "env": {
262 |             "SQLITE_DATABASE": "./sample.db"
263 |           }
264 |         }
265 |       }
266 |     }
267 |     ```
268 | {{% /tab %}}
269 | {{< /tabpane >}}
270 | 
271 | ## Use Tools
272 | 
273 | Your AI tool is now connected to SQLite using MCP. Try asking your AI assistant
274 | to list tables, create a table, or define and execute other SQL statements.
275 | 
276 | The following tools are available to the LLM:
277 | 
278 | 1.  **list_tables**: lists tables and descriptions
279 | 1.  **execute_sql**: execute any SQL statement
280 | 
281 | {{< notice note >}}
282 | Prebuilt tools are pre-1.0, so expect some tool changes between versions. LLMs
283 | will adapt to the tools available, so this shouldn't affect most users.
284 | {{< /notice >}}
285 | 
```

--------------------------------------------------------------------------------
/tests/oracle/oracle_integration_test.go:
--------------------------------------------------------------------------------

```go
  1 | // Copyright © 2025, Oracle and/or its affiliates.
  2 | 
  3 | package oracle
  4 | 
  5 | import (
  6 | 	"context"
  7 | 	"database/sql"
  8 | 	"fmt"
  9 | 	"os"
 10 | 	"regexp"
 11 | 	"strings"
 12 | 	"testing"
 13 | 	"time"
 14 | 
 15 | 	"github.com/google/uuid"
 16 | 	"github.com/googleapis/genai-toolbox/internal/testutils"
 17 | 	"github.com/googleapis/genai-toolbox/tests"
 18 | )
 19 | 
 20 | var (
 21 | 	OracleSourceKind = "oracle"
 22 | 	OracleToolKind   = "oracle-sql"
 23 | 	OracleHost       = os.Getenv("ORACLE_HOST")
 24 | 	OracleUser       = os.Getenv("ORACLE_USER")
 25 | 	OraclePass       = os.Getenv("ORACLE_PASS")
 26 | 	OracleServerName = os.Getenv("ORACLE_SERVER_NAME")
 27 | 	OracleConnStr    = fmt.Sprintf(
 28 | 		"%s:%s/%s", OracleHost, "1521", OracleServerName)
 29 | )
 30 | 
 31 | func getOracleVars(t *testing.T) map[string]any {
 32 | 	switch "" {
 33 | 	case OracleHost:
 34 | 		t.Fatal("'ORACLE_HOST not set")
 35 | 	case OracleUser:
 36 | 		t.Fatal("'ORACLE_USER' not set")
 37 | 	case OraclePass:
 38 | 		t.Fatal("'ORACLE_PASS' not set")
 39 | 	case OracleServerName:
 40 | 		t.Fatal("'ORACLE_SERVER_NAME' not set")
 41 | 	}
 42 | 
 43 | 	return map[string]any{
 44 | 		"kind":             OracleSourceKind,
 45 | 		"connectionString": OracleConnStr,
 46 | 		"user":             OracleUser,
 47 | 		"password":         OraclePass,
 48 | 	}
 49 | }
 50 | 
 51 | // Copied over from oracle.go
 52 | func initOracleConnection(ctx context.Context, user, pass, connStr string) (*sql.DB, error) {
 53 | 	fullConnStr := fmt.Sprintf("oracle://%s:%s@%s", user, pass, connStr)
 54 | 
 55 | 	db, err := sql.Open("oracle", fullConnStr)
 56 | 	if err != nil {
 57 | 		return nil, fmt.Errorf("unable to open Oracle connection: %w", err)
 58 | 	}
 59 | 
 60 | 	err = db.PingContext(ctx)
 61 | 	if err != nil {
 62 | 		return nil, fmt.Errorf("unable to ping Oracle connection: %w", err)
 63 | 	}
 64 | 
 65 | 	return db, nil
 66 | }
 67 | 
 68 | func TestOracleSimpleToolEndpoints(t *testing.T) {
 69 | 	sourceConfig := getOracleVars(t)
 70 | 	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
 71 | 	defer cancel()
 72 | 
 73 | 	var args []string
 74 | 
 75 | 	db, err := initOracleConnection(ctx, OracleUser, OraclePass, OracleConnStr)
 76 | 	if err != nil {
 77 | 		t.Fatalf("unable to create Oracle connection pool: %s", err)
 78 | 	}
 79 | 
 80 | 	dropAllUserTables(t, ctx, db)
 81 | 
 82 | 	// create table name with UUID
 83 | 	tableNameParam := "param_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
 84 | 	tableNameAuth := "auth_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
 85 | 	tableNameTemplateParam := "template_param_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
 86 | 
 87 | 	// set up data for param tool
 88 | 	createParamTableStmt, insertParamTableStmt, paramToolStmt, idParamToolStmt, nameParamToolStmt, arrayToolStmt, paramTestParams := getOracleParamToolInfo(tableNameParam)
 89 | 	teardownTable1 := setupOracleTable(t, ctx, db, createParamTableStmt, insertParamTableStmt, tableNameParam, paramTestParams)
 90 | 	defer teardownTable1(t)
 91 | 
 92 | 	// set up data for auth tool
 93 | 	createAuthTableStmt, insertAuthTableStmt, authToolStmt, authTestParams := getOracleAuthToolInfo(tableNameAuth)
 94 | 	teardownTable2 := setupOracleTable(t, ctx, db, createAuthTableStmt, insertAuthTableStmt, tableNameAuth, authTestParams)
 95 | 	defer teardownTable2(t)
 96 | 
 97 | 	// Write config into a file and pass it to command
 98 | 	toolsFile := tests.GetToolsConfig(sourceConfig, OracleToolKind, paramToolStmt, idParamToolStmt, nameParamToolStmt, arrayToolStmt, authToolStmt)
 99 | 	toolsFile = tests.AddExecuteSqlConfig(t, toolsFile, "oracle-execute-sql")
100 | 	tmplSelectCombined, tmplSelectFilterCombined := tests.GetMySQLTmplToolStatement()
101 | 	toolsFile = tests.AddTemplateParamConfig(t, toolsFile, OracleToolKind, tmplSelectCombined, tmplSelectFilterCombined, "")
102 | 
103 | 	cmd, cleanup, err := tests.StartCmd(ctx, toolsFile, args...)
104 | 	if err != nil {
105 | 		t.Fatalf("command initialization returned an error: %s", err)
106 | 	}
107 | 	defer cleanup()
108 | 
109 | 	waitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
110 | 	defer cancel()
111 | 	out, err := testutils.WaitForString(waitCtx, regexp.MustCompile(`Server ready to serve`), cmd.Out)
112 | 	if err != nil {
113 | 		t.Logf("toolbox command logs: \n%s", out)
114 | 		t.Fatalf("toolbox didn't start successfully: %s", err)
115 | 	}
116 | 
117 | 	// Get configs for tests
118 | 	select1Want := "[{\"1\":1}]"
119 | 	mcpMyFailToolWant := `{"jsonrpc":"2.0","id":"invoke-fail-tool","result":{"content":[{"type":"text","text":"unable to execute query: ORA-00900: invalid SQL statement\n error occur at position: 0"}],"isError":true}}`
120 | 	createTableStatement := `"CREATE TABLE t (id NUMBER GENERATED AS IDENTITY PRIMARY KEY, name VARCHAR2(255))"`
121 | 	mcpSelect1Want := `{"jsonrpc":"2.0","id":"invoke my-auth-required-tool","result":{"content":[{"type":"text","text":"{\"1\":1}"}]}}`
122 | 
123 | 	// Run tests
124 | 	tests.RunToolGetTest(t)
125 | 	tests.RunToolInvokeTest(t, select1Want,
126 | 		tests.DisableArrayTest(),
127 | 	)
128 | 	tests.RunMCPToolCallMethod(t, mcpMyFailToolWant, mcpSelect1Want)
129 | 	tests.RunExecuteSqlToolInvokeTest(t, createTableStatement, select1Want)
130 | 	tests.RunToolInvokeWithTemplateParameters(t, tableNameTemplateParam)
131 | }
132 | 
133 | func setupOracleTable(t *testing.T, ctx context.Context, pool *sql.DB, createStatement, insertStatement, tableName string, params []any) func(*testing.T) {
134 | 	err := pool.PingContext(ctx)
135 | 	if err != nil {
136 | 		t.Fatalf("unable to connect to test database: %s", err)
137 | 	}
138 | 
139 | 	// Create table
140 | 	_, err = pool.QueryContext(ctx, createStatement)
141 | 	if err != nil {
142 | 		t.Fatalf("unable to create test table %s: %s", tableName, err)
143 | 	}
144 | 
145 | 	// Insert test data
146 | 	_, err = pool.QueryContext(ctx, insertStatement, params...)
147 | 	if err != nil {
148 | 		t.Fatalf("unable to insert test data: %s", err)
149 | 	}
150 | 
151 | 	return func(t *testing.T) {
152 | 		// tear down test
153 | 		_, err = pool.ExecContext(ctx, fmt.Sprintf("DROP TABLE %s", tableName))
154 | 		if err != nil {
155 | 			t.Errorf("Teardown failed: %s", err)
156 | 		}
157 | 	}
158 | }
159 | 
160 | func getOracleParamToolInfo(tableName string) (string, string, string, string, string, string, []any) {
161 | 	// Use GENERATED AS IDENTITY for auto-incrementing primary keys.
162 | 	// VARCHAR2 is the standard string type in Oracle.
163 | 	createStatement := fmt.Sprintf(`CREATE TABLE %s ("id" NUMBER GENERATED AS IDENTITY PRIMARY KEY, "name" VARCHAR2(255))`, tableName)
164 | 
165 | 	// MODIFIED: Use a PL/SQL block for multiple inserts
166 | 	insertStatement := fmt.Sprintf(`
167 | 		BEGIN
168 | 			INSERT INTO %s ("name") VALUES (:1);
169 | 			INSERT INTO %s ("name") VALUES (:2);
170 | 			INSERT INTO %s ("name") VALUES (:3);
171 | 			INSERT INTO %s ("name") VALUES (:4);
172 | 		END;`, tableName, tableName, tableName, tableName)
173 | 
174 | 	toolStatement := fmt.Sprintf(`SELECT * FROM %s WHERE "id" = :1 OR "name" = :2`, tableName)
175 | 	idParamStatement := fmt.Sprintf(`SELECT * FROM %s WHERE "id" = :1`, tableName)
176 | 	nameParamStatement := fmt.Sprintf(`SELECT * FROM %s WHERE "name" = :1`, tableName)
177 | 
178 | 	// Oracle's equivalent for array parameters is using the 'MEMBER OF' operator
179 | 	// with a collection type defined in the database schema.
180 | 	arrayToolStatement := fmt.Sprintf(`SELECT * FROM %s WHERE "id" MEMBER OF :1 AND "name" MEMBER OF :2`, tableName)
181 | 
182 | 	params := []any{"Alice", "Jane", "Sid", nil}
183 | 
184 | 	return createStatement, insertStatement, toolStatement, idParamStatement, nameParamStatement, arrayToolStatement, params
185 | }
186 | 
187 | // getOracleAuthToolInfo returns statements and params for my-auth-tool for Oracle SQL
188 | func getOracleAuthToolInfo(tableName string) (string, string, string, []any) {
189 | 	createStatement := fmt.Sprintf(`CREATE TABLE %s ("id" NUMBER GENERATED AS IDENTITY PRIMARY KEY, "name" VARCHAR2(255), "email" VARCHAR2(255))`, tableName)
190 | 
191 | 	// MODIFIED: Use a PL/SQL block for multiple inserts
192 | 	insertStatement := fmt.Sprintf(`
193 | 		BEGIN
194 | 			INSERT INTO %s ("name", "email") VALUES (:1, :2);
195 | 			INSERT INTO %s ("name", "email") VALUES (:3, :4);
196 | 		END;`, tableName, tableName)
197 | 
198 | 	toolStatement := fmt.Sprintf(`SELECT "name" FROM %s WHERE "email" = :1`, tableName)
199 | 
200 | 	params := []any{"Alice", tests.ServiceAccountEmail, "Jane", "[email protected]"}
201 | 
202 | 	return createStatement, insertStatement, toolStatement, params
203 | }
204 | 
205 | // dropAllUserTables finds and drops all tables owned by the current user.
206 | func dropAllUserTables(t *testing.T, ctx context.Context, db *sql.DB) {
207 | 	// Query for only the tables we know are created by this test suite.
208 | 	const query = `
209 | 		SELECT table_name FROM user_tables
210 | 		WHERE table_name LIKE 'param_table_%'
211 | 		   OR table_name LIKE 'auth_table_%'
212 | 		   OR table_name LIKE 'template_param_table_%'`
213 | 
214 | 	rows, err := db.QueryContext(ctx, query)
215 | 	if err != nil {
216 | 		t.Fatalf("failed to query for user tables: %v", err)
217 | 	}
218 | 	defer rows.Close()
219 | 
220 | 	var tablesToDrop []string
221 | 	for rows.Next() {
222 | 		var tableName string
223 | 		if err := rows.Scan(&tableName); err != nil {
224 | 			t.Fatalf("failed to scan table name: %v", err)
225 | 		}
226 | 		tablesToDrop = append(tablesToDrop, tableName)
227 | 	}
228 | 
229 | 	if err := rows.Err(); err != nil {
230 | 		t.Fatalf("error iterating over tables: %v", err)
231 | 	}
232 | 
233 | 	for _, tableName := range tablesToDrop {
234 | 		_, err := db.ExecContext(ctx, fmt.Sprintf("DROP TABLE %s CASCADE CONSTRAINTS", tableName))
235 | 		if err != nil {
236 | 			t.Logf("failed to drop table %s: %v", tableName, err)
237 | 		}
238 | 	}
239 | }
240 | 
```

--------------------------------------------------------------------------------
/tests/cloudsqlpg/cloud_sql_pg_integration_test.go:
--------------------------------------------------------------------------------

```go
  1 | // Copyright 2024 Google LLC
  2 | //
  3 | // Licensed under the Apache License, Version 2.0 (the "License");
  4 | // you may not use this file except in compliance with the License.
  5 | // You may obtain a copy of the License at
  6 | //
  7 | //     http://www.apache.org/licenses/LICENSE-2.0
  8 | //
  9 | // Unless required by applicable law or agreed to in writing, software
 10 | // distributed under the License is distributed on an "AS IS" BASIS,
 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12 | // See the License for the specific language governing permissions and
 13 | // limitations under the License.
 14 | 
 15 | package cloudsqlpg
 16 | 
 17 | import (
 18 | 	"context"
 19 | 	"fmt"
 20 | 	"net"
 21 | 	"os"
 22 | 	"regexp"
 23 | 	"strings"
 24 | 	"testing"
 25 | 	"time"
 26 | 
 27 | 	"cloud.google.com/go/cloudsqlconn"
 28 | 	"github.com/google/uuid"
 29 | 	"github.com/googleapis/genai-toolbox/internal/testutils"
 30 | 	"github.com/googleapis/genai-toolbox/tests"
 31 | 	"github.com/jackc/pgx/v5/pgxpool"
 32 | )
 33 | 
 34 | var (
 35 | 	CloudSQLPostgresSourceKind = "cloud-sql-postgres"
 36 | 	CloudSQLPostgresToolKind   = "postgres-sql"
 37 | 	CloudSQLPostgresProject    = os.Getenv("CLOUD_SQL_POSTGRES_PROJECT")
 38 | 	CloudSQLPostgresRegion     = os.Getenv("CLOUD_SQL_POSTGRES_REGION")
 39 | 	CloudSQLPostgresInstance   = os.Getenv("CLOUD_SQL_POSTGRES_INSTANCE")
 40 | 	CloudSQLPostgresDatabase   = os.Getenv("CLOUD_SQL_POSTGRES_DATABASE")
 41 | 	CloudSQLPostgresUser       = os.Getenv("CLOUD_SQL_POSTGRES_USER")
 42 | 	CloudSQLPostgresPass       = os.Getenv("CLOUD_SQL_POSTGRES_PASS")
 43 | )
 44 | 
 45 | func getCloudSQLPgVars(t *testing.T) map[string]any {
 46 | 	switch "" {
 47 | 	case CloudSQLPostgresProject:
 48 | 		t.Fatal("'CLOUD_SQL_POSTGRES_PROJECT' not set")
 49 | 	case CloudSQLPostgresRegion:
 50 | 		t.Fatal("'CLOUD_SQL_POSTGRES_REGION' not set")
 51 | 	case CloudSQLPostgresInstance:
 52 | 		t.Fatal("'CLOUD_SQL_POSTGRES_INSTANCE' not set")
 53 | 	case CloudSQLPostgresDatabase:
 54 | 		t.Fatal("'CLOUD_SQL_POSTGRES_DATABASE' not set")
 55 | 	case CloudSQLPostgresUser:
 56 | 		t.Fatal("'CLOUD_SQL_POSTGRES_USER' not set")
 57 | 	case CloudSQLPostgresPass:
 58 | 		t.Fatal("'CLOUD_SQL_POSTGRES_PASS' not set")
 59 | 	}
 60 | 
 61 | 	return map[string]any{
 62 | 		"kind":     CloudSQLPostgresSourceKind,
 63 | 		"project":  CloudSQLPostgresProject,
 64 | 		"instance": CloudSQLPostgresInstance,
 65 | 		"region":   CloudSQLPostgresRegion,
 66 | 		"database": CloudSQLPostgresDatabase,
 67 | 		"user":     CloudSQLPostgresUser,
 68 | 		"password": CloudSQLPostgresPass,
 69 | 	}
 70 | }
 71 | 
 72 | // Copied over from cloud_sql_pg.go
 73 | func initCloudSQLPgConnectionPool(project, region, instance, ip_type, user, pass, dbname string) (*pgxpool.Pool, error) {
 74 | 	// Configure the driver to connect to the database
 75 | 	dsn := fmt.Sprintf("user=%s password=%s dbname=%s sslmode=disable", user, pass, dbname)
 76 | 	config, err := pgxpool.ParseConfig(dsn)
 77 | 	if err != nil {
 78 | 		return nil, fmt.Errorf("unable to parse connection uri: %w", err)
 79 | 	}
 80 | 
 81 | 	// Create a new dialer with options
 82 | 	dialOpts, err := tests.GetCloudSQLDialOpts(ip_type)
 83 | 	if err != nil {
 84 | 		return nil, err
 85 | 	}
 86 | 	d, err := cloudsqlconn.NewDialer(context.Background(), cloudsqlconn.WithDefaultDialOptions(dialOpts...))
 87 | 	if err != nil {
 88 | 		return nil, fmt.Errorf("unable to parse connection uri: %w", err)
 89 | 	}
 90 | 
 91 | 	// Tell the driver to use the Cloud SQL Go Connector to create connections
 92 | 	i := fmt.Sprintf("%s:%s:%s", project, region, instance)
 93 | 	config.ConnConfig.DialFunc = func(ctx context.Context, _ string, instance string) (net.Conn, error) {
 94 | 		return d.Dial(ctx, i)
 95 | 	}
 96 | 
 97 | 	// Interact with the driver directly as you normally would
 98 | 	pool, err := pgxpool.NewWithConfig(context.Background(), config)
 99 | 	if err != nil {
100 | 		return nil, err
101 | 	}
102 | 	return pool, nil
103 | }
104 | 
105 | func TestCloudSQLPgSimpleToolEndpoints(t *testing.T) {
106 | 	sourceConfig := getCloudSQLPgVars(t)
107 | 	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
108 | 	defer cancel()
109 | 
110 | 	var args []string
111 | 
112 | 	pool, err := initCloudSQLPgConnectionPool(CloudSQLPostgresProject, CloudSQLPostgresRegion, CloudSQLPostgresInstance, "public", CloudSQLPostgresUser, CloudSQLPostgresPass, CloudSQLPostgresDatabase)
113 | 	if err != nil {
114 | 		t.Fatalf("unable to create Cloud SQL connection pool: %s", err)
115 | 	}
116 | 
117 | 	// create table name with UUID
118 | 	tableNameParam := "param_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
119 | 	tableNameAuth := "auth_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
120 | 	tableNameTemplateParam := "template_param_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
121 | 
122 | 	// set up data for param tool
123 | 	createParamTableStmt, insertParamTableStmt, paramToolStmt, idParamToolStmt, nameParamToolStmt, arrayToolStmt, paramTestParams := tests.GetPostgresSQLParamToolInfo(tableNameParam)
124 | 	teardownTable1 := tests.SetupPostgresSQLTable(t, ctx, pool, createParamTableStmt, insertParamTableStmt, tableNameParam, paramTestParams)
125 | 	defer teardownTable1(t)
126 | 
127 | 	// set up data for auth tool
128 | 	createAuthTableStmt, insertAuthTableStmt, authToolStmt, authTestParams := tests.GetPostgresSQLAuthToolInfo(tableNameAuth)
129 | 	teardownTable2 := tests.SetupPostgresSQLTable(t, ctx, pool, createAuthTableStmt, insertAuthTableStmt, tableNameAuth, authTestParams)
130 | 	defer teardownTable2(t)
131 | 
132 | 	// Write config into a file and pass it to command
133 | 	toolsFile := tests.GetToolsConfig(sourceConfig, CloudSQLPostgresToolKind, paramToolStmt, idParamToolStmt, nameParamToolStmt, arrayToolStmt, authToolStmt)
134 | 	toolsFile = tests.AddExecuteSqlConfig(t, toolsFile, "postgres-execute-sql")
135 | 	tmplSelectCombined, tmplSelectFilterCombined := tests.GetPostgresSQLTmplToolStatement()
136 | 	toolsFile = tests.AddTemplateParamConfig(t, toolsFile, CloudSQLPostgresToolKind, tmplSelectCombined, tmplSelectFilterCombined, "")
137 | 
138 | 	cmd, cleanup, err := tests.StartCmd(ctx, toolsFile, args...)
139 | 	if err != nil {
140 | 		t.Fatalf("command initialization returned an error: %s", err)
141 | 	}
142 | 	defer cleanup()
143 | 
144 | 	waitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
145 | 	defer cancel()
146 | 	out, err := testutils.WaitForString(waitCtx, regexp.MustCompile(`Server ready to serve`), cmd.Out)
147 | 	if err != nil {
148 | 		t.Logf("toolbox command logs: \n%s", out)
149 | 		t.Fatalf("toolbox didn't start successfully: %s", err)
150 | 	}
151 | 
152 | 	// Get configs for tests
153 | 	select1Want, mcpMyFailToolWant, createTableStatement, mcpSelect1Want := tests.GetPostgresWants()
154 | 
155 | 	// Run tests
156 | 	tests.RunToolGetTest(t)
157 | 	tests.RunToolInvokeTest(t, select1Want)
158 | 	tests.RunMCPToolCallMethod(t, mcpMyFailToolWant, mcpSelect1Want)
159 | 	tests.RunExecuteSqlToolInvokeTest(t, createTableStatement, select1Want)
160 | 	tests.RunToolInvokeWithTemplateParameters(t, tableNameTemplateParam)
161 | }
162 | 
163 | // Test connection with different IP type
164 | func TestCloudSQLPgIpConnection(t *testing.T) {
165 | 	sourceConfig := getCloudSQLPgVars(t)
166 | 
167 | 	tcs := []struct {
168 | 		name   string
169 | 		ipType string
170 | 	}{
171 | 		{
172 | 			name:   "public ip",
173 | 			ipType: "public",
174 | 		},
175 | 		{
176 | 			name:   "private ip",
177 | 			ipType: "private",
178 | 		},
179 | 	}
180 | 	for _, tc := range tcs {
181 | 		t.Run(tc.name, func(t *testing.T) {
182 | 			sourceConfig["ipType"] = tc.ipType
183 | 			err := tests.RunSourceConnectionTest(t, sourceConfig, CloudSQLPostgresToolKind)
184 | 			if err != nil {
185 | 				t.Fatalf("Connection test failure: %s", err)
186 | 			}
187 | 		})
188 | 	}
189 | }
190 | 
191 | func TestCloudSQLPgIAMConnection(t *testing.T) {
192 | 	getCloudSQLPgVars(t)
193 | 	// service account email used for IAM should trim the suffix
194 | 	serviceAccountEmail := strings.TrimSuffix(tests.ServiceAccountEmail, ".gserviceaccount.com")
195 | 
196 | 	noPassSourceConfig := map[string]any{
197 | 		"kind":     CloudSQLPostgresSourceKind,
198 | 		"project":  CloudSQLPostgresProject,
199 | 		"instance": CloudSQLPostgresInstance,
200 | 		"region":   CloudSQLPostgresRegion,
201 | 		"database": CloudSQLPostgresDatabase,
202 | 		"user":     serviceAccountEmail,
203 | 	}
204 | 
205 | 	noUserSourceConfig := map[string]any{
206 | 		"kind":     CloudSQLPostgresSourceKind,
207 | 		"project":  CloudSQLPostgresProject,
208 | 		"instance": CloudSQLPostgresInstance,
209 | 		"region":   CloudSQLPostgresRegion,
210 | 		"database": CloudSQLPostgresDatabase,
211 | 		"password": "random",
212 | 	}
213 | 
214 | 	noUserNoPassSourceConfig := map[string]any{
215 | 		"kind":     CloudSQLPostgresSourceKind,
216 | 		"project":  CloudSQLPostgresProject,
217 | 		"instance": CloudSQLPostgresInstance,
218 | 		"region":   CloudSQLPostgresRegion,
219 | 		"database": CloudSQLPostgresDatabase,
220 | 	}
221 | 	tcs := []struct {
222 | 		name         string
223 | 		sourceConfig map[string]any
224 | 		isErr        bool
225 | 	}{
226 | 		{
227 | 			name:         "no user no pass",
228 | 			sourceConfig: noUserNoPassSourceConfig,
229 | 			isErr:        false,
230 | 		},
231 | 		{
232 | 			name:         "no password",
233 | 			sourceConfig: noPassSourceConfig,
234 | 			isErr:        false,
235 | 		},
236 | 		{
237 | 			name:         "no user",
238 | 			sourceConfig: noUserSourceConfig,
239 | 			isErr:        true,
240 | 		},
241 | 	}
242 | 	for _, tc := range tcs {
243 | 		t.Run(tc.name, func(t *testing.T) {
244 | 			err := tests.RunSourceConnectionTest(t, tc.sourceConfig, CloudSQLPostgresToolKind)
245 | 			if err != nil {
246 | 				if tc.isErr {
247 | 					return
248 | 				}
249 | 				t.Fatalf("Connection test failure: %s", err)
250 | 			}
251 | 			if tc.isErr {
252 | 				t.Fatalf("Expected error but test passed.")
253 | 			}
254 | 		})
255 | 	}
256 | }
257 | 
```

--------------------------------------------------------------------------------
/.ci/versioned.release.cloudbuild.yaml:
--------------------------------------------------------------------------------

```yaml
  1 | # Copyright 2024 Google LLC
  2 | # Licensed under the Apache License, Version 2.0 (the "License");
  3 | # you may not use this file except in compliance with the License.
  4 | # You may obtain a copy of the License at
  5 | #
  6 | #      http://www.apache.org/licenses/LICENSE-2.0
  7 | #
  8 | # Unless required by applicable law or agreed to in writing, software
  9 | # distributed under the License is distributed on an "AS IS" BASIS,
 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 11 | # See the License for the specific language governing permissions and
 12 | # limitations under the License.
 13 | 
 14 | steps:
 15 |   - id: "build-docker"
 16 |     name: "gcr.io/cloud-builders/docker"
 17 |     waitFor: ['-']
 18 |     script: |
 19 |         #!/usr/bin/env bash
 20 |         export VERSION=$(cat ./cmd/version.txt)
 21 |         docker buildx create --name container-builder --driver docker-container --bootstrap --use
 22 | 
 23 |         export TAGS="-t ${_DOCKER_URI}:$VERSION"
 24 |         if [[ $_PUSH_LATEST == 'true' ]]; then
 25 |           export TAGS="$TAGS -t ${_DOCKER_URI}:latest"
 26 |         fi
 27 |         docker buildx build --platform linux/amd64,linux/arm64 --build-arg BUILD_TYPE=container.release --build-arg COMMIT_SHA=$(git rev-parse --short HEAD) $TAGS --push .
 28 | 
 29 |   - id: "install-dependencies"
 30 |     name: golang:1
 31 |     waitFor: ['-']
 32 |     env: 
 33 |       - 'GOPATH=/gopath'
 34 |     volumes:
 35 |       - name: 'go'
 36 |         path: '/gopath'
 37 |     script: |
 38 |         go get -d ./...
 39 | 
 40 |   - id: "build-linux-amd64"
 41 |     name: golang:1
 42 |     waitFor: 
 43 |       - "install-dependencies"
 44 |     env: 
 45 |       - 'GOPATH=/gopath'
 46 |     volumes:
 47 |       - name: 'go'
 48 |         path: '/gopath'
 49 |     script: |
 50 |         #!/usr/bin/env bash
 51 |         export VERSION=$(cat ./cmd/version.txt)
 52 |         CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
 53 |           go build -ldflags "-X github.com/googleapis/genai-toolbox/cmd.buildType=binary -X github.com/googleapis/genai-toolbox/cmd.commitSha=$(git rev-parse --short HEAD)" -o toolbox.linux.amd64
 54 | 
 55 |   - id: "store-linux-amd64"
 56 |     name: "gcr.io/cloud-builders/gcloud:latest"
 57 |     waitFor:
 58 |       - "build-linux-amd64"
 59 |     script: |
 60 |         #!/usr/bin/env bash
 61 |         export VERSION=v$(cat ./cmd/version.txt)
 62 |         gcloud storage cp toolbox.linux.amd64 gs://$_BUCKET_NAME/$VERSION/linux/amd64/toolbox
 63 | 
 64 |   - id: "build-linux-amd64-geminicli"
 65 |     name: golang:1
 66 |     waitFor: 
 67 |       - "install-dependencies"
 68 |     env: 
 69 |       - 'GOPATH=/gopath'
 70 |     volumes:
 71 |       - name: 'go'
 72 |         path: '/gopath'
 73 |     script: |
 74 |         #!/usr/bin/env bash
 75 |         export VERSION=$(cat ./cmd/version.txt)
 76 |         CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
 77 |           go build -ldflags "-X github.com/googleapis/genai-toolbox/cmd.buildType=geminicli.binary -X github.com/googleapis/genai-toolbox/cmd.commitSha=$(git rev-parse --short HEAD)" -o toolbox.geminicli.linux.amd64
 78 | 
 79 |   - id: "store-linux-amd64-geminicli"
 80 |     name: "gcr.io/cloud-builders/gcloud:latest"
 81 |     waitFor:
 82 |       - "build-linux-amd64-geminicli"
 83 |     script: |
 84 |         #!/usr/bin/env bash
 85 |         export VERSION=v$(cat ./cmd/version.txt)
 86 |         gcloud storage cp toolbox.geminicli.linux.amd64 gs://$_BUCKET_NAME/geminicli/$VERSION/linux/amd64/toolbox
 87 | 
 88 |   - id: "build-darwin-arm64"
 89 |     name: golang:1
 90 |     waitFor: 
 91 |       - "install-dependencies"
 92 |     env: 
 93 |       - 'GOPATH=/gopath'
 94 |     volumes:
 95 |       - name: 'go'
 96 |         path: '/gopath'
 97 |     script: |
 98 |         #!/usr/bin/env bash
 99 |         export VERSION=$(cat ./cmd/version.txt)
100 |         CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 \
101 |           go build -ldflags "-X github.com/googleapis/genai-toolbox/cmd.buildType=binary -X github.com/googleapis/genai-toolbox/cmd.commitSha=$(git rev-parse --short HEAD)" -o toolbox.darwin.arm64
102 | 
103 |   - id: "store-darwin-arm64"
104 |     name: "gcr.io/cloud-builders/gcloud:latest"
105 |     waitFor:
106 |       - "build-darwin-arm64"
107 |     script: |
108 |         #!/usr/bin/env bash
109 |         export VERSION=v$(cat ./cmd/version.txt)
110 |         gcloud storage cp toolbox.darwin.arm64 gs://$_BUCKET_NAME/$VERSION/darwin/arm64/toolbox
111 | 
112 |   - id: "build-darwin-arm64-geminicli"
113 |     name: golang:1
114 |     waitFor: 
115 |       - "install-dependencies"
116 |     env: 
117 |       - 'GOPATH=/gopath'
118 |     volumes:
119 |       - name: 'go'
120 |         path: '/gopath'
121 |     script: |
122 |         #!/usr/bin/env bash
123 |         export VERSION=$(cat ./cmd/version.txt)
124 |         CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 \
125 |           go build -ldflags "-X github.com/googleapis/genai-toolbox/cmd.buildType=geminicli.binary -X github.com/googleapis/genai-toolbox/cmd.commitSha=$(git rev-parse --short HEAD)" -o toolbox.geminicli.darwin.arm64
126 | 
127 |   - id: "store-darwin-arm64-geminicli"
128 |     name: "gcr.io/cloud-builders/gcloud:latest"
129 |     waitFor:
130 |       - "build-darwin-arm64-geminicli"
131 |     script: |
132 |         #!/usr/bin/env bash
133 |         export VERSION=v$(cat ./cmd/version.txt)
134 |         gcloud storage cp toolbox.geminicli.darwin.arm64 gs://$_BUCKET_NAME/geminicli/$VERSION/darwin/arm64/toolbox
135 | 
136 |   - id: "build-darwin-amd64"
137 |     name: golang:1
138 |     waitFor: 
139 |       - "install-dependencies"
140 |     env: 
141 |       - 'GOPATH=/gopath'
142 |     volumes:
143 |       - name: 'go'
144 |         path: '/gopath'
145 |     script: |
146 |         #!/usr/bin/env bash
147 |         export VERSION=$(cat ./cmd/version.txt)
148 |         CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 \
149 |           go build -ldflags "-X github.com/googleapis/genai-toolbox/cmd.buildType=binary -X github.com/googleapis/genai-toolbox/cmd.commitSha=$(git rev-parse --short HEAD)" -o toolbox.darwin.amd64
150 | 
151 |   - id: "store-darwin-amd64"
152 |     name: "gcr.io/cloud-builders/gcloud:latest"
153 |     waitFor:
154 |       - "build-darwin-amd64"
155 |     script: |
156 |         #!/usr/bin/env bash
157 |         export VERSION=v$(cat ./cmd/version.txt)
158 |         gcloud storage cp toolbox.darwin.amd64 gs://$_BUCKET_NAME/$VERSION/darwin/amd64/toolbox
159 | 
160 |   - id: "build-darwin-amd64-geminicli"
161 |     name: golang:1
162 |     waitFor: 
163 |       - "install-dependencies"
164 |     env: 
165 |       - 'GOPATH=/gopath'
166 |     volumes:
167 |       - name: 'go'
168 |         path: '/gopath'
169 |     script: |
170 |         #!/usr/bin/env bash
171 |         export VERSION=$(cat ./cmd/version.txt)
172 |         CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 \
173 |           go build -ldflags "-X github.com/googleapis/genai-toolbox/cmd.buildType=geminicli.binary -X github.com/googleapis/genai-toolbox/cmd.commitSha=$(git rev-parse --short HEAD)" -o toolbox.geminicli.darwin.amd64
174 | 
175 |   - id: "store-darwin-amd64-geminicli"
176 |     name: "gcr.io/cloud-builders/gcloud:latest"
177 |     waitFor:
178 |       - "build-darwin-amd64-geminicli"
179 |     script: |
180 |         #!/usr/bin/env bash
181 |         export VERSION=v$(cat ./cmd/version.txt)
182 |         gcloud storage cp toolbox.geminicli.darwin.amd64 gs://$_BUCKET_NAME/geminicli/$VERSION/darwin/amd64/toolbox
183 | 
184 |   - id: "build-windows-amd64"
185 |     name: golang:1
186 |     waitFor: 
187 |       - "install-dependencies"
188 |     env: 
189 |       - 'GOPATH=/gopath'
190 |     volumes:
191 |       - name: 'go'
192 |         path: '/gopath'
193 |     script: |
194 |         #!/usr/bin/env bash
195 |         export VERSION=$(cat ./cmd/version.txt)
196 |         CGO_ENABLED=0 GOOS=windows GOARCH=amd64 \
197 |           go build -ldflags "-X github.com/googleapis/genai-toolbox/cmd.buildType=binary -X github.com/googleapis/genai-toolbox/cmd.commitSha=$(git rev-parse --short HEAD)" -o toolbox.windows.amd64
198 | 
199 |   - id: "store-windows-amd64"
200 |     name: "gcr.io/cloud-builders/gcloud:latest"
201 |     waitFor:
202 |       - "build-windows-amd64"
203 |     script: |
204 |         #!/usr/bin/env bash
205 |         export VERSION=v$(cat ./cmd/version.txt)
206 |         gcloud storage cp toolbox.windows.amd64 gs://$_BUCKET_NAME/$VERSION/windows/amd64/toolbox.exe
207 | 
208 |   - id: "build-windows-amd64-geminicli"
209 |     name: golang:1
210 |     waitFor: 
211 |       - "install-dependencies"
212 |     env: 
213 |       - 'GOPATH=/gopath'
214 |     volumes:
215 |       - name: 'go'
216 |         path: '/gopath'
217 |     script: |
218 |         #!/usr/bin/env bash
219 |         export VERSION=$(cat ./cmd/version.txt)
220 |         CGO_ENABLED=0 GOOS=windows GOARCH=amd64 \
221 |           go build -ldflags "-X github.com/googleapis/genai-toolbox/cmd.buildType=geminicli.binary -X github.com/googleapis/genai-toolbox/cmd.commitSha=$(git rev-parse --short HEAD)" -o toolbox.geminicli.windows.amd64
222 | 
223 |   - id: "store-windows-amd64-geminicli"
224 |     name: "gcr.io/cloud-builders/gcloud:latest"
225 |     waitFor:
226 |       - "build-windows-amd64-geminicli"
227 |     script: |
228 |         #!/usr/bin/env bash
229 |         export VERSION=v$(cat ./cmd/version.txt)
230 |         gcloud storage cp toolbox.geminicli.windows.amd64 gs://$_BUCKET_NAME/geminicli/$VERSION/windows/amd64/toolbox.exe
231 | 
232 | options:
233 |   automapSubstitutions: true
234 |   dynamicSubstitutions: true
235 |   logging: CLOUD_LOGGING_ONLY # Necessary for custom service account
236 |   machineType: 'E2_HIGHCPU_32'
237 | 
238 | substitutions:
239 |   _REGION: us-central1
240 |   _AR_HOSTNAME: ${_REGION}-docker.pkg.dev
241 |   _AR_REPO_NAME: toolbox
242 |   _BUCKET_NAME: genai-toolbox
243 |   _DOCKER_URI: ${_AR_HOSTNAME}/${PROJECT_ID}/${_AR_REPO_NAME}/toolbox
244 |   _PUSH_LATEST: "true"
245 | 
```

--------------------------------------------------------------------------------
/docs/en/resources/tools/firestore/firestore-add-documents.md:
--------------------------------------------------------------------------------

```markdown
  1 | ---
  2 | title: "firestore-add-documents"
  3 | type: docs
  4 | weight: 1
  5 | description: >
  6 |   A "firestore-add-documents" tool adds document to a given collection path.
  7 | aliases:
  8 | - /resources/tools/firestore-add-documents
  9 | ---
 10 | ## Description
 11 | 
 12 | The `firestore-add-documents` tool allows you to add new documents to a
 13 | Firestore collection. It supports all Firestore data types using Firestore's
 14 | native JSON format. The tool automatically generates a unique document ID for
 15 | each new document.
 16 | 
 17 | ## Parameters
 18 | 
 19 | | Parameter        | Type    | Required | Description                                                                                                                                                                                                   |
 20 | |------------------|---------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
 21 | | `collectionPath` | string  | Yes      | The path of the collection where the document will be added                                                                                                                                                   |
 22 | | `documentData`   | map     | Yes      | The data to be added as a document to the given collection. Must use [Firestore's native JSON format](https://cloud.google.com/firestore/docs/reference/rest/Shared.Types/ArrayValue#Value) with typed values |
 23 | | `returnData`     | boolean | No       | If set to true, the output will include the data of the created document. Defaults to false to help avoid overloading the context                                                                             |
 24 | 
 25 | ## Output
 26 | 
 27 | The tool returns a map containing:
 28 | 
 29 | | Field          | Type   | Description                                                                                                                    |
 30 | |----------------|--------|--------------------------------------------------------------------------------------------------------------------------------|
 31 | | `documentPath` | string | The full resource name of the created document (e.g., `projects/{projectId}/databases/{databaseId}/documents/{document_path}`) |
 32 | | `createTime`   | string | The timestamp when the document was created                                                                                    |
 33 | | `documentData` | map    | The data that was added (only included when `returnData` is true)                                                              |
 34 | 
 35 | ## Data Type Format
 36 | 
 37 | The tool requires Firestore's native JSON format for document data. Each field
 38 | must be wrapped with its type indicator:
 39 | 
 40 | ### Basic Types
 41 | - **String**: `{"stringValue": "your string"}`
 42 | - **Integer**: `{"integerValue": "123"}` or `{"integerValue": 123}`
 43 | - **Double**: `{"doubleValue": 123.45}`
 44 | - **Boolean**: `{"booleanValue": true}`
 45 | - **Null**: `{"nullValue": null}`
 46 | - **Bytes**: `{"bytesValue": "base64EncodedString"}`
 47 | - **Timestamp**: `{"timestampValue": "2025-01-07T10:00:00Z"}` (RFC3339 format)
 48 | 
 49 | ### Complex Types
 50 | - **GeoPoint**: `{"geoPointValue": {"latitude": 34.052235, "longitude": -118.243683}}`
 51 | - **Array**: `{"arrayValue": {"values": [{"stringValue": "item1"}, {"integerValue": "2"}]}}`
 52 | - **Map**: `{"mapValue": {"fields": {"key1": {"stringValue": "value1"}, "key2": {"booleanValue": true}}}}`
 53 | - **Reference**: `{"referenceValue": "collection/document"}`
 54 | 
 55 | ## Examples
 56 | 
 57 | ### Basic Document Creation
 58 | 
 59 | ```yaml
 60 | tools:
 61 |   add-company-doc:
 62 |     kind: firestore-add-documents
 63 |     source: my-firestore
 64 |     description: Add a new company document
 65 | ```
 66 | 
 67 | Usage:
 68 | ```json
 69 | {
 70 |   "collectionPath": "companies",
 71 |   "documentData": {
 72 |     "name": {
 73 |       "stringValue": "Acme Corporation"
 74 |     },
 75 |     "establishmentDate": {
 76 |       "timestampValue": "2000-01-15T10:30:00Z"
 77 |     },
 78 |     "location": {
 79 |       "geoPointValue": {
 80 |         "latitude": 34.052235,
 81 |         "longitude": -118.243683
 82 |       }
 83 |     },
 84 |     "active": {
 85 |       "booleanValue": true
 86 |     },
 87 |     "employeeCount": {
 88 |       "integerValue": "1500"
 89 |     },
 90 |     "annualRevenue": {
 91 |       "doubleValue": 1234567.89
 92 |     }
 93 |   }
 94 | }
 95 | ```
 96 | 
 97 | ### With Nested Maps and Arrays
 98 | 
 99 | ```json
100 | {
101 |   "collectionPath": "companies",
102 |   "documentData": {
103 |     "name": {
104 |       "stringValue": "Tech Innovations Inc"
105 |     },
106 |     "contactInfo": {
107 |       "mapValue": {
108 |         "fields": {
109 |           "email": {
110 |             "stringValue": "[email protected]"
111 |           },
112 |           "phone": {
113 |             "stringValue": "+1-555-123-4567"
114 |           },
115 |           "address": {
116 |             "mapValue": {
117 |               "fields": {
118 |                 "street": {
119 |                   "stringValue": "123 Innovation Drive"
120 |                 },
121 |                 "city": {
122 |                   "stringValue": "San Francisco"
123 |                 },
124 |                 "state": {
125 |                   "stringValue": "CA"
126 |                 },
127 |                 "zipCode": {
128 |                   "stringValue": "94105"
129 |                 }
130 |               }
131 |             }
132 |           }
133 |         }
134 |       }
135 |     },
136 |     "products": {
137 |       "arrayValue": {
138 |         "values": [
139 |           {
140 |             "stringValue": "Product A"
141 |           },
142 |           {
143 |             "stringValue": "Product B"
144 |           },
145 |           {
146 |             "mapValue": {
147 |               "fields": {
148 |                 "productName": {
149 |                   "stringValue": "Product C Premium"
150 |                 },
151 |                 "version": {
152 |                   "integerValue": "3"
153 |                 },
154 |                 "features": {
155 |                   "arrayValue": {
156 |                     "values": [
157 |                       {
158 |                         "stringValue": "Advanced Analytics"
159 |                       },
160 |                       {
161 |                         "stringValue": "Real-time Sync"
162 |                       }
163 |                     ]
164 |                   }
165 |                 }
166 |               }
167 |             }
168 |           }
169 |         ]
170 |       }
171 |     }
172 |   },
173 |   "returnData": true
174 | }
175 | ```
176 | 
177 | ### Complete Example with All Data Types
178 | 
179 | ```json
180 | {
181 |   "collectionPath": "test-documents",
182 |   "documentData": {
183 |     "stringField": {
184 |       "stringValue": "Hello World"
185 |     },
186 |     "integerField": {
187 |       "integerValue": "42"
188 |     },
189 |     "doubleField": {
190 |       "doubleValue": 3.14159
191 |     },
192 |     "booleanField": {
193 |       "booleanValue": true
194 |     },
195 |     "nullField": {
196 |       "nullValue": null
197 |     },
198 |     "timestampField": {
199 |       "timestampValue": "2025-01-07T15:30:00Z"
200 |     },
201 |     "geoPointField": {
202 |       "geoPointValue": {
203 |         "latitude": 37.7749,
204 |         "longitude": -122.4194
205 |       }
206 |     },
207 |     "bytesField": {
208 |       "bytesValue": "SGVsbG8gV29ybGQh"
209 |     },
210 |     "arrayField": {
211 |       "arrayValue": {
212 |         "values": [
213 |           {
214 |             "stringValue": "item1"
215 |           },
216 |           {
217 |             "integerValue": "2"
218 |           },
219 |           {
220 |             "booleanValue": false
221 |           }
222 |         ]
223 |       }
224 |     },
225 |     "mapField": {
226 |       "mapValue": {
227 |         "fields": {
228 |           "nestedString": {
229 |             "stringValue": "nested value"
230 |           },
231 |           "nestedNumber": {
232 |             "doubleValue": 99.99
233 |           }
234 |         }
235 |       }
236 |     }
237 |   }
238 | }
239 | ```
240 | 
241 | ## Authentication
242 | 
243 | The tool can be configured to require authentication:
244 | 
245 | ```yaml
246 | tools:
247 |   secure-add-docs:
248 |     kind: firestore-add-documents
249 |     source: prod-firestore
250 |     description: Add documents with authentication required
251 |     authRequired:
252 |       - google-oauth
253 |       - api-key
254 | ```
255 | 
256 | ## Error Handling
257 | 
258 | Common errors include:
259 | 
260 | - Invalid collection path
261 | - Missing or invalid document data
262 | - Permission denied (if Firestore security rules block the operation)
263 | - Invalid data type conversions
264 | 
265 | ## Best Practices
266 | 
267 | 1. **Always use typed values**: Every field must be wrapped with its appropriate
268 |    type indicator (e.g., `{"stringValue": "text"}`)
269 | 2. **Integer values can be strings**: The tool accepts integer values as strings
270 |    (e.g., `{"integerValue": "1500"}`)
271 | 3. **Use returnData sparingly**: Only set to true when you need to verify the
272 |    exact data that was written
273 | 4. **Validate data before sending**: Ensure your data matches Firestore's native
274 |    JSON format
275 | 5. **Handle timestamps properly**: Use RFC3339 format for timestamp strings
276 | 6. **Base64 encode binary data**: Binary data must be base64 encoded in the
277 |    `bytesValue` field
278 | 7. **Consider security rules**: Ensure your Firestore security rules allow
279 |    document creation in the target collection
280 | 
281 | ## Related Tools
282 | 
283 | - [`firestore-get-documents`](firestore-get-documents.md) - Retrieve documents
284 |   by their paths
285 | - [`firestore-query-collection`](firestore-query-collection.md) - Query
286 |   documents in a collection
287 | - [`firestore-delete-documents`](firestore-delete-documents.md) - Delete
288 |   documents from Firestore
289 | 
```

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

```yaml
  1 | # Copyright 2025 Google LLC
  2 | #
  3 | # Licensed under the Apache License, Version 2.0 (the "License");
  4 | # you may not use this file except in compliance with the License.
  5 | # You may obtain a copy of the License at
  6 | #
  7 | #     http://www.apache.org/licenses/LICENSE-2.0
  8 | #
  9 | # Unless required by applicable law or agreed to in writing, software
 10 | # distributed under the License is distributed on an "AS IS" BASIS,
 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12 | # See the License for the specific language governing permissions and
 13 | # limitations under the License.
 14 | 
 15 | sources:
 16 |   oceanbase-source:
 17 |     kind: oceanbase
 18 |     host: ${OCEANBASE_HOST}
 19 |     port: ${OCEANBASE_PORT}
 20 |     database: ${OCEANBASE_DATABASE}
 21 |     user: ${OCEANBASE_USER}
 22 |     password: ${OCEANBASE_PASSWORD}
 23 | tools:
 24 |   execute_sql:
 25 |     kind: oceanbase-execute-sql
 26 |     source: oceanbase-source
 27 |     description: Use this tool to execute SQL.
 28 |   list_tables:
 29 |     kind: oceanbase-sql
 30 |     source: oceanbase-source
 31 |     description: "Lists detailed schema information (object type, columns, constraints, indexes, triggers, 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."
 32 |     statement: |
 33 |       SELECT
 34 |           T.TABLE_SCHEMA AS schema_name,
 35 |           T.TABLE_NAME AS object_name,
 36 |           CONVERT( JSON_OBJECT(
 37 |               'schema_name', T.TABLE_SCHEMA,
 38 |               'object_name', T.TABLE_NAME,
 39 |               'object_type', 'TABLE',
 40 |               'owner', (
 41 |                   SELECT
 42 |                       IFNULL(U.GRANTEE, 'N/A')
 43 |                   FROM
 44 |                       INFORMATION_SCHEMA.SCHEMA_PRIVILEGES U
 45 |                   WHERE
 46 |                       U.TABLE_SCHEMA = T.TABLE_SCHEMA
 47 |                   LIMIT 1
 48 |               ),
 49 |               'comment', IFNULL(T.TABLE_COMMENT, ''),
 50 |               'columns', (
 51 |                   SELECT
 52 |                       IFNULL(
 53 |                           JSON_ARRAYAGG(
 54 |                               JSON_OBJECT(
 55 |                                   'column_name', C.COLUMN_NAME,
 56 |                                   'data_type', C.COLUMN_TYPE,
 57 |                                   'ordinal_position', C.ORDINAL_POSITION,
 58 |                                   'is_not_nullable', IF(C.IS_NULLABLE = 'NO', TRUE, FALSE),
 59 |                                   'column_default', C.COLUMN_DEFAULT,
 60 |                                   'column_comment', IFNULL(C.COLUMN_COMMENT, '')
 61 |                               )
 62 |                           ),
 63 |                           JSON_ARRAY()
 64 |                       )
 65 |                   FROM
 66 |                       INFORMATION_SCHEMA.COLUMNS C
 67 |                   WHERE
 68 |                       C.TABLE_SCHEMA = T.TABLE_SCHEMA AND C.TABLE_NAME = T.TABLE_NAME
 69 |                   ORDER BY C.ORDINAL_POSITION
 70 |               ),
 71 |               'constraints', (
 72 |                   SELECT
 73 |                       IFNULL(
 74 |                           JSON_ARRAYAGG(
 75 |                               JSON_OBJECT(
 76 |                                   'constraint_name', TC.CONSTRAINT_NAME,
 77 |                                   'constraint_type',
 78 |                                       CASE TC.CONSTRAINT_TYPE
 79 |                                           WHEN 'PRIMARY KEY' THEN 'PRIMARY KEY'
 80 |                                           WHEN 'FOREIGN KEY' THEN 'FOREIGN KEY'
 81 |                                           WHEN 'UNIQUE' THEN 'UNIQUE'
 82 |                                           ELSE TC.CONSTRAINT_TYPE
 83 |                                       END,
 84 |                                   'constraint_definition', '',
 85 |                                   'constraint_columns', (
 86 |                                       SELECT
 87 |                                           IFNULL(JSON_ARRAYAGG(KCU.COLUMN_NAME), JSON_ARRAY())
 88 |                                       FROM
 89 |                                           INFORMATION_SCHEMA.KEY_COLUMN_USAGE KCU
 90 |                                       WHERE
 91 |                                           KCU.CONSTRAINT_SCHEMA = TC.CONSTRAINT_SCHEMA
 92 |                                           AND KCU.CONSTRAINT_NAME = TC.CONSTRAINT_NAME
 93 |                                           AND KCU.TABLE_NAME = TC.TABLE_NAME
 94 |                                       ORDER BY KCU.ORDINAL_POSITION
 95 |                                   ),
 96 |                                   'foreign_key_referenced_table', IF(TC.CONSTRAINT_TYPE = 'FOREIGN KEY', RC.REFERENCED_TABLE_NAME, NULL),
 97 |                                   'foreign_key_referenced_columns', IF(TC.CONSTRAINT_TYPE = 'FOREIGN KEY',
 98 |                                       (SELECT IFNULL(JSON_ARRAYAGG(FKCU.REFERENCED_COLUMN_NAME), JSON_ARRAY())
 99 |                                       FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE FKCU
100 |                                       WHERE FKCU.CONSTRAINT_SCHEMA = TC.CONSTRAINT_SCHEMA
101 |                                         AND FKCU.CONSTRAINT_NAME = TC.CONSTRAINT_NAME
102 |                                         AND FKCU.TABLE_NAME = TC.TABLE_NAME
103 |                                         AND FKCU.REFERENCED_TABLE_NAME IS NOT NULL
104 |                                       ORDER BY FKCU.ORDINAL_POSITION),
105 |                                       NULL
106 |                                   )
107 |                               )
108 |                           ),
109 |                           JSON_ARRAY()
110 |                       )
111 |                   FROM
112 |                       INFORMATION_SCHEMA.TABLE_CONSTRAINTS TC
113 |                   LEFT JOIN
114 |                       INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS RC
115 |                       ON TC.CONSTRAINT_SCHEMA = RC.CONSTRAINT_SCHEMA
116 |                       AND TC.CONSTRAINT_NAME = RC.CONSTRAINT_NAME
117 |                       AND TC.TABLE_NAME = RC.TABLE_NAME
118 |                   WHERE
119 |                       TC.TABLE_SCHEMA = T.TABLE_SCHEMA AND TC.TABLE_NAME = T.TABLE_NAME
120 |               ),
121 |               'indexes', (
122 |                   SELECT
123 |                       IFNULL(
124 |                           JSON_ARRAYAGG(
125 |                               JSON_OBJECT(
126 |                                   'index_name', IndexData.INDEX_NAME,
127 |                                   'is_unique', IF(IndexData.NON_UNIQUE = 0, TRUE, FALSE),
128 |                                   'is_primary', IF(IndexData.INDEX_NAME = 'PRIMARY', TRUE, FALSE),
129 |                                   'index_columns', IFNULL(IndexData.INDEX_COLUMNS_ARRAY, JSON_ARRAY())
130 |                               )
131 |                           ),
132 |                           JSON_ARRAY()
133 |                       )
134 |                   FROM (
135 |                       SELECT
136 |                           S.TABLE_SCHEMA,
137 |                           S.TABLE_NAME,
138 |                           S.INDEX_NAME,
139 |                           MIN(S.NON_UNIQUE) AS NON_UNIQUE, -- Aggregate NON_UNIQUE here to get unique status for the index
140 |                           JSON_ARRAYAGG(S.COLUMN_NAME) AS INDEX_COLUMNS_ARRAY -- Aggregate columns into an array for this index
141 |                       FROM
142 |                           INFORMATION_SCHEMA.STATISTICS S
143 |                       WHERE
144 |                           S.TABLE_SCHEMA = T.TABLE_SCHEMA AND S.TABLE_NAME = T.TABLE_NAME
145 |                       GROUP BY
146 |                           S.TABLE_SCHEMA, S.TABLE_NAME, S.INDEX_NAME
147 |                   ) AS IndexData
148 |                   ORDER BY IndexData.INDEX_NAME
149 |               ),
150 |               'triggers', (
151 |                   SELECT
152 |                       IFNULL(
153 |                           JSON_ARRAYAGG(
154 |                               JSON_OBJECT(
155 |                                   'trigger_name', TR.TRIGGER_NAME,
156 |                                   'trigger_definition', TR.ACTION_STATEMENT
157 |                               )
158 |                           ),
159 |                           JSON_ARRAY()
160 |                       )
161 |                   FROM
162 |                       INFORMATION_SCHEMA.TRIGGERS TR
163 |                   WHERE
164 |                       TR.EVENT_OBJECT_SCHEMA = T.TABLE_SCHEMA AND TR.EVENT_OBJECT_TABLE = T.TABLE_NAME
165 |                   ORDER BY TR.TRIGGER_NAME
166 |               )
167 |           ) USING utf8mb4) AS object_details
168 |       FROM
169 |           INFORMATION_SCHEMA.TABLES T
170 |       WHERE
171 |           T.TABLE_SCHEMA NOT IN ('mysql', 'information_schema', 'performance_schema', 'sys')
172 |           AND (NULLIF(TRIM(?), '') IS NULL OR FIND_IN_SET(T.TABLE_NAME, ?))
173 |           AND T.TABLE_TYPE = 'BASE TABLE'
174 |       ORDER BY
175 |           T.TABLE_SCHEMA, T.TABLE_NAME;
176 |     parameters:
177 |       - name: table_names
178 |         type: string
179 |         description: "Optional: A comma-separated list of table names. If empty, details for all tables in user-accessible schemas will be listed."
180 | toolsets:
181 |   oceanbase_database_tools:
182 |     - execute_sql
183 |     - list_tables
184 | 
```

--------------------------------------------------------------------------------
/docs/en/samples/bigquery/mcp_quickstart/_index.md:
--------------------------------------------------------------------------------

```markdown
  1 | ---
  2 | title: "Quickstart (MCP with BigQuery)"
  3 | type: docs
  4 | weight: 2
  5 | description: >
  6 |   How to get started running Toolbox with MCP Inspector and BigQuery as the source.
  7 | ---
  8 | 
  9 | ## Overview
 10 | 
 11 | [Model Context Protocol](https://modelcontextprotocol.io) is an open protocol
 12 | that standardizes how applications provide context to LLMs. Check out this page
 13 | on how to [connect to Toolbox via MCP](../../../how-to/connect_via_mcp.md).
 14 | 
 15 | ## Step 1: Set up your BigQuery Dataset and Table
 16 | 
 17 | In this section, we will create a BigQuery dataset and a table, then insert some
 18 | data that needs to be accessed by our agent.
 19 | 
 20 | 1. Create a new BigQuery dataset (replace `YOUR_DATASET_NAME` with your desired
 21 |    dataset name, e.g., `toolbox_mcp_ds`, and optionally specify a location like
 22 |    `US` or `EU`):
 23 | 
 24 |     ```bash
 25 |     export BQ_DATASET_NAME="YOUR_DATASET_NAME"
 26 |     export BQ_LOCATION="US"
 27 | 
 28 |     bq --location=$BQ_LOCATION mk $BQ_DATASET_NAME
 29 |     ```
 30 | 
 31 |     You can also do this through the [Google Cloud
 32 |     Console](https://console.cloud.google.com/bigquery).
 33 | 
 34 | 1. The `hotels` table needs to be defined in your new dataset. First, create a
 35 |    file named `create_hotels_table.sql` with the following content:
 36 | 
 37 |     ```sql
 38 |     CREATE TABLE IF NOT EXISTS `YOUR_PROJECT_ID.YOUR_DATASET_NAME.hotels` (
 39 |       id            INT64 NOT NULL,
 40 |       name          STRING NOT NULL,
 41 |       location      STRING NOT NULL,
 42 |       price_tier    STRING NOT NULL,
 43 |       checkin_date  DATE NOT NULL,
 44 |       checkout_date DATE NOT NULL,
 45 |       booked        BOOLEAN NOT NULL
 46 |     );
 47 |     ```
 48 | 
 49 |     > **Note:** Replace `YOUR_PROJECT_ID` and `YOUR_DATASET_NAME` in the SQL
 50 |     > with your actual project ID and dataset name.
 51 | 
 52 |     Then run the command below to execute the sql query:
 53 | 
 54 |     ```bash
 55 |     bq query --project_id=$GOOGLE_CLOUD_PROJECT --dataset_id=$BQ_DATASET_NAME --use_legacy_sql=false < create_hotels_table.sql
 56 |     ```
 57 | 
 58 | 1. .  Next, populate the hotels table with some initial data. To do this, create
 59 |    a file named `insert_hotels_data.sql` and add the following SQL INSERT
 60 |    statement to it.
 61 | 
 62 |     ```sql
 63 |     INSERT INTO `YOUR_PROJECT_ID.YOUR_DATASET_NAME.hotels` (id, name, location, price_tier, checkin_date, checkout_date, booked)
 64 |     VALUES
 65 |       (1, 'Hilton Basel', 'Basel', 'Luxury', '2024-04-20', '2024-04-22', FALSE),
 66 |       (2, 'Marriott Zurich', 'Zurich', 'Upscale', '2024-04-14', '2024-04-21', FALSE),
 67 |       (3, 'Hyatt Regency Basel', 'Basel', 'Upper Upscale', '2024-04-02', '2024-04-20', FALSE),
 68 |       (4, 'Radisson Blu Lucerne', 'Lucerne', 'Midscale', '2024-04-05', '2024-04-24', FALSE),
 69 |       (5, 'Best Western Bern', 'Bern', 'Upper Midscale', '2024-04-01', '2024-04-23', FALSE),
 70 |       (6, 'InterContinental Geneva', 'Geneva', 'Luxury', '2024-04-23', '2024-04-28', FALSE),
 71 |       (7, 'Sheraton Zurich', 'Zurich', 'Upper Upscale', '2024-04-02', '2024-04-27', FALSE),
 72 |       (8, 'Holiday Inn Basel', 'Basel', 'Upper Midscale', '2024-04-09', '2024-04-24', FALSE),
 73 |       (9, 'Courtyard Zurich', 'Zurich', 'Upscale', '2024-04-03', '2024-04-13', FALSE),
 74 |       (10, 'Comfort Inn Bern', 'Bern', 'Midscale', '2024-04-04', '2024-04-16', FALSE);
 75 |     ```
 76 | 
 77 |     > **Note:** Replace `YOUR_PROJECT_ID` and `YOUR_DATASET_NAME` in the SQL
 78 |     > with your actual project ID and dataset name.
 79 | 
 80 |     Then run the command below to execute the sql query:
 81 | 
 82 |     ```bash
 83 |     bq query --project_id=$GOOGLE_CLOUD_PROJECT --dataset_id=$BQ_DATASET_NAME --use_legacy_sql=false < insert_hotels_data.sql
 84 |     ```
 85 | 
 86 | ## Step 2: Install and configure Toolbox
 87 | 
 88 | In this section, we will download Toolbox, configure our tools in a
 89 | `tools.yaml`, and then run the Toolbox server.
 90 | 
 91 | 1. Download the latest version of Toolbox as a binary:
 92 | 
 93 |     {{< notice tip >}}
 94 |    Select the
 95 |    [correct binary](https://github.com/googleapis/genai-toolbox/releases)
 96 |    corresponding to your OS and CPU architecture.
 97 |     {{< /notice >}}
 98 |     <!-- {x-release-please-start-version} -->
 99 |     ```bash
100 |     export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
101 |     curl -O https://storage.googleapis.com/genai-toolbox/v0.18.0/$OS/toolbox
102 |     ```
103 |     <!-- {x-release-please-end} -->
104 | 
105 | 1. Make the binary executable:
106 | 
107 |     ```bash
108 |     chmod +x toolbox
109 |     ```
110 | 
111 | 1. Write the following into a `tools.yaml` file. You must replace the
112 |    `YOUR_PROJECT_ID` and `YOUR_DATASET_NAME` placeholder in the config with your
113 |    actual BigQuery project and dataset name. The `location` field is optional;
114 |    if not specified, it defaults to 'us'. The table name `hotels` is used
115 |    directly in the statements.
116 | 
117 |     {{< notice tip >}}
118 |   Authentication with BigQuery is handled via Application Default Credentials
119 |   (ADC). Ensure you have run `gcloud auth application-default login`.
120 |     {{< /notice >}}
121 | 
122 |     ```yaml
123 |     sources:
124 |       my-bigquery-source:
125 |         kind: bigquery
126 |         project: YOUR_PROJECT_ID
127 |         location: us
128 |     tools:
129 |       search-hotels-by-name:
130 |         kind: bigquery-sql
131 |         source: my-bigquery-source
132 |         description: Search for hotels based on name.
133 |         parameters:
134 |           - name: name
135 |             type: string
136 |             description: The name of the hotel.
137 |         statement: SELECT * FROM `YOUR_DATASET_NAME.hotels` WHERE LOWER(name) LIKE LOWER(CONCAT('%', @name, '%'));
138 |       search-hotels-by-location:
139 |         kind: bigquery-sql
140 |         source: my-bigquery-source
141 |         description: Search for hotels based on location.
142 |         parameters:
143 |           - name: location
144 |             type: string
145 |             description: The location of the hotel.
146 |         statement: SELECT * FROM `YOUR_DATASET_NAME.hotels` WHERE LOWER(location) LIKE LOWER(CONCAT('%', @location, '%'));
147 |       book-hotel:
148 |         kind: bigquery-sql
149 |         source: my-bigquery-source
150 |         description: >-
151 |            Book a hotel by its ID. If the hotel is successfully booked, returns a NULL, raises an error if not.
152 |         parameters:
153 |           - name: hotel_id
154 |             type: integer
155 |             description: The ID of the hotel to book.
156 |         statement: UPDATE `YOUR_DATASET_NAME.hotels` SET booked = TRUE WHERE id = @hotel_id;
157 |       update-hotel:
158 |         kind: bigquery-sql
159 |         source: my-bigquery-source
160 |         description: >-
161 |           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.
162 |         parameters:
163 |           - name: checkin_date
164 |             type: string
165 |             description: The new check-in date of the hotel.
166 |           - name: checkout_date
167 |             type: string
168 |             description: The new check-out date of the hotel.
169 |           - name: hotel_id
170 |             type: integer
171 |             description: The ID of the hotel to update.
172 |         statement: >-
173 |           UPDATE `YOUR_DATASET_NAME.hotels` SET checkin_date = PARSE_DATE('%Y-%m-%d', @checkin_date), checkout_date = PARSE_DATE('%Y-%m-%d', @checkout_date) WHERE id = @hotel_id;
174 |       cancel-hotel:
175 |         kind: bigquery-sql
176 |         source: my-bigquery-source
177 |         description: Cancel a hotel by its ID.
178 |         parameters:
179 |           - name: hotel_id
180 |             type: integer
181 |             description: The ID of the hotel to cancel.
182 |         statement: UPDATE `YOUR_DATASET_NAME.hotels` SET booked = FALSE WHERE id = @hotel_id;
183 |     toolsets:
184 |       my-toolset:
185 |         - search-hotels-by-name
186 |         - search-hotels-by-location
187 |         - book-hotel
188 |         - update-hotel
189 |         - cancel-hotel
190 |     ```
191 | 
192 |     For more info on tools, check out the
193 |     [Tools](../../../resources/tools/) section.
194 | 
195 | 1. Run the Toolbox server, pointing to the `tools.yaml` file created earlier:
196 | 
197 |     ```bash
198 |     ./toolbox --tools-file "tools.yaml"
199 |     ```
200 | 
201 | ## Step 3: Connect to MCP Inspector
202 | 
203 | 1. Run the MCP Inspector:
204 | 
205 |     ```bash
206 |     npx @modelcontextprotocol/inspector
207 |     ```
208 | 
209 | 1. Type `y` when it asks to install the inspector package.
210 | 
211 | 1. It should show the following when the MCP Inspector is up and running (please
212 |    take note of `<YOUR_SESSION_TOKEN>`):
213 | 
214 |     ```bash
215 |     Starting MCP inspector...
216 |     ⚙️ Proxy server listening on localhost:6277
217 |     🔑 Session token: <YOUR_SESSION_TOKEN>
218 |        Use this token to authenticate requests or set DANGEROUSLY_OMIT_AUTH=true to disable auth
219 | 
220 |     🚀 MCP Inspector is up and running at:
221 |        http://localhost:6274/?MCP_PROXY_AUTH_TOKEN=<YOUR_SESSION_TOKEN>
222 |     ```
223 | 
224 | 1. Open the above link in your browser.
225 | 
226 | 1. For `Transport Type`, select `Streamable HTTP`.
227 | 
228 | 1. For `URL`, type in `http://127.0.0.1:5000/mcp`.
229 | 
230 | 1. For `Configuration` -> `Proxy Session Token`, make sure
231 |    `<YOUR_SESSION_TOKEN>` is present.
232 | 
233 | 1. Click Connect.
234 | 
235 |     ![inspector](./inspector.png)
236 | 
237 | 1. Select `List Tools`, you will see a list of tools configured in `tools.yaml`.
238 | 
239 |     ![inspector_tools](./inspector_tools.png)
240 | 
241 | 1. Test out your tools here!
242 | 
```

--------------------------------------------------------------------------------
/internal/tools/sqlite/sqlitesql/sqlitesql_test.go:
--------------------------------------------------------------------------------

```go
  1 | // Copyright 2025 Google LLC
  2 | //
  3 | // Licensed under the Apache License, Version 2.0 (the "License");
  4 | // you may not use this file except in compliance with the License.
  5 | // You may obtain a copy of the License at
  6 | //
  7 | //     http://www.apache.org/licenses/LICENSE-2.0
  8 | //
  9 | // Unless required by applicable law or agreed to in writing, software
 10 | // distributed under the License is distributed on an "AS IS" BASIS,
 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12 | // See the License for the specific language governing permissions and
 13 | // limitations under the License.
 14 | 
 15 | package sqlitesql_test
 16 | 
 17 | import (
 18 | 	"context"
 19 | 	"database/sql"
 20 | 	"reflect"
 21 | 	"testing"
 22 | 
 23 | 	yaml "github.com/goccy/go-yaml"
 24 | 	"github.com/google/go-cmp/cmp"
 25 | 	"github.com/googleapis/genai-toolbox/internal/server"
 26 | 	"github.com/googleapis/genai-toolbox/internal/testutils"
 27 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 28 | 	"github.com/googleapis/genai-toolbox/internal/tools/sqlite/sqlitesql"
 29 | 	_ "modernc.org/sqlite"
 30 | )
 31 | 
 32 | func TestParseFromYamlSQLite(t *testing.T) {
 33 | 	ctx, err := testutils.ContextWithNewLogger()
 34 | 	if err != nil {
 35 | 		t.Fatalf("unexpected error: %s", err)
 36 | 	}
 37 | 	tcs := []struct {
 38 | 		desc string
 39 | 		in   string
 40 | 		want server.ToolConfigs
 41 | 	}{
 42 | 		{
 43 | 			desc: "basic example",
 44 | 			in: `
 45 | 			tools:
 46 | 				example_tool:
 47 | 					kind: sqlite-sql
 48 | 					source: my-sqlite-instance
 49 | 					description: some description
 50 | 					statement: |
 51 | 						SELECT * FROM SQL_STATEMENT;
 52 | 					authRequired:
 53 | 						- my-google-auth-service
 54 | 						- other-auth-service
 55 | 					parameters:
 56 | 						- name: country
 57 | 						  type: string
 58 | 						  description: some description
 59 | 						  authServices:
 60 | 							- name: my-google-auth-service
 61 | 							  field: user_id
 62 | 							- name: other-auth-service
 63 | 							  field: user_id
 64 | 			`,
 65 | 			want: server.ToolConfigs{
 66 | 				"example_tool": sqlitesql.Config{
 67 | 					Name:         "example_tool",
 68 | 					Kind:         "sqlite-sql",
 69 | 					Source:       "my-sqlite-instance",
 70 | 					Description:  "some description",
 71 | 					Statement:    "SELECT * FROM SQL_STATEMENT;\n",
 72 | 					AuthRequired: []string{"my-google-auth-service", "other-auth-service"},
 73 | 					Parameters: []tools.Parameter{
 74 | 						tools.NewStringParameterWithAuth("country", "some description",
 75 | 							[]tools.ParamAuthService{{Name: "my-google-auth-service", Field: "user_id"},
 76 | 								{Name: "other-auth-service", Field: "user_id"}}),
 77 | 					},
 78 | 				},
 79 | 			},
 80 | 		},
 81 | 	}
 82 | 	for _, tc := range tcs {
 83 | 		t.Run(tc.desc, func(t *testing.T) {
 84 | 			got := struct {
 85 | 				Tools server.ToolConfigs `yaml:"tools"`
 86 | 			}{}
 87 | 			// Parse contents
 88 | 			err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
 89 | 			if err != nil {
 90 | 				t.Fatalf("unable to unmarshal: %s", err)
 91 | 			}
 92 | 			if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
 93 | 				t.Fatalf("incorrect parse: diff %v", diff)
 94 | 			}
 95 | 		})
 96 | 	}
 97 | 
 98 | }
 99 | 
100 | func TestParseFromYamlWithTemplateSqlite(t *testing.T) {
101 | 	ctx, err := testutils.ContextWithNewLogger()
102 | 	if err != nil {
103 | 		t.Fatalf("unexpected error: %s", err)
104 | 	}
105 | 	tcs := []struct {
106 | 		desc string
107 | 		in   string
108 | 		want server.ToolConfigs
109 | 	}{
110 | 		{
111 | 			desc: "basic example",
112 | 			in: `
113 | 			tools:
114 | 				example_tool:
115 | 					kind: sqlite-sql
116 | 					source: my-sqlite-db
117 | 					description: some description
118 | 					statement: |
119 | 						SELECT * FROM SQL_STATEMENT;
120 | 					authRequired:
121 | 						- my-google-auth-service
122 | 						- other-auth-service
123 | 					parameters:
124 | 						- name: country
125 | 						  type: string
126 | 						  description: some description
127 | 						  authServices:
128 | 							- name: my-google-auth-service
129 | 							  field: user_id
130 | 							- name: other-auth-service
131 | 							  field: user_id
132 | 					templateParameters:
133 | 						- name: tableName
134 | 						  type: string
135 | 						  description: The table to select hotels from.
136 | 						- name: fieldArray
137 | 						  type: array
138 | 						  description: The columns to return for the query.
139 | 						  items: 
140 | 								name: column
141 | 								type: string
142 | 								description: A column name that will be returned from the query.
143 | 			`,
144 | 			want: server.ToolConfigs{
145 | 				"example_tool": sqlitesql.Config{
146 | 					Name:         "example_tool",
147 | 					Kind:         "sqlite-sql",
148 | 					Source:       "my-sqlite-db",
149 | 					Description:  "some description",
150 | 					Statement:    "SELECT * FROM SQL_STATEMENT;\n",
151 | 					AuthRequired: []string{"my-google-auth-service", "other-auth-service"},
152 | 					Parameters: []tools.Parameter{
153 | 						tools.NewStringParameterWithAuth("country", "some description",
154 | 							[]tools.ParamAuthService{{Name: "my-google-auth-service", Field: "user_id"},
155 | 								{Name: "other-auth-service", Field: "user_id"}}),
156 | 					},
157 | 					TemplateParameters: []tools.Parameter{
158 | 						tools.NewStringParameter("tableName", "The table to select hotels from."),
159 | 						tools.NewArrayParameter("fieldArray", "The columns to return for the query.", tools.NewStringParameter("column", "A column name that will be returned from the query.")),
160 | 					},
161 | 				},
162 | 			},
163 | 		},
164 | 	}
165 | 	for _, tc := range tcs {
166 | 		t.Run(tc.desc, func(t *testing.T) {
167 | 			got := struct {
168 | 				Tools server.ToolConfigs `yaml:"tools"`
169 | 			}{}
170 | 			// Parse contents
171 | 			err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
172 | 			if err != nil {
173 | 				t.Fatalf("unable to unmarshal: %s", err)
174 | 			}
175 | 			if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
176 | 				t.Fatalf("incorrect parse: diff %v", diff)
177 | 			}
178 | 		})
179 | 	}
180 | }
181 | 
182 | func setupTestDB(t *testing.T) *sql.DB {
183 | 	db, err := sql.Open("sqlite", ":memory:")
184 | 	if err != nil {
185 | 		t.Fatalf("Failed to open in-memory database: %v", err)
186 | 	}
187 | 
188 | 	createTable := `
189 | 	CREATE TABLE users (
190 | 		id INTEGER PRIMARY KEY,
191 | 		name TEXT,
192 | 		age INTEGER
193 | 	);`
194 | 	if _, err := db.Exec(createTable); err != nil {
195 | 		t.Fatalf("Failed to create table: %v", err)
196 | 	}
197 | 
198 | 	insertData := `
199 | 	INSERT INTO users (id, name, age) VALUES
200 | 	(1, 'Alice', 30),
201 | 	(2, 'Bob', 25);`
202 | 	if _, err := db.Exec(insertData); err != nil {
203 | 		t.Fatalf("Failed to insert data: %v", err)
204 | 	}
205 | 
206 | 	return db
207 | }
208 | 
209 | func TestTool_Invoke(t *testing.T) {
210 | 	type fields struct {
211 | 		Name               string
212 | 		Kind               string
213 | 		AuthRequired       []string
214 | 		Parameters         tools.Parameters
215 | 		TemplateParameters tools.Parameters
216 | 		AllParams          tools.Parameters
217 | 		Db                 *sql.DB
218 | 		Statement          string
219 | 	}
220 | 	type args struct {
221 | 		ctx         context.Context
222 | 		params      tools.ParamValues
223 | 		accessToken tools.AccessToken
224 | 	}
225 | 	tests := []struct {
226 | 		name    string
227 | 		fields  fields
228 | 		args    args
229 | 		want    any
230 | 		wantErr bool
231 | 	}{
232 | 		{
233 | 			name: "simple select",
234 | 			fields: fields{
235 | 				Db:        setupTestDB(t),
236 | 				Statement: "SELECT * FROM users",
237 | 			},
238 | 			args: args{
239 | 				ctx: context.Background(),
240 | 			},
241 | 			want: []any{
242 | 				map[string]any{"id": int64(1), "name": "Alice", "age": int64(30)},
243 | 				map[string]any{"id": int64(2), "name": "Bob", "age": int64(25)},
244 | 			},
245 | 			wantErr: false,
246 | 		},
247 | 		{
248 | 			name: "select with parameter",
249 | 			fields: fields{
250 | 				Db:        setupTestDB(t),
251 | 				Statement: "SELECT * FROM users WHERE name = ?",
252 | 				Parameters: []tools.Parameter{
253 | 					tools.NewStringParameter("name", "user name"),
254 | 				},
255 | 			},
256 | 			args: args{
257 | 				ctx: context.Background(),
258 | 				params: []tools.ParamValue{
259 | 					{Name: "name", Value: "Alice"},
260 | 				},
261 | 			},
262 | 			want: []any{
263 | 				map[string]any{"id": int64(1), "name": "Alice", "age": int64(30)},
264 | 			},
265 | 			wantErr: false,
266 | 		},
267 | 		{
268 | 			name: "select with template parameter",
269 | 			fields: fields{
270 | 				Db:        setupTestDB(t),
271 | 				Statement: "SELECT * FROM {{.tableName}}",
272 | 				TemplateParameters: []tools.Parameter{
273 | 					tools.NewStringParameter("tableName", "table name"),
274 | 				},
275 | 			},
276 | 			args: args{
277 | 				ctx: context.Background(),
278 | 				params: []tools.ParamValue{
279 | 					{Name: "tableName", Value: "users"},
280 | 				},
281 | 			},
282 | 			want: []any{
283 | 				map[string]any{"id": int64(1), "name": "Alice", "age": int64(30)},
284 | 				map[string]any{"id": int64(2), "name": "Bob", "age": int64(25)},
285 | 			},
286 | 			wantErr: false,
287 | 		},
288 | 		{
289 | 			name: "invalid sql",
290 | 			fields: fields{
291 | 				Db:        setupTestDB(t),
292 | 				Statement: "SELECT * FROM non_existent_table",
293 | 			},
294 | 			args: args{
295 | 				ctx: context.Background(),
296 | 			},
297 | 			want:    nil,
298 | 			wantErr: true,
299 | 		},
300 | 	}
301 | 	for _, tt := range tests {
302 | 		t.Run(tt.name, func(t *testing.T) {
303 | 			tr := sqlitesql.Tool{
304 | 				Name:               tt.fields.Name,
305 | 				Kind:               tt.fields.Kind,
306 | 				AuthRequired:       tt.fields.AuthRequired,
307 | 				Parameters:         tt.fields.Parameters,
308 | 				TemplateParameters: tt.fields.TemplateParameters,
309 | 				AllParams:          tt.fields.AllParams,
310 | 				Db:                 tt.fields.Db,
311 | 				Statement:          tt.fields.Statement,
312 | 			}
313 | 			got, err := tr.Invoke(tt.args.ctx, tt.args.params, tt.args.accessToken)
314 | 			if (err != nil) != tt.wantErr {
315 | 				t.Errorf("Tool.Invoke() error = %v, wantErr %v", err, tt.wantErr)
316 | 				return
317 | 			}
318 | 			if !reflect.DeepEqual(got, tt.want) {
319 | 				t.Errorf("Tool.Invoke() = %v, want %v", got, tt.want)
320 | 			}
321 | 		})
322 | 	}
323 | }
324 | 
```
Page 24/48FirstPrevNextLast