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

# Directory Structure

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

# Files

--------------------------------------------------------------------------------
/internal/tools/mssql/mssqlsql/mssqlsql_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 mssqlsql_test
 16 | 
 17 | import (
 18 | 	"testing"
 19 | 
 20 | 	yaml "github.com/goccy/go-yaml"
 21 | 	"github.com/google/go-cmp/cmp"
 22 | 	"github.com/googleapis/genai-toolbox/internal/server"
 23 | 	"github.com/googleapis/genai-toolbox/internal/testutils"
 24 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 25 | 	"github.com/googleapis/genai-toolbox/internal/tools/mssql/mssqlsql"
 26 | )
 27 | 
 28 | func TestParseFromYamlMssql(t *testing.T) {
 29 | 	ctx, err := testutils.ContextWithNewLogger()
 30 | 	if err != nil {
 31 | 		t.Fatalf("unexpected error: %s", err)
 32 | 	}
 33 | 	tcs := []struct {
 34 | 		desc string
 35 | 		in   string
 36 | 		want server.ToolConfigs
 37 | 	}{
 38 | 		{
 39 | 			desc: "basic example",
 40 | 			in: `
 41 | 			tools:
 42 | 				example_tool:
 43 | 					kind: mssql-sql
 44 | 					source: my-instance
 45 | 					description: some description
 46 | 					statement: |
 47 | 						SELECT * FROM SQL_STATEMENT;
 48 | 					authRequired:
 49 | 						- my-google-auth-service
 50 | 						- other-auth-service
 51 | 					parameters:
 52 | 						- name: country
 53 | 						  type: string
 54 | 						  description: some description
 55 | 						  authServices:
 56 | 							- name: my-google-auth-service
 57 | 							  field: user_id
 58 | 							- name: other-auth-service
 59 | 							  field: user_id
 60 | 			`,
 61 | 			want: server.ToolConfigs{
 62 | 				"example_tool": mssqlsql.Config{
 63 | 					Name:         "example_tool",
 64 | 					Kind:         "mssql-sql",
 65 | 					Source:       "my-instance",
 66 | 					Description:  "some description",
 67 | 					Statement:    "SELECT * FROM SQL_STATEMENT;\n",
 68 | 					AuthRequired: []string{"my-google-auth-service", "other-auth-service"},
 69 | 					Parameters: []tools.Parameter{
 70 | 						tools.NewStringParameterWithAuth("country", "some description",
 71 | 							[]tools.ParamAuthService{{Name: "my-google-auth-service", Field: "user_id"},
 72 | 								{Name: "other-auth-service", Field: "user_id"}}),
 73 | 					},
 74 | 				},
 75 | 			},
 76 | 		},
 77 | 	}
 78 | 	for _, tc := range tcs {
 79 | 		t.Run(tc.desc, func(t *testing.T) {
 80 | 			got := struct {
 81 | 				Tools server.ToolConfigs `yaml:"tools"`
 82 | 			}{}
 83 | 			// Parse contents
 84 | 			err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
 85 | 			if err != nil {
 86 | 				t.Fatalf("unable to unmarshal: %s", err)
 87 | 			}
 88 | 			if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
 89 | 				t.Fatalf("incorrect parse: diff %v", diff)
 90 | 			}
 91 | 		})
 92 | 	}
 93 | }
 94 | 
 95 | func TestParseFromYamlWithTemplateMssql(t *testing.T) {
 96 | 	ctx, err := testutils.ContextWithNewLogger()
 97 | 	if err != nil {
 98 | 		t.Fatalf("unexpected error: %s", err)
 99 | 	}
100 | 	tcs := []struct {
101 | 		desc string
102 | 		in   string
103 | 		want server.ToolConfigs
104 | 	}{
105 | 		{
106 | 			desc: "basic example",
107 | 			in: `
108 | 			tools:
109 | 				example_tool:
110 | 					kind: mssql-sql
111 | 					source: my-instance
112 | 					description: some description
113 | 					statement: |
114 | 						SELECT * FROM SQL_STATEMENT;
115 | 					authRequired:
116 | 						- my-google-auth-service
117 | 						- other-auth-service
118 | 					parameters:
119 | 						- name: country
120 | 						  type: string
121 | 						  description: some description
122 | 						  authServices:
123 | 							- name: my-google-auth-service
124 | 							  field: user_id
125 | 							- name: other-auth-service
126 | 							  field: user_id
127 | 					templateParameters:
128 | 						- name: tableName
129 | 						  type: string
130 | 						  description: The table to select hotels from.
131 | 						- name: fieldArray
132 | 						  type: array
133 | 						  description: The columns to return for the query.
134 | 						  items: 
135 | 								name: column
136 | 								type: string
137 | 								description: A column name that will be returned from the query.
138 | 			`,
139 | 			want: server.ToolConfigs{
140 | 				"example_tool": mssqlsql.Config{
141 | 					Name:         "example_tool",
142 | 					Kind:         "mssql-sql",
143 | 					Source:       "my-instance",
144 | 					Description:  "some description",
145 | 					Statement:    "SELECT * FROM SQL_STATEMENT;\n",
146 | 					AuthRequired: []string{"my-google-auth-service", "other-auth-service"},
147 | 					Parameters: []tools.Parameter{
148 | 						tools.NewStringParameterWithAuth("country", "some description",
149 | 							[]tools.ParamAuthService{{Name: "my-google-auth-service", Field: "user_id"},
150 | 								{Name: "other-auth-service", Field: "user_id"}}),
151 | 					},
152 | 					TemplateParameters: []tools.Parameter{
153 | 						tools.NewStringParameter("tableName", "The table to select hotels from."),
154 | 						tools.NewArrayParameter("fieldArray", "The columns to return for the query.", tools.NewStringParameter("column", "A column name that will be returned from the query.")),
155 | 					},
156 | 				},
157 | 			},
158 | 		},
159 | 	}
160 | 	for _, tc := range tcs {
161 | 		t.Run(tc.desc, func(t *testing.T) {
162 | 			got := struct {
163 | 				Tools server.ToolConfigs `yaml:"tools"`
164 | 			}{}
165 | 			// Parse contents
166 | 			err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
167 | 			if err != nil {
168 | 				t.Fatalf("unable to unmarshal: %s", err)
169 | 			}
170 | 			if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
171 | 				t.Fatalf("incorrect parse: diff %v", diff)
172 | 			}
173 | 		})
174 | 	}
175 | }
176 | 
```

--------------------------------------------------------------------------------
/tests/mysql/mysql_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 mysql
 16 | 
 17 | import (
 18 | 	"context"
 19 | 	"database/sql"
 20 | 	"fmt"
 21 | 	"os"
 22 | 	"regexp"
 23 | 	"strings"
 24 | 	"testing"
 25 | 	"time"
 26 | 
 27 | 	"github.com/google/uuid"
 28 | 	"github.com/googleapis/genai-toolbox/internal/testutils"
 29 | 	"github.com/googleapis/genai-toolbox/tests"
 30 | )
 31 | 
 32 | var (
 33 | 	MySQLSourceKind = "mysql"
 34 | 	MySQLToolKind   = "mysql-sql"
 35 | 	MySQLDatabase   = os.Getenv("MYSQL_DATABASE")
 36 | 	MySQLHost       = os.Getenv("MYSQL_HOST")
 37 | 	MySQLPort       = os.Getenv("MYSQL_PORT")
 38 | 	MySQLUser       = os.Getenv("MYSQL_USER")
 39 | 	MySQLPass       = os.Getenv("MYSQL_PASS")
 40 | )
 41 | 
 42 | func getMySQLVars(t *testing.T) map[string]any {
 43 | 	switch "" {
 44 | 	case MySQLDatabase:
 45 | 		t.Fatal("'MYSQL_DATABASE' not set")
 46 | 	case MySQLHost:
 47 | 		t.Fatal("'MYSQL_HOST' not set")
 48 | 	case MySQLPort:
 49 | 		t.Fatal("'MYSQL_PORT' not set")
 50 | 	case MySQLUser:
 51 | 		t.Fatal("'MYSQL_USER' not set")
 52 | 	case MySQLPass:
 53 | 		t.Fatal("'MYSQL_PASS' not set")
 54 | 	}
 55 | 
 56 | 	return map[string]any{
 57 | 		"kind":     MySQLSourceKind,
 58 | 		"host":     MySQLHost,
 59 | 		"port":     MySQLPort,
 60 | 		"database": MySQLDatabase,
 61 | 		"user":     MySQLUser,
 62 | 		"password": MySQLPass,
 63 | 	}
 64 | }
 65 | 
 66 | // Copied over from mysql.go
 67 | func initMySQLConnectionPool(host, port, user, pass, dbname string) (*sql.DB, error) {
 68 | 	dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?parseTime=true", user, pass, host, port, dbname)
 69 | 
 70 | 	// Interact with the driver directly as you normally would
 71 | 	pool, err := sql.Open("mysql", dsn)
 72 | 	if err != nil {
 73 | 		return nil, fmt.Errorf("sql.Open: %w", err)
 74 | 	}
 75 | 	return pool, nil
 76 | }
 77 | 
 78 | func TestMySQLToolEndpoints(t *testing.T) {
 79 | 	sourceConfig := getMySQLVars(t)
 80 | 	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
 81 | 	defer cancel()
 82 | 
 83 | 	var args []string
 84 | 
 85 | 	pool, err := initMySQLConnectionPool(MySQLHost, MySQLPort, MySQLUser, MySQLPass, MySQLDatabase)
 86 | 	if err != nil {
 87 | 		t.Fatalf("unable to create MySQL connection pool: %s", err)
 88 | 	}
 89 | 
 90 | 	// cleanup test environment
 91 | 	tests.CleanupMySQLTables(t, ctx, pool)
 92 | 
 93 | 	// create table name with UUID
 94 | 	tableNameParam := "param_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
 95 | 	tableNameAuth := "auth_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
 96 | 	tableNameTemplateParam := "template_param_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
 97 | 
 98 | 	// set up data for param tool
 99 | 	createParamTableStmt, insertParamTableStmt, paramToolStmt, idParamToolStmt, nameParamToolStmt, arrayToolStmt, paramTestParams := tests.GetMySQLParamToolInfo(tableNameParam)
100 | 	teardownTable1 := tests.SetupMySQLTable(t, ctx, pool, createParamTableStmt, insertParamTableStmt, tableNameParam, paramTestParams)
101 | 	defer teardownTable1(t)
102 | 
103 | 	// set up data for auth tool
104 | 	createAuthTableStmt, insertAuthTableStmt, authToolStmt, authTestParams := tests.GetMySQLAuthToolInfo(tableNameAuth)
105 | 	teardownTable2 := tests.SetupMySQLTable(t, ctx, pool, createAuthTableStmt, insertAuthTableStmt, tableNameAuth, authTestParams)
106 | 	defer teardownTable2(t)
107 | 
108 | 	// Write config into a file and pass it to command
109 | 	toolsFile := tests.GetToolsConfig(sourceConfig, MySQLToolKind, paramToolStmt, idParamToolStmt, nameParamToolStmt, arrayToolStmt, authToolStmt)
110 | 	toolsFile = tests.AddMySqlExecuteSqlConfig(t, toolsFile)
111 | 	tmplSelectCombined, tmplSelectFilterCombined := tests.GetMySQLTmplToolStatement()
112 | 	toolsFile = tests.AddTemplateParamConfig(t, toolsFile, MySQLToolKind, tmplSelectCombined, tmplSelectFilterCombined, "")
113 | 
114 | 	toolsFile = tests.AddMySQLPrebuiltToolConfig(t, toolsFile)
115 | 
116 | 	cmd, cleanup, err := tests.StartCmd(ctx, toolsFile, args...)
117 | 	if err != nil {
118 | 		t.Fatalf("command initialization returned an error: %s", err)
119 | 	}
120 | 	defer cleanup()
121 | 
122 | 	waitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
123 | 	defer cancel()
124 | 	out, err := testutils.WaitForString(waitCtx, regexp.MustCompile(`Server ready to serve`), cmd.Out)
125 | 	if err != nil {
126 | 		t.Logf("toolbox command logs: \n%s", out)
127 | 		t.Fatalf("toolbox didn't start successfully: %s", err)
128 | 	}
129 | 
130 | 	// Get configs for tests
131 | 	select1Want, mcpMyFailToolWant, createTableStatement, mcpSelect1Want := tests.GetMySQLWants()
132 | 
133 | 	// Run tests
134 | 	tests.RunToolGetTest(t)
135 | 	tests.RunToolInvokeTest(t, select1Want, tests.DisableArrayTest())
136 | 	tests.RunMCPToolCallMethod(t, mcpMyFailToolWant, mcpSelect1Want)
137 | 	tests.RunExecuteSqlToolInvokeTest(t, createTableStatement, select1Want)
138 | 	tests.RunToolInvokeWithTemplateParameters(t, tableNameTemplateParam)
139 | 
140 | 	// Run specific MySQL tool tests
141 | 	tests.RunMySQLListTablesTest(t, MySQLDatabase, tableNameParam, tableNameAuth)
142 | 	tests.RunMySQLListActiveQueriesTest(t, ctx, pool)
143 | 	tests.RunMySQLListTablesMissingUniqueIndexes(t, ctx, pool, MySQLDatabase)
144 | 	tests.RunMySQLListTableFragmentationTest(t, MySQLDatabase, tableNameParam, tableNameAuth)
145 | }
146 | 
```

--------------------------------------------------------------------------------
/internal/tools/firebird/firebirdexecutesql/firebirdexecutesql.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 firebirdexecutesql
 16 | 
 17 | import (
 18 | 	"context"
 19 | 	"database/sql"
 20 | 	"fmt"
 21 | 
 22 | 	yaml "github.com/goccy/go-yaml"
 23 | 	"github.com/googleapis/genai-toolbox/internal/sources"
 24 | 	"github.com/googleapis/genai-toolbox/internal/sources/firebird"
 25 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 26 | 	"github.com/googleapis/genai-toolbox/internal/util"
 27 | )
 28 | 
 29 | const kind string = "firebird-execute-sql"
 30 | 
 31 | func init() {
 32 | 	if !tools.Register(kind, newConfig) {
 33 | 		panic(fmt.Sprintf("tool kind %q already registered", kind))
 34 | 	}
 35 | }
 36 | 
 37 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
 38 | 	actual := Config{Name: name}
 39 | 	if err := decoder.DecodeContext(ctx, &actual); err != nil {
 40 | 		return nil, err
 41 | 	}
 42 | 	return actual, nil
 43 | }
 44 | 
 45 | type compatibleSource interface {
 46 | 	FirebirdDB() *sql.DB
 47 | }
 48 | 
 49 | var _ compatibleSource = &firebird.Source{}
 50 | 
 51 | var compatibleSources = [...]string{firebird.SourceKind}
 52 | 
 53 | type Config struct {
 54 | 	Name         string   `yaml:"name" validate:"required"`
 55 | 	Kind         string   `yaml:"kind" validate:"required"`
 56 | 	Source       string   `yaml:"source" validate:"required"`
 57 | 	Description  string   `yaml:"description" validate:"required"`
 58 | 	AuthRequired []string `yaml:"authRequired"`
 59 | }
 60 | 
 61 | var _ tools.ToolConfig = Config{}
 62 | 
 63 | func (cfg Config) ToolConfigKind() string {
 64 | 	return kind
 65 | }
 66 | 
 67 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
 68 | 	rawS, ok := srcs[cfg.Source]
 69 | 	if !ok {
 70 | 		return nil, fmt.Errorf("no source named %q configured", cfg.Source)
 71 | 	}
 72 | 
 73 | 	s, ok := rawS.(compatibleSource)
 74 | 	if !ok {
 75 | 		return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
 76 | 	}
 77 | 
 78 | 	sqlParameter := tools.NewStringParameter("sql", "The sql to execute.")
 79 | 	parameters := tools.Parameters{sqlParameter}
 80 | 
 81 | 	mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
 82 | 
 83 | 	t := &Tool{
 84 | 		Name:         cfg.Name,
 85 | 		Parameters:   parameters,
 86 | 		AuthRequired: cfg.AuthRequired,
 87 | 		Db:           s.FirebirdDB(),
 88 | 		manifest:     tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired},
 89 | 		mcpManifest:  mcpManifest,
 90 | 	}
 91 | 	return t, nil
 92 | }
 93 | 
 94 | var _ tools.Tool = &Tool{}
 95 | 
 96 | type Tool struct {
 97 | 	Name         string           `yaml:"name"`
 98 | 	Kind         string           `yaml:"kind"`
 99 | 	AuthRequired []string         `yaml:"authRequired"`
100 | 	Parameters   tools.Parameters `yaml:"parameters"`
101 | 
102 | 	Db          *sql.DB
103 | 	manifest    tools.Manifest
104 | 	mcpManifest tools.McpManifest
105 | }
106 | 
107 | func (t *Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
108 | 	paramsMap := params.AsMap()
109 | 	sql, ok := paramsMap["sql"].(string)
110 | 	if !ok {
111 | 		return nil, fmt.Errorf("unable to get cast %s", paramsMap["sql"])
112 | 	}
113 | 
114 | 	// Log the query executed for debugging.
115 | 	logger, err := util.LoggerFromContext(ctx)
116 | 	if err != nil {
117 | 		return nil, fmt.Errorf("error getting logger: %s", err)
118 | 	}
119 | 	logger.DebugContext(ctx, "executing `%s` tool query: %s", kind, sql)
120 | 
121 | 	rows, err := t.Db.QueryContext(ctx, sql)
122 | 	if err != nil {
123 | 		return nil, fmt.Errorf("unable to execute query: %w", err)
124 | 	}
125 | 	defer rows.Close()
126 | 
127 | 	cols, err := rows.Columns()
128 | 
129 | 	var out []any
130 | 	if err == nil && len(cols) > 0 {
131 | 		values := make([]any, len(cols))
132 | 		scanArgs := make([]any, len(values))
133 | 		for i := range values {
134 | 			scanArgs[i] = &values[i]
135 | 		}
136 | 
137 | 		for rows.Next() {
138 | 			err = rows.Scan(scanArgs...)
139 | 			if err != nil {
140 | 				return nil, fmt.Errorf("unable to parse row: %w", err)
141 | 			}
142 | 
143 | 			vMap := make(map[string]any)
144 | 			for i, colName := range cols {
145 | 				if b, ok := values[i].([]byte); ok {
146 | 					vMap[colName] = string(b)
147 | 				} else {
148 | 					vMap[colName] = values[i]
149 | 				}
150 | 			}
151 | 			out = append(out, vMap)
152 | 		}
153 | 	}
154 | 
155 | 	if err := rows.Err(); err != nil {
156 | 		return nil, fmt.Errorf("error iterating rows: %w", err)
157 | 	}
158 | 
159 | 	// In most cases, DML/DDL statements like INSERT, UPDATE, CREATE, etc. might return no rows
160 | 	// However, it is also possible that this was a query that was expected to return rows
161 | 	// but returned none, a case that we cannot distinguish here.
162 | 	return out, nil
163 | }
164 | 
165 | func (t *Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
166 | 	return tools.ParseParams(t.Parameters, data, claims)
167 | }
168 | 
169 | func (t *Tool) Manifest() tools.Manifest {
170 | 	return t.manifest
171 | }
172 | 
173 | func (t *Tool) McpManifest() tools.McpManifest {
174 | 	return t.mcpManifest
175 | }
176 | 
177 | func (t *Tool) Authorized(verifiedAuthServices []string) bool {
178 | 	return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
179 | }
180 | 
181 | func (t Tool) RequiresClientAuthorization() bool {
182 | 	return false
183 | }
184 | 
```

--------------------------------------------------------------------------------
/internal/tools/looker/lookergetfilters/lookergetfilters.go:
--------------------------------------------------------------------------------

```go
  1 | // Copyright 2025 Google LLC
  2 | //
  3 | // Licensed under the Apache License, Version 2.0 (the "License");
  4 | // you may not use this file except in compliance with the License.
  5 | // You may obtain a copy of the License at
  6 | //
  7 | //	http://www.apache.org/licenses/LICENSE-2.0
  8 | //
  9 | // Unless required by applicable law or agreed to in writing, software
 10 | // distributed under the License is distributed on an "AS IS" BASIS,
 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12 | // See the License for the specific language governing permissions and
 13 | // limitations under the License.
 14 | package lookergetfilters
 15 | 
 16 | import (
 17 | 	"context"
 18 | 	"fmt"
 19 | 
 20 | 	yaml "github.com/goccy/go-yaml"
 21 | 	"github.com/googleapis/genai-toolbox/internal/sources"
 22 | 	lookersrc "github.com/googleapis/genai-toolbox/internal/sources/looker"
 23 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 24 | 	"github.com/googleapis/genai-toolbox/internal/tools/looker/lookercommon"
 25 | 	"github.com/googleapis/genai-toolbox/internal/util"
 26 | 
 27 | 	"github.com/looker-open-source/sdk-codegen/go/rtl"
 28 | 	v4 "github.com/looker-open-source/sdk-codegen/go/sdk/v4"
 29 | )
 30 | 
 31 | const kind string = "looker-get-filters"
 32 | 
 33 | func init() {
 34 | 	if !tools.Register(kind, newConfig) {
 35 | 		panic(fmt.Sprintf("tool kind %q already registered", kind))
 36 | 	}
 37 | }
 38 | 
 39 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
 40 | 	actual := Config{Name: name}
 41 | 	if err := decoder.DecodeContext(ctx, &actual); err != nil {
 42 | 		return nil, err
 43 | 	}
 44 | 	return actual, nil
 45 | }
 46 | 
 47 | type Config struct {
 48 | 	Name         string   `yaml:"name" validate:"required"`
 49 | 	Kind         string   `yaml:"kind" validate:"required"`
 50 | 	Source       string   `yaml:"source" validate:"required"`
 51 | 	Description  string   `yaml:"description" validate:"required"`
 52 | 	AuthRequired []string `yaml:"authRequired"`
 53 | }
 54 | 
 55 | // validate interface
 56 | var _ tools.ToolConfig = Config{}
 57 | 
 58 | func (cfg Config) ToolConfigKind() string {
 59 | 	return kind
 60 | }
 61 | 
 62 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
 63 | 	// verify source exists
 64 | 	rawS, ok := srcs[cfg.Source]
 65 | 	if !ok {
 66 | 		return nil, fmt.Errorf("no source named %q configured", cfg.Source)
 67 | 	}
 68 | 
 69 | 	// verify the source is compatible
 70 | 	s, ok := rawS.(*lookersrc.Source)
 71 | 	if !ok {
 72 | 		return nil, fmt.Errorf("invalid source for %q tool: source kind must be `looker`", kind)
 73 | 	}
 74 | 
 75 | 	parameters := lookercommon.GetFieldParameters()
 76 | 
 77 | 	mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
 78 | 
 79 | 	// finish tool setup
 80 | 	return Tool{
 81 | 		Name:           cfg.Name,
 82 | 		Kind:           kind,
 83 | 		Parameters:     parameters,
 84 | 		AuthRequired:   cfg.AuthRequired,
 85 | 		UseClientOAuth: s.UseClientOAuth,
 86 | 		Client:         s.Client,
 87 | 		ApiSettings:    s.ApiSettings,
 88 | 		manifest: tools.Manifest{
 89 | 			Description:  cfg.Description,
 90 | 			Parameters:   parameters.Manifest(),
 91 | 			AuthRequired: cfg.AuthRequired,
 92 | 		},
 93 | 		mcpManifest:      mcpManifest,
 94 | 		ShowHiddenFields: s.ShowHiddenFields,
 95 | 	}, nil
 96 | }
 97 | 
 98 | // validate interface
 99 | var _ tools.Tool = Tool{}
100 | 
101 | type Tool struct {
102 | 	Name             string `yaml:"name"`
103 | 	Kind             string `yaml:"kind"`
104 | 	UseClientOAuth   bool
105 | 	Client           *v4.LookerSDK
106 | 	ApiSettings      *rtl.ApiSettings
107 | 	AuthRequired     []string         `yaml:"authRequired"`
108 | 	Parameters       tools.Parameters `yaml:"parameters"`
109 | 	manifest         tools.Manifest
110 | 	mcpManifest      tools.McpManifest
111 | 	ShowHiddenFields bool
112 | }
113 | 
114 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
115 | 	logger, err := util.LoggerFromContext(ctx)
116 | 	if err != nil {
117 | 		return nil, fmt.Errorf("unable to get logger from ctx: %s", err)
118 | 	}
119 | 	model, explore, err := lookercommon.ProcessFieldArgs(ctx, params)
120 | 	if err != nil {
121 | 		return nil, fmt.Errorf("error processing model or explore: %w", err)
122 | 	}
123 | 
124 | 	fields := lookercommon.FiltersFields
125 | 	sdk, err := lookercommon.GetLookerSDK(t.UseClientOAuth, t.ApiSettings, t.Client, accessToken)
126 | 	if err != nil {
127 | 		return nil, fmt.Errorf("error getting sdk: %w", err)
128 | 	}
129 | 	req := v4.RequestLookmlModelExplore{
130 | 		LookmlModelName: *model,
131 | 		ExploreName:     *explore,
132 | 		Fields:          &fields,
133 | 	}
134 | 	resp, err := sdk.LookmlModelExplore(req, t.ApiSettings)
135 | 	if err != nil {
136 | 		return nil, fmt.Errorf("error making get_filters request: %w", err)
137 | 	}
138 | 
139 | 	if err := lookercommon.CheckLookerExploreFields(&resp); err != nil {
140 | 		return nil, fmt.Errorf("error processing get_filters response: %w", err)
141 | 	}
142 | 
143 | 	data, err := lookercommon.ExtractLookerFieldProperties(ctx, resp.Fields.Filters, t.ShowHiddenFields)
144 | 	if err != nil {
145 | 		return nil, fmt.Errorf("error extracting get_filters response: %w", err)
146 | 	}
147 | 	logger.DebugContext(ctx, "data = ", data)
148 | 
149 | 	return data, nil
150 | }
151 | 
152 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
153 | 	return tools.ParseParams(t.Parameters, data, claims)
154 | }
155 | 
156 | func (t Tool) Manifest() tools.Manifest {
157 | 	return t.manifest
158 | }
159 | 
160 | func (t Tool) McpManifest() tools.McpManifest {
161 | 	return t.mcpManifest
162 | }
163 | 
164 | func (t Tool) Authorized(verifiedAuthServices []string) bool {
165 | 	return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
166 | }
167 | 
168 | func (t Tool) RequiresClientAuthorization() bool {
169 | 	return t.UseClientOAuth
170 | }
171 | 
```

--------------------------------------------------------------------------------
/internal/tools/tidb/tidbsql/tidbsql_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 tidbsql_test
 16 | 
 17 | import (
 18 | 	"testing"
 19 | 
 20 | 	yaml "github.com/goccy/go-yaml"
 21 | 	"github.com/google/go-cmp/cmp"
 22 | 	"github.com/googleapis/genai-toolbox/internal/server"
 23 | 	"github.com/googleapis/genai-toolbox/internal/testutils"
 24 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 25 | 	"github.com/googleapis/genai-toolbox/internal/tools/tidb/tidbsql"
 26 | )
 27 | 
 28 | func TestParseFromYamlTiDB(t *testing.T) {
 29 | 	ctx, err := testutils.ContextWithNewLogger()
 30 | 	if err != nil {
 31 | 		t.Fatalf("unexpected error: %s", err)
 32 | 	}
 33 | 	tcs := []struct {
 34 | 		desc string
 35 | 		in   string
 36 | 		want server.ToolConfigs
 37 | 	}{
 38 | 		{
 39 | 			desc: "basic example",
 40 | 			in: `
 41 | 			tools:
 42 | 				example_tool:
 43 | 					kind: tidb-sql
 44 | 					source: my-tidb-instance
 45 | 					description: some description
 46 | 					statement: |
 47 | 						SELECT * FROM SQL_STATEMENT;
 48 | 					authRequired:
 49 | 						- my-google-auth-service
 50 | 						- other-auth-service
 51 | 					parameters:
 52 | 						- name: country
 53 | 						  type: string
 54 | 						  description: some description
 55 | 						  authServices:
 56 | 							- name: my-google-auth-service
 57 | 							  field: user_id
 58 | 							- name: other-auth-service
 59 | 							  field: user_id
 60 | 			`,
 61 | 			want: server.ToolConfigs{
 62 | 				"example_tool": tidbsql.Config{
 63 | 					Name:         "example_tool",
 64 | 					Kind:         "tidb-sql",
 65 | 					Source:       "my-tidb-instance",
 66 | 					Description:  "some description",
 67 | 					Statement:    "SELECT * FROM SQL_STATEMENT;\n",
 68 | 					AuthRequired: []string{"my-google-auth-service", "other-auth-service"},
 69 | 					Parameters: []tools.Parameter{
 70 | 						tools.NewStringParameterWithAuth("country", "some description",
 71 | 							[]tools.ParamAuthService{{Name: "my-google-auth-service", Field: "user_id"},
 72 | 								{Name: "other-auth-service", Field: "user_id"}}),
 73 | 					},
 74 | 				},
 75 | 			},
 76 | 		},
 77 | 	}
 78 | 	for _, tc := range tcs {
 79 | 		t.Run(tc.desc, func(t *testing.T) {
 80 | 			got := struct {
 81 | 				Tools server.ToolConfigs `yaml:"tools"`
 82 | 			}{}
 83 | 			// Parse contents
 84 | 			err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
 85 | 			if err != nil {
 86 | 				t.Fatalf("unable to unmarshal: %s", err)
 87 | 			}
 88 | 			if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
 89 | 				t.Fatalf("incorrect parse: diff %v", diff)
 90 | 			}
 91 | 		})
 92 | 	}
 93 | }
 94 | 
 95 | func TestParseFromYamlWithTemplateParamsTiDB(t *testing.T) {
 96 | 	ctx, err := testutils.ContextWithNewLogger()
 97 | 	if err != nil {
 98 | 		t.Fatalf("unexpected error: %s", err)
 99 | 	}
100 | 	tcs := []struct {
101 | 		desc string
102 | 		in   string
103 | 		want server.ToolConfigs
104 | 	}{
105 | 		{
106 | 			desc: "basic example",
107 | 			in: `
108 | 			tools:
109 | 				example_tool:
110 | 					kind: tidb-sql
111 | 					source: my-tidb-instance
112 | 					description: some description
113 | 					statement: |
114 | 						SELECT * FROM SQL_STATEMENT;
115 | 					authRequired:
116 | 						- my-google-auth-service
117 | 						- other-auth-service
118 | 					parameters:
119 | 						- name: country
120 | 						  type: string
121 | 						  description: some description
122 | 						  authServices:
123 | 							- name: my-google-auth-service
124 | 							  field: user_id
125 | 							- name: other-auth-service
126 | 							  field: user_id
127 | 					templateParameters:
128 | 						- name: tableName
129 | 						  type: string
130 | 						  description: The table to select hotels from.
131 | 						- name: fieldArray
132 | 						  type: array
133 | 						  description: The columns to return for the query.
134 | 						  items: 
135 | 								name: column
136 | 								type: string
137 | 								description: A column name that will be returned from the query.
138 | 			`,
139 | 			want: server.ToolConfigs{
140 | 				"example_tool": tidbsql.Config{
141 | 					Name:         "example_tool",
142 | 					Kind:         "tidb-sql",
143 | 					Source:       "my-tidb-instance",
144 | 					Description:  "some description",
145 | 					Statement:    "SELECT * FROM SQL_STATEMENT;\n",
146 | 					AuthRequired: []string{"my-google-auth-service", "other-auth-service"},
147 | 					Parameters: []tools.Parameter{
148 | 						tools.NewStringParameterWithAuth("country", "some description",
149 | 							[]tools.ParamAuthService{{Name: "my-google-auth-service", Field: "user_id"},
150 | 								{Name: "other-auth-service", Field: "user_id"}}),
151 | 					},
152 | 					TemplateParameters: []tools.Parameter{
153 | 						tools.NewStringParameter("tableName", "The table to select hotels from."),
154 | 						tools.NewArrayParameter("fieldArray", "The columns to return for the query.", tools.NewStringParameter("column", "A column name that will be returned from the query.")),
155 | 					},
156 | 				},
157 | 			},
158 | 		},
159 | 	}
160 | 	for _, tc := range tcs {
161 | 		t.Run(tc.desc, func(t *testing.T) {
162 | 			got := struct {
163 | 				Tools server.ToolConfigs `yaml:"tools"`
164 | 			}{}
165 | 			// Parse contents
166 | 			err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
167 | 			if err != nil {
168 | 				t.Fatalf("unable to unmarshal: %s", err)
169 | 			}
170 | 			if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
171 | 				t.Fatalf("incorrect parse: diff %v", diff)
172 | 			}
173 | 		})
174 | 	}
175 | }
176 | 
```

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

```markdown
  1 | ---
  2 | title: "postgres-sql"
  3 | type: docs
  4 | weight: 1
  5 | description: >
  6 |   A "postgres-sql" tool executes a pre-defined SQL statement against a Postgres
  7 |   database.
  8 | aliases:
  9 | - /resources/tools/postgres-sql
 10 | ---
 11 | 
 12 | ## About
 13 | 
 14 | A `postgres-sql` tool executes a pre-defined SQL statement against a Postgres
 15 | database. It's compatible with any of the following sources:
 16 | 
 17 | - [alloydb-postgres](../../sources/alloydb-pg.md)
 18 | - [cloud-sql-postgres](../../sources/cloud-sql-pg.md)
 19 | - [postgres](../../sources/postgres.md)
 20 | 
 21 | The specified SQL statement is executed as a [prepared statement][pg-prepare],
 22 | and specified parameters will be inserted according to their position: e.g. `$1`
 23 | will be the first parameter specified, `$2` will be the second parameter, and so
 24 | on. If template parameters are included, they will be resolved before execution
 25 | of the prepared statement.
 26 | 
 27 | [pg-prepare]: https://www.postgresql.org/docs/current/sql-prepare.html
 28 | 
 29 | ## Example
 30 | 
 31 | > **Note:** This tool uses parameterized queries to prevent SQL injections.
 32 | > Query parameters can be used as substitutes for arbitrary expressions.
 33 | > Parameters cannot be used as substitutes for identifiers, column names, table
 34 | > names, or other parts of the query.
 35 | 
 36 | ```yaml
 37 | tools:
 38 |  search_flights_by_number:
 39 |     kind: postgres-sql
 40 |     source: my-pg-instance
 41 |     statement: |
 42 |       SELECT * FROM flights
 43 |       WHERE airline = $1
 44 |       AND flight_number = $2
 45 |       LIMIT 10
 46 |     description: |
 47 |       Use this tool to get information for a specific flight.
 48 |       Takes an airline code and flight number and returns info on the flight.
 49 |       Do NOT use this tool with a flight id. Do NOT guess an airline code or flight number.
 50 |       A airline code is a code for an airline service consisting of two-character
 51 |       airline designator and followed by flight number, which is 1 to 4 digit number.
 52 |       For example, if given CY 0123, the airline is "CY", and flight_number is "123".
 53 |       Another example for this is DL 1234, the airline is "DL", and flight_number is "1234".
 54 |       If the tool returns more than one option choose the date closes to today.
 55 |       Example:
 56 |       {{
 57 |           "airline": "CY",
 58 |           "flight_number": "888",
 59 |       }}
 60 |       Example:
 61 |       {{
 62 |           "airline": "DL",
 63 |           "flight_number": "1234",
 64 |       }}
 65 |     parameters:
 66 |       - name: airline
 67 |         type: string
 68 |         description: Airline unique 2 letter identifier
 69 |       - name: flight_number
 70 |         type: string
 71 |         description: 1 to 4 digit number
 72 | ```
 73 | 
 74 | ### Example with Template Parameters
 75 | 
 76 | > **Note:** This tool allows direct modifications to the SQL statement,
 77 | > including identifiers, column names, and table names. **This makes it more
 78 | > vulnerable to SQL injections**. Using basic parameters only (see above) is
 79 | > recommended for performance and safety reasons. For more details, please check
 80 | > [templateParameters](..#template-parameters).
 81 | 
 82 | ```yaml
 83 | tools:
 84 |  list_table:
 85 |     kind: postgres-sql
 86 |     source: my-pg-instance
 87 |     statement: |
 88 |       SELECT * FROM {{.tableName}}
 89 |     description: |
 90 |       Use this tool to list all information from a specific table.
 91 |       Example:
 92 |       {{
 93 |           "tableName": "flights",
 94 |       }}
 95 |     templateParameters:
 96 |       - name: tableName
 97 |         type: string
 98 |         description: Table to select from
 99 | ```
100 | 
101 | ## Reference
102 | 
103 | | **field**           |                  **type**                                 | **required** | **description**                                                                                                                            |
104 | |---------------------|:---------------------------------------------------------:|:------------:|--------------------------------------------------------------------------------------------------------------------------------------------|
105 | | kind                |                   string                                  |     true     | Must be "postgres-sql".                                                                                                                    |
106 | | source              |                   string                                  |     true     | Name of the source the SQL should execute on.                                                                                              |
107 | | description         |                   string                                  |     true     | Description of the tool that is passed to the LLM.                                                                                         |
108 | | statement           |                   string                                  |     true     | SQL statement to execute on.                                                                                                               |
109 | | parameters          | [parameters](../#specifying-parameters)                |    false     | List of [parameters](../#specifying-parameters) that will be inserted into the SQL statement.                                           |
110 | | templateParameters  |  [templateParameters](..#template-parameters)         |    false     | List of [templateParameters](..#template-parameters) that will be inserted into the SQL statement before executing prepared statement. |
111 | 
```

--------------------------------------------------------------------------------
/internal/tools/looker/lookergetmeasures/lookergetmeasures.go:
--------------------------------------------------------------------------------

```go
  1 | // Copyright 2025 Google LLC
  2 | //
  3 | // Licensed under the Apache License, Version 2.0 (the "License");
  4 | // you may not use this file except in compliance with the License.
  5 | // You may obtain a copy of the License at
  6 | //
  7 | //	http://www.apache.org/licenses/LICENSE-2.0
  8 | //
  9 | // Unless required by applicable law or agreed to in writing, software
 10 | // distributed under the License is distributed on an "AS IS" BASIS,
 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12 | // See the License for the specific language governing permissions and
 13 | // limitations under the License.
 14 | package lookergetmeasures
 15 | 
 16 | import (
 17 | 	"context"
 18 | 	"fmt"
 19 | 
 20 | 	yaml "github.com/goccy/go-yaml"
 21 | 	"github.com/googleapis/genai-toolbox/internal/sources"
 22 | 	lookersrc "github.com/googleapis/genai-toolbox/internal/sources/looker"
 23 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 24 | 	"github.com/googleapis/genai-toolbox/internal/tools/looker/lookercommon"
 25 | 	"github.com/googleapis/genai-toolbox/internal/util"
 26 | 
 27 | 	"github.com/looker-open-source/sdk-codegen/go/rtl"
 28 | 	v4 "github.com/looker-open-source/sdk-codegen/go/sdk/v4"
 29 | )
 30 | 
 31 | const kind string = "looker-get-measures"
 32 | 
 33 | func init() {
 34 | 	if !tools.Register(kind, newConfig) {
 35 | 		panic(fmt.Sprintf("tool kind %q already registered", kind))
 36 | 	}
 37 | }
 38 | 
 39 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
 40 | 	actual := Config{Name: name}
 41 | 	if err := decoder.DecodeContext(ctx, &actual); err != nil {
 42 | 		return nil, err
 43 | 	}
 44 | 	return actual, nil
 45 | }
 46 | 
 47 | type Config struct {
 48 | 	Name         string   `yaml:"name" validate:"required"`
 49 | 	Kind         string   `yaml:"kind" validate:"required"`
 50 | 	Source       string   `yaml:"source" validate:"required"`
 51 | 	Description  string   `yaml:"description" validate:"required"`
 52 | 	AuthRequired []string `yaml:"authRequired"`
 53 | }
 54 | 
 55 | // validate interface
 56 | var _ tools.ToolConfig = Config{}
 57 | 
 58 | func (cfg Config) ToolConfigKind() string {
 59 | 	return kind
 60 | }
 61 | 
 62 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
 63 | 	// verify source exists
 64 | 	rawS, ok := srcs[cfg.Source]
 65 | 	if !ok {
 66 | 		return nil, fmt.Errorf("no source named %q configured", cfg.Source)
 67 | 	}
 68 | 
 69 | 	// verify the source is compatible
 70 | 	s, ok := rawS.(*lookersrc.Source)
 71 | 	if !ok {
 72 | 		return nil, fmt.Errorf("invalid source for %q tool: source kind must be `looker`", kind)
 73 | 	}
 74 | 
 75 | 	parameters := lookercommon.GetFieldParameters()
 76 | 
 77 | 	mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
 78 | 
 79 | 	// finish tool setup
 80 | 	return Tool{
 81 | 		Name:           cfg.Name,
 82 | 		Kind:           kind,
 83 | 		Parameters:     parameters,
 84 | 		AuthRequired:   cfg.AuthRequired,
 85 | 		UseClientOAuth: s.UseClientOAuth,
 86 | 		Client:         s.Client,
 87 | 		ApiSettings:    s.ApiSettings,
 88 | 		manifest: tools.Manifest{
 89 | 			Description:  cfg.Description,
 90 | 			Parameters:   parameters.Manifest(),
 91 | 			AuthRequired: cfg.AuthRequired,
 92 | 		},
 93 | 		mcpManifest:      mcpManifest,
 94 | 		ShowHiddenFields: s.ShowHiddenFields,
 95 | 	}, nil
 96 | }
 97 | 
 98 | // validate interface
 99 | var _ tools.Tool = Tool{}
100 | 
101 | type Tool struct {
102 | 	Name             string `yaml:"name"`
103 | 	Kind             string `yaml:"kind"`
104 | 	UseClientOAuth   bool
105 | 	Client           *v4.LookerSDK
106 | 	ApiSettings      *rtl.ApiSettings
107 | 	AuthRequired     []string         `yaml:"authRequired"`
108 | 	Parameters       tools.Parameters `yaml:"parameters"`
109 | 	manifest         tools.Manifest
110 | 	mcpManifest      tools.McpManifest
111 | 	ShowHiddenFields bool
112 | }
113 | 
114 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
115 | 	logger, err := util.LoggerFromContext(ctx)
116 | 	if err != nil {
117 | 		return nil, fmt.Errorf("unable to get logger from ctx: %s", err)
118 | 	}
119 | 	model, explore, err := lookercommon.ProcessFieldArgs(ctx, params)
120 | 	if err != nil {
121 | 		return nil, fmt.Errorf("error processing model or explore: %w", err)
122 | 	}
123 | 
124 | 	fields := lookercommon.MeasuresFields
125 | 	sdk, err := lookercommon.GetLookerSDK(t.UseClientOAuth, t.ApiSettings, t.Client, accessToken)
126 | 	if err != nil {
127 | 		return nil, fmt.Errorf("error getting sdk: %w", err)
128 | 	}
129 | 	req := v4.RequestLookmlModelExplore{
130 | 		LookmlModelName: *model,
131 | 		ExploreName:     *explore,
132 | 		Fields:          &fields,
133 | 	}
134 | 	resp, err := sdk.LookmlModelExplore(req, t.ApiSettings)
135 | 	if err != nil {
136 | 		return nil, fmt.Errorf("error making get_measures request: %w", err)
137 | 	}
138 | 
139 | 	if err := lookercommon.CheckLookerExploreFields(&resp); err != nil {
140 | 		return nil, fmt.Errorf("error processing get_measures response: %w", err)
141 | 	}
142 | 
143 | 	data, err := lookercommon.ExtractLookerFieldProperties(ctx, resp.Fields.Measures, t.ShowHiddenFields)
144 | 	if err != nil {
145 | 		return nil, fmt.Errorf("error extracting get_measures response: %w", err)
146 | 	}
147 | 	logger.DebugContext(ctx, "data = ", data)
148 | 
149 | 	return data, nil
150 | }
151 | 
152 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
153 | 	return tools.ParseParams(t.Parameters, data, claims)
154 | }
155 | 
156 | func (t Tool) Manifest() tools.Manifest {
157 | 	return t.manifest
158 | }
159 | 
160 | func (t Tool) McpManifest() tools.McpManifest {
161 | 	return t.mcpManifest
162 | }
163 | 
164 | func (t Tool) Authorized(verifiedAuthServices []string) bool {
165 | 	return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
166 | }
167 | 
168 | func (t Tool) RequiresClientAuthorization() bool {
169 | 	return t.UseClientOAuth
170 | }
171 | 
```

--------------------------------------------------------------------------------
/internal/tools/mysql/mysqlsql/mysqlsql_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 mysqlsql_test
 16 | 
 17 | import (
 18 | 	"testing"
 19 | 
 20 | 	yaml "github.com/goccy/go-yaml"
 21 | 	"github.com/google/go-cmp/cmp"
 22 | 	"github.com/googleapis/genai-toolbox/internal/server"
 23 | 	"github.com/googleapis/genai-toolbox/internal/testutils"
 24 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 25 | 	"github.com/googleapis/genai-toolbox/internal/tools/mysql/mysqlsql"
 26 | )
 27 | 
 28 | func TestParseFromYamlMySQL(t *testing.T) {
 29 | 	ctx, err := testutils.ContextWithNewLogger()
 30 | 	if err != nil {
 31 | 		t.Fatalf("unexpected error: %s", err)
 32 | 	}
 33 | 	tcs := []struct {
 34 | 		desc string
 35 | 		in   string
 36 | 		want server.ToolConfigs
 37 | 	}{
 38 | 		{
 39 | 			desc: "basic example",
 40 | 			in: `
 41 | 			tools:
 42 | 				example_tool:
 43 | 					kind: mysql-sql
 44 | 					source: my-mysql-instance
 45 | 					description: some description
 46 | 					statement: |
 47 | 						SELECT * FROM SQL_STATEMENT;
 48 | 					authRequired:
 49 | 						- my-google-auth-service
 50 | 						- other-auth-service
 51 | 					parameters:
 52 | 						- name: country
 53 | 						  type: string
 54 | 						  description: some description
 55 | 						  authServices:
 56 | 							- name: my-google-auth-service
 57 | 							  field: user_id
 58 | 							- name: other-auth-service
 59 | 							  field: user_id
 60 | 			`,
 61 | 			want: server.ToolConfigs{
 62 | 				"example_tool": mysqlsql.Config{
 63 | 					Name:         "example_tool",
 64 | 					Kind:         "mysql-sql",
 65 | 					Source:       "my-mysql-instance",
 66 | 					Description:  "some description",
 67 | 					Statement:    "SELECT * FROM SQL_STATEMENT;\n",
 68 | 					AuthRequired: []string{"my-google-auth-service", "other-auth-service"},
 69 | 					Parameters: []tools.Parameter{
 70 | 						tools.NewStringParameterWithAuth("country", "some description",
 71 | 							[]tools.ParamAuthService{{Name: "my-google-auth-service", Field: "user_id"},
 72 | 								{Name: "other-auth-service", Field: "user_id"}}),
 73 | 					},
 74 | 				},
 75 | 			},
 76 | 		},
 77 | 	}
 78 | 	for _, tc := range tcs {
 79 | 		t.Run(tc.desc, func(t *testing.T) {
 80 | 			got := struct {
 81 | 				Tools server.ToolConfigs `yaml:"tools"`
 82 | 			}{}
 83 | 			// Parse contents
 84 | 			err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
 85 | 			if err != nil {
 86 | 				t.Fatalf("unable to unmarshal: %s", err)
 87 | 			}
 88 | 			if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
 89 | 				t.Fatalf("incorrect parse: diff %v", diff)
 90 | 			}
 91 | 		})
 92 | 	}
 93 | }
 94 | 
 95 | func TestParseFromYamlWithTemplateParamsMySQL(t *testing.T) {
 96 | 	ctx, err := testutils.ContextWithNewLogger()
 97 | 	if err != nil {
 98 | 		t.Fatalf("unexpected error: %s", err)
 99 | 	}
100 | 	tcs := []struct {
101 | 		desc string
102 | 		in   string
103 | 		want server.ToolConfigs
104 | 	}{
105 | 		{
106 | 			desc: "basic example",
107 | 			in: `
108 | 			tools:
109 | 				example_tool:
110 | 					kind: mysql-sql
111 | 					source: my-mysql-instance
112 | 					description: some description
113 | 					statement: |
114 | 						SELECT * FROM SQL_STATEMENT;
115 | 					authRequired:
116 | 						- my-google-auth-service
117 | 						- other-auth-service
118 | 					parameters:
119 | 						- name: country
120 | 						  type: string
121 | 						  description: some description
122 | 						  authServices:
123 | 							- name: my-google-auth-service
124 | 							  field: user_id
125 | 							- name: other-auth-service
126 | 							  field: user_id
127 | 					templateParameters:
128 | 						- name: tableName
129 | 						  type: string
130 | 						  description: The table to select hotels from.
131 | 						- name: fieldArray
132 | 						  type: array
133 | 						  description: The columns to return for the query.
134 | 						  items: 
135 | 								name: column
136 | 								type: string
137 | 								description: A column name that will be returned from the query.
138 | 			`,
139 | 			want: server.ToolConfigs{
140 | 				"example_tool": mysqlsql.Config{
141 | 					Name:         "example_tool",
142 | 					Kind:         "mysql-sql",
143 | 					Source:       "my-mysql-instance",
144 | 					Description:  "some description",
145 | 					Statement:    "SELECT * FROM SQL_STATEMENT;\n",
146 | 					AuthRequired: []string{"my-google-auth-service", "other-auth-service"},
147 | 					Parameters: []tools.Parameter{
148 | 						tools.NewStringParameterWithAuth("country", "some description",
149 | 							[]tools.ParamAuthService{{Name: "my-google-auth-service", Field: "user_id"},
150 | 								{Name: "other-auth-service", Field: "user_id"}}),
151 | 					},
152 | 					TemplateParameters: []tools.Parameter{
153 | 						tools.NewStringParameter("tableName", "The table to select hotels from."),
154 | 						tools.NewArrayParameter("fieldArray", "The columns to return for the query.", tools.NewStringParameter("column", "A column name that will be returned from the query.")),
155 | 					},
156 | 				},
157 | 			},
158 | 		},
159 | 	}
160 | 	for _, tc := range tcs {
161 | 		t.Run(tc.desc, func(t *testing.T) {
162 | 			got := struct {
163 | 				Tools server.ToolConfigs `yaml:"tools"`
164 | 			}{}
165 | 			// Parse contents
166 | 			err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
167 | 			if err != nil {
168 | 				t.Fatalf("unable to unmarshal: %s", err)
169 | 			}
170 | 			if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
171 | 				t.Fatalf("incorrect parse: diff %v", diff)
172 | 			}
173 | 		})
174 | 	}
175 | }
176 | 
```

--------------------------------------------------------------------------------
/internal/tools/alloydb/alloydblistinstances/alloydblistinstances.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 alloydblistinstances
 16 | 
 17 | import (
 18 | 	"context"
 19 | 	"fmt"
 20 | 
 21 | 	yaml "github.com/goccy/go-yaml"
 22 | 	"github.com/googleapis/genai-toolbox/internal/sources"
 23 | 	alloydbadmin "github.com/googleapis/genai-toolbox/internal/sources/alloydbadmin"
 24 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 25 | )
 26 | 
 27 | const kind string = "alloydb-list-instances"
 28 | 
 29 | func init() {
 30 | 	if !tools.Register(kind, newConfig) {
 31 | 		panic(fmt.Sprintf("tool kind %q already registered", kind))
 32 | 	}
 33 | }
 34 | 
 35 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
 36 | 	actual := Config{Name: name}
 37 | 	if err := decoder.DecodeContext(ctx, &actual); err != nil {
 38 | 		return nil, err
 39 | 	}
 40 | 	return actual, nil
 41 | }
 42 | 
 43 | // Configuration for the list-instances tool.
 44 | type Config struct {
 45 | 	Name         string   `yaml:"name" validate:"required"`
 46 | 	Kind         string   `yaml:"kind" validate:"required"`
 47 | 	Source       string   `yaml:"source" validate:"required"`
 48 | 	Description  string   `yaml:"description"`
 49 | 	AuthRequired []string `yaml:"authRequired"`
 50 | 	BaseURL      string   `yaml:"baseURL"`
 51 | }
 52 | 
 53 | // validate interface
 54 | var _ tools.ToolConfig = Config{}
 55 | 
 56 | // ToolConfigKind returns the kind of the tool.
 57 | func (cfg Config) ToolConfigKind() string {
 58 | 	return kind
 59 | }
 60 | 
 61 | // Initialize initializes the tool from the configuration.
 62 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
 63 | 	rawS, ok := srcs[cfg.Source]
 64 | 	if !ok {
 65 | 		return nil, fmt.Errorf("source %q not found", cfg.Source)
 66 | 	}
 67 | 
 68 | 	s, ok := rawS.(*alloydbadmin.Source)
 69 | 	if !ok {
 70 | 		return nil, fmt.Errorf("invalid source for %q tool: source kind must be `%s`", kind, alloydbadmin.SourceKind)
 71 | 	}
 72 | 
 73 | 	allParameters := tools.Parameters{
 74 | 		tools.NewStringParameter("project", "The GCP project ID to list instances for."),
 75 | 		tools.NewStringParameterWithDefault("location", "-", "Optional: The location of the cluster (e.g., 'us-central1'). Use '-' to get results for all regions.(Default: '-')"),
 76 | 		tools.NewStringParameterWithDefault("cluster", "-", "Optional: The ID of the cluster to list instances from. Use '-' to get results for all clusters.(Default: '-')"),
 77 | 	}
 78 | 	paramManifest := allParameters.Manifest()
 79 | 
 80 | 	description := cfg.Description
 81 | 	if description == "" {
 82 | 		description = "Lists all AlloyDB instances in a given project, location and cluster."
 83 | 	}
 84 | 	mcpManifest := tools.GetMcpManifest(cfg.Name, description, cfg.AuthRequired, allParameters)
 85 | 
 86 | 	return Tool{
 87 | 		Name:        cfg.Name,
 88 | 		Kind:        kind,
 89 | 		Source:      s,
 90 | 		AllParams:   allParameters,
 91 | 		manifest:    tools.Manifest{Description: description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired},
 92 | 		mcpManifest: mcpManifest,
 93 | 	}, nil
 94 | }
 95 | 
 96 | // Tool represents the list-instances tool.
 97 | type Tool struct {
 98 | 	Name        string `yaml:"name"`
 99 | 	Kind        string `yaml:"kind"`
100 | 	Description string `yaml:"description"`
101 | 
102 | 	Source    *alloydbadmin.Source
103 | 	AllParams tools.Parameters `yaml:"allParams"`
104 | 
105 | 	manifest    tools.Manifest
106 | 	mcpManifest tools.McpManifest
107 | }
108 | 
109 | // Invoke executes the tool's logic.
110 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
111 | 	paramsMap := params.AsMap()
112 | 
113 | 	project, ok := paramsMap["project"].(string)
114 | 	if !ok {
115 | 		return nil, fmt.Errorf("invalid or missing 'project' parameter; expected a string")
116 | 	}
117 | 	location, ok := paramsMap["location"].(string)
118 | 	if !ok {
119 | 		return nil, fmt.Errorf("invalid 'location' parameter; expected a string")
120 | 	}
121 | 	cluster, ok := paramsMap["cluster"].(string)
122 | 	if !ok {
123 | 		return nil, fmt.Errorf("invalid 'cluster' parameter; expected a string")
124 | 	}
125 | 
126 | 	service, err := t.Source.GetService(ctx, string(accessToken))
127 | 	if err != nil {
128 | 		return nil, err
129 | 	}
130 | 
131 | 	urlString := fmt.Sprintf("projects/%s/locations/%s/clusters/%s", project, location, cluster)
132 | 
133 | 	resp, err := service.Projects.Locations.Clusters.Instances.List(urlString).Do()
134 | 	if err != nil {
135 | 		return nil, fmt.Errorf("error listing AlloyDB instances: %w", err)
136 | 	}
137 | 
138 | 	return resp, nil
139 | }
140 | 
141 | // ParseParams parses the parameters for the tool.
142 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
143 | 	return tools.ParseParams(t.AllParams, data, claims)
144 | }
145 | 
146 | // Manifest returns the tool's manifest.
147 | func (t Tool) Manifest() tools.Manifest {
148 | 	return t.manifest
149 | }
150 | 
151 | // McpManifest returns the tool's MCP manifest.
152 | func (t Tool) McpManifest() tools.McpManifest {
153 | 	return t.mcpManifest
154 | }
155 | 
156 | // Authorized checks if the tool is authorized.
157 | func (t Tool) Authorized(verifiedAuthServices []string) bool {
158 | 	return true
159 | }
160 | 
161 | func (t Tool) RequiresClientAuthorization() bool {
162 | 	return t.Source.UseClientAuthorization()
163 | }
164 | 
```

--------------------------------------------------------------------------------
/internal/tools/looker/lookergetdimensions/lookergetdimensions.go:
--------------------------------------------------------------------------------

```go
  1 | // Copyright 2025 Google LLC
  2 | //
  3 | // Licensed under the Apache License, Version 2.0 (the "License");
  4 | // you may not use this file except in compliance with the License.
  5 | // You may obtain a copy of the License at
  6 | //
  7 | //	http://www.apache.org/licenses/LICENSE-2.0
  8 | //
  9 | // Unless required by applicable law or agreed to in writing, software
 10 | // distributed under the License is distributed on an "AS IS" BASIS,
 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12 | // See the License for the specific language governing permissions and
 13 | // limitations under the License.
 14 | package lookergetdimensions
 15 | 
 16 | import (
 17 | 	"context"
 18 | 	"fmt"
 19 | 
 20 | 	yaml "github.com/goccy/go-yaml"
 21 | 	"github.com/googleapis/genai-toolbox/internal/sources"
 22 | 	lookersrc "github.com/googleapis/genai-toolbox/internal/sources/looker"
 23 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 24 | 	"github.com/googleapis/genai-toolbox/internal/tools/looker/lookercommon"
 25 | 	"github.com/googleapis/genai-toolbox/internal/util"
 26 | 
 27 | 	"github.com/looker-open-source/sdk-codegen/go/rtl"
 28 | 	v4 "github.com/looker-open-source/sdk-codegen/go/sdk/v4"
 29 | )
 30 | 
 31 | const kind string = "looker-get-dimensions"
 32 | 
 33 | func init() {
 34 | 	if !tools.Register(kind, newConfig) {
 35 | 		panic(fmt.Sprintf("tool kind %q already registered", kind))
 36 | 	}
 37 | }
 38 | 
 39 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
 40 | 	actual := Config{Name: name}
 41 | 	if err := decoder.DecodeContext(ctx, &actual); err != nil {
 42 | 		return nil, err
 43 | 	}
 44 | 	return actual, nil
 45 | }
 46 | 
 47 | type Config struct {
 48 | 	Name         string   `yaml:"name" validate:"required"`
 49 | 	Kind         string   `yaml:"kind" validate:"required"`
 50 | 	Source       string   `yaml:"source" validate:"required"`
 51 | 	Description  string   `yaml:"description" validate:"required"`
 52 | 	AuthRequired []string `yaml:"authRequired"`
 53 | }
 54 | 
 55 | // validate interface
 56 | var _ tools.ToolConfig = Config{}
 57 | 
 58 | func (cfg Config) ToolConfigKind() string {
 59 | 	return kind
 60 | }
 61 | 
 62 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
 63 | 	// verify source exists
 64 | 	rawS, ok := srcs[cfg.Source]
 65 | 	if !ok {
 66 | 		return nil, fmt.Errorf("no source named %q configured", cfg.Source)
 67 | 	}
 68 | 
 69 | 	// verify the source is compatible
 70 | 	s, ok := rawS.(*lookersrc.Source)
 71 | 	if !ok {
 72 | 		return nil, fmt.Errorf("invalid source for %q tool: source kind must be `looker`", kind)
 73 | 	}
 74 | 
 75 | 	parameters := lookercommon.GetFieldParameters()
 76 | 
 77 | 	mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
 78 | 
 79 | 	// finish tool setup
 80 | 	return Tool{
 81 | 		Name:           cfg.Name,
 82 | 		Kind:           kind,
 83 | 		Parameters:     parameters,
 84 | 		UseClientOAuth: s.UseClientOAuth,
 85 | 		Client:         s.Client,
 86 | 		AuthRequired:   cfg.AuthRequired,
 87 | 		ApiSettings:    s.ApiSettings,
 88 | 		manifest: tools.Manifest{
 89 | 			Description:  cfg.Description,
 90 | 			Parameters:   parameters.Manifest(),
 91 | 			AuthRequired: cfg.AuthRequired,
 92 | 		},
 93 | 		mcpManifest:      mcpManifest,
 94 | 		ShowHiddenFields: s.ShowHiddenFields,
 95 | 	}, nil
 96 | }
 97 | 
 98 | // validate interface
 99 | var _ tools.Tool = Tool{}
100 | 
101 | type Tool struct {
102 | 	Name             string `yaml:"name"`
103 | 	Kind             string `yaml:"kind"`
104 | 	UseClientOAuth   bool
105 | 	Client           *v4.LookerSDK
106 | 	ApiSettings      *rtl.ApiSettings
107 | 	AuthRequired     []string         `yaml:"authRequired"`
108 | 	Parameters       tools.Parameters `yaml:"parameters"`
109 | 	manifest         tools.Manifest
110 | 	mcpManifest      tools.McpManifest
111 | 	ShowHiddenFields bool
112 | }
113 | 
114 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
115 | 	logger, err := util.LoggerFromContext(ctx)
116 | 	if err != nil {
117 | 		return nil, fmt.Errorf("unable to get logger from ctx: %s", err)
118 | 	}
119 | 	model, explore, err := lookercommon.ProcessFieldArgs(ctx, params)
120 | 	if err != nil {
121 | 		return nil, fmt.Errorf("error processing model or explore: %w", err)
122 | 	}
123 | 
124 | 	sdk, err := lookercommon.GetLookerSDK(t.UseClientOAuth, t.ApiSettings, t.Client, accessToken)
125 | 	if err != nil {
126 | 		return nil, fmt.Errorf("error getting sdk: %w", err)
127 | 	}
128 | 	fields := lookercommon.DimensionsFields
129 | 	req := v4.RequestLookmlModelExplore{
130 | 		LookmlModelName: *model,
131 | 		ExploreName:     *explore,
132 | 		Fields:          &fields,
133 | 	}
134 | 	resp, err := sdk.LookmlModelExplore(req, t.ApiSettings)
135 | 	if err != nil {
136 | 		return nil, fmt.Errorf("error making get_dimensions request: %w", err)
137 | 	}
138 | 
139 | 	if err := lookercommon.CheckLookerExploreFields(&resp); err != nil {
140 | 		return nil, fmt.Errorf("error processing get_dimensions response: %w", err)
141 | 	}
142 | 
143 | 	data, err := lookercommon.ExtractLookerFieldProperties(ctx, resp.Fields.Dimensions, t.ShowHiddenFields)
144 | 	if err != nil {
145 | 		return nil, fmt.Errorf("error extracting get_dimensions response: %w", err)
146 | 	}
147 | 	logger.DebugContext(ctx, "data = ", data)
148 | 
149 | 	return data, nil
150 | }
151 | 
152 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
153 | 	return tools.ParseParams(t.Parameters, data, claims)
154 | }
155 | 
156 | func (t Tool) Manifest() tools.Manifest {
157 | 	return t.manifest
158 | }
159 | 
160 | func (t Tool) McpManifest() tools.McpManifest {
161 | 	return t.mcpManifest
162 | }
163 | 
164 | func (t Tool) Authorized(verifiedAuthServices []string) bool {
165 | 	return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
166 | }
167 | 
168 | func (t Tool) RequiresClientAuthorization() bool {
169 | 	return t.UseClientOAuth
170 | }
171 | 
```

--------------------------------------------------------------------------------
/internal/tools/looker/lookergetparameters/lookergetparameters.go:
--------------------------------------------------------------------------------

```go
  1 | // Copyright 2025 Google LLC
  2 | //
  3 | // Licensed under the Apache License, Version 2.0 (the "License");
  4 | // you may not use this file except in compliance with the License.
  5 | // You may obtain a copy of the License at
  6 | //
  7 | //	http://www.apache.org/licenses/LICENSE-2.0
  8 | //
  9 | // Unless required by applicable law or agreed to in writing, software
 10 | // distributed under the License is distributed on an "AS IS" BASIS,
 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12 | // See the License for the specific language governing permissions and
 13 | // limitations under the License.
 14 | package lookergetparameters
 15 | 
 16 | import (
 17 | 	"context"
 18 | 	"fmt"
 19 | 
 20 | 	yaml "github.com/goccy/go-yaml"
 21 | 	"github.com/googleapis/genai-toolbox/internal/sources"
 22 | 	lookersrc "github.com/googleapis/genai-toolbox/internal/sources/looker"
 23 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 24 | 	"github.com/googleapis/genai-toolbox/internal/tools/looker/lookercommon"
 25 | 	"github.com/googleapis/genai-toolbox/internal/util"
 26 | 
 27 | 	"github.com/looker-open-source/sdk-codegen/go/rtl"
 28 | 	v4 "github.com/looker-open-source/sdk-codegen/go/sdk/v4"
 29 | )
 30 | 
 31 | const kind string = "looker-get-parameters"
 32 | 
 33 | func init() {
 34 | 	if !tools.Register(kind, newConfig) {
 35 | 		panic(fmt.Sprintf("tool kind %q already registered", kind))
 36 | 	}
 37 | }
 38 | 
 39 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
 40 | 	actual := Config{Name: name}
 41 | 	if err := decoder.DecodeContext(ctx, &actual); err != nil {
 42 | 		return nil, err
 43 | 	}
 44 | 	return actual, nil
 45 | }
 46 | 
 47 | type Config struct {
 48 | 	Name         string   `yaml:"name" validate:"required"`
 49 | 	Kind         string   `yaml:"kind" validate:"required"`
 50 | 	Source       string   `yaml:"source" validate:"required"`
 51 | 	Description  string   `yaml:"description" validate:"required"`
 52 | 	AuthRequired []string `yaml:"authRequired"`
 53 | }
 54 | 
 55 | // validate interface
 56 | var _ tools.ToolConfig = Config{}
 57 | 
 58 | func (cfg Config) ToolConfigKind() string {
 59 | 	return kind
 60 | }
 61 | 
 62 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
 63 | 	// verify source exists
 64 | 	rawS, ok := srcs[cfg.Source]
 65 | 	if !ok {
 66 | 		return nil, fmt.Errorf("no source named %q configured", cfg.Source)
 67 | 	}
 68 | 
 69 | 	// verify the source is compatible
 70 | 	s, ok := rawS.(*lookersrc.Source)
 71 | 	if !ok {
 72 | 		return nil, fmt.Errorf("invalid source for %q tool: source kind must be `looker`", kind)
 73 | 	}
 74 | 
 75 | 	parameters := lookercommon.GetFieldParameters()
 76 | 
 77 | 	mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
 78 | 
 79 | 	// finish tool setup
 80 | 	return Tool{
 81 | 		Name:           cfg.Name,
 82 | 		Kind:           kind,
 83 | 		Parameters:     parameters,
 84 | 		AuthRequired:   cfg.AuthRequired,
 85 | 		UseClientOAuth: s.UseClientOAuth,
 86 | 		Client:         s.Client,
 87 | 		ApiSettings:    s.ApiSettings,
 88 | 		manifest: tools.Manifest{
 89 | 			Description:  cfg.Description,
 90 | 			Parameters:   parameters.Manifest(),
 91 | 			AuthRequired: cfg.AuthRequired,
 92 | 		},
 93 | 		mcpManifest:      mcpManifest,
 94 | 		ShowHiddenFields: s.ShowHiddenFields,
 95 | 	}, nil
 96 | }
 97 | 
 98 | // validate interface
 99 | var _ tools.Tool = Tool{}
100 | 
101 | type Tool struct {
102 | 	Name             string `yaml:"name"`
103 | 	Kind             string `yaml:"kind"`
104 | 	UseClientOAuth   bool
105 | 	Client           *v4.LookerSDK
106 | 	ApiSettings      *rtl.ApiSettings
107 | 	AuthRequired     []string         `yaml:"authRequired"`
108 | 	Parameters       tools.Parameters `yaml:"parameters"`
109 | 	manifest         tools.Manifest
110 | 	mcpManifest      tools.McpManifest
111 | 	ShowHiddenFields bool
112 | }
113 | 
114 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
115 | 	logger, err := util.LoggerFromContext(ctx)
116 | 	if err != nil {
117 | 		return nil, fmt.Errorf("unable to get logger from ctx: %s", err)
118 | 	}
119 | 	model, explore, err := lookercommon.ProcessFieldArgs(ctx, params)
120 | 	if err != nil {
121 | 		return nil, fmt.Errorf("error processing model or explore: %w", err)
122 | 	}
123 | 
124 | 	fields := lookercommon.ParametersFields
125 | 	sdk, err := lookercommon.GetLookerSDK(t.UseClientOAuth, t.ApiSettings, t.Client, accessToken)
126 | 	if err != nil {
127 | 		return nil, fmt.Errorf("error getting sdk: %w", err)
128 | 	}
129 | 	req := v4.RequestLookmlModelExplore{
130 | 		LookmlModelName: *model,
131 | 		ExploreName:     *explore,
132 | 		Fields:          &fields,
133 | 	}
134 | 	resp, err := sdk.LookmlModelExplore(req, t.ApiSettings)
135 | 	if err != nil {
136 | 		return nil, fmt.Errorf("error making get_parameters request: %w", err)
137 | 	}
138 | 
139 | 	if err := lookercommon.CheckLookerExploreFields(&resp); err != nil {
140 | 		return nil, fmt.Errorf("error processing get_parameters response: %w", err)
141 | 	}
142 | 
143 | 	data, err := lookercommon.ExtractLookerFieldProperties(ctx, resp.Fields.Parameters, t.ShowHiddenFields)
144 | 	if err != nil {
145 | 		return nil, fmt.Errorf("error extracting get_parameters response: %w", err)
146 | 	}
147 | 	logger.DebugContext(ctx, "data = ", data)
148 | 
149 | 	return data, nil
150 | }
151 | 
152 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
153 | 	return tools.ParseParams(t.Parameters, data, claims)
154 | }
155 | 
156 | func (t Tool) Manifest() tools.Manifest {
157 | 	return t.manifest
158 | }
159 | 
160 | func (t Tool) McpManifest() tools.McpManifest {
161 | 	return t.mcpManifest
162 | }
163 | 
164 | func (t Tool) Authorized(verifiedAuthServices []string) bool {
165 | 	return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
166 | }
167 | 
168 | func (t Tool) RequiresClientAuthorization() bool {
169 | 	return t.UseClientOAuth
170 | }
171 | 
```

--------------------------------------------------------------------------------
/internal/tools/cassandra/cassandracql/cassandracql_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 cassandracql_test
 16 | 
 17 | import (
 18 | 	"testing"
 19 | 
 20 | 	yaml "github.com/goccy/go-yaml"
 21 | 	"github.com/google/go-cmp/cmp"
 22 | 	"github.com/googleapis/genai-toolbox/internal/server"
 23 | 	"github.com/googleapis/genai-toolbox/internal/testutils"
 24 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 25 | 	"github.com/googleapis/genai-toolbox/internal/tools/cassandra/cassandracql"
 26 | )
 27 | 
 28 | func TestParseFromYamlCassandra(t *testing.T) {
 29 | 	ctx, err := testutils.ContextWithNewLogger()
 30 | 	if err != nil {
 31 | 		t.Fatalf("unexpected error: %s", err)
 32 | 	}
 33 | 	tcs := []struct {
 34 | 		desc string
 35 | 		in   string
 36 | 		want server.ToolConfigs
 37 | 	}{
 38 | 		{
 39 | 			desc: "basic example",
 40 | 			in: `
 41 | 			tools:
 42 | 				example_tool:
 43 | 					kind: cassandra-cql
 44 | 					source: my-cassandra-instance
 45 | 					description: some description
 46 | 					statement: |
 47 | 						SELECT * FROM CQL_STATEMENT;
 48 | 					authRequired:
 49 | 						- my-google-auth-service
 50 | 						- other-auth-service
 51 | 					parameters:
 52 | 						- name: country
 53 | 						  type: string
 54 | 						  description: some description
 55 | 						  authServices:
 56 | 							- name: my-google-auth-service
 57 | 							  field: user_id
 58 | 							- name: other-auth-service
 59 | 							  field: user_id
 60 | 			`,
 61 | 			want: server.ToolConfigs{
 62 | 				"example_tool": cassandracql.Config{
 63 | 					Name:         "example_tool",
 64 | 					Kind:         "cassandra-cql",
 65 | 					Source:       "my-cassandra-instance",
 66 | 					Description:  "some description",
 67 | 					Statement:    "SELECT * FROM CQL_STATEMENT;\n",
 68 | 					AuthRequired: []string{"my-google-auth-service", "other-auth-service"},
 69 | 					Parameters: []tools.Parameter{
 70 | 						tools.NewStringParameterWithAuth("country", "some description",
 71 | 							[]tools.ParamAuthService{{Name: "my-google-auth-service", Field: "user_id"},
 72 | 								{Name: "other-auth-service", Field: "user_id"}}),
 73 | 					},
 74 | 				},
 75 | 			},
 76 | 		},
 77 | 		{
 78 | 			desc: "with template parameters",
 79 | 			in: `
 80 | 			tools:
 81 | 				example_tool:
 82 | 					kind: cassandra-cql
 83 | 					source: my-cassandra-instance
 84 | 					description: some description
 85 | 					statement: |
 86 | 						SELECT * FROM CQL_STATEMENT;
 87 | 					authRequired:
 88 | 						- my-google-auth-service
 89 | 						- other-auth-service
 90 | 					parameters:
 91 | 						- name: country
 92 | 						  type: string
 93 | 						  description: some description
 94 | 						  authServices:
 95 | 							- name: my-google-auth-service
 96 | 							  field: user_id
 97 | 							- name: other-auth-service
 98 | 							  field: user_id
 99 | 					templateParameters:
100 | 						- name: tableName
101 | 						  type: string
102 | 						  description: some description.
103 | 						- name: fieldArray
104 | 						  type: array
105 | 						  description: The columns to return for the query.
106 | 						  items: 
107 | 								name: column
108 | 								type: string
109 | 								description: A column name that will be returned from the query.
110 | 			`,
111 | 			want: server.ToolConfigs{
112 | 				"example_tool": cassandracql.Config{
113 | 					Name:         "example_tool",
114 | 					Kind:         "cassandra-cql",
115 | 					Source:       "my-cassandra-instance",
116 | 					Description:  "some description",
117 | 					Statement:    "SELECT * FROM CQL_STATEMENT;\n",
118 | 					AuthRequired: []string{"my-google-auth-service", "other-auth-service"},
119 | 					Parameters: []tools.Parameter{
120 | 						tools.NewStringParameterWithAuth("country", "some description",
121 | 							[]tools.ParamAuthService{{Name: "my-google-auth-service", Field: "user_id"},
122 | 								{Name: "other-auth-service", Field: "user_id"}}),
123 | 					},
124 | 					TemplateParameters: []tools.Parameter{
125 | 						tools.NewStringParameter("tableName", "some description."),
126 | 						tools.NewArrayParameter("fieldArray", "The columns to return for the query.", tools.NewStringParameter("column", "A column name that will be returned from the query.")),
127 | 					},
128 | 				},
129 | 			},
130 | 		},
131 | 		{
132 | 			desc: "without optional fields",
133 | 			in: `
134 | 			tools:
135 | 				example_tool:
136 | 					kind: cassandra-cql
137 | 					source: my-cassandra-instance
138 | 					description: some description
139 | 					statement: |
140 | 						SELECT * FROM CQL_STATEMENT;
141 | 			`,
142 | 			want: server.ToolConfigs{
143 | 				"example_tool": cassandracql.Config{
144 | 					Name:               "example_tool",
145 | 					Kind:               "cassandra-cql",
146 | 					Source:             "my-cassandra-instance",
147 | 					Description:        "some description",
148 | 					Statement:          "SELECT * FROM CQL_STATEMENT;\n",
149 | 					AuthRequired:       []string{},
150 | 					Parameters:         nil,
151 | 					TemplateParameters: nil,
152 | 				},
153 | 			},
154 | 		},
155 | 	}
156 | 	for _, tc := range tcs {
157 | 		t.Run(tc.desc, func(t *testing.T) {
158 | 			got := struct {
159 | 				Tools server.ToolConfigs `yaml:"tools"`
160 | 			}{}
161 | 			// Parse contents
162 | 			err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
163 | 			if err != nil {
164 | 				t.Fatalf("unable to unmarshal: %s", err)
165 | 			}
166 | 			if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
167 | 				t.Fatalf("incorrect parse: diff %v", diff)
168 | 			}
169 | 		})
170 | 	}
171 | }
172 | 
```

--------------------------------------------------------------------------------
/docs/en/resources/tools/alloydbainl/alloydb-ai-nl.md:
--------------------------------------------------------------------------------

```markdown
  1 | ---
  2 | title: "alloydb-ai-nl"
  3 | type: docs
  4 | weight: 1
  5 | description: >
  6 |   The "alloydb-ai-nl" tool leverages
  7 |   [AlloyDB AI](https://cloud.google.com/alloydb/ai) next-generation Natural
  8 |   Language support to provide the ability to query the database directly using
  9 |   natural language.
 10 | aliases:
 11 | - /resources/tools/alloydb-ai-nl
 12 | ---
 13 | 
 14 | ## About
 15 | 
 16 | The `alloydb-ai-nl` tool leverages [AlloyDB AI next-generation natural
 17 | Language][alloydb-ai-nl-overview] support to allow an Agent the ability to query
 18 | the database directly using natural language. Natural language streamlines the
 19 | development of generative AI applications by transferring the complexity of
 20 | converting natural language to SQL from the application layer to the database
 21 | layer.
 22 | 
 23 | This tool is compatible with the following sources:
 24 | 
 25 | - [alloydb-postgres](../../sources/alloydb-pg.md)
 26 | 
 27 | AlloyDB AI Natural Language delivers secure and accurate responses for
 28 | application end user natural language questions. Natural language streamlines
 29 | the development of generative AI applications by transferring the complexity
 30 | of converting natural language to SQL from the application layer to the
 31 | database layer.
 32 | 
 33 | ## Requirements
 34 | 
 35 | {{< notice tip >}} AlloyDB AI natural language is currently in gated public
 36 | preview. For more information on availability and limitations, please see
 37 | [AlloyDB AI natural language overview](https://cloud.google.com/alloydb/docs/ai/natural-language-overview)
 38 | {{< /notice >}}
 39 | 
 40 | To enable AlloyDB AI natural language for your AlloyDB cluster, please follow
 41 | the steps listed in the [Generate SQL queries that answer natural language
 42 | questions][alloydb-ai-gen-nl], including enabling the extension and configuring
 43 | context for your application.
 44 | 
 45 | [alloydb-ai-nl-overview]: https://cloud.google.com/alloydb/docs/ai/natural-language-overview
 46 | [alloydb-ai-gen-nl]: https://cloud.google.com/alloydb/docs/ai/generate-sql-queries-natural-language
 47 | 
 48 | ## Configuration
 49 | 
 50 | ### Specifying an `nl_config`
 51 | 
 52 | A `nl_config` is a configuration that associates an application to schema
 53 | objects, examples and other contexts that can be used. A large application can
 54 | also use different configurations for different parts of the app, as long as the
 55 | correct configuration can be specified when a question is sent from that part of
 56 | the application.
 57 | 
 58 | Once you've followed the steps for configuring context, you can use the
 59 | `context` field when configuring a `alloydb-ai-nl` tool. When this tool is
 60 | invoked, the SQL will be generated and executed using this context.
 61 | 
 62 | ### Specifying Parameters to PSV's
 63 | 
 64 | [Parameterized Secure Views (PSVs)][alloydb-psv] are a feature unique to AlloyDB
 65 | that allows you to require one or more named parameter values passed
 66 | to the view when querying it, somewhat like bind variables with ordinary
 67 | database queries.
 68 | 
 69 | You can use the `nlConfigParameters` to list the parameters required for your
 70 | `nl_config`. You **must** supply all parameters required for all PSVs in the
 71 | context. It's strongly recommended to use features like [Authenticated
 72 | Parameters](../#array-parameters) or Bound Parameters to provide secure
 73 | access to queries generated using natural language, as these parameters are not
 74 | visible to the LLM.
 75 | 
 76 | [alloydb-psv]: https://cloud.google.com/alloydb/docs/parameterized-secure-views-overview
 77 | 
 78 | {{< notice tip >}} Make sure to enable the `parameterized_views` extension before running this tool. You can do so by running this command in the AlloyDB studio:
 79 | ```sql
 80 | CREATE EXTENSION IF NOT EXISTS parameterized_views;
 81 | ```
 82 | {{< /notice >}}
 83 | 
 84 | ## Example
 85 | 
 86 | ```yaml
 87 | tools:
 88 |   ask_questions:
 89 |     kind: alloydb-ai-nl
 90 |     source: my-alloydb-source
 91 |     description: "Ask questions to check information about flights"
 92 |     nlConfig: "cymbal_air_nl_config"
 93 |     nlConfigParameters:
 94 |       - name: user_email
 95 |         type: string
 96 |         description: User ID of the logged in user.
 97 |         # note: we strongly recommend using features like Authenticated or
 98 |         # Bound parameters to prevent the LLM from seeing these params and
 99 |         # specifying values it shouldn't in the tool input
100 |         authServices:
101 |           - name: my_google_service
102 |             field: email
103 | ```
104 | ## Reference
105 | 
106 | | **field**          |                  **type**                  | **required** | **description**                                                          |
107 | |--------------------|:------------------------------------------:|:------------:|--------------------------------------------------------------------------|
108 | | kind               |                   string                   |     true     | Must be "alloydb-ai-nl".                                                 |
109 | | source             |                   string                   |     true     | Name of the AlloyDB source the natural language query should execute on. |
110 | | description        |                   string                   |     true     | Description of the tool that is passed to the LLM.                       |
111 | | nlConfig           |                   string                   |     true     | The name of the  `nl_config` in AlloyDB                                  |
112 | | nlConfigParameters | [parameters](../#specifying-parameters) |     true     | List of PSV parameters defined in the `nl_config`                        |
113 | 
```

--------------------------------------------------------------------------------
/internal/sources/couchbase/couchbase.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 couchbase
 16 | 
 17 | import (
 18 | 	"context"
 19 | 	"crypto/tls"
 20 | 	"fmt"
 21 | 	"os"
 22 | 
 23 | 	"github.com/couchbase/gocb/v2"
 24 | 	tlsutil "github.com/couchbase/tools-common/http/tls"
 25 | 	"github.com/goccy/go-yaml"
 26 | 	"github.com/googleapis/genai-toolbox/internal/sources"
 27 | 	"go.opentelemetry.io/otel/trace"
 28 | )
 29 | 
 30 | const SourceKind string = "couchbase"
 31 | 
 32 | // validate interface
 33 | var _ sources.SourceConfig = Config{}
 34 | 
 35 | func init() {
 36 | 	if !sources.Register(SourceKind, newConfig) {
 37 | 		panic(fmt.Sprintf("source kind %q already registered", SourceKind))
 38 | 	}
 39 | }
 40 | 
 41 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) {
 42 | 	actual := Config{Name: name}
 43 | 	if err := decoder.DecodeContext(ctx, &actual); err != nil {
 44 | 		return nil, err
 45 | 	}
 46 | 	return actual, nil
 47 | }
 48 | 
 49 | type Config struct {
 50 | 	Name                 string `yaml:"name" validate:"required"`
 51 | 	Kind                 string `yaml:"kind" validate:"required"`
 52 | 	ConnectionString     string `yaml:"connectionString" validate:"required"`
 53 | 	Bucket               string `yaml:"bucket" validate:"required"`
 54 | 	Scope                string `yaml:"scope" validate:"required"`
 55 | 	Username             string `yaml:"username"`
 56 | 	Password             string `yaml:"password"`
 57 | 	ClientCert           string `yaml:"clientCert"`
 58 | 	ClientCertPassword   string `yaml:"clientCertPassword"`
 59 | 	ClientKey            string `yaml:"clientKey"`
 60 | 	ClientKeyPassword    string `yaml:"clientKeyPassword"`
 61 | 	CACert               string `yaml:"caCert"`
 62 | 	NoSSLVerify          bool   `yaml:"noSslVerify"`
 63 | 	Profile              string `yaml:"profile"`
 64 | 	QueryScanConsistency uint   `yaml:"queryScanConsistency"`
 65 | }
 66 | 
 67 | func (r Config) SourceConfigKind() string {
 68 | 	return SourceKind
 69 | }
 70 | 
 71 | func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) {
 72 | 
 73 | 	opts, err := r.createCouchbaseOptions()
 74 | 	if err != nil {
 75 | 		return nil, err
 76 | 	}
 77 | 	cluster, err := gocb.Connect(r.ConnectionString, opts)
 78 | 	if err != nil {
 79 | 		return nil, err
 80 | 	}
 81 | 
 82 | 	scope := cluster.Bucket(r.Bucket).Scope(r.Scope)
 83 | 	s := &Source{
 84 | 		Name:                 r.Name,
 85 | 		Kind:                 SourceKind,
 86 | 		QueryScanConsistency: r.QueryScanConsistency,
 87 | 		Scope:                scope,
 88 | 	}
 89 | 	return s, nil
 90 | }
 91 | 
 92 | var _ sources.Source = &Source{}
 93 | 
 94 | type Source struct {
 95 | 	Name                 string `yaml:"name"`
 96 | 	Kind                 string `yaml:"kind"`
 97 | 	QueryScanConsistency uint   `yaml:"queryScanConsistency"`
 98 | 	Scope                *gocb.Scope
 99 | }
100 | 
101 | func (s *Source) SourceKind() string {
102 | 	return SourceKind
103 | }
104 | 
105 | func (s *Source) CouchbaseScope() *gocb.Scope {
106 | 	return s.Scope
107 | }
108 | 
109 | func (s *Source) CouchbaseQueryScanConsistency() uint {
110 | 	return s.QueryScanConsistency
111 | }
112 | 
113 | func (r Config) createCouchbaseOptions() (gocb.ClusterOptions, error) {
114 | 	cbOpts := gocb.ClusterOptions{}
115 | 
116 | 	if r.Username != "" {
117 | 		auth := gocb.PasswordAuthenticator{
118 | 			Username: r.Username,
119 | 			Password: r.Password,
120 | 		}
121 | 		cbOpts.Authenticator = auth
122 | 	}
123 | 
124 | 	var clientCert, clientKey, caCert []byte
125 | 	var err error
126 | 	if r.ClientCert != "" {
127 | 		clientCert, err = os.ReadFile(r.ClientCert)
128 | 		if err != nil {
129 | 			return gocb.ClusterOptions{}, err
130 | 		}
131 | 	}
132 | 
133 | 	if r.ClientKey != "" {
134 | 		clientKey, err = os.ReadFile(r.ClientKey)
135 | 		if err != nil {
136 | 			return gocb.ClusterOptions{}, err
137 | 		}
138 | 	}
139 | 	if r.CACert != "" {
140 | 		caCert, err = os.ReadFile(r.CACert)
141 | 		if err != nil {
142 | 			return gocb.ClusterOptions{}, err
143 | 		}
144 | 	}
145 | 	if clientCert != nil || caCert != nil {
146 | 		// tls parsing code is similar to the code used in the cbimport.
147 | 		tlsConfig, err := tlsutil.NewConfig(tlsutil.ConfigOptions{
148 | 			ClientCert:     clientCert,
149 | 			ClientKey:      clientKey,
150 | 			Password:       []byte(getCertKeyPassword(r.ClientCertPassword, r.ClientKeyPassword)),
151 | 			ClientAuthType: tls.VerifyClientCertIfGiven,
152 | 			RootCAs:        caCert,
153 | 			NoSSLVerify:    r.NoSSLVerify,
154 | 		})
155 | 		if err != nil {
156 | 			return gocb.ClusterOptions{}, err
157 | 		}
158 | 
159 | 		if r.ClientCert != "" {
160 | 			auth := gocb.CertificateAuthenticator{
161 | 				ClientCertificate: &tlsConfig.Certificates[0],
162 | 			}
163 | 			cbOpts.Authenticator = auth
164 | 		}
165 | 		if r.CACert != "" {
166 | 			cbOpts.SecurityConfig = gocb.SecurityConfig{
167 | 				TLSSkipVerify: r.NoSSLVerify,
168 | 				TLSRootCAs:    tlsConfig.RootCAs,
169 | 			}
170 | 		}
171 | 		if r.NoSSLVerify {
172 | 			cbOpts.SecurityConfig = gocb.SecurityConfig{
173 | 				TLSSkipVerify: r.NoSSLVerify,
174 | 			}
175 | 		}
176 | 	}
177 | 	if r.Profile != "" {
178 | 		err = cbOpts.ApplyProfile(gocb.ClusterConfigProfile(r.Profile))
179 | 		if err != nil {
180 | 			return gocb.ClusterOptions{}, err
181 | 		}
182 | 	}
183 | 	return cbOpts, nil
184 | }
185 | 
186 | // GetCertKeyPassword - Returns the password which should be used when creating a new TLS config.
187 | func getCertKeyPassword(certPassword, keyPassword string) string {
188 | 	if keyPassword != "" {
189 | 		return keyPassword
190 | 	}
191 | 
192 | 	return certPassword
193 | }
194 | 
```

--------------------------------------------------------------------------------
/internal/tools/firestore/util/validator_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 util
 16 | 
 17 | import (
 18 | 	"strings"
 19 | 	"testing"
 20 | )
 21 | 
 22 | func TestValidateCollectionPath(t *testing.T) {
 23 | 	tests := []struct {
 24 | 		name    string
 25 | 		path    string
 26 | 		wantErr bool
 27 | 		errMsg  string
 28 | 	}{
 29 | 		// Valid cases
 30 | 		{
 31 | 			name:    "valid root collection",
 32 | 			path:    "users",
 33 | 			wantErr: false,
 34 | 		},
 35 | 		{
 36 | 			name:    "valid subcollection",
 37 | 			path:    "users/user123/posts",
 38 | 			wantErr: false,
 39 | 		},
 40 | 		{
 41 | 			name:    "valid deeply nested",
 42 | 			path:    "users/user123/posts/post456/comments",
 43 | 			wantErr: false,
 44 | 		},
 45 | 
 46 | 		// Invalid cases
 47 | 		{
 48 | 			name:    "empty path",
 49 | 			path:    "",
 50 | 			wantErr: true,
 51 | 			errMsg:  "collection path cannot be empty",
 52 | 		},
 53 | 		{
 54 | 			name:    "even segments (document path)",
 55 | 			path:    "users/user123",
 56 | 			wantErr: true,
 57 | 			errMsg:  "must have an odd number of segments",
 58 | 		},
 59 | 		{
 60 | 			name:    "absolute path",
 61 | 			path:    "projects/my-project/databases/(default)/documents/users",
 62 | 			wantErr: true,
 63 | 			errMsg:  "path must be relative",
 64 | 		},
 65 | 		{
 66 | 			name:    "reserved prefix __",
 67 | 			path:    "__users",
 68 | 			wantErr: true,
 69 | 			errMsg:  "collection ID cannot start with '__'",
 70 | 		},
 71 | 		{
 72 | 			name:    "dot segment",
 73 | 			path:    "users/./posts",
 74 | 			wantErr: true,
 75 | 			errMsg:  "segment cannot be '.'",
 76 | 		},
 77 | 		{
 78 | 			name:    "double slashes",
 79 | 			path:    "users//posts",
 80 | 			wantErr: true,
 81 | 			errMsg:  "segment cannot be empty",
 82 | 		},
 83 | 		{
 84 | 			name:    "trailing slash",
 85 | 			path:    "users/",
 86 | 			wantErr: true,
 87 | 			errMsg:  "must have an odd number of segments",
 88 | 		},
 89 | 		{
 90 | 			name:    "whitespace only segment",
 91 | 			path:    "users/   /posts",
 92 | 			wantErr: true,
 93 | 			errMsg:  "segment cannot be only whitespace",
 94 | 		},
 95 | 		{
 96 | 			name:    "tab whitespace segment",
 97 | 			path:    "users/\t/posts",
 98 | 			wantErr: true,
 99 | 			errMsg:  "segment cannot be only whitespace",
100 | 		},
101 | 	}
102 | 
103 | 	for _, tt := range tests {
104 | 		t.Run(tt.name, func(t *testing.T) {
105 | 			err := ValidateCollectionPath(tt.path)
106 | 			if tt.wantErr {
107 | 				if err == nil {
108 | 					t.Errorf("ValidateCollectionPath(%q) expected error but got none", tt.path)
109 | 				} else if tt.errMsg != "" && !strings.Contains(err.Error(), tt.errMsg) {
110 | 					t.Errorf("ValidateCollectionPath(%q) error = %v, want error containing %q", tt.path, err, tt.errMsg)
111 | 				}
112 | 			} else {
113 | 				if err != nil {
114 | 					t.Errorf("ValidateCollectionPath(%q) unexpected error: %v", tt.path, err)
115 | 				}
116 | 			}
117 | 		})
118 | 	}
119 | }
120 | 
121 | func TestValidateDocumentPath(t *testing.T) {
122 | 	tests := []struct {
123 | 		name    string
124 | 		path    string
125 | 		wantErr bool
126 | 		errMsg  string
127 | 	}{
128 | 		// Valid cases
129 | 		{
130 | 			name:    "valid root document",
131 | 			path:    "users/user123",
132 | 			wantErr: false,
133 | 		},
134 | 		{
135 | 			name:    "valid nested document",
136 | 			path:    "users/user123/posts/post456",
137 | 			wantErr: false,
138 | 		},
139 | 		{
140 | 			name:    "valid deeply nested",
141 | 			path:    "users/user123/posts/post456/comments/comment789",
142 | 			wantErr: false,
143 | 		},
144 | 
145 | 		// Invalid cases
146 | 		{
147 | 			name:    "empty path",
148 | 			path:    "",
149 | 			wantErr: true,
150 | 			errMsg:  "document path cannot be empty",
151 | 		},
152 | 		{
153 | 			name:    "odd segments (collection path)",
154 | 			path:    "users",
155 | 			wantErr: true,
156 | 			errMsg:  "must have an even number of segments",
157 | 		},
158 | 		{
159 | 			name:    "absolute path",
160 | 			path:    "projects/my-project/databases/(default)/documents/users/user123",
161 | 			wantErr: true,
162 | 			errMsg:  "path must be relative",
163 | 		},
164 | 		{
165 | 			name:    "reserved prefix __",
166 | 			path:    "users/__user123",
167 | 			wantErr: true,
168 | 			errMsg:  "document ID cannot start with '__'",
169 | 		},
170 | 		{
171 | 			name:    "double dot segment",
172 | 			path:    "users/..",
173 | 			wantErr: true,
174 | 			errMsg:  "segment cannot be '.'",
175 | 		},
176 | 		{
177 | 			name:    "double slashes in document path",
178 | 			path:    "users//user123",
179 | 			wantErr: true,
180 | 			errMsg:  "must have an even number of segments",
181 | 		},
182 | 		{
183 | 			name:    "trailing slash document",
184 | 			path:    "users/user123/",
185 | 			wantErr: true,
186 | 			errMsg:  "must have an even number of segments",
187 | 		},
188 | 		{
189 | 			name:    "whitespace only document ID",
190 | 			path:    "users/   ",
191 | 			wantErr: true,
192 | 			errMsg:  "segment cannot be only whitespace",
193 | 		},
194 | 		{
195 | 			name:    "whitespace in middle segment",
196 | 			path:    "users/user123/posts/ \t ",
197 | 			wantErr: true,
198 | 			errMsg:  "segment cannot be only whitespace",
199 | 		},
200 | 	}
201 | 
202 | 	for _, tt := range tests {
203 | 		t.Run(tt.name, func(t *testing.T) {
204 | 			err := ValidateDocumentPath(tt.path)
205 | 			if tt.wantErr {
206 | 				if err == nil {
207 | 					t.Errorf("ValidateDocumentPath(%q) expected error but got none", tt.path)
208 | 				} else if tt.errMsg != "" && !strings.Contains(err.Error(), tt.errMsg) {
209 | 					t.Errorf("ValidateDocumentPath(%q) error = %v, want error containing %q", tt.path, err, tt.errMsg)
210 | 				}
211 | 			} else {
212 | 				if err != nil {
213 | 					t.Errorf("ValidateDocumentPath(%q) unexpected error: %v", tt.path, err)
214 | 				}
215 | 			}
216 | 		})
217 | 	}
218 | }
219 | 
```

--------------------------------------------------------------------------------
/internal/log/log.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 log
 16 | 
 17 | import (
 18 | 	"context"
 19 | 	"fmt"
 20 | 	"io"
 21 | 	"log/slog"
 22 | 	"strings"
 23 | )
 24 | 
 25 | // StdLogger is the standard logger
 26 | type StdLogger struct {
 27 | 	outLogger *slog.Logger
 28 | 	errLogger *slog.Logger
 29 | }
 30 | 
 31 | // NewStdLogger create a Logger that uses out and err for informational and error messages.
 32 | func NewStdLogger(outW, errW io.Writer, logLevel string) (Logger, error) {
 33 | 	//Set log level
 34 | 	var programLevel = new(slog.LevelVar)
 35 | 	slogLevel, err := SeverityToLevel(logLevel)
 36 | 	if err != nil {
 37 | 		return nil, err
 38 | 	}
 39 | 	programLevel.Set(slogLevel)
 40 | 
 41 | 	handlerOptions := &slog.HandlerOptions{Level: programLevel}
 42 | 
 43 | 	return &StdLogger{
 44 | 		outLogger: slog.New(NewValueTextHandler(outW, handlerOptions)),
 45 | 		errLogger: slog.New(NewValueTextHandler(errW, handlerOptions)),
 46 | 	}, nil
 47 | }
 48 | 
 49 | // DebugContext logs debug messages
 50 | func (sl *StdLogger) DebugContext(ctx context.Context, msg string, keysAndValues ...interface{}) {
 51 | 	sl.outLogger.DebugContext(ctx, msg, keysAndValues...)
 52 | }
 53 | 
 54 | // InfoContext logs debug messages
 55 | func (sl *StdLogger) InfoContext(ctx context.Context, msg string, keysAndValues ...interface{}) {
 56 | 	sl.outLogger.InfoContext(ctx, msg, keysAndValues...)
 57 | }
 58 | 
 59 | // WarnContext logs warning messages
 60 | func (sl *StdLogger) WarnContext(ctx context.Context, msg string, keysAndValues ...interface{}) {
 61 | 	sl.errLogger.WarnContext(ctx, msg, keysAndValues...)
 62 | }
 63 | 
 64 | // ErrorContext logs error messages
 65 | func (sl *StdLogger) ErrorContext(ctx context.Context, msg string, keysAndValues ...interface{}) {
 66 | 	sl.errLogger.ErrorContext(ctx, msg, keysAndValues...)
 67 | }
 68 | 
 69 | const (
 70 | 	Debug = "DEBUG"
 71 | 	Info  = "INFO"
 72 | 	Warn  = "WARN"
 73 | 	Error = "ERROR"
 74 | )
 75 | 
 76 | // Returns severity level based on string.
 77 | func SeverityToLevel(s string) (slog.Level, error) {
 78 | 	switch strings.ToUpper(s) {
 79 | 	case Debug:
 80 | 		return slog.LevelDebug, nil
 81 | 	case Info:
 82 | 		return slog.LevelInfo, nil
 83 | 	case Warn:
 84 | 		return slog.LevelWarn, nil
 85 | 	case Error:
 86 | 		return slog.LevelError, nil
 87 | 	default:
 88 | 		return slog.Level(-5), fmt.Errorf("invalid log level")
 89 | 	}
 90 | }
 91 | 
 92 | // Returns severity string based on level.
 93 | func levelToSeverity(s string) (string, error) {
 94 | 	switch s {
 95 | 	case slog.LevelDebug.String():
 96 | 		return Debug, nil
 97 | 	case slog.LevelInfo.String():
 98 | 		return Info, nil
 99 | 	case slog.LevelWarn.String():
100 | 		return Warn, nil
101 | 	case slog.LevelError.String():
102 | 		return Error, nil
103 | 	default:
104 | 		return "", fmt.Errorf("invalid slog level")
105 | 	}
106 | }
107 | 
108 | type StructuredLogger struct {
109 | 	outLogger *slog.Logger
110 | 	errLogger *slog.Logger
111 | }
112 | 
113 | // NewStructuredLogger create a Logger that logs messages using JSON.
114 | func NewStructuredLogger(outW, errW io.Writer, logLevel string) (Logger, error) {
115 | 	//Set log level
116 | 	var programLevel = new(slog.LevelVar)
117 | 	slogLevel, err := SeverityToLevel(logLevel)
118 | 	if err != nil {
119 | 		return nil, err
120 | 	}
121 | 	programLevel.Set(slogLevel)
122 | 
123 | 	replace := func(groups []string, a slog.Attr) slog.Attr {
124 | 		switch a.Key {
125 | 		case slog.LevelKey:
126 | 			value := a.Value.String()
127 | 			sev, _ := levelToSeverity(value)
128 | 			return slog.Attr{
129 | 				Key:   "severity",
130 | 				Value: slog.StringValue(sev),
131 | 			}
132 | 		case slog.MessageKey:
133 | 			return slog.Attr{
134 | 				Key:   "message",
135 | 				Value: a.Value,
136 | 			}
137 | 		case slog.SourceKey:
138 | 			return slog.Attr{
139 | 				Key:   "logging.googleapis.com/sourceLocation",
140 | 				Value: a.Value,
141 | 			}
142 | 		case slog.TimeKey:
143 | 			return slog.Attr{
144 | 				Key:   "timestamp",
145 | 				Value: a.Value,
146 | 			}
147 | 		}
148 | 		return a
149 | 	}
150 | 
151 | 	// Configure structured logs to adhere to Cloud LogEntry format
152 | 	// https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry
153 | 	outHandler := handlerWithSpanContext(slog.NewJSONHandler(outW, &slog.HandlerOptions{
154 | 		AddSource:   true,
155 | 		Level:       programLevel,
156 | 		ReplaceAttr: replace,
157 | 	}))
158 | 	errHandler := handlerWithSpanContext(slog.NewJSONHandler(errW, &slog.HandlerOptions{
159 | 		AddSource:   true,
160 | 		Level:       programLevel,
161 | 		ReplaceAttr: replace,
162 | 	}))
163 | 
164 | 	return &StructuredLogger{outLogger: slog.New(outHandler), errLogger: slog.New(errHandler)}, nil
165 | }
166 | 
167 | // DebugContext logs debug messages
168 | func (sl *StructuredLogger) DebugContext(ctx context.Context, msg string, keysAndValues ...interface{}) {
169 | 	sl.outLogger.DebugContext(ctx, msg, keysAndValues...)
170 | }
171 | 
172 | // InfoContext logs info messages
173 | func (sl *StructuredLogger) InfoContext(ctx context.Context, msg string, keysAndValues ...interface{}) {
174 | 	sl.outLogger.InfoContext(ctx, msg, keysAndValues...)
175 | }
176 | 
177 | // WarnContext logs warning messages
178 | func (sl *StructuredLogger) WarnContext(ctx context.Context, msg string, keysAndValues ...interface{}) {
179 | 	sl.errLogger.WarnContext(ctx, msg, keysAndValues...)
180 | }
181 | 
182 | // ErrorContext logs error messages
183 | func (sl *StructuredLogger) ErrorContext(ctx context.Context, msg string, keysAndValues ...interface{}) {
184 | 	sl.errLogger.ErrorContext(ctx, msg, keysAndValues...)
185 | }
186 | 
```

--------------------------------------------------------------------------------
/internal/tools/yugabytedbsql/yugabytedbsql.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 yugabytedbsql
 16 | 
 17 | import (
 18 | 	"context"
 19 | 	"fmt"
 20 | 
 21 | 	yaml "github.com/goccy/go-yaml"
 22 | 	"github.com/googleapis/genai-toolbox/internal/sources"
 23 | 	"github.com/googleapis/genai-toolbox/internal/sources/yugabytedb"
 24 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 25 | 	"github.com/yugabyte/pgx/v5/pgxpool"
 26 | )
 27 | 
 28 | const kind string = "yugabytedb-sql"
 29 | 
 30 | func init() {
 31 | 	if !tools.Register(kind, newConfig) {
 32 | 		panic(fmt.Sprintf("tool kind %q already registered", kind))
 33 | 	}
 34 | }
 35 | 
 36 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
 37 | 	actual := Config{Name: name}
 38 | 	if err := decoder.DecodeContext(ctx, &actual); err != nil {
 39 | 		return nil, err
 40 | 	}
 41 | 	return actual, nil
 42 | }
 43 | 
 44 | type compatibleSource interface {
 45 | 	YugabyteDBPool() *pgxpool.Pool
 46 | }
 47 | 
 48 | var compatibleSources = [...]string{yugabytedb.SourceKind}
 49 | 
 50 | type Config struct {
 51 | 	Name               string           `yaml:"name" validate:"required"`
 52 | 	Kind               string           `yaml:"kind" validate:"required"`
 53 | 	Source             string           `yaml:"source" validate:"required"`
 54 | 	Description        string           `yaml:"description" validate:"required"`
 55 | 	Statement          string           `yaml:"statement" validate:"required"`
 56 | 	AuthRequired       []string         `yaml:"authRequired"`
 57 | 	Parameters         tools.Parameters `yaml:"parameters"`
 58 | 	TemplateParameters tools.Parameters `yaml:"templateParameters"`
 59 | }
 60 | 
 61 | // validate interface
 62 | var _ tools.ToolConfig = Config{}
 63 | 
 64 | func (cfg Config) ToolConfigKind() string {
 65 | 	return kind
 66 | }
 67 | 
 68 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
 69 | 	// verify source exists
 70 | 	rawS, ok := srcs[cfg.Source]
 71 | 	if !ok {
 72 | 		return nil, fmt.Errorf("no source named %q configured", cfg.Source)
 73 | 	}
 74 | 
 75 | 	// verify the source is compatible
 76 | 	s, ok := rawS.(compatibleSource)
 77 | 	if !ok {
 78 | 		return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
 79 | 	}
 80 | 
 81 | 	allParameters, paramManifest, err := tools.ProcessParameters(cfg.TemplateParameters, cfg.Parameters)
 82 | 	if err != nil {
 83 | 		return nil, err
 84 | 	}
 85 | 
 86 | 	mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, allParameters)
 87 | 
 88 | 	// finish tool setup
 89 | 	t := Tool{
 90 | 		Name:               cfg.Name,
 91 | 		Kind:               kind,
 92 | 		Parameters:         cfg.Parameters,
 93 | 		TemplateParameters: cfg.TemplateParameters,
 94 | 		AllParams:          allParameters,
 95 | 		Statement:          cfg.Statement,
 96 | 		AuthRequired:       cfg.AuthRequired,
 97 | 		Pool:               s.YugabyteDBPool(),
 98 | 		manifest:           tools.Manifest{Description: cfg.Description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired},
 99 | 		mcpManifest:        mcpManifest,
100 | 	}
101 | 	return t, nil
102 | }
103 | 
104 | // validate interface
105 | var _ tools.Tool = Tool{}
106 | 
107 | type Tool struct {
108 | 	Name               string           `yaml:"name"`
109 | 	Kind               string           `yaml:"kind"`
110 | 	AuthRequired       []string         `yaml:"authRequired"`
111 | 	Parameters         tools.Parameters `yaml:"parameters"`
112 | 	TemplateParameters tools.Parameters `yaml:"templateParameters"`
113 | 	AllParams          tools.Parameters `yaml:"allParams"`
114 | 
115 | 	Pool        *pgxpool.Pool
116 | 	Statement   string
117 | 	manifest    tools.Manifest
118 | 	mcpManifest tools.McpManifest
119 | }
120 | 
121 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
122 | 	paramsMap := params.AsMap()
123 | 	newStatement, err := tools.ResolveTemplateParams(t.TemplateParameters, t.Statement, paramsMap)
124 | 	if err != nil {
125 | 		return nil, fmt.Errorf("unable to extract template params %w", err)
126 | 	}
127 | 
128 | 	newParams, err := tools.GetParams(t.Parameters, paramsMap)
129 | 	if err != nil {
130 | 		return nil, fmt.Errorf("unable to extract standard params %w", err)
131 | 	}
132 | 	sliceParams := newParams.AsSlice()
133 | 	results, err := t.Pool.Query(ctx, newStatement, sliceParams...)
134 | 	if err != nil {
135 | 		return nil, fmt.Errorf("unable to execute query: %w", err)
136 | 	}
137 | 
138 | 	fields := results.FieldDescriptions()
139 | 
140 | 	var out []any
141 | 	for results.Next() {
142 | 		v, err := results.Values()
143 | 		if err != nil {
144 | 			return nil, fmt.Errorf("unable to parse row: %w", err)
145 | 		}
146 | 		vMap := make(map[string]any)
147 | 		for i, f := range fields {
148 | 			vMap[f.Name] = v[i]
149 | 		}
150 | 		out = append(out, vMap)
151 | 	}
152 | 
153 | 	return out, nil
154 | }
155 | 
156 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
157 | 	return tools.ParseParams(t.AllParams, data, claims)
158 | }
159 | 
160 | func (t Tool) Manifest() tools.Manifest {
161 | 	return t.manifest
162 | }
163 | 
164 | func (t Tool) McpManifest() tools.McpManifest {
165 | 	return t.mcpManifest
166 | }
167 | 
168 | func (t Tool) Authorized(verifiedAuthServices []string) bool {
169 | 	return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
170 | }
171 | 
172 | func (t Tool) RequiresClientAuthorization() bool {
173 | 	return false
174 | }
175 | 
```

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

```markdown
  1 | ---
  2 | title: "Cloud SQL for SQL Server"
  3 | linkTitle: "Cloud SQL (SQL Server)"
  4 | type: docs
  5 | weight: 1
  6 | description: >
  7 |   Cloud SQL for SQL Server is a fully-managed database service for SQL Server.
  8 | ---
  9 | 
 10 | ## About
 11 | 
 12 | [Cloud SQL for SQL Server][csql-mssql-docs] is a managed database service that
 13 | helps you set up, maintain, manage, and administer your SQL Server databases on
 14 | Google Cloud.
 15 | 
 16 | If you are new to Cloud SQL for SQL Server, you can try [creating and connecting
 17 | to a database by following these instructions][csql-mssql-connect].
 18 | 
 19 | [csql-mssql-docs]: https://cloud.google.com/sql/docs/sqlserver
 20 | [csql-mssql-connect]: https://cloud.google.com/sql/docs/sqlserver/connect-overview
 21 | 
 22 | ## Available Tools
 23 | 
 24 | - [`mssql-sql`](../tools/mssql/mssql-sql.md)  
 25 |   Execute pre-defined SQL Server queries with placeholder parameters.
 26 | 
 27 | - [`mssql-execute-sql`](../tools/mssql/mssql-execute-sql.md)  
 28 |   Run parameterized SQL Server queries in Cloud SQL for SQL Server.
 29 | 
 30 | - [`mssql-list-tables`](../tools/mssql/mssql-list-tables.md)  
 31 |   List tables in a Cloud SQL for SQL Server database.
 32 | 
 33 | ### Pre-built Configurations
 34 | 
 35 | - [Cloud SQL for SQL Server using MCP](https://googleapis.github.io/genai-toolbox/how-to/connect-ide/cloud_sql_mssql_mcp/)  
 36 | Connect your IDE to Cloud SQL for SQL Server using Toolbox.
 37 | 
 38 | ## Requirements
 39 | 
 40 | ### IAM Permissions
 41 | 
 42 | By default, this source uses the [Cloud SQL Go Connector][csql-go-conn] to
 43 | authorize and establish mTLS connections to your Cloud SQL instance. The Go
 44 | connector uses your [Application Default Credentials (ADC)][adc] to authorize
 45 | your connection to Cloud SQL.
 46 | 
 47 | In addition to [setting the ADC for your server][set-adc], you need to ensure
 48 | the IAM identity has been given the following IAM roles (or corresponding
 49 | permissions):
 50 | 
 51 | - `roles/cloudsql.client`
 52 | 
 53 | {{< notice tip >}}
 54 | If you are connecting from Compute Engine, make sure your VM
 55 | also has the [proper
 56 | scope](https://cloud.google.com/compute/docs/access/service-accounts#accesscopesiam)
 57 | to connect using the Cloud SQL Admin API.
 58 | {{< /notice >}}
 59 | 
 60 | [csql-go-conn]: https://github.com/GoogleCloudPlatform/cloud-sql-go-connector
 61 | [adc]: https://cloud.google.com/docs/authentication#adc
 62 | [set-adc]: https://cloud.google.com/docs/authentication/provide-credentials-adc
 63 | 
 64 | ### Networking
 65 | 
 66 | Cloud SQL supports connecting over both from external networks via the internet
 67 | ([public IP][public-ip]), and internal networks ([private IP][private-ip]).
 68 | For more information on choosing between the two options, see the Cloud SQL page
 69 | [Connection overview][conn-overview].
 70 | 
 71 | You can configure the `ipType` parameter in your source configuration to
 72 | `public` or `private` to match your cluster's configuration. Regardless of which
 73 | you choose, all connections use IAM-based authorization and are encrypted with
 74 | mTLS.
 75 | 
 76 | [private-ip]: https://cloud.google.com/sql/docs/sqlserver/configure-private-ip
 77 | [public-ip]: https://cloud.google.com/sql/docs/sqlserver/configure-ip
 78 | [conn-overview]: https://cloud.google.com/sql/docs/sqlserver/connect-overview
 79 | 
 80 | ### Database User
 81 | 
 82 | Currently, this source only uses standard authentication. You will need to
 83 | [create a SQL Server user][cloud-sql-users] to login to the database with.
 84 | 
 85 | [cloud-sql-users]: https://cloud.google.com/sql/docs/sqlserver/create-manage-users
 86 | 
 87 | ## Example
 88 | 
 89 | ```yaml
 90 | sources:
 91 |     my-cloud-sql-mssql-instance:
 92 |      kind: cloud-sql-mssql
 93 |      project: my-project
 94 |      region: my-region
 95 |      instance: my-instance
 96 |      database: my_db
 97 |      ipAddress: localhost
 98 |      user: ${USER_NAME}
 99 |      password: ${PASSWORD}
100 |      # ipType: private
101 | ```
102 | 
103 | {{< notice tip >}}
104 | Use environment variable replacement with the format ${ENV_NAME}
105 | instead of hardcoding your secrets into the configuration file.
106 | {{< /notice >}}
107 | 
108 | ## Reference
109 | 
110 | | **field** | **type** | **required** | **description**                                                                                      |
111 | |-----------|:--------:|:------------:|------------------------------------------------------------------------------------------------------|
112 | | kind      |  string  |     true     | Must be "cloud-sql-mssql".                                                                           |
113 | | project   |  string  |     true     | Id of the GCP project that the cluster was created in (e.g. "my-project-id").                        |
114 | | region    |  string  |     true     | Name of the GCP region that the cluster was created in (e.g. "us-central1").                         |
115 | | instance  |  string  |     true     | Name of the Cloud SQL instance within the cluster (e.g. "my-instance").                              |
116 | | database  |  string  |     true     | Name of the Cloud SQL database to connect to (e.g. "my_db").                                         |
117 | | ipAddress |  string  |     true     | IP address of the Cloud SQL instance to connect to.                                                  |
118 | | user      |  string  |     true     | Name of the SQL Server user to connect as (e.g. "my-pg-user").                                       |
119 | | password  |  string  |     true     | Password of the SQL Server user (e.g. "my-password").                                                |
120 | | ipType    |  string  |    false     | IP Type of the Cloud SQL instance, must be either `public`,  `private`, or `psc`. Default: `public`. |
121 | 
```

--------------------------------------------------------------------------------
/internal/sources/mysql/mysql_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 mysql_test
 16 | 
 17 | import (
 18 | 	"context"
 19 | 	"strings"
 20 | 	"testing"
 21 | 
 22 | 	yaml "github.com/goccy/go-yaml"
 23 | 	"github.com/google/go-cmp/cmp"
 24 | 	"github.com/google/go-cmp/cmp/cmpopts"
 25 | 	"go.opentelemetry.io/otel/trace/noop"
 26 | 
 27 | 	"github.com/googleapis/genai-toolbox/internal/server"
 28 | 	"github.com/googleapis/genai-toolbox/internal/sources/mysql"
 29 | 	"github.com/googleapis/genai-toolbox/internal/testutils"
 30 | )
 31 | 
 32 | func TestParseFromYamlCloudSQLMySQL(t *testing.T) {
 33 | 	tcs := []struct {
 34 | 		desc string
 35 | 		in   string
 36 | 		want server.SourceConfigs
 37 | 	}{
 38 | 		{
 39 | 			desc: "basic example",
 40 | 			in: `
 41 | 			sources:
 42 | 				my-mysql-instance:
 43 | 					kind: mysql
 44 | 					host: 0.0.0.0
 45 | 					port: my-port
 46 | 					database: my_db
 47 | 					user: my_user
 48 | 					password: my_pass
 49 | 			`,
 50 | 			want: server.SourceConfigs{
 51 | 				"my-mysql-instance": mysql.Config{
 52 | 					Name:     "my-mysql-instance",
 53 | 					Kind:     mysql.SourceKind,
 54 | 					Host:     "0.0.0.0",
 55 | 					Port:     "my-port",
 56 | 					Database: "my_db",
 57 | 					User:     "my_user",
 58 | 					Password: "my_pass",
 59 | 				},
 60 | 			},
 61 | 		},
 62 | 		{
 63 | 			desc: "with query timeout",
 64 | 			in: `
 65 | 			sources:
 66 | 				my-mysql-instance:
 67 | 					kind: mysql
 68 | 					host: 0.0.0.0
 69 | 					port: my-port
 70 | 					database: my_db
 71 | 					user: my_user
 72 | 					password: my_pass
 73 | 					queryTimeout: 45s
 74 | 			`,
 75 | 			want: server.SourceConfigs{
 76 | 				"my-mysql-instance": mysql.Config{
 77 | 					Name:         "my-mysql-instance",
 78 | 					Kind:         mysql.SourceKind,
 79 | 					Host:         "0.0.0.0",
 80 | 					Port:         "my-port",
 81 | 					Database:     "my_db",
 82 | 					User:         "my_user",
 83 | 					Password:     "my_pass",
 84 | 					QueryTimeout: "45s",
 85 | 				},
 86 | 			},
 87 | 		},
 88 | 		{
 89 | 			desc: "with query params",
 90 | 			in: `
 91 | 			sources:
 92 | 				my-mysql-instance:
 93 | 					kind: mysql
 94 | 					host: 0.0.0.0
 95 | 					port: my-port
 96 | 					database: my_db
 97 | 					user: my_user
 98 | 					password: my_pass
 99 | 					queryParams:
100 | 						tls: preferred
101 | 						charset: utf8mb4
102 | 			`,
103 | 			want: server.SourceConfigs{
104 | 				"my-mysql-instance": mysql.Config{
105 | 					Name:     "my-mysql-instance",
106 | 					Kind:     mysql.SourceKind,
107 | 					Host:     "0.0.0.0",
108 | 					Port:     "my-port",
109 | 					Database: "my_db",
110 | 					User:     "my_user",
111 | 					Password: "my_pass",
112 | 					QueryParams: map[string]string{
113 | 						"tls":     "preferred",
114 | 						"charset": "utf8mb4",
115 | 					},
116 | 				},
117 | 			},
118 | 		},
119 | 	}
120 | 	for _, tc := range tcs {
121 | 		t.Run(tc.desc, func(t *testing.T) {
122 | 			t.Parallel()
123 | 			got := struct {
124 | 				Sources server.SourceConfigs `yaml:"sources"`
125 | 			}{}
126 | 			// Parse contents
127 | 			err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
128 | 			if err != nil {
129 | 				t.Fatalf("unable to unmarshal: %s", err)
130 | 			}
131 | 			if diff := cmp.Diff(tc.want, got.Sources, cmpopts.EquateEmpty()); diff != "" {
132 | 				t.Fatalf("mismatch (-want +got):\n%s", diff)
133 | 			}
134 | 		})
135 | 	}
136 | 
137 | }
138 | 
139 | func TestFailParseFromYaml(t *testing.T) {
140 | 	tcs := []struct {
141 | 		desc string
142 | 		in   string
143 | 		err  string
144 | 	}{
145 | 		{
146 | 			desc: "extra field",
147 | 			in: `
148 | 			sources:
149 | 				my-mysql-instance:
150 | 					kind: mysql
151 | 					host: 0.0.0.0
152 | 					port: my-port
153 | 					database: my_db
154 | 					user: my_user
155 | 					password: my_pass
156 | 					foo: bar
157 | 			`,
158 | 			err: "unknown field \"foo\"",
159 | 		},
160 | 		{
161 | 			desc: "missing required field",
162 | 			in: `
163 | 			sources:
164 | 				my-mysql-instance:
165 | 					kind: mysql
166 | 					port: my-port
167 | 					database: my_db
168 | 					user: my_user
169 | 					password: my_pass
170 | 			`,
171 | 			err: "Field validation for 'Host' failed",
172 | 		},
173 | 		{
174 | 			desc: "invalid query params type",
175 | 			in: `
176 | 			sources:
177 | 				my-mysql-instance:
178 | 					kind: mysql
179 | 					host: 0.0.0.0
180 | 					port: 3306
181 | 					database: my_db
182 | 					user: my_user
183 | 					password: my_pass
184 | 					queryParams: not-a-map
185 | 			`,
186 | 			err: "string was used where mapping is expected",
187 | 		},
188 | 	}
189 | 	for _, tc := range tcs {
190 | 		t.Run(tc.desc, func(t *testing.T) {
191 | 			t.Parallel()
192 | 			got := struct {
193 | 				Sources server.SourceConfigs `yaml:"sources"`
194 | 			}{}
195 | 			// Parse contents
196 | 			err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
197 | 			if err == nil {
198 | 				t.Fatalf("expect parsing to fail")
199 | 			}
200 | 			errStr := err.Error()
201 | 			if !strings.Contains(errStr, tc.err) {
202 | 				t.Fatalf("unexpected error: got %q, want substring %q", errStr, tc.err)
203 | 			}
204 | 		})
205 | 	}
206 | }
207 | 
208 | // TestFailInitialization test error during initialization without attempting a DB connection.
209 | func TestFailInitialization(t *testing.T) {
210 | 	t.Parallel()
211 | 
212 | 	cfg := mysql.Config{
213 | 		Name:         "instance",
214 | 		Kind:         "mysql",
215 | 		Host:         "localhost",
216 | 		Port:         "3306",
217 | 		Database:     "db",
218 | 		User:         "user",
219 | 		Password:     "pass",
220 | 		QueryTimeout: "abc", // invalid duration
221 | 	}
222 | 	_, err := cfg.Initialize(context.Background(), noop.NewTracerProvider().Tracer("test"))
223 | 	if err == nil {
224 | 		t.Fatalf("expected error for invalid queryTimeout, got nil")
225 | 	}
226 | 	if !strings.Contains(err.Error(), "invalid queryTimeout") {
227 | 		t.Fatalf("unexpected error: %v", err)
228 | 	}
229 | }
230 | 
```
Page 15/47FirstPrevNextLast