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

# Directory Structure

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

# Files

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

```javascript
 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 | /**
16 |  * Renders the main content area into the HTML.
17 |  * @param {string} containerId The ID of the DOM element to inject the content into.
18 |  * @param {string} idString The id of the item inside the main content area.
19 |  */
20 | function renderMainContent(containerId, idString, instructionContent) {
21 |     const mainContentContainer = document.getElementById(containerId);
22 |     if (!mainContentContainer) {
23 |         console.error(`Content container with ID "${containerId}" not found.`);
24 |         return;
25 |     }
26 | 
27 |     const idAttribute = idString ? `id="${idString}"` : '';
28 |     const contentHTML = `
29 |         <div class="main-content-area">
30 |         <div class="top-bar">
31 |         </div>
32 |         <main class="content" ${idAttribute}">
33 |             ${instructionContent}
34 |         </main>
35 |     </div>
36 |     `;
37 | 
38 |     mainContentContainer.innerHTML = contentHTML;
39 | }
40 | 
41 | function getHomepageInstructions() {
42 |     return `
43 |       <div class="resource-instructions">
44 |         <h1 class="resource-title">Welcome to Toolbox UI</h1>
45 |         <p class="resource-intro">Toolbox UI is a built-in web interface that allows users to visually inspect and test out configured resources such as tools and toolsets. To get started, select a resource from the navigation tab to the left.</p>
46 |         <a href="https://googleapis.github.io/genai-toolbox/how-to/use-toolbox-ui/" class="btn btn--externalDocs" target="_blank" rel="noopener noreferrer">Toolbox UI Documentation</a>
47 |       </div>
48 |     `;
49 | }
50 | 
51 | function getToolInstructions() {
52 |     return `
53 |       <div class="resource-instructions">
54 |         <h1 class="resource-title">Tools</h1>
55 |         <p class="resource-intro">To inspect and test a tool, please click on one of your tools to the left.</p>
56 |         <h2 class="resource-subtitle">What are Tools?</h2>
57 |         <p class="resource-description">
58 |           Tools define actions an agent can take, such as running a SQL statement or interacting with a source. 
59 |           You can define Tools as a map in the <code>tools</code> section of your <code>tools.yaml</code> file. <br><br>
60 |           Some tools also use <strong>parameters</strong>. Parameters for each Tool will define what inputs the agent will need to provide to invoke them. 
61 |         </p>
62 |         <a href="https://googleapis.github.io/genai-toolbox/resources/tools/" class="btn btn--externalDocs" target="_blank" rel="noopener noreferrer">Tools Documentation</a>
63 |       </div>
64 |     `;
65 | }
66 | 
67 | function getToolsetInstructions() {
68 |     return `
69 |       <div class="resource-instructions">
70 |         <h1 class="resource-title">Toolsets</h1>
71 |         <p class="resource-intro">To inspect a specific toolset, please enter the name of a toolset and press search.</p>
72 |         <h2 class="resource-subtitle">What are Toolsets?</h2>
73 |         <p class="resource-description">
74 |           Toolsets define groups of tools an agent can access. You can define Toolsets as a map in the <code>toolsets</code> section of your <code>tools.yaml</code> file. Toolsets may
75 |           only include valid tools that are also defined in your <code>tools.yaml</code> file.
76 |         </p>
77 |         <a href="https://googleapis.github.io/genai-toolbox/getting-started/configure/#toolsets" class="btn btn--externalDocs" target="_blank" rel="noopener noreferrer">Toolsets Documentation</a>
78 |       </div>
79 |     `;
80 | }
```

--------------------------------------------------------------------------------
/internal/sources/cloudsqlmysql/cloud_sql_mysql.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
 16 | 
 17 | import (
 18 | 	"context"
 19 | 	"database/sql"
 20 | 	"fmt"
 21 | 	"net/url"
 22 | 	"slices"
 23 | 
 24 | 	"cloud.google.com/go/cloudsqlconn/mysql/mysql"
 25 | 	"github.com/goccy/go-yaml"
 26 | 	"github.com/googleapis/genai-toolbox/internal/sources"
 27 | 	"github.com/googleapis/genai-toolbox/internal/util"
 28 | 	"go.opentelemetry.io/otel/trace"
 29 | )
 30 | 
 31 | const SourceKind string = "cloud-sql-mysql"
 32 | 
 33 | // validate interface
 34 | var _ sources.SourceConfig = Config{}
 35 | 
 36 | func init() {
 37 | 	if !sources.Register(SourceKind, newConfig) {
 38 | 		panic(fmt.Sprintf("source kind %q already registered", SourceKind))
 39 | 	}
 40 | }
 41 | 
 42 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) {
 43 | 	actual := Config{Name: name, IPType: "public"} // Default IPType
 44 | 	if err := decoder.DecodeContext(ctx, &actual); err != nil {
 45 | 		return nil, err
 46 | 	}
 47 | 	return actual, nil
 48 | }
 49 | 
 50 | type Config struct {
 51 | 	Name     string         `yaml:"name" validate:"required"`
 52 | 	Kind     string         `yaml:"kind" validate:"required"`
 53 | 	Project  string         `yaml:"project" validate:"required"`
 54 | 	Region   string         `yaml:"region" validate:"required"`
 55 | 	Instance string         `yaml:"instance" validate:"required"`
 56 | 	IPType   sources.IPType `yaml:"ipType"`
 57 | 	User     string         `yaml:"user" validate:"required"`
 58 | 	Password string         `yaml:"password" validate:"required"`
 59 | 	Database string         `yaml:"database" validate:"required"`
 60 | }
 61 | 
 62 | func (r Config) SourceConfigKind() string {
 63 | 	return SourceKind
 64 | }
 65 | 
 66 | func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) {
 67 | 	pool, err := initCloudSQLMySQLConnectionPool(ctx, tracer, r.Name, r.Project, r.Region, r.Instance, r.IPType.String(), r.User, r.Password, r.Database)
 68 | 	if err != nil {
 69 | 		return nil, fmt.Errorf("unable to create pool: %w", err)
 70 | 	}
 71 | 
 72 | 	err = pool.PingContext(ctx)
 73 | 	if err != nil {
 74 | 		return nil, fmt.Errorf("unable to connect successfully: %w", err)
 75 | 	}
 76 | 
 77 | 	s := &Source{
 78 | 		Name: r.Name,
 79 | 		Kind: SourceKind,
 80 | 		Pool: pool,
 81 | 	}
 82 | 	return s, nil
 83 | }
 84 | 
 85 | var _ sources.Source = &Source{}
 86 | 
 87 | type Source struct {
 88 | 	Name string `yaml:"name"`
 89 | 	Kind string `yaml:"kind"`
 90 | 	Pool *sql.DB
 91 | }
 92 | 
 93 | func (s *Source) SourceKind() string {
 94 | 	return SourceKind
 95 | }
 96 | 
 97 | func (s *Source) MySQLPool() *sql.DB {
 98 | 	return s.Pool
 99 | }
100 | 
101 | func initCloudSQLMySQLConnectionPool(ctx context.Context, tracer trace.Tracer, name, project, region, instance, ipType, user, pass, dbname string) (*sql.DB, error) {
102 | 	//nolint:all // Reassigned ctx
103 | 	ctx, span := sources.InitConnectionSpan(ctx, tracer, SourceKind, name)
104 | 	defer span.End()
105 | 
106 | 	// Create a new dialer with options
107 | 	userAgent, err := util.UserAgentFromContext(ctx)
108 | 	if err != nil {
109 | 		return nil, err
110 | 	}
111 | 	opts, err := sources.GetCloudSQLOpts(ipType, userAgent, false)
112 | 	if err != nil {
113 | 		return nil, err
114 | 	}
115 | 
116 | 	if !slices.Contains(sql.Drivers(), "cloudsql-mysql") {
117 | 		_, err = mysql.RegisterDriver("cloudsql-mysql", opts...)
118 | 		if err != nil {
119 | 			return nil, fmt.Errorf("unable to register driver: %w", err)
120 | 		}
121 | 	}
122 | 	// Tell the driver to use the Cloud SQL Go Connector to create connections
123 | 	dsn := fmt.Sprintf("%s:%s@cloudsql-mysql(%s:%s:%s)/%s?connectionAttributes=program_name:%s", user, pass, project, region, instance, dbname, url.QueryEscape(userAgent))
124 | 	db, err := sql.Open(
125 | 		"cloudsql-mysql",
126 | 		dsn,
127 | 	)
128 | 	if err != nil {
129 | 		return nil, err
130 | 	}
131 | 	return db, nil
132 | }
133 | 
```

--------------------------------------------------------------------------------
/docs/en/about/faq.md:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | title: "FAQ"
 3 | type: docs
 4 | weight: 2
 5 | description: Frequently asked questions about Toolbox. 
 6 | ---
 7 | 
 8 | ## How can I deploy or run Toolbox?
 9 | 
10 | MCP Toolbox for Databases is open-source and can be run or deployed to a
11 | multitude of environments. For convenience, we release [compiled binaries and
12 | docker images][release-notes] (but you can always compile yourself as well!).
13 | 
14 | For detailed instructions, check out these resources:
15 | 
16 | - [Quickstart: How to Run Locally](../getting-started/local_quickstart.md)
17 | - [Deploy to Cloud Run](../how-to/deploy_toolbox.md)
18 | 
19 | [release-notes]: https://github.com/googleapis/genai-toolbox/releases/
20 | 
21 | ## Do I need a Google Cloud account/project to get started with Toolbox?
22 | 
23 | Nope! While some of the sources Toolbox connects to may require GCP credentials,
24 | Toolbox doesn't require them and can connect to a bunch of different resources
25 | that don't.
26 | 
27 | ## Does Toolbox take contributions from external users?
28 | 
29 | Absolutely! Please check out our [DEVELOPER.md][] for instructions on how to get
30 | started developing _on_ Toolbox instead of with it, and the [CONTRIBUTING.md][]
31 | for instructions on completing the CLA and getting a PR accepted.
32 | 
33 | [DEVELOPER.md]: https://github.com/googleapis/genai-toolbox/blob/main/DEVELOPER.md
34 | [CONTRIBUTING.MD]: https://github.com/googleapis/genai-toolbox/blob/main/CONTRIBUTING.md
35 | 
36 | ## Can Toolbox support a feature to let me do _$FOO_?
37 | 
38 | Maybe? The best place to start is by [opening an issue][github-issue] for
39 | discussion (or seeing if there is already one open), so we can better understand
40 | your use case and the best way to solve it. Generally we aim to prioritize the
41 | most popular issues, so make sure to +1 ones you are the most interested in.
42 | 
43 | [github-issue]: https://github.com/googleapis/genai-toolbox/issues
44 | 
45 | ## Can Toolbox be used for non-database tools?
46 | 
47 | Currently, Toolbox is primarily focused on making it easier to create and
48 | develop tools focused on interacting with Databases. We believe that there are a
49 | lot of unique problems when interacting with Databases for Gen AI use cases, and
50 | want to prioritize solving those first.  
51 | 
52 | However, we've also received feedback that supporting more generic HTTP or
53 | GRPC tools might be helpful in assisting with migrating to Toolbox or in
54 | accomplishing more complicated workflows. We're looking into what that might
55 | best look like in Toolbox.
56 | 
57 | ## Can I use _$BAR_ orchestration framework to use tools from Toolbox?
58 | 
59 | Currently, Toolbox only supports a limited number of client SDKs at our initial
60 | launch. We are investigating support for more frameworks as well as more general
61 | approaches for users without a framework -- look forward to seeing an update
62 | soon.
63 | 
64 | ## Why does Toolbox use a server-client architecture pattern?
65 | 
66 | Toolbox's server-client architecture allows us to more easily support a wide
67 | variety of languages and frameworks with a centralized implementation. It also
68 | allows us to tackle problems like connection pooling, auth, or caching more
69 | completely than entirely client-side solutions.
70 | 
71 | ## Why was Toolbox written in Go?
72 | 
73 | While a large part of the Gen AI Ecosystem is predominately Python, we opted to
74 | use Go. We chose Go because it's still easy and simple to use, but also easier
75 | to write fast, efficient, and concurrent servers. Additionally, given the
76 | server-client architecture, we can still meet many developers where they are
77 | with clients in their preferred language. As Gen AI matures, we want developers
78 | to be able to use Toolbox on the serving path of mission critical applications.
79 | It's easier to build the needed robustness, performance and scalability in Go
80 | than in Python.
81 | 
82 | ## Is Toolbox compatible with Model Context Protocol (MCP)?
83 | 
84 | Yes! Toolbox is compatible with [Anthropic's Model Context Protocol
85 | (MCP)](https://modelcontextprotocol.io/). Please checkout [Connect via
86 | MCP](../how-to/connect_via_mcp.md) on how to connect to Toolbox with an MCP
87 | client.
88 | 
```

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

```markdown
 1 | ---
 2 | title: "sqlite-sql"
 3 | type: docs
 4 | weight: 1
 5 | description: >
 6 |   Execute SQL statements against a SQLite database.
 7 | aliases:
 8 | - /resources/tools/sqlite-sql
 9 | ---
10 | 
11 | ## About
12 | 
13 | A `sqlite-sql` tool executes SQL statements against a SQLite database.
14 | It's compatible with any of the following sources:
15 | 
16 | - [sqlite](../../sources/sqlite.md)
17 | 
18 | SQLite uses the `?` placeholder for parameters in SQL statements. Parameters are
19 | bound in the order they are provided.
20 | 
21 | The statement field supports any valid SQLite SQL statement, including `SELECT`,
22 | `INSERT`, `UPDATE`, `DELETE`, `CREATE/ALTER/DROP` table statements, and other
23 | DDL statements.
24 | 
25 | ### Example
26 | 
27 | > **Note:** This tool uses parameterized queries to prevent SQL injections.
28 | > Query parameters can be used as substitutes for arbitrary expressions.
29 | > Parameters cannot be used as substitutes for identifiers, column names, table
30 | > names, or other parts of the query.
31 | 
32 | ```yaml
33 | tools:
34 |   search-users:
35 |     kind: sqlite-sql
36 |     source: my-sqlite-db
37 |     description: Search users by name and age
38 |     parameters:
39 |       - name: name
40 |         type: string
41 |         description: The name to search for
42 |       - name: min_age
43 |         type: integer
44 |         description: Minimum age
45 |     statement: SELECT * FROM users WHERE name LIKE ? AND age >= ?
46 | ```
47 | 
48 | ### Example with Template Parameters
49 | 
50 | > **Note:** This tool allows direct modifications to the SQL statement,
51 | > including identifiers, column names, and table names. **This makes it more
52 | > vulnerable to SQL injections**. Using basic parameters only (see above) is
53 | > recommended for performance and safety reasons. For more details, please check
54 | > [templateParameters](..#template-parameters).
55 | 
56 | ```yaml
57 | tools:
58 |  list_table:
59 |     kind: sqlite-sql
60 |     source: my-sqlite-db
61 |     statement: |
62 |       SELECT * FROM {{.tableName}};
63 |     description: |
64 |       Use this tool to list all information from a specific table.
65 |       Example:
66 |       {{
67 |           "tableName": "flights",
68 |       }}
69 |     templateParameters:
70 |       - name: tableName
71 |         type: string
72 |         description: Table to select from
73 | ```
74 | 
75 | ## Reference
76 | 
77 | | **field**          |                  **type**                        | **required** | **description**                                                                                                                            |
78 | |--------------------|:------------------------------------------------:|:------------:|--------------------------------------------------------------------------------------------------------------------------------------------|
79 | | kind               |                   string                         |     true     | Must be "sqlite-sql".                                                                                                                      |
80 | | source             |                   string                         |     true     | Name of the source the SQLite source configuration.                                                                                        |
81 | | description        |                   string                         |     true     | Description of the tool that is passed to the LLM.                                                                                         |
82 | | statement          |                   string                         |     true     | The SQL statement to execute.                                                                                                              |
83 | | parameters         | [parameters](../#specifying-parameters)       |    false     | List of [parameters](../#specifying-parameters) that will be inserted into the SQL statement.                                           |
84 | | templateParameters | [templateParameters](..#template-parameters) |    false     | List of [templateParameters](..#template-parameters) that will be inserted into the SQL statement before executing prepared statement. |
85 | 
```

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

```markdown
  1 | ---
  2 | title: "Redis"
  3 | linkTitle: "Redis"
  4 | type: docs
  5 | weight: 1
  6 | description: >
  7 |     Redis is a in-memory data structure store.
  8 |     
  9 | ---
 10 | 
 11 | ## About
 12 | 
 13 | Redis is a in-memory data structure store, used as a database,
 14 | cache, and message broker. It supports data structures such as strings, hashes,
 15 | lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, and
 16 | geospatial indexes with radius queries.
 17 | 
 18 | If you are new to Redis, you can find installation and getting started guides on
 19 | the [official Redis website](https://redis.io/docs/).
 20 | 
 21 | ## Available Tools
 22 | 
 23 | - [`redis`](../tools/redis/redis.md)  
 24 |   Run Redis commands and interact with key-value pairs.
 25 | 
 26 | ## Requirements
 27 | 
 28 | ### Redis
 29 | 
 30 | [AUTH string][auth] is a password for connection to Redis. If you have the
 31 | `requirepass` directive set in your Redis configuration, incoming client
 32 | connections must authenticate in order to connect.
 33 | 
 34 | Specify your AUTH string in the password field:
 35 | 
 36 | ```yaml
 37 | sources:
 38 |     my-redis-instance:
 39 |      kind: redis
 40 |      address:
 41 |        - 127.0.0.1:6379
 42 |      username: ${MY_USER_NAME}
 43 |      password: ${MY_AUTH_STRING} # Omit this field if you don't have a password.
 44 |      # database: 0
 45 |      # clusterEnabled: false
 46 |      # useGCPIAM: false
 47 | ```
 48 | 
 49 | {{< notice tip >}}
 50 | Use environment variable replacement with the format ${ENV_NAME}
 51 | instead of hardcoding your secrets into the configuration file.
 52 | {{< /notice >}}
 53 | 
 54 | ### Memorystore For Redis
 55 | 
 56 | Memorystore standalone instances support authentication using an [AUTH][auth]
 57 | string.
 58 | 
 59 | Here is an example tools.yaml config with [AUTH][auth] enabled:
 60 | 
 61 | ```yaml
 62 | sources:
 63 |     my-redis-cluster-instance:
 64 |      kind: memorystore-redis
 65 |      address:
 66 |        - 127.0.0.1:6379
 67 |      password: ${MY_AUTH_STRING}
 68 |      # useGCPIAM: false
 69 |      # clusterEnabled: false
 70 | ```
 71 | 
 72 | Memorystore Redis Cluster supports IAM authentication instead. Grant your
 73 | account the required [IAM role][iam] and make sure to set `useGCPIAM` to `true`.
 74 | 
 75 | Here is an example tools.yaml config for Memorystore Redis Cluster instances
 76 | using IAM authentication:
 77 | 
 78 | ```yaml
 79 | sources:
 80 |     my-redis-cluster-instance:
 81 |      kind: memorystore-redis
 82 |      address:
 83 |        - 127.0.0.1:6379
 84 |      useGCPIAM: true
 85 |      clusterEnabled: true
 86 | ```
 87 | 
 88 | [iam]: https://cloud.google.com/memorystore/docs/cluster/about-iam-auth
 89 | 
 90 | ## Reference
 91 | 
 92 | | **field**      | **type** | **required** | **description**                                                                                                                 |
 93 | |----------------|:--------:|:------------:|---------------------------------------------------------------------------------------------------------------------------------|
 94 | | kind           |  string  |     true     | Must be "memorystore-redis".                                                                                                    |
 95 | | address        |  string  |     true     | Primary endpoint for the Memorystore Redis instance to connect to.                                                              |
 96 | | username       |  string  |    false     | If you are using a non-default user, specify the user name here. If you are using Memorystore for Redis, leave this field blank |
 97 | | password       |  string  |    false     | If you have [Redis AUTH][auth] enabled, specify the AUTH string here                                                            |
 98 | | database       |   int    |    false     | The Redis database to connect to. Not applicable for cluster enabled instances. The default database is `0`.                    |
 99 | | clusterEnabled |   bool   |    false     | Set it to `true` if using a Redis Cluster instance. Defaults to `false`.                                                        |
100 | | useGCPIAM      |  string  |    false     | Set it to `true` if you are using GCP's IAM authentication. Defaults to `false`.                                                |
101 | 
102 | [auth]: https://cloud.google.com/memorystore/docs/redis/about-redis-auth
103 | 
```

--------------------------------------------------------------------------------
/internal/sources/valkey/valkey_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 valkey_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/sources"
 25 | 	"github.com/googleapis/genai-toolbox/internal/sources/valkey"
 26 | 	"github.com/googleapis/genai-toolbox/internal/testutils"
 27 | )
 28 | 
 29 | func TestParseFromYamlValkey(t *testing.T) {
 30 | 	tcs := []struct {
 31 | 		desc string
 32 | 		in   string
 33 | 		want server.SourceConfigs
 34 | 	}{
 35 | 		{
 36 | 			desc: "default setting",
 37 | 			in: `
 38 | 			sources:
 39 | 				my-valkey-instance:
 40 | 					kind: valkey
 41 | 					address:
 42 | 					  - 127.0.0.1
 43 | 			`,
 44 | 			want: map[string]sources.SourceConfig{
 45 | 				"my-valkey-instance": valkey.Config{
 46 | 					Name:         "my-valkey-instance",
 47 | 					Kind:         valkey.SourceKind,
 48 | 					Address:      []string{"127.0.0.1"},
 49 | 					Username:     "",
 50 | 					Password:     "",
 51 | 					Database:     0,
 52 | 					UseGCPIAM:    false,
 53 | 					DisableCache: false,
 54 | 				},
 55 | 			},
 56 | 		},
 57 | 		{
 58 | 			desc: "advanced example",
 59 | 			in: `
 60 | 			sources:
 61 | 				my-valkey-instance:
 62 | 					kind: valkey
 63 | 					address:
 64 | 					  - 127.0.0.1
 65 | 					database: 1
 66 | 					username: user
 67 | 					password: pass
 68 | 					useGCPIAM: true
 69 | 					disableCache: true
 70 | 			`,
 71 | 			want: map[string]sources.SourceConfig{
 72 | 				"my-valkey-instance": valkey.Config{
 73 | 					Name:         "my-valkey-instance",
 74 | 					Kind:         valkey.SourceKind,
 75 | 					Address:      []string{"127.0.0.1"},
 76 | 					Username:     "user",
 77 | 					Password:     "pass",
 78 | 					Database:     1,
 79 | 					UseGCPIAM:    true,
 80 | 					DisableCache: true,
 81 | 				},
 82 | 			},
 83 | 		},
 84 | 	}
 85 | 	for _, tc := range tcs {
 86 | 		t.Run(tc.desc, func(t *testing.T) {
 87 | 			got := struct {
 88 | 				Sources server.SourceConfigs `yaml:"sources"`
 89 | 			}{}
 90 | 			// Parse contents
 91 | 			err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
 92 | 			if err != nil {
 93 | 				t.Fatalf("unable to unmarshal: %s", err)
 94 | 			}
 95 | 			if !cmp.Equal(tc.want, got.Sources) {
 96 | 				t.Fatalf("incorrect parse: want %v, got %v", tc.want, got.Sources)
 97 | 			}
 98 | 		})
 99 | 	}
100 | 
101 | }
102 | 
103 | func TestFailParseFromYaml(t *testing.T) {
104 | 	tcs := []struct {
105 | 		desc string
106 | 		in   string
107 | 		err  string
108 | 	}{
109 | 		{
110 | 			desc: "invalid database",
111 | 			in: `
112 | 			sources:
113 | 				my-valkey-instance:
114 | 					kind: valkey
115 | 					project: my-project
116 | 					address:
117 | 					  - 127.0.0.1
118 | 					database: my-db
119 | 					useGCPIAM: false
120 | 			`,
121 | 			err: "cannot unmarshal string into Go struct field .Sources of type int",
122 | 		},
123 | 		{
124 | 			desc: "extra field",
125 | 			in: `
126 | 			sources:
127 | 				my-valkey-instance:
128 | 					kind: valkey
129 | 					address:
130 | 					  - 127.0.0.1
131 | 					project: proj
132 | 					database: 1
133 | 			`,
134 | 			err: "unable to parse source \"my-valkey-instance\" as \"valkey\": [5:1] unknown field \"project\"",
135 | 		},
136 | 		{
137 | 			desc: "missing required field",
138 | 			in: `
139 | 			sources:
140 | 				my-valkey-instance:
141 | 					kind: valkey
142 | 			`,
143 | 			err: "unable to parse source \"my-valkey-instance\" as \"valkey\": Key: 'Config.Address' Error:Field validation for 'Address' failed on the 'required' tag",
144 | 		},
145 | 	}
146 | 	for _, tc := range tcs {
147 | 		t.Run(tc.desc, func(t *testing.T) {
148 | 			got := struct {
149 | 				Sources server.SourceConfigs `yaml:"sources"`
150 | 			}{}
151 | 			// Parse contents
152 | 			err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
153 | 			if err == nil {
154 | 				t.Fatalf("expect parsing to fail")
155 | 			}
156 | 			errStr := err.Error()
157 | 			if !strings.Contains(errStr, tc.err) {
158 | 				t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err)
159 | 			}
160 | 		})
161 | 	}
162 | }
163 | 
```

--------------------------------------------------------------------------------
/internal/tools/mongodb/mongodbfind/mongodbfind_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 mongodbfind_test
 16 | 
 17 | import (
 18 | 	"strings"
 19 | 	"testing"
 20 | 
 21 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 22 | 	"github.com/googleapis/genai-toolbox/internal/tools/mongodb/mongodbfind"
 23 | 
 24 | 	yaml "github.com/goccy/go-yaml"
 25 | 	"github.com/google/go-cmp/cmp"
 26 | 	"github.com/googleapis/genai-toolbox/internal/server"
 27 | 	"github.com/googleapis/genai-toolbox/internal/testutils"
 28 | )
 29 | 
 30 | func TestParseFromYamlMongoQuery(t *testing.T) {
 31 | 	ctx, err := testutils.ContextWithNewLogger()
 32 | 	if err != nil {
 33 | 		t.Fatalf("unexpected error: %s", err)
 34 | 	}
 35 | 	tcs := []struct {
 36 | 		desc string
 37 | 		in   string
 38 | 		want server.ToolConfigs
 39 | 	}{
 40 | 		{
 41 | 			desc: "basic example",
 42 | 			in: `
 43 | 			tools:
 44 | 				example_tool:
 45 | 					kind: mongodb-find
 46 | 					source: my-instance
 47 | 					description: some description
 48 | 					database: test_db
 49 | 					collection: test_coll
 50 | 					filterPayload: |
 51 | 					    { name: {{json .name}} }
 52 | 					filterParams:
 53 |                         - name: name 
 54 |                           type: string
 55 |                           description: small description
 56 | 					projectPayload: |
 57 | 					  { name: 1, age: 1 }
 58 | 					projectParams: []
 59 | 					sortPayload: |
 60 | 					  { timestamp: -1 }
 61 | 					sortParams: []
 62 | 			`,
 63 | 			want: server.ToolConfigs{
 64 | 				"example_tool": mongodbfind.Config{
 65 | 					Name:          "example_tool",
 66 | 					Kind:          "mongodb-find",
 67 | 					Source:        "my-instance",
 68 | 					AuthRequired:  []string{},
 69 | 					Database:      "test_db",
 70 | 					Collection:    "test_coll",
 71 | 					Description:   "some description",
 72 | 					FilterPayload: "{ name: {{json .name}} }\n",
 73 | 					FilterParams: tools.Parameters{
 74 | 						&tools.StringParameter{
 75 | 							CommonParameter: tools.CommonParameter{
 76 | 								Name: "name",
 77 | 								Type: "string",
 78 | 								Desc: "small description",
 79 | 							},
 80 | 						},
 81 | 					},
 82 | 					ProjectPayload: "{ name: 1, age: 1 }\n",
 83 | 					ProjectParams:  tools.Parameters{},
 84 | 					SortPayload:    "{ timestamp: -1 }\n",
 85 | 					SortParams:     tools.Parameters{},
 86 | 				},
 87 | 			},
 88 | 		},
 89 | 	}
 90 | 	for _, tc := range tcs {
 91 | 		t.Run(tc.desc, func(t *testing.T) {
 92 | 			got := struct {
 93 | 				Tools server.ToolConfigs `yaml:"tools"`
 94 | 			}{}
 95 | 			// Parse contents
 96 | 			err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
 97 | 			if err != nil {
 98 | 				t.Fatalf("unable to unmarshal: %s", err)
 99 | 			}
100 | 			if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
101 | 				t.Fatalf("incorrect parse: diff %v", diff)
102 | 			}
103 | 		})
104 | 	}
105 | 
106 | }
107 | 
108 | func TestFailParseFromYamlMongoQuery(t *testing.T) {
109 | 	ctx, err := testutils.ContextWithNewLogger()
110 | 	if err != nil {
111 | 		t.Fatalf("unexpected error: %s", err)
112 | 	}
113 | 	tcs := []struct {
114 | 		desc string
115 | 		in   string
116 | 		err  string
117 | 	}{
118 | 		{
119 | 			desc: "Invalid method",
120 | 			in: `
121 | 			tools:
122 | 				example_tool:
123 | 					kind: mongodb-find
124 | 					source: my-instance
125 | 					description: some description
126 | 					collection: test_coll
127 | 					filterPayload: |
128 | 					  { name : {{json .name}} }
129 | 			`,
130 | 			err: `unable to parse tool "example_tool" as kind "mongodb-find"`,
131 | 		},
132 | 	}
133 | 	for _, tc := range tcs {
134 | 		t.Run(tc.desc, func(t *testing.T) {
135 | 			got := struct {
136 | 				Tools server.ToolConfigs `yaml:"tools"`
137 | 			}{}
138 | 			// Parse contents
139 | 			err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
140 | 			if err == nil {
141 | 				t.Fatalf("expect parsing to fail")
142 | 			}
143 | 			errStr := err.Error()
144 | 			if !strings.Contains(errStr, tc.err) {
145 | 				t.Fatalf("unexpected error string: got %q, want substring %q", errStr, tc.err)
146 | 			}
147 | 		})
148 | 	}
149 | 
150 | }
151 | 
```

--------------------------------------------------------------------------------
/internal/tools/mongodb/mongodbfindone/mongodbfindone_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 mongodbfindone_test
 16 | 
 17 | import (
 18 | 	"strings"
 19 | 	"testing"
 20 | 
 21 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 22 | 	"github.com/googleapis/genai-toolbox/internal/tools/mongodb/mongodbfindone"
 23 | 
 24 | 	yaml "github.com/goccy/go-yaml"
 25 | 	"github.com/google/go-cmp/cmp"
 26 | 	"github.com/googleapis/genai-toolbox/internal/server"
 27 | 	"github.com/googleapis/genai-toolbox/internal/testutils"
 28 | )
 29 | 
 30 | func TestParseFromYamlMongoQuery(t *testing.T) {
 31 | 	ctx, err := testutils.ContextWithNewLogger()
 32 | 	if err != nil {
 33 | 		t.Fatalf("unexpected error: %s", err)
 34 | 	}
 35 | 	tcs := []struct {
 36 | 		desc string
 37 | 		in   string
 38 | 		want server.ToolConfigs
 39 | 	}{
 40 | 		{
 41 | 			desc: "basic example",
 42 | 			in: `
 43 | 			tools:
 44 | 				example_tool:
 45 | 					kind: mongodb-find-one
 46 | 					source: my-instance
 47 | 					description: some description
 48 | 					database: test_db
 49 | 					collection: test_coll
 50 | 					filterPayload: |
 51 | 					    { name: {{json .name}} }
 52 | 					filterParams:
 53 |                         - name: name 
 54 |                           type: string
 55 |                           description: small description
 56 | 					projectPayload: |
 57 | 					  { name: 1, age: 1 }
 58 | 					projectParams: []
 59 | 					sortPayload: |
 60 | 					  { timestamp: -1 }
 61 | 					sortParams: []
 62 | 			`,
 63 | 			want: server.ToolConfigs{
 64 | 				"example_tool": mongodbfindone.Config{
 65 | 					Name:          "example_tool",
 66 | 					Kind:          "mongodb-find-one",
 67 | 					Source:        "my-instance",
 68 | 					AuthRequired:  []string{},
 69 | 					Database:      "test_db",
 70 | 					Collection:    "test_coll",
 71 | 					Description:   "some description",
 72 | 					FilterPayload: "{ name: {{json .name}} }\n",
 73 | 					FilterParams: tools.Parameters{
 74 | 						&tools.StringParameter{
 75 | 							CommonParameter: tools.CommonParameter{
 76 | 								Name: "name",
 77 | 								Type: "string",
 78 | 								Desc: "small description",
 79 | 							},
 80 | 						},
 81 | 					},
 82 | 					ProjectPayload: "{ name: 1, age: 1 }\n",
 83 | 					ProjectParams:  tools.Parameters{},
 84 | 					SortPayload:    "{ timestamp: -1 }\n",
 85 | 					SortParams:     tools.Parameters{},
 86 | 				},
 87 | 			},
 88 | 		},
 89 | 	}
 90 | 	for _, tc := range tcs {
 91 | 		t.Run(tc.desc, func(t *testing.T) {
 92 | 			got := struct {
 93 | 				Tools server.ToolConfigs `yaml:"tools"`
 94 | 			}{}
 95 | 			// Parse contents
 96 | 			err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
 97 | 			if err != nil {
 98 | 				t.Fatalf("unable to unmarshal: %s", err)
 99 | 			}
100 | 			if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
101 | 				t.Fatalf("incorrect parse: diff %v", diff)
102 | 			}
103 | 		})
104 | 	}
105 | 
106 | }
107 | 
108 | func TestFailParseFromYamlMongoQuery(t *testing.T) {
109 | 	ctx, err := testutils.ContextWithNewLogger()
110 | 	if err != nil {
111 | 		t.Fatalf("unexpected error: %s", err)
112 | 	}
113 | 	tcs := []struct {
114 | 		desc string
115 | 		in   string
116 | 		err  string
117 | 	}{
118 | 		{
119 | 			desc: "Invalid method",
120 | 			in: `
121 | 			tools:
122 | 				example_tool:
123 | 					kind: mongodb-find-one
124 | 					source: my-instance
125 | 					description: some description
126 | 					collection: test_coll
127 | 					filterPayload: |
128 | 					  { name : {{json .name}} }
129 | 			`,
130 | 			err: `unable to parse tool "example_tool" as kind "mongodb-find-one"`,
131 | 		},
132 | 	}
133 | 	for _, tc := range tcs {
134 | 		t.Run(tc.desc, func(t *testing.T) {
135 | 			got := struct {
136 | 				Tools server.ToolConfigs `yaml:"tools"`
137 | 			}{}
138 | 			// Parse contents
139 | 			err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
140 | 			if err == nil {
141 | 				t.Fatalf("expect parsing to fail")
142 | 			}
143 | 			errStr := err.Error()
144 | 			if !strings.Contains(errStr, tc.err) {
145 | 				t.Fatalf("unexpected error string: got %q, want substring %q", errStr, tc.err)
146 | 			}
147 | 		})
148 | 	}
149 | 
150 | }
151 | 
```

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

--------------------------------------------------------------------------------
/internal/sources/postgres/postgres.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 postgres
 16 | 
 17 | import (
 18 | 	"context"
 19 | 	"fmt"
 20 | 	"net/url"
 21 | 	"strings"
 22 | 
 23 | 	"github.com/goccy/go-yaml"
 24 | 	"github.com/googleapis/genai-toolbox/internal/sources"
 25 | 	"github.com/googleapis/genai-toolbox/internal/util"
 26 | 	"github.com/jackc/pgx/v5/pgxpool"
 27 | 	"go.opentelemetry.io/otel/trace"
 28 | )
 29 | 
 30 | const SourceKind string = "postgres"
 31 | 
 32 | // validate interface
 33 | var _ sources.SourceConfig = Config{}
 34 | 
 35 | func init() {
 36 | 	if !sources.Register(SourceKind, newConfig) {
 37 | 		panic(fmt.Sprintf("source kind %q already registered", SourceKind))
 38 | 	}
 39 | }
 40 | 
 41 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) {
 42 | 	actual := Config{Name: name}
 43 | 	if err := decoder.DecodeContext(ctx, &actual); err != nil {
 44 | 		return nil, err
 45 | 	}
 46 | 	return actual, nil
 47 | }
 48 | 
 49 | type Config struct {
 50 | 	Name        string            `yaml:"name" validate:"required"`
 51 | 	Kind        string            `yaml:"kind" validate:"required"`
 52 | 	Host        string            `yaml:"host" validate:"required"`
 53 | 	Port        string            `yaml:"port" validate:"required"`
 54 | 	User        string            `yaml:"user" validate:"required"`
 55 | 	Password    string            `yaml:"password" validate:"required"`
 56 | 	Database    string            `yaml:"database" validate:"required"`
 57 | 	QueryParams map[string]string `yaml:"queryParams"`
 58 | }
 59 | 
 60 | func (r Config) SourceConfigKind() string {
 61 | 	return SourceKind
 62 | }
 63 | 
 64 | func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) {
 65 | 	pool, err := initPostgresConnectionPool(ctx, tracer, r.Name, r.Host, r.Port, r.User, r.Password, r.Database, r.QueryParams)
 66 | 	if err != nil {
 67 | 		return nil, fmt.Errorf("unable to create pool: %w", err)
 68 | 	}
 69 | 
 70 | 	err = pool.Ping(ctx)
 71 | 	if err != nil {
 72 | 		return nil, fmt.Errorf("unable to connect successfully: %w", err)
 73 | 	}
 74 | 
 75 | 	s := &Source{
 76 | 		Name: r.Name,
 77 | 		Kind: SourceKind,
 78 | 		Pool: pool,
 79 | 	}
 80 | 	return s, nil
 81 | }
 82 | 
 83 | var _ sources.Source = &Source{}
 84 | 
 85 | type Source struct {
 86 | 	Name string `yaml:"name"`
 87 | 	Kind string `yaml:"kind"`
 88 | 	Pool *pgxpool.Pool
 89 | }
 90 | 
 91 | func (s *Source) SourceKind() string {
 92 | 	return SourceKind
 93 | }
 94 | 
 95 | func (s *Source) PostgresPool() *pgxpool.Pool {
 96 | 	return s.Pool
 97 | }
 98 | 
 99 | func initPostgresConnectionPool(ctx context.Context, tracer trace.Tracer, name, host, port, user, pass, dbname string, queryParams map[string]string) (*pgxpool.Pool, error) {
100 | 	//nolint:all // Reassigned ctx
101 | 	ctx, span := sources.InitConnectionSpan(ctx, tracer, SourceKind, name)
102 | 	defer span.End()
103 | 	userAgent, err := util.UserAgentFromContext(ctx)
104 | 	if err != nil {
105 | 		userAgent = "genai-toolbox"
106 | 	}
107 | 	if queryParams == nil {
108 | 		// Initialize the map before using it
109 | 		queryParams = make(map[string]string)
110 | 	}
111 | 	if _, ok := queryParams["application_name"]; !ok {
112 | 		queryParams["application_name"] = userAgent
113 | 	}
114 | 
115 | 	// urlExample := "postgres:dd//username:password@localhost:5432/database_name"
116 | 	url := &url.URL{
117 | 		Scheme:   "postgres",
118 | 		User:     url.UserPassword(user, pass),
119 | 		Host:     fmt.Sprintf("%s:%s", host, port),
120 | 		Path:     dbname,
121 | 		RawQuery: ConvertParamMapToRawQuery(queryParams),
122 | 	}
123 | 	pool, err := pgxpool.New(ctx, url.String())
124 | 	if err != nil {
125 | 		return nil, fmt.Errorf("unable to create connection pool: %w", err)
126 | 	}
127 | 
128 | 	return pool, nil
129 | }
130 | 
131 | func ConvertParamMapToRawQuery(queryParams map[string]string) string {
132 | 	queryArray := []string{}
133 | 	for k, v := range queryParams {
134 | 		queryArray = append(queryArray, fmt.Sprintf("%s=%s", k, v))
135 | 	}
136 | 	return strings.Join(queryArray, "&")
137 | }
138 | 
```

--------------------------------------------------------------------------------
/internal/sources/cassandra/cassandra.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 cassandra
 16 | 
 17 | import (
 18 | 	"context"
 19 | 	"fmt"
 20 | 
 21 | 	"github.com/goccy/go-yaml"
 22 | 	"github.com/gocql/gocql"
 23 | 	"github.com/googleapis/genai-toolbox/internal/sources"
 24 | 	"go.opentelemetry.io/otel/trace"
 25 | )
 26 | 
 27 | const SourceKind string = "cassandra"
 28 | 
 29 | func init() {
 30 | 	if !sources.Register(SourceKind, newConfig) {
 31 | 		panic(fmt.Sprintf("source kind %q already registered", SourceKind))
 32 | 	}
 33 | }
 34 | 
 35 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) {
 36 | 	actual := Config{Name: name}
 37 | 	if err := decoder.DecodeContext(ctx, &actual); err != nil {
 38 | 		return nil, err
 39 | 	}
 40 | 	return actual, nil
 41 | }
 42 | 
 43 | type Config struct {
 44 | 	Name                   string   `yaml:"name" validate:"required"`
 45 | 	Kind                   string   `yaml:"kind" validate:"required"`
 46 | 	Hosts                  []string `yaml:"hosts" validate:"required"`
 47 | 	Keyspace               string   `yaml:"keyspace"`
 48 | 	ProtoVersion           int      `yaml:"protoVersion"`
 49 | 	Username               string   `yaml:"username"`
 50 | 	Password               string   `yaml:"password"`
 51 | 	CAPath                 string   `yaml:"caPath"`
 52 | 	CertPath               string   `yaml:"certPath"`
 53 | 	KeyPath                string   `yaml:"keyPath"`
 54 | 	EnableHostVerification bool     `yaml:"enableHostVerification"`
 55 | }
 56 | 
 57 | // Initialize implements sources.SourceConfig.
 58 | func (c Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) {
 59 | 	session, err := initCassandraSession(ctx, tracer, c)
 60 | 	if err != nil {
 61 | 		return nil, fmt.Errorf("unable to create session: %v", err)
 62 | 	}
 63 | 	s := &Source{
 64 | 		Name:    c.Name,
 65 | 		Kind:    SourceKind,
 66 | 		Session: session,
 67 | 	}
 68 | 	return s, nil
 69 | }
 70 | 
 71 | // SourceConfigKind implements sources.SourceConfig.
 72 | func (c Config) SourceConfigKind() string {
 73 | 	return SourceKind
 74 | }
 75 | 
 76 | var _ sources.SourceConfig = Config{}
 77 | 
 78 | type Source struct {
 79 | 	Name    string `yaml:"name"`
 80 | 	Kind    string `yaml:"kind"`
 81 | 	Session *gocql.Session
 82 | }
 83 | 
 84 | // CassandraSession implements cassandra.compatibleSource.
 85 | func (s *Source) CassandraSession() *gocql.Session {
 86 | 	return s.Session
 87 | }
 88 | 
 89 | // SourceKind implements sources.Source.
 90 | func (s Source) SourceKind() string {
 91 | 	return SourceKind
 92 | }
 93 | 
 94 | var _ sources.Source = &Source{}
 95 | 
 96 | func initCassandraSession(ctx context.Context, tracer trace.Tracer, c Config) (*gocql.Session, error) {
 97 | 	//nolint:all // Reassigned ctx
 98 | 	ctx, span := sources.InitConnectionSpan(ctx, tracer, SourceKind, c.Name)
 99 | 	defer span.End()
100 | 
101 | 	// Validate authentication configuration
102 | 	if c.Password != "" && c.Username == "" {
103 | 		return nil, fmt.Errorf("invalid Cassandra configuration: password provided without a username")
104 | 	}
105 | 
106 | 	cluster := gocql.NewCluster(c.Hosts...)
107 | 	cluster.ProtoVersion = c.ProtoVersion
108 | 	cluster.Keyspace = c.Keyspace
109 | 
110 | 	// Configure authentication if username is provided
111 | 	if c.Username != "" {
112 | 		cluster.Authenticator = gocql.PasswordAuthenticator{
113 | 			Username: c.Username,
114 | 			Password: c.Password,
115 | 		}
116 | 	}
117 | 
118 | 	// Configure SSL options if any are specified
119 | 	if c.CAPath != "" || c.CertPath != "" || c.KeyPath != "" || c.EnableHostVerification {
120 | 		cluster.SslOpts = &gocql.SslOptions{
121 | 			CaPath:                 c.CAPath,
122 | 			CertPath:               c.CertPath,
123 | 			KeyPath:                c.KeyPath,
124 | 			EnableHostVerification: c.EnableHostVerification,
125 | 		}
126 | 	}
127 | 
128 | 	// Create session
129 | 	session, err := cluster.CreateSession()
130 | 	if err != nil {
131 | 		return nil, fmt.Errorf("failed to create Cassandra session: %w", err)
132 | 	}
133 | 	return session, nil
134 | }
135 | 
```

--------------------------------------------------------------------------------
/internal/tools/common.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 tools
 16 | 
 17 | import (
 18 | 	"bytes"
 19 | 	"encoding/json"
 20 | 	"fmt"
 21 | 	"regexp"
 22 | 	"text/template"
 23 | )
 24 | 
 25 | var validName = regexp.MustCompile(`^[a-zA-Z0-9_-]*$`)
 26 | 
 27 | func IsValidName(s string) bool {
 28 | 	return validName.MatchString(s)
 29 | }
 30 | 
 31 | // ConvertAnySliceToTyped a []any to typed slice ([]string, []int, []float etc.)
 32 | func ConvertAnySliceToTyped(s []any, itemType string) (any, error) {
 33 | 	var typedSlice any
 34 | 	switch itemType {
 35 | 	case "string":
 36 | 		tempSlice := make([]string, len(s))
 37 | 		for j, item := range s {
 38 | 			s, ok := item.(string)
 39 | 			if !ok {
 40 | 				return nil, fmt.Errorf("expected item at index %d to be string, got %T", j, item)
 41 | 			}
 42 | 			tempSlice[j] = s
 43 | 		}
 44 | 		typedSlice = tempSlice
 45 | 	case "integer":
 46 | 		tempSlice := make([]int64, len(s))
 47 | 		for j, item := range s {
 48 | 			i, ok := item.(int)
 49 | 			if !ok {
 50 | 				return nil, fmt.Errorf("expected item at index %d to be integer, got %T", j, item)
 51 | 			}
 52 | 			tempSlice[j] = int64(i)
 53 | 		}
 54 | 		typedSlice = tempSlice
 55 | 	case "float":
 56 | 		tempSlice := make([]float64, len(s))
 57 | 		for j, item := range s {
 58 | 			f, ok := item.(float64)
 59 | 			if !ok {
 60 | 				return nil, fmt.Errorf("expected item at index %d to be float, got %T", j, item)
 61 | 			}
 62 | 			tempSlice[j] = f
 63 | 		}
 64 | 		typedSlice = tempSlice
 65 | 	case "boolean":
 66 | 		tempSlice := make([]bool, len(s))
 67 | 		for j, item := range s {
 68 | 			b, ok := item.(bool)
 69 | 			if !ok {
 70 | 				return nil, fmt.Errorf("expected item at index %d to be boolean, got %T", j, item)
 71 | 			}
 72 | 			tempSlice[j] = b
 73 | 		}
 74 | 		typedSlice = tempSlice
 75 | 	}
 76 | 	return typedSlice, nil
 77 | }
 78 | 
 79 | // convertParamToJSON  is a Go template helper function to convert a parameter to JSON formatted string.
 80 | func convertParamToJSON(param any) (string, error) {
 81 | 	jsonData, err := json.Marshal(param)
 82 | 	if err != nil {
 83 | 		return "", fmt.Errorf("failed to marshal param to JSON: %w", err)
 84 | 	}
 85 | 	return string(jsonData), nil
 86 | }
 87 | 
 88 | // PopulateTemplateWithJSON populate a Go template with a custom `json` array formatter
 89 | func PopulateTemplateWithJSON(templateName, templateString string, data map[string]any) (string, error) {
 90 | 	return PopulateTemplateWithFunc(templateName, templateString, data, template.FuncMap{
 91 | 		"json": convertParamToJSON,
 92 | 	})
 93 | }
 94 | 
 95 | // PopulateTemplate populate a Go template with no custom formatters
 96 | func PopulateTemplate(templateName, templateString string, data map[string]any) (string, error) {
 97 | 	return PopulateTemplateWithFunc(templateName, templateString, data, nil)
 98 | }
 99 | 
100 | // PopulateTemplateWithFunc populate a Go template with provided functions
101 | func PopulateTemplateWithFunc(templateName, templateString string, data map[string]any, funcMap template.FuncMap) (string, error) {
102 | 	tmpl := template.New(templateName)
103 | 	if funcMap != nil {
104 | 		tmpl = tmpl.Funcs(funcMap)
105 | 	}
106 | 
107 | 	parsedTmpl, err := tmpl.Parse(templateString)
108 | 	if err != nil {
109 | 		return "", fmt.Errorf("error parsing template '%s': %w", templateName, err)
110 | 	}
111 | 
112 | 	var result bytes.Buffer
113 | 	if err := parsedTmpl.Execute(&result, data); err != nil {
114 | 		return "", fmt.Errorf("error executing template '%s': %w", templateName, err)
115 | 	}
116 | 	return result.String(), nil
117 | }
118 | 
119 | // CheckDuplicateParameters verify there are no duplicate parameter names
120 | func CheckDuplicateParameters(ps Parameters) error {
121 | 	seenNames := make(map[string]bool)
122 | 	for _, p := range ps {
123 | 		pName := p.GetName()
124 | 		if _, exists := seenNames[pName]; exists {
125 | 			return fmt.Errorf("parameter name must be unique across all parameter fields. Duplicate parameter: %s", pName)
126 | 		}
127 | 		seenNames[pName] = true
128 | 	}
129 | 	return nil
130 | }
131 | 
```

--------------------------------------------------------------------------------
/internal/tools/cloudmonitoring/cloudmonitoring_test.go:
--------------------------------------------------------------------------------

```go
  1 | // Copyright 2025 Google LLC
  2 | //
  3 | // Licensed under the Apache License, Version 2.0 (the "License");
  4 | // you may not use this file except in compliance with the License.
  5 | // You may obtain a copy of the License at
  6 | //
  7 | //     http://www.apache.org/licenses/LICENSE-2.0
  8 | //
  9 | // Unless required by applicable law or agreed to in writing, software
 10 | // distributed under the License is distributed on an "AS IS" BASIS,
 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12 | // See the License for the specific language governing permissions and
 13 | // limitations under the License.
 14 | 
 15 | package cloudmonitoring_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/cloudmonitoring"
 26 | )
 27 | 
 28 | func TestParseFromYamlCloudMonitoring(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: cloud-monitoring-query-prometheus
 44 | 					source: my-instance
 45 | 					description: some description
 46 | 				`,
 47 | 			want: server.ToolConfigs{
 48 | 				"example_tool": cloudmonitoring.Config{
 49 | 					Name:         "example_tool",
 50 | 					Kind:         "cloud-monitoring-query-prometheus",
 51 | 					Source:       "my-instance",
 52 | 					Description:  "some description",
 53 | 					AuthRequired: []string{},
 54 | 				},
 55 | 			},
 56 | 		},
 57 | 		{
 58 | 			desc: "advanced example",
 59 | 			in: `
 60 | 			tools:
 61 | 				example_tool:
 62 | 					kind: cloud-monitoring-query-prometheus
 63 | 					source: my-instance
 64 | 					description: some description
 65 | 					authRequired:
 66 | 						- my-google-auth-service
 67 | 						- other-auth-service
 68 | 			`,
 69 | 			want: server.ToolConfigs{
 70 | 				"example_tool": cloudmonitoring.Config{
 71 | 					Name:         "example_tool",
 72 | 					Kind:         "cloud-monitoring-query-prometheus",
 73 | 					Source:       "my-instance",
 74 | 					Description:  "some description",
 75 | 					AuthRequired: []string{"my-google-auth-service", "other-auth-service"},
 76 | 				},
 77 | 			},
 78 | 		},
 79 | 	}
 80 | 	for _, tc := range tcs {
 81 | 		t.Run(tc.desc, func(t *testing.T) {
 82 | 			got := struct {
 83 | 				Tools server.ToolConfigs `yaml:"tools"`
 84 | 			}{}
 85 | 			// Parse contents
 86 | 			err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
 87 | 			if err != nil {
 88 | 				t.Fatalf("unable to unmarshal: %s", err)
 89 | 			}
 90 | 			if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
 91 | 				t.Fatalf("incorrect parse: diff %v", diff)
 92 | 			}
 93 | 		})
 94 | 	}
 95 | }
 96 | 
 97 | func TestFailParseFromYamlCloudMonitoring(t *testing.T) {
 98 | 	ctx, err := testutils.ContextWithNewLogger()
 99 | 	if err != nil {
100 | 		t.Fatalf("unexpected error: %s", err)
101 | 	}
102 | 	tcs := []struct {
103 | 		desc string
104 | 		in   string
105 | 		err  string
106 | 	}{
107 | 		{
108 | 			desc: "Invalid kind",
109 | 			in: `
110 | 			tools:
111 | 				example_tool:
112 | 					kind: invalid-kind
113 | 					source: my-instance
114 | 					description: some description
115 | 			`,
116 | 			err: `unknown tool kind: "invalid-kind"`,
117 | 		},
118 | 		{
119 | 			desc: "missing source",
120 | 			in: `
121 | 			tools:
122 | 				example_tool:
123 | 					kind: cloud-monitoring-query-prometheus
124 | 					description: some description
125 | 			`,
126 | 			err: `Key: 'Config.Source' Error:Field validation for 'Source' failed on the 'required' tag`,
127 | 		},
128 | 		{
129 | 			desc: "missing description",
130 | 			in: `
131 | 			tools:
132 | 				example_tool:
133 | 					kind: cloud-monitoring-query-prometheus
134 | 					source: my-instance
135 | 			`,
136 | 			err: `Key: 'Config.Description' Error:Field validation for 'Description' failed on the 'required' tag`,
137 | 		},
138 | 	}
139 | 	for _, tc := range tcs {
140 | 		t.Run(tc.desc, func(t *testing.T) {
141 | 			got := struct {
142 | 				Tools server.ToolConfigs `yaml:"tools"`
143 | 			}{}
144 | 			// Parse contents
145 | 			err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
146 | 			if err == nil {
147 | 				t.Fatalf("expect parsing to fail")
148 | 			}
149 | 			errStr := err.Error()
150 | 			if !strings.Contains(errStr, tc.err) {
151 | 				t.Fatalf("unexpected error string: got %q, want substring %q", errStr, tc.err)
152 | 			}
153 | 		})
154 | 	}
155 | }
156 | 
```

--------------------------------------------------------------------------------
/internal/sources/cloudmonitoring/cloud_monitoring.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 cloudmonitoring
 15 | 
 16 | import (
 17 | 	"context"
 18 | 	"fmt"
 19 | 	"net/http"
 20 | 
 21 | 	"github.com/goccy/go-yaml"
 22 | 	"github.com/googleapis/genai-toolbox/internal/sources"
 23 | 	"github.com/googleapis/genai-toolbox/internal/util"
 24 | 	"go.opentelemetry.io/otel/trace"
 25 | 	"golang.org/x/oauth2"
 26 | 	"golang.org/x/oauth2/google"
 27 | 	monitoring "google.golang.org/api/monitoring/v3"
 28 | )
 29 | 
 30 | const SourceKind string = "cloud-monitoring"
 31 | 
 32 | type userAgentRoundTripper struct {
 33 | 	userAgent string
 34 | 	next      http.RoundTripper
 35 | }
 36 | 
 37 | func (rt *userAgentRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
 38 | 	newReq := *req
 39 | 	newReq.Header = make(http.Header)
 40 | 	for k, v := range req.Header {
 41 | 		newReq.Header[k] = v
 42 | 	}
 43 | 	ua := newReq.Header.Get("User-Agent")
 44 | 	if ua == "" {
 45 | 		newReq.Header.Set("User-Agent", rt.userAgent)
 46 | 	} else {
 47 | 		newReq.Header.Set("User-Agent", ua+" "+rt.userAgent)
 48 | 	}
 49 | 	return rt.next.RoundTrip(&newReq)
 50 | }
 51 | 
 52 | // validate interface
 53 | var _ sources.SourceConfig = Config{}
 54 | 
 55 | func init() {
 56 | 	if !sources.Register(SourceKind, newConfig) {
 57 | 		panic(fmt.Sprintf("source kind %q already registered", SourceKind))
 58 | 	}
 59 | }
 60 | 
 61 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) {
 62 | 	actual := Config{Name: name}
 63 | 	if err := decoder.DecodeContext(ctx, &actual); err != nil {
 64 | 		return nil, err
 65 | 	}
 66 | 	return actual, nil
 67 | }
 68 | 
 69 | type Config struct {
 70 | 	Name           string `yaml:"name" validate:"required"`
 71 | 	Kind           string `yaml:"kind" validate:"required"`
 72 | 	UseClientOAuth bool   `yaml:"useClientOAuth"`
 73 | }
 74 | 
 75 | func (r Config) SourceConfigKind() string {
 76 | 	return SourceKind
 77 | }
 78 | 
 79 | // Initialize initializes a Cloud Monitoring Source instance.
 80 | func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) {
 81 | 	ua, err := util.UserAgentFromContext(ctx)
 82 | 	if err != nil {
 83 | 		return nil, fmt.Errorf("error in User Agent retrieval: %s", err)
 84 | 	}
 85 | 
 86 | 	var client *http.Client
 87 | 	if r.UseClientOAuth {
 88 | 		client = &http.Client{
 89 | 			Transport: &userAgentRoundTripper{
 90 | 				userAgent: ua,
 91 | 				next:      http.DefaultTransport,
 92 | 			},
 93 | 		}
 94 | 	} else {
 95 | 		// Use Application Default Credentials
 96 | 		creds, err := google.FindDefaultCredentials(ctx, monitoring.MonitoringScope)
 97 | 		if err != nil {
 98 | 			return nil, fmt.Errorf("failed to find default credentials: %w", err)
 99 | 		}
100 | 		baseClient := oauth2.NewClient(ctx, creds.TokenSource)
101 | 		baseClient.Transport = &userAgentRoundTripper{
102 | 			userAgent: ua,
103 | 			next:      baseClient.Transport,
104 | 		}
105 | 		client = baseClient
106 | 	}
107 | 
108 | 	s := &Source{
109 | 		Name:           r.Name,
110 | 		Kind:           SourceKind,
111 | 		BaseURL:        "https://monitoring.googleapis.com",
112 | 		Client:         client,
113 | 		UserAgent:      ua,
114 | 		UseClientOAuth: r.UseClientOAuth,
115 | 	}
116 | 	return s, nil
117 | }
118 | 
119 | var _ sources.Source = &Source{}
120 | 
121 | type Source struct {
122 | 	Name           string `yaml:"name"`
123 | 	Kind           string `yaml:"kind"`
124 | 	BaseURL        string `yaml:"baseUrl"`
125 | 	Client         *http.Client
126 | 	UserAgent      string
127 | 	UseClientOAuth bool
128 | }
129 | 
130 | func (s *Source) SourceKind() string {
131 | 	return SourceKind
132 | }
133 | 
134 | func (s *Source) GetClient(ctx context.Context, accessToken string) (*http.Client, error) {
135 | 	if s.UseClientOAuth {
136 | 		if accessToken == "" {
137 | 			return nil, fmt.Errorf("client-side OAuth is enabled but no access token was provided")
138 | 		}
139 | 		token := &oauth2.Token{AccessToken: accessToken}
140 | 		return oauth2.NewClient(ctx, oauth2.StaticTokenSource(token)), nil
141 | 	}
142 | 	return s.Client, nil
143 | }
144 | 
145 | func (s *Source) UseClientAuthorization() bool {
146 | 	return s.UseClientOAuth
147 | }
148 | 
```

--------------------------------------------------------------------------------
/docs/en/resources/tools/mongodb/mongodb-find.md:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | title: "mongodb-find"
 3 | type: docs
 4 | weight: 1
 5 | description: > 
 6 |   A "mongodb-find" tool finds and retrieves documents from a MongoDB collection.
 7 | aliases:
 8 | - /resources/tools/mongodb-find
 9 | ---
10 | 
11 | ## About
12 | 
13 | A `mongodb-find` tool is used to query a MongoDB collection and retrieve
14 | documents that match a specified filter. It's a flexible tool that allows you to
15 | shape the output by selecting specific fields (**projection**), ordering the
16 | results (**sorting**), and restricting the number of documents returned
17 | (**limiting**).
18 | 
19 | The tool returns a JSON array of the documents found.
20 | 
21 | This tool is compatible with the following source kind:
22 | 
23 | * [`mongodb`](../../sources/mongodb.md)
24 | 
25 | ## Example
26 | 
27 | Here's an example that finds up to 10 users from the `customers` collection who
28 | live in a specific city. The results are sorted by their last name, and only
29 | their first name, last name, and email are returned.
30 | 
31 | ```yaml
32 | tools:
33 |   find_local_customers:
34 |     kind: mongodb-find
35 |     source: my-mongo-source
36 |     description: Finds customers by city, sorted by last name.
37 |     database: crm
38 |     collection: customers
39 |     limit: 10
40 |     filterPayload: |
41 |         { "address.city": {{json .city}} }
42 |     filterParams:
43 |       - name: city
44 |         type: string
45 |         description: The city to search for customers in.
46 |     projectPayload: |
47 |         { 
48 |           "first_name": 1,
49 |           "last_name": 1,
50 |           "email": 1,
51 |           "_id": 0
52 |         }
53 |     sortPayload: |
54 |         { "last_name": {{json .sort_order}} }
55 |     sortParams:
56 |       - name: sort_order
57 |         type: integer
58 |         description: The sort order (1 for ascending, -1 for descending).
59 | ```
60 | 
61 | ## Reference
62 | 
63 | | **field**      | **type** | **required** | **description**                                                                                                             |
64 | |:---------------|:---------|:-------------|:----------------------------------------------------------------------------------------------------------------------------|
65 | | kind           | string   | true         | Must be `mongodb-find`.                                                                                                     |
66 | | source         | string   | true         | The name of the `mongodb` source to use.                                                                                    |
67 | | description    | string   | true         | A description of the tool that is passed to the LLM.                                                                        |
68 | | database       | string   | true         | The name of the MongoDB database to query.                                                                                  |
69 | | collection     | string   | true         | The name of the MongoDB collection to query.                                                                                |
70 | | filterPayload  | string   | true         | The MongoDB query filter document to select which documents to return. Uses `{{json .param_name}}` for templating.          |
71 | | filterParams   | list     | true         | A list of parameter objects that define the variables used in the `filterPayload`.                                          |
72 | | projectPayload | string   | false        | An optional MongoDB projection document to specify which fields to include (1) or exclude (0) in the results.               |
73 | | projectParams  | list     | false        | A list of parameter objects for the `projectPayload`.                                                                       |
74 | | sortPayload    | string   | false        | An optional MongoDB sort document to define the order of the returned documents. Use 1 for ascending and -1 for descending. |
75 | | sortParams     | list     | false        | A list of parameter objects for the `sortPayload`.                                                                          |
76 | | limit          | integer  | false        | An optional integer specifying the maximum number of documents to return.                                                   |
77 | 
```

--------------------------------------------------------------------------------
/internal/sources/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 | 	"testing"
 19 | 
 20 | 	yaml "github.com/goccy/go-yaml"
 21 | 	"github.com/google/go-cmp/cmp"
 22 | 	"github.com/googleapis/genai-toolbox/internal/server"
 23 | 	"github.com/googleapis/genai-toolbox/internal/sources"
 24 | 	"github.com/googleapis/genai-toolbox/internal/sources/http"
 25 | 	"github.com/googleapis/genai-toolbox/internal/testutils"
 26 | )
 27 | 
 28 | func TestParseFromYamlHttp(t *testing.T) {
 29 | 	tcs := []struct {
 30 | 		desc string
 31 | 		in   string
 32 | 		want server.SourceConfigs
 33 | 	}{
 34 | 		{
 35 | 			desc: "basic example",
 36 | 			in: `
 37 | 			sources:
 38 | 				my-http-instance:
 39 | 					kind: http
 40 | 					baseUrl: http://test_server/
 41 | 			`,
 42 | 			want: map[string]sources.SourceConfig{
 43 | 				"my-http-instance": http.Config{
 44 | 					Name:                   "my-http-instance",
 45 | 					Kind:                   http.SourceKind,
 46 | 					BaseURL:                "http://test_server/",
 47 | 					Timeout:                "30s",
 48 | 					DisableSslVerification: false,
 49 | 				},
 50 | 			},
 51 | 		},
 52 | 		{
 53 | 			desc: "advanced example",
 54 | 			in: `
 55 | 			sources:
 56 | 				my-http-instance:
 57 | 					kind: http
 58 | 					baseUrl: http://test_server/
 59 | 					timeout: 10s
 60 | 					headers:
 61 | 						Authorization: test_header
 62 | 						Custom-Header: custom
 63 | 					queryParams:
 64 | 						api-key: test_api_key
 65 | 						param: param-value
 66 | 					disableSslVerification: true
 67 | 			`,
 68 | 			want: map[string]sources.SourceConfig{
 69 | 				"my-http-instance": http.Config{
 70 | 					Name:                   "my-http-instance",
 71 | 					Kind:                   http.SourceKind,
 72 | 					BaseURL:                "http://test_server/",
 73 | 					Timeout:                "10s",
 74 | 					DefaultHeaders:         map[string]string{"Authorization": "test_header", "Custom-Header": "custom"},
 75 | 					QueryParams:            map[string]string{"api-key": "test_api_key", "param": "param-value"},
 76 | 					DisableSslVerification: true,
 77 | 				},
 78 | 			},
 79 | 		},
 80 | 	}
 81 | 	for _, tc := range tcs {
 82 | 		t.Run(tc.desc, func(t *testing.T) {
 83 | 			got := struct {
 84 | 				Sources server.SourceConfigs `yaml:"sources"`
 85 | 			}{}
 86 | 			// Parse contents
 87 | 			err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
 88 | 			if err != nil {
 89 | 				t.Fatalf("unable to unmarshal: %s", err)
 90 | 			}
 91 | 			if !cmp.Equal(tc.want, got.Sources) {
 92 | 				t.Fatalf("incorrect parse: want %v, got %v", tc.want, got.Sources)
 93 | 			}
 94 | 		})
 95 | 	}
 96 | }
 97 | 
 98 | func TestFailParseFromYaml(t *testing.T) {
 99 | 	tcs := []struct {
100 | 		desc string
101 | 		in   string
102 | 		err  string
103 | 	}{
104 | 		{
105 | 			desc: "extra field",
106 | 			in: `
107 | 			sources:
108 | 				my-http-instance:
109 | 					kind: http
110 | 					baseUrl: http://test_server/
111 | 					timeout: 10s
112 | 					headers:
113 | 						Authorization: test_header
114 | 					queryParams:
115 | 						api-key: test_api_key
116 | 					project: test-project
117 | 			`,
118 | 			err: "unable to parse source \"my-http-instance\" as \"http\": [5:1] unknown field \"project\"\n   2 | headers:\n   3 |   Authorization: test_header\n   4 | kind: http\n>  5 | project: test-project\n       ^\n   6 | queryParams:\n   7 |   api-key: test_api_key\n   8 | timeout: 10s",
119 | 		},
120 | 		{
121 | 			desc: "missing required field",
122 | 			in: `
123 | 			sources:
124 | 				my-http-instance:
125 | 					baseUrl: http://test_server/
126 | 			`,
127 | 			err: "missing 'kind' field for source \"my-http-instance\"",
128 | 		},
129 | 	}
130 | 	for _, tc := range tcs {
131 | 		t.Run(tc.desc, func(t *testing.T) {
132 | 			got := struct {
133 | 				Sources server.SourceConfigs `yaml:"sources"`
134 | 			}{}
135 | 			// Parse contents
136 | 			err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
137 | 			if err == nil {
138 | 				t.Fatalf("expect parsing to fail")
139 | 			}
140 | 			errStr := err.Error()
141 | 			if errStr != tc.err {
142 | 				t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err)
143 | 			}
144 | 		})
145 | 	}
146 | }
147 | 
```

--------------------------------------------------------------------------------
/internal/tools/alloydbainl/alloydbainl_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 alloydbainl_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/alloydbainl"
 26 | )
 27 | 
 28 | func TestParseFromYamlAlloyDBNLA(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: alloydb-ai-nl
 44 | 					source: my-alloydb-instance
 45 | 					description: AlloyDB natural language query tool
 46 | 					nlConfig: 'my_nl_config'
 47 | 					authRequired:
 48 | 						- my-google-auth-service
 49 | 					nlConfigParameters:
 50 | 						- name: user_id
 51 | 						  type: string
 52 | 						  description: user_id to use
 53 | 						  authServices:
 54 | 							- name: my-google-auth-service
 55 | 							  field: sub
 56 | 			`,
 57 | 			want: server.ToolConfigs{
 58 | 				"example_tool": alloydbainl.Config{
 59 | 					Name:         "example_tool",
 60 | 					Kind:         "alloydb-ai-nl",
 61 | 					Source:       "my-alloydb-instance",
 62 | 					Description:  "AlloyDB natural language query tool",
 63 | 					NLConfig:     "my_nl_config",
 64 | 					AuthRequired: []string{"my-google-auth-service"},
 65 | 					NLConfigParameters: []tools.Parameter{
 66 | 						tools.NewStringParameterWithAuth("user_id", "user_id to use",
 67 | 							[]tools.ParamAuthService{{Name: "my-google-auth-service", Field: "sub"}}),
 68 | 					},
 69 | 				},
 70 | 			},
 71 | 		},
 72 | 		{
 73 | 			desc: "with multiple parameters",
 74 | 			in: `
 75 | 			tools:
 76 | 				complex_tool:
 77 | 					kind: alloydb-ai-nl
 78 | 					source: my-alloydb-instance
 79 | 					description: AlloyDB natural language query tool with multiple parameters
 80 | 					nlConfig: 'complex_nl_config'
 81 | 					authRequired:
 82 | 						- my-google-auth-service
 83 | 						- other-auth-service
 84 | 					nlConfigParameters:
 85 | 						- name: user_id
 86 | 						  type: string
 87 | 						  description: user_id to use
 88 | 						  authServices:
 89 | 							- name: my-google-auth-service
 90 | 							  field: sub
 91 | 						- name: user_email
 92 | 						  type: string
 93 | 						  description: user_email to use
 94 | 						  authServices:
 95 | 							- name: my-google-auth-service
 96 | 							  field: user_email
 97 | 			`,
 98 | 			want: server.ToolConfigs{
 99 | 				"complex_tool": alloydbainl.Config{
100 | 					Name:         "complex_tool",
101 | 					Kind:         "alloydb-ai-nl",
102 | 					Source:       "my-alloydb-instance",
103 | 					Description:  "AlloyDB natural language query tool with multiple parameters",
104 | 					NLConfig:     "complex_nl_config",
105 | 					AuthRequired: []string{"my-google-auth-service", "other-auth-service"},
106 | 					NLConfigParameters: []tools.Parameter{
107 | 						tools.NewStringParameterWithAuth("user_id", "user_id to use",
108 | 							[]tools.ParamAuthService{{Name: "my-google-auth-service", Field: "sub"}}),
109 | 						tools.NewStringParameterWithAuth("user_email", "user_email to use",
110 | 							[]tools.ParamAuthService{{Name: "my-google-auth-service", Field: "user_email"}}),
111 | 					},
112 | 				},
113 | 			},
114 | 		},
115 | 	}
116 | 	for _, tc := range tcs {
117 | 		t.Run(tc.desc, func(t *testing.T) {
118 | 			got := struct {
119 | 				Tools server.ToolConfigs `yaml:"tools"`
120 | 			}{}
121 | 			// Parse contents
122 | 			err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
123 | 			if err != nil {
124 | 				t.Fatalf("unable to unmarshal: %s", err)
125 | 			}
126 | 			if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
127 | 				t.Fatalf("incorrect parse: diff %v", diff)
128 | 			}
129 | 		})
130 | 	}
131 | }
132 | 
```

--------------------------------------------------------------------------------
/tests/redis/redis_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 redis
 16 | 
 17 | import (
 18 | 	"context"
 19 | 	"fmt"
 20 | 	"os"
 21 | 	"regexp"
 22 | 	"testing"
 23 | 	"time"
 24 | 
 25 | 	"github.com/googleapis/genai-toolbox/internal/testutils"
 26 | 	"github.com/googleapis/genai-toolbox/tests"
 27 | 	"github.com/redis/go-redis/v9"
 28 | )
 29 | 
 30 | var (
 31 | 	RedisSourceKind = "redis"
 32 | 	RedisToolKind   = "redis"
 33 | 	RedisAddress    = os.Getenv("REDIS_ADDRESS")
 34 | 	RedisPass       = os.Getenv("REDIS_PASS")
 35 | )
 36 | 
 37 | func getRedisVars(t *testing.T) map[string]any {
 38 | 	switch "" {
 39 | 	case RedisAddress:
 40 | 		t.Fatal("'REDIS_ADDRESS' not set")
 41 | 	case RedisPass:
 42 | 		t.Fatal("'REDIS_PASS' not set")
 43 | 	}
 44 | 	return map[string]any{
 45 | 		"kind":     RedisSourceKind,
 46 | 		"address":  []string{RedisAddress},
 47 | 		"password": RedisPass,
 48 | 	}
 49 | }
 50 | 
 51 | func initRedisClient(ctx context.Context, address, pass string) (*redis.Client, error) {
 52 | 	// Create a new Redis client
 53 | 	standaloneClient := redis.NewClient(&redis.Options{
 54 | 		Addr:            address,
 55 | 		PoolSize:        10,
 56 | 		ConnMaxIdleTime: 60 * time.Second,
 57 | 		MinIdleConns:    1,
 58 | 		Password:        pass,
 59 | 	})
 60 | 	_, err := standaloneClient.Ping(ctx).Result()
 61 | 	if err != nil {
 62 | 		return nil, fmt.Errorf("unable to connect to redis: %s", err)
 63 | 	}
 64 | 	return standaloneClient, nil
 65 | }
 66 | 
 67 | func TestRedisToolEndpoints(t *testing.T) {
 68 | 	sourceConfig := getRedisVars(t)
 69 | 	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
 70 | 	defer cancel()
 71 | 
 72 | 	var args []string
 73 | 
 74 | 	client, err := initRedisClient(ctx, RedisAddress, RedisPass)
 75 | 	if err != nil {
 76 | 		t.Fatalf("unable to create Redis connection: %s", err)
 77 | 	}
 78 | 
 79 | 	// set up data for param tool
 80 | 	teardownDB := setupRedisDB(t, ctx, client)
 81 | 	defer teardownDB(t)
 82 | 
 83 | 	// Write config into a file and pass it to command
 84 | 	toolsFile := tests.GetRedisValkeyToolsConfig(sourceConfig, RedisToolKind)
 85 | 
 86 | 	cmd, cleanup, err := tests.StartCmd(ctx, toolsFile, args...)
 87 | 	if err != nil {
 88 | 		t.Fatalf("command initialization returned an error: %s", err)
 89 | 	}
 90 | 	defer cleanup()
 91 | 
 92 | 	waitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
 93 | 	defer cancel()
 94 | 	out, err := testutils.WaitForString(waitCtx, regexp.MustCompile(`Server ready to serve`), cmd.Out)
 95 | 	if err != nil {
 96 | 		t.Logf("toolbox command logs: \n%s", out)
 97 | 		t.Fatalf("toolbox didn't start successfully: %s", err)
 98 | 	}
 99 | 
100 | 	// Get configs for tests
101 | 	select1Want, mcpMyFailToolWant, invokeParamWant, invokeIdNullWant, nullWant, mcpSelect1Want, mcpInvokeParamWant := tests.GetRedisValkeyWants()
102 | 
103 | 	// Run tests
104 | 	tests.RunToolGetTest(t)
105 | 	tests.RunToolInvokeTest(t, select1Want,
106 | 		tests.WithMyToolId3NameAliceWant(invokeParamWant),
107 | 		tests.WithMyArrayToolWant(invokeParamWant),
108 | 		tests.WithMyToolById4Want(invokeIdNullWant),
109 | 		tests.WithNullWant(nullWant),
110 | 	)
111 | 	tests.RunMCPToolCallMethod(t, mcpMyFailToolWant, mcpSelect1Want,
112 | 		tests.WithMcpMyToolId3NameAliceWant(mcpInvokeParamWant),
113 | 	)
114 | }
115 | 
116 | func setupRedisDB(t *testing.T, ctx context.Context, client *redis.Client) func(*testing.T) {
117 | 	keys := []string{"row1", "row2", "row3", "row4", "null"}
118 | 	commands := [][]any{
119 | 		{"HSET", keys[0], "id", 1, "name", "Alice"},
120 | 		{"HSET", keys[1], "id", 2, "name", "Jane"},
121 | 		{"HSET", keys[2], "id", 3, "name", "Sid"},
122 | 		{"HSET", keys[3], "id", 4, "name", nil},
123 | 		{"SET", keys[4], "null"},
124 | 		{"HSET", tests.ServiceAccountEmail, "name", "Alice"},
125 | 	}
126 | 	for _, c := range commands {
127 | 		resp := client.Do(ctx, c...)
128 | 		if err := resp.Err(); err != nil {
129 | 			t.Fatalf("unable to insert test data: %s", err)
130 | 		}
131 | 	}
132 | 
133 | 	return func(t *testing.T) {
134 | 		// tear down test
135 | 		_, err := client.Del(ctx, keys...).Result()
136 | 		if err != nil {
137 | 			t.Errorf("Teardown failed: %s", err)
138 | 		}
139 | 	}
140 | 
141 | }
142 | 
```

--------------------------------------------------------------------------------
/internal/server/mcp/jsonrpc/jsonrpc.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 jsonrpc
 16 | 
 17 | // JSONRPC_VERSION is the version of JSON-RPC used by MCP.
 18 | const JSONRPC_VERSION = "2.0"
 19 | 
 20 | // Standard JSON-RPC error codes
 21 | const (
 22 | 	PARSE_ERROR      = -32700
 23 | 	INVALID_REQUEST  = -32600
 24 | 	METHOD_NOT_FOUND = -32601
 25 | 	INVALID_PARAMS   = -32602
 26 | 	INTERNAL_ERROR   = -32603
 27 | )
 28 | 
 29 | // ProgressToken is used to associate progress notifications with the original request.
 30 | type ProgressToken interface{}
 31 | 
 32 | // RequestId is a uniquely identifying ID for a request in JSON-RPC.
 33 | // It can be any JSON-serializable value, typically a number or string.
 34 | type RequestId interface{}
 35 | 
 36 | // Request represents a bidirectional message with method and parameters expecting a response.
 37 | type Request struct {
 38 | 	Method string `json:"method"`
 39 | 	Params struct {
 40 | 		Meta struct {
 41 | 			// If specified, the caller is requesting out-of-band progress
 42 | 			// notifications for this request (as represented by
 43 | 			// notifications/progress). The value of this parameter is an
 44 | 			// opaque token that will be attached to any subsequent
 45 | 			// notifications. The receiver is not obligated to provide these
 46 | 			// notifications.
 47 | 			ProgressToken ProgressToken `json:"progressToken,omitempty"`
 48 | 		} `json:"_meta,omitempty"`
 49 | 	} `json:"params,omitempty"`
 50 | }
 51 | 
 52 | // JSONRPCRequest represents a request that expects a response.
 53 | type JSONRPCRequest struct {
 54 | 	Jsonrpc string    `json:"jsonrpc"`
 55 | 	Id      RequestId `json:"id"`
 56 | 	Request
 57 | 	Params any `json:"params,omitempty"`
 58 | }
 59 | 
 60 | // Notification is a one-way message requiring no response.
 61 | type Notification struct {
 62 | 	Method string `json:"method"`
 63 | 	Params struct {
 64 | 		Meta map[string]interface{} `json:"_meta,omitempty"`
 65 | 	} `json:"params,omitempty"`
 66 | }
 67 | 
 68 | // JSONRPCNotification represents a notification which does not expect a response.
 69 | type JSONRPCNotification struct {
 70 | 	Jsonrpc string `json:"jsonrpc"`
 71 | 	Notification
 72 | }
 73 | 
 74 | // Result represents a response for the request query.
 75 | type Result struct {
 76 | 	// This result property is reserved by the protocol to allow clients and
 77 | 	// servers to attach additional metadata to their responses.
 78 | 	Meta map[string]interface{} `json:"_meta,omitempty"`
 79 | }
 80 | 
 81 | // JSONRPCResponse represents a successful (non-error) response to a request.
 82 | type JSONRPCResponse struct {
 83 | 	Jsonrpc string      `json:"jsonrpc"`
 84 | 	Id      RequestId   `json:"id"`
 85 | 	Result  interface{} `json:"result"`
 86 | }
 87 | 
 88 | // Error represents the error content.
 89 | type Error struct {
 90 | 	// The error type that occurred.
 91 | 	Code int `json:"code"`
 92 | 	// A short description of the error. The message SHOULD be limited
 93 | 	// to a concise single sentence.
 94 | 	Message string `json:"message"`
 95 | 	// Additional information about the error. The value of this member
 96 | 	// is defined by the sender (e.g. detailed error information, nested errors etc.).
 97 | 	Data interface{} `json:"data,omitempty"`
 98 | }
 99 | 
100 | // JSONRPCError represents a non-successful (error) response to a request.
101 | type JSONRPCError struct {
102 | 	Jsonrpc string    `json:"jsonrpc"`
103 | 	Id      RequestId `json:"id"`
104 | 	Error   Error     `json:"error"`
105 | }
106 | 
107 | // Generic baseMessage could either be a JSONRPCNotification or JSONRPCRequest
108 | type BaseMessage struct {
109 | 	Jsonrpc string    `json:"jsonrpc"`
110 | 	Method  string    `json:"method"`
111 | 	Id      RequestId `json:"id,omitempty"`
112 | }
113 | 
114 | // NewError is the standard JSONRPC response sent back when an error has been encountered.
115 | func NewError(id RequestId, code int, message string, data any) JSONRPCError {
116 | 	return JSONRPCError{
117 | 		Jsonrpc: JSONRPC_VERSION,
118 | 		Id:      id,
119 | 		Error: Error{
120 | 			Code:    code,
121 | 			Message: message,
122 | 			Data:    data,
123 | 		},
124 | 	}
125 | }
126 | 
```

--------------------------------------------------------------------------------
/internal/sources/mysql/mysql.go:
--------------------------------------------------------------------------------

```go
  1 | // Copyright 2025 Google LLC
  2 | //
  3 | // Licensed under the Apache License, Version 2.0 (the "License");
  4 | // you may not use this file except in compliance with the License.
  5 | // You may obtain a copy of the License at
  6 | //
  7 | //     http://www.apache.org/licenses/LICENSE-2.0
  8 | //
  9 | // Unless required by applicable law or agreed to in writing, software
 10 | // distributed under the License is distributed on an "AS IS" BASIS,
 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12 | // See the License for the specific language governing permissions and
 13 | // limitations under the License.
 14 | 
 15 | package mysql
 16 | 
 17 | import (
 18 | 	"context"
 19 | 	"database/sql"
 20 | 	"fmt"
 21 | 	"net/url"
 22 | 	"time"
 23 | 
 24 | 	_ "github.com/go-sql-driver/mysql"
 25 | 	"github.com/goccy/go-yaml"
 26 | 	"github.com/googleapis/genai-toolbox/internal/sources"
 27 | 	"github.com/googleapis/genai-toolbox/internal/util"
 28 | 	"go.opentelemetry.io/otel/trace"
 29 | )
 30 | 
 31 | const SourceKind string = "mysql"
 32 | 
 33 | // validate interface
 34 | var _ sources.SourceConfig = Config{}
 35 | 
 36 | func init() {
 37 | 	if !sources.Register(SourceKind, newConfig) {
 38 | 		panic(fmt.Sprintf("source kind %q already registered", SourceKind))
 39 | 	}
 40 | }
 41 | 
 42 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) {
 43 | 	actual := Config{Name: name}
 44 | 	if err := decoder.DecodeContext(ctx, &actual); err != nil {
 45 | 		return nil, err
 46 | 	}
 47 | 	return actual, nil
 48 | }
 49 | 
 50 | type Config struct {
 51 | 	Name         string            `yaml:"name" validate:"required"`
 52 | 	Kind         string            `yaml:"kind" validate:"required"`
 53 | 	Host         string            `yaml:"host" validate:"required"`
 54 | 	Port         string            `yaml:"port" validate:"required"`
 55 | 	User         string            `yaml:"user" validate:"required"`
 56 | 	Password     string            `yaml:"password" validate:"required"`
 57 | 	Database     string            `yaml:"database" validate:"required"`
 58 | 	QueryTimeout string            `yaml:"queryTimeout"`
 59 | 	QueryParams  map[string]string `yaml:"queryParams"`
 60 | }
 61 | 
 62 | func (r Config) SourceConfigKind() string {
 63 | 	return SourceKind
 64 | }
 65 | 
 66 | func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) {
 67 | 	pool, err := initMySQLConnectionPool(ctx, tracer, r.Name, r.Host, r.Port, r.User, r.Password, r.Database, r.QueryTimeout, r.QueryParams)
 68 | 	if err != nil {
 69 | 		return nil, fmt.Errorf("unable to create pool: %w", err)
 70 | 	}
 71 | 
 72 | 	err = pool.PingContext(ctx)
 73 | 	if err != nil {
 74 | 		return nil, fmt.Errorf("unable to connect successfully: %w", err)
 75 | 	}
 76 | 
 77 | 	s := &Source{
 78 | 		Name: r.Name,
 79 | 		Kind: SourceKind,
 80 | 		Pool: pool,
 81 | 	}
 82 | 	return s, nil
 83 | }
 84 | 
 85 | var _ sources.Source = &Source{}
 86 | 
 87 | type Source struct {
 88 | 	Name string `yaml:"name"`
 89 | 	Kind string `yaml:"kind"`
 90 | 	Pool *sql.DB
 91 | }
 92 | 
 93 | func (s *Source) SourceKind() string {
 94 | 	return SourceKind
 95 | }
 96 | 
 97 | func (s *Source) MySQLPool() *sql.DB {
 98 | 	return s.Pool
 99 | }
100 | 
101 | func initMySQLConnectionPool(ctx context.Context, tracer trace.Tracer, name, host, port, user, pass, dbname, queryTimeout string, queryParams map[string]string) (*sql.DB, error) {
102 | 	//nolint:all // Reassigned ctx
103 | 	ctx, span := sources.InitConnectionSpan(ctx, tracer, SourceKind, name)
104 | 	defer span.End()
105 | 
106 | 	// Build query parameters via url.Values for deterministic order and proper escaping.
107 | 	values := url.Values{}
108 | 
109 | 	// Derive readTimeout from queryTimeout when provided.
110 | 	if queryTimeout != "" {
111 | 		timeout, err := time.ParseDuration(queryTimeout)
112 | 		if err != nil {
113 | 			return nil, fmt.Errorf("invalid queryTimeout %q: %w", queryTimeout, err)
114 | 		}
115 | 		values.Set("readTimeout", timeout.String())
116 | 	}
117 | 
118 | 	// Custom user parameters
119 | 	for k, v := range queryParams {
120 | 		if v == "" {
121 | 			continue // skip empty values
122 | 		}
123 | 		values.Set(k, v)
124 | 	}
125 | 
126 | 	userAgent, err := util.UserAgentFromContext(ctx)
127 | 	if err != nil {
128 | 		return nil, err
129 | 	}
130 | 	dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?parseTime=true&connectionAttributes=program_name:%s", user, pass, host, port, dbname, url.QueryEscape(userAgent))
131 | 	if enc := values.Encode(); enc != "" {
132 | 		dsn += "&" + enc
133 | 	}
134 | 
135 | 	// Interact with the driver directly as you normally would
136 | 	pool, err := sql.Open("mysql", dsn)
137 | 	if err != nil {
138 | 		return nil, fmt.Errorf("sql.Open: %w", err)
139 | 	}
140 | 	return pool, nil
141 | }
142 | 
```

--------------------------------------------------------------------------------
/.github/workflows/tests.yaml:
--------------------------------------------------------------------------------

```yaml
  1 | # Copyright 2024 Google LLC
  2 | #
  3 | # Licensed under the Apache License, Version 2.0 (the "License");
  4 | # you may not use this file except in compliance with the License.
  5 | # You may obtain a copy of the License at
  6 | #
  7 | #      http://www.apache.org/licenses/LICENSE-2.0
  8 | #
  9 | # Unless required by applicable law or agreed to in writing, software
 10 | # distributed under the License is distributed on an "AS IS" BASIS,
 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12 | # See the License for the specific language governing permissions and
 13 | # limitations under the License.
 14 | 
 15 | name: tests
 16 | on:
 17 |   push:
 18 |     branches:
 19 |       - "main"
 20 |   pull_request:
 21 |   pull_request_target:
 22 |     types: [labeled]
 23 | 
 24 | # Declare default permissions as read only.
 25 | permissions: read-all
 26 | 
 27 | jobs:
 28 |   integration:
 29 |     # run job on proper workflow event triggers (skip job for pull_request event from forks and only run pull_request_target for "tests: run" label)
 30 |     if: "${{ (github.event.action != 'labeled' && github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name) || github.event.label.name == 'tests: run' }}"
 31 |     name: unit tests
 32 |     runs-on: ${{ matrix.os }}
 33 |     strategy:
 34 |       matrix:
 35 |         os: [macos-latest, windows-latest, ubuntu-latest]
 36 |       fail-fast: false
 37 |     permissions:
 38 |       contents: "read"
 39 |       issues: "write"
 40 |       pull-requests: "write"
 41 |     steps:
 42 |       - name: Remove PR label
 43 |         if: "${{ github.event.action == 'labeled' && github.event.label.name == 'tests: run' }}"
 44 |         uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
 45 |         with:
 46 |           github-token: ${{ secrets.GITHUB_TOKEN }}
 47 |           script: |
 48 |             try {
 49 |               await github.rest.issues.removeLabel({
 50 |                 name: 'tests: run',
 51 |                 owner: context.repo.owner,
 52 |                 repo: context.repo.repo,
 53 |                 issue_number: context.payload.pull_request.number
 54 |               });
 55 |             } catch (e) {
 56 |               console.log('Failed to remove label. Another job may have already removed it!');
 57 |             }
 58 | 
 59 |       - name: Setup Go
 60 |         uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
 61 |         with:
 62 |           go-version: "1.24"
 63 | 
 64 |       - name: Checkout code
 65 |         uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
 66 |         with:
 67 |           ref: ${{ github.event.pull_request.head.sha }}
 68 |           repository: ${{ github.event.pull_request.head.repo.full_name }}
 69 |           token: ${{ secrets.GITHUB_TOKEN }}
 70 | 
 71 |       - name: Install dependencies
 72 |         run: go get .
 73 | 
 74 |       - name: Build
 75 |         run: go build -v ./...
 76 | 
 77 |       - name: Run tests with coverage
 78 |         if: ${{ runner.os == 'Linux' }}
 79 |         env:
 80 |           GOTOOLCHAIN: go1.25.0+auto
 81 |         run: |
 82 |           source_dir="./internal/sources/*"
 83 |           tool_dir="./internal/tools/*"
 84 |           auth_dir="./internal/auth/*"
 85 |           int_test_dir="./tests/*"
 86 |           included_packages=$(go list ./... | grep -v -e "$source_dir" -e "$tool_dir" -e "$auth_dir" -e "$int_test_dir")
 87 |           go test -race -cover -coverprofile=coverage.out -v $included_packages
 88 |           go test -race -v ./internal/sources/... ./internal/tools/... ./internal/auth/...
 89 | 
 90 |       - name: Run tests without coverage
 91 |         if: ${{ runner.os != 'Linux' }}
 92 |         run: |
 93 |           go test -race -v ./internal/... ./cmd/...
 94 | 
 95 |       - name: Check coverage
 96 |         if: ${{ runner.os == 'Linux' }}
 97 |         run: |
 98 |           FILE_TO_EXCLUDE="github.com/googleapis/genai-toolbox/internal/server/config.go"
 99 |           ESCAPED_PATH=$(echo "$FILE_TO_EXCLUDE" | sed 's/\//\\\//g; s/\./\\\./g')
100 |           sed -i "/^${ESCAPED_PATH}:/d" coverage.out
101 |           total_coverage=$(go tool cover -func=coverage.out | grep "total:" | awk '{print $3}')
102 |           echo "Total coverage: $total_coverage"
103 |           coverage_numeric=$(echo "$total_coverage" | sed 's/%//')
104 |           if (( $(echo "$coverage_numeric < 40" | bc -l) )); then
105 |               echo "Coverage failure: total coverage($total_coverage) is below 40%."
106 |               exit 1
107 |           fi
108 | 
```

--------------------------------------------------------------------------------
/tests/dataform/dataform_integration_test.go:
--------------------------------------------------------------------------------

```go
  1 | // Copyright 2025 Google LLC
  2 | //
  3 | // Licensed under the Apache License, Version 2.0 (the "License");
  4 | // you may not use this file except in compliance with the License.
  5 | // You may obtain a copy of the License at
  6 | //
  7 | //	http://www.apache.org/licenses/LICENSE-2.0
  8 | //
  9 | // Unless required by applicable law or agreed to in writing, software
 10 | // distributed under the License is distributed on an "AS IS" BASIS,
 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12 | // See the License for the specific language governing permissions and
 13 | // limitations under the License.
 14 | 
 15 | package dataformcompilelocal
 16 | 
 17 | import (
 18 | 	"context"
 19 | 	"fmt"
 20 | 	"net/http"
 21 | 	"os"
 22 | 	"os/exec"
 23 | 	"path/filepath"
 24 | 	"regexp"
 25 | 	"strings"
 26 | 	"testing"
 27 | 	"time"
 28 | 
 29 | 	"github.com/googleapis/genai-toolbox/internal/testutils"
 30 | 	"github.com/googleapis/genai-toolbox/tests"
 31 | )
 32 | 
 33 | // setupTestProject creates a minimal dataform project using the 'dataform init' CLI.
 34 | // It returns the path to the directory and a cleanup function.
 35 | func setupTestProject(t *testing.T) (string, func()) {
 36 | 	tmpDir, err := os.MkdirTemp("", "dataform-project-*")
 37 | 	if err != nil {
 38 | 		t.Fatalf("Failed to create temp dir: %v", err)
 39 | 	}
 40 | 	cleanup := func() {
 41 | 		os.RemoveAll(tmpDir)
 42 | 	}
 43 | 
 44 | 	cmd := exec.Command("dataform", "init", tmpDir, "test-project-id", "US")
 45 | 	if output, err := cmd.CombinedOutput(); err != nil {
 46 | 		cleanup()
 47 | 		t.Fatalf("Failed to run 'dataform init': %v\nOutput: %s", err, string(output))
 48 | 	}
 49 | 
 50 | 	definitionsDir := filepath.Join(tmpDir, "definitions")
 51 | 	exampleSQLX := `config { type: "table" } SELECT 1 AS test_col`
 52 | 	err = os.WriteFile(filepath.Join(definitionsDir, "example.sqlx"), []byte(exampleSQLX), 0644)
 53 | 	if err != nil {
 54 | 		cleanup()
 55 | 		t.Fatalf("Failed to write example.sqlx: %v", err)
 56 | 	}
 57 | 
 58 | 	return tmpDir, cleanup
 59 | }
 60 | 
 61 | func TestDataformCompileTool(t *testing.T) {
 62 | 	if _, err := exec.LookPath("dataform"); err != nil {
 63 | 		t.Skip("dataform CLI not found in $PATH, skipping integration test")
 64 | 	}
 65 | 
 66 | 	projectDir, cleanupProject := setupTestProject(t)
 67 | 	defer cleanupProject()
 68 | 
 69 | 	toolsFile := map[string]any{
 70 | 		"tools": map[string]any{
 71 | 			"my-dataform-compiler": map[string]any{
 72 | 				"kind":        "dataform-compile-local",
 73 | 				"description": "Tool to compile dataform projects",
 74 | 			},
 75 | 		},
 76 | 	}
 77 | 
 78 | 	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
 79 | 	defer cancel()
 80 | 
 81 | 	cmd, cleanupServer, err := tests.StartCmd(ctx, toolsFile)
 82 | 	if err != nil {
 83 | 		t.Fatalf("command initialization returned an error: %s", err)
 84 | 	}
 85 | 	defer cleanupServer()
 86 | 
 87 | 	waitCtx, cancelWait := context.WithTimeout(ctx, 30*time.Second)
 88 | 	defer cancelWait()
 89 | 	out, err := testutils.WaitForString(waitCtx, regexp.MustCompile(`Server ready to serve`), cmd.Out)
 90 | 	if err != nil {
 91 | 		t.Logf("toolbox command logs: \n%s", out)
 92 | 		t.Fatalf("toolbox didn't start successfully: %s", err)
 93 | 	}
 94 | 
 95 | 	nonExistentDir := filepath.Join(os.TempDir(), "non-existent-dir")
 96 | 
 97 | 	testCases := []struct {
 98 | 		name       string
 99 | 		reqBody    string
100 | 		wantStatus int
101 | 		wantBody   string // Substring to check for in the response
102 | 	}{
103 | 		{
104 | 			name:       "success case",
105 | 			reqBody:    fmt.Sprintf(`{"project_dir":"%s"}`, projectDir),
106 | 			wantStatus: http.StatusOK,
107 | 			wantBody:   "test_col",
108 | 		},
109 | 		{
110 | 			name:       "missing parameter",
111 | 			reqBody:    `{}`,
112 | 			wantStatus: http.StatusBadRequest,
113 | 			wantBody:   `parameter \"project_dir\" is required`,
114 | 		},
115 | 		{
116 | 			name:       "non-existent directory",
117 | 			reqBody:    fmt.Sprintf(`{"project_dir":"%s"}`, nonExistentDir),
118 | 			wantStatus: http.StatusBadRequest,
119 | 			wantBody:   "error executing dataform compile",
120 | 		},
121 | 	}
122 | 
123 | 	api := "http://127.0.0.1:5000/api/tool/my-dataform-compiler/invoke"
124 | 
125 | 	for _, tc := range testCases {
126 | 		t.Run(tc.name, func(t *testing.T) {
127 | 			resp, bodyBytes := tests.RunRequest(t, http.MethodPost, api, strings.NewReader(tc.reqBody), nil)
128 | 
129 | 			if resp.StatusCode != tc.wantStatus {
130 | 				t.Fatalf("unexpected status: got %d, want %d. Body: %s", resp.StatusCode, tc.wantStatus, string(bodyBytes))
131 | 			}
132 | 
133 | 			if tc.wantBody != "" && !strings.Contains(string(bodyBytes), tc.wantBody) {
134 | 				t.Fatalf("expected body to contain %q, got: %s", tc.wantBody, string(bodyBytes))
135 | 			}
136 | 		})
137 | 	}
138 | }
139 | 
```

--------------------------------------------------------------------------------
/docs/en/getting-started/quickstart/go/openAI/quickstart.go:
--------------------------------------------------------------------------------

```go
  1 | package main
  2 | 
  3 | import (
  4 | 	"context"
  5 | 	"encoding/json"
  6 | 	"fmt"
  7 | 	"log"
  8 | 
  9 | 	"github.com/googleapis/mcp-toolbox-sdk-go/core"
 10 | 	openai "github.com/openai/openai-go"
 11 | )
 12 | 
 13 | // ConvertToOpenAITool converts a ToolboxTool into the go-openai library's Tool format.
 14 | func ConvertToOpenAITool(toolboxTool *core.ToolboxTool) openai.ChatCompletionToolParam {
 15 | 	// Get the input schema
 16 | 	jsonSchemaBytes, err := toolboxTool.InputSchema()
 17 | 	if err != nil {
 18 | 		return openai.ChatCompletionToolParam{}
 19 | 	}
 20 | 
 21 | 	// Unmarshal the JSON bytes into FunctionParameters
 22 | 	var paramsSchema openai.FunctionParameters
 23 | 	if err := json.Unmarshal(jsonSchemaBytes, &paramsSchema); err != nil {
 24 | 		return openai.ChatCompletionToolParam{}
 25 | 	}
 26 | 
 27 | 	// Create and return the final tool parameter struct.
 28 | 	return openai.ChatCompletionToolParam{
 29 | 		Function: openai.FunctionDefinitionParam{
 30 | 			Name:        toolboxTool.Name(),
 31 | 			Description: openai.String(toolboxTool.Description()),
 32 | 			Parameters:  paramsSchema,
 33 | 		},
 34 | 	}
 35 | }
 36 | 
 37 | const systemPrompt = `
 38 | You're a helpful hotel assistant. You handle hotel searching, booking, and
 39 | cancellations. When the user searches for a hotel, mention its name, id,
 40 | location and price tier. Always mention hotel ids while performing any
 41 | searches. This is very important for any operations. For any bookings or
 42 | cancellations, please provide the appropriate confirmation. Be sure to
 43 | update checkin or checkout dates if mentioned by the user.
 44 | Don't ask for confirmations from the user.
 45 | `
 46 | 
 47 | var queries = []string{
 48 | 	"Find hotels in Basel with Basel in its name.",
 49 | 	"Can you book the hotel Hilton Basel for me?",
 50 | 	"Oh wait, this is too expensive. Please cancel it and book the Hyatt Regency instead.",
 51 | 	"My check in dates would be from April 10, 2024 to April 19, 2024.",
 52 | }
 53 | 
 54 | func main() {
 55 | 	// Setup
 56 | 	ctx := context.Background()
 57 | 	toolboxURL := "http://localhost:5000"
 58 | 	openAIClient := openai.NewClient()
 59 | 
 60 | 	// Initialize the MCP Toolbox client.
 61 | 	toolboxClient, err := core.NewToolboxClient(toolboxURL)
 62 | 	if err != nil {
 63 | 		log.Fatalf("Failed to create Toolbox client: %v", err)
 64 | 	}
 65 | 
 66 | 	// Load the tools using the MCP Toolbox SDK.
 67 | 	tools, err := toolboxClient.LoadToolset("my-toolset", ctx)
 68 | 	if err != nil {
 69 | 		log.Fatalf("Failed to load tool : %v\nMake sure your Toolbox server is running and the tool is configured.", err)
 70 | 	}
 71 | 
 72 | 	openAITools := make([]openai.ChatCompletionToolParam, len(tools))
 73 | 	toolsMap := make(map[string]*core.ToolboxTool, len(tools))
 74 | 
 75 | 	for i, tool := range tools {
 76 | 		// Convert the Toolbox tool into the openAI FunctionDeclaration format.
 77 | 		openAITools[i] = ConvertToOpenAITool(tool)
 78 | 		// Add tool to a map for lookup later
 79 | 		toolsMap[tool.Name()] = tool
 80 | 
 81 | 	}
 82 | 
 83 | 	params := openai.ChatCompletionNewParams{
 84 | 		Messages: []openai.ChatCompletionMessageParamUnion{
 85 | 			openai.SystemMessage(systemPrompt),
 86 | 		},
 87 | 		Tools: openAITools,
 88 | 		Seed:  openai.Int(0),
 89 | 		Model: openai.ChatModelGPT4o,
 90 | 	}
 91 | 
 92 | 	for _, query := range queries {
 93 | 
 94 | 		params.Messages = append(params.Messages, openai.UserMessage(query))
 95 | 
 96 | 		// Make initial chat completion request
 97 | 		completion, err := openAIClient.Chat.Completions.New(ctx, params)
 98 | 		if err != nil {
 99 | 			panic(err)
100 | 		}
101 | 
102 | 		toolCalls := completion.Choices[0].Message.ToolCalls
103 | 
104 | 		// Return early if there are no tool calls
105 | 		if len(toolCalls) == 0 {
106 | 			log.Println("No function call")
107 | 		}
108 | 
109 | 		// If there was a function call, continue the conversation
110 | 		params.Messages = append(params.Messages, completion.Choices[0].Message.ToParam())
111 | 		for _, toolCall := range toolCalls {
112 | 
113 | 			toolName := toolCall.Function.Name
114 | 			toolToInvoke := toolsMap[toolName]
115 | 
116 | 			var args map[string]any
117 | 			err := json.Unmarshal([]byte(toolCall.Function.Arguments), &args)
118 | 			if err != nil {
119 | 				panic(err)
120 | 			}
121 | 
122 | 			result, err := toolToInvoke.Invoke(ctx, args)
123 | 			if err != nil {
124 | 				log.Fatal("Could not invoke tool", err)
125 | 			}
126 | 
127 | 			params.Messages = append(params.Messages, openai.ToolMessage(result.(string), toolCall.ID))
128 | 		}
129 | 
130 | 		completion, err = openAIClient.Chat.Completions.New(ctx, params)
131 | 		if err != nil {
132 | 			panic(err)
133 | 		}
134 | 
135 | 		params.Messages = append(params.Messages, openai.AssistantMessage(query))
136 | 
137 | 		fmt.Println("\n", completion.Choices[0].Message.Content)
138 | 
139 | 	}
140 | 
141 | }
142 | 
```

--------------------------------------------------------------------------------
/docs/en/getting-started/quickstart/shared/configure_toolbox.md:
--------------------------------------------------------------------------------

```markdown
  1 | <!-- This file has been used in local_quickstart.md, local_quickstart_go.md & local_quickstart_js.md -->
  2 | <!-- [START configure_toolbox] -->
  3 | In this section, we will download Toolbox, configure our tools in a
  4 | `tools.yaml`, and then run the Toolbox server.
  5 | 
  6 | 1. Download the latest version of Toolbox as a binary:
  7 | 
  8 |     {{< notice tip >}}
  9 |   Select the
 10 |   [correct binary](https://github.com/googleapis/genai-toolbox/releases)
 11 |   corresponding to your OS and CPU architecture.
 12 |     {{< /notice >}}
 13 |     <!-- {x-release-please-start-version} -->
 14 |     ```bash
 15 |     export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64
 16 |     curl -O https://storage.googleapis.com/genai-toolbox/v0.17.0/$OS/toolbox
 17 |     ```
 18 |     <!-- {x-release-please-end} -->
 19 | 
 20 | 1. Make the binary executable:
 21 | 
 22 |     ```bash
 23 |     chmod +x toolbox
 24 |     ```
 25 | 
 26 | 1. Write the following into a `tools.yaml` file. Be sure to update any fields
 27 |    such as `user`, `password`, or `database` that you may have customized in the
 28 |    previous step.
 29 | 
 30 |     {{< notice tip >}}
 31 |   In practice, use environment variable replacement with the format ${ENV_NAME}
 32 |   instead of hardcoding your secrets into the configuration file.
 33 |     {{< /notice >}}
 34 | 
 35 |     ```yaml
 36 |     sources:
 37 |       my-pg-source:
 38 |         kind: postgres
 39 |         host: 127.0.0.1
 40 |         port: 5432
 41 |         database: toolbox_db
 42 |         user: ${USER_NAME}
 43 |         password: ${PASSWORD}
 44 |     tools:
 45 |       search-hotels-by-name:
 46 |         kind: postgres-sql
 47 |         source: my-pg-source
 48 |         description: Search for hotels based on name.
 49 |         parameters:
 50 |           - name: name
 51 |             type: string
 52 |             description: The name of the hotel.
 53 |         statement: SELECT * FROM hotels WHERE name ILIKE '%' || $1 || '%';
 54 |       search-hotels-by-location:
 55 |         kind: postgres-sql
 56 |         source: my-pg-source
 57 |         description: Search for hotels based on location.
 58 |         parameters:
 59 |           - name: location
 60 |             type: string
 61 |             description: The location of the hotel.
 62 |         statement: SELECT * FROM hotels WHERE location ILIKE '%' || $1 || '%';
 63 |       book-hotel:
 64 |         kind: postgres-sql
 65 |         source: my-pg-source
 66 |         description: >-
 67 |            Book a hotel by its ID. If the hotel is successfully booked, returns a NULL, raises an error if not.
 68 |         parameters:
 69 |           - name: hotel_id
 70 |             type: string
 71 |             description: The ID of the hotel to book.
 72 |         statement: UPDATE hotels SET booked = B'1' WHERE id = $1;
 73 |       update-hotel:
 74 |         kind: postgres-sql
 75 |         source: my-pg-source
 76 |         description: >-
 77 |           Update a hotel's check-in and check-out dates by its ID. Returns a message
 78 |           indicating  whether the hotel was successfully updated or not.
 79 |         parameters:
 80 |           - name: hotel_id
 81 |             type: string
 82 |             description: The ID of the hotel to update.
 83 |           - name: checkin_date
 84 |             type: string
 85 |             description: The new check-in date of the hotel.
 86 |           - name: checkout_date
 87 |             type: string
 88 |             description: The new check-out date of the hotel.
 89 |         statement: >-
 90 |           UPDATE hotels SET checkin_date = CAST($2 as date), checkout_date = CAST($3
 91 |           as date) WHERE id = $1;
 92 |       cancel-hotel:
 93 |         kind: postgres-sql
 94 |         source: my-pg-source
 95 |         description: Cancel a hotel by its ID.
 96 |         parameters:
 97 |           - name: hotel_id
 98 |             type: string
 99 |             description: The ID of the hotel to cancel.
100 |         statement: UPDATE hotels SET booked = B'0' WHERE id = $1;
101 |    toolsets:
102 |       my-toolset:
103 |         - search-hotels-by-name
104 |         - search-hotels-by-location
105 |         - book-hotel
106 |         - update-hotel
107 |         - cancel-hotel
108 |     ```
109 | 
110 |     For more info on tools, check out the `Resources` section of the docs.
111 | 
112 | 1. Run the Toolbox server, pointing to the `tools.yaml` file created earlier:
113 | 
114 |     ```bash
115 |     ./toolbox --tools-file "tools.yaml"
116 |     ```
117 | 
118 |     {{< notice note >}}
119 |     Toolbox enables dynamic reloading by default. To disable, use the
120 |     `--disable-reload` flag.
121 |     {{< /notice >}}
122 | <!-- [END configure_toolbox] -->
```

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

```yaml
 1 | # Copyright 2025 Google LLC
 2 | #
 3 | # Licensed under the Apache License, Version 2.0 (the "License");
 4 | # you may not use this file except in compliance with the License.
 5 | # You may obtain a copy of the License at
 6 | #
 7 | #     http://www.apache.org/licenses/LICENSE-2.0
 8 | #
 9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | 
15 | sources:
16 |   firestore-source:
17 |     kind: firestore
18 |     project: ${FIRESTORE_PROJECT}
19 |     database: ${FIRESTORE_DATABASE:}
20 | 
21 | tools:
22 |   get_documents:
23 |     kind: firestore-get-documents
24 |     source: firestore-source
25 |     description: Gets multiple documents from Firestore by their paths
26 |   add_documents:
27 |     kind: firestore-add-documents
28 |     source: firestore-source
29 |     description: |
30 |       Adds a new document to a Firestore collection. Please follow the best practices :
31 |        1. Always use typed values in the documentData: Every field must be wrapped with its appropriate type indicator (e.g., {"stringValue": "text"})
32 |        2. Integer values can be strings in the documentData: The tool accepts integer values as strings (e.g., {"integerValue": "1500"})
33 |        3. Use returnData sparingly: Only set to true when you need to verify the exact data that was written
34 |        4. Validate data before sending: Ensure your data matches Firestore's native JSON format
35 |        5. Handle timestamps properly: Use RFC3339 format for timestamp strings
36 |        6. Base64 encode binary data: Binary data must be base64 encoded in the bytesValue field
37 |        7. Consider security rules: Ensure your Firestore security rules allow document creation in the target collection
38 |   update_document:
39 |     kind: firestore-update-document
40 |     source: firestore-source
41 |     description: |
42 |       Updates an existing document in Firestore. Supports both full document updates and selective field updates using an update mask. Please follow the best practices:
43 |        1. Use update masks for precision: When you only need to update specific fields, use the updateMask parameter to avoid unintended changes
44 |        2. Always use typed values in the documentData: Every field must be wrapped with its appropriate type indicator (e.g., {"stringValue": "text"})
45 |        3. Delete fields using update mask: To delete fields, include them in the updateMask but omit them from documentData
46 |        4. Integer values can be strings: The tool accepts integer values as strings (e.g., {"integerValue": "1500"})
47 |        5. Use returnData sparingly: Only set to true when you need to verify the exact data after the update
48 |        6. Handle timestamps properly: Use RFC3339 format for timestamp strings
49 |        7. Consider security rules: Ensure your Firestore security rules allow document updates
50 |   list_collections:
51 |     kind: firestore-list-collections
52 |     source: firestore-source
53 |     description: List Firestore collections for a given parent path
54 |   delete_documents:
55 |     kind: firestore-delete-documents
56 |     source: firestore-source
57 |     description: Delete multiple documents from Firestore
58 |   query_collection:
59 |     kind: firestore-query-collection
60 |     source: firestore-source
61 |     description: |
62 |       Retrieves one or more Firestore documents from a collection in a database in the current project by a collection with a full document path.
63 |       Use this if you know the exact path of a collection and the filtering clause you would like for the document.
64 |   get_rules:
65 |     kind: firestore-get-rules
66 |     source: firestore-source
67 |     description: Retrieves the active Firestore security rules for the current project
68 |   validate_rules:
69 |     kind: firestore-validate-rules
70 |     source: firestore-source
71 |     description: Checks the provided Firestore Rules source for syntax and validation errors. Provide the source code to validate.
72 | 
73 | toolsets:
74 |   firestore_database_tools:
75 |     - get_documents
76 |     - add_documents
77 |     - update_document
78 |     - list_collections
79 |     - delete_documents
80 |     - query_collection
81 |     - get_rules
82 |     - validate_rules
83 | 
```
Page 9/45FirstPrevNextLast