#
tokens: 49971/50000 31/786 files (page 7/45)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 7 of 45. 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-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-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
│       │       ├── 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
│   │   │   ├── 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
│   │   │   ├── 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
│   │   ├── 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/looker/lookergetparameters/lookergetparameters_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 lookergetparameters_test
 16 | 
 17 | import (
 18 | 	"strings"
 19 | 	"testing"
 20 | 
 21 | 	yaml "github.com/goccy/go-yaml"
 22 | 	"github.com/google/go-cmp/cmp"
 23 | 	"github.com/googleapis/genai-toolbox/internal/server"
 24 | 	"github.com/googleapis/genai-toolbox/internal/testutils"
 25 | 	lkr "github.com/googleapis/genai-toolbox/internal/tools/looker/lookergetparameters"
 26 | )
 27 | 
 28 | func TestParseFromYamlLookerGetParameters(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: looker-get-parameters
 44 | 					source: my-instance
 45 | 					description: some description
 46 | 				`,
 47 | 			want: server.ToolConfigs{
 48 | 				"example_tool": lkr.Config{
 49 | 					Name:         "example_tool",
 50 | 					Kind:         "looker-get-parameters",
 51 | 					Source:       "my-instance",
 52 | 					Description:  "some description",
 53 | 					AuthRequired: []string{},
 54 | 				},
 55 | 			},
 56 | 		},
 57 | 	}
 58 | 	for _, tc := range tcs {
 59 | 		t.Run(tc.desc, func(t *testing.T) {
 60 | 			got := struct {
 61 | 				Tools server.ToolConfigs `yaml:"tools"`
 62 | 			}{}
 63 | 			// Parse contents
 64 | 			err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
 65 | 			if err != nil {
 66 | 				t.Fatalf("unable to unmarshal: %s", err)
 67 | 			}
 68 | 			if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
 69 | 				t.Fatalf("incorrect parse: diff %v", diff)
 70 | 			}
 71 | 		})
 72 | 	}
 73 | 
 74 | }
 75 | 
 76 | func TestFailParseFromYamlLookerGetParameters(t *testing.T) {
 77 | 	ctx, err := testutils.ContextWithNewLogger()
 78 | 	if err != nil {
 79 | 		t.Fatalf("unexpected error: %s", err)
 80 | 	}
 81 | 	tcs := []struct {
 82 | 		desc string
 83 | 		in   string
 84 | 		err  string
 85 | 	}{
 86 | 		{
 87 | 			desc: "Invalid method",
 88 | 			in: `
 89 | 			tools:
 90 | 				example_tool:
 91 | 					kind: looker-get-parameters
 92 | 					source: my-instance
 93 | 					method: GOT
 94 | 					description: some description
 95 | 			`,
 96 | 			err: "unable to parse tool \"example_tool\" as kind \"looker-get-parameters\": [4:1] unknown field \"method\"\n   1 | authRequired: []\n   2 | description: some description\n   3 | kind: looker-get-parameters\n>  4 | method: GOT\n       ^\n   5 | source: my-instance",
 97 | 		},
 98 | 	}
 99 | 	for _, tc := range tcs {
100 | 		t.Run(tc.desc, func(t *testing.T) {
101 | 			got := struct {
102 | 				Tools server.ToolConfigs `yaml:"tools"`
103 | 			}{}
104 | 			// Parse contents
105 | 			err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
106 | 			if err == nil {
107 | 				t.Fatalf("expect parsing to fail")
108 | 			}
109 | 			errStr := err.Error()
110 | 			if !strings.Contains(errStr, tc.err) {
111 | 				t.Fatalf("unexpected error string: got %q, want substring %q", errStr, tc.err)
112 | 			}
113 | 		})
114 | 	}
115 | 
116 | }
117 | 
```

--------------------------------------------------------------------------------
/internal/sources/neo4j/neo4j_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 neo4j_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/sources/neo4j"
 24 | 	"github.com/googleapis/genai-toolbox/internal/testutils"
 25 | )
 26 | 
 27 | func TestParseFromYamlNeo4j(t *testing.T) {
 28 | 	tcs := []struct {
 29 | 		desc string
 30 | 		in   string
 31 | 		want server.SourceConfigs
 32 | 	}{
 33 | 		{
 34 | 			desc: "basic example",
 35 | 			in: `
 36 | 			sources:
 37 | 				my-neo4j-instance:
 38 | 					kind: neo4j
 39 | 					uri: neo4j+s://my-host:7687
 40 | 					database: my_db
 41 | 					user: my_user
 42 | 					password: my_pass
 43 | 			`,
 44 | 			want: server.SourceConfigs{
 45 | 				"my-neo4j-instance": neo4j.Config{
 46 | 					Name:     "my-neo4j-instance",
 47 | 					Kind:     neo4j.SourceKind,
 48 | 					Uri:      "neo4j+s://my-host:7687",
 49 | 					Database: "my_db",
 50 | 					User:     "my_user",
 51 | 					Password: "my_pass",
 52 | 				},
 53 | 			},
 54 | 		},
 55 | 	}
 56 | 	for _, tc := range tcs {
 57 | 		t.Run(tc.desc, func(t *testing.T) {
 58 | 			got := struct {
 59 | 				Sources server.SourceConfigs `yaml:"sources"`
 60 | 			}{}
 61 | 			// Parse contents
 62 | 			err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
 63 | 			if err != nil {
 64 | 				t.Fatalf("unable to unmarshal: %s", err)
 65 | 			}
 66 | 			if !cmp.Equal(tc.want, got.Sources) {
 67 | 				t.Fatalf("incorrect parse: want %v, got %v", tc.want, got.Sources)
 68 | 			}
 69 | 		})
 70 | 	}
 71 | 
 72 | }
 73 | 
 74 | func TestFailParseFromYaml(t *testing.T) {
 75 | 	tcs := []struct {
 76 | 		desc string
 77 | 		in   string
 78 | 		err  string
 79 | 	}{
 80 | 		{
 81 | 			desc: "extra field",
 82 | 			in: `
 83 | 			sources:
 84 | 				my-neo4j-instance:
 85 | 					kind: neo4j
 86 | 					uri: neo4j+s://my-host:7687
 87 | 					database: my_db
 88 | 					user: my_user
 89 | 					password: my_pass
 90 | 					foo: bar
 91 | 			`,
 92 | 			err: "unable to parse source \"my-neo4j-instance\" as \"neo4j\": [2:1] unknown field \"foo\"\n   1 | database: my_db\n>  2 | foo: bar\n       ^\n   3 | kind: neo4j\n   4 | password: my_pass\n   5 | uri: neo4j+s://my-host:7687\n   6 | ",
 93 | 		},
 94 | 		{
 95 | 			desc: "missing required field",
 96 | 			in: `
 97 | 			sources:
 98 | 				my-neo4j-instance:
 99 | 					kind: neo4j
100 | 					uri: neo4j+s://my-host:7687
101 | 					database: my_db
102 | 					user: my_user
103 | 			`,
104 | 			err: "unable to parse source \"my-neo4j-instance\" as \"neo4j\": Key: 'Config.Password' Error:Field validation for 'Password' failed on the 'required' tag",
105 | 		},
106 | 	}
107 | 	for _, tc := range tcs {
108 | 		t.Run(tc.desc, func(t *testing.T) {
109 | 			got := struct {
110 | 				Sources server.SourceConfigs `yaml:"sources"`
111 | 			}{}
112 | 			// Parse contents
113 | 			err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
114 | 			if err == nil {
115 | 				t.Fatalf("expect parsing to fail")
116 | 			}
117 | 			errStr := err.Error()
118 | 			if errStr != tc.err {
119 | 				t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err)
120 | 			}
121 | 		})
122 | 	}
123 | }
124 | 
```

--------------------------------------------------------------------------------
/internal/tools/clickhouse/clickhouselisttables/clickhouselisttables_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 clickhouse
 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/sources"
 24 | 	"github.com/googleapis/genai-toolbox/internal/testutils"
 25 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 26 | )
 27 | 
 28 | func TestListTablesConfigToolConfigKind(t *testing.T) {
 29 | 	cfg := Config{}
 30 | 	if cfg.ToolConfigKind() != listTablesKind {
 31 | 		t.Errorf("expected %q, got %q", listTablesKind, cfg.ToolConfigKind())
 32 | 	}
 33 | }
 34 | 
 35 | func TestListTablesConfigInitializeMissingSource(t *testing.T) {
 36 | 	cfg := Config{
 37 | 		Name:        "test-list-tables",
 38 | 		Kind:        listTablesKind,
 39 | 		Source:      "missing-source",
 40 | 		Description: "Test list tables tool",
 41 | 	}
 42 | 
 43 | 	srcs := map[string]sources.Source{}
 44 | 	_, err := cfg.Initialize(srcs)
 45 | 	if err == nil {
 46 | 		t.Error("expected error for missing source")
 47 | 	}
 48 | }
 49 | 
 50 | func TestParseFromYamlClickHouseListTables(t *testing.T) {
 51 | 	ctx, err := testutils.ContextWithNewLogger()
 52 | 	if err != nil {
 53 | 		t.Fatalf("unexpected error: %s", err)
 54 | 	}
 55 | 	tcs := []struct {
 56 | 		desc string
 57 | 		in   string
 58 | 		want server.ToolConfigs
 59 | 	}{
 60 | 		{
 61 | 			desc: "basic example",
 62 | 			in: `
 63 | 			tools:
 64 | 				example_tool:
 65 | 					kind: clickhouse-list-tables
 66 | 					source: my-instance
 67 | 					description: some description
 68 | 			`,
 69 | 			want: server.ToolConfigs{
 70 | 				"example_tool": Config{
 71 | 					Name:         "example_tool",
 72 | 					Kind:         "clickhouse-list-tables",
 73 | 					Source:       "my-instance",
 74 | 					Description:  "some description",
 75 | 					AuthRequired: []string{},
 76 | 				},
 77 | 			},
 78 | 		},
 79 | 	}
 80 | 	for _, tc := range tcs {
 81 | 		t.Run(tc.desc, func(t *testing.T) {
 82 | 			got := struct {
 83 | 				Tools server.ToolConfigs `yaml:"tools"`
 84 | 			}{}
 85 | 			err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
 86 | 			if err != nil {
 87 | 				t.Fatalf("unable to unmarshal: %s", err)
 88 | 			}
 89 | 			if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
 90 | 				t.Fatalf("incorrect parse: diff %v", diff)
 91 | 			}
 92 | 		})
 93 | 	}
 94 | }
 95 | 
 96 | func TestListTablesToolParseParams(t *testing.T) {
 97 | 	databaseParam := tools.NewStringParameter("database", "The database to list tables from.")
 98 | 	tool := Tool{
 99 | 		Parameters: tools.Parameters{databaseParam},
100 | 		AllParams:  tools.Parameters{databaseParam},
101 | 	}
102 | 
103 | 	params, err := tool.ParseParams(map[string]any{"database": "test_db"}, map[string]map[string]any{})
104 | 	if err != nil {
105 | 		t.Errorf("unexpected error: %v", err)
106 | 	}
107 | 
108 | 	if len(params) != 1 {
109 | 		t.Errorf("expected 1 parameter, got %d", len(params))
110 | 	}
111 | 
112 | 	mapParams := params.AsMap()
113 | 	if mapParams["database"] != "test_db" {
114 | 		t.Errorf("expected database parameter to be 'test_db', got %v", mapParams["database"])
115 | 	}
116 | }
117 | 
```

--------------------------------------------------------------------------------
/internal/tools/looker/lookeradddashboardelement/lookeradddashboardelement_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 lookeradddashboardelement_test
 16 | 
 17 | import (
 18 | 	"strings"
 19 | 	"testing"
 20 | 
 21 | 	yaml "github.com/goccy/go-yaml"
 22 | 	"github.com/google/go-cmp/cmp"
 23 | 	"github.com/googleapis/genai-toolbox/internal/server"
 24 | 	"github.com/googleapis/genai-toolbox/internal/testutils"
 25 | 	lkr "github.com/googleapis/genai-toolbox/internal/tools/looker/lookeradddashboardelement"
 26 | )
 27 | 
 28 | func TestParseFromYamlLookerAddDashboardElement(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: looker-add-dashboard-element
 44 | 					source: my-instance
 45 | 					description: some description
 46 | 				`,
 47 | 			want: server.ToolConfigs{
 48 | 				"example_tool": lkr.Config{
 49 | 					Name:         "example_tool",
 50 | 					Kind:         "looker-add-dashboard-element",
 51 | 					Source:       "my-instance",
 52 | 					Description:  "some description",
 53 | 					AuthRequired: []string{},
 54 | 				},
 55 | 			},
 56 | 		},
 57 | 	}
 58 | 	for _, tc := range tcs {
 59 | 		t.Run(tc.desc, func(t *testing.T) {
 60 | 			got := struct {
 61 | 				Tools server.ToolConfigs `yaml:"tools"`
 62 | 			}{}
 63 | 			// Parse contents
 64 | 			err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
 65 | 			if err != nil {
 66 | 				t.Fatalf("unable to unmarshal: %s", err)
 67 | 			}
 68 | 			if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
 69 | 				t.Fatalf("incorrect parse: diff %v", diff)
 70 | 			}
 71 | 		})
 72 | 	}
 73 | 
 74 | }
 75 | 
 76 | func TestFailParseFromYamlLookerAddDashboardElement(t *testing.T) {
 77 | 	ctx, err := testutils.ContextWithNewLogger()
 78 | 	if err != nil {
 79 | 		t.Fatalf("unexpected error: %s", err)
 80 | 	}
 81 | 	tcs := []struct {
 82 | 		desc string
 83 | 		in   string
 84 | 		err  string
 85 | 	}{
 86 | 		{
 87 | 			desc: "Invalid method",
 88 | 			in: `
 89 | 			tools:
 90 | 				example_tool:
 91 | 					kind: looker-add-dashboard-element
 92 | 					source: my-instance
 93 | 					method: GOT
 94 | 					description: some description
 95 | 			`,
 96 | 			err: "unable to parse tool \"example_tool\" as kind \"looker-add-dashboard-element\": [4:1] unknown field \"method\"\n   1 | authRequired: []\n   2 | description: some description\n   3 | kind: looker-add-dashboard-element\n>  4 | method: GOT\n       ^\n   5 | source: my-instance",
 97 | 		},
 98 | 	}
 99 | 	for _, tc := range tcs {
100 | 		t.Run(tc.desc, func(t *testing.T) {
101 | 			got := struct {
102 | 				Tools server.ToolConfigs `yaml:"tools"`
103 | 			}{}
104 | 			// Parse contents
105 | 			err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
106 | 			if err == nil {
107 | 				t.Fatalf("expect parsing to fail")
108 | 			}
109 | 			errStr := err.Error()
110 | 			if !strings.Contains(errStr, tc.err) {
111 | 				t.Fatalf("unexpected error string: got %q, want substring %q", errStr, tc.err)
112 | 			}
113 | 		})
114 | 	}
115 | 
116 | }
117 | 
```

--------------------------------------------------------------------------------
/tests/cloudmonitoring/cloud_monitoring_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 cloudmonitoring
 16 | 
 17 | import (
 18 | 	"context"
 19 | 	"fmt"
 20 | 	"net/http"
 21 | 	"net/http/httptest"
 22 | 	"testing"
 23 | 
 24 | 	"github.com/google/go-cmp/cmp"
 25 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 26 | 	"github.com/googleapis/genai-toolbox/internal/tools/cloudmonitoring"
 27 | )
 28 | 
 29 | func TestTool_Invoke(t *testing.T) {
 30 | 	t.Parallel()
 31 | 
 32 | 	// Mock the monitoring server
 33 | 	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 34 | 		if r.URL.Path != "/v1/projects/test-project/location/global/prometheus/api/v1/query" {
 35 | 			http.Error(w, "not found", http.StatusNotFound)
 36 | 			return
 37 | 		}
 38 | 		query := r.URL.Query().Get("query")
 39 | 		if query != "up" {
 40 | 			http.Error(w, "bad request", http.StatusBadRequest)
 41 | 			return
 42 | 		}
 43 | 		w.Header().Set("Content-Type", "application/json")
 44 | 		fmt.Fprintln(w, `{"status":"success","data":{"resultType":"vector","result":[]}}`)
 45 | 	}))
 46 | 	defer server.Close()
 47 | 
 48 | 	// Create a new observability tool
 49 | 	tool := &cloudmonitoring.Tool{
 50 | 		Name:        "test-cloudmonitoring",
 51 | 		Kind:        "cloud-monitoring-query-prometheus",
 52 | 		Description: "Test Cloudmonitoring Tool",
 53 | 		AllParams:   tools.Parameters{},
 54 | 		BaseURL:     server.URL,
 55 | 		Client:      &http.Client{},
 56 | 	}
 57 | 
 58 | 	// Define the test parameters
 59 | 	params := tools.ParamValues{
 60 | 		{Name: "projectId", Value: "test-project"},
 61 | 		{Name: "query", Value: "up"},
 62 | 	}
 63 | 
 64 | 	// Invoke the tool
 65 | 	result, err := tool.Invoke(context.Background(), params, "")
 66 | 	if err != nil {
 67 | 		t.Fatalf("Invoke() error = %v", err)
 68 | 	}
 69 | 
 70 | 	// Check the result
 71 | 	expected := map[string]any{
 72 | 		"status": "success",
 73 | 		"data": map[string]any{
 74 | 			"resultType": "vector",
 75 | 			"result":     []any{},
 76 | 		},
 77 | 	}
 78 | 	if diff := cmp.Diff(expected, result); diff != "" {
 79 | 		t.Errorf("Invoke() result mismatch (-want +got):\n%s", diff)
 80 | 	}
 81 | }
 82 | 
 83 | func TestTool_Invoke_Error(t *testing.T) {
 84 | 	t.Parallel()
 85 | 
 86 | 	// Mock the monitoring server to return an error
 87 | 	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 88 | 		http.Error(w, "internal server error", http.StatusInternalServerError)
 89 | 	}))
 90 | 	defer server.Close()
 91 | 
 92 | 	// Create a new observability tool
 93 | 	tool := &cloudmonitoring.Tool{
 94 | 		Name:        "test-cloudmonitoring",
 95 | 		Kind:        "clou-monitoring-query-prometheus",
 96 | 		Description: "Test Cloudmonitoring Tool",
 97 | 		AllParams:   tools.Parameters{},
 98 | 		BaseURL:     server.URL,
 99 | 		Client:      &http.Client{},
100 | 	}
101 | 
102 | 	// Define the test parameters
103 | 	params := tools.ParamValues{
104 | 		{Name: "projectId", Value: "test-project"},
105 | 		{Name: "query", Value: "up"},
106 | 	}
107 | 
108 | 	// Invoke the tool
109 | 	_, err := tool.Invoke(context.Background(), params, "")
110 | 	if err == nil {
111 | 		t.Fatal("Invoke() error = nil, want error")
112 | 	}
113 | }
114 | 
```

--------------------------------------------------------------------------------
/internal/sources/firebird/firebird_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 firebird_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/sources/firebird"
 24 | 	"github.com/googleapis/genai-toolbox/internal/testutils"
 25 | )
 26 | 
 27 | func TestParseFromYamlFirebird(t *testing.T) {
 28 | 	tcs := []struct {
 29 | 		desc string
 30 | 		in   string
 31 | 		want server.SourceConfigs
 32 | 	}{
 33 | 		{
 34 | 			desc: "basic example",
 35 | 			in: `
 36 | 			sources:
 37 | 				my-fdb-instance:
 38 | 					kind: firebird
 39 | 					host: my-host
 40 | 					port: my-port
 41 | 					database: my_db
 42 | 					user: my_user
 43 | 					password: my_pass
 44 | 			`,
 45 | 			want: server.SourceConfigs{
 46 | 				"my-fdb-instance": firebird.Config{
 47 | 					Name:     "my-fdb-instance",
 48 | 					Kind:     firebird.SourceKind,
 49 | 					Host:     "my-host",
 50 | 					Port:     "my-port",
 51 | 					Database: "my_db",
 52 | 					User:     "my_user",
 53 | 					Password: "my_pass",
 54 | 				},
 55 | 			},
 56 | 		},
 57 | 	}
 58 | 	for _, tc := range tcs {
 59 | 		t.Run(tc.desc, func(t *testing.T) {
 60 | 			got := struct {
 61 | 				Sources server.SourceConfigs `yaml:"sources"`
 62 | 			}{}
 63 | 			// Parse contents
 64 | 			err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
 65 | 			if err != nil {
 66 | 				t.Fatalf("unable to unmarshal: %s", err)
 67 | 			}
 68 | 			if !cmp.Equal(tc.want, got.Sources) {
 69 | 				t.Fatalf("incorrect parse: want %v, got %v", tc.want, got.Sources)
 70 | 			}
 71 | 		})
 72 | 	}
 73 | 
 74 | }
 75 | 
 76 | func TestFailParseFromYaml(t *testing.T) {
 77 | 	tcs := []struct {
 78 | 		desc string
 79 | 		in   string
 80 | 		err  string
 81 | 	}{
 82 | 		{
 83 | 			desc: "extra field",
 84 | 			in: `
 85 | 			sources:
 86 | 				my-fdb-instance:
 87 | 					kind: firebird
 88 | 					host: my-host
 89 | 					port: my-port
 90 | 					database: my_db
 91 | 					user: my_user
 92 | 					password: my_pass
 93 | 					foo: bar
 94 | 			`,
 95 | 			err: "unable to parse source \"my-fdb-instance\" as \"firebird\": [2:1] unknown field \"foo\"\n   1 | database: my_db\n>  2 | foo: bar\n       ^\n   3 | host: my-host\n   4 | kind: firebird\n   5 | password: my_pass\n   6 | ",
 96 | 		},
 97 | 		{
 98 | 			desc: "missing required field",
 99 | 			in: `
100 | 			sources:
101 | 				my-fdb-instance:
102 | 					kind: firebird
103 | 					host: my-host
104 | 					port: my-port
105 | 					database: my_db
106 | 					user: my_user
107 | 			`,
108 | 			err: "unable to parse source \"my-fdb-instance\" as \"firebird\": Key: 'Config.Password' Error:Field validation for 'Password' failed on the 'required' tag",
109 | 		},
110 | 	}
111 | 	for _, tc := range tcs {
112 | 		t.Run(tc.desc, func(t *testing.T) {
113 | 			got := struct {
114 | 				Sources server.SourceConfigs `yaml:"sources"`
115 | 			}{}
116 | 			// Parse contents
117 | 			err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
118 | 			if err == nil {
119 | 				t.Fatalf("expect parsing to fail")
120 | 			}
121 | 			errStr := err.Error()
122 | 			if errStr != tc.err {
123 | 				t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err)
124 | 			}
125 | 		})
126 | 	}
127 | }
128 | 
```

--------------------------------------------------------------------------------
/docs/en/resources/tools/looker/looker-query.md:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | title: "looker-query"
 3 | type: docs
 4 | weight: 1
 5 | description: >
 6 |   "looker-query" runs an inline query using the Looker
 7 |   semantic model.
 8 | aliases:
 9 | - /resources/tools/looker-query
10 | ---
11 | 
12 | ## About
13 | 
14 | The `looker-query` tool runs a query using the Looker
15 | semantic model.
16 | 
17 | It's compatible with the following sources:
18 | 
19 | - [looker](../../sources/looker.md)
20 | 
21 | `looker-query` takes eight parameters:
22 | 
23 | 1. the `model`
24 | 2. the `explore`
25 | 3. the `fields` list
26 | 4. an optional set of `filters`
27 | 5. an optional set of `pivots`
28 | 6. an optional set of `sorts`
29 | 7. an optional `limit`
30 | 8. an optional `tz`
31 | 
32 | Starting in Looker v25.18, these queries can be identified in Looker's
33 | System Activity. In the History explore, use the field API Client Name
34 | to find MCP Toolbox queries.
35 | 
36 | ## Example
37 | 
38 | ```yaml
39 | tools:
40 |     query:
41 |         kind: looker-query
42 |         source: looker-source
43 |         description: |
44 |           Query Tool
45 | 
46 |           This tool is used to run a query against the LookML model. The
47 |           model, explore, and fields list must be specified. Pivots,
48 |           filters and sorts are optional.
49 | 
50 |           The model can be found from the get_models tool. The explore
51 |           can be found from the get_explores tool passing in the model.
52 |           The fields can be found from the get_dimensions, get_measures,
53 |           get_filters, and get_parameters tools, passing in the model
54 |           and the explore.
55 | 
56 |           Provide a model_id and explore_name, then a list
57 |           of fields. Optionally a list of pivots can be provided.
58 |           The pivots must also be included in the fields list.
59 | 
60 |           Filters are provided as a map of {"field.id": "condition",
61 |           "field.id2": "condition2", ...}. Do not put the field.id in
62 |           quotes. Filter expressions can be found at
63 |           https://cloud.google.com/looker/docs/filter-expressions.
64 | 
65 |           Sorts can be specified like [ "field.id desc 0" ].
66 | 
67 |           An optional row limit can be added. If not provided the limit
68 |           will default to 500. "-1" can be specified for unlimited.
69 | 
70 |           An optional query timezone can be added. The query_timezone to
71 |           will default to that of the workstation where this MCP server
72 |           is running, or Etc/UTC if that can't be determined. Not all
73 |           models support custom timezones.
74 | 
75 |           The result of the query tool is JSON
76 | ```
77 | 
78 | ## Reference
79 | 
80 | | **field**   |                  **type**                  | **required** | **description**                                                                                  |
81 | |-------------|:------------------------------------------:|:------------:|--------------------------------------------------------------------------------------------------|
82 | | kind        |                   string                   |     true     | Must be "looker-query"                                                                           |
83 | | source      |                   string                   |     true     | Name of the source the SQL should execute on.                                                    |
84 | | description |                   string                   |     true     | Description of the tool that is passed to the LLM.                                               |
85 | 
```

--------------------------------------------------------------------------------
/tests/server.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 tests
 16 | 
 17 | import (
 18 | 	"context"
 19 | 	"fmt"
 20 | 	"io"
 21 | 	"os"
 22 | 
 23 | 	yaml "github.com/goccy/go-yaml"
 24 | 
 25 | 	"github.com/googleapis/genai-toolbox/cmd"
 26 | )
 27 | 
 28 | // tmpFileWithCleanup creates a temporary file with the content and returns the path and
 29 | // a function to clean it up, or any errors encountered instead
 30 | func tmpFileWithCleanup(content []byte) (string, func(), error) {
 31 | 	// create a random file in the temp dir
 32 | 	f, err := os.CreateTemp("", "*") // * indicates random string
 33 | 	if err != nil {
 34 | 		return "", nil, err
 35 | 	}
 36 | 	cleanup := func() { os.Remove(f.Name()) }
 37 | 
 38 | 	if _, err := f.Write(content); err != nil {
 39 | 		cleanup()
 40 | 		return "", nil, err
 41 | 	}
 42 | 	if err := f.Close(); err != nil {
 43 | 		cleanup()
 44 | 		return "", nil, err
 45 | 	}
 46 | 	return f.Name(), cleanup, err
 47 | }
 48 | 
 49 | // CmdExec represents an invocation of a toolbox command.
 50 | type CmdExec struct {
 51 | 	Out io.ReadCloser
 52 | 
 53 | 	cmd     *cmd.Command
 54 | 	cancel  context.CancelFunc
 55 | 	closers []io.Closer
 56 | 	done    chan bool // closed once the cmd is completed
 57 | 	err     error
 58 | }
 59 | 
 60 | // StartCmd returns a CmdExec representing a running instance of a toolbox command.
 61 | func StartCmd(ctx context.Context, toolsFile map[string]any, args ...string) (*CmdExec, func(), error) {
 62 | 	b, err := yaml.Marshal(toolsFile)
 63 | 	if err != nil {
 64 | 		return nil, nil, fmt.Errorf("unable to marshal tools file: %s", err)
 65 | 	}
 66 | 	path, cleanup, err := tmpFileWithCleanup(b)
 67 | 	if err != nil {
 68 | 		return nil, nil, fmt.Errorf("unable to write tools file: %s", err)
 69 | 	}
 70 | 	args = append(args, "--tools-file", path)
 71 | 
 72 | 	ctx, cancel := context.WithCancel(ctx)
 73 | 	// Open a pipe for tracking the output from the cmd
 74 | 	pr, pw, err := os.Pipe()
 75 | 	if err != nil {
 76 | 		cancel()
 77 | 		return nil, nil, fmt.Errorf("unable to open stdout pipe: %w", err)
 78 | 	}
 79 | 
 80 | 	c := cmd.NewCommand(cmd.WithStreams(pw, pw))
 81 | 	c.SetArgs(args)
 82 | 
 83 | 	t := &CmdExec{
 84 | 		Out:     pr,
 85 | 		cmd:     c,
 86 | 		cancel:  cancel,
 87 | 		closers: []io.Closer{pr, pw},
 88 | 		done:    make(chan bool),
 89 | 	}
 90 | 
 91 | 	// Start the command in the background
 92 | 	go func() {
 93 | 		defer close(t.done)
 94 | 		defer cancel()
 95 | 		t.err = c.ExecuteContext(ctx)
 96 | 	}()
 97 | 	return t, cleanup, nil
 98 | 
 99 | }
100 | 
101 | // Stop sends the TERM signal to the cmd and returns.
102 | func (c *CmdExec) Stop() {
103 | 	c.cancel()
104 | }
105 | 
106 | // Waits until the execution is completed and returns any error from the result.
107 | func (c *CmdExec) Wait(ctx context.Context) error {
108 | 	select {
109 | 	case <-ctx.Done():
110 | 		return ctx.Err()
111 | 	case <-c.done:
112 | 		return c.err
113 | 	}
114 | }
115 | 
116 | // Done returns true if the command has exited.
117 | func (c *CmdExec) Done() bool {
118 | 	select {
119 | 	case <-c.done:
120 | 		return true
121 | 	default:
122 | 	}
123 | 	return false
124 | }
125 | 
126 | // Close releases any resources associated with the instance.
127 | func (c *CmdExec) Close() {
128 | 	c.cancel()
129 | 	for _, c := range c.closers {
130 | 		c.Close()
131 | 	}
132 | }
133 | 
```

--------------------------------------------------------------------------------
/internal/sources/oceanbase/oceanbase.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 oceanbase
 16 | 
 17 | import (
 18 | 	"context"
 19 | 	"database/sql"
 20 | 	"fmt"
 21 | 	"time"
 22 | 
 23 | 	_ "github.com/go-sql-driver/mysql"
 24 | 	"github.com/goccy/go-yaml"
 25 | 	"github.com/googleapis/genai-toolbox/internal/sources"
 26 | 	"go.opentelemetry.io/otel/trace"
 27 | )
 28 | 
 29 | const SourceKind string = "oceanbase"
 30 | 
 31 | // validate interface
 32 | var _ sources.SourceConfig = Config{}
 33 | 
 34 | func init() {
 35 | 	if !sources.Register(SourceKind, newConfig) {
 36 | 		panic(fmt.Sprintf("source kind %q already registered", SourceKind))
 37 | 	}
 38 | }
 39 | 
 40 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) {
 41 | 	actual := Config{Name: name}
 42 | 	if err := decoder.DecodeContext(ctx, &actual); err != nil {
 43 | 		return nil, err
 44 | 	}
 45 | 	return actual, nil
 46 | }
 47 | 
 48 | type Config struct {
 49 | 	Name         string `yaml:"name" validate:"required"`
 50 | 	Kind         string `yaml:"kind" validate:"required"`
 51 | 	Host         string `yaml:"host" validate:"required"`
 52 | 	Port         string `yaml:"port" validate:"required"`
 53 | 	User         string `yaml:"user" validate:"required"`
 54 | 	Password     string `yaml:"password" validate:"required"`
 55 | 	Database     string `yaml:"database" validate:"required"`
 56 | 	QueryTimeout string `yaml:"queryTimeout"`
 57 | }
 58 | 
 59 | func (r Config) SourceConfigKind() string {
 60 | 	return SourceKind
 61 | }
 62 | 
 63 | func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) {
 64 | 	pool, err := initOceanBaseConnectionPool(ctx, tracer, r.Name, r.Host, r.Port, r.User, r.Password, r.Database, r.QueryTimeout)
 65 | 	if err != nil {
 66 | 		return nil, fmt.Errorf("unable to create pool: %w", err)
 67 | 	}
 68 | 
 69 | 	err = pool.PingContext(ctx)
 70 | 	if err != nil {
 71 | 		return nil, fmt.Errorf("unable to connect successfully: %w", err)
 72 | 	}
 73 | 
 74 | 	s := &Source{
 75 | 		Name: r.Name,
 76 | 		Kind: SourceKind,
 77 | 		Pool: pool,
 78 | 	}
 79 | 	return s, nil
 80 | }
 81 | 
 82 | var _ sources.Source = &Source{}
 83 | 
 84 | type Source struct {
 85 | 	Name string `yaml:"name"`
 86 | 	Kind string `yaml:"kind"`
 87 | 	Pool *sql.DB
 88 | }
 89 | 
 90 | func (s *Source) SourceKind() string {
 91 | 	return SourceKind
 92 | }
 93 | 
 94 | func (s *Source) OceanBasePool() *sql.DB {
 95 | 	return s.Pool
 96 | }
 97 | 
 98 | func initOceanBaseConnectionPool(ctx context.Context, tracer trace.Tracer, name, host, port, user, pass, dbname, queryTimeout string) (*sql.DB, error) {
 99 | 	_, span := sources.InitConnectionSpan(ctx, tracer, SourceKind, name)
100 | 	defer span.End()
101 | 
102 | 	dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?parseTime=true", user, pass, host, port, dbname)
103 | 
104 | 	if queryTimeout != "" {
105 | 		timeout, err := time.ParseDuration(queryTimeout)
106 | 		if err != nil {
107 | 			return nil, fmt.Errorf("invalid queryTimeout %q: %w", queryTimeout, err)
108 | 		}
109 | 		dsn += "&readTimeout=" + timeout.String()
110 | 	}
111 | 
112 | 	pool, err := sql.Open("mysql", dsn)
113 | 	if err != nil {
114 | 		return nil, fmt.Errorf("sql.Open: %w", err)
115 | 	}
116 | 	return pool, nil
117 | }
118 | 
```

--------------------------------------------------------------------------------
/internal/telemetry/instrumentation.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 telemetry
 16 | 
 17 | import (
 18 | 	"fmt"
 19 | 
 20 | 	"go.opentelemetry.io/otel"
 21 | 	"go.opentelemetry.io/otel/metric"
 22 | 	"go.opentelemetry.io/otel/trace"
 23 | )
 24 | 
 25 | const (
 26 | 	TracerName = "github.com/googleapis/genai-toolbox/internal/opentel"
 27 | 	MetricName = "github.com/googleapis/genai-toolbox/internal/opentel"
 28 | 
 29 | 	toolsetGetCountName = "toolbox.server.toolset.get.count"
 30 | 	toolGetCountName    = "toolbox.server.tool.get.count"
 31 | 	toolInvokeCountName = "toolbox.server.tool.invoke.count"
 32 | 	mcpSseCountName     = "toolbox.server.mcp.sse.count"
 33 | 	mcpPostCountName    = "toolbox.server.mcp.post.count"
 34 | )
 35 | 
 36 | // Instrumentation defines the telemetry instrumentation for toolbox
 37 | type Instrumentation struct {
 38 | 	Tracer     trace.Tracer
 39 | 	meter      metric.Meter
 40 | 	ToolsetGet metric.Int64Counter
 41 | 	ToolGet    metric.Int64Counter
 42 | 	ToolInvoke metric.Int64Counter
 43 | 	McpSse     metric.Int64Counter
 44 | 	McpPost    metric.Int64Counter
 45 | }
 46 | 
 47 | func CreateTelemetryInstrumentation(versionString string) (*Instrumentation, error) {
 48 | 	tracer := otel.Tracer(
 49 | 		TracerName,
 50 | 		trace.WithInstrumentationVersion(versionString),
 51 | 	)
 52 | 
 53 | 	meter := otel.Meter(MetricName, metric.WithInstrumentationVersion(versionString))
 54 | 	toolsetGet, err := meter.Int64Counter(
 55 | 		toolsetGetCountName,
 56 | 		metric.WithDescription("Number of toolset GET API calls."),
 57 | 		metric.WithUnit("{call}"),
 58 | 	)
 59 | 	if err != nil {
 60 | 		return nil, fmt.Errorf("unable to create %s metric: %w", toolsetGetCountName, err)
 61 | 	}
 62 | 
 63 | 	toolGet, err := meter.Int64Counter(
 64 | 		toolGetCountName,
 65 | 		metric.WithDescription("Number of tool GET API calls."),
 66 | 		metric.WithUnit("{call}"),
 67 | 	)
 68 | 	if err != nil {
 69 | 		return nil, fmt.Errorf("unable to create %s metric: %w", toolGetCountName, err)
 70 | 	}
 71 | 
 72 | 	toolInvoke, err := meter.Int64Counter(
 73 | 		toolInvokeCountName,
 74 | 		metric.WithDescription("Number of tool Invoke API calls."),
 75 | 		metric.WithUnit("{call}"),
 76 | 	)
 77 | 	if err != nil {
 78 | 		return nil, fmt.Errorf("unable to create %s metric: %w", toolInvokeCountName, err)
 79 | 	}
 80 | 
 81 | 	mcpSse, err := meter.Int64Counter(
 82 | 		mcpSseCountName,
 83 | 		metric.WithDescription("Number of MCP SSE connection requests."),
 84 | 		metric.WithUnit("{connection}"),
 85 | 	)
 86 | 	if err != nil {
 87 | 		return nil, fmt.Errorf("unable to create %s metric: %w", mcpSseCountName, err)
 88 | 	}
 89 | 
 90 | 	mcpPost, err := meter.Int64Counter(
 91 | 		mcpPostCountName,
 92 | 		metric.WithDescription("Number of MCP Post calls."),
 93 | 		metric.WithUnit("{call}"),
 94 | 	)
 95 | 	if err != nil {
 96 | 		return nil, fmt.Errorf("unable to create %s metric: %w", mcpPostCountName, err)
 97 | 	}
 98 | 
 99 | 	instrumentation := &Instrumentation{
100 | 		Tracer:     tracer,
101 | 		meter:      meter,
102 | 		ToolsetGet: toolsetGet,
103 | 		ToolGet:    toolGet,
104 | 		ToolInvoke: toolInvoke,
105 | 		McpSse:     mcpSse,
106 | 		McpPost:    mcpPost,
107 | 	}
108 | 	return instrumentation, nil
109 | }
110 | 
```

--------------------------------------------------------------------------------
/docs/en/resources/sources/tidb.md:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | title: "TiDB"
 3 | type: docs
 4 | weight: 1
 5 | description: >
 6 |   TiDB is a distributed SQL database that combines the best of traditional RDBMS and NoSQL databases.
 7 | 
 8 | ---
 9 | 
10 | ## About
11 | 
12 | [TiDB][tidb-docs] is an open-source distributed SQL database that supports
13 | Hybrid Transactional and Analytical Processing (HTAP) workloads. It is
14 | MySQL-compatible and features horizontal scalability, strong consistency, and
15 | high availability.
16 | 
17 | [tidb-docs]: https://docs.pingcap.com/tidb/stable
18 | 
19 | ## Requirements
20 | 
21 | ### Database User
22 | 
23 | This source uses standard MySQL protocol authentication. You will need to
24 | [create a TiDB user][tidb-users] to login to the database with.
25 | 
26 | For TiDB Cloud users, you can create database users through the TiDB Cloud
27 | console.
28 | 
29 | [tidb-users]: https://docs.pingcap.com/tidb/stable/user-account-management
30 | 
31 | ## SSL Configuration
32 | 
33 | - TiDB Cloud
34 | 
35 |     For TiDB Cloud instances, SSL is automatically enabled when the hostname
36 |     matches the TiDB Cloud pattern (`gateway*.*.*.tidbcloud.com`). You don't
37 |     need to explicitly set `ssl: true` for TiDB Cloud connections.
38 | 
39 | - Self-Hosted TiDB
40 | 
41 |     For self-hosted TiDB instances, you can optionally enable SSL by setting
42 |     `ssl: true` in your configuration.
43 | 
44 | ## Example
45 | 
46 | - TiDB Cloud
47 | 
48 |     ```yaml
49 |     sources:
50 |         my-tidb-cloud-source:
51 |             kind: tidb
52 |             host: gateway01.us-west-2.prod.aws.tidbcloud.com
53 |             port: 4000
54 |             database: my_db
55 |             user: ${TIDB_USERNAME}
56 |             password: ${TIDB_PASSWORD}
57 |             # SSL is automatically enabled for TiDB Cloud
58 |     ```
59 | 
60 | - Self-Hosted TiDB
61 | 
62 |     ```yaml
63 |     sources:
64 |         my-tidb-source:
65 |             kind: tidb
66 |             host: 127.0.0.1
67 |             port: 4000
68 |             database: my_db
69 |             user: ${TIDB_USERNAME}
70 |             password: ${TIDB_PASSWORD}
71 |             # ssl: true  # Optional: enable SSL for secure connections
72 |     ```
73 | 
74 | {{< notice tip >}}
75 | Use environment variable replacement with the format ${ENV_NAME}
76 | instead of hardcoding your secrets into the configuration file.
77 | {{< /notice >}}
78 | 
79 | ## Reference
80 | 
81 | | **field** | **type** | **required** | **description**                                                                            |
82 | |-----------|:--------:|:------------:|--------------------------------------------------------------------------------------------|
83 | | kind      |  string  |     true     | Must be "tidb".                                                                            |
84 | | host      |  string  |     true     | IP address or hostname to connect to (e.g. "127.0.0.1" or "gateway01.*.tidbcloud.com").    |
85 | | port      |  string  |     true     | Port to connect to (typically "4000" for TiDB).                                            |
86 | | database  |  string  |     true     | Name of the TiDB database to connect to (e.g. "my_db").                                    |
87 | | user      |  string  |     true     | Name of the TiDB user to connect as (e.g. "my-tidb-user").                                 |
88 | | password  |  string  |     true     | Password of the TiDB user (e.g. "my-password").                                            |
89 | | ssl       |  boolean |    false     | Whether to use SSL/TLS encryption. Automatically enabled for TiDB Cloud instances.         |
90 | 
```

--------------------------------------------------------------------------------
/docs/en/resources/tools/firestore/firestore-validate-rules.md:
--------------------------------------------------------------------------------

```markdown
  1 | ---
  2 | title: "firestore-validate-rules"
  3 | type: docs
  4 | weight: 1
  5 | description: > 
  6 |   A "firestore-validate-rules" tool validates Firestore security rules syntax and semantic correctness without deploying them. It provides detailed error reporting with source positions and code snippets.
  7 | aliases:
  8 | - /resources/tools/firestore-validate-rules
  9 | ---
 10 | 
 11 | ## Overview
 12 | 
 13 | The `firestore-validate-rules` tool validates Firestore security rules syntax
 14 | and semantic correctness without deploying them. It provides detailed error
 15 | reporting with source positions and code snippets.
 16 | 
 17 | ## Configuration
 18 | 
 19 | ```yaml
 20 | tools:
 21 |   firestore-validate-rules:
 22 |     kind: firestore-validate-rules
 23 |     source: <firestore-source-name>
 24 |     description: "Checks the provided Firestore Rules source for syntax and validation errors"
 25 | ```
 26 | 
 27 | ## Authentication
 28 | 
 29 | This tool requires authentication if the source requires authentication.
 30 | 
 31 | ## Parameters
 32 | 
 33 | | **parameters**  |   **type**   | **required** | **description**                              |
 34 | |-----------------|:------------:|:------------:|----------------------------------------------|
 35 | | source          |    string    |     true     | The Firestore Rules source code to validate  |
 36 | 
 37 | ## Response
 38 | 
 39 | The tool returns a `ValidationResult` object containing:
 40 | 
 41 | ```json
 42 | {
 43 |   "valid": "boolean",      
 44 |   "issueCount": "number",
 45 |   "formattedIssues": "string",
 46 |   "rawIssues": [
 47 |     {
 48 |       "sourcePosition": {
 49 |         "fileName": "string",
 50 |         "line": "number",
 51 |         "column": "number",
 52 |         "currentOffset": "number",
 53 |         "endOffset": "number"
 54 |       },
 55 |       "description": "string",
 56 |       "severity": "string"
 57 |     }
 58 |   ]
 59 | }
 60 | ```
 61 | 
 62 | ## Example Usage
 63 | 
 64 | ### Validate simple rules
 65 | 
 66 | ```json
 67 | {
 68 |   "source": "rules_version = '2';\nservice cloud.firestore {\n  match /databases/{database}/documents {\n    match /{document=**} {\n      allow read, write: if true;\n    }\n  }\n}"
 69 | }
 70 | ```
 71 | 
 72 | ### Example response for valid rules
 73 | 
 74 | ```json
 75 | {
 76 |   "valid": true,
 77 |   "issueCount": 0,
 78 |   "formattedIssues": "✓ No errors detected. Rules are valid."
 79 | }
 80 | ```
 81 | 
 82 | ### Example response with errors
 83 | 
 84 | ```json
 85 | {
 86 |   "valid": false,
 87 |   "issueCount": 1,
 88 |   "formattedIssues": "Found 1 issue(s) in rules source:\n\nERROR: Unexpected token ';' [Ln 4, Col 32]\n```\n      allow read, write: if true;;\n                               ^\n```",
 89 |   "rawIssues": [
 90 |     {
 91 |       "sourcePosition": {
 92 |         "line": 4,
 93 |         "column": 32,
 94 |         "currentOffset": 105,
 95 |         "endOffset": 106
 96 |       },
 97 |       "description": "Unexpected token ';'",
 98 |       "severity": "ERROR"
 99 |     }
100 |   ]
101 | }
102 | ```
103 | 
104 | ## Error Handling
105 | 
106 | The tool will return errors for:
107 | 
108 | - Missing or empty `source` parameter
109 | - API errors when calling the Firebase Rules service
110 | - Network connectivity issues
111 | 
112 | ## Use Cases
113 | 
114 | 1. **Pre-deployment validation**: Validate rules before deploying to production
115 | 2. **CI/CD integration**: Integrate rules validation into your build pipeline
116 | 3. **Development workflow**: Quickly check rules syntax while developing
117 | 4. **Error debugging**: Get detailed error locations with code snippets
118 | 
119 | ## Related Tools
120 | 
121 | - [firestore-get-rules]({{< ref "firestore-get-rules" >}}): Retrieve current
122 |   active rules
123 | - [firestore-query-collection]({{< ref "firestore-query-collection" >}}): Test
124 |   rules by querying collections
125 | 
```

--------------------------------------------------------------------------------
/internal/sources/dataplex/dataplex.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 dataplex
 16 | 
 17 | import (
 18 | 	"context"
 19 | 	"fmt"
 20 | 
 21 | 	dataplexapi "cloud.google.com/go/dataplex/apiv1"
 22 | 	"github.com/goccy/go-yaml"
 23 | 	"github.com/googleapis/genai-toolbox/internal/sources"
 24 | 	"github.com/googleapis/genai-toolbox/internal/util"
 25 | 	"go.opentelemetry.io/otel/trace"
 26 | 	"golang.org/x/oauth2/google"
 27 | 	"google.golang.org/api/option"
 28 | )
 29 | 
 30 | const SourceKind string = "dataplex"
 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 | 	// Dataplex configs
 51 | 	Name    string `yaml:"name" validate:"required"`
 52 | 	Kind    string `yaml:"kind" validate:"required"`
 53 | 	Project string `yaml:"project" validate:"required"`
 54 | }
 55 | 
 56 | func (r Config) SourceConfigKind() string {
 57 | 	// Returns Dataplex source kind
 58 | 	return SourceKind
 59 | }
 60 | 
 61 | func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) {
 62 | 	// Initializes a Dataplex source
 63 | 	client, err := initDataplexConnection(ctx, tracer, r.Name, r.Project)
 64 | 	if err != nil {
 65 | 		return nil, err
 66 | 	}
 67 | 	s := &Source{
 68 | 		Name:    r.Name,
 69 | 		Kind:    SourceKind,
 70 | 		Client:  client,
 71 | 		Project: r.Project,
 72 | 	}
 73 | 
 74 | 	return s, nil
 75 | }
 76 | 
 77 | var _ sources.Source = &Source{}
 78 | 
 79 | type Source struct {
 80 | 	// Source struct with Dataplex client
 81 | 	Name     string `yaml:"name"`
 82 | 	Kind     string `yaml:"kind"`
 83 | 	Client   *dataplexapi.CatalogClient
 84 | 	Project  string `yaml:"project"`
 85 | 	Location string `yaml:"location"`
 86 | }
 87 | 
 88 | func (s *Source) SourceKind() string {
 89 | 	// Returns Dataplex source kind
 90 | 	return SourceKind
 91 | }
 92 | 
 93 | func (s *Source) ProjectID() string {
 94 | 	return s.Project
 95 | }
 96 | 
 97 | func (s *Source) CatalogClient() *dataplexapi.CatalogClient {
 98 | 	return s.Client
 99 | }
100 | 
101 | func initDataplexConnection(
102 | 	ctx context.Context,
103 | 	tracer trace.Tracer,
104 | 	name string,
105 | 	project string,
106 | ) (*dataplexapi.CatalogClient, error) {
107 | 	ctx, span := sources.InitConnectionSpan(ctx, tracer, SourceKind, name)
108 | 	defer span.End()
109 | 
110 | 	cred, err := google.FindDefaultCredentials(ctx)
111 | 	if err != nil {
112 | 		return nil, fmt.Errorf("failed to find default Google Cloud credentials: %w", err)
113 | 	}
114 | 
115 | 	userAgent, err := util.UserAgentFromContext(ctx)
116 | 	if err != nil {
117 | 		return nil, err
118 | 	}
119 | 
120 | 	client, err := dataplexapi.NewCatalogClient(ctx, option.WithUserAgent(userAgent), option.WithCredentials(cred))
121 | 	if err != nil {
122 | 		return nil, fmt.Errorf("failed to create Dataplex client for project %q: %w", project, err)
123 | 	}
124 | 	return client, nil
125 | }
126 | 
```

--------------------------------------------------------------------------------
/internal/sources/firestore/firestore_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 firestore_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/sources/firestore"
 24 | 	"github.com/googleapis/genai-toolbox/internal/testutils"
 25 | )
 26 | 
 27 | func TestParseFromYamlFirestore(t *testing.T) {
 28 | 	tcs := []struct {
 29 | 		desc string
 30 | 		in   string
 31 | 		want server.SourceConfigs
 32 | 	}{
 33 | 		{
 34 | 			desc: "basic example with default database",
 35 | 			in: `
 36 | 			sources:
 37 | 				my-firestore:
 38 | 					kind: firestore
 39 | 					project: my-project
 40 | 			`,
 41 | 			want: server.SourceConfigs{
 42 | 				"my-firestore": firestore.Config{
 43 | 					Name:     "my-firestore",
 44 | 					Kind:     firestore.SourceKind,
 45 | 					Project:  "my-project",
 46 | 					Database: "",
 47 | 				},
 48 | 			},
 49 | 		},
 50 | 		{
 51 | 			desc: "with custom database",
 52 | 			in: `
 53 | 			sources:
 54 | 				my-firestore:
 55 | 					kind: firestore
 56 | 					project: my-project
 57 | 					database: my-database
 58 | 			`,
 59 | 			want: server.SourceConfigs{
 60 | 				"my-firestore": firestore.Config{
 61 | 					Name:     "my-firestore",
 62 | 					Kind:     firestore.SourceKind,
 63 | 					Project:  "my-project",
 64 | 					Database: "my-database",
 65 | 				},
 66 | 			},
 67 | 		},
 68 | 	}
 69 | 	for _, tc := range tcs {
 70 | 		t.Run(tc.desc, func(t *testing.T) {
 71 | 			got := struct {
 72 | 				Sources server.SourceConfigs `yaml:"sources"`
 73 | 			}{}
 74 | 			// Parse contents
 75 | 			err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
 76 | 			if err != nil {
 77 | 				t.Fatalf("unable to unmarshal: %s", err)
 78 | 			}
 79 | 			if !cmp.Equal(tc.want, got.Sources) {
 80 | 				t.Fatalf("incorrect parse: want %v, got %v", tc.want, got.Sources)
 81 | 			}
 82 | 		})
 83 | 	}
 84 | }
 85 | 
 86 | func TestFailParseFromYamlFirestore(t *testing.T) {
 87 | 	tcs := []struct {
 88 | 		desc string
 89 | 		in   string
 90 | 		err  string
 91 | 	}{
 92 | 		{
 93 | 			desc: "extra field",
 94 | 			in: `
 95 | 			sources:
 96 | 				my-firestore:
 97 | 					kind: firestore
 98 | 					project: my-project
 99 | 					foo: bar
100 | 			`,
101 | 			err: "unable to parse source \"my-firestore\" as \"firestore\": [1:1] unknown field \"foo\"\n>  1 | foo: bar\n       ^\n   2 | kind: firestore\n   3 | project: my-project",
102 | 		},
103 | 		{
104 | 			desc: "missing required field",
105 | 			in: `
106 | 			sources:
107 | 				my-firestore:
108 | 					kind: firestore
109 | 					database: my-database
110 | 			`,
111 | 			err: "unable to parse source \"my-firestore\" as \"firestore\": Key: 'Config.Project' Error:Field validation for 'Project' failed on the 'required' tag",
112 | 		},
113 | 	}
114 | 	for _, tc := range tcs {
115 | 		t.Run(tc.desc, func(t *testing.T) {
116 | 			got := struct {
117 | 				Sources server.SourceConfigs `yaml:"sources"`
118 | 			}{}
119 | 			// Parse contents
120 | 			err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
121 | 			if err == nil {
122 | 				t.Fatalf("expect parsing to fail")
123 | 			}
124 | 			errStr := err.Error()
125 | 			if errStr != tc.err {
126 | 				t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err)
127 | 			}
128 | 		})
129 | 	}
130 | }
131 | 
```

--------------------------------------------------------------------------------
/internal/sources/alloydbadmin/alloydbadmin_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 alloydbadmin_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/sources"
 24 | 	"github.com/googleapis/genai-toolbox/internal/sources/alloydbadmin"
 25 | 	"github.com/googleapis/genai-toolbox/internal/testutils"
 26 | )
 27 | 
 28 | func TestParseFromYamlAlloyDBAdmin(t *testing.T) {
 29 | 	tcs := []struct {
 30 | 		desc string
 31 | 		in   string
 32 | 		want server.SourceConfigs
 33 | 	}{
 34 | 		{
 35 | 			desc: "basic example",
 36 | 			in: `
 37 | 			sources:
 38 | 				my-alloydb-admin-instance:
 39 | 					kind: alloydb-admin
 40 | 			`,
 41 | 			want: map[string]sources.SourceConfig{
 42 | 				"my-alloydb-admin-instance": alloydbadmin.Config{
 43 | 					Name:           "my-alloydb-admin-instance",
 44 | 					Kind:           alloydbadmin.SourceKind,
 45 | 					UseClientOAuth: false,
 46 | 				},
 47 | 			},
 48 | 		},
 49 | 		{
 50 | 			desc: "use client auth example",
 51 | 			in: `
 52 | 			sources:
 53 | 				my-alloydb-admin-instance:
 54 | 					kind: alloydb-admin
 55 | 					useClientOAuth: true
 56 | 			`,
 57 | 			want: map[string]sources.SourceConfig{
 58 | 				"my-alloydb-admin-instance": alloydbadmin.Config{
 59 | 					Name:           "my-alloydb-admin-instance",
 60 | 					Kind:           alloydbadmin.SourceKind,
 61 | 					UseClientOAuth: true,
 62 | 				},
 63 | 			},
 64 | 		},
 65 | 	}
 66 | 	for _, tc := range tcs {
 67 | 		t.Run(tc.desc, func(t *testing.T) {
 68 | 			got := struct {
 69 | 				Sources server.SourceConfigs `yaml:"sources"`
 70 | 			}{}
 71 | 			// Parse contents
 72 | 			err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
 73 | 			if err != nil {
 74 | 				t.Fatalf("unable to unmarshal: %s", err)
 75 | 			}
 76 | 			if !cmp.Equal(tc.want, got.Sources) {
 77 | 				t.Fatalf("incorrect parse: want %v, got %v", tc.want, got.Sources)
 78 | 			}
 79 | 		})
 80 | 	}
 81 | }
 82 | 
 83 | func TestFailParseFromYaml(t *testing.T) {
 84 | 	tcs := []struct {
 85 | 		desc string
 86 | 		in   string
 87 | 		err  string
 88 | 	}{
 89 | 		{
 90 | 			desc: "extra field",
 91 | 			in: `
 92 | 			sources:
 93 | 				my-alloydb-admin-instance:
 94 | 					kind: alloydb-admin
 95 | 					project: test-project
 96 | 			`,
 97 | 			err: "unable to parse source \"my-alloydb-admin-instance\" as \"alloydb-admin\": [2:1] unknown field \"project\"\n   1 | kind: alloydb-admin\n>  2 | project: test-project\n       ^\n",
 98 | 		},
 99 | 		{
100 | 			desc: "missing required field",
101 | 			in: `
102 | 			sources:
103 | 				my-alloydb-admin-instance:
104 | 					useClientOAuth: true
105 | 			`,
106 | 			err: "missing 'kind' field for source \"my-alloydb-admin-instance\"",
107 | 		},
108 | 	}
109 | 	for _, tc := range tcs {
110 | 		t.Run(tc.desc, func(t *testing.T) {
111 | 			got := struct {
112 | 				Sources server.SourceConfigs `yaml:"sources"`
113 | 			}{}
114 | 			// Parse contents
115 | 			err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
116 | 			if err == nil {
117 | 				t.Fatalf("expect parsing to fail")
118 | 			}
119 | 			errStr := err.Error()
120 | 			if errStr != tc.err {
121 | 				t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err)
122 | 			}
123 | 		})
124 | 	}
125 | }
126 | 
```

--------------------------------------------------------------------------------
/docs/en/samples/looker/looker_gemini.md:
--------------------------------------------------------------------------------

```markdown
  1 | ---
  2 | title: "Quickstart (MCP with Looker and Gemini-CLI)"
  3 | type: docs
  4 | weight: 2
  5 | description: >
  6 |   How to get started running Toolbox with Gemini-CLI and Looker as the source.
  7 | ---
  8 | 
  9 | ## Overview
 10 | 
 11 | [Model Context Protocol](https://modelcontextprotocol.io) is an open protocol
 12 | that standardizes how applications provide context to LLMs. Check out this page
 13 | on how to [connect to Toolbox via MCP](../../how-to/connect_via_mcp.md).
 14 | 
 15 | ## Step 1: Get a Looker Client ID and Client Secret
 16 | 
 17 | The Looker Client ID and Client Secret can be obtained from the Users page of
 18 | your Looker instance. Refer to the documentation
 19 | [here](https://cloud.google.com/looker/docs/api-auth#authentication_with_an_sdk).
 20 | You may need to ask an administrator to get the Client ID and Client Secret
 21 | for you.
 22 | 
 23 | ## Step 2: Install and configure Toolbox
 24 | 
 25 | In this section, we will download Toolbox and run the Toolbox server.
 26 | 
 27 | 1. Download the latest version of Toolbox as a binary:
 28 | 
 29 |     {{< notice tip >}}
 30 |    Select the
 31 |    [correct binary](https://github.com/googleapis/genai-toolbox/releases)
 32 |    corresponding to your OS and CPU architecture.
 33 |     {{< /notice >}}
 34 |     <!-- {x-release-please-start-version} -->
 35 |     ```bash
 36 |     export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
 37 |     curl -O https://storage.googleapis.com/genai-toolbox/v0.17.0/$OS/toolbox
 38 |     ```
 39 |     <!-- {x-release-please-end} -->
 40 | 
 41 | 1. Make the binary executable:
 42 | 
 43 |     ```bash
 44 |     chmod +x toolbox
 45 |     ```
 46 | 
 47 | 1. Edit the file `~/.gemini/settings.json` and add the following
 48 |    to the list of mcpServers. Use the Client Id and Client Secret
 49 |    you obtained earlier. The name of the server - here
 50 |    `looker-toolbox` - can be anything meaningful to you.
 51 | 
 52 |    ```json
 53 |       "mcpServers": {
 54 |         "looker-toolbox": {
 55 |           "command": "/path/to/toolbox",
 56 |           "args": [
 57 |             "--stdio",
 58 |             "--prebuilt",
 59 |             "looker"
 60 |           ],
 61 |           "env": {
 62 |             "LOOKER_BASE_URL": "https://looker.example.com",
 63 |             "LOOKER_CLIENT_ID": "",
 64 |             "LOOKER_CLIENT_SECRET": "",
 65 |             "LOOKER_VERIFY_SSL": "true"
 66 |           }
 67 |         }
 68 |       }
 69 |    ```
 70 | 
 71 |    In some instances you may need to append `:19999` to
 72 |    the LOOKER_BASE_URL.
 73 | 
 74 | ## Step 3: Start Gemini-CLI
 75 | 
 76 | 1. Run Gemini-CLI:
 77 | 
 78 |     ```bash
 79 |     npx https://github.com/google-gemini/gemini-cli
 80 |     ```
 81 | 
 82 | 1. Type `y` when it asks to download.
 83 | 
 84 | 1. Log into Gemini-CLI
 85 | 
 86 | 1. Enter the command `/mcp` and you should see a list of
 87 |    available tools like
 88 | 
 89 |    ```
 90 |     ℹ Configured MCP servers:
 91 | 
 92 |       🟢 looker-toolbox - Ready (10 tools)
 93 |         - looker-toolbox__get_models
 94 |         - looker-toolbox__query
 95 |         - looker-toolbox__get_looks
 96 |         - looker-toolbox__get_measures
 97 |         - looker-toolbox__get_filters
 98 |         - looker-toolbox__get_parameters
 99 |         - looker-toolbox__get_explores
100 |         - looker-toolbox__query_sql
101 |         - looker-toolbox__get_dimensions
102 |         - looker-toolbox__run_look
103 |         - looker-toolbox__query_url
104 |     ```
105 | 
106 | 1. Start exploring your Looker instance with commands like
107 |    `Find an explore to see orders` or `show me my current
108 |    inventory broken down by item category`.
109 | 
110 | 1. Gemini will prompt you for your approval before using
111 |    a tool. You can approve all the tools at once or
112 |    one at a time.
113 | 
```

--------------------------------------------------------------------------------
/tests/utility/wait_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 utility
 16 | 
 17 | import (
 18 | 	"bytes"
 19 | 	"context"
 20 | 	"encoding/json"
 21 | 	"io"
 22 | 	"net/http"
 23 | 	"regexp"
 24 | 	"testing"
 25 | 	"time"
 26 | 
 27 | 	"github.com/googleapis/genai-toolbox/internal/testutils"
 28 | 	"github.com/googleapis/genai-toolbox/tests"
 29 | )
 30 | 
 31 | func RunWaitTool(t *testing.T) {
 32 | 	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
 33 | 	defer cancel()
 34 | 
 35 | 	args := []string{"--port", "5001"}
 36 | 
 37 | 	toolsFile := map[string]any{
 38 | 		"tools": map[string]any{
 39 | 			"my-wait-for-tool": map[string]any{
 40 | 				"kind":        "wait",
 41 | 				"description": "Wait for a specified duration.",
 42 | 				"timeout":     "30s",
 43 | 			},
 44 | 		},
 45 | 	}
 46 | 	cmd, cleanup, err := tests.StartCmd(ctx, toolsFile, args...)
 47 | 	if err != nil {
 48 | 		t.Fatalf("command initialization returned an error: %s", err)
 49 | 	}
 50 | 	defer cleanup()
 51 | 
 52 | 	waitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
 53 | 	defer cancel()
 54 | 	out, err := testutils.WaitForString(waitCtx, regexp.MustCompile(`Server ready to serve`), cmd.Out)
 55 | 	if err != nil {
 56 | 		t.Logf("toolbox command logs: \n%s", out)
 57 | 		t.Fatalf("toolbox didn't start successfully: %s", err)
 58 | 	}
 59 | 
 60 | 	invokeTcs := []struct {
 61 | 		name        string
 62 | 		api         string
 63 | 		requestBody io.Reader
 64 | 		want        string
 65 | 		isErr       bool
 66 | 	}{
 67 | 		{
 68 | 			name:        "invoke my-wait-for-tool",
 69 | 			api:         "http://127.0.0.1:5001/api/tool/my-wait-for-tool/invoke",
 70 | 			requestBody: bytes.NewBuffer([]byte(`{"duration": "1s"}`)),
 71 | 			want:        `["Wait for 1s completed successfully."]`,
 72 | 			isErr:       false,
 73 | 		},
 74 | 		{
 75 | 			name:        "invoke my-wait-for-tool with invalid duration",
 76 | 			api:         "http://127.0.0.1:5001/api/tool/my-wait-for-tool/invoke",
 77 | 			requestBody: bytes.NewBuffer([]byte(`{"duration": "invalid"}`)),
 78 | 			isErr:       true,
 79 | 		},
 80 | 	}
 81 | 	for _, tc := range invokeTcs {
 82 | 		t.Run(tc.name, func(t *testing.T) {
 83 | 			req, err := http.NewRequest(http.MethodPost, tc.api, tc.requestBody)
 84 | 			if err != nil {
 85 | 				t.Fatalf("unable to create request: %s", err)
 86 | 			}
 87 | 			req.Header.Add("Content-type", "application/json")
 88 | 			resp, err := http.DefaultClient.Do(req)
 89 | 			if err != nil {
 90 | 				t.Fatalf("unable to send request: %s", err)
 91 | 			}
 92 | 			defer resp.Body.Close()
 93 | 
 94 | 			if resp.StatusCode != http.StatusOK {
 95 | 				if tc.isErr {
 96 | 					return
 97 | 				}
 98 | 				bodyBytes, _ := io.ReadAll(resp.Body)
 99 | 				t.Fatalf("response status code is not 200, got %d: %s", resp.StatusCode, string(bodyBytes))
100 | 			}
101 | 
102 | 			var body map[string]interface{}
103 | 			err = json.NewDecoder(resp.Body).Decode(&body)
104 | 			if err != nil {
105 | 				t.Fatalf("error parsing response body")
106 | 			}
107 | 
108 | 			got, ok := body["result"].(string)
109 | 			if !ok {
110 | 				t.Fatalf("unable to find result in response body")
111 | 			}
112 | 
113 | 			if got != tc.want {
114 | 				t.Fatalf("unexpected value: got %q, want %q", got, tc.want)
115 | 			}
116 | 		})
117 | 	}
118 | }
119 | 
```

--------------------------------------------------------------------------------
/docs/en/resources/tools/looker/looker-query-sql.md:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | title: "looker-query-sql"
 3 | type: docs
 4 | weight: 1
 5 | description: >
 6 |   "looker-query-sql" generates a sql query using the Looker
 7 |   semantic model.
 8 | aliases:
 9 | - /resources/tools/looker-query-sql
10 | ---
11 | 
12 | ## About
13 | 
14 | The `looker-query-sql` generates a sql query using the Looker
15 | semantic model.
16 | 
17 | It's compatible with the following sources:
18 | 
19 | - [looker](../../sources/looker.md)
20 | 
21 | `looker-query-sql` takes eight parameters:
22 | 
23 | 1. the `model`
24 | 2. the `explore`
25 | 3. the `fields` list
26 | 4. an optional set of `filters`
27 | 5. an optional set of `pivots`
28 | 6. an optional set of `sorts`
29 | 7. an optional `limit`
30 | 8. an optional `tz`
31 | 
32 | Starting in Looker v25.18, these queries can be identified in Looker's
33 | System Activity. In the History explore, use the field API Client Name
34 | to find MCP Toolbox queries.
35 | 
36 | ## Example
37 | 
38 | ```yaml
39 | tools:
40 |     query_sql:
41 |         kind: looker-query-sql
42 |         source: looker-source
43 |         description: |
44 |           Query SQL Tool
45 | 
46 |           This tool is used to generate a sql query against the LookML model. The
47 |           model, explore, and fields list must be specified. Pivots,
48 |           filters and sorts are optional.
49 | 
50 |           The model can be found from the get_models tool. The explore
51 |           can be found from the get_explores tool passing in the model.
52 |           The fields can be found from the get_dimensions, get_measures,
53 |           get_filters, and get_parameters tools, passing in the model
54 |           and the explore.
55 | 
56 |           Provide a model_id and explore_name, then a list
57 |           of fields. Optionally a list of pivots can be provided.
58 |           The pivots must also be included in the fields list.
59 | 
60 |           Filters are provided as a map of {"field.id": "condition",
61 |           "field.id2": "condition2", ...}. Do not put the field.id in
62 |           quotes. Filter expressions can be found at
63 |           https://cloud.google.com/looker/docs/filter-expressions.
64 | 
65 |           Sorts can be specified like [ "field.id desc 0" ].
66 | 
67 |           An optional row limit can be added. If not provided the limit
68 |           will default to 500. "-1" can be specified for unlimited.
69 | 
70 |           An optional query timezone can be added. The query_timezone to
71 |           will default to that of the workstation where this MCP server
72 |           is running, or Etc/UTC if that can't be determined. Not all
73 |           models support custom timezones.
74 | 
75 |           The result of the query tool is the sql string.
76 | ```
77 | 
78 | ## Reference
79 | 
80 | | **field**   |                  **type**                  | **required** | **description**                                                                                  |
81 | |-------------|:------------------------------------------:|:------------:|--------------------------------------------------------------------------------------------------|
82 | | kind        |                   string                   |     true     | Must be "looker-query-sql"                                                                       |
83 | | source      |                   string                   |     true     | Name of the source the SQL should execute on.                                                    |
84 | | description |                   string                   |     true     | Description of the tool that is passed to the LLM.                                               |
85 | 
```

--------------------------------------------------------------------------------
/docs/en/resources/tools/bigquery/bigquery-execute-sql.md:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | title: "bigquery-execute-sql"
 3 | type: docs
 4 | weight: 1
 5 | description: >
 6 |   A "bigquery-execute-sql" tool executes a SQL statement against BigQuery.
 7 | aliases:
 8 | - /resources/tools/bigquery-execute-sql
 9 | ---
10 | 
11 | ## About
12 | 
13 | A `bigquery-execute-sql` tool executes a SQL statement against BigQuery.
14 | It's compatible with the following sources:
15 | 
16 | - [bigquery](../../sources/bigquery.md)
17 | 
18 | `bigquery-execute-sql` accepts the following parameters:
19 | - **`sql`** (required): The GoogleSQL statement to execute.
20 | - **`dry_run`** (optional): If set to `true`, the query is validated but not run,
21 |   returning information about the execution instead. Defaults to `false`.
22 | 
23 | The behavior of this tool is influenced by the `writeMode` setting on its `bigquery` source:
24 | 
25 | - **`allowed` (default):** All SQL statements are permitted.
26 | - **`blocked`:** Only `SELECT` statements are allowed. Any other type of statement (e.g., `INSERT`, `UPDATE`, `CREATE`) will be rejected.
27 | - **`protected`:** This mode enables session-based execution. `SELECT` statements can be used on all tables, while write operations are allowed only for the session's temporary dataset (e.g., `CREATE TEMP TABLE ...`). This prevents modifications to permanent datasets while allowing stateful, multi-step operations within a secure session.
28 | 
29 | The tool's behavior is influenced by the `allowedDatasets` restriction on the
30 | `bigquery` source. Similar to `writeMode`, this setting provides an additional layer of security by controlling which datasets can be accessed:
31 | 
32 | - **Without `allowedDatasets` restriction:** The tool can execute any valid GoogleSQL
33 |   query.
34 | - **With `allowedDatasets` restriction:** Before execution, the tool performs a dry run
35 |   to analyze the query.
36 |   It will reject the query if it attempts to access any table outside the
37 |   allowed `datasets` list. To enforce this restriction, the following operations
38 |   are also disallowed:
39 |   - **Dataset-level operations** (e.g., `CREATE SCHEMA`, `ALTER SCHEMA`).
40 |   - **Unanalyzable operations** where the accessed tables cannot be determined
41 |     statically (e.g., `EXECUTE IMMEDIATE`, `CREATE PROCEDURE`, `CALL`).
42 | 
43 | > **Note:** This tool is intended for developer assistant workflows with human-in-the-loop and shouldn't be used for production agents.
44 | 
45 | ## Example
46 | 
47 | ```yaml
48 | tools:
49 |  execute_sql_tool:
50 |     kind: bigquery-execute-sql
51 |     source: my-bigquery-source
52 |     description: Use this tool to execute sql statement.
53 | ```
54 | 
55 | ## Reference
56 | 
57 | | **field**   |                  **type**                  | **required** | **description**                                                                                  |
58 | |-------------|:------------------------------------------:|:------------:|--------------------------------------------------------------------------------------------------|
59 | | kind        |                   string                   |     true     | Must be "bigquery-execute-sql".                                                                  |
60 | | source      |                   string                   |     true     | Name of the source the SQL should execute on.                                                    |
61 | | description |                   string                   |     true     | Description of the tool that is passed to the LLM.                                               |
62 | 
```

--------------------------------------------------------------------------------
/.hugo/hugo.toml:
--------------------------------------------------------------------------------

```toml
  1 | title = 'MCP Toolbox for Databases'
  2 | relativeURLs = false
  3 | 
  4 | languageCode = 'en-us'
  5 | defaultContentLanguage = "en"
  6 | defaultContentLanguageInSubdir = false
  7 | 
  8 | enableGitInfo = true
  9 | enableRobotsTXT = true
 10 | 
 11 | ignoreFiles = ["quickstart/shared", "quickstart/python", "quickstart/js", "quickstart/go"]
 12 | 
 13 | [languages]
 14 |   [languages.en]
 15 |     languageName ="English"
 16 |     weight = 1
 17 | 
 18 | [module]
 19 |   proxy = "direct"
 20 |   [module.hugoVersion]
 21 |     extended = true
 22 |     min = "0.146.0"
 23 |   [[module.mounts]]
 24 |     source = "../docs/en"
 25 |     target = 'content'
 26 |   [[module.imports]]
 27 |     path = "github.com/google/docsy"
 28 |     disable = false
 29 |   [[module.imports]]
 30 |     path = "github.com/martignoni/hugo-notice"
 31 | 
 32 | [params]
 33 |   description = "MCP Toolbox for Databases is an open source MCP server for databases. It enables you to develop tools easier, faster, and more securely by handling the complexities such as connection pooling, authentication, and more."
 34 |   copyright = "Google LLC"
 35 |   github_repo = "https://github.com/googleapis/genai-toolbox"
 36 |   github_project_repo = "https://github.com/googleapis/genai-toolbox"
 37 |   github_subdir = "docs"
 38 |   offlineSearch = true
 39 |   version_menu = "Releases"
 40 |   [params.ui]
 41 |     ul_show = 100
 42 |     showLightDarkModeMenu = true
 43 |     breadcrumb_disable = true
 44 |     sidebar_menu_foldable = true
 45 |     sidebar_menu_compact = false
 46 | 
 47 | [[params.versions]]
 48 |   version = "Dev"
 49 |   url = "https://googleapis.github.io/genai-toolbox/dev/"
 50 | 
 51 | # Add a new version block here before every release
 52 | # The order of versions in this file is mirrored into the dropdown
 53 | 
 54 | [[params.versions]]
 55 |   version = "v0.17.0"
 56 |   url = "https://googleapis.github.io/genai-toolbox/v0.17.0/"
 57 |   
 58 | [[params.versions]]
 59 |   version = "v0.16.0"
 60 |   url = "https://googleapis.github.io/genai-toolbox/v0.16.0/"
 61 | 
 62 | [[params.versions]]
 63 |   version = "v0.15.0"
 64 |   url = "https://googleapis.github.io/genai-toolbox/v0.15.0/"
 65 | 
 66 | [[params.versions]]
 67 |   version = "v0.14.0"
 68 |   url = "https://googleapis.github.io/genai-toolbox/v0.14.0/"
 69 | 
 70 | [[params.versions]]
 71 |   version = "v0.13.0"
 72 |   url = "https://googleapis.github.io/genai-toolbox/v0.13.0/"
 73 | 
 74 | [[params.versions]]
 75 |   version = "v0.12.0"
 76 |   url = "https://googleapis.github.io/genai-toolbox/v0.12.0/"
 77 | 
 78 | [[params.versions]]
 79 |   version = "v0.11.0"
 80 |   url = "https://googleapis.github.io/genai-toolbox/v0.11.0/"
 81 | 
 82 | [[params.versions]]
 83 |   version = "v0.10.0"
 84 |   url = "https://googleapis.github.io/genai-toolbox/v0.10.0/"
 85 | 
 86 | [[params.versions]]
 87 |   version = "v0.9.0"
 88 |   url = "https://googleapis.github.io/genai-toolbox/v0.9.0/"
 89 | 
 90 | [[params.versions]]
 91 |   version = "v0.8.0"
 92 |   url = "https://googleapis.github.io/genai-toolbox/v0.8.0/"
 93 | 
 94 | 
 95 | [[menu.main]]
 96 |   name = "GitHub"
 97 |   weight = 50
 98 |   url = "https://github.com/googleapis/genai-toolbox"
 99 |   pre = "<i class='fa-brands fa-github'></i>"
100 | 
101 | [markup.goldmark.renderer]
102 |   unsafe= true
103 | 
104 | [markup.highlight]
105 |   noClasses = false
106 |   style = "tango"
107 | 
108 | [outputFormats]
109 |   [outputFormats.LLMS]
110 |     mediaType = "text/plain"
111 |     baseName = "llms"
112 |     isPlainText = true
113 |     root = true
114 |   [outputFormats.LLMS-FULL]
115 |     mediaType = "text/plain"
116 |     baseName = "llms-full"
117 |     isPlainText = true
118 |     root = true
119 |   [outputFormats.releases]
120 |     baseName = 'releases'
121 |     isPlainText = true
122 |     mediaType = 'text/releases'
123 | 
124 | [mediaTypes."text/releases"]
125 |   suffixes = ["releases"]
126 | 
127 | [outputs]
128 |   home = ["HTML", "RSS", "LLMS", "LLMS-FULL", "releases"]
129 | 
```

--------------------------------------------------------------------------------
/docs/en/resources/sources/oceanbase.md:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | title: "OceanBase"
 3 | type: docs
 4 | weight: 1
 5 | description: >
 6 |   OceanBase is a distributed relational database that provides high availability, scalability, and compatibility with MySQL.
 7 | ---
 8 | 
 9 | ## About
10 | 
11 | [OceanBase][oceanbase-docs] is a distributed relational database management
12 | system (RDBMS) that provides high availability, scalability, and strong
13 | consistency. It's designed to handle large-scale data processing and is
14 | compatible with MySQL, making it easy for developers to migrate from MySQL to
15 | OceanBase.
16 | 
17 | [oceanbase-docs]: https://www.oceanbase.com/
18 | 
19 | ## Requirements
20 | 
21 | ### Database User
22 | 
23 | This source only uses standard authentication. You will need to create an
24 | OceanBase user to login to the database with. OceanBase supports
25 | MySQL-compatible user management syntax.
26 | 
27 | ### Network Connectivity
28 | 
29 | Ensure that your application can connect to the OceanBase cluster. OceanBase
30 | typically runs on ports 2881 (for MySQL protocol) or 3881 (for MySQL protocol
31 | with SSL).
32 | 
33 | ## Example
34 | 
35 | ```yaml
36 | sources:
37 |     my-oceanbase-source:
38 |         kind: oceanbase
39 |         host: 127.0.0.1
40 |         port: 2881
41 |         database: my_db
42 |         user: ${USER_NAME}
43 |         password: ${PASSWORD}
44 |         queryTimeout: 30s # Optional: query timeout duration
45 | ```
46 | 
47 | {{< notice tip >}}
48 | Use environment variable replacement with the format ${ENV_NAME}
49 | instead of hardcoding your secrets into the configuration file.
50 | {{< /notice >}}
51 | 
52 | ## Reference
53 | 
54 | | **field**    | **type** | **required** | **description**                                                                                 |
55 | | ------------ | :------: | :----------: |-------------------------------------------------------------------------------------------------|
56 | | kind         |  string  |     true     | Must be "oceanbase".                                                                            |
57 | | host         |  string  |     true     | IP address to connect to (e.g. "127.0.0.1").                                                    |
58 | | port         |  string  |     true     | Port to connect to (e.g. "2881").                                                               |
59 | | database     |  string  |     true     | Name of the OceanBase database to connect to (e.g. "my_db").                                    |
60 | | user         |  string  |     true     | Name of the OceanBase user to connect as (e.g. "my-oceanbase-user").                            |
61 | | password     |  string  |     true     | Password of the OceanBase user (e.g. "my-password").                                            |
62 | | queryTimeout |  string  |    false     | Maximum time to wait for query execution (e.g. "30s", "2m"). By default, no timeout is applied. |
63 | 
64 | ## Features
65 | 
66 | ### MySQL Compatibility
67 | 
68 | OceanBase is highly compatible with MySQL, supporting most MySQL SQL syntax,
69 | data types, and functions. This makes it easy to migrate existing MySQL
70 | applications to OceanBase.
71 | 
72 | ### High Availability
73 | 
74 | OceanBase provides automatic failover and data replication across multiple
75 | nodes, ensuring high availability and data durability.
76 | 
77 | ### Scalability
78 | 
79 | OceanBase can scale horizontally by adding more nodes to the cluster, making it
80 | suitable for large-scale applications.
81 | 
82 | ### Strong Consistency
83 | 
84 | OceanBase provides strong consistency guarantees, ensuring that all transactions
85 | are ACID compliant.
```

--------------------------------------------------------------------------------
/docs/en/resources/sources/clickhouse.md:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | title: "ClickHouse"
 3 | type: docs
 4 | weight: 1
 5 | description: >
 6 |   ClickHouse is an open-source, OLTP database.
 7 | 
 8 | ---
 9 | 
10 | ## About
11 | 
12 | [ClickHouse][clickhouse-docs] is a fast, open-source, column-oriented database
13 | 
14 | [clickhouse-docs]: https://clickhouse.com/docs
15 | 
16 | ## Available Tools
17 | 
18 | - [`clickhouse-execute-sql`](../tools/clickhouse/clickhouse-execute-sql.md)  
19 |   Execute parameterized SQL queries in ClickHouse with query logging.
20 | 
21 | - [`clickhouse-sql`](../tools/clickhouse/clickhouse-sql.md)  
22 |   Execute SQL queries as prepared statements in ClickHouse.
23 | 
24 | 
25 | ## Requirements
26 | 
27 | ### Database User
28 | 
29 | This source uses standard ClickHouse authentication. You will need to [create a
30 | ClickHouse user][clickhouse-users] (or with [ClickHouse
31 | Cloud][clickhouse-cloud]) to connect to the database with. The user should have
32 | appropriate permissions for the operations you plan to perform.
33 | 
34 | [clickhouse-cloud]:
35 |     https://clickhouse.com/docs/getting-started/quick-start/cloud#connect-with-your-app
36 | [clickhouse-users]: https://clickhouse.com/docs/en/sql-reference/statements/create/user
37 | 
38 | ### Network Access
39 | 
40 | ClickHouse supports multiple protocols:
41 | 
42 | - **HTTPS protocol** (default port 8443) - Secure HTTP access (default)
43 | - **HTTP protocol** (default port 8123) - Good for web-based access
44 | 
45 | ## Example
46 | 
47 | ### Secure Connection Example
48 | 
49 | ```yaml
50 | sources:
51 |     secure-clickhouse-source:
52 |         kind: clickhouse
53 |         host: clickhouse.example.com
54 |         port: "8443"
55 |         database: analytics
56 |         user: ${CLICKHOUSE_USER}
57 |         password: ${CLICKHOUSE_PASSWORD}
58 |         protocol: https
59 |         secure: true
60 | ```
61 | 
62 | ### HTTP Protocol Example
63 | 
64 | ```yaml
65 | sources:
66 |     http-clickhouse-source:
67 |         kind: clickhouse
68 |         host: localhost
69 |         port: "8123"
70 |         database: logs
71 |         user: ${CLICKHOUSE_USER}
72 |         password: ${CLICKHOUSE_PASSWORD}
73 |         protocol: http
74 |         secure: false
75 | ```
76 | 
77 | {{< notice tip >}}
78 | Use environment variable replacement with the format ${ENV_NAME}
79 | instead of hardcoding your secrets into the configuration file.
80 | {{< /notice >}}
81 | 
82 | ## Reference
83 | 
84 | | **field** | **type** | **required** | **description**                                                                     |
85 | |-----------|:--------:|:------------:|-------------------------------------------------------------------------------------|
86 | | kind      |  string  |     true     | Must be "clickhouse".                                                               |
87 | | host      |  string  |     true     | IP address or hostname to connect to (e.g. "127.0.0.1" or "clickhouse.example.com") |
88 | | port      |  string  |     true     | Port to connect to (e.g. "8443" for HTTPS, "8123" for HTTP)                         |
89 | | database  |  string  |     true     | Name of the ClickHouse database to connect to (e.g. "my_database").                 |
90 | | user      |  string  |     true     | Name of the ClickHouse user to connect as (e.g. "analytics_user").                  |
91 | | password  |  string  |    false     | Password of the ClickHouse user (e.g. "my-password").                               |
92 | | protocol  |  string  |    false     | Connection protocol: "https" (default) or "http".                                   |
93 | | secure    | boolean  |    false     | Whether to use a secure connection (TLS). Default: false.                           |
94 | 
```

--------------------------------------------------------------------------------
/internal/tools/utility/wait/wait.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 wait
 16 | 
 17 | import (
 18 | 	"context"
 19 | 	"fmt"
 20 | 	"time"
 21 | 
 22 | 	yaml "github.com/goccy/go-yaml"
 23 | 	"github.com/googleapis/genai-toolbox/internal/sources"
 24 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 25 | )
 26 | 
 27 | const kind string = "wait"
 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 | type Config struct {
 44 | 	Name         string   `yaml:"name" validate:"required"`
 45 | 	Kind         string   `yaml:"kind" validate:"required"`
 46 | 	Description  string   `yaml:"description" validate:"required"`
 47 | 	Timeout      string   `yaml:"timeout" validate:"required"`
 48 | 	AuthRequired []string `yaml:"authRequired"`
 49 | }
 50 | 
 51 | var _ tools.ToolConfig = Config{}
 52 | 
 53 | func (cfg Config) ToolConfigKind() string {
 54 | 	return kind
 55 | }
 56 | 
 57 | func (cfg Config) Initialize(_ map[string]sources.Source) (tools.Tool, error) {
 58 | 	durationParameter := tools.NewStringParameter("duration", "The duration to wait for, specified as a string (e.g., '10s', '2m', '1h').")
 59 | 	parameters := tools.Parameters{durationParameter}
 60 | 
 61 | 	mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
 62 | 
 63 | 	t := Tool{
 64 | 		Name:        cfg.Name,
 65 | 		Kind:        kind,
 66 | 		Parameters:  parameters,
 67 | 		manifest:    tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired},
 68 | 		mcpManifest: mcpManifest,
 69 | 	}
 70 | 	return t, nil
 71 | }
 72 | 
 73 | // validate interface
 74 | var _ tools.Tool = Tool{}
 75 | 
 76 | type Tool struct {
 77 | 	Name        string
 78 | 	Kind        string
 79 | 	Parameters  tools.Parameters
 80 | 	manifest    tools.Manifest
 81 | 	mcpManifest tools.McpManifest
 82 | }
 83 | 
 84 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
 85 | 	paramsMap := params.AsMap()
 86 | 
 87 | 	durationStr, ok := paramsMap["duration"].(string)
 88 | 	if !ok {
 89 | 		return nil, fmt.Errorf("duration parameter is not a string")
 90 | 	}
 91 | 
 92 | 	totalDuration, err := time.ParseDuration(durationStr)
 93 | 	if err != nil {
 94 | 		return nil, fmt.Errorf("invalid duration format: %w", err)
 95 | 	}
 96 | 
 97 | 	time.Sleep(totalDuration)
 98 | 
 99 | 	return fmt.Sprintf("Wait for %v completed successfully.", totalDuration), nil
100 | }
101 | 
102 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
103 | 	return tools.ParseParams(t.Parameters, data, claims)
104 | }
105 | 
106 | func (t Tool) Manifest() tools.Manifest {
107 | 	return t.manifest
108 | }
109 | 
110 | func (t Tool) McpManifest() tools.McpManifest {
111 | 	return t.mcpManifest
112 | }
113 | 
114 | func (t Tool) Authorized(verifiedAuthServices []string) bool {
115 | 	return true
116 | }
117 | 
118 | func (t Tool) RequiresClientAuthorization() bool {
119 | 	return false
120 | }
121 | 
```

--------------------------------------------------------------------------------
/docs/en/resources/sources/cassandra.md:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | title: "Cassandra"
 3 | type: docs
 4 | weight: 1
 5 | description: >
 6 |   Apache Cassandra is a NoSQL distributed database known for its horizontal scalability, distributed architecture, and flexible schema definition.
 7 | ---
 8 | 
 9 | ## About
10 | 
11 | [Apache Cassandra][cassandra-docs] is a NoSQL distributed database. By design, NoSQL databases are lightweight, open-source, non-relational, and largely distributed. Counted among their strengths are horizontal scalability, distributed architectures, and a flexible approach to schema definition.
12 | 
13 | [cassandra-docs]: https://cassandra.apache.org/
14 | 
15 | ## Available Tools
16 | 
17 | - [`cassandra-cql`](../tools/cassandra/cassandra-cql.md)  
18 |   Run parameterized CQL queries in Cassandra.
19 | 
20 | 
21 | ## Example
22 | 
23 | ```yaml
24 | sources:
25 |     my-cassandra-source:
26 |         kind: cassandra
27 |         hosts:
28 |             - 127.0.0.1
29 |         keyspace: my_keyspace
30 |         protoVersion: 4
31 |         username: ${USER_NAME}
32 |         password: ${PASSWORD}
33 |         caPath: /path/to/ca.crt # Optional: path to CA certificate
34 |         certPath: /path/to/client.crt # Optional: path to client certificate
35 |         keyPath: /path/to/client.key # Optional: path to client key
36 |         enableHostVerification: true # Optional: enable host verification
37 | ```
38 | 
39 | {{< notice tip >}}
40 | Use environment variable replacement with the format ${ENV_NAME}
41 | instead of hardcoding your secrets into the configuration file.
42 | {{< /notice >}}
43 | 
44 | ## Reference
45 | 
46 | | **field**              | **type**  | **required** | **description**                                                                                       |
47 | |------------------------|:---------:|:------------:|-------------------------------------------------------------------------------------------------------|
48 | | kind                   |  string   |     true     | Must be "cassandra".                                                                                  |
49 | | hosts                  |  string[] |     true     | List of IP addresses to connect to (e.g., ["192.168.1.1:9042", "192.168.1.2:9042","192.168.1.3:9042"]). The default port is 9042 if not specified.                                    |
50 | | keyspace               |  string   |     true     | Name of the Cassandra keyspace to connect to (e.g., "my_keyspace").                                   |
51 | | protoVersion           |  integer  |    false     | Protocol version for the Cassandra connection (e.g., 4).                                              |
52 | | username               |  string   |    false     | Name of the Cassandra user to connect as (e.g., "my-cassandra-user").                                 |
53 | | password               |  string   |    false     | Password of the Cassandra user (e.g., "my-password").                                                 |
54 | | caPath                 |  string   |    false     | Path to the CA certificate for SSL/TLS (e.g., "/path/to/ca.crt").                                     |
55 | | certPath               |  string   |    false     | Path to the client certificate for SSL/TLS (e.g., "/path/to/client.crt").                             |
56 | | keyPath                |  string   |    false     | Path to the client key for SSL/TLS (e.g., "/path/to/client.key").                                     |
57 | | enableHostVerification |  boolean  |    false     | Enable host verification for SSL/TLS (e.g., true). By default, host verification is disabled.         |
58 | 
```

--------------------------------------------------------------------------------
/docs/en/resources/tools/cloudmonitoring/cloud-monitoring-query-prometheus.md:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | title: cloud-monitoring-query-prometheus
 3 | type: docs
 4 | weight: 1
 5 | description: The "cloud-monitoring-query-prometheus" tool fetches time series metrics for a project using a given prometheus query.
 6 | ---
 7 | 
 8 | The `cloud-monitoring-query-prometheus` tool fetches timeseries metrics data
 9 | from Google Cloud Monitoring for a project using a given prometheus query.
10 | 
11 | ## About
12 | 
13 | The `cloud-monitoring-query-prometheus` tool allows you to query all metrics
14 | available in Google Cloud Monitoring using the Prometheus Query Language
15 | (PromQL).
16 | It's compatible with any of the following sources:
17 | 
18 | - [cloud-monitoring](../../sources/cloud-monitoring.md)
19 | 
20 | ## Prerequisites
21 | 
22 | To use this tool, you need to have the following IAM role on your Google Cloud
23 | project:
24 | 
25 | - `roles/monitoring.viewer`
26 | 
27 | ## Arguments
28 | 
29 | | Name        | Type   | Description                      |
30 | |-------------|--------|----------------------------------|
31 | | `projectId` | string | The Google Cloud project ID.     |
32 | | `query`     | string | The Prometheus query to execute. |
33 | 
34 | ## Use Cases
35 | 
36 | - **Ad-hoc analysis:** Quickly investigate performance issues by executing
37 |   direct promql queries for a database instance.
38 | - **Prebuilt Configs:** Use the already added prebuilt tools mentioned in
39 |   prebuilt-tools.md to query the databases system/query level metrics. 
40 | 
41 | Here are some common use cases for the `cloud-monitoring-query-prometheus` tool:
42 | 
43 | - **Monitoring resource utilization:** Track CPU, memory, and disk usage for
44 |   your database instance (Can use the [prebuilt
45 |   tools](../../../reference/prebuilt-tools.md)).
46 | - **Monitoring query performance:** Monitor latency, execution_time, wait_time
47 |   for database instance or even for the queries running (Can use the [prebuilt
48 |   tools](../../../reference/prebuilt-tools.md)).
49 | - **System Health:** Get the overall system health for the database instance
50 |   (Can use the [prebuilt tools](../../../reference/prebuilt-tools.md)).
51 | 
52 | ## Examples
53 | 
54 | Here are some examples of how to use the `cloud-monitoring-query-prometheus`
55 | tool.
56 | 
57 | 
58 | ```yaml
59 | tools:
60 |  get_wait_time_metrics:
61 |     kind: cloud-monitoring-query-prometheus
62 |     source: cloud-monitoring-source
63 |     description: |
64 |       This tool fetches system wait time information for AlloyDB cluster, instance. Get the `projectID`, `clusterID` and `instanceID` from the user intent. To use this tool, you must provide the Google Cloud `projectId` and a PromQL `query`.
65 |       Generate `query` using these metric details:
66 |       metric: `alloydb.googleapis.com/instance/postgresql/wait_time`,  monitored_resource: `alloydb.googleapis.com/Instance`. labels: `cluster_id`, `instance_id`, `wait_event_type`, `wait_event_name`.
67 |       Basic time series example promql query: `avg_over_time({"__name__"="alloydb.googleapis.com/instance/postgresql/wait_time","monitored_resource"="alloydb.googleapis.com/Instance","instance_id"="alloydb-instance"}[5m])`
68 | ```
69 | 
70 | ## Reference
71 | | **field**   | **type** | **required** | **description**                                      |
72 | |-------------|:--------:|:------------:|------------------------------------------------------|
73 | | kind        |  string  |     true     | Must be cloud-monitoring-query-prometheus.           |
74 | | source      |  string  |     true     | The name of an `cloud-monitoring` source.            |
75 | | description |  string  |     true     | Description of the tool that is passed to the agent. |
76 | 
```

--------------------------------------------------------------------------------
/internal/sources/valkey/valkey.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 valkey
 15 | 
 16 | import (
 17 | 	"context"
 18 | 	"fmt"
 19 | 	"log"
 20 | 
 21 | 	"github.com/goccy/go-yaml"
 22 | 	"github.com/googleapis/genai-toolbox/internal/sources"
 23 | 	"github.com/valkey-io/valkey-go"
 24 | 	"go.opentelemetry.io/otel/trace"
 25 | )
 26 | 
 27 | const SourceKind string = "valkey"
 28 | 
 29 | // validate interface
 30 | var _ sources.SourceConfig = Config{}
 31 | 
 32 | func init() {
 33 | 	if !sources.Register(SourceKind, newConfig) {
 34 | 		panic(fmt.Sprintf("source kind %q already registered", SourceKind))
 35 | 	}
 36 | }
 37 | 
 38 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) {
 39 | 	actual := Config{Name: name}
 40 | 	if err := decoder.DecodeContext(ctx, &actual); err != nil {
 41 | 		return nil, err
 42 | 	}
 43 | 	return actual, nil
 44 | }
 45 | 
 46 | type Config struct {
 47 | 	Name         string   `yaml:"name" validate:"required"`
 48 | 	Kind         string   `yaml:"kind" validate:"required"`
 49 | 	Address      []string `yaml:"address" validate:"required"`
 50 | 	Username     string   `yaml:"username"`
 51 | 	Password     string   `yaml:"password"`
 52 | 	Database     int      `yaml:"database"`
 53 | 	UseGCPIAM    bool     `yaml:"useGCPIAM"`
 54 | 	DisableCache bool     `yaml:"disableCache"`
 55 | }
 56 | 
 57 | func (r Config) SourceConfigKind() string {
 58 | 	return SourceKind
 59 | }
 60 | 
 61 | func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) {
 62 | 
 63 | 	client, err := initValkeyClient(ctx, r)
 64 | 	if err != nil {
 65 | 		return nil, fmt.Errorf("error initializing Valkey client: %s", err)
 66 | 	}
 67 | 	s := &Source{
 68 | 		Name:   r.Name,
 69 | 		Kind:   SourceKind,
 70 | 		Client: client,
 71 | 	}
 72 | 	return s, nil
 73 | }
 74 | 
 75 | func initValkeyClient(ctx context.Context, r Config) (valkey.Client, error) {
 76 | 	var authFn func(valkey.AuthCredentialsContext) (valkey.AuthCredentials, error)
 77 | 	if r.UseGCPIAM {
 78 | 		// Pass in an access token getter fn for IAM auth
 79 | 		authFn = func(valkey.AuthCredentialsContext) (valkey.AuthCredentials, error) {
 80 | 			token, err := sources.GetIAMAccessToken(ctx)
 81 | 			creds := valkey.AuthCredentials{Username: "default", Password: token}
 82 | 			if err != nil {
 83 | 				return creds, err
 84 | 			}
 85 | 			return creds, nil
 86 | 		}
 87 | 	}
 88 | 
 89 | 	client, err := valkey.NewClient(valkey.ClientOption{
 90 | 		InitAddress:       r.Address,
 91 | 		SelectDB:          r.Database,
 92 | 		Username:          r.Username,
 93 | 		Password:          r.Password,
 94 | 		AuthCredentialsFn: authFn,
 95 | 		DisableCache:      r.DisableCache,
 96 | 	})
 97 | 
 98 | 	if err != nil {
 99 | 		log.Fatalf("error creating Valkey client: %v", err)
100 | 	}
101 | 
102 | 	// Ping the server to check connectivity
103 | 	pingCmd := client.B().Ping().Build()
104 | 	_, err = client.Do(ctx, pingCmd).ToString()
105 | 	if err != nil {
106 | 		log.Fatalf("Failed to execute PING command: %v", err)
107 | 	}
108 | 	return client, nil
109 | }
110 | 
111 | var _ sources.Source = &Source{}
112 | 
113 | type Source struct {
114 | 	Name   string `yaml:"name"`
115 | 	Kind   string `yaml:"kind"`
116 | 	Client valkey.Client
117 | }
118 | 
119 | func (s *Source) SourceKind() string {
120 | 	return SourceKind
121 | }
122 | 
123 | func (s *Source) ValkeyClient() valkey.Client {
124 | 	return s.Client
125 | }
126 | 
```

--------------------------------------------------------------------------------
/.github/labels.yaml:
--------------------------------------------------------------------------------

```yaml
  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 | - name: duplicate
 16 |   color: ededed
 17 |   description: ""
 18 | 
 19 | - name: 'type: bug'
 20 |   color: db4437
 21 |   description: Error or flaw in code with unintended results or allowing sub-optimal
 22 |     usage patterns.
 23 | - name: 'type: cleanup'
 24 |   color: c5def5
 25 |   description: An internal cleanup or hygiene concern.
 26 | - name: 'type: docs'
 27 |   color: 0000A0
 28 |   description: Improvement to the documentation for an API.
 29 | - name: 'type: feature request'
 30 |   color: c5def5
 31 |   description: ‘Nice-to-have’ improvement, new feature or different behavior or design.
 32 | - name: 'type: process'
 33 |   color: c5def5
 34 |   description: A process-related concern. May include testing, release, or the like.
 35 | - name: 'type: question'
 36 |   color: c5def5
 37 |   description: Request for information or clarification.
 38 | 
 39 | - name: 'priority: p0'
 40 |   color: b60205
 41 |   description: Highest priority. Critical issue. P0 implies highest priority.
 42 | - name: 'priority: p1'
 43 |   color: ffa03e
 44 |   description: Important issue which blocks shipping the next release. Will be fixed
 45 |     prior to next release.
 46 | - name: 'priority: p2'
 47 |   color: fef2c0
 48 |   description: Moderately-important priority. Fix may not be included in next release.
 49 | - name: 'priority: p3'
 50 |   color: ffffc7
 51 |   description: Desirable enhancement or fix. May not be included in next release.
 52 | 
 53 | - name: 'do not merge'
 54 |   color: d93f0b
 55 |   description: Indicates a pull request not ready for merge, due to either quality
 56 |     or timing.
 57 | 
 58 | - name: 'autorelease: pending'
 59 |   color: ededed
 60 |   description: Release please needs to do its work on this.
 61 | - name: 'autorelease: triggered'
 62 |   color: ededed
 63 |   description: Release please has triggered a release for this.
 64 | - name: 'autorelease: tagged'
 65 |   color: ededed
 66 |   description: Release please has completed a release for this.
 67 | 
 68 | - name: 'blunderbuss: assign'
 69 |   color: 3DED97
 70 |   description: Have blunderbuss assign this to someone new.
 71 | 
 72 | - name: 'tests: run'
 73 |   color: 3DED97
 74 |   description: Label to trigger Github Action tests.
 75 |   
 76 | - name: 'docs: deploy-preview'
 77 |   color: BFDADC
 78 |   description: Label to trigger Github Action docs preview.
 79 | 
 80 | - name: 'status: help wanted'
 81 |   color: 8befd7
 82 |   description: 'Status: Unplanned work open to contributions from the community.'
 83 | - name: 'status: feedback wanted'
 84 |   color: 8befd7
 85 |   description: 'Status: waiting for feedback from community or issue author.'
 86 | 
 87 | - name: 'status: waiting for response'
 88 |   color: 8befd7
 89 |   description: 'Status: reviewer is awaiting feedback or responses from the author before proceeding.'
 90 | 
 91 | - name: 'release candidate'
 92 |   color: 32CD32
 93 |   description: 'Use label to signal PR should be included in the next release.'
 94 | 
 95 | # Product Labels
 96 | - name: 'product: bigquery'
 97 |   color: 5065c7
 98 |   description: 'Product: Assigned to the BigQuery team.'
 99 | # Product Labels
100 | - name: 'product: looker'
101 |   color: 5065c7
102 |   description: 'Product: Assigned to the Looker team.'
103 | 
```

--------------------------------------------------------------------------------
/docs/en/resources/tools/neo4j/neo4j-cypher.md:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | title: "neo4j-cypher"
 3 | type: docs
 4 | weight: 1
 5 | description: >
 6 |   A "neo4j-cypher" tool executes a pre-defined cypher statement against a Neo4j
 7 |   database.
 8 | aliases:
 9 | - /resources/tools/neo4j-cypher
10 | ---
11 | 
12 | ## About
13 | 
14 | A `neo4j-cypher` tool executes a pre-defined Cypher statement against a Neo4j
15 | database. It's compatible with any of the following sources:
16 | 
17 | - [neo4j](../../sources/neo4j.md)
18 | 
19 | The specified Cypher statement is executed as a [parameterized
20 | statement][neo4j-parameters], and specified parameters will be used according to
21 | their name: e.g. `$id`.
22 | 
23 | > **Note:** This tool uses parameterized queries to prevent SQL injections.
24 | > Query parameters can be used as substitutes for arbitrary expressions.
25 | > Parameters cannot be used as substitutes for identifiers, column names, table
26 | > names, or other parts of the query.
27 | 
28 | [neo4j-parameters]:
29 |     https://neo4j.com/docs/cypher-manual/current/syntax/parameters/
30 | 
31 | ## Example
32 | 
33 | ```yaml
34 | tools:
35 |  search_movies_by_actor:
36 |     kind: neo4j-cypher
37 |     source: my-neo4j-movies-instance
38 |     statement: |
39 |       MATCH (m:Movie)<-[:ACTED_IN]-(p:Person)
40 |       WHERE p.name = $name AND m.year > $year
41 |       RETURN m.title, m.year
42 |       LIMIT 10
43 |     description: |
44 |       Use this tool to get a list of movies for a specific actor and a given minimum release year.
45 |       Takes a full actor name, e.g. "Tom Hanks" and a year e.g 1993 and returns a list of movie titles and release years.
46 |       Do NOT use this tool with a movie title. Do NOT guess an actor name, Do NOT guess a year.
47 |       A actor name is a fully qualified name with first and last name separated by a space.
48 |       For example, if given "Hanks, Tom" the actor name is "Tom Hanks".
49 |       If the tool returns more than one option choose the most recent movies.
50 |       Example:
51 |       {{
52 |           "name": "Meg Ryan",
53 |           "year": 1993
54 |       }}
55 |       Example:
56 |       {{
57 |           "name": "Clint Eastwood",
58 |           "year": 2000
59 |       }}
60 |     parameters:
61 |       - name: name
62 |         type: string
63 |         description: Full actor name, "firstname lastname"
64 |       - name: year
65 |         type: integer
66 |         description: 4 digit number starting in 1900 up to the current year
67 | ```
68 | 
69 | ## Reference
70 | 
71 | | **field**   |                  **type**                  | **required** | **description**                                                                                 |
72 | |-------------|:------------------------------------------:|:------------:|-------------------------------------------------------------------------------------------------|
73 | | kind        |                   string                   |     true     | Must be "neo4j-cypher".                                                                         |
74 | | source      |                   string                   |     true     | Name of the source the Cypher query should execute on.                                          |
75 | | description |                   string                   |     true     | Description of the tool that is passed to the LLM.                                              |
76 | | statement   |                   string                   |     true     | Cypher statement to execute                                                                     |
77 | | parameters  | [parameters](../#specifying-parameters) |    false     | List of [parameters](../#specifying-parameters) that will be used with the Cypher statement. |
78 | 
```

--------------------------------------------------------------------------------
/internal/sources/cloudsqladmin/cloud_sql_admin_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 cloudsqladmin_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/sources"
 24 | 	"github.com/googleapis/genai-toolbox/internal/sources/cloudsqladmin"
 25 | 	"github.com/googleapis/genai-toolbox/internal/testutils"
 26 | )
 27 | 
 28 | func TestParseFromYamlCloudSQLAdmin(t *testing.T) {
 29 | 	t.Parallel()
 30 | 	tcs := []struct {
 31 | 		desc string
 32 | 		in   string
 33 | 		want server.SourceConfigs
 34 | 	}{
 35 | 		{
 36 | 			desc: "basic example",
 37 | 			in: `
 38 | 			sources:
 39 | 				my-cloud-sql-admin-instance:
 40 | 					kind: cloud-sql-admin
 41 | 			`,
 42 | 			want: map[string]sources.SourceConfig{
 43 | 				"my-cloud-sql-admin-instance": cloudsqladmin.Config{
 44 | 					Name:           "my-cloud-sql-admin-instance",
 45 | 					Kind:           cloudsqladmin.SourceKind,
 46 | 					UseClientOAuth: false,
 47 | 				},
 48 | 			},
 49 | 		},
 50 | 		{
 51 | 			desc: "use client auth example",
 52 | 			in: `
 53 | 			sources:
 54 | 				my-cloud-sql-admin-instance:
 55 | 					kind: cloud-sql-admin
 56 | 					useClientOAuth: true
 57 | 			`,
 58 | 			want: map[string]sources.SourceConfig{
 59 | 				"my-cloud-sql-admin-instance": cloudsqladmin.Config{
 60 | 					Name:           "my-cloud-sql-admin-instance",
 61 | 					Kind:           cloudsqladmin.SourceKind,
 62 | 					UseClientOAuth: true,
 63 | 				},
 64 | 			},
 65 | 		},
 66 | 	}
 67 | 	for _, tc := range tcs {
 68 | 		tc := tc
 69 | 		t.Run(tc.desc, func(t *testing.T) {
 70 | 			t.Parallel()
 71 | 			got := struct {
 72 | 				Sources server.SourceConfigs `yaml:"sources"`
 73 | 			}{}
 74 | 			// Parse contents
 75 | 			err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
 76 | 			if err != nil {
 77 | 				t.Fatalf("unable to unmarshal: %s", err)
 78 | 			}
 79 | 			if !cmp.Equal(tc.want, got.Sources) {
 80 | 				t.Fatalf("incorrect parse: want %v, got %v", tc.want, got.Sources)
 81 | 			}
 82 | 		})
 83 | 	}
 84 | }
 85 | 
 86 | func TestFailParseFromYaml(t *testing.T) {
 87 | 	t.Parallel()
 88 | 	tcs := []struct {
 89 | 		desc string
 90 | 		in   string
 91 | 		err  string
 92 | 	}{
 93 | 		{
 94 | 			desc: "extra field",
 95 | 			in: `
 96 | 			sources:
 97 | 				my-cloud-sql-admin-instance:
 98 | 					kind: cloud-sql-admin
 99 | 					project: test-project
100 | 			`,
101 | 			err: `unable to parse source "my-cloud-sql-admin-instance" as "cloud-sql-admin": [2:1] unknown field "project"
102 |    1 | kind: cloud-sql-admin
103 | >  2 | project: test-project
104 |        ^
105 | `,
106 | 		},
107 | 		{
108 | 			desc: "missing required field",
109 | 			in: `
110 | 			sources:
111 | 				my-cloud-sql-admin-instance:
112 | 					useClientOAuth: true
113 | 			`,
114 | 			err: "missing 'kind' field for source \"my-cloud-sql-admin-instance\"",
115 | 		},
116 | 	}
117 | 	for _, tc := range tcs {
118 | 		tc := tc
119 | 		t.Run(tc.desc, func(t *testing.T) {
120 | 			t.Parallel()
121 | 			got := struct {
122 | 				Sources server.SourceConfigs `yaml:"sources"`
123 | 			}{}
124 | 			// Parse contents
125 | 			err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
126 | 			if err == nil {
127 | 				t.Fatalf("expect parsing to fail")
128 | 			}
129 | 			errStr := err.Error()
130 | 			if errStr != tc.err {
131 | 				t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err)
132 | 			}
133 | 		})
134 | 	}
135 | }
136 | 
```

--------------------------------------------------------------------------------
/docs/en/resources/sources/bigtable.md:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | title: "Bigtable"
 3 | type: docs
 4 | weight: 1
 5 | description: >
 6 |   Bigtable is a low-latency NoSQL database service for machine learning, operational analytics, and user-facing operations. It's a wide-column, key-value store that can scale to billions of rows and thousands of columns. With Bigtable, you can replicate your data to regions across the world for high availability and data resiliency.
 7 | 
 8 | ---
 9 | 
10 | # Bigtable Source
11 | 
12 | [Bigtable][bigtable-docs] is a low-latency NoSQL database service for machine
13 | learning, operational analytics, and user-facing operations. It's a wide-column,
14 | key-value store that can scale to billions of rows and thousands of columns.
15 | With Bigtable, you can replicate your data to regions across the world for high
16 | availability and data resiliency.
17 | 
18 | If you are new to Bigtable, you can try to [create an instance and write data
19 | with the cbt CLI][bigtable-quickstart-with-cli].
20 | 
21 | You can use [GoogleSQL statements][bigtable-googlesql] to query your Bigtable
22 | data. GoogleSQL is an ANSI-compliant structured query language (SQL) that is
23 | also implemented for other Google Cloud services. SQL queries are handled by
24 | cluster nodes in the same way as NoSQL data requests. Therefore, the same best
25 | practices apply when creating SQL queries to run against your Bigtable data,
26 | such as avoiding full table scans or complex filters.
27 | 
28 | [bigtable-docs]: https://cloud.google.com/bigtable/docs
29 | [bigtable-quickstart-with-cli]:
30 |     https://cloud.google.com/bigtable/docs/create-instance-write-data-cbt-cli
31 | 
32 | [bigtable-googlesql]:
33 |     https://cloud.google.com/bigtable/docs/googlesql-overview
34 | 
35 | ## Available Tools
36 | 
37 | - [`bigtable-sql`](../tools/bigtable/bigtable-sql.md)
38 |   Run SQL-like queries over Bigtable rows.
39 | 
40 | ## Requirements
41 | 
42 | ### IAM Permissions
43 | 
44 | Bigtable uses [Identity and Access Management (IAM)][iam-overview] to control
45 | user and group access to Bigtable resources at the project, instance, table, and
46 | backup level. Toolbox will use your [Application Default Credentials (ADC)][adc]
47 | to authorize and authenticate when interacting with [Bigtable][bigtable-docs].
48 | 
49 | In addition to [setting the ADC for your server][set-adc], you need to ensure
50 | the IAM identity has been given the correct IAM permissions for the query
51 | provided. See [Apply IAM roles][grant-permissions] for more information on
52 | applying IAM permissions and roles to an identity.
53 | 
54 | [iam-overview]: https://cloud.google.com/bigtable/docs/access-control
55 | [adc]: https://cloud.google.com/docs/authentication#adc
56 | [set-adc]: https://cloud.google.com/docs/authentication/provide-credentials-adc
57 | [grant-permissions]: https://cloud.google.com/bigtable/docs/access-control#iam-management-instance
58 | 
59 | ## Example
60 | 
61 | ```yaml
62 | sources:
63 |   my-bigtable-source:
64 |     kind: "bigtable"
65 |     project: "my-project-id"
66 |     instance: "test-instance"
67 | ```
68 | 
69 | ## Reference
70 | 
71 | | **field** | **type** | **required** | **description**                                                               |
72 | |-----------|:--------:|:------------:|-------------------------------------------------------------------------------|
73 | | kind      |  string  |     true     | Must be "bigtable".                                                           |
74 | | project   |  string  |     true     | Id of the GCP project that the cluster was created in (e.g. "my-project-id"). |
75 | | instance  |  string  |     true     | Name of the Bigtable instance.                                                |
76 | 
```

--------------------------------------------------------------------------------
/docs/en/resources/sources/mysql.md:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | title: "MySQL"
 3 | type: docs
 4 | weight: 1
 5 | description: >
 6 |   MySQL is a relational database management system that stores and manages data.
 7 | ---
 8 | 
 9 | ## About
10 | 
11 | [MySQL][mysql-docs] is a relational database management system (RDBMS) that
12 | stores and manages data. It's a popular choice for developers because of its
13 | reliability, performance, and ease of use.
14 | 
15 | [mysql-docs]: https://www.mysql.com/
16 | 
17 | ## Available Tools
18 | 
19 | - [`mysql-sql`](../tools/mysql/mysql-sql.md)
20 |   Execute pre-defined prepared SQL queries in MySQL.
21 | 
22 | - [`mysql-execute-sql`](../tools/mysql/mysql-execute-sql.md)
23 |   Run parameterized SQL queries in MySQL.
24 | 
25 | - [`mysql-list-active-queries`](../tools/mysql/mysql-list-active-queries.md)
26 |   List active queries in MySQL.
27 | 
28 | - [`mysql-list-tables`](../tools/mysql/mysql-list-tables.md)
29 |   List tables in a MySQL database.
30 | 
31 | - [`mysql-list-tables-missing-unique-indexes`](../tools/mysql/mysql-list-tables-missing-unique-indexes.md)
32 |   List tables in a MySQL database that do not have primary or unique indices.
33 | 
34 | - [`mysql-list-table-fragmentation`](../tools/mysql/mysql-list-table-fragmentation.md)
35 |   List table fragmentation in MySQL tables.
36 | 
37 | ## Requirements
38 | 
39 | ### Database User
40 | 
41 | This source only uses standard authentication. You will need to [create a
42 | MySQL user][mysql-users] to login to the database with.
43 | 
44 | [mysql-users]: https://dev.mysql.com/doc/refman/8.4/en/user-names.html
45 | 
46 | ## Example
47 | 
48 | ```yaml
49 | sources:
50 |     my-mysql-source:
51 |         kind: mysql
52 |         host: 127.0.0.1
53 |         port: 3306
54 |         database: my_db
55 |         user: ${USER_NAME}
56 |         password: ${PASSWORD}
57 |         # Optional TLS and other driver parameters. For example, enable preferred TLS:
58 |         # queryParams:
59 |         #     tls: preferred
60 |         queryTimeout: 30s # Optional: query timeout duration
61 | ```
62 | 
63 | {{< notice tip >}}
64 | Use environment variable replacement with the format ${ENV_NAME}
65 | instead of hardcoding your secrets into the configuration file.
66 | {{< /notice >}}
67 | 
68 | ## Reference
69 | 
70 | | **field**    | **type** | **required** | **description**                                                                                 |
71 | | ------------ | :------: | :----------: | ----------------------------------------------------------------------------------------------- |
72 | | kind         |  string  |     true     | Must be "mysql".                                                                                |
73 | | host         |  string  |     true     | IP address to connect to (e.g. "127.0.0.1").                                                    |
74 | | port         |  string  |     true     | Port to connect to (e.g. "3306").                                                               |
75 | | database     |  string  |     true     | Name of the MySQL database to connect to (e.g. "my_db").                                        |
76 | | user         |  string  |     true     | Name of the MySQL user to connect as (e.g. "my-mysql-user").                                    |
77 | | password     |  string  |     true     | Password of the MySQL user (e.g. "my-password").                                                |
78 | | queryTimeout |  string  |    false     | Maximum time to wait for query execution (e.g. "30s", "2m"). By default, no timeout is applied. |
79 | | queryParams | map<string,string> | false | Arbitrary DSN parameters passed to the driver (e.g. `tls: preferred`, `charset: utf8mb4`). Useful for enabling TLS or other connection options. |
80 | 
```
Page 7/45FirstPrevNextLast