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

# Directory Structure

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

# Files

--------------------------------------------------------------------------------
/internal/tools/looker/lookergetlooks/lookergetlooks.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 lookergetlooks
 15 | 
 16 | import (
 17 | 	"context"
 18 | 	"fmt"
 19 | 
 20 | 	yaml "github.com/goccy/go-yaml"
 21 | 	"github.com/googleapis/genai-toolbox/internal/sources"
 22 | 	lookersrc "github.com/googleapis/genai-toolbox/internal/sources/looker"
 23 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 24 | 	"github.com/googleapis/genai-toolbox/internal/tools/looker/lookercommon"
 25 | 	"github.com/googleapis/genai-toolbox/internal/util"
 26 | 
 27 | 	"github.com/looker-open-source/sdk-codegen/go/rtl"
 28 | 	v4 "github.com/looker-open-source/sdk-codegen/go/sdk/v4"
 29 | )
 30 | 
 31 | const kind string = "looker-get-looks"
 32 | 
 33 | func init() {
 34 | 	if !tools.Register(kind, newConfig) {
 35 | 		panic(fmt.Sprintf("tool kind %q already registered", kind))
 36 | 	}
 37 | }
 38 | 
 39 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
 40 | 	actual := Config{Name: name}
 41 | 	if err := decoder.DecodeContext(ctx, &actual); err != nil {
 42 | 		return nil, err
 43 | 	}
 44 | 	return actual, nil
 45 | }
 46 | 
 47 | type Config struct {
 48 | 	Name         string   `yaml:"name" validate:"required"`
 49 | 	Kind         string   `yaml:"kind" validate:"required"`
 50 | 	Source       string   `yaml:"source" validate:"required"`
 51 | 	Description  string   `yaml:"description" validate:"required"`
 52 | 	AuthRequired []string `yaml:"authRequired"`
 53 | }
 54 | 
 55 | // validate interface
 56 | var _ tools.ToolConfig = Config{}
 57 | 
 58 | func (cfg Config) ToolConfigKind() string {
 59 | 	return kind
 60 | }
 61 | 
 62 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
 63 | 	// verify source exists
 64 | 	rawS, ok := srcs[cfg.Source]
 65 | 	if !ok {
 66 | 		return nil, fmt.Errorf("no source named %q configured", cfg.Source)
 67 | 	}
 68 | 
 69 | 	// verify the source is compatible
 70 | 	s, ok := rawS.(*lookersrc.Source)
 71 | 	if !ok {
 72 | 		return nil, fmt.Errorf("invalid source for %q tool: source kind must be `looker`", kind)
 73 | 	}
 74 | 
 75 | 	titleParameter := tools.NewStringParameterWithDefault("title", "", "The title of the look.")
 76 | 	descParameter := tools.NewStringParameterWithDefault("desc", "", "The description of the look.")
 77 | 	limitParameter := tools.NewIntParameterWithDefault("limit", 100, "The number of looks to fetch. Default 100")
 78 | 	offsetParameter := tools.NewIntParameterWithDefault("offset", 0, "The number of looks to skip before fetching. Default 0")
 79 | 	parameters := tools.Parameters{
 80 | 		titleParameter,
 81 | 		descParameter,
 82 | 		limitParameter,
 83 | 		offsetParameter,
 84 | 	}
 85 | 
 86 | 	mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
 87 | 
 88 | 	// finish tool setup
 89 | 	return Tool{
 90 | 		Name:           cfg.Name,
 91 | 		Kind:           kind,
 92 | 		Parameters:     parameters,
 93 | 		AuthRequired:   cfg.AuthRequired,
 94 | 		UseClientOAuth: s.UseClientOAuth,
 95 | 		Client:         s.Client,
 96 | 		ApiSettings:    s.ApiSettings,
 97 | 		manifest: tools.Manifest{
 98 | 			Description:  cfg.Description,
 99 | 			Parameters:   parameters.Manifest(),
100 | 			AuthRequired: cfg.AuthRequired,
101 | 		},
102 | 		mcpManifest: mcpManifest,
103 | 	}, nil
104 | }
105 | 
106 | // validate interface
107 | var _ tools.Tool = Tool{}
108 | 
109 | type Tool struct {
110 | 	Name           string `yaml:"name"`
111 | 	Kind           string `yaml:"kind"`
112 | 	UseClientOAuth bool
113 | 	Client         *v4.LookerSDK
114 | 	ApiSettings    *rtl.ApiSettings
115 | 	AuthRequired   []string         `yaml:"authRequired"`
116 | 	Parameters     tools.Parameters `yaml:"parameters"`
117 | 	manifest       tools.Manifest
118 | 	mcpManifest    tools.McpManifest
119 | }
120 | 
121 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
122 | 	logger, err := util.LoggerFromContext(ctx)
123 | 	if err != nil {
124 | 		return nil, fmt.Errorf("unable to get logger from ctx: %s", err)
125 | 	}
126 | 	paramsMap := params.AsMap()
127 | 	title := paramsMap["title"].(string)
128 | 	title_ptr := &title
129 | 	if *title_ptr == "" {
130 | 		title_ptr = nil
131 | 	}
132 | 	desc := paramsMap["desc"].(string)
133 | 	desc_ptr := &desc
134 | 	if *desc_ptr == "" {
135 | 		desc_ptr = nil
136 | 	}
137 | 	limit := int64(paramsMap["limit"].(int))
138 | 	offset := int64(paramsMap["offset"].(int))
139 | 
140 | 	sdk, err := lookercommon.GetLookerSDK(t.UseClientOAuth, t.ApiSettings, t.Client, accessToken)
141 | 	if err != nil {
142 | 		return nil, fmt.Errorf("error getting sdk: %w", err)
143 | 	}
144 | 	req := v4.RequestSearchLooks{
145 | 		Title:       title_ptr,
146 | 		Description: desc_ptr,
147 | 		Limit:       &limit,
148 | 		Offset:      &offset,
149 | 	}
150 | 	resp, err := sdk.SearchLooks(req, t.ApiSettings)
151 | 	if err != nil {
152 | 		return nil, fmt.Errorf("error making get_looks request: %s", err)
153 | 	}
154 | 
155 | 	var data []any
156 | 	for _, v := range resp {
157 | 		logger.DebugContext(ctx, "Got response element of %v\n", v)
158 | 		vMap := make(map[string]any)
159 | 		if v.Id != nil {
160 | 			vMap["id"] = *v.Id
161 | 		}
162 | 		if v.Title != nil {
163 | 			vMap["title"] = *v.Title
164 | 		}
165 | 		if v.Description != nil {
166 | 			vMap["description"] = *v.Description
167 | 		}
168 | 		vMap["model_id"] = *v.Model.Id
169 | 		logger.DebugContext(ctx, "Converted to %v\n", vMap)
170 | 		data = append(data, vMap)
171 | 	}
172 | 	logger.DebugContext(ctx, "data = ", data)
173 | 
174 | 	return data, nil
175 | }
176 | 
177 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
178 | 	return tools.ParseParams(t.Parameters, data, claims)
179 | }
180 | 
181 | func (t Tool) Manifest() tools.Manifest {
182 | 	return t.manifest
183 | }
184 | 
185 | func (t Tool) McpManifest() tools.McpManifest {
186 | 	return t.mcpManifest
187 | }
188 | 
189 | func (t Tool) Authorized(verifiedAuthServices []string) bool {
190 | 	return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
191 | }
192 | 
193 | func (t Tool) RequiresClientAuthorization() bool {
194 | 	return t.UseClientOAuth
195 | }
196 | 
```

--------------------------------------------------------------------------------
/internal/sources/cloudsqlmysql/cloud_sql_mysql_test.go:
--------------------------------------------------------------------------------

```go
  1 | // Copyright 2025 Google LLC
  2 | //
  3 | // Licensed under the Apache License, Version 2.0 (the "License");
  4 | // you may not use this file except in compliance with the License.
  5 | // You may obtain a copy of the License at
  6 | //
  7 | //     http://www.apache.org/licenses/LICENSE-2.0
  8 | //
  9 | // Unless required by applicable law or agreed to in writing, software
 10 | // distributed under the License is distributed on an "AS IS" BASIS,
 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12 | // See the License for the specific language governing permissions and
 13 | // limitations under the License.
 14 | 
 15 | package cloudsqlmysql_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/cloudsqlmysql"
 24 | 	"github.com/googleapis/genai-toolbox/internal/testutils"
 25 | )
 26 | 
 27 | func TestParseFromYamlCloudSQLMySQL(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-mysql-instance:
 38 | 					kind: cloud-sql-mysql
 39 | 					project: my-project
 40 | 					region: my-region
 41 | 					instance: my-instance
 42 | 					database: my_db
 43 | 					user: my_user
 44 | 					password: my_pass
 45 | 			`,
 46 | 			want: server.SourceConfigs{
 47 | 				"my-mysql-instance": cloudsqlmysql.Config{
 48 | 					Name:     "my-mysql-instance",
 49 | 					Kind:     cloudsqlmysql.SourceKind,
 50 | 					Project:  "my-project",
 51 | 					Region:   "my-region",
 52 | 					Instance: "my-instance",
 53 | 					IPType:   "public",
 54 | 					Database: "my_db",
 55 | 					User:     "my_user",
 56 | 					Password: "my_pass",
 57 | 				},
 58 | 			},
 59 | 		},
 60 | 		{
 61 | 			desc: "public ipType",
 62 | 			in: `
 63 | 			sources:
 64 | 				my-mysql-instance:
 65 | 					kind: cloud-sql-mysql
 66 | 					project: my-project
 67 | 					region: my-region
 68 | 					instance: my-instance
 69 | 					ipType: Public
 70 | 					database: my_db
 71 | 					user: my_user
 72 | 					password: my_pass
 73 | 			`,
 74 | 			want: server.SourceConfigs{
 75 | 				"my-mysql-instance": cloudsqlmysql.Config{
 76 | 					Name:     "my-mysql-instance",
 77 | 					Kind:     cloudsqlmysql.SourceKind,
 78 | 					Project:  "my-project",
 79 | 					Region:   "my-region",
 80 | 					Instance: "my-instance",
 81 | 					IPType:   "public",
 82 | 					Database: "my_db",
 83 | 					User:     "my_user",
 84 | 					Password: "my_pass",
 85 | 				},
 86 | 			},
 87 | 		},
 88 | 		{
 89 | 			desc: "private ipType",
 90 | 			in: `
 91 | 			sources:
 92 | 				my-mysql-instance:
 93 | 					kind: cloud-sql-mysql
 94 | 					project: my-project
 95 | 					region: my-region
 96 | 					instance: my-instance
 97 | 					ipType: private 
 98 | 					database: my_db
 99 | 					user: my_user
100 | 					password: my_pass
101 | 			`,
102 | 			want: server.SourceConfigs{
103 | 				"my-mysql-instance": cloudsqlmysql.Config{
104 | 					Name:     "my-mysql-instance",
105 | 					Kind:     cloudsqlmysql.SourceKind,
106 | 					Project:  "my-project",
107 | 					Region:   "my-region",
108 | 					Instance: "my-instance",
109 | 					IPType:   "private",
110 | 					Database: "my_db",
111 | 					User:     "my_user",
112 | 					Password: "my_pass",
113 | 				},
114 | 			},
115 | 		},
116 | 		{
117 | 			desc: "psc ipType",
118 | 			in: `
119 | 			sources:
120 | 				my-mysql-instance:
121 | 					kind: cloud-sql-mysql
122 | 					project: my-project
123 | 					region: my-region
124 | 					instance: my-instance
125 | 					ipType: psc
126 | 					database: my_db
127 | 					user: my_user
128 | 					password: my_pass
129 | 			`,
130 | 			want: server.SourceConfigs{
131 | 				"my-mysql-instance": cloudsqlmysql.Config{
132 | 					Name:     "my-mysql-instance",
133 | 					Kind:     cloudsqlmysql.SourceKind,
134 | 					Project:  "my-project",
135 | 					Region:   "my-region",
136 | 					Instance: "my-instance",
137 | 					IPType:   "psc",
138 | 					Database: "my_db",
139 | 					User:     "my_user",
140 | 					Password: "my_pass",
141 | 				},
142 | 			},
143 | 		},
144 | 	}
145 | 	for _, tc := range tcs {
146 | 		t.Run(tc.desc, func(t *testing.T) {
147 | 			got := struct {
148 | 				Sources server.SourceConfigs `yaml:"sources"`
149 | 			}{}
150 | 			// Parse contents
151 | 			err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
152 | 			if err != nil {
153 | 				t.Fatalf("unable to unmarshal: %s", err)
154 | 			}
155 | 			if !cmp.Equal(tc.want, got.Sources) {
156 | 				t.Fatalf("incorrect parse: want %v, got %v", tc.want, got.Sources)
157 | 			}
158 | 		})
159 | 	}
160 | 
161 | }
162 | 
163 | func TestFailParseFromYaml(t *testing.T) {
164 | 	tcs := []struct {
165 | 		desc string
166 | 		in   string
167 | 		err  string
168 | 	}{
169 | 		{
170 | 			desc: "invalid ipType",
171 | 			in: `
172 | 			sources:
173 | 				my-mysql-instance:
174 | 					kind: cloud-sql-mysql
175 | 					project: my-project
176 | 					region: my-region
177 | 					instance: my-instance
178 | 					ipType: fail 
179 | 					database: my_db
180 | 					user: my_user
181 | 					password: my_pass
182 | 			`,
183 | 			err: "unable to parse source \"my-mysql-instance\" as \"cloud-sql-mysql\": ipType invalid: must be one of \"public\", \"private\", or \"psc\"",
184 | 		},
185 | 		{
186 | 			desc: "extra field",
187 | 			in: `
188 | 			sources:
189 | 				my-mysql-instance:
190 | 					kind: cloud-sql-mysql
191 | 					project: my-project
192 | 					region: my-region
193 | 					instance: my-instance
194 | 					database: my_db
195 | 					user: my_user
196 | 					password: my_pass
197 | 					foo: bar
198 | 			`,
199 | 			err: "unable to parse source \"my-mysql-instance\" as \"cloud-sql-mysql\": [2:1] unknown field \"foo\"\n   1 | database: my_db\n>  2 | foo: bar\n       ^\n   3 | instance: my-instance\n   4 | kind: cloud-sql-mysql\n   5 | password: my_pass\n   6 | ",
200 | 		},
201 | 		{
202 | 			desc: "missing required field",
203 | 			in: `
204 | 			sources:
205 | 				my-mysql-instance:
206 | 					kind: cloud-sql-mysql
207 | 					region: my-region
208 | 					instance: my-instance
209 | 					database: my_db
210 | 					user: my_user
211 | 					password: my_pass
212 | 			`,
213 | 			err: "unable to parse source \"my-mysql-instance\" as \"cloud-sql-mysql\": Key: 'Config.Project' Error:Field validation for 'Project' failed on the 'required' tag",
214 | 		},
215 | 	}
216 | 	for _, tc := range tcs {
217 | 		t.Run(tc.desc, func(t *testing.T) {
218 | 			got := struct {
219 | 				Sources server.SourceConfigs `yaml:"sources"`
220 | 			}{}
221 | 			// Parse contents
222 | 			err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
223 | 			if err == nil {
224 | 				t.Fatalf("expect parsing to fail")
225 | 			}
226 | 			errStr := err.Error()
227 | 			if errStr != tc.err {
228 | 				t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err)
229 | 			}
230 | 		})
231 | 	}
232 | }
233 | 
```

--------------------------------------------------------------------------------
/internal/tools/sqlite/sqliteexecutesql/sqliteexecutesql.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 sqliteexecutesql
 16 | 
 17 | import (
 18 | 	"context"
 19 | 	"database/sql"
 20 | 	"encoding/json"
 21 | 	"fmt"
 22 | 
 23 | 	yaml "github.com/goccy/go-yaml"
 24 | 	"github.com/googleapis/genai-toolbox/internal/sources"
 25 | 	"github.com/googleapis/genai-toolbox/internal/sources/sqlite"
 26 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 27 | 	"github.com/googleapis/genai-toolbox/internal/util"
 28 | )
 29 | 
 30 | const kind string = "sqlite-execute-sql"
 31 | 
 32 | func init() {
 33 | 	if !tools.Register(kind, newConfig) {
 34 | 		panic(fmt.Sprintf("tool kind %q already registered", kind))
 35 | 	}
 36 | }
 37 | 
 38 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
 39 | 	actual := Config{Name: name}
 40 | 	if err := decoder.DecodeContext(ctx, &actual); err != nil {
 41 | 		return nil, err
 42 | 	}
 43 | 	return actual, nil
 44 | }
 45 | 
 46 | type compatibleSource interface {
 47 | 	SQLiteDB() *sql.DB
 48 | }
 49 | 
 50 | // validate compatible sources are still compatible
 51 | var _ compatibleSource = &sqlite.Source{}
 52 | 
 53 | var compatibleSources = [...]string{sqlite.SourceKind}
 54 | 
 55 | type Config struct {
 56 | 	Name         string   `yaml:"name" validate:"required"`
 57 | 	Kind         string   `yaml:"kind" validate:"required"`
 58 | 	Source       string   `yaml:"source" validate:"required"`
 59 | 	Description  string   `yaml:"description" validate:"required"`
 60 | 	AuthRequired []string `yaml:"authRequired"`
 61 | }
 62 | 
 63 | // validate interface
 64 | var _ tools.ToolConfig = Config{}
 65 | 
 66 | func (cfg Config) ToolConfigKind() string {
 67 | 	return kind
 68 | }
 69 | 
 70 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
 71 | 	// verify source exists
 72 | 	rawS, ok := srcs[cfg.Source]
 73 | 	if !ok {
 74 | 		return nil, fmt.Errorf("no source named %q configured", cfg.Source)
 75 | 	}
 76 | 
 77 | 	// verify the source is compatible
 78 | 	s, ok := rawS.(compatibleSource)
 79 | 	if !ok {
 80 | 		return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
 81 | 	}
 82 | 
 83 | 	sqlParameter := tools.NewStringParameter("sql", "The sql to execute.")
 84 | 	parameters := tools.Parameters{sqlParameter}
 85 | 	mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
 86 | 
 87 | 	// finish tool setup
 88 | 	t := Tool{
 89 | 		Name:         cfg.Name,
 90 | 		Kind:         kind,
 91 | 		Parameters:   parameters,
 92 | 		AuthRequired: cfg.AuthRequired,
 93 | 		DB:           s.SQLiteDB(),
 94 | 		manifest:     tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired},
 95 | 		mcpManifest:  mcpManifest,
 96 | 	}
 97 | 	return t, nil
 98 | }
 99 | 
100 | // validate interface
101 | var _ tools.Tool = Tool{}
102 | 
103 | type Tool struct {
104 | 	Name         string           `yaml:"name"`
105 | 	Kind         string           `yaml:"kind"`
106 | 	AuthRequired []string         `yaml:"authRequired"`
107 | 	Parameters   tools.Parameters `yaml:"parameters"`
108 | 
109 | 	DB          *sql.DB
110 | 	manifest    tools.Manifest
111 | 	mcpManifest tools.McpManifest
112 | }
113 | 
114 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
115 | 	sql, ok := params.AsMap()["sql"].(string)
116 | 	if !ok {
117 | 		return nil, fmt.Errorf("missing or invalid 'sql' parameter")
118 | 	}
119 | 	if sql == "" {
120 | 		return nil, fmt.Errorf("sql parameter cannot be empty")
121 | 	}
122 | 
123 | 	// Log the query executed for debugging.
124 | 	logger, err := util.LoggerFromContext(ctx)
125 | 	if err != nil {
126 | 		return nil, fmt.Errorf("error getting logger: %s", err)
127 | 	}
128 | 	logger.DebugContext(ctx, "executing `%s` tool query: %s", kind, sql)
129 | 
130 | 	results, err := t.DB.QueryContext(ctx, sql)
131 | 	if err != nil {
132 | 		return nil, fmt.Errorf("unable to execute query: %w", err)
133 | 	}
134 | 
135 | 	cols, err := results.Columns()
136 | 	if err != nil {
137 | 		return nil, fmt.Errorf("unable to retrieve rows column name: %w", err)
138 | 	}
139 | 
140 | 	// The sqlite driver does not support ColumnTypes, so we can't get the
141 | 	// underlying database type of the columns. We'll have to rely on the
142 | 	// generic `any` type and then handle the JSON data separately.
143 | 
144 | 	// create an array of values for each column, which can be re-used to scan each row
145 | 	rawValues := make([]any, len(cols))
146 | 	values := make([]any, len(cols))
147 | 	for i := range rawValues {
148 | 		values[i] = &rawValues[i]
149 | 	}
150 | 	defer results.Close()
151 | 
152 | 	var out []any
153 | 	for results.Next() {
154 | 		err := results.Scan(values...)
155 | 		if err != nil {
156 | 			return nil, fmt.Errorf("unable to parse row: %w", err)
157 | 		}
158 | 		vMap := make(map[string]any)
159 | 		for i, name := range cols {
160 | 			val := rawValues[i]
161 | 			if val == nil {
162 | 				vMap[name] = nil
163 | 				continue
164 | 			}
165 | 
166 | 			// Handle JSON data
167 | 			if jsonString, ok := val.(string); ok {
168 | 				var unmarshaledData any
169 | 				if json.Unmarshal([]byte(jsonString), &unmarshaledData) == nil {
170 | 					vMap[name] = unmarshaledData
171 | 					continue
172 | 				}
173 | 			}
174 | 			vMap[name] = val
175 | 		}
176 | 		out = append(out, vMap)
177 | 	}
178 | 
179 | 	if err := results.Err(); err != nil {
180 | 		return nil, fmt.Errorf("errors encountered during row iteration: %w", err)
181 | 	}
182 | 
183 | 	if len(out) == 0 {
184 | 		return nil, nil
185 | 	}
186 | 
187 | 	return out, nil
188 | }
189 | 
190 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
191 | 	return tools.ParseParams(t.Parameters, data, claims)
192 | }
193 | 
194 | func (t Tool) Manifest() tools.Manifest {
195 | 	return t.manifest
196 | }
197 | 
198 | func (t Tool) McpManifest() tools.McpManifest {
199 | 	return t.mcpManifest
200 | }
201 | 
202 | func (t Tool) Authorized(verifiedAuthServices []string) bool {
203 | 	return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
204 | }
205 | 
206 | func (t Tool) RequiresClientAuthorization() bool {
207 | 	return false
208 | }
209 | 
```

--------------------------------------------------------------------------------
/internal/tools/cloudsql/cloudsqlcreateusers/cloudsqlcreateusers.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 cloudsqlcreateusers
 16 | 
 17 | import (
 18 | 	"context"
 19 | 	"fmt"
 20 | 
 21 | 	"github.com/goccy/go-yaml"
 22 | 	"github.com/googleapis/genai-toolbox/internal/sources"
 23 | 	"github.com/googleapis/genai-toolbox/internal/sources/cloudsqladmin"
 24 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 25 | 	sqladmin "google.golang.org/api/sqladmin/v1"
 26 | )
 27 | 
 28 | const kind string = "cloud-sql-create-users"
 29 | 
 30 | func init() {
 31 | 	if !tools.Register(kind, newConfig) {
 32 | 		panic(fmt.Sprintf("tool kind %q already registered", kind))
 33 | 	}
 34 | }
 35 | 
 36 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
 37 | 	actual := Config{Name: name}
 38 | 	if err := decoder.DecodeContext(ctx, &actual); err != nil {
 39 | 		return nil, err
 40 | 	}
 41 | 	return actual, nil
 42 | }
 43 | 
 44 | // Config defines the configuration for the create-user tool.
 45 | type Config struct {
 46 | 	Name         string   `yaml:"name" validate:"required"`
 47 | 	Kind         string   `yaml:"kind" validate:"required"`
 48 | 	Source       string   `yaml:"source" validate:"required"`
 49 | 	Description  string   `yaml:"description"`
 50 | 	AuthRequired []string `yaml:"authRequired"`
 51 | }
 52 | 
 53 | // validate interface
 54 | var _ tools.ToolConfig = Config{}
 55 | 
 56 | // ToolConfigKind returns the kind of the tool.
 57 | func (cfg Config) ToolConfigKind() string {
 58 | 	return kind
 59 | }
 60 | 
 61 | // Initialize initializes the tool from the configuration.
 62 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
 63 | 	rawS, ok := srcs[cfg.Source]
 64 | 	if !ok {
 65 | 		return nil, fmt.Errorf("no source named %q configured", cfg.Source)
 66 | 	}
 67 | 	s, ok := rawS.(*cloudsqladmin.Source)
 68 | 	if !ok {
 69 | 		return nil, fmt.Errorf("invalid source for %q tool: source kind must be `cloud-sql-admin`", kind)
 70 | 	}
 71 | 
 72 | 	allParameters := tools.Parameters{
 73 | 		tools.NewStringParameter("project", "The project ID"),
 74 | 		tools.NewStringParameter("instance", "The ID of the instance where the user will be created."),
 75 | 		tools.NewStringParameter("name", "The name for the new user. Must be unique within the instance."),
 76 | 		tools.NewStringParameterWithRequired("password", "A secure password for the new user. Not required for IAM users.", false),
 77 | 		tools.NewBooleanParameter("iamUser", "Set to true to create a Cloud IAM user."),
 78 | 	}
 79 | 	paramManifest := allParameters.Manifest()
 80 | 
 81 | 	description := cfg.Description
 82 | 	if description == "" {
 83 | 		description = "Creates a new user in a Cloud SQL instance. Both built-in and IAM users are supported. IAM users require an email account as the user name. IAM is the more secure and recommended way to manage users. The agent should always ask the user what type of user they want to create. For more information, see https://cloud.google.com/sql/docs/postgres/add-manage-iam-users"
 84 | 	}
 85 | 	mcpManifest := tools.GetMcpManifest(cfg.Name, description, cfg.AuthRequired, allParameters)
 86 | 
 87 | 	return Tool{
 88 | 		Name:         cfg.Name,
 89 | 		Kind:         kind,
 90 | 		AuthRequired: cfg.AuthRequired,
 91 | 		Source:       s,
 92 | 		AllParams:    allParameters,
 93 | 		manifest:     tools.Manifest{Description: cfg.Description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired},
 94 | 		mcpManifest:  mcpManifest,
 95 | 	}, nil
 96 | }
 97 | 
 98 | // Tool represents the create-user tool.
 99 | type Tool struct {
100 | 	Name         string   `yaml:"name"`
101 | 	Kind         string   `yaml:"kind"`
102 | 	Description  string   `yaml:"description"`
103 | 	AuthRequired []string `yaml:"authRequired"`
104 | 
105 | 	Source      *cloudsqladmin.Source
106 | 	AllParams   tools.Parameters `yaml:"allParams"`
107 | 	manifest    tools.Manifest
108 | 	mcpManifest tools.McpManifest
109 | }
110 | 
111 | // Invoke executes the tool's logic.
112 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
113 | 	paramsMap := params.AsMap()
114 | 
115 | 	project, ok := paramsMap["project"].(string)
116 | 	if !ok {
117 | 		return nil, fmt.Errorf("missing 'project' parameter")
118 | 	}
119 | 	instance, ok := paramsMap["instance"].(string)
120 | 	if !ok {
121 | 		return nil, fmt.Errorf("missing 'instance' parameter")
122 | 	}
123 | 	name, ok := paramsMap["name"].(string)
124 | 	if !ok {
125 | 		return nil, fmt.Errorf("missing 'name' parameter")
126 | 	}
127 | 
128 | 	iamUser, _ := paramsMap["iamUser"].(bool)
129 | 
130 | 	user := sqladmin.User{
131 | 		Name: name,
132 | 	}
133 | 
134 | 	if iamUser {
135 | 		user.Type = "CLOUD_IAM_USER"
136 | 	} else {
137 | 		user.Type = "BUILT_IN"
138 | 		password, ok := paramsMap["password"].(string)
139 | 		if !ok || password == "" {
140 | 			return nil, fmt.Errorf("missing 'password' parameter for non-IAM user")
141 | 		}
142 | 		user.Password = password
143 | 	}
144 | 
145 | 	service, err := t.Source.GetService(ctx, string(accessToken))
146 | 	if err != nil {
147 | 		return nil, err
148 | 	}
149 | 
150 | 	resp, err := service.Users.Insert(project, instance, &user).Do()
151 | 	if err != nil {
152 | 		return nil, fmt.Errorf("error creating user: %w", err)
153 | 	}
154 | 
155 | 	return resp, nil
156 | }
157 | 
158 | // ParseParams parses the parameters for the tool.
159 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
160 | 	return tools.ParseParams(t.AllParams, data, claims)
161 | }
162 | 
163 | // Manifest returns the tool's manifest.
164 | func (t Tool) Manifest() tools.Manifest {
165 | 	return t.manifest
166 | }
167 | 
168 | // McpManifest returns the tool's MCP manifest.
169 | func (t Tool) McpManifest() tools.McpManifest {
170 | 	return t.mcpManifest
171 | }
172 | 
173 | // Authorized checks if the tool is authorized.
174 | func (t Tool) Authorized(verifiedAuthServices []string) bool {
175 | 	return true
176 | }
177 | 
178 | func (t Tool) RequiresClientAuthorization() bool {
179 | 	return t.Source.UseClientAuthorization()
180 | }
181 | 
```

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

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

--------------------------------------------------------------------------------
/internal/tools/looker/lookergetdashboards/lookergetdashboards.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 lookergetdashboards
 15 | 
 16 | import (
 17 | 	"context"
 18 | 	"fmt"
 19 | 
 20 | 	yaml "github.com/goccy/go-yaml"
 21 | 	"github.com/googleapis/genai-toolbox/internal/sources"
 22 | 	lookersrc "github.com/googleapis/genai-toolbox/internal/sources/looker"
 23 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 24 | 	"github.com/googleapis/genai-toolbox/internal/tools/looker/lookercommon"
 25 | 	"github.com/googleapis/genai-toolbox/internal/util"
 26 | 
 27 | 	"github.com/looker-open-source/sdk-codegen/go/rtl"
 28 | 	v4 "github.com/looker-open-source/sdk-codegen/go/sdk/v4"
 29 | )
 30 | 
 31 | const kind string = "looker-get-dashboards"
 32 | 
 33 | func init() {
 34 | 	if !tools.Register(kind, newConfig) {
 35 | 		panic(fmt.Sprintf("tool kind %q already registered", kind))
 36 | 	}
 37 | }
 38 | 
 39 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
 40 | 	actual := Config{Name: name}
 41 | 	if err := decoder.DecodeContext(ctx, &actual); err != nil {
 42 | 		return nil, err
 43 | 	}
 44 | 	return actual, nil
 45 | }
 46 | 
 47 | type Config struct {
 48 | 	Name         string   `yaml:"name" validate:"required"`
 49 | 	Kind         string   `yaml:"kind" validate:"required"`
 50 | 	Source       string   `yaml:"source" validate:"required"`
 51 | 	Description  string   `yaml:"description" validate:"required"`
 52 | 	AuthRequired []string `yaml:"authRequired"`
 53 | }
 54 | 
 55 | // validate interface
 56 | var _ tools.ToolConfig = Config{}
 57 | 
 58 | func (cfg Config) ToolConfigKind() string {
 59 | 	return kind
 60 | }
 61 | 
 62 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
 63 | 	// verify source exists
 64 | 	rawS, ok := srcs[cfg.Source]
 65 | 	if !ok {
 66 | 		return nil, fmt.Errorf("no source named %q configured", cfg.Source)
 67 | 	}
 68 | 
 69 | 	// verify the source is compatible
 70 | 	s, ok := rawS.(*lookersrc.Source)
 71 | 	if !ok {
 72 | 		return nil, fmt.Errorf("invalid source for %q tool: source kind must be `looker`", kind)
 73 | 	}
 74 | 
 75 | 	titleParameter := tools.NewStringParameterWithDefault("title", "", "The title of the dashboard.")
 76 | 	descParameter := tools.NewStringParameterWithDefault("desc", "", "The description of the dashboard.")
 77 | 	limitParameter := tools.NewIntParameterWithDefault("limit", 100, "The number of dashboards to fetch. Default 100")
 78 | 	offsetParameter := tools.NewIntParameterWithDefault("offset", 0, "The number of dashboards to skip before fetching. Default 0")
 79 | 	parameters := tools.Parameters{
 80 | 		titleParameter,
 81 | 		descParameter,
 82 | 		limitParameter,
 83 | 		offsetParameter,
 84 | 	}
 85 | 
 86 | 	mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
 87 | 
 88 | 	// finish tool setup
 89 | 	return Tool{
 90 | 		Name:           cfg.Name,
 91 | 		Kind:           kind,
 92 | 		Parameters:     parameters,
 93 | 		AuthRequired:   cfg.AuthRequired,
 94 | 		UseClientOAuth: s.UseClientOAuth,
 95 | 		Client:         s.Client,
 96 | 		ApiSettings:    s.ApiSettings,
 97 | 		manifest: tools.Manifest{
 98 | 			Description:  cfg.Description,
 99 | 			Parameters:   parameters.Manifest(),
100 | 			AuthRequired: cfg.AuthRequired,
101 | 		},
102 | 		mcpManifest: mcpManifest,
103 | 	}, nil
104 | }
105 | 
106 | // validate interface
107 | var _ tools.Tool = Tool{}
108 | 
109 | type Tool struct {
110 | 	Name           string `yaml:"name"`
111 | 	Kind           string `yaml:"kind"`
112 | 	UseClientOAuth bool
113 | 	Client         *v4.LookerSDK
114 | 	ApiSettings    *rtl.ApiSettings
115 | 	AuthRequired   []string         `yaml:"authRequired"`
116 | 	Parameters     tools.Parameters `yaml:"parameters"`
117 | 	manifest       tools.Manifest
118 | 	mcpManifest    tools.McpManifest
119 | }
120 | 
121 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
122 | 	logger, err := util.LoggerFromContext(ctx)
123 | 	if err != nil {
124 | 		return nil, fmt.Errorf("unable to get logger from ctx: %s", err)
125 | 	}
126 | 	paramsMap := params.AsMap()
127 | 	title := paramsMap["title"].(string)
128 | 	title_ptr := &title
129 | 	if *title_ptr == "" {
130 | 		title_ptr = nil
131 | 	}
132 | 	desc := paramsMap["desc"].(string)
133 | 	desc_ptr := &desc
134 | 	if *desc_ptr == "" {
135 | 		desc_ptr = nil
136 | 	}
137 | 	limit := int64(paramsMap["limit"].(int))
138 | 	offset := int64(paramsMap["offset"].(int))
139 | 
140 | 	sdk, err := lookercommon.GetLookerSDK(t.UseClientOAuth, t.ApiSettings, t.Client, accessToken)
141 | 	if err != nil {
142 | 		return nil, fmt.Errorf("error getting sdk: %w", err)
143 | 	}
144 | 	req := v4.RequestSearchDashboards{
145 | 		Title:       title_ptr,
146 | 		Description: desc_ptr,
147 | 		Limit:       &limit,
148 | 		Offset:      &offset,
149 | 	}
150 | 	logger.ErrorContext(ctx, "Making request %v", req)
151 | 	resp, err := sdk.SearchDashboards(req, t.ApiSettings)
152 | 	if err != nil {
153 | 		return nil, fmt.Errorf("error making get_dashboards request: %s", err)
154 | 	}
155 | 	logger.ErrorContext(ctx, "Got response %v", resp)
156 | 	var data []any
157 | 	for _, v := range resp {
158 | 		logger.DebugContext(ctx, "Got response element of %v\n", v)
159 | 		vMap := make(map[string]any)
160 | 		if v.Id != nil {
161 | 			vMap["id"] = *v.Id
162 | 		}
163 | 		if v.Title != nil {
164 | 			vMap["title"] = *v.Title
165 | 		}
166 | 		if v.Description != nil {
167 | 			vMap["description"] = *v.Description
168 | 		}
169 | 		logger.DebugContext(ctx, "Converted to %v\n", vMap)
170 | 		data = append(data, vMap)
171 | 	}
172 | 	logger.DebugContext(ctx, "data = ", data)
173 | 
174 | 	return data, nil
175 | }
176 | 
177 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
178 | 	return tools.ParseParams(t.Parameters, data, claims)
179 | }
180 | 
181 | func (t Tool) Manifest() tools.Manifest {
182 | 	return t.manifest
183 | }
184 | 
185 | func (t Tool) McpManifest() tools.McpManifest {
186 | 	return t.mcpManifest
187 | }
188 | 
189 | func (t Tool) Authorized(verifiedAuthServices []string) bool {
190 | 	return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
191 | }
192 | 
193 | func (t Tool) RequiresClientAuthorization() bool {
194 | 	return t.UseClientOAuth
195 | }
196 | 
```

--------------------------------------------------------------------------------
/internal/tools/clickhouse/clickhousesql/clickhousesql.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 | 	"context"
 19 | 	"database/sql"
 20 | 	"fmt"
 21 | 
 22 | 	yaml "github.com/goccy/go-yaml"
 23 | 	"github.com/googleapis/genai-toolbox/internal/sources"
 24 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 25 | )
 26 | 
 27 | type compatibleSource interface {
 28 | 	ClickHousePool() *sql.DB
 29 | }
 30 | 
 31 | var compatibleSources = []string{"clickhouse"}
 32 | 
 33 | const sqlKind string = "clickhouse-sql"
 34 | 
 35 | func init() {
 36 | 	if !tools.Register(sqlKind, newSQLConfig) {
 37 | 		panic(fmt.Sprintf("tool kind %q already registered", sqlKind))
 38 | 	}
 39 | }
 40 | 
 41 | func newSQLConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
 42 | 	actual := Config{Name: name}
 43 | 	if err := decoder.DecodeContext(ctx, &actual); err != nil {
 44 | 		return nil, err
 45 | 	}
 46 | 	return actual, nil
 47 | }
 48 | 
 49 | type Config struct {
 50 | 	Name               string           `yaml:"name" validate:"required"`
 51 | 	Kind               string           `yaml:"kind" validate:"required"`
 52 | 	Source             string           `yaml:"source" validate:"required"`
 53 | 	Description        string           `yaml:"description" validate:"required"`
 54 | 	Statement          string           `yaml:"statement" validate:"required"`
 55 | 	AuthRequired       []string         `yaml:"authRequired"`
 56 | 	Parameters         tools.Parameters `yaml:"parameters"`
 57 | 	TemplateParameters tools.Parameters `yaml:"templateParameters"`
 58 | }
 59 | 
 60 | var _ tools.ToolConfig = Config{}
 61 | 
 62 | func (cfg Config) ToolConfigKind() string {
 63 | 	return sqlKind
 64 | }
 65 | 
 66 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
 67 | 	rawS, ok := srcs[cfg.Source]
 68 | 	if !ok {
 69 | 		return nil, fmt.Errorf("no source named %q configured", cfg.Source)
 70 | 	}
 71 | 
 72 | 	s, ok := rawS.(compatibleSource)
 73 | 	if !ok {
 74 | 		return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", sqlKind, compatibleSources)
 75 | 	}
 76 | 
 77 | 	allParameters, paramManifest, _ := tools.ProcessParameters(cfg.TemplateParameters, cfg.Parameters)
 78 | 	mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, allParameters)
 79 | 
 80 | 	t := Tool{
 81 | 		Name:               cfg.Name,
 82 | 		Kind:               sqlKind,
 83 | 		Parameters:         cfg.Parameters,
 84 | 		TemplateParameters: cfg.TemplateParameters,
 85 | 		AllParams:          allParameters,
 86 | 		Statement:          cfg.Statement,
 87 | 		AuthRequired:       cfg.AuthRequired,
 88 | 		Pool:               s.ClickHousePool(),
 89 | 		manifest:           tools.Manifest{Description: cfg.Description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired},
 90 | 		mcpManifest:        mcpManifest,
 91 | 	}
 92 | 	return t, nil
 93 | }
 94 | 
 95 | var _ tools.Tool = Tool{}
 96 | 
 97 | type Tool struct {
 98 | 	Name               string           `yaml:"name"`
 99 | 	Kind               string           `yaml:"kind"`
100 | 	AuthRequired       []string         `yaml:"authRequired"`
101 | 	Parameters         tools.Parameters `yaml:"parameters"`
102 | 	TemplateParameters tools.Parameters `yaml:"templateParameters"`
103 | 	AllParams          tools.Parameters `yaml:"allParams"`
104 | 
105 | 	Pool        *sql.DB
106 | 	Statement   string
107 | 	manifest    tools.Manifest
108 | 	mcpManifest tools.McpManifest
109 | }
110 | 
111 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, token tools.AccessToken) (any, error) {
112 | 	paramsMap := params.AsMap()
113 | 	newStatement, err := tools.ResolveTemplateParams(t.TemplateParameters, t.Statement, paramsMap)
114 | 	if err != nil {
115 | 		return nil, fmt.Errorf("unable to extract template params: %w", err)
116 | 	}
117 | 
118 | 	newParams, err := tools.GetParams(t.Parameters, paramsMap)
119 | 	if err != nil {
120 | 		return nil, fmt.Errorf("unable to extract standard params: %w", err)
121 | 	}
122 | 
123 | 	sliceParams := newParams.AsSlice()
124 | 	results, err := t.Pool.QueryContext(ctx, newStatement, sliceParams...)
125 | 	if err != nil {
126 | 		return nil, fmt.Errorf("unable to execute query: %w", err)
127 | 	}
128 | 
129 | 	cols, err := results.Columns()
130 | 	if err != nil {
131 | 		return nil, fmt.Errorf("unable to retrieve rows column name: %w", err)
132 | 	}
133 | 
134 | 	rawValues := make([]any, len(cols))
135 | 	values := make([]any, len(cols))
136 | 	for i := range rawValues {
137 | 		values[i] = &rawValues[i]
138 | 	}
139 | 
140 | 	colTypes, err := results.ColumnTypes()
141 | 	if err != nil {
142 | 		return nil, fmt.Errorf("unable to get column types: %w", err)
143 | 	}
144 | 
145 | 	var out []any
146 | 	for results.Next() {
147 | 		err := results.Scan(values...)
148 | 		if err != nil {
149 | 			return nil, fmt.Errorf("unable to parse row: %w", err)
150 | 		}
151 | 		vMap := make(map[string]any)
152 | 		for i, name := range cols {
153 | 			switch colTypes[i].DatabaseTypeName() {
154 | 			case "String", "FixedString":
155 | 				if rawValues[i] != nil {
156 | 					// Handle potential []byte to string conversion if needed
157 | 					if b, ok := rawValues[i].([]byte); ok {
158 | 						vMap[name] = string(b)
159 | 					} else {
160 | 						vMap[name] = rawValues[i]
161 | 					}
162 | 				} else {
163 | 					vMap[name] = nil
164 | 				}
165 | 			default:
166 | 				vMap[name] = rawValues[i]
167 | 			}
168 | 		}
169 | 		out = append(out, vMap)
170 | 	}
171 | 
172 | 	err = results.Close()
173 | 	if err != nil {
174 | 		return nil, fmt.Errorf("unable to close rows: %w", err)
175 | 	}
176 | 
177 | 	if err := results.Err(); err != nil {
178 | 		return nil, fmt.Errorf("errors encountered by results.Scan: %w", err)
179 | 	}
180 | 
181 | 	return out, nil
182 | }
183 | 
184 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
185 | 	return tools.ParseParams(t.AllParams, data, claims)
186 | }
187 | 
188 | func (t Tool) Manifest() tools.Manifest {
189 | 	return t.manifest
190 | }
191 | 
192 | func (t Tool) McpManifest() tools.McpManifest {
193 | 	return t.mcpManifest
194 | }
195 | 
196 | func (t Tool) Authorized(verifiedAuthServices []string) bool {
197 | 	return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
198 | }
199 | 
200 | func (t Tool) RequiresClientAuthorization() bool {
201 | 	return false
202 | }
203 | 
```

--------------------------------------------------------------------------------
/internal/tools/spanner/spannersql/spanner_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 spannersql_test
 16 | 
 17 | import (
 18 | 	"testing"
 19 | 
 20 | 	yaml "github.com/goccy/go-yaml"
 21 | 	"github.com/google/go-cmp/cmp"
 22 | 	"github.com/googleapis/genai-toolbox/internal/server"
 23 | 	"github.com/googleapis/genai-toolbox/internal/testutils"
 24 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 25 | 	"github.com/googleapis/genai-toolbox/internal/tools/spanner/spannersql"
 26 | )
 27 | 
 28 | func TestParseFromYamlSpanner(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: spanner-sql
 44 | 					source: my-pg-instance
 45 | 					description: some description
 46 | 					statement: |
 47 | 						SELECT * FROM SQL_STATEMENT;
 48 | 					parameters:
 49 | 						- name: country
 50 | 						  type: string
 51 | 						  description: some description
 52 | 			`,
 53 | 			want: server.ToolConfigs{
 54 | 				"example_tool": spannersql.Config{
 55 | 					Name:         "example_tool",
 56 | 					Kind:         "spanner-sql",
 57 | 					Source:       "my-pg-instance",
 58 | 					Description:  "some description",
 59 | 					Statement:    "SELECT * FROM SQL_STATEMENT;\n",
 60 | 					AuthRequired: []string{},
 61 | 					Parameters: []tools.Parameter{
 62 | 						tools.NewStringParameter("country", "some description"),
 63 | 					},
 64 | 				},
 65 | 			},
 66 | 		},
 67 | 		{
 68 | 			desc: "read only set to true",
 69 | 			in: `
 70 | 			tools:
 71 | 				example_tool:
 72 | 					kind: spanner-sql
 73 | 					source: my-pg-instance
 74 | 					description: some description
 75 | 					readOnly: true
 76 | 					statement: |
 77 | 						SELECT * FROM SQL_STATEMENT;
 78 | 					parameters:
 79 | 						- name: country
 80 | 						  type: string
 81 | 						  description: some description
 82 | 			`,
 83 | 			want: server.ToolConfigs{
 84 | 				"example_tool": spannersql.Config{
 85 | 					Name:         "example_tool",
 86 | 					Kind:         "spanner-sql",
 87 | 					Source:       "my-pg-instance",
 88 | 					Description:  "some description",
 89 | 					Statement:    "SELECT * FROM SQL_STATEMENT;\n",
 90 | 					ReadOnly:     true,
 91 | 					AuthRequired: []string{},
 92 | 					Parameters: []tools.Parameter{
 93 | 						tools.NewStringParameter("country", "some description"),
 94 | 					},
 95 | 				},
 96 | 			},
 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("unable to unmarshal: %s", err)
108 | 			}
109 | 			if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
110 | 				t.Fatalf("incorrect parse: diff %v", diff)
111 | 			}
112 | 		})
113 | 	}
114 | 
115 | }
116 | 
117 | func TestParseFromYamlWithTemplateParamsSpanner(t *testing.T) {
118 | 	ctx, err := testutils.ContextWithNewLogger()
119 | 	if err != nil {
120 | 		t.Fatalf("unexpected error: %s", err)
121 | 	}
122 | 	tcs := []struct {
123 | 		desc string
124 | 		in   string
125 | 		want server.ToolConfigs
126 | 	}{
127 | 		{
128 | 			desc: "basic example",
129 | 			in: `
130 | 			tools:
131 | 				example_tool:
132 | 					kind: spanner-sql
133 | 					source: my-pg-instance
134 | 					description: some description
135 | 					statement: |
136 | 						SELECT * FROM SQL_STATEMENT;
137 | 					parameters:
138 | 						- name: country
139 | 						  type: string
140 | 						  description: some description
141 | 					templateParameters:
142 | 						- name: tableName
143 | 						  type: string
144 | 						  description: The table to select hotels from.
145 | 						- name: fieldArray
146 | 						  type: array
147 | 						  description: The columns to return for the query.
148 | 						  items: 
149 | 								name: column
150 | 								type: string
151 | 								description: A column name that will be returned from the query.
152 | 			`,
153 | 			want: server.ToolConfigs{
154 | 				"example_tool": spannersql.Config{
155 | 					Name:         "example_tool",
156 | 					Kind:         "spanner-sql",
157 | 					Source:       "my-pg-instance",
158 | 					Description:  "some description",
159 | 					Statement:    "SELECT * FROM SQL_STATEMENT;\n",
160 | 					AuthRequired: []string{},
161 | 					Parameters: []tools.Parameter{
162 | 						tools.NewStringParameter("country", "some description"),
163 | 					},
164 | 					TemplateParameters: []tools.Parameter{
165 | 						tools.NewStringParameter("tableName", "The table to select hotels from."),
166 | 						tools.NewArrayParameter("fieldArray", "The columns to return for the query.", tools.NewStringParameter("column", "A column name that will be returned from the query.")),
167 | 					},
168 | 				},
169 | 			},
170 | 		},
171 | 		{
172 | 			desc: "read only set to true",
173 | 			in: `
174 | 			tools:
175 | 				example_tool:
176 | 					kind: spanner-sql
177 | 					source: my-pg-instance
178 | 					description: some description
179 | 					readOnly: true
180 | 					statement: |
181 | 						SELECT * FROM SQL_STATEMENT;
182 | 					parameters:
183 | 						- name: country
184 | 						  type: string
185 | 						  description: some description
186 | 			`,
187 | 			want: server.ToolConfigs{
188 | 				"example_tool": spannersql.Config{
189 | 					Name:         "example_tool",
190 | 					Kind:         "spanner-sql",
191 | 					Source:       "my-pg-instance",
192 | 					Description:  "some description",
193 | 					Statement:    "SELECT * FROM SQL_STATEMENT;\n",
194 | 					ReadOnly:     true,
195 | 					AuthRequired: []string{},
196 | 					Parameters: []tools.Parameter{
197 | 						tools.NewStringParameter("country", "some description"),
198 | 					},
199 | 				},
200 | 			},
201 | 		},
202 | 	}
203 | 	for _, tc := range tcs {
204 | 		t.Run(tc.desc, func(t *testing.T) {
205 | 			got := struct {
206 | 				Tools server.ToolConfigs `yaml:"tools"`
207 | 			}{}
208 | 			// Parse contents
209 | 			err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
210 | 			if err != nil {
211 | 				t.Fatalf("unable to unmarshal: %s", err)
212 | 			}
213 | 			if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
214 | 				t.Fatalf("incorrect parse: diff %v", diff)
215 | 			}
216 | 		})
217 | 	}
218 | 
219 | }
220 | 
```

--------------------------------------------------------------------------------
/internal/sources/looker/looker.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 looker
 15 | 
 16 | import (
 17 | 	"context"
 18 | 	"fmt"
 19 | 	"time"
 20 | 
 21 | 	geminidataanalytics "cloud.google.com/go/geminidataanalytics/apiv1beta"
 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"
 27 | 	"golang.org/x/oauth2/google"
 28 | 
 29 | 	"github.com/looker-open-source/sdk-codegen/go/rtl"
 30 | 	v4 "github.com/looker-open-source/sdk-codegen/go/sdk/v4"
 31 | )
 32 | 
 33 | const SourceKind string = "looker"
 34 | 
 35 | // validate interface
 36 | var _ sources.SourceConfig = Config{}
 37 | 
 38 | func init() {
 39 | 	if !sources.Register(SourceKind, newConfig) {
 40 | 		panic(fmt.Sprintf("source kind %q already registered", SourceKind))
 41 | 	}
 42 | }
 43 | 
 44 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) {
 45 | 	actual := Config{
 46 | 		Name:               name,
 47 | 		SslVerification:    true,
 48 | 		Timeout:            "600s",
 49 | 		UseClientOAuth:     false,
 50 | 		ShowHiddenModels:   true,
 51 | 		ShowHiddenExplores: true,
 52 | 		ShowHiddenFields:   true,
 53 | 		Location:           "us",
 54 | 	} // Default Ssl,timeout, ShowHidden
 55 | 	if err := decoder.DecodeContext(ctx, &actual); err != nil {
 56 | 		return nil, err
 57 | 	}
 58 | 	return actual, nil
 59 | }
 60 | 
 61 | type Config struct {
 62 | 	Name               string `yaml:"name" validate:"required"`
 63 | 	Kind               string `yaml:"kind" validate:"required"`
 64 | 	BaseURL            string `yaml:"base_url" validate:"required"`
 65 | 	ClientId           string `yaml:"client_id"`
 66 | 	ClientSecret       string `yaml:"client_secret"`
 67 | 	SslVerification    bool   `yaml:"verify_ssl"`
 68 | 	UseClientOAuth     bool   `yaml:"use_client_oauth"`
 69 | 	Timeout            string `yaml:"timeout"`
 70 | 	ShowHiddenModels   bool   `yaml:"show_hidden_models"`
 71 | 	ShowHiddenExplores bool   `yaml:"show_hidden_explores"`
 72 | 	ShowHiddenFields   bool   `yaml:"show_hidden_fields"`
 73 | 	Project            string `yaml:"project"`
 74 | 	Location           string `yaml:"location"`
 75 | }
 76 | 
 77 | func (r Config) SourceConfigKind() string {
 78 | 	return SourceKind
 79 | }
 80 | 
 81 | // Initialize initializes a Looker Source instance.
 82 | func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) {
 83 | 	logger, err := util.LoggerFromContext(ctx)
 84 | 	if err != nil {
 85 | 		return nil, fmt.Errorf("unable to get logger from ctx: %s", err)
 86 | 	}
 87 | 
 88 | 	userAgent, err := util.UserAgentFromContext(ctx)
 89 | 	if err != nil {
 90 | 		return nil, err
 91 | 	}
 92 | 
 93 | 	duration, err := time.ParseDuration(r.Timeout)
 94 | 	if err != nil {
 95 | 		return nil, fmt.Errorf("unable to parse Timeout string as time.Duration: %s", err)
 96 | 	}
 97 | 
 98 | 	if !r.SslVerification {
 99 | 		logger.WarnContext(ctx, "Insecure HTTP is enabled for Looker source %s. TLS certificate verification is skipped.\n", r.Name)
100 | 	}
101 | 	cfg := rtl.ApiSettings{
102 | 		AgentTag:     userAgent,
103 | 		BaseUrl:      r.BaseURL,
104 | 		ApiVersion:   "4.0",
105 | 		VerifySsl:    r.SslVerification,
106 | 		Timeout:      int32(duration.Seconds()),
107 | 		ClientId:     r.ClientId,
108 | 		ClientSecret: r.ClientSecret,
109 | 	}
110 | 
111 | 	var tokenSource oauth2.TokenSource
112 | 	tokenSource, _ = initGoogleCloudConnection(ctx)
113 | 
114 | 	s := &Source{
115 | 		Name:               r.Name,
116 | 		Kind:               SourceKind,
117 | 		Timeout:            r.Timeout,
118 | 		UseClientOAuth:     r.UseClientOAuth,
119 | 		ApiSettings:        &cfg,
120 | 		ShowHiddenModels:   r.ShowHiddenModels,
121 | 		ShowHiddenExplores: r.ShowHiddenExplores,
122 | 		ShowHiddenFields:   r.ShowHiddenFields,
123 | 		Project:            r.Project,
124 | 		Location:           r.Location,
125 | 		TokenSource:        tokenSource,
126 | 	}
127 | 
128 | 	if !r.UseClientOAuth {
129 | 		if r.ClientId == "" || r.ClientSecret == "" {
130 | 			return nil, fmt.Errorf("client_id and client_secret need to be specified")
131 | 		}
132 | 		s.Client = v4.NewLookerSDK(rtl.NewAuthSession(cfg))
133 | 		resp, err := s.Client.Me("", s.ApiSettings)
134 | 		if err != nil {
135 | 			return nil, fmt.Errorf("incorrect settings: %w", err)
136 | 		}
137 | 		logger.DebugContext(ctx, fmt.Sprintf("logged in as %s %s", *resp.FirstName, *resp.LastName))
138 | 	}
139 | 
140 | 	return s, nil
141 | 
142 | }
143 | 
144 | var _ sources.Source = &Source{}
145 | 
146 | type Source struct {
147 | 	Name               string `yaml:"name"`
148 | 	Kind               string `yaml:"kind"`
149 | 	Timeout            string `yaml:"timeout"`
150 | 	Client             *v4.LookerSDK
151 | 	ApiSettings        *rtl.ApiSettings
152 | 	UseClientOAuth     bool   `yaml:"use_client_oauth"`
153 | 	ShowHiddenModels   bool   `yaml:"show_hidden_models"`
154 | 	ShowHiddenExplores bool   `yaml:"show_hidden_explores"`
155 | 	ShowHiddenFields   bool   `yaml:"show_hidden_fields"`
156 | 	Project            string `yaml:"project"`
157 | 	Location           string `yaml:"location"`
158 | 	TokenSource        oauth2.TokenSource
159 | }
160 | 
161 | func (s *Source) SourceKind() string {
162 | 	return SourceKind
163 | }
164 | 
165 | func (s *Source) GetApiSettings() *rtl.ApiSettings {
166 | 	return s.ApiSettings
167 | }
168 | 
169 | func (s *Source) UseClientAuthorization() bool {
170 | 	return s.UseClientOAuth
171 | }
172 | 
173 | func (s *Source) GoogleCloudProject() string {
174 | 	return s.Project
175 | }
176 | 
177 | func (s *Source) GoogleCloudLocation() string {
178 | 	return s.Location
179 | }
180 | 
181 | func (s *Source) GoogleCloudTokenSource() oauth2.TokenSource {
182 | 	return s.TokenSource
183 | }
184 | 
185 | func (s *Source) GoogleCloudTokenSourceWithScope(ctx context.Context, scope string) (oauth2.TokenSource, error) {
186 | 	return google.DefaultTokenSource(ctx, scope)
187 | }
188 | 
189 | func initGoogleCloudConnection(ctx context.Context) (oauth2.TokenSource, error) {
190 | 	cred, err := google.FindDefaultCredentials(ctx, geminidataanalytics.DefaultAuthScopes()...)
191 | 	if err != nil {
192 | 		return nil, fmt.Errorf("failed to find default Google Cloud credentials with scope %q: %w", geminidataanalytics.DefaultAuthScopes(), err)
193 | 	}
194 | 
195 | 	return cred.TokenSource, nil
196 | }
197 | 
```

--------------------------------------------------------------------------------
/internal/sources/trino/trino_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 trino
 16 | 
 17 | import (
 18 | 	"testing"
 19 | 
 20 | 	"github.com/goccy/go-yaml"
 21 | 	"github.com/google/go-cmp/cmp"
 22 | 	"github.com/googleapis/genai-toolbox/internal/server"
 23 | 	"github.com/googleapis/genai-toolbox/internal/testutils"
 24 | )
 25 | 
 26 | func TestBuildTrinoDSN(t *testing.T) {
 27 | 	tests := []struct {
 28 | 		name            string
 29 | 		host            string
 30 | 		port            string
 31 | 		user            string
 32 | 		password        string
 33 | 		catalog         string
 34 | 		schema          string
 35 | 		queryTimeout    string
 36 | 		accessToken     string
 37 | 		kerberosEnabled bool
 38 | 		sslEnabled      bool
 39 | 		want            string
 40 | 		wantErr         bool
 41 | 	}{
 42 | 		{
 43 | 			name:    "basic configuration",
 44 | 			host:    "localhost",
 45 | 			port:    "8080",
 46 | 			user:    "testuser",
 47 | 			catalog: "hive",
 48 | 			schema:  "default",
 49 | 			want:    "http://testuser@localhost:8080?catalog=hive&schema=default",
 50 | 			wantErr: false,
 51 | 		},
 52 | 		{
 53 | 			name:     "with password",
 54 | 			host:     "localhost",
 55 | 			port:     "8080",
 56 | 			user:     "testuser",
 57 | 			password: "testpass",
 58 | 			catalog:  "hive",
 59 | 			schema:   "default",
 60 | 			want:     "http://testuser:testpass@localhost:8080?catalog=hive&schema=default",
 61 | 			wantErr:  false,
 62 | 		},
 63 | 		{
 64 | 			name:       "with SSL",
 65 | 			host:       "localhost",
 66 | 			port:       "8443",
 67 | 			user:       "testuser",
 68 | 			catalog:    "hive",
 69 | 			schema:     "default",
 70 | 			sslEnabled: true,
 71 | 			want:       "https://testuser@localhost:8443?catalog=hive&schema=default",
 72 | 			wantErr:    false,
 73 | 		},
 74 | 		{
 75 | 			name:        "with access token",
 76 | 			host:        "localhost",
 77 | 			port:        "8080",
 78 | 			user:        "testuser",
 79 | 			catalog:     "hive",
 80 | 			schema:      "default",
 81 | 			accessToken: "jwt-token-here",
 82 | 			want:        "http://testuser@localhost:8080?accessToken=jwt-token-here&catalog=hive&schema=default",
 83 | 			wantErr:     false,
 84 | 		},
 85 | 		{
 86 | 			name:            "with kerberos",
 87 | 			host:            "localhost",
 88 | 			port:            "8080",
 89 | 			user:            "testuser",
 90 | 			catalog:         "hive",
 91 | 			schema:          "default",
 92 | 			kerberosEnabled: true,
 93 | 			want:            "http://testuser@localhost:8080?KerberosEnabled=true&catalog=hive&schema=default",
 94 | 			wantErr:         false,
 95 | 		},
 96 | 		{
 97 | 			name:         "with query timeout",
 98 | 			host:         "localhost",
 99 | 			port:         "8080",
100 | 			user:         "testuser",
101 | 			catalog:      "hive",
102 | 			schema:       "default",
103 | 			queryTimeout: "30m",
104 | 			want:         "http://testuser@localhost:8080?catalog=hive&queryTimeout=30m&schema=default",
105 | 			wantErr:      false,
106 | 		},
107 | 		{
108 | 			name:    "anonymous access (empty user)",
109 | 			host:    "localhost",
110 | 			port:    "8080",
111 | 			catalog: "hive",
112 | 			schema:  "default",
113 | 			want:    "http://localhost:8080?catalog=hive&schema=default",
114 | 			wantErr: false,
115 | 		},
116 | 	}
117 | 
118 | 	for _, tt := range tests {
119 | 		t.Run(tt.name, func(t *testing.T) {
120 | 			got, err := buildTrinoDSN(tt.host, tt.port, tt.user, tt.password, tt.catalog, tt.schema, tt.queryTimeout, tt.accessToken, tt.kerberosEnabled, tt.sslEnabled)
121 | 			if (err != nil) != tt.wantErr {
122 | 				t.Errorf("buildTrinoDSN() error = %v, wantErr %v", err, tt.wantErr)
123 | 				return
124 | 			}
125 | 			if diff := cmp.Diff(tt.want, got); diff != "" {
126 | 				t.Errorf("buildTrinoDSN() mismatch (-want +got):\n%s", diff)
127 | 			}
128 | 		})
129 | 	}
130 | }
131 | 
132 | func TestParseFromYamlTrino(t *testing.T) {
133 | 	tcs := []struct {
134 | 		desc string
135 | 		in   string
136 | 		want server.SourceConfigs
137 | 	}{
138 | 		{
139 | 			desc: "basic example",
140 | 			in: `
141 | 			sources:
142 | 				my-trino-instance:
143 | 					kind: trino
144 | 					host: localhost
145 | 					port: "8080"
146 | 					user: testuser
147 | 					catalog: hive
148 | 					schema: default
149 | 			`,
150 | 			want: server.SourceConfigs{
151 | 				"my-trino-instance": Config{
152 | 					Name:    "my-trino-instance",
153 | 					Kind:    SourceKind,
154 | 					Host:    "localhost",
155 | 					Port:    "8080",
156 | 					User:    "testuser",
157 | 					Catalog: "hive",
158 | 					Schema:  "default",
159 | 				},
160 | 			},
161 | 		},
162 | 		{
163 | 			desc: "example with optional fields",
164 | 			in: `
165 | 			sources:
166 | 				my-trino-instance:
167 | 					kind: trino
168 | 					host: localhost
169 | 					port: "8443"
170 | 					user: testuser
171 | 					password: testpass
172 | 					catalog: hive
173 | 					schema: default
174 | 					queryTimeout: "30m"
175 | 					accessToken: "jwt-token-here"
176 | 					kerberosEnabled: true
177 | 					sslEnabled: true
178 | 			`,
179 | 			want: server.SourceConfigs{
180 | 				"my-trino-instance": Config{
181 | 					Name:            "my-trino-instance",
182 | 					Kind:            SourceKind,
183 | 					Host:            "localhost",
184 | 					Port:            "8443",
185 | 					User:            "testuser",
186 | 					Password:        "testpass",
187 | 					Catalog:         "hive",
188 | 					Schema:          "default",
189 | 					QueryTimeout:    "30m",
190 | 					AccessToken:     "jwt-token-here",
191 | 					KerberosEnabled: true,
192 | 					SSLEnabled:      true,
193 | 				},
194 | 			},
195 | 		},
196 | 		{
197 | 			desc: "anonymous access without user",
198 | 			in: `
199 | 			sources:
200 | 				my-trino-anonymous:
201 | 					kind: trino
202 | 					host: localhost
203 | 					port: "8080"
204 | 					catalog: hive
205 | 					schema: default
206 | 			`,
207 | 			want: server.SourceConfigs{
208 | 				"my-trino-anonymous": Config{
209 | 					Name:    "my-trino-anonymous",
210 | 					Kind:    SourceKind,
211 | 					Host:    "localhost",
212 | 					Port:    "8080",
213 | 					Catalog: "hive",
214 | 					Schema:  "default",
215 | 				},
216 | 			},
217 | 		},
218 | 	}
219 | 	for _, tc := range tcs {
220 | 		t.Run(tc.desc, func(t *testing.T) {
221 | 			got := struct {
222 | 				Sources server.SourceConfigs `yaml:"sources"`
223 | 			}{}
224 | 			// Parse contents
225 | 			err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
226 | 			if err != nil {
227 | 				t.Fatalf("unable to unmarshal: %s", err)
228 | 			}
229 | 			if !cmp.Equal(tc.want, got.Sources) {
230 | 				t.Fatalf("incorrect parse: want %v, got %v", tc.want, got.Sources)
231 | 			}
232 | 		})
233 | 	}
234 | }
235 | 
```

--------------------------------------------------------------------------------
/internal/tools/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 | 
 20 | 	yaml "github.com/goccy/go-yaml"
 21 | 	"github.com/googleapis/genai-toolbox/internal/sources"
 22 | 	valkeysrc "github.com/googleapis/genai-toolbox/internal/sources/valkey"
 23 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 24 | 	"github.com/valkey-io/valkey-go"
 25 | )
 26 | 
 27 | const kind string = "valkey"
 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 compatibleSource interface {
 44 | 	ValkeyClient() valkey.Client
 45 | }
 46 | 
 47 | // validate compatible sources are still compatible
 48 | var _ compatibleSource = &valkeysrc.Source{}
 49 | 
 50 | var compatibleSources = [...]string{valkeysrc.SourceKind, valkeysrc.SourceKind}
 51 | 
 52 | type Config struct {
 53 | 	Name         string           `yaml:"name" validate:"required"`
 54 | 	Kind         string           `yaml:"kind" validate:"required"`
 55 | 	Source       string           `yaml:"source" validate:"required"`
 56 | 	Description  string           `yaml:"description" validate:"required"`
 57 | 	Commands     [][]string       `yaml:"commands" validate:"required"`
 58 | 	AuthRequired []string         `yaml:"authRequired"`
 59 | 	Parameters   tools.Parameters `yaml:"parameters"`
 60 | }
 61 | 
 62 | // validate interface
 63 | var _ tools.ToolConfig = Config{}
 64 | 
 65 | func (cfg Config) ToolConfigKind() string {
 66 | 	return kind
 67 | }
 68 | 
 69 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
 70 | 	// verify source exists
 71 | 	rawS, ok := srcs[cfg.Source]
 72 | 	if !ok {
 73 | 		return nil, fmt.Errorf("no source named %q configured", cfg.Source)
 74 | 	}
 75 | 
 76 | 	// verify the source is compatible
 77 | 	s, ok := rawS.(compatibleSource)
 78 | 	if !ok {
 79 | 		return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
 80 | 	}
 81 | 
 82 | 	mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, cfg.Parameters)
 83 | 
 84 | 	// finish tool setup
 85 | 	t := Tool{
 86 | 		Name:         cfg.Name,
 87 | 		Kind:         kind,
 88 | 		Parameters:   cfg.Parameters,
 89 | 		Commands:     cfg.Commands,
 90 | 		AuthRequired: cfg.AuthRequired,
 91 | 		Client:       s.ValkeyClient(),
 92 | 		manifest:     tools.Manifest{Description: cfg.Description, Parameters: cfg.Parameters.Manifest(), AuthRequired: cfg.AuthRequired},
 93 | 		mcpManifest:  mcpManifest,
 94 | 	}
 95 | 	return t, nil
 96 | }
 97 | 
 98 | // validate interface
 99 | var _ tools.Tool = Tool{}
100 | 
101 | type Tool struct {
102 | 	Name         string           `yaml:"name"`
103 | 	Kind         string           `yaml:"kind"`
104 | 	AuthRequired []string         `yaml:"authRequired"`
105 | 	Parameters   tools.Parameters `yaml:"parameters"`
106 | 
107 | 	Client      valkey.Client
108 | 	Commands    [][]string
109 | 	manifest    tools.Manifest
110 | 	mcpManifest tools.McpManifest
111 | }
112 | 
113 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
114 | 	// Replace parameters
115 | 	commands, err := replaceCommandsParams(t.Commands, t.Parameters, params)
116 | 	if err != nil {
117 | 		return nil, fmt.Errorf("error replacing commands' parameters: %s", err)
118 | 	}
119 | 
120 | 	// Build commands
121 | 	builtCmds := make(valkey.Commands, len(commands))
122 | 
123 | 	for i, cmd := range commands {
124 | 		builtCmds[i] = t.Client.B().Arbitrary(cmd...).Build()
125 | 	}
126 | 
127 | 	if len(builtCmds) == 0 {
128 | 		return nil, fmt.Errorf("no valid commands were built to execute")
129 | 	}
130 | 
131 | 	// Execute commands
132 | 	responses := t.Client.DoMulti(ctx, builtCmds...)
133 | 
134 | 	// Parse responses
135 | 	out := make([]any, len(t.Commands))
136 | 	for i, resp := range responses {
137 | 		if err := resp.Error(); err != nil {
138 | 			// Add error from each command to `errSum`
139 | 			out[i] = fmt.Sprintf("error from executing command at index %d: %s", i, err)
140 | 			continue
141 | 		}
142 | 		val, err := resp.ToAny()
143 | 		if err != nil {
144 | 			out[i] = fmt.Sprintf("error parsing response: %s", err)
145 | 			continue
146 | 		}
147 | 		out[i] = val
148 | 	}
149 | 
150 | 	return out, nil
151 | }
152 | 
153 | // replaceCommandsParams is a helper function to replace parameters in the commands
154 | func replaceCommandsParams(commands [][]string, params tools.Parameters, paramValues tools.ParamValues) ([][]string, error) {
155 | 	paramMap := paramValues.AsMapWithDollarPrefix()
156 | 	typeMap := make(map[string]string, len(params))
157 | 	for _, p := range params {
158 | 		placeholder := "$" + p.GetName()
159 | 		typeMap[placeholder] = p.GetType()
160 | 	}
161 | 
162 | 	newCommands := make([][]string, len(commands))
163 | 	for i, cmd := range commands {
164 | 		newCmd := make([]string, 0)
165 | 		for _, part := range cmd {
166 | 			v, ok := paramMap[part]
167 | 			if !ok {
168 | 				// Command part is not a Parameter placeholder
169 | 				newCmd = append(newCmd, part)
170 | 				continue
171 | 			}
172 | 			if typeMap[part] == "array" {
173 | 				for _, item := range v.([]any) {
174 | 					// Nested arrays will only be expanded once
175 | 					// e.g., [A, [B, C]]  --> ["A", "[B C]"]
176 | 					newCmd = append(newCmd, fmt.Sprintf("%s", item))
177 | 				}
178 | 				continue
179 | 			}
180 | 			newCmd = append(newCmd, fmt.Sprintf("%s", v))
181 | 		}
182 | 		newCommands[i] = newCmd
183 | 	}
184 | 	return newCommands, nil
185 | }
186 | 
187 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
188 | 	return tools.ParseParams(t.Parameters, data, claims)
189 | }
190 | 
191 | func (t Tool) Manifest() tools.Manifest {
192 | 	return t.manifest
193 | }
194 | 
195 | func (t Tool) McpManifest() tools.McpManifest {
196 | 	return t.mcpManifest
197 | }
198 | 
199 | func (t Tool) Authorized(verifiedAuthServices []string) bool {
200 | 	return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
201 | }
202 | 
203 | func (t Tool) RequiresClientAuthorization() bool {
204 | 	return false
205 | }
206 | 
```

--------------------------------------------------------------------------------
/internal/tools/trino/trinosql/trinosql.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 trinosql
 16 | 
 17 | import (
 18 | 	"context"
 19 | 	"database/sql"
 20 | 	"fmt"
 21 | 
 22 | 	yaml "github.com/goccy/go-yaml"
 23 | 	"github.com/googleapis/genai-toolbox/internal/sources"
 24 | 	"github.com/googleapis/genai-toolbox/internal/sources/trino"
 25 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 26 | )
 27 | 
 28 | const kind string = "trino-sql"
 29 | 
 30 | func init() {
 31 | 	if !tools.Register(kind, newConfig) {
 32 | 		panic(fmt.Sprintf("tool kind %q already registered", kind))
 33 | 	}
 34 | }
 35 | 
 36 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
 37 | 	actual := Config{Name: name}
 38 | 	if err := decoder.DecodeContext(ctx, &actual); err != nil {
 39 | 		return nil, err
 40 | 	}
 41 | 	return actual, nil
 42 | }
 43 | 
 44 | type compatibleSource interface {
 45 | 	TrinoDB() *sql.DB
 46 | }
 47 | 
 48 | // validate compatible sources are still compatible
 49 | var _ compatibleSource = &trino.Source{}
 50 | 
 51 | var compatibleSources = [...]string{trino.SourceKind}
 52 | 
 53 | type Config struct {
 54 | 	Name               string           `yaml:"name" validate:"required"`
 55 | 	Kind               string           `yaml:"kind" validate:"required"`
 56 | 	Source             string           `yaml:"source" validate:"required"`
 57 | 	Description        string           `yaml:"description" validate:"required"`
 58 | 	Statement          string           `yaml:"statement" validate:"required"`
 59 | 	AuthRequired       []string         `yaml:"authRequired"`
 60 | 	Parameters         tools.Parameters `yaml:"parameters"`
 61 | 	TemplateParameters tools.Parameters `yaml:"templateParameters"`
 62 | }
 63 | 
 64 | // validate interface
 65 | var _ tools.ToolConfig = Config{}
 66 | 
 67 | func (cfg Config) ToolConfigKind() string {
 68 | 	return kind
 69 | }
 70 | 
 71 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
 72 | 	// verify source exists
 73 | 	rawS, ok := srcs[cfg.Source]
 74 | 	if !ok {
 75 | 		return nil, fmt.Errorf("no source named %q configured", cfg.Source)
 76 | 	}
 77 | 
 78 | 	// verify the source is compatible
 79 | 	s, ok := rawS.(compatibleSource)
 80 | 	if !ok {
 81 | 		return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
 82 | 	}
 83 | 
 84 | 	allParameters, paramManifest, err := tools.ProcessParameters(cfg.TemplateParameters, cfg.Parameters)
 85 | 	if err != nil {
 86 | 		return nil, fmt.Errorf("unable to process parameters: %w", err)
 87 | 	}
 88 | 
 89 | 	mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, allParameters)
 90 | 
 91 | 	// finish tool setup
 92 | 	t := Tool{
 93 | 		Name:               cfg.Name,
 94 | 		Kind:               kind,
 95 | 		Parameters:         cfg.Parameters,
 96 | 		TemplateParameters: cfg.TemplateParameters,
 97 | 		AllParams:          allParameters,
 98 | 		Statement:          cfg.Statement,
 99 | 		AuthRequired:       cfg.AuthRequired,
100 | 		Db:                 s.TrinoDB(),
101 | 		manifest:           tools.Manifest{Description: cfg.Description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired},
102 | 		mcpManifest:        mcpManifest,
103 | 	}
104 | 	return t, nil
105 | }
106 | 
107 | // validate interface
108 | var _ tools.Tool = Tool{}
109 | 
110 | type Tool struct {
111 | 	Name               string           `yaml:"name"`
112 | 	Kind               string           `yaml:"kind"`
113 | 	AuthRequired       []string         `yaml:"authRequired"`
114 | 	Parameters         tools.Parameters `yaml:"parameters"`
115 | 	TemplateParameters tools.Parameters `yaml:"templateParameters"`
116 | 	AllParams          tools.Parameters `yaml:"allParams"`
117 | 
118 | 	Statement   string
119 | 	Db          *sql.DB
120 | 	manifest    tools.Manifest
121 | 	mcpManifest tools.McpManifest
122 | }
123 | 
124 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
125 | 	paramsMap := params.AsMap()
126 | 	newStatement, err := tools.ResolveTemplateParams(t.TemplateParameters, t.Statement, paramsMap)
127 | 	if err != nil {
128 | 		return nil, fmt.Errorf("unable to extract template params %w", err)
129 | 	}
130 | 	newParams, err := tools.GetParams(t.Parameters, paramsMap)
131 | 	if err != nil {
132 | 		return nil, fmt.Errorf("unable to extract standard params %w", err)
133 | 	}
134 | 	sliceParams := newParams.AsSlice()
135 | 	results, err := t.Db.QueryContext(ctx, newStatement, sliceParams...)
136 | 	if err != nil {
137 | 		return nil, fmt.Errorf("unable to execute query: %w", err)
138 | 	}
139 | 	defer results.Close()
140 | 
141 | 	cols, err := results.Columns()
142 | 	if err != nil {
143 | 		return nil, fmt.Errorf("unable to retrieve column names: %w", err)
144 | 	}
145 | 
146 | 	// create an array of values for each column, which can be re-used to scan each row
147 | 	rawValues := make([]any, len(cols))
148 | 	values := make([]any, len(cols))
149 | 	for i := range rawValues {
150 | 		values[i] = &rawValues[i]
151 | 	}
152 | 
153 | 	var out []any
154 | 	for results.Next() {
155 | 		err := results.Scan(values...)
156 | 		if err != nil {
157 | 			return nil, fmt.Errorf("unable to parse row: %w", err)
158 | 		}
159 | 		vMap := make(map[string]any)
160 | 		for i, name := range cols {
161 | 			val := rawValues[i]
162 | 			if val == nil {
163 | 				vMap[name] = nil
164 | 				continue
165 | 			}
166 | 
167 | 			// Convert byte arrays to strings for text fields
168 | 			if b, ok := val.([]byte); ok {
169 | 				vMap[name] = string(b)
170 | 			} else {
171 | 				vMap[name] = val
172 | 			}
173 | 		}
174 | 		out = append(out, vMap)
175 | 	}
176 | 
177 | 	if err := results.Err(); err != nil {
178 | 		return nil, fmt.Errorf("errors encountered during row iteration: %w", err)
179 | 	}
180 | 
181 | 	return out, nil
182 | }
183 | 
184 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
185 | 	return tools.ParseParams(t.AllParams, data, claims)
186 | }
187 | 
188 | func (t Tool) Manifest() tools.Manifest {
189 | 	return t.manifest
190 | }
191 | 
192 | func (t Tool) McpManifest() tools.McpManifest {
193 | 	return t.mcpManifest
194 | }
195 | 
196 | func (t Tool) Authorized(verifiedAuthServices []string) bool {
197 | 	return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
198 | }
199 | 
200 | func (t Tool) RequiresClientAuthorization() bool {
201 | 	return false
202 | }
203 | 
```

--------------------------------------------------------------------------------
/internal/tools/looker/lookeradddashboardelement/lookeradddashboardelement.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 lookeradddashboardelement
 15 | 
 16 | import (
 17 | 	"context"
 18 | 	"fmt"
 19 | 
 20 | 	yaml "github.com/goccy/go-yaml"
 21 | 	"github.com/googleapis/genai-toolbox/internal/sources"
 22 | 	lookersrc "github.com/googleapis/genai-toolbox/internal/sources/looker"
 23 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 24 | 	"github.com/googleapis/genai-toolbox/internal/tools/looker/lookercommon"
 25 | 	"github.com/googleapis/genai-toolbox/internal/util"
 26 | 
 27 | 	"github.com/looker-open-source/sdk-codegen/go/rtl"
 28 | 	v4 "github.com/looker-open-source/sdk-codegen/go/sdk/v4"
 29 | )
 30 | 
 31 | const kind string = "looker-add-dashboard-element"
 32 | 
 33 | func init() {
 34 | 	if !tools.Register(kind, newConfig) {
 35 | 		panic(fmt.Sprintf("tool kind %q already registered", kind))
 36 | 	}
 37 | }
 38 | 
 39 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
 40 | 	actual := Config{Name: name}
 41 | 	if err := decoder.DecodeContext(ctx, &actual); err != nil {
 42 | 		return nil, err
 43 | 	}
 44 | 	return actual, nil
 45 | }
 46 | 
 47 | type Config struct {
 48 | 	Name         string   `yaml:"name" validate:"required"`
 49 | 	Kind         string   `yaml:"kind" validate:"required"`
 50 | 	Source       string   `yaml:"source" validate:"required"`
 51 | 	Description  string   `yaml:"description" validate:"required"`
 52 | 	AuthRequired []string `yaml:"authRequired"`
 53 | }
 54 | 
 55 | // validate interface
 56 | var _ tools.ToolConfig = Config{}
 57 | 
 58 | func (cfg Config) ToolConfigKind() string {
 59 | 	return kind
 60 | }
 61 | 
 62 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
 63 | 	// verify source exists
 64 | 	rawS, ok := srcs[cfg.Source]
 65 | 	if !ok {
 66 | 		return nil, fmt.Errorf("no source named %q configured", cfg.Source)
 67 | 	}
 68 | 
 69 | 	// verify the source is compatible
 70 | 	s, ok := rawS.(*lookersrc.Source)
 71 | 	if !ok {
 72 | 		return nil, fmt.Errorf("invalid source for %q tool: source kind must be `looker`", kind)
 73 | 	}
 74 | 
 75 | 	parameters := lookercommon.GetQueryParameters()
 76 | 
 77 | 	dashIdParameter := tools.NewStringParameter("dashboard_id", "The id of the dashboard where this tile will exist")
 78 | 	parameters = append(parameters, dashIdParameter)
 79 | 	titleParameter := tools.NewStringParameterWithDefault("title", "", "The title of the Dashboard Element")
 80 | 	parameters = append(parameters, titleParameter)
 81 | 	vizParameter := tools.NewMapParameterWithDefault("vis_config",
 82 | 		map[string]any{},
 83 | 		"The visualization config for the query",
 84 | 		"",
 85 | 	)
 86 | 	parameters = append(parameters, vizParameter)
 87 | 
 88 | 	mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
 89 | 
 90 | 	// finish tool setup
 91 | 	return Tool{
 92 | 		Name:           cfg.Name,
 93 | 		Kind:           kind,
 94 | 		Parameters:     parameters,
 95 | 		AuthRequired:   cfg.AuthRequired,
 96 | 		UseClientOAuth: s.UseClientOAuth,
 97 | 		Client:         s.Client,
 98 | 		ApiSettings:    s.ApiSettings,
 99 | 		manifest: tools.Manifest{
100 | 			Description:  cfg.Description,
101 | 			Parameters:   parameters.Manifest(),
102 | 			AuthRequired: cfg.AuthRequired,
103 | 		},
104 | 		mcpManifest: mcpManifest,
105 | 	}, nil
106 | }
107 | 
108 | // validate interface
109 | var _ tools.Tool = Tool{}
110 | 
111 | type Tool struct {
112 | 	Name           string `yaml:"name"`
113 | 	Kind           string `yaml:"kind"`
114 | 	UseClientOAuth bool
115 | 	Client         *v4.LookerSDK
116 | 	ApiSettings    *rtl.ApiSettings
117 | 	AuthRequired   []string         `yaml:"authRequired"`
118 | 	Parameters     tools.Parameters `yaml:"parameters"`
119 | 	manifest       tools.Manifest
120 | 	mcpManifest    tools.McpManifest
121 | }
122 | 
123 | var (
124 | 	dataType string = "data"
125 | 	visType  string = "vis"
126 | )
127 | 
128 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
129 | 	logger, err := util.LoggerFromContext(ctx)
130 | 	if err != nil {
131 | 		return nil, fmt.Errorf("unable to get logger from ctx: %s", err)
132 | 	}
133 | 	logger.DebugContext(ctx, "params = ", params)
134 | 	wq, err := lookercommon.ProcessQueryArgs(ctx, params)
135 | 	if err != nil {
136 | 		return nil, fmt.Errorf("error building query request: %w", err)
137 | 	}
138 | 
139 | 	paramsMap := params.AsMap()
140 | 	dashboard_id := paramsMap["dashboard_id"].(string)
141 | 	title := paramsMap["title"].(string)
142 | 
143 | 	visConfig := paramsMap["vis_config"].(map[string]any)
144 | 	wq.VisConfig = &visConfig
145 | 
146 | 	qrespFields := "id"
147 | 
148 | 	sdk, err := lookercommon.GetLookerSDK(t.UseClientOAuth, t.ApiSettings, t.Client, accessToken)
149 | 	if err != nil {
150 | 		return nil, fmt.Errorf("error getting sdk: %w", err)
151 | 	}
152 | 
153 | 	qresp, err := sdk.CreateQuery(*wq, qrespFields, t.ApiSettings)
154 | 	if err != nil {
155 | 		return nil, fmt.Errorf("error making create query request: %w", err)
156 | 	}
157 | 
158 | 	wde := v4.WriteDashboardElement{
159 | 		DashboardId: &dashboard_id,
160 | 		Title:       &title,
161 | 		QueryId:     qresp.Id,
162 | 	}
163 | 	switch len(visConfig) {
164 | 	case 0:
165 | 		wde.Type = &dataType
166 | 	default:
167 | 		wde.Type = &visType
168 | 	}
169 | 
170 | 	fields := ""
171 | 
172 | 	req := v4.RequestCreateDashboardElement{
173 | 		Body:   wde,
174 | 		Fields: &fields,
175 | 	}
176 | 
177 | 	resp, err := sdk.CreateDashboardElement(req, t.ApiSettings)
178 | 	if err != nil {
179 | 		return nil, fmt.Errorf("error making create dashboard element request: %w", err)
180 | 	}
181 | 	logger.DebugContext(ctx, "resp = %v", resp)
182 | 
183 | 	data := make(map[string]any)
184 | 
185 | 	data["result"] = fmt.Sprintf("Dashboard element added to dashboard %s", dashboard_id)
186 | 
187 | 	return data, nil
188 | }
189 | 
190 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
191 | 	return tools.ParseParams(t.Parameters, data, claims)
192 | }
193 | 
194 | func (t Tool) Manifest() tools.Manifest {
195 | 	return t.manifest
196 | }
197 | 
198 | func (t Tool) McpManifest() tools.McpManifest {
199 | 	return t.mcpManifest
200 | }
201 | 
202 | func (t Tool) Authorized(verifiedAuthServices []string) bool {
203 | 	return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
204 | }
205 | 
206 | func (t Tool) RequiresClientAuthorization() bool {
207 | 	return t.UseClientOAuth
208 | }
209 | 
```

--------------------------------------------------------------------------------
/internal/tools/mongodb/mongodbaggregate/mongodbaggregate.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 mongodbaggregate
 15 | 
 16 | import (
 17 | 	"context"
 18 | 	"encoding/json"
 19 | 	"fmt"
 20 | 	"slices"
 21 | 
 22 | 	"github.com/goccy/go-yaml"
 23 | 	mongosrc "github.com/googleapis/genai-toolbox/internal/sources/mongodb"
 24 | 	"go.mongodb.org/mongo-driver/bson"
 25 | 	"go.mongodb.org/mongo-driver/mongo"
 26 | 
 27 | 	"github.com/googleapis/genai-toolbox/internal/sources"
 28 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 29 | )
 30 | 
 31 | const kind string = "mongodb-aggregate"
 32 | 
 33 | func init() {
 34 | 	if !tools.Register(kind, newConfig) {
 35 | 		panic(fmt.Sprintf("tool kind %q already registered", kind))
 36 | 	}
 37 | }
 38 | 
 39 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
 40 | 	actual := Config{Name: name}
 41 | 	if err := decoder.DecodeContext(ctx, &actual); err != nil {
 42 | 		return nil, err
 43 | 	}
 44 | 	return actual, nil
 45 | }
 46 | 
 47 | type Config struct {
 48 | 	Name            string           `yaml:"name" validate:"required"`
 49 | 	Kind            string           `yaml:"kind" validate:"required"`
 50 | 	Source          string           `yaml:"source" validate:"required"`
 51 | 	AuthRequired    []string         `yaml:"authRequired" validate:"required"`
 52 | 	Description     string           `yaml:"description" validate:"required"`
 53 | 	Database        string           `yaml:"database" validate:"required"`
 54 | 	Collection      string           `yaml:"collection" validate:"required"`
 55 | 	PipelinePayload string           `yaml:"pipelinePayload" validate:"required"`
 56 | 	PipelineParams  tools.Parameters `yaml:"pipelineParams" validate:"required"`
 57 | 	Canonical       bool             `yaml:"canonical"`
 58 | 	ReadOnly        bool             `yaml:"readOnly"`
 59 | }
 60 | 
 61 | // validate interface
 62 | var _ tools.ToolConfig = Config{}
 63 | 
 64 | func (cfg Config) ToolConfigKind() string {
 65 | 	return kind
 66 | }
 67 | 
 68 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
 69 | 	// verify source exists
 70 | 	rawS, ok := srcs[cfg.Source]
 71 | 	if !ok {
 72 | 		return nil, fmt.Errorf("no source named %q configured", cfg.Source)
 73 | 	}
 74 | 
 75 | 	// verify the source is compatible
 76 | 	s, ok := rawS.(*mongosrc.Source)
 77 | 	if !ok {
 78 | 		return nil, fmt.Errorf("invalid source for %q tool: source kind must be `mongodb`", kind)
 79 | 	}
 80 | 
 81 | 	// Create a slice for all parameters
 82 | 	allParameters := slices.Concat(cfg.PipelineParams)
 83 | 
 84 | 	// Create Toolbox manifest
 85 | 	paramManifest := allParameters.Manifest()
 86 | 
 87 | 	if paramManifest == nil {
 88 | 		paramManifest = make([]tools.ParameterManifest, 0)
 89 | 	}
 90 | 
 91 | 	// Create MCP manifest
 92 | 	mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, allParameters)
 93 | 
 94 | 	// finish tool setup
 95 | 	return Tool{
 96 | 		Name:            cfg.Name,
 97 | 		Kind:            kind,
 98 | 		AuthRequired:    cfg.AuthRequired,
 99 | 		Collection:      cfg.Collection,
100 | 		PipelinePayload: cfg.PipelinePayload,
101 | 		PipelineParams:  cfg.PipelineParams,
102 | 		Canonical:       cfg.Canonical,
103 | 		ReadOnly:        cfg.ReadOnly,
104 | 		AllParams:       allParameters,
105 | 		database:        s.Client.Database(cfg.Database),
106 | 		manifest:        tools.Manifest{Description: cfg.Description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired},
107 | 		mcpManifest:     mcpManifest,
108 | 	}, nil
109 | }
110 | 
111 | // validate interface
112 | var _ tools.Tool = Tool{}
113 | 
114 | type Tool struct {
115 | 	Name            string           `yaml:"name"`
116 | 	Kind            string           `yaml:"kind"`
117 | 	Description     string           `yaml:"description"`
118 | 	AuthRequired    []string         `yaml:"authRequired"`
119 | 	Collection      string           `yaml:"collection"`
120 | 	PipelinePayload string           `yaml:"pipelinePayload"`
121 | 	PipelineParams  tools.Parameters `yaml:"pipelineParams"`
122 | 	Canonical       bool             `yaml:"canonical"`
123 | 	ReadOnly        bool             `yaml:"readOnly"`
124 | 	AllParams       tools.Parameters `yaml:"allParams"`
125 | 
126 | 	database    *mongo.Database
127 | 	manifest    tools.Manifest
128 | 	mcpManifest tools.McpManifest
129 | }
130 | 
131 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
132 | 	paramsMap := params.AsMap()
133 | 
134 | 	pipelineString, err := tools.PopulateTemplateWithJSON("MongoDBAggregatePipeline", t.PipelinePayload, paramsMap)
135 | 	if err != nil {
136 | 		return nil, fmt.Errorf("error populating pipeline: %s", err)
137 | 	}
138 | 
139 | 	var pipeline = []bson.M{}
140 | 	err = bson.UnmarshalExtJSON([]byte(pipelineString), t.Canonical, &pipeline)
141 | 	if err != nil {
142 | 		return nil, err
143 | 	}
144 | 
145 | 	if t.ReadOnly {
146 | 		//fail if we do a merge or an out
147 | 		for _, stage := range pipeline {
148 | 			for key := range stage {
149 | 				if key == "$merge" || key == "$out" {
150 | 					return nil, fmt.Errorf("this is not a read-only pipeline: %+v", stage)
151 | 				}
152 | 			}
153 | 		}
154 | 	}
155 | 
156 | 	cur, err := t.database.Collection(t.Collection).Aggregate(ctx, pipeline)
157 | 	if err != nil {
158 | 		return nil, err
159 | 	}
160 | 	defer cur.Close(ctx)
161 | 
162 | 	var data = []any{}
163 | 	err = cur.All(ctx, &data)
164 | 	if err != nil {
165 | 		return nil, err
166 | 	}
167 | 
168 | 	if len(data) == 0 {
169 | 		return []any{}, nil
170 | 	}
171 | 
172 | 	var final []any
173 | 	for _, item := range data {
174 | 		tmp, _ := bson.MarshalExtJSON(item, false, false)
175 | 		var tmp2 any
176 | 		err = json.Unmarshal(tmp, &tmp2)
177 | 		if err != nil {
178 | 			return nil, err
179 | 		}
180 | 		final = append(final, tmp2)
181 | 	}
182 | 
183 | 	return final, err
184 | }
185 | 
186 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
187 | 	return tools.ParseParams(t.AllParams, data, claims)
188 | }
189 | 
190 | func (t Tool) Manifest() tools.Manifest {
191 | 	return t.manifest
192 | }
193 | 
194 | func (t Tool) McpManifest() tools.McpManifest {
195 | 	return t.mcpManifest
196 | }
197 | 
198 | func (t Tool) Authorized(verifiedAuthServices []string) bool {
199 | 	return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
200 | }
201 | 
202 | func (t Tool) RequiresClientAuthorization() bool {
203 | 	return false
204 | }
205 | 
```

--------------------------------------------------------------------------------
/internal/tools/http/http_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 http_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 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 26 | 	http "github.com/googleapis/genai-toolbox/internal/tools/http"
 27 | )
 28 | 
 29 | func TestParseFromYamlHTTP(t *testing.T) {
 30 | 	ctx, err := testutils.ContextWithNewLogger()
 31 | 	if err != nil {
 32 | 		t.Fatalf("unexpected error: %s", err)
 33 | 	}
 34 | 	tcs := []struct {
 35 | 		desc string
 36 | 		in   string
 37 | 		want server.ToolConfigs
 38 | 	}{
 39 | 		{
 40 | 			desc: "basic example",
 41 | 			in: `
 42 | 			tools:
 43 | 				example_tool:
 44 | 					kind: http
 45 | 					source: my-instance
 46 | 					method: GET
 47 | 					description: some description
 48 | 					path: search
 49 | 				`,
 50 | 			want: server.ToolConfigs{
 51 | 				"example_tool": http.Config{
 52 | 					Name:         "example_tool",
 53 | 					Kind:         "http",
 54 | 					Source:       "my-instance",
 55 | 					Method:       "GET",
 56 | 					Path:         "search",
 57 | 					Description:  "some description",
 58 | 					AuthRequired: []string{},
 59 | 				},
 60 | 			},
 61 | 		},
 62 | 		{
 63 | 			desc: "advanced example",
 64 | 			in: `
 65 | 			tools:
 66 | 				example_tool:
 67 | 					kind: http
 68 | 					source: my-instance
 69 | 					method: GET
 70 | 					path: "{{.pathParam}}?name=alice&pet=cat"
 71 | 					description: some description
 72 | 					authRequired:
 73 | 						- my-google-auth-service
 74 | 						- other-auth-service
 75 | 					queryParams:
 76 | 						- name: country
 77 | 						  type: string
 78 | 						  description: some description
 79 | 						  authServices:
 80 | 							- name: my-google-auth-service
 81 | 							  field: user_id
 82 | 							- name: other-auth-service
 83 | 							  field: user_id
 84 | 					pathParams:
 85 | 					    - name: pathParam
 86 | 					      type: string
 87 | 					      description: path param
 88 | 					requestBody: |
 89 | 							{
 90 | 								"age": {{.age}},
 91 | 								"city": "{{.city}}",
 92 | 								"food": {{.food}}
 93 | 							}
 94 | 					bodyParams:
 95 | 						- name: age
 96 | 						  type: integer
 97 | 						  description: age num
 98 | 						- name: city
 99 | 						  type: string
100 | 						  description: city string
101 | 					headers:
102 | 						Authorization: API_KEY
103 | 						Content-Type: application/json
104 | 					headerParams:
105 | 						- name: Language
106 | 						  type: string
107 | 						  description: language string
108 | 			`,
109 | 			want: server.ToolConfigs{
110 | 				"example_tool": http.Config{
111 | 					Name:         "example_tool",
112 | 					Kind:         "http",
113 | 					Source:       "my-instance",
114 | 					Method:       "GET",
115 | 					Path:         "{{.pathParam}}?name=alice&pet=cat",
116 | 					Description:  "some description",
117 | 					AuthRequired: []string{"my-google-auth-service", "other-auth-service"},
118 | 					QueryParams: []tools.Parameter{
119 | 						tools.NewStringParameterWithAuth("country", "some description",
120 | 							[]tools.ParamAuthService{{Name: "my-google-auth-service", Field: "user_id"},
121 | 								{Name: "other-auth-service", Field: "user_id"}}),
122 | 					},
123 | 					PathParams: tools.Parameters{
124 | 						&tools.StringParameter{
125 | 							CommonParameter: tools.CommonParameter{Name: "pathParam", Type: "string", Desc: "path param"},
126 | 						},
127 | 					},
128 | 					RequestBody: `{
129 |   "age": {{.age}},
130 |   "city": "{{.city}}",
131 |   "food": {{.food}}
132 | }
133 | `,
134 | 					BodyParams:   []tools.Parameter{tools.NewIntParameter("age", "age num"), tools.NewStringParameter("city", "city string")},
135 | 					Headers:      map[string]string{"Authorization": "API_KEY", "Content-Type": "application/json"},
136 | 					HeaderParams: []tools.Parameter{tools.NewStringParameter("Language", "language string")},
137 | 				},
138 | 			},
139 | 		},
140 | 	}
141 | 	for _, tc := range tcs {
142 | 		t.Run(tc.desc, func(t *testing.T) {
143 | 			got := struct {
144 | 				Tools server.ToolConfigs `yaml:"tools"`
145 | 			}{}
146 | 			// Parse contents
147 | 			err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
148 | 			if err != nil {
149 | 				t.Fatalf("unable to unmarshal: %s", err)
150 | 			}
151 | 			if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
152 | 				t.Fatalf("incorrect parse: diff %v", diff)
153 | 			}
154 | 		})
155 | 	}
156 | 
157 | }
158 | 
159 | func TestFailParseFromYamlHTTP(t *testing.T) {
160 | 	ctx, err := testutils.ContextWithNewLogger()
161 | 	if err != nil {
162 | 		t.Fatalf("unexpected error: %s", err)
163 | 	}
164 | 	tcs := []struct {
165 | 		desc string
166 | 		in   string
167 | 		err  string
168 | 	}{
169 | 		{
170 | 			desc: "Invalid method",
171 | 			in: `
172 | 			tools:
173 | 				example_tool:
174 | 					kind: http
175 | 					source: my-instance
176 | 					method: GOT
177 | 					path: "search?name=alice&pet=cat"
178 | 					description: some description
179 | 					authRequired:
180 | 						- my-google-auth-service
181 | 						- other-auth-service
182 | 					queryParams:
183 | 						- name: country
184 | 						  type: string
185 | 						  description: some description
186 | 						  authServices:
187 | 							- name: my-google-auth-service
188 | 							  field: user_id
189 | 							- name: other-auth-service
190 | 							  field: user_id
191 | 					requestBody: |
192 | 							{
193 | 								"age": {{.age}},
194 | 								"city": "{{.city}}"
195 | 							}
196 | 					bodyParams:
197 | 						- name: age
198 | 						  type: integer
199 | 						  description: age num
200 | 						- name: city
201 | 						  type: string
202 | 						  description: city string
203 | 					headers:
204 | 						Authorization: API_KEY
205 | 						Content-Type: application/json
206 | 					headerParams:
207 | 						- name: Language
208 | 						  type: string
209 | 						  description: language string
210 | 			`,
211 | 			err: `GOT is not a valid http method`,
212 | 		},
213 | 	}
214 | 	for _, tc := range tcs {
215 | 		t.Run(tc.desc, func(t *testing.T) {
216 | 			got := struct {
217 | 				Tools server.ToolConfigs `yaml:"tools"`
218 | 			}{}
219 | 			// Parse contents
220 | 			err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
221 | 			if err == nil {
222 | 				t.Fatalf("expect parsing to fail")
223 | 			}
224 | 			errStr := err.Error()
225 | 			if !strings.Contains(errStr, tc.err) {
226 | 				t.Fatalf("unexpected error string: got %q, want substring %q", errStr, tc.err)
227 | 			}
228 | 		})
229 | 	}
230 | 
231 | }
232 | 
```

--------------------------------------------------------------------------------
/internal/tools/firestore/firestoregetdocuments/firestoregetdocuments.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 firestoregetdocuments
 16 | 
 17 | import (
 18 | 	"context"
 19 | 	"fmt"
 20 | 
 21 | 	firestoreapi "cloud.google.com/go/firestore"
 22 | 	yaml "github.com/goccy/go-yaml"
 23 | 	"github.com/googleapis/genai-toolbox/internal/sources"
 24 | 	firestoreds "github.com/googleapis/genai-toolbox/internal/sources/firestore"
 25 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 26 | 	"github.com/googleapis/genai-toolbox/internal/tools/firestore/util"
 27 | )
 28 | 
 29 | const kind string = "firestore-get-documents"
 30 | const documentPathsKey string = "documentPaths"
 31 | 
 32 | func init() {
 33 | 	if !tools.Register(kind, newConfig) {
 34 | 		panic(fmt.Sprintf("tool kind %q already registered", kind))
 35 | 	}
 36 | }
 37 | 
 38 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
 39 | 	actual := Config{Name: name}
 40 | 	if err := decoder.DecodeContext(ctx, &actual); err != nil {
 41 | 		return nil, err
 42 | 	}
 43 | 	return actual, nil
 44 | }
 45 | 
 46 | type compatibleSource interface {
 47 | 	FirestoreClient() *firestoreapi.Client
 48 | }
 49 | 
 50 | // validate compatible sources are still compatible
 51 | var _ compatibleSource = &firestoreds.Source{}
 52 | 
 53 | var compatibleSources = [...]string{firestoreds.SourceKind}
 54 | 
 55 | type Config struct {
 56 | 	Name         string   `yaml:"name" validate:"required"`
 57 | 	Kind         string   `yaml:"kind" validate:"required"`
 58 | 	Source       string   `yaml:"source" validate:"required"`
 59 | 	Description  string   `yaml:"description" validate:"required"`
 60 | 	AuthRequired []string `yaml:"authRequired"`
 61 | }
 62 | 
 63 | // validate interface
 64 | var _ tools.ToolConfig = Config{}
 65 | 
 66 | func (cfg Config) ToolConfigKind() string {
 67 | 	return kind
 68 | }
 69 | 
 70 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
 71 | 	// verify source exists
 72 | 	rawS, ok := srcs[cfg.Source]
 73 | 	if !ok {
 74 | 		return nil, fmt.Errorf("no source named %q configured", cfg.Source)
 75 | 	}
 76 | 
 77 | 	// verify the source is compatible
 78 | 	s, ok := rawS.(compatibleSource)
 79 | 	if !ok {
 80 | 		return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
 81 | 	}
 82 | 
 83 | 	documentPathsParameter := tools.NewArrayParameter(documentPathsKey, "Array of relative document paths to retrieve from Firestore (e.g., 'users/userId' or 'users/userId/posts/postId'). Note: These are relative paths, NOT absolute paths like 'projects/{project_id}/databases/{database_id}/documents/...'", tools.NewStringParameter("item", "Relative document path"))
 84 | 	parameters := tools.Parameters{documentPathsParameter}
 85 | 
 86 | 	mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
 87 | 
 88 | 	// finish tool setup
 89 | 	t := Tool{
 90 | 		Name:         cfg.Name,
 91 | 		Kind:         kind,
 92 | 		Parameters:   parameters,
 93 | 		AuthRequired: cfg.AuthRequired,
 94 | 		Client:       s.FirestoreClient(),
 95 | 		manifest:     tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired},
 96 | 		mcpManifest:  mcpManifest,
 97 | 	}
 98 | 	return t, nil
 99 | }
100 | 
101 | // validate interface
102 | var _ tools.Tool = Tool{}
103 | 
104 | type Tool struct {
105 | 	Name         string           `yaml:"name"`
106 | 	Kind         string           `yaml:"kind"`
107 | 	AuthRequired []string         `yaml:"authRequired"`
108 | 	Parameters   tools.Parameters `yaml:"parameters"`
109 | 
110 | 	Client      *firestoreapi.Client
111 | 	manifest    tools.Manifest
112 | 	mcpManifest tools.McpManifest
113 | }
114 | 
115 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
116 | 	mapParams := params.AsMap()
117 | 	documentPathsRaw, ok := mapParams[documentPathsKey].([]any)
118 | 	if !ok {
119 | 		return nil, fmt.Errorf("invalid or missing '%s' parameter; expected an array", documentPathsKey)
120 | 	}
121 | 
122 | 	if len(documentPathsRaw) == 0 {
123 | 		return nil, fmt.Errorf("'%s' parameter cannot be empty", documentPathsKey)
124 | 	}
125 | 
126 | 	// Use ConvertAnySliceToTyped to convert the slice
127 | 	typedSlice, err := tools.ConvertAnySliceToTyped(documentPathsRaw, "string")
128 | 	if err != nil {
129 | 		return nil, fmt.Errorf("failed to convert document paths: %w", err)
130 | 	}
131 | 
132 | 	documentPaths, ok := typedSlice.([]string)
133 | 	if !ok {
134 | 		return nil, fmt.Errorf("unexpected type conversion error for document paths")
135 | 	}
136 | 
137 | 	// Validate each document path
138 | 	for i, path := range documentPaths {
139 | 		if err := util.ValidateDocumentPath(path); err != nil {
140 | 			return nil, fmt.Errorf("invalid document path at index %d: %w", i, err)
141 | 		}
142 | 	}
143 | 
144 | 	// Create document references from paths
145 | 	docRefs := make([]*firestoreapi.DocumentRef, len(documentPaths))
146 | 	for i, path := range documentPaths {
147 | 		docRefs[i] = t.Client.Doc(path)
148 | 	}
149 | 
150 | 	// Get all documents
151 | 	snapshots, err := t.Client.GetAll(ctx, docRefs)
152 | 	if err != nil {
153 | 		return nil, fmt.Errorf("failed to get documents: %w", err)
154 | 	}
155 | 
156 | 	// Convert snapshots to response data
157 | 	results := make([]any, len(snapshots))
158 | 	for i, snapshot := range snapshots {
159 | 		docData := make(map[string]any)
160 | 		docData["path"] = documentPaths[i]
161 | 		docData["exists"] = snapshot.Exists()
162 | 
163 | 		if snapshot.Exists() {
164 | 			docData["data"] = snapshot.Data()
165 | 			docData["createTime"] = snapshot.CreateTime
166 | 			docData["updateTime"] = snapshot.UpdateTime
167 | 			docData["readTime"] = snapshot.ReadTime
168 | 		}
169 | 
170 | 		results[i] = docData
171 | 	}
172 | 
173 | 	return results, nil
174 | }
175 | 
176 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
177 | 	return tools.ParseParams(t.Parameters, data, claims)
178 | }
179 | 
180 | func (t Tool) Manifest() tools.Manifest {
181 | 	return t.manifest
182 | }
183 | 
184 | func (t Tool) McpManifest() tools.McpManifest {
185 | 	return t.mcpManifest
186 | }
187 | 
188 | func (t Tool) Authorized(verifiedAuthServices []string) bool {
189 | 	return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
190 | }
191 | 
192 | func (t Tool) RequiresClientAuthorization() bool {
193 | 	return false
194 | }
195 | 
```
Page 18/47FirstPrevNextLast