#
tokens: 47497/50000 6/816 files (page 26/35)
lines: off (toggle) GitHub
raw markdown copy
This is page 26 of 35. Use http://codebase.md/googleapis/genai-toolbox?lines=false&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
│       │   │   ├── serverless-spark.md
│       │   │   ├── spanner.md
│       │   │   ├── sqlite.md
│       │   │   ├── tidb.md
│       │   │   ├── trino.md
│       │   │   ├── valkey.md
│       │   │   └── yugabytedb.md
│       │   └── tools
│       │       ├── _index.md
│       │       ├── alloydb
│       │       │   ├── _index.md
│       │       │   ├── alloydb-create-cluster.md
│       │       │   ├── alloydb-create-instance.md
│       │       │   ├── alloydb-create-user.md
│       │       │   ├── alloydb-get-cluster.md
│       │       │   ├── alloydb-get-instance.md
│       │       │   ├── alloydb-get-user.md
│       │       │   ├── alloydb-list-clusters.md
│       │       │   ├── alloydb-list-instances.md
│       │       │   ├── alloydb-list-users.md
│       │       │   └── alloydb-wait-for-operation.md
│       │       ├── alloydbainl
│       │       │   ├── _index.md
│       │       │   └── alloydb-ai-nl.md
│       │       ├── bigquery
│       │       │   ├── _index.md
│       │       │   ├── bigquery-analyze-contribution.md
│       │       │   ├── bigquery-conversational-analytics.md
│       │       │   ├── bigquery-execute-sql.md
│       │       │   ├── bigquery-forecast.md
│       │       │   ├── bigquery-get-dataset-info.md
│       │       │   ├── bigquery-get-table-info.md
│       │       │   ├── bigquery-list-dataset-ids.md
│       │       │   ├── bigquery-list-table-ids.md
│       │       │   ├── bigquery-search-catalog.md
│       │       │   └── bigquery-sql.md
│       │       ├── bigtable
│       │       │   ├── _index.md
│       │       │   └── bigtable-sql.md
│       │       ├── cassandra
│       │       │   ├── _index.md
│       │       │   └── cassandra-cql.md
│       │       ├── clickhouse
│       │       │   ├── _index.md
│       │       │   ├── clickhouse-execute-sql.md
│       │       │   ├── clickhouse-list-databases.md
│       │       │   ├── clickhouse-list-tables.md
│       │       │   └── clickhouse-sql.md
│       │       ├── cloudmonitoring
│       │       │   ├── _index.md
│       │       │   └── cloud-monitoring-query-prometheus.md
│       │       ├── cloudsql
│       │       │   ├── _index.md
│       │       │   ├── cloudsqlcreatedatabase.md
│       │       │   ├── cloudsqlcreateusers.md
│       │       │   ├── cloudsqlgetinstances.md
│       │       │   ├── cloudsqllistdatabases.md
│       │       │   ├── cloudsqllistinstances.md
│       │       │   ├── cloudsqlmssqlcreateinstance.md
│       │       │   ├── cloudsqlmysqlcreateinstance.md
│       │       │   ├── cloudsqlpgcreateinstances.md
│       │       │   └── cloudsqlwaitforoperation.md
│       │       ├── couchbase
│       │       │   ├── _index.md
│       │       │   └── couchbase-sql.md
│       │       ├── dataform
│       │       │   ├── _index.md
│       │       │   └── dataform-compile-local.md
│       │       ├── dataplex
│       │       │   ├── _index.md
│       │       │   ├── dataplex-lookup-entry.md
│       │       │   ├── dataplex-search-aspect-types.md
│       │       │   └── dataplex-search-entries.md
│       │       ├── dgraph
│       │       │   ├── _index.md
│       │       │   └── dgraph-dql.md
│       │       ├── firebird
│       │       │   ├── _index.md
│       │       │   ├── firebird-execute-sql.md
│       │       │   └── firebird-sql.md
│       │       ├── firestore
│       │       │   ├── _index.md
│       │       │   ├── firestore-add-documents.md
│       │       │   ├── firestore-delete-documents.md
│       │       │   ├── firestore-get-documents.md
│       │       │   ├── firestore-get-rules.md
│       │       │   ├── firestore-list-collections.md
│       │       │   ├── firestore-query-collection.md
│       │       │   ├── firestore-query.md
│       │       │   ├── firestore-update-document.md
│       │       │   └── firestore-validate-rules.md
│       │       ├── http
│       │       │   ├── _index.md
│       │       │   └── http.md
│       │       ├── looker
│       │       │   ├── _index.md
│       │       │   ├── looker-add-dashboard-element.md
│       │       │   ├── looker-conversational-analytics.md
│       │       │   ├── looker-create-project-file.md
│       │       │   ├── looker-delete-project-file.md
│       │       │   ├── looker-dev-mode.md
│       │       │   ├── looker-get-dashboards.md
│       │       │   ├── looker-get-dimensions.md
│       │       │   ├── looker-get-explores.md
│       │       │   ├── looker-get-filters.md
│       │       │   ├── looker-get-looks.md
│       │       │   ├── looker-get-measures.md
│       │       │   ├── looker-get-models.md
│       │       │   ├── looker-get-parameters.md
│       │       │   ├── looker-get-project-file.md
│       │       │   ├── looker-get-project-files.md
│       │       │   ├── looker-get-projects.md
│       │       │   ├── looker-health-analyze.md
│       │       │   ├── looker-health-pulse.md
│       │       │   ├── looker-health-vacuum.md
│       │       │   ├── looker-make-dashboard.md
│       │       │   ├── looker-make-look.md
│       │       │   ├── looker-query-sql.md
│       │       │   ├── looker-query-url.md
│       │       │   ├── looker-query.md
│       │       │   ├── looker-run-look.md
│       │       │   └── looker-update-project-file.md
│       │       ├── mongodb
│       │       │   ├── _index.md
│       │       │   ├── mongodb-aggregate.md
│       │       │   ├── mongodb-delete-many.md
│       │       │   ├── mongodb-delete-one.md
│       │       │   ├── mongodb-find-one.md
│       │       │   ├── mongodb-find.md
│       │       │   ├── mongodb-insert-many.md
│       │       │   ├── mongodb-insert-one.md
│       │       │   ├── mongodb-update-many.md
│       │       │   └── mongodb-update-one.md
│       │       ├── mssql
│       │       │   ├── _index.md
│       │       │   ├── mssql-execute-sql.md
│       │       │   ├── mssql-list-tables.md
│       │       │   └── mssql-sql.md
│       │       ├── mysql
│       │       │   ├── _index.md
│       │       │   ├── mysql-execute-sql.md
│       │       │   ├── mysql-list-active-queries.md
│       │       │   ├── mysql-list-table-fragmentation.md
│       │       │   ├── mysql-list-tables-missing-unique-indexes.md
│       │       │   ├── mysql-list-tables.md
│       │       │   └── mysql-sql.md
│       │       ├── neo4j
│       │       │   ├── _index.md
│       │       │   ├── neo4j-cypher.md
│       │       │   ├── neo4j-execute-cypher.md
│       │       │   └── neo4j-schema.md
│       │       ├── oceanbase
│       │       │   ├── _index.md
│       │       │   ├── oceanbase-execute-sql.md
│       │       │   └── oceanbase-sql.md
│       │       ├── oracle
│       │       │   ├── _index.md
│       │       │   ├── oracle-execute-sql.md
│       │       │   └── oracle-sql.md
│       │       ├── postgres
│       │       │   ├── _index.md
│       │       │   ├── postgres-execute-sql.md
│       │       │   ├── postgres-list-active-queries.md
│       │       │   ├── postgres-list-available-extensions.md
│       │       │   ├── postgres-list-installed-extensions.md
│       │       │   ├── postgres-list-tables.md
│       │       │   └── postgres-sql.md
│       │       ├── redis
│       │       │   ├── _index.md
│       │       │   └── redis.md
│       │       ├── serverless-spark
│       │       │   ├── _index.md
│       │       │   └── serverless-spark-list-batches.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
│   │       ├── serverless-spark.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
│   │   ├── serverlessspark
│   │   │   ├── serverlessspark_test.go
│   │   │   └── serverlessspark.go
│   │   ├── sources.go
│   │   ├── spanner
│   │   │   ├── spanner_test.go
│   │   │   └── spanner.go
│   │   ├── sqlite
│   │   │   ├── sqlite_test.go
│   │   │   └── sqlite.go
│   │   ├── tidb
│   │   │   ├── tidb_test.go
│   │   │   └── tidb.go
│   │   ├── trino
│   │   │   ├── trino_test.go
│   │   │   └── trino.go
│   │   ├── util.go
│   │   ├── valkey
│   │   │   ├── valkey_test.go
│   │   │   └── valkey.go
│   │   └── yugabytedb
│   │       ├── yugabytedb_test.go
│   │       └── yugabytedb.go
│   ├── telemetry
│   │   ├── instrumentation.go
│   │   └── telemetry.go
│   ├── testutils
│   │   └── testutils.go
│   ├── tools
│   │   ├── alloydb
│   │   │   ├── alloydbcreatecluster
│   │   │   │   ├── alloydbcreatecluster_test.go
│   │   │   │   └── alloydbcreatecluster.go
│   │   │   ├── alloydbcreateinstance
│   │   │   │   ├── alloydbcreateinstance_test.go
│   │   │   │   └── alloydbcreateinstance.go
│   │   │   ├── alloydbcreateuser
│   │   │   │   ├── alloydbcreateuser_test.go
│   │   │   │   └── alloydbcreateuser.go
│   │   │   ├── alloydbgetcluster
│   │   │   │   ├── alloydbgetcluster_test.go
│   │   │   │   └── alloydbgetcluster.go
│   │   │   ├── alloydbgetinstance
│   │   │   │   ├── alloydbgetinstance_test.go
│   │   │   │   └── alloydbgetinstance.go
│   │   │   ├── alloydbgetuser
│   │   │   │   ├── alloydbgetuser_test.go
│   │   │   │   └── alloydbgetuser.go
│   │   │   ├── alloydblistclusters
│   │   │   │   ├── alloydblistclusters_test.go
│   │   │   │   └── alloydblistclusters.go
│   │   │   ├── alloydblistinstances
│   │   │   │   ├── alloydblistinstances_test.go
│   │   │   │   └── alloydblistinstances.go
│   │   │   ├── alloydblistusers
│   │   │   │   ├── alloydblistusers_test.go
│   │   │   │   └── alloydblistusers.go
│   │   │   └── alloydbwaitforoperation
│   │   │       ├── alloydbwaitforoperation_test.go
│   │   │       └── alloydbwaitforoperation.go
│   │   ├── alloydbainl
│   │   │   ├── alloydbainl_test.go
│   │   │   └── alloydbainl.go
│   │   ├── bigquery
│   │   │   ├── bigqueryanalyzecontribution
│   │   │   │   ├── bigqueryanalyzecontribution_test.go
│   │   │   │   └── bigqueryanalyzecontribution.go
│   │   │   ├── bigquerycommon
│   │   │   │   ├── table_name_parser_test.go
│   │   │   │   ├── table_name_parser.go
│   │   │   │   └── util.go
│   │   │   ├── bigqueryconversationalanalytics
│   │   │   │   ├── bigqueryconversationalanalytics_test.go
│   │   │   │   └── bigqueryconversationalanalytics.go
│   │   │   ├── bigqueryexecutesql
│   │   │   │   ├── bigqueryexecutesql_test.go
│   │   │   │   └── bigqueryexecutesql.go
│   │   │   ├── bigqueryforecast
│   │   │   │   ├── bigqueryforecast_test.go
│   │   │   │   └── bigqueryforecast.go
│   │   │   ├── bigquerygetdatasetinfo
│   │   │   │   ├── bigquerygetdatasetinfo_test.go
│   │   │   │   └── bigquerygetdatasetinfo.go
│   │   │   ├── bigquerygettableinfo
│   │   │   │   ├── bigquerygettableinfo_test.go
│   │   │   │   └── bigquerygettableinfo.go
│   │   │   ├── bigquerylistdatasetids
│   │   │   │   ├── bigquerylistdatasetids_test.go
│   │   │   │   └── bigquerylistdatasetids.go
│   │   │   ├── bigquerylisttableids
│   │   │   │   ├── bigquerylisttableids_test.go
│   │   │   │   └── bigquerylisttableids.go
│   │   │   ├── bigquerysearchcatalog
│   │   │   │   ├── bigquerysearchcatalog_test.go
│   │   │   │   └── bigquerysearchcatalog.go
│   │   │   └── bigquerysql
│   │   │       ├── bigquerysql_test.go
│   │   │       └── bigquerysql.go
│   │   ├── bigtable
│   │   │   ├── bigtable_test.go
│   │   │   └── bigtable.go
│   │   ├── cassandra
│   │   │   └── cassandracql
│   │   │       ├── cassandracql_test.go
│   │   │       └── cassandracql.go
│   │   ├── clickhouse
│   │   │   ├── clickhouseexecutesql
│   │   │   │   ├── clickhouseexecutesql_test.go
│   │   │   │   └── clickhouseexecutesql.go
│   │   │   ├── clickhouselistdatabases
│   │   │   │   ├── clickhouselistdatabases_test.go
│   │   │   │   └── clickhouselistdatabases.go
│   │   │   ├── clickhouselisttables
│   │   │   │   ├── clickhouselisttables_test.go
│   │   │   │   └── clickhouselisttables.go
│   │   │   └── clickhousesql
│   │   │       ├── clickhousesql_test.go
│   │   │       └── clickhousesql.go
│   │   ├── cloudmonitoring
│   │   │   ├── cloudmonitoring_test.go
│   │   │   └── cloudmonitoring.go
│   │   ├── cloudsql
│   │   │   ├── cloudsqlcreatedatabase
│   │   │   │   ├── cloudsqlcreatedatabase_test.go
│   │   │   │   └── cloudsqlcreatedatabase.go
│   │   │   ├── cloudsqlcreateusers
│   │   │   │   ├── cloudsqlcreateusers_test.go
│   │   │   │   └── cloudsqlcreateusers.go
│   │   │   ├── cloudsqlgetinstances
│   │   │   │   ├── cloudsqlgetinstances_test.go
│   │   │   │   └── cloudsqlgetinstances.go
│   │   │   ├── cloudsqllistdatabases
│   │   │   │   ├── cloudsqllistdatabases_test.go
│   │   │   │   └── cloudsqllistdatabases.go
│   │   │   ├── cloudsqllistinstances
│   │   │   │   ├── cloudsqllistinstances_test.go
│   │   │   │   └── cloudsqllistinstances.go
│   │   │   └── cloudsqlwaitforoperation
│   │   │       ├── cloudsqlwaitforoperation_test.go
│   │   │       └── cloudsqlwaitforoperation.go
│   │   ├── cloudsqlmssql
│   │   │   └── cloudsqlmssqlcreateinstance
│   │   │       ├── cloudsqlmssqlcreateinstance_test.go
│   │   │       └── cloudsqlmssqlcreateinstance.go
│   │   ├── cloudsqlmysql
│   │   │   └── cloudsqlmysqlcreateinstance
│   │   │       ├── cloudsqlmysqlcreateinstance_test.go
│   │   │       └── cloudsqlmysqlcreateinstance.go
│   │   ├── cloudsqlpg
│   │   │   └── cloudsqlpgcreateinstances
│   │   │       ├── cloudsqlpgcreateinstances_test.go
│   │   │       └── cloudsqlpgcreateinstances.go
│   │   ├── common_test.go
│   │   ├── common.go
│   │   ├── couchbase
│   │   │   ├── couchbase_test.go
│   │   │   └── couchbase.go
│   │   ├── dataform
│   │   │   └── dataformcompilelocal
│   │   │       ├── dataformcompilelocal_test.go
│   │   │       └── dataformcompilelocal.go
│   │   ├── dataplex
│   │   │   ├── dataplexlookupentry
│   │   │   │   ├── dataplexlookupentry_test.go
│   │   │   │   └── dataplexlookupentry.go
│   │   │   ├── dataplexsearchaspecttypes
│   │   │   │   ├── dataplexsearchaspecttypes_test.go
│   │   │   │   └── dataplexsearchaspecttypes.go
│   │   │   └── dataplexsearchentries
│   │   │       ├── dataplexsearchentries_test.go
│   │   │       └── dataplexsearchentries.go
│   │   ├── dgraph
│   │   │   ├── dgraph_test.go
│   │   │   └── dgraph.go
│   │   ├── firebird
│   │   │   ├── firebirdexecutesql
│   │   │   │   ├── firebirdexecutesql_test.go
│   │   │   │   └── firebirdexecutesql.go
│   │   │   └── firebirdsql
│   │   │       ├── firebirdsql_test.go
│   │   │       └── firebirdsql.go
│   │   ├── firestore
│   │   │   ├── firestoreadddocuments
│   │   │   │   ├── firestoreadddocuments_test.go
│   │   │   │   └── firestoreadddocuments.go
│   │   │   ├── firestoredeletedocuments
│   │   │   │   ├── firestoredeletedocuments_test.go
│   │   │   │   └── firestoredeletedocuments.go
│   │   │   ├── firestoregetdocuments
│   │   │   │   ├── firestoregetdocuments_test.go
│   │   │   │   └── firestoregetdocuments.go
│   │   │   ├── firestoregetrules
│   │   │   │   ├── firestoregetrules_test.go
│   │   │   │   └── firestoregetrules.go
│   │   │   ├── firestorelistcollections
│   │   │   │   ├── firestorelistcollections_test.go
│   │   │   │   └── firestorelistcollections.go
│   │   │   ├── firestorequery
│   │   │   │   ├── firestorequery_test.go
│   │   │   │   └── firestorequery.go
│   │   │   ├── firestorequerycollection
│   │   │   │   ├── firestorequerycollection_test.go
│   │   │   │   └── firestorequerycollection.go
│   │   │   ├── firestoreupdatedocument
│   │   │   │   ├── firestoreupdatedocument_test.go
│   │   │   │   └── firestoreupdatedocument.go
│   │   │   ├── firestorevalidaterules
│   │   │   │   ├── firestorevalidaterules_test.go
│   │   │   │   └── firestorevalidaterules.go
│   │   │   └── util
│   │   │       ├── converter_test.go
│   │   │       ├── converter.go
│   │   │       ├── validator_test.go
│   │   │       └── validator.go
│   │   ├── http
│   │   │   ├── http_test.go
│   │   │   └── http.go
│   │   ├── http_method.go
│   │   ├── looker
│   │   │   ├── lookeradddashboardelement
│   │   │   │   ├── lookeradddashboardelement_test.go
│   │   │   │   └── lookeradddashboardelement.go
│   │   │   ├── lookercommon
│   │   │   │   ├── lookercommon_test.go
│   │   │   │   └── lookercommon.go
│   │   │   ├── lookerconversationalanalytics
│   │   │   │   ├── lookerconversationalanalytics_test.go
│   │   │   │   └── lookerconversationalanalytics.go
│   │   │   ├── lookercreateprojectfile
│   │   │   │   ├── lookercreateprojectfile_test.go
│   │   │   │   └── lookercreateprojectfile.go
│   │   │   ├── lookerdeleteprojectfile
│   │   │   │   ├── lookerdeleteprojectfile_test.go
│   │   │   │   └── lookerdeleteprojectfile.go
│   │   │   ├── lookerdevmode
│   │   │   │   ├── lookerdevmode_test.go
│   │   │   │   └── lookerdevmode.go
│   │   │   ├── lookergetdashboards
│   │   │   │   ├── lookergetdashboards_test.go
│   │   │   │   └── lookergetdashboards.go
│   │   │   ├── lookergetdimensions
│   │   │   │   ├── lookergetdimensions_test.go
│   │   │   │   └── lookergetdimensions.go
│   │   │   ├── lookergetexplores
│   │   │   │   ├── lookergetexplores_test.go
│   │   │   │   └── lookergetexplores.go
│   │   │   ├── lookergetfilters
│   │   │   │   ├── lookergetfilters_test.go
│   │   │   │   └── lookergetfilters.go
│   │   │   ├── lookergetlooks
│   │   │   │   ├── lookergetlooks_test.go
│   │   │   │   └── lookergetlooks.go
│   │   │   ├── lookergetmeasures
│   │   │   │   ├── lookergetmeasures_test.go
│   │   │   │   └── lookergetmeasures.go
│   │   │   ├── lookergetmodels
│   │   │   │   ├── lookergetmodels_test.go
│   │   │   │   └── lookergetmodels.go
│   │   │   ├── lookergetparameters
│   │   │   │   ├── lookergetparameters_test.go
│   │   │   │   └── lookergetparameters.go
│   │   │   ├── lookergetprojectfile
│   │   │   │   ├── lookergetprojectfile_test.go
│   │   │   │   └── lookergetprojectfile.go
│   │   │   ├── lookergetprojectfiles
│   │   │   │   ├── lookergetprojectfiles_test.go
│   │   │   │   └── lookergetprojectfiles.go
│   │   │   ├── lookergetprojects
│   │   │   │   ├── lookergetprojects_test.go
│   │   │   │   └── lookergetprojects.go
│   │   │   ├── lookerhealthanalyze
│   │   │   │   ├── lookerhealthanalyze_test.go
│   │   │   │   └── lookerhealthanalyze.go
│   │   │   ├── lookerhealthpulse
│   │   │   │   ├── lookerhealthpulse_test.go
│   │   │   │   └── lookerhealthpulse.go
│   │   │   ├── lookerhealthvacuum
│   │   │   │   ├── lookerhealthvacuum_test.go
│   │   │   │   └── lookerhealthvacuum.go
│   │   │   ├── lookermakedashboard
│   │   │   │   ├── lookermakedashboard_test.go
│   │   │   │   └── lookermakedashboard.go
│   │   │   ├── lookermakelook
│   │   │   │   ├── lookermakelook_test.go
│   │   │   │   └── lookermakelook.go
│   │   │   ├── lookerquery
│   │   │   │   ├── lookerquery_test.go
│   │   │   │   └── lookerquery.go
│   │   │   ├── lookerquerysql
│   │   │   │   ├── lookerquerysql_test.go
│   │   │   │   └── lookerquerysql.go
│   │   │   ├── lookerqueryurl
│   │   │   │   ├── lookerqueryurl_test.go
│   │   │   │   └── lookerqueryurl.go
│   │   │   ├── lookerrunlook
│   │   │   │   ├── lookerrunlook_test.go
│   │   │   │   └── lookerrunlook.go
│   │   │   └── lookerupdateprojectfile
│   │   │       ├── lookerupdateprojectfile_test.go
│   │   │       └── lookerupdateprojectfile.go
│   │   ├── mongodb
│   │   │   ├── mongodbaggregate
│   │   │   │   ├── mongodbaggregate_test.go
│   │   │   │   └── mongodbaggregate.go
│   │   │   ├── mongodbdeletemany
│   │   │   │   ├── mongodbdeletemany_test.go
│   │   │   │   └── mongodbdeletemany.go
│   │   │   ├── mongodbdeleteone
│   │   │   │   ├── mongodbdeleteone_test.go
│   │   │   │   └── mongodbdeleteone.go
│   │   │   ├── mongodbfind
│   │   │   │   ├── mongodbfind_test.go
│   │   │   │   └── mongodbfind.go
│   │   │   ├── mongodbfindone
│   │   │   │   ├── mongodbfindone_test.go
│   │   │   │   └── mongodbfindone.go
│   │   │   ├── mongodbinsertmany
│   │   │   │   ├── mongodbinsertmany_test.go
│   │   │   │   └── mongodbinsertmany.go
│   │   │   ├── mongodbinsertone
│   │   │   │   ├── mongodbinsertone_test.go
│   │   │   │   └── mongodbinsertone.go
│   │   │   ├── mongodbupdatemany
│   │   │   │   ├── mongodbupdatemany_test.go
│   │   │   │   └── mongodbupdatemany.go
│   │   │   └── mongodbupdateone
│   │   │       ├── mongodbupdateone_test.go
│   │   │       └── mongodbupdateone.go
│   │   ├── mssql
│   │   │   ├── mssqlexecutesql
│   │   │   │   ├── mssqlexecutesql_test.go
│   │   │   │   └── mssqlexecutesql.go
│   │   │   ├── mssqllisttables
│   │   │   │   ├── mssqllisttables_test.go
│   │   │   │   └── mssqllisttables.go
│   │   │   └── mssqlsql
│   │   │       ├── mssqlsql_test.go
│   │   │       └── mssqlsql.go
│   │   ├── mysql
│   │   │   ├── mysqlcommon
│   │   │   │   └── mysqlcommon.go
│   │   │   ├── mysqlexecutesql
│   │   │   │   ├── mysqlexecutesql_test.go
│   │   │   │   └── mysqlexecutesql.go
│   │   │   ├── mysqllistactivequeries
│   │   │   │   ├── mysqllistactivequeries_test.go
│   │   │   │   └── mysqllistactivequeries.go
│   │   │   ├── mysqllisttablefragmentation
│   │   │   │   ├── mysqllisttablefragmentation_test.go
│   │   │   │   └── mysqllisttablefragmentation.go
│   │   │   ├── mysqllisttables
│   │   │   │   ├── mysqllisttables_test.go
│   │   │   │   └── mysqllisttables.go
│   │   │   ├── mysqllisttablesmissinguniqueindexes
│   │   │   │   ├── mysqllisttablesmissinguniqueindexes_test.go
│   │   │   │   └── mysqllisttablesmissinguniqueindexes.go
│   │   │   └── mysqlsql
│   │   │       ├── mysqlsql_test.go
│   │   │       └── mysqlsql.go
│   │   ├── neo4j
│   │   │   ├── neo4jcypher
│   │   │   │   ├── neo4jcypher_test.go
│   │   │   │   └── neo4jcypher.go
│   │   │   ├── neo4jexecutecypher
│   │   │   │   ├── classifier
│   │   │   │   │   ├── classifier_test.go
│   │   │   │   │   └── classifier.go
│   │   │   │   ├── neo4jexecutecypher_test.go
│   │   │   │   └── neo4jexecutecypher.go
│   │   │   └── neo4jschema
│   │   │       ├── cache
│   │   │       │   ├── cache_test.go
│   │   │       │   └── cache.go
│   │   │       ├── helpers
│   │   │       │   ├── helpers_test.go
│   │   │       │   └── helpers.go
│   │   │       ├── neo4jschema_test.go
│   │   │       ├── neo4jschema.go
│   │   │       └── types
│   │   │           └── types.go
│   │   ├── oceanbase
│   │   │   ├── oceanbaseexecutesql
│   │   │   │   ├── oceanbaseexecutesql_test.go
│   │   │   │   └── oceanbaseexecutesql.go
│   │   │   └── oceanbasesql
│   │   │       ├── oceanbasesql_test.go
│   │   │       └── oceanbasesql.go
│   │   ├── oracle
│   │   │   ├── oracleexecutesql
│   │   │   │   └── oracleexecutesql.go
│   │   │   └── oraclesql
│   │   │       └── oraclesql.go
│   │   ├── parameters_test.go
│   │   ├── parameters.go
│   │   ├── postgres
│   │   │   ├── postgresexecutesql
│   │   │   │   ├── postgresexecutesql_test.go
│   │   │   │   └── postgresexecutesql.go
│   │   │   ├── postgreslistactivequeries
│   │   │   │   ├── postgreslistactivequeries_test.go
│   │   │   │   └── postgreslistactivequeries.go
│   │   │   ├── postgreslistavailableextensions
│   │   │   │   ├── postgreslistavailableextensions_test.go
│   │   │   │   └── postgreslistavailableextensions.go
│   │   │   ├── postgreslistinstalledextensions
│   │   │   │   ├── postgreslistinstalledextensions_test.go
│   │   │   │   └── postgreslistinstalledextensions.go
│   │   │   ├── postgreslisttables
│   │   │   │   ├── postgreslisttables_test.go
│   │   │   │   └── postgreslisttables.go
│   │   │   └── postgressql
│   │   │       ├── postgressql_test.go
│   │   │       └── postgressql.go
│   │   ├── redis
│   │   │   ├── redis_test.go
│   │   │   └── redis.go
│   │   ├── serverlessspark
│   │   │   └── serverlesssparklistbatches
│   │   │       ├── serverlesssparklistbatches_test.go
│   │   │       └── serverlesssparklistbatches.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
    ├── serverlessspark
    │   └── serverless_spark_integration_test.go
    ├── source.go
    ├── spanner
    │   └── spanner_integration_test.go
    ├── sqlite
    │   └── sqlite_integration_test.go
    ├── tidb
    │   └── tidb_integration_test.go
    ├── tool.go
    ├── trino
    │   └── trino_integration_test.go
    ├── utility
    │   └── wait_integration_test.go
    ├── valkey
    │   └── valkey_test.go
    └── yugabytedb
        └── yugabytedb_integration_test.go
```

# Files

--------------------------------------------------------------------------------
/internal/tools/bigquery/bigquerycommon/table_name_parser_test.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package bigquerycommon_test

import (
	"sort"
	"strings"
	"testing"

	"github.com/google/go-cmp/cmp"
	"github.com/googleapis/genai-toolbox/internal/tools/bigquery/bigquerycommon"
)

func TestTableParser(t *testing.T) {
	testCases := []struct {
		name             string
		sql              string
		defaultProjectID string
		want             []string
		wantErr          bool
		wantErrMsg       string
	}{
		{
			name:             "single fully qualified table",
			sql:              "SELECT * FROM `my-project.my_dataset.my_table`",
			defaultProjectID: "default-proj",
			want:             []string{"my-project.my_dataset.my_table"},
			wantErr:          false,
		},
		{
			name:             "multiple statements with same table",
			sql:              "select * from proj1.data1.tbl1 limit 1; select A.b from proj1.data1.tbl1 as A limit 1;",
			defaultProjectID: "default-proj",
			want:             []string{"proj1.data1.tbl1"},
			wantErr:          false,
		},
		{
			name:             "multiple fully qualified tables",
			sql:              "SELECT * FROM `proj1.data1`.`tbl1` JOIN proj2.`data2.tbl2` ON id",
			defaultProjectID: "default-proj",
			want:             []string{"proj1.data1.tbl1", "proj2.data2.tbl2"},
			wantErr:          false,
		},
		{
			name:             "duplicate tables",
			sql:              "SELECT * FROM `proj1.data1.tbl1` JOIN proj1.data1.tbl1 ON id",
			defaultProjectID: "default-proj",
			want:             []string{"proj1.data1.tbl1"},
			wantErr:          false,
		},
		{
			name:             "partial table with default project",
			sql:              "SELECT * FROM `my_dataset`.my_table",
			defaultProjectID: "default-proj",
			want:             []string{"default-proj.my_dataset.my_table"},
			wantErr:          false,
		},
		{
			name:             "partial table without default project",
			sql:              "SELECT * FROM `my_dataset.my_table`",
			defaultProjectID: "",
			want:             nil,
			wantErr:          true,
		},
		{
			name:             "mixed fully qualified and partial tables",
			sql:              "SELECT t1.*, t2.* FROM `proj1.data1.tbl1` AS t1 JOIN `data2.tbl2` AS t2 ON t1.id = t2.id",
			defaultProjectID: "default-proj",
			want:             []string{"proj1.data1.tbl1", "default-proj.data2.tbl2"},
			wantErr:          false,
		},
		{
			name:             "no tables",
			sql:              "SELECT 1+1",
			defaultProjectID: "default-proj",
			want:             []string{},
			wantErr:          false,
		},
		{
			name:             "ignore single part identifiers (like CTEs)",
			sql:              "WITH my_cte AS (SELECT 1) SELECT * FROM `my_cte`",
			defaultProjectID: "default-proj",
			want:             []string{},
			wantErr:          false,
		},
		{
			name:             "complex CTE",
			sql:              "WITH cte1 AS (SELECT * FROM `real.table.one`), cte2 AS (SELECT * FROM cte1) SELECT * FROM cte2 JOIN `real.table.two` ON true",
			defaultProjectID: "default-proj",
			want:             []string{"real.table.one", "real.table.two"},
			wantErr:          false,
		},
		{
			name:             "nested subquery should be parsed",
			sql:              "SELECT * FROM (SELECT a FROM (SELECT A.b FROM `real.table.nested` AS A))",
			defaultProjectID: "default-proj",
			want:             []string{"real.table.nested"},
			wantErr:          false,
		},
		{
			name:             "from clause with unnest",
			sql:              "SELECT event.name FROM `my-project.my_dataset.my_table` AS A, UNNEST(A.events) AS event",
			defaultProjectID: "default-proj",
			want:             []string{"my-project.my_dataset.my_table"},
			wantErr:          false,
		},
		{
			name:             "ignore more than 3 parts",
			sql:              "SELECT * FROM `proj.data.tbl.col`",
			defaultProjectID: "default-proj",
			want:             []string{},
			wantErr:          false,
		},
		{
			name:             "complex query",
			sql:              "SELECT name FROM (SELECT name FROM `proj1.data1.tbl1`) UNION ALL SELECT name FROM `data2.tbl2`",
			defaultProjectID: "default-proj",
			want:             []string{"proj1.data1.tbl1", "default-proj.data2.tbl2"},
			wantErr:          false,
		},
		{
			name:             "empty sql",
			sql:              "",
			defaultProjectID: "default-proj",
			want:             []string{},
			wantErr:          false,
		},
		{
			name:             "with comments",
			sql:              "SELECT * FROM `proj1.data1.tbl1`; -- comment `fake.table.one` \n SELECT * FROM `proj2.data2.tbl2`; # comment `fake.table.two`",
			defaultProjectID: "default-proj",
			want:             []string{"proj1.data1.tbl1", "proj2.data2.tbl2"},
			wantErr:          false,
		},
		{
			name:             "multi-statement with semicolon",
			sql:              "SELECT * FROM `proj1.data1.tbl1`; SELECT * FROM `proj2.data2.tbl2`",
			defaultProjectID: "default-proj",
			want:             []string{"proj1.data1.tbl1", "proj2.data2.tbl2"},
			wantErr:          false,
		},
		{
			name:             "simple execute immediate",
			sql:              "EXECUTE IMMEDIATE 'SELECT * FROM `exec.proj.tbl`'",
			defaultProjectID: "default-proj",
			want:             nil,
			wantErr:          true,
			wantErrMsg:       "EXECUTE IMMEDIATE is not allowed when dataset restrictions are in place",
		},
		{
			name:             "execute immediate with multiple spaces",
			sql:              "EXECUTE  IMMEDIATE 'SELECT 1'",
			defaultProjectID: "default-proj",
			want:             nil,
			wantErr:          true,
			wantErrMsg:       "EXECUTE IMMEDIATE is not allowed when dataset restrictions are in place",
		},
		{
			name:             "execute immediate with newline",
			sql:              "EXECUTE\nIMMEDIATE 'SELECT 1'",
			defaultProjectID: "default-proj",
			want:             nil,
			wantErr:          true,
			wantErrMsg:       "EXECUTE IMMEDIATE is not allowed when dataset restrictions are in place",
		},
		{
			name:             "execute immediate with comment",
			sql:              "EXECUTE -- some comment\n IMMEDIATE 'SELECT * FROM `exec.proj.tbl`'",
			defaultProjectID: "default-proj",
			want:             nil,
			wantErr:          true,
			wantErrMsg:       "EXECUTE IMMEDIATE is not allowed when dataset restrictions are in place",
		},
		{
			name:             "nested execute immediate",
			sql:              "EXECUTE IMMEDIATE \"EXECUTE IMMEDIATE '''SELECT * FROM `nested.exec.tbl`'''\"",
			defaultProjectID: "default-proj",
			want:             nil,
			wantErr:          true,
			wantErrMsg:       "EXECUTE IMMEDIATE is not allowed when dataset restrictions are in place",
		},
		{
			name:             "begin execute immediate",
			sql:              "BEGIN EXECUTE IMMEDIATE 'SELECT * FROM `exec.proj.tbl`'; END;",
			defaultProjectID: "default-proj",
			want:             nil,
			wantErr:          true,
			wantErrMsg:       "EXECUTE IMMEDIATE is not allowed when dataset restrictions are in place",
		},
		{
			name:             "table inside string literal should be ignored",
			sql:              "SELECT * FROM `real.table.one` WHERE name = 'select * from `fake.table.two`'",
			defaultProjectID: "default-proj",
			want:             []string{"real.table.one"},
			wantErr:          false,
		},
		{
			name:             "string with escaped single quote",
			sql:              "SELECT 'this is a string with an escaped quote \\' and a fake table `fake.table.one`' FROM `real.table.two`",
			defaultProjectID: "default-proj",
			want:             []string{"real.table.two"},
			wantErr:          false,
		},
		{
			name:             "string with escaped double quote",
			sql:              `SELECT "this is a string with an escaped quote \" and a fake table ` + "`fake.table.one`" + `" FROM ` + "`real.table.two`",
			defaultProjectID: "default-proj",
			want:             []string{"real.table.two"},
			wantErr:          false,
		},
		{
			name:             "multi-line comment",
			sql:              "/* `fake.table.1` */ SELECT * FROM `real.table.2`",
			defaultProjectID: "default-proj",
			want:             []string{"real.table.2"},
			wantErr:          false,
		},
		{
			name:             "raw string with backslash should be ignored",
			sql:              "SELECT * FROM `real.table.one` WHERE name = r'a raw string with a \\ and a fake table `fake.table.two`'",
			defaultProjectID: "default-proj",
			want:             []string{"real.table.one"},
			wantErr:          false,
		},
		{
			name:             "capital R raw string with quotes inside should be ignored",
			sql:              `SELECT * FROM ` + "`real.table.one`" + ` WHERE name = R"""a raw string with a ' and a " and a \ and a fake table ` + "`fake.table.two`" + `"""`,
			defaultProjectID: "default-proj",
			want:             []string{"real.table.one"},
			wantErr:          false,
		},
		{
			name:             "triple quoted raw string should be ignored",
			sql:              "SELECT * FROM `real.table.one` WHERE name = r'''a raw string with a ' and a \" and a \\ and a fake table `fake.table.two`'''",
			defaultProjectID: "default-proj",
			want:             []string{"real.table.one"},
			wantErr:          false,
		},
		{
			name:             "triple quoted capital R raw string should be ignored",
			sql:              `SELECT * FROM ` + "`real.table.one`" + ` WHERE name = R"""a raw string with a ' and a " and a \ and a fake table ` + "`fake.table.two`" + `"""`,
			defaultProjectID: "default-proj",
			want:             []string{"real.table.one"},
			wantErr:          false,
		},
		{
			name:             "unquoted fully qualified table",
			sql:              "SELECT * FROM my-project.my_dataset.my_table",
			defaultProjectID: "default-proj",
			want:             []string{"my-project.my_dataset.my_table"},
			wantErr:          false,
		},
		{
			name:             "unquoted partial table with default project",
			sql:              "SELECT * FROM my_dataset.my_table",
			defaultProjectID: "default-proj",
			want:             []string{"default-proj.my_dataset.my_table"},
			wantErr:          false,
		},
		{
			name:             "unquoted partial table without default project",
			sql:              "SELECT * FROM my_dataset.my_table",
			defaultProjectID: "",
			want:             nil,
			wantErr:          true,
		},
		{
			name:             "mixed quoting style 1",
			sql:              "SELECT * FROM `my-project`.my_dataset.my_table",
			defaultProjectID: "default-proj",
			want:             []string{"my-project.my_dataset.my_table"},
			wantErr:          false,
		},
		{
			name:             "mixed quoting style 2",
			sql:              "SELECT * FROM `my-project`.`my_dataset`.my_table",
			defaultProjectID: "default-proj",
			want:             []string{"my-project.my_dataset.my_table"},
			wantErr:          false,
		},
		{
			name:             "mixed quoting style 3",
			sql:              "SELECT * FROM `my-project`.`my_dataset`.`my_table`",
			defaultProjectID: "default-proj",
			want:             []string{"my-project.my_dataset.my_table"},
			wantErr:          false,
		},
		{
			name:             "mixed quoted and unquoted tables",
			sql:              "SELECT * FROM `proj1.data1.tbl1` JOIN proj2.data2.tbl2 ON id",
			defaultProjectID: "default-proj",
			want:             []string{"proj1.data1.tbl1", "proj2.data2.tbl2"},
			wantErr:          false,
		},
		{
			name:             "create table statement",
			sql:              "CREATE TABLE `my-project.my_dataset.my_table` (x INT64)",
			defaultProjectID: "default-proj",
			want:             []string{"my-project.my_dataset.my_table"},
			wantErr:          false,
		},
		{
			name:             "insert into statement",
			sql:              "INSERT INTO `my-project.my_dataset.my_table` (x) VALUES (1)",
			defaultProjectID: "default-proj",
			want:             []string{"my-project.my_dataset.my_table"},
			wantErr:          false,
		},
		{
			name:             "update statement",
			sql:              "UPDATE `my-project.my_dataset.my_table` SET x = 2 WHERE true",
			defaultProjectID: "default-proj",
			want:             []string{"my-project.my_dataset.my_table"},
			wantErr:          false,
		},
		{
			name:             "delete from statement",
			sql:              "DELETE FROM `my-project.my_dataset.my_table` WHERE true",
			defaultProjectID: "default-proj",
			want:             []string{"my-project.my_dataset.my_table"},
			wantErr:          false,
		},
		{
			name:             "merge into statement",
			sql:              "MERGE `proj.data.target` T USING `proj.data.source` S ON T.id = S.id WHEN NOT MATCHED THEN INSERT ROW",
			defaultProjectID: "default-proj",
			want:             []string{"proj.data.source", "proj.data.target"},
			wantErr:          false,
		},
		{
			name:             "create schema statement",
			sql:              "CREATE SCHEMA `my-project.my_dataset`",
			defaultProjectID: "default-proj",
			want:             nil,
			wantErr:          true,
			wantErrMsg:       "dataset-level operations like 'CREATE SCHEMA' are not allowed",
		},
		{
			name:             "create dataset statement",
			sql:              "CREATE DATASET `my-project.my_dataset`",
			defaultProjectID: "default-proj",
			want:             nil,
			wantErr:          true,
			wantErrMsg:       "dataset-level operations like 'CREATE DATASET' are not allowed",
		},
		{
			name:             "drop schema statement",
			sql:              "DROP SCHEMA `my-project.my_dataset`",
			defaultProjectID: "default-proj",
			want:             nil,
			wantErr:          true,
			wantErrMsg:       "dataset-level operations like 'DROP SCHEMA' are not allowed",
		},
		{
			name:             "drop dataset statement",
			sql:              "DROP DATASET `my-project.my_dataset`",
			defaultProjectID: "default-proj",
			want:             nil,
			wantErr:          true,
			wantErrMsg:       "dataset-level operations like 'DROP DATASET' are not allowed",
		},
		{
			name:             "alter schema statement",
			sql:              "ALTER SCHEMA my_dataset SET OPTIONS(description='new description')",
			defaultProjectID: "default-proj",
			want:             nil,
			wantErr:          true,
			wantErrMsg:       "dataset-level operations like 'ALTER SCHEMA' are not allowed",
		},
		{
			name:             "alter dataset statement",
			sql:              "ALTER DATASET my_dataset SET OPTIONS(description='new description')",
			defaultProjectID: "default-proj",
			want:             nil,
			wantErr:          true,
			wantErrMsg:       "dataset-level operations like 'ALTER DATASET' are not allowed",
		},
		{
			name:             "begin...end block",
			sql:              "BEGIN CREATE TABLE `proj.data.tbl1` (x INT64); INSERT `proj.data.tbl2` (y) VALUES (1); END;",
			defaultProjectID: "default-proj",
			want:             []string{"proj.data.tbl1", "proj.data.tbl2"},
			wantErr:          false,
		},
		{
			name: "complex begin...end block with comments and different quoting",
			sql: `
				BEGIN
					-- Create a new table
					CREATE TABLE proj.data.tbl1 (x INT64);
					/* Insert some data from another table */
					INSERT INTO ` + "`proj.data.tbl2`" + ` (y) SELECT y FROM proj.data.source;
				END;`,
			defaultProjectID: "default-proj",
			want:             []string{"proj.data.source", "proj.data.tbl1", "proj.data.tbl2"},
			wantErr:          false,
		},
		{
			name:             "call fully qualified procedure",
			sql:              "CALL my-project.my_dataset.my_procedure()",
			defaultProjectID: "default-proj",
			want:             nil,
			wantErr:          true,
			wantErrMsg:       "CALL is not allowed when dataset restrictions are in place",
		},
		{
			name:             "call partially qualified procedure",
			sql:              "CALL my_dataset.my_procedure()",
			defaultProjectID: "default-proj",
			want:             nil,
			wantErr:          true,
			wantErrMsg:       "CALL is not allowed when dataset restrictions are in place",
		},
		{
			name:             "call procedure in begin...end block",
			sql:              "BEGIN CALL proj.data.proc1(); SELECT * FROM proj.data.tbl1; END;",
			defaultProjectID: "default-proj",
			want:             nil,
			wantErr:          true,
			wantErrMsg:       "CALL is not allowed when dataset restrictions are in place",
		},
		{
			name:             "call procedure with newline",
			sql:              "CALL\nmy_dataset.my_procedure()",
			defaultProjectID: "default-proj",
			want:             nil,
			wantErr:          true,
			wantErrMsg:       "CALL is not allowed when dataset restrictions are in place",
		},
		{
			name:             "call procedure without default project should fail",
			sql:              "CALL my_dataset.my_procedure()",
			defaultProjectID: "",
			want:             nil,
			wantErr:          true,
			wantErrMsg:       "CALL is not allowed when dataset restrictions are in place",
		},
		{
			name:             "create procedure statement",
			sql:              "CREATE PROCEDURE my_dataset.my_procedure() BEGIN SELECT 1; END;",
			defaultProjectID: "default-proj",
			want:             nil,
			wantErr:          true,
			wantErrMsg:       "unanalyzable statements like 'CREATE PROCEDURE' are not allowed",
		},
		{
			name:             "create or replace procedure statement",
			sql:              "CREATE\n OR \nREPLACE \nPROCEDURE my_dataset.my_procedure() BEGIN SELECT 1; END;",
			defaultProjectID: "default-proj",
			want:             nil,
			wantErr:          true,
			wantErrMsg:       "unanalyzable statements like 'CREATE OR REPLACE PROCEDURE' are not allowed",
		},
		{
			name:             "create function statement",
			sql:              "CREATE FUNCTION my_dataset.my_function() RETURNS INT64 AS (1);",
			defaultProjectID: "default-proj",
			want:             nil,
			wantErr:          true,
			wantErrMsg:       "unanalyzable statements like 'CREATE FUNCTION' are not allowed",
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			got, err := bigquerycommon.TableParser(tc.sql, tc.defaultProjectID)
			if (err != nil) != tc.wantErr {
				t.Errorf("TableParser() error = %v, wantErr %v", err, tc.wantErr)
				return
			}
			if tc.wantErr && tc.wantErrMsg != "" {
				if err == nil || !strings.Contains(err.Error(), tc.wantErrMsg) {
					t.Errorf("TableParser() error = %v, want err containing %q", err, tc.wantErrMsg)
				}
			}
			// Sort slices to ensure comparison is order-independent.
			sort.Strings(got)
			sort.Strings(tc.want)
			if diff := cmp.Diff(tc.want, got); diff != "" {
				t.Errorf("TableParser() mismatch (-want +got):\n%s", diff)
			}
		})
	}
}

```

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

```markdown
---
title: "looker-query-url"
type: docs
weight: 1
description: >
  "looker-query-url" generates a url link to a Looker explore.
aliases:
- /resources/tools/looker-query-url
---

## About

The `looker-query-url` generates a url link to an explore in
Looker so the query can be investigated further.

It's compatible with the following sources:

- [looker](../../sources/looker.md)

`looker-query-url` takes nine parameters:

1. the `model`
2. the `explore`
3. the `fields` list
4. an optional set of `filters`
5. an optional set of `pivots`
6. an optional set of `sorts`
7. an optional `limit`
8. an optional `tz`
9. an optional `vis_config`

## Example

```yaml
tools:
    query_url:
        kind: looker-query-url
        source: looker-source
        description: |
          Query URL Tool

          This tool is used to generate the URL of a query in Looker.
          The user can then explore the query further inside Looker.
          The tool also returns the query_id and slug. The parameters
          are the same as the query tool with an additional vis_config
          parameter.

          The vis_config is optional. If provided, it will be used to
          control the default visualization for the query. Here are
          some notes on making visualizations.

          ### Cartesian Charts (Area, Bar, Column, Line, Scatter)

          These chart types share a large number of configuration options.

          **General**
          *   `type`: The type of visualization (`looker_area`, `looker_bar`, `looker_column`, `looker_line`, `looker_scatter`).
          *   `series_types`: Override the chart type for individual series.
          *   `show_view_names`: Display view names in labels and tooltips (`true`/`false`).
          *   `series_labels`: Provide custom names for series.

          **Styling & Colors**
          *   `colors`: An array of color values to be used for the chart series.
          *   `series_colors`: A mapping of series names to specific color values.
          *   `color_application`: Advanced controls for color palette application (collection, palette, reverse, etc.).
          *   `font_size`: Font size for labels (e.g., '12px').

          **Legend**
          *   `hide_legend`: Show or hide the chart legend (`true`/`false`).
          *   `legend_position`: Placement of the legend (`'center'`, `'left'`, `'right'`).

          **Axes**
          *   `swap_axes`: Swap the X and Y axes (`true`/`false`).
          *   `x_axis_scale`: Scale of the x-axis (`'auto'`, `'ordinal'`, `'linear'`, `'time'`).
          *   `x_axis_reversed`, `y_axis_reversed`: Reverse the direction of an axis (`true`/`false`).
          *   `x_axis_gridlines`, `y_axis_gridlines`: Display gridlines for an axis (`true`/`false`).
          *   `show_x_axis_label`, `show_y_axis_label`: Show or hide the axis title (`true`/`false`).
          *   `show_x_axis_ticks`, `show_y_axis_ticks`: Show or hide axis tick marks (`true`/`false`).
          *   `x_axis_label`, `y_axis_label`: Set a custom title for an axis.
          *   `x_axis_datetime_label`: A format string for datetime labels on the x-axis (e.g., `'%Y-%m'`).
          *   `x_padding_left`, `x_padding_right`: Adjust padding on the ends of the x-axis.
          *   `x_axis_label_rotation`, `x_axis_label_rotation_bar`: Set rotation for x-axis labels.
          *   `x_axis_zoom`, `y_axis_zoom`: Enable zooming on an axis (`true`/`false`).
          *   `y_axes`: An array of configuration objects for multiple y-axes.

          **Data & Series**
          *   `stacking`: How to stack series (`''` for none, `'normal'`, `'percent'`).
          *   `ordering`: Order of series in a stack (`'none'`, etc.).
          *   `limit_displayed_rows`: Enable or disable limiting the number of rows displayed (`true`/`false`).
          *   `limit_displayed_rows_values`: Configuration for the row limit (e.g., `{ "first_last": "first", "show_hide": "show", "num_rows": 10 }`).
          *   `discontinuous_nulls`: How to render null values in line charts (`true`/`false`).
          *   `point_style`: Style for points on line and area charts (`'none'`, `'circle'`, `'circle_outline'`).
          *   `series_point_styles`: Override point styles for individual series.
          *   `interpolation`: Line interpolation style (`'linear'`, `'monotone'`, `'step'`, etc.).
          *   `show_value_labels`: Display values on data points (`true`/`false`).
          *   `label_value_format`: A format string for value labels.
          *   `show_totals_labels`: Display total labels on stacked charts (`true`/`false`).
          *   `totals_color`: Color for total labels.
          *   `show_silhouette`: Display a "silhouette" of hidden series in stacked charts (`true`/`false`).
          *   `hidden_series`: An array of series names to hide from the visualization.

          **Scatter/Bubble Specific**
          *   `size_by_field`: The field used to determine the size of bubbles.
          *   `color_by_field`: The field used to determine the color of bubbles.
          *   `plot_size_by_field`: Whether to display the size-by field in the legend.
          *   `cluster_points`: Group nearby points into clusters (`true`/`false`).
          *   `quadrants_enabled`: Display quadrants on the chart (`true`/`false`).
          *   `quadrant_properties`: Configuration for quadrant labels and colors.
          *   `custom_quadrant_value_x`, `custom_quadrant_value_y`: Set quadrant boundaries as a percentage.
          *   `custom_quadrant_point_x`, `custom_quadrant_point_y`: Set quadrant boundaries to a specific value.

          **Miscellaneous**
          *   `reference_lines`: Configuration for displaying reference lines.
          *   `trend_lines`: Configuration for displaying trend lines.
          *   `trellis`: Configuration for creating trellis (small multiple) charts.
          *   `crossfilterEnabled`, `crossfilters`: Configuration for cross-filtering interactions.

          ### Boxplot

          *   Inherits most of the Cartesian chart options.
          *   `type`: Must be `looker_boxplot`.

          ### Funnel

          *   `type`: Must be `looker_funnel`.
          *   `orientation`: How data is read (`'automatic'`, `'dataInRows'`, `'dataInColumns'`).
          *   `percentType`: How percentages are calculated (`'percentOfMaxValue'`, `'percentOfPriorRow'`).
          *   `labelPosition`, `valuePosition`, `percentPosition`: Placement of labels (`'left'`, `'right'`, `'inline'`, `'hidden'`).
          *   `labelColor`, `labelColorEnabled`: Set a custom color for labels.
          *   `labelOverlap`: Allow labels to overlap (`true`/`false`).
          *   `barColors`: An array of colors for the funnel steps.
          *   `color_application`: Advanced color palette controls.
          *   `crossfilterEnabled`, `crossfilters`: Configuration for cross-filtering.

          ### Pie / Donut

          *   `type`: Must be `looker_pie`.
          *   `value_labels`: Where to display values (`'legend'`, `'labels'`).
          *   `label_type`: The format of data labels (`'labPer'`, `'labVal'`, `'lab'`, `'val'`, `'per'`).
          *   `start_angle`, `end_angle`: The start and end angles of the pie chart.
          *   `inner_radius`: The inner radius, used to create a donut chart.
          *   `series_colors`, `series_labels`: Override colors and labels for specific slices.
          *   `color_application`: Advanced color palette controls.
          *   `crossfilterEnabled`, `crossfilters`: Configuration for cross-filtering.
          *   `advanced_vis_config`: A string containing JSON for advanced Highcharts configuration.

          ### Waterfall

          *   Inherits most of the Cartesian chart options.
          *   `type`: Must be `looker_waterfall`.
          *   `up_color`: Color for positive (increasing) values.
          *   `down_color`: Color for negative (decreasing) values.
          *   `total_color`: Color for the total bar.

          ### Word Cloud

          *   `type`: Must be `looker_wordcloud`.
          *   `rotation`: Enable random word rotation (`true`/`false`).
          *   `colors`: An array of colors for the words.
          *   `color_application`: Advanced color palette controls.
          *   `crossfilterEnabled`, `crossfilters`: Configuration for cross-filtering.

          These are some sample vis_config settings.

          A bar chart -
          {{
            "defaults_version": 1,
            "label_density": 25,
            "legend_position": "center",
            "limit_displayed_rows": false,
            "ordering": "none",
            "plot_size_by_field": false,
            "point_style": "none",
            "show_null_labels": false,
            "show_silhouette": false,
            "show_totals_labels": false,
            "show_value_labels": false,
            "show_view_names": false,
            "show_x_axis_label": true,
            "show_x_axis_ticks": true,
            "show_y_axis_labels": true,
            "show_y_axis_ticks": true,
            "stacking": "normal",
            "totals_color": "#808080",
            "trellis": "",
            "type": "looker_bar",
            "x_axis_gridlines": false,
            "x_axis_reversed": false,
            "x_axis_scale": "auto",
            "x_axis_zoom": true,
            "y_axis_combined": true,
            "y_axis_gridlines": true,
            "y_axis_reversed": false,
            "y_axis_scale_mode": "linear",
            "y_axis_tick_density": "default",
            "y_axis_tick_density_custom": 5,
            "y_axis_zoom": true
          }}

          A column chart with an option advanced_vis_config -
          {{
            "advanced_vis_config": "{ chart: { type: 'pie', spacingBottom: 50, spacingLeft: 50, spacingRight: 50, spacingTop: 50, }, legend: { enabled: false, }, plotOptions: { pie: { dataLabels: { enabled: true, format: '\u003cb\u003e{key}\u003c/b\u003e\u003cspan style=\"font-weight: normal\"\u003e - {percentage:.2f}%\u003c/span\u003e', }, showInLegend: false, }, }, series: [], }",
            "colors": [
              "grey"
            ],
            "defaults_version": 1,
            "hidden_fields": [],
            "label_density": 25,
            "legend_position": "center",
            "limit_displayed_rows": false,
            "note_display": "below",
            "note_state": "collapsed",
            "note_text": "Unsold inventory only",
            "ordering": "none",
            "plot_size_by_field": false,
            "point_style": "none",
            "series_colors": {},
            "show_null_labels": false,
            "show_silhouette": false,
            "show_totals_labels": false,
            "show_value_labels": true,
            "show_view_names": false,
            "show_x_axis_label": true,
            "show_x_axis_ticks": true,
            "show_y_axis_labels": true,
            "show_y_axis_ticks": true,
            "stacking": "normal",
            "totals_color": "#808080",
            "trellis": "",
            "type": "looker_column",
            "x_axis_gridlines": false,
            "x_axis_reversed": false,
            "x_axis_scale": "auto",
            "x_axis_zoom": true,
            "y_axes": [],
            "y_axis_combined": true,
            "y_axis_gridlines": true,
            "y_axis_reversed": false,
            "y_axis_scale_mode": "linear",
            "y_axis_tick_density": "default",
            "y_axis_tick_density_custom": 5,
            "y_axis_zoom": true
          }}

          A line chart -
          {{
            "defaults_version": 1,
            "hidden_pivots": {},
            "hidden_series": [],
            "interpolation": "linear",
            "label_density": 25,
            "legend_position": "center",
            "limit_displayed_rows": false,
            "plot_size_by_field": false,
            "point_style": "none",
            "series_types": {},
            "show_null_points": true,
            "show_value_labels": false,
            "show_view_names": false,
            "show_x_axis_label": true,
            "show_x_axis_ticks": true,
            "show_y_axis_labels": true,
            "show_y_axis_ticks": true,
            "stacking": "",
            "trellis": "",
            "type": "looker_line",
            "x_axis_gridlines": false,
            "x_axis_reversed": false,
            "x_axis_scale": "auto",
            "y_axis_combined": true,
            "y_axis_gridlines": true,
            "y_axis_reversed": false,
            "y_axis_scale_mode": "linear",
            "y_axis_tick_density": "default",
            "y_axis_tick_density_custom": 5
          }}

          An area chart -
          {{
            "defaults_version": 1,
            "interpolation": "linear",
            "label_density": 25,
            "legend_position": "center",
            "limit_displayed_rows": false,
            "plot_size_by_field": false,
            "point_style": "none",
            "series_types": {},
            "show_null_points": true,
            "show_silhouette": false,
            "show_totals_labels": false,
            "show_value_labels": false,
            "show_view_names": false,
            "show_x_axis_label": true,
            "show_x_axis_ticks": true,
            "show_y_axis_labels": true,
            "show_y_axis_ticks": true,
            "stacking": "normal",
            "totals_color": "#808080",
            "trellis": "",
            "type": "looker_area",
            "x_axis_gridlines": false,
            "x_axis_reversed": false,
            "x_axis_scale": "auto",
            "x_axis_zoom": true,
            "y_axis_combined": true,
            "y_axis_gridlines": true,
            "y_axis_reversed": false,
            "y_axis_scale_mode": "linear",
            "y_axis_tick_density": "default",
            "y_axis_tick_density_custom": 5,
            "y_axis_zoom": true
          }}

          A scatter plot -
          {{
            "cluster_points": false,
            "custom_quadrant_point_x": 5,
            "custom_quadrant_point_y": 5,
            "custom_value_label_column": "",
            "custom_x_column": "",
            "custom_y_column": "",
            "defaults_version": 1,
            "hidden_fields": [],
            "hidden_pivots": {},
            "hidden_points_if_no": [],
            "hidden_series": [],
            "interpolation": "linear",
            "label_density": 25,
            "legend_position": "center",
            "limit_displayed_rows": false,
            "limit_displayed_rows_values": {
              "first_last": "first",
              "num_rows": 0,
              "show_hide": "hide"
            },
            "plot_size_by_field": false,
            "point_style": "circle",
            "quadrant_properties": {
              "0": {
                "color": "",
                "label": "Quadrant 1"
              },
              "1": {
                "color": "",
                "label": "Quadrant 2"
              },
              "2": {
                "color": "",
                "label": "Quadrant 3"
              },
              "3": {
                "color": "",
                "label": "Quadrant 4"
              }
            },
            "quadrants_enabled": false,
            "series_labels": {},
            "series_types": {},
            "show_null_points": false,
            "show_value_labels": false,
            "show_view_names": true,
            "show_x_axis_label": true,
            "show_x_axis_ticks": true,
            "show_y_axis_labels": true,
            "show_y_axis_ticks": true,
            "size_by_field": "roi",
            "stacking": "normal",
            "swap_axes": true,
            "trellis": "",
            "type": "looker_scatter",
            "x_axis_gridlines": false,
            "x_axis_reversed": false,
            "x_axis_scale": "auto",
            "x_axis_zoom": true,
            "y_axes": [
              {
                "label": "",
                "orientation": "bottom",
                "series": [
                  {
                    "axisId": "Channel_0 - average_of_roi_first",
                    "id": "Channel_0 - average_of_roi_first",
                    "name": "Channel_0"
                  },
                  {
                    "axisId": "Channel_1 - average_of_roi_first",
                    "id": "Channel_1 - average_of_roi_first",
                    "name": "Channel_1"
                  },
                  {
                    "axisId": "Channel_2 - average_of_roi_first",
                    "id": "Channel_2 - average_of_roi_first",
                    "name": "Channel_2"
                  },
                  {
                    "axisId": "Channel_3 - average_of_roi_first",
                    "id": "Channel_3 - average_of_roi_first",
                    "name": "Channel_3"
                  },
                  {
                    "axisId": "Channel_4 - average_of_roi_first",
                    "id": "Channel_4 - average_of_roi_first",
                    "name": "Channel_4"
                  }
                ],
                "showLabels": true,
                "showValues": true,
                "tickDensity": "custom",
                "tickDensityCustom": 100,
                "type": "linear",
                "unpinAxis": false
              }
            ],
            "y_axis_combined": true,
            "y_axis_gridlines": true,
            "y_axis_reversed": false,
            "y_axis_scale_mode": "linear",
            "y_axis_tick_density": "default",
            "y_axis_tick_density_custom": 5,
            "y_axis_zoom": true
          }}

          A single record visualization -
          {{
            "defaults_version": 1,
            "show_view_names": false,
            "type": "looker_single_record"
          }}

          A single value visualization -
          {{
            "comparison_reverse_colors": false,
            "comparison_type": "value",                                                                                                                                            "conditional_formatting_include_nulls": false,                                                                                                                         "conditional_formatting_include_totals": false,
            "custom_color": "#1A73E8",
            "custom_color_enabled": true,
            "defaults_version": 1,
            "enable_conditional_formatting": false,
            "series_types": {},
            "show_comparison": false,
            "show_comparison_label": true,
            "show_single_value_title": true,
            "single_value_title": "Total Clicks",
            "type": "single_value"
          }}

          A Pie chart -
          {{
            "defaults_version": 1,
            "label_density": 25,
            "label_type": "labPer",
            "legend_position": "center",
            "limit_displayed_rows": false,
            "ordering": "none",
            "plot_size_by_field": false,
            "point_style": "none",
            "series_types": {},
            "show_null_labels": false,
            "show_silhouette": false,
            "show_totals_labels": false,
            "show_value_labels": false,
            "show_view_names": false,
            "show_x_axis_label": true,
            "show_x_axis_ticks": true,
            "show_y_axis_labels": true,
            "show_y_axis_ticks": true,
            "stacking": "",
            "totals_color": "#808080",
            "trellis": "",
            "type": "looker_pie",
            "value_labels": "legend",
            "x_axis_gridlines": false,
            "x_axis_reversed": false,
            "x_axis_scale": "auto",
            "y_axis_combined": true,
            "y_axis_gridlines": true,
            "y_axis_reversed": false,
            "y_axis_scale_mode": "linear",
            "y_axis_tick_density": "default",
            "y_axis_tick_density_custom": 5
          }}

          The result is a JSON object with the id, slug, the url, and
          the long_url.
```

## Reference

| **field**   |                  **type**                  | **required** | **description**                                                                                  |
|-------------|:------------------------------------------:|:------------:|--------------------------------------------------------------------------------------------------|
| kind        |                   string                   |     true     | Must be "looker-query-url"                                                                       |
| source      |                   string                   |     true     | Name of the source the SQL should execute on.                                                    |
| description |                   string                   |     true     | Description of the tool that is passed to the LLM.                                               |

```

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

```javascript
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import { handleRunTool, displayResults } from './runTool.js';
import { createGoogleAuthMethodItem } from './auth.js'

/**
 * Helper function to create form inputs for parameters.
 */
function createParamInput(param, toolId) {
    const paramItem = document.createElement('div');
    paramItem.className = 'param-item';

    const label = document.createElement('label');
    const INPUT_ID = `param-${toolId}-${param.name}`;
    const NAME_TEXT = document.createTextNode(param.name);
    label.setAttribute('for', INPUT_ID);
    label.appendChild(NAME_TEXT);

    const IS_AUTH_PARAM = param.authServices && param.authServices.length > 0;
    let additionalLabelText = '';
    if (IS_AUTH_PARAM) {
        additionalLabelText += ' (auth)';
    }
    if (!param.required) {
        additionalLabelText += ' (optional)';
    }

    if (additionalLabelText) {
        const additionalSpan = document.createElement('span');
        additionalSpan.textContent = additionalLabelText;
        additionalSpan.classList.add('param-label-extras');
        label.appendChild(additionalSpan);
    }
    paramItem.appendChild(label);

    const inputCheckboxWrapper = document.createElement('div');
    const inputContainer = document.createElement('div');
    inputCheckboxWrapper.className = 'input-checkbox-wrapper';
    inputContainer.className = 'param-input-element-container';

    // Build parameter's value input box.
    const PLACEHOLDER_LABEL = param.label;
    let inputElement;
    let boolValueLabel = null;

    if (param.type === 'textarea') {
        inputElement = document.createElement('textarea');
        inputElement.rows = 3;
        inputContainer.appendChild(inputElement);
    } else if(param.type === 'checkbox') {
        inputElement = document.createElement('input');
        inputElement.type = 'checkbox';
        inputElement.title = PLACEHOLDER_LABEL;
        inputElement.checked = false;

        // handle true/false label for boolean params
        boolValueLabel = document.createElement('span');
        boolValueLabel.className = 'checkbox-bool-label';
        boolValueLabel.textContent = inputElement.checked ? ' true' : ' false';

        inputContainer.appendChild(inputElement); 
        inputContainer.appendChild(boolValueLabel); 

        inputElement.addEventListener('change', () => {
            boolValueLabel.textContent = inputElement.checked ? ' true' : ' false';
        });
    } else {
        inputElement = document.createElement('input');
        inputElement.type = param.type;
        inputContainer.appendChild(inputElement);
    }

    inputElement.id = INPUT_ID;
    inputElement.name = param.name;
    inputElement.classList.add('param-input-element');

    if (IS_AUTH_PARAM) {
        inputElement.disabled = true;
        inputElement.classList.add('auth-param-input');
        if (param.type !== 'checkbox') {
            inputElement.placeholder = param.authServices;
        }
    } else if (param.type !== 'checkbox') {
        inputElement.placeholder = PLACEHOLDER_LABEL ? PLACEHOLDER_LABEL.trim() : '';
    }
    inputCheckboxWrapper.appendChild(inputContainer);

    // create the "Include Param" checkbox
    const INCLUDE_CHECKBOX_ID = `include-${INPUT_ID}`;
    const includeContainer = document.createElement('div');
    const includeCheckbox = document.createElement('input');

    includeContainer.className = 'include-param-container';
    includeCheckbox.type = 'checkbox';
    includeCheckbox.id = INCLUDE_CHECKBOX_ID;
    includeCheckbox.name = `include-${param.name}`;
    includeCheckbox.title = 'Include this parameter'; // Add a tooltip

    // default to checked, unless it's an optional parameter
    includeCheckbox.checked = param.required;

    includeContainer.appendChild(includeCheckbox);
    inputCheckboxWrapper.appendChild(includeContainer);

    paramItem.appendChild(inputCheckboxWrapper);

    // function to update UI based on checkbox state
    const updateParamIncludedState = () => {
        const isIncluded = includeCheckbox.checked;
        if (isIncluded) {
            paramItem.classList.remove('disabled-param');
            if (!IS_AUTH_PARAM) {
                 inputElement.disabled = false;
            }
            if (boolValueLabel) {
                boolValueLabel.classList.remove('disabled');
            }
        } else {
            paramItem.classList.add('disabled-param');
            inputElement.disabled = true;
            if (boolValueLabel) {
                boolValueLabel.classList.add('disabled');
            }
        }
    };

    // add event listener to the include checkbox
    includeCheckbox.addEventListener('change', updateParamIncludedState);
    updateParamIncludedState(); 

    return paramItem;
}

/**
 * Function to create the header editor popup modal.
 * @param {string} toolId The unique identifier for the tool.
 * @param {!Object<string, string>} currentHeaders The current headers.
 * @param {function(!Object<string, string>): void} saveCallback A function to be
 *     called when the "Save" button is clicked and the headers are successfully
 *     parsed. The function receives the updated headers object as its argument.
 * @return {!HTMLDivElement} The outermost div element of the created modal.
 */
function createHeaderEditorModal(toolId, currentHeaders, toolParameters, authRequired, saveCallback) {
    const MODAL_ID = `header-modal-${toolId}`;
    let modal = document.getElementById(MODAL_ID);

    if (modal) {
        modal.remove(); 
    }

    modal = document.createElement('div');
    modal.id = MODAL_ID;
    modal.className = 'header-modal';

    const modalContent = document.createElement('div');
    const modalHeader = document.createElement('h5');
    const headersTextarea = document.createElement('textarea');

    modalContent.className = 'header-modal-content';
    modalHeader.textContent = 'Edit Request Headers';
    headersTextarea.id = `headers-textarea-${toolId}`;
    headersTextarea.className = 'headers-textarea';
    headersTextarea.rows = 10;
    headersTextarea.value = JSON.stringify(currentHeaders, null, 2);

    // handle authenticated params
    const authProfileNames = new Set();
    toolParameters.forEach(param => {
        const isAuthParam = param.authServices && param.authServices.length > 0;
        if (isAuthParam && param.authServices) {
             param.authServices.forEach(name => authProfileNames.add(name));
        }
    });

    // handle authorized invocations
    if (authRequired && authRequired.length > 0) {
        authRequired.forEach(name => authProfileNames.add(name));
    }

    modalContent.appendChild(modalHeader);
    modalContent.appendChild(headersTextarea);

    if (authProfileNames.size > 0 || authRequired.length > 0) {
        const authHelperSection = document.createElement('div');
        authHelperSection.className = 'auth-helper-section';
        const authList = document.createElement('div');
        authList.className = 'auth-method-list';

        authProfileNames.forEach(profileName => {
            const authItem = createGoogleAuthMethodItem(toolId, profileName);
            authList.appendChild(authItem);
        });
        authHelperSection.appendChild(authList);
        modalContent.appendChild(authHelperSection);
    }

    const modalActions = document.createElement('div');
    const closeButton = document.createElement('button');
    const saveButton = document.createElement('button');
    const authTokenDropdown = createAuthTokenInfoDropdown();

    modalActions.className = 'header-modal-actions';
    closeButton.textContent = 'Close';
    closeButton.className = 'btn btn--closeHeaders';
    closeButton.addEventListener('click', () => closeHeaderEditor(toolId));
    saveButton.textContent = 'Save';
    saveButton.className = 'btn btn--saveHeaders';
    saveButton.addEventListener('click', () => {
        try {
            const updatedHeaders = JSON.parse(headersTextarea.value);
            saveCallback(updatedHeaders);
            closeHeaderEditor(toolId);
        } catch (e) {
            alert('Invalid JSON format for headers.');
            console.error("Header JSON parse error:", e);
        }
    });

    modalActions.appendChild(closeButton);
    modalActions.appendChild(saveButton);
    modalContent.appendChild(modalActions);
    modalContent.appendChild(authTokenDropdown);
    modal.appendChild(modalContent);

    return modal;
}

/**
 * Function to open the header popup.
 */
function openHeaderEditor(toolId) {
    const modal = document.getElementById(`header-modal-${toolId}`);
    if (modal) {
        modal.style.display = 'block';
    }
}

/**
 * Function to close the header popup.
 */
function closeHeaderEditor(toolId) {
    const modal = document.getElementById(`header-modal-${toolId}`);
    if (modal) {
        modal.style.display = 'none';
    }
}

/**
 * Creates a dropdown element showing information on how to extract Google auth tokens.
 * @return {HTMLDetailsElement} The details element representing the dropdown.
 */
function createAuthTokenInfoDropdown() {
    const details = document.createElement('details');
    const summary = document.createElement('summary');
    const content = document.createElement('div');

    details.className = 'auth-token-details';
    details.appendChild(summary);
    summary.textContent = 'How to extract Google OAuth ID Token manually';
    content.className = 'auth-token-content';

    // auth instruction dropdown
    const tabButtons = document.createElement('div');
    const leftTab = document.createElement('button');
    const rightTab = document.createElement('button');
    
    tabButtons.className = 'auth-tab-group';
    leftTab.className = 'auth-tab-picker active';
    leftTab.textContent = 'With Standard Account';
    leftTab.setAttribute('data-tab', 'standard');
    rightTab.className = 'auth-tab-picker';
    rightTab.textContent = 'With Service Account';
    rightTab.setAttribute('data-tab', 'service');

    tabButtons.appendChild(leftTab);
    tabButtons.appendChild(rightTab);
    content.appendChild(tabButtons);

    const tabContentContainer = document.createElement('div');
    const standardAccInstructions = document.createElement('div');
    const serviceAccInstructions = document.createElement('div');

    standardAccInstructions.id = 'auth-tab-standard';
    standardAccInstructions.className = 'auth-tab-content active'; 
    standardAccInstructions.innerHTML = AUTH_TOKEN_INSTRUCTIONS_STANDARD;
    serviceAccInstructions.id = 'auth-tab-service';
    serviceAccInstructions.className = 'auth-tab-content';
    serviceAccInstructions.innerHTML = AUTH_TOKEN_INSTRUCTIONS_SERVICE_ACCOUNT;

    tabContentContainer.appendChild(standardAccInstructions);
    tabContentContainer.appendChild(serviceAccInstructions);
    content.appendChild(tabContentContainer);

    // switching tabs logic
    const tabBtns = [leftTab, rightTab];
    const tabContents = [standardAccInstructions, serviceAccInstructions];

    tabBtns.forEach(btn => {
        btn.addEventListener('click', () => {
            // deactivate all buttons and contents
            tabBtns.forEach(b => b.classList.remove('active'));
            tabContents.forEach(c => c.classList.remove('active'));

            btn.classList.add('active');

            const tabId = btn.getAttribute('data-tab');
            const activeContent = content.querySelector(`#auth-tab-${tabId}`);
            if (activeContent) {
                activeContent.classList.add('active');
            }
        });
    });

    details.appendChild(content);
    return details;
}

/**
 * Renders the tool display area.
 */
export function renderToolInterface(tool, containerElement) {
    const TOOL_ID = tool.id;
    containerElement.innerHTML = '';

    let lastResults = null;
    let currentHeaders = {
        "Content-Type": "application/json"
    };

    // function to update lastResults so we can toggle json
    const updateLastResults = (newResults) => {
        lastResults = newResults;
    };
    const updateCurrentHeaders = (newHeaders) => {
        currentHeaders = newHeaders;
        const newModal = createHeaderEditorModal(TOOL_ID, currentHeaders, tool.parameters, tool.authRequired, updateCurrentHeaders);
        containerElement.appendChild(newModal);
    };

    const gridContainer = document.createElement('div');
    gridContainer.className = 'tool-details-grid';

    const toolInfoContainer = document.createElement('div');
    const nameBox = document.createElement('div');
    const descBox = document.createElement('div');

    nameBox.className = 'tool-box tool-name';
    nameBox.innerHTML = `<h5>Name:</h5><p>${tool.name}</p>`;
    descBox.className = 'tool-box tool-description';
    descBox.innerHTML = `<h5>Description:</h5><p>${tool.description}</p>`;

    toolInfoContainer.className = 'tool-info';
    toolInfoContainer.appendChild(nameBox);
    toolInfoContainer.appendChild(descBox);
    gridContainer.appendChild(toolInfoContainer);

    const DISLCAIMER_INFO = "*Checked parameters are sent with the value from their text field. Empty fields will be sent as an empty string. To exclude a parameter, uncheck it."
    const paramsContainer = document.createElement('div');
    const form = document.createElement('form');
    const paramsHeader = document.createElement('div');
    const disclaimerText = document.createElement('div');

    paramsContainer.className = 'tool-params tool-box';
    paramsContainer.innerHTML = '<h5>Parameters:</h5>';
    paramsHeader.className = 'params-header';
    paramsContainer.appendChild(paramsHeader);
    disclaimerText.textContent = DISLCAIMER_INFO;
    disclaimerText.className = 'params-disclaimer'; 
    paramsContainer.appendChild(disclaimerText);

    form.id = `tool-params-form-${TOOL_ID}`;

    tool.parameters.forEach(param => {
        form.appendChild(createParamInput(param, TOOL_ID));
    });
    paramsContainer.appendChild(form);
    gridContainer.appendChild(paramsContainer);

    containerElement.appendChild(gridContainer);

    const RESPONSE_AREA_ID = `tool-response-area-${TOOL_ID}`;
    const runButtonContainer = document.createElement('div');
    const editHeadersButton = document.createElement('button');
    const runButton = document.createElement('button');

    editHeadersButton.className = 'btn btn--editHeaders';
    editHeadersButton.textContent = 'Edit Headers';
    editHeadersButton.addEventListener('click', () => openHeaderEditor(TOOL_ID));
    runButtonContainer.className = 'run-button-container';
    runButtonContainer.appendChild(editHeadersButton);

    runButton.className = 'btn btn--run';
    runButton.textContent = 'Run Tool';
    runButtonContainer.appendChild(runButton);
    containerElement.appendChild(runButtonContainer);

    // response Area (bottom)
    const responseContainer = document.createElement('div');
    const responseHeaderControls = document.createElement('div');
    const responseHeader = document.createElement('h5');
    const responseArea = document.createElement('textarea');

    responseContainer.className = 'tool-response tool-box';
    responseHeaderControls.className = 'response-header-controls';
    responseHeader.textContent = 'Response:';
    responseHeaderControls.appendChild(responseHeader);

    // prettify box
    const PRETTIFY_ID = `prettify-${TOOL_ID}`;
    const prettifyDiv = document.createElement('div');
    const prettifyLabel = document.createElement('label');
    const prettifyCheckbox = document.createElement('input');

    prettifyDiv.className = 'prettify-container';
    prettifyLabel.setAttribute('for', PRETTIFY_ID);
    prettifyLabel.textContent = 'Prettify JSON';
    prettifyLabel.className = 'prettify-label';

    prettifyCheckbox.type = 'checkbox';
    prettifyCheckbox.id = PRETTIFY_ID;
    prettifyCheckbox.checked = true;
    prettifyCheckbox.className = 'prettify-checkbox';

    prettifyDiv.appendChild(prettifyLabel);
    prettifyDiv.appendChild(prettifyCheckbox);

    responseHeaderControls.appendChild(prettifyDiv);
    responseContainer.appendChild(responseHeaderControls);

    responseArea.id = RESPONSE_AREA_ID;
    responseArea.readOnly = true;
    responseArea.placeholder = 'Results will appear here...';
    responseArea.className = 'tool-response-area';
    responseArea.rows = 10;
    responseContainer.appendChild(responseArea);

    containerElement.appendChild(responseContainer);

    // create and append the header editor modal
    const headerModal = createHeaderEditorModal(TOOL_ID, currentHeaders, tool.parameters, tool.authRequired, updateCurrentHeaders);
    containerElement.appendChild(headerModal);

    prettifyCheckbox.addEventListener('change', () => {
        if (lastResults) {
            displayResults(lastResults, responseArea, prettifyCheckbox.checked);
        }
    });

    runButton.addEventListener('click', (event) => {
        event.preventDefault();
        handleRunTool(TOOL_ID, form, responseArea, tool.parameters, prettifyCheckbox, updateLastResults, currentHeaders);
    });
}

/**
 * Checks if a specific parameter is marked as included for a given tool.
 * @param {string} toolId The ID of the tool.
 * @param {string} paramName The name of the parameter.
 * @return {boolean|null} True if the parameter's include checkbox is checked,
 *                         False if unchecked, Null if the checkbox element is not found.
 */
export function isParamIncluded(toolId, paramName) {
    const inputId = `param-${toolId}-${paramName}`;
    const includeCheckboxId = `include-${inputId}`;
    const includeCheckbox = document.getElementById(includeCheckboxId);

    if (includeCheckbox && includeCheckbox.type === 'checkbox') {
        return includeCheckbox.checked;
    }

    console.warn(`Include checkbox not found for ID: ${includeCheckboxId}`);
    return null;
}

// Templates for inserting token retrieval instructions into edit header modal
const AUTH_TOKEN_INSTRUCTIONS_SERVICE_ACCOUNT = `
        <p>To obtain a Google OAuth ID token using a service account:</p>
        <ol>
            <li>Make sure you are on the intended SERVICE account (typically contain iam.gserviceaccount.com). Verify by running the command below.
                <pre><code>gcloud auth list</code></pre>
            </li>
            <li>Print an id token with the audience set to your clientID defined in tools file:
                <pre><code>gcloud auth print-identity-token --audiences=YOUR_CLIENT_ID_HERE</code></pre>
            </li>
            <li>Copy the output token.</li>
            <li>Paste this token into the header in JSON editor. The key should be the name of your auth service followed by <code>_token</code>
                <pre><code>{
  "Content-Type": "application/json",
  "my-google-auth_token": "YOUR_ID_TOKEN_HERE"
}               </code></pre>
            </li>
        </ol>
        <p>This token is typically short-lived.</p>`;

const AUTH_TOKEN_INSTRUCTIONS_STANDARD = `
        <p>To obtain a Google OAuth ID token using a standard account:</p>
        <ol>
            <li>Make sure you are on your intended standard account. Verify by running the command below.
                <pre><code>gcloud auth list</code></pre>
            </li>
            <li>Within your Cloud Console, add the following link to the "Authorized Redirect URIs".</li>
            <pre><code>https://developers.google.com/oauthplayground</code></pre>
            <li>Go to the Google OAuth Playground site: <a href="https://developers.google.com/oauthplayground/" target="_blank">https://developers.google.com/oauthplayground/</a></li>
            <li>In the top right settings menu, select "Use your own OAuth Credentials".</li>
            <li>Input your clientID (from tools file), along with the client secret from Cloud Console.</li>
            <li>Inside the Google OAuth Playground, select "Google OAuth2 API v2.</li>
            <ul>
                <li>Select "Authorize APIs".</li>
                <li>Select "Exchange Authorization codes for tokens"</li>
                <li>Copy the id_token field provided in the response.</li>
            </ul>
            <li>Paste this token into the header in JSON editor. The key should be the name of your auth service followed by <code>_token</code>
                <pre><code>{
  "Content-Type": "application/json",
  "my-google-auth_token": "YOUR_ID_TOKEN_HERE"
}               </code></pre>
            </li>
        </ol>
        <p>This token is typically short-lived.</p>`;
```

--------------------------------------------------------------------------------
/tests/postgres/postgres_integration_test.go:
--------------------------------------------------------------------------------

```go
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package postgres

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"reflect"
	"regexp"
	"sort"
	"strings"
	"sync"
	"testing"
	"time"

	"github.com/google/go-cmp/cmp"
	"github.com/google/uuid"
	"github.com/googleapis/genai-toolbox/internal/testutils"
	"github.com/googleapis/genai-toolbox/tests"
	"github.com/jackc/pgx/v5/pgxpool"
)

var (
	PostgresSourceKind                      = "postgres"
	PostgresToolKind                        = "postgres-sql"
	PostgresListTablesToolKind              = "postgres-list-tables"
	PostgresListActiveQueriesToolKind       = "postgres-list-active-queries"
	PostgresListInstalledExtensionsToolKind = "postgres-list-installed-extensions"
	PostgresListAvailableExtensionsToolKind = "postgres-list-available-extensions"
	PostgresDatabase                        = os.Getenv("POSTGRES_DATABASE")
	PostgresHost                            = os.Getenv("POSTGRES_HOST")
	PostgresPort                            = os.Getenv("POSTGRES_PORT")
	PostgresUser                            = os.Getenv("POSTGRES_USER")
	PostgresPass                            = os.Getenv("POSTGRES_PASS")
)

func getPostgresVars(t *testing.T) map[string]any {
	switch "" {
	case PostgresDatabase:
		t.Fatal("'POSTGRES_DATABASE' not set")
	case PostgresHost:
		t.Fatal("'POSTGRES_HOST' not set")
	case PostgresPort:
		t.Fatal("'POSTGRES_PORT' not set")
	case PostgresUser:
		t.Fatal("'POSTGRES_USER' not set")
	case PostgresPass:
		t.Fatal("'POSTGRES_PASS' not set")
	}

	return map[string]any{
		"kind":     PostgresSourceKind,
		"host":     PostgresHost,
		"port":     PostgresPort,
		"database": PostgresDatabase,
		"user":     PostgresUser,
		"password": PostgresPass,
	}
}

func addPrebuiltToolConfig(t *testing.T, config map[string]any) map[string]any {
	tools, ok := config["tools"].(map[string]any)
	if !ok {
		t.Fatalf("unable to get tools from config")
	}
	tools["list_tables"] = map[string]any{
		"kind":        PostgresListTablesToolKind,
		"source":      "my-instance",
		"description": "Lists tables in the database.",
	}
	tools["list_active_queries"] = map[string]any{
		"kind":        PostgresListActiveQueriesToolKind,
		"source":      "my-instance",
		"description": "Lists active queries in the database.",
	}

	tools["list_installed_extensions"] = map[string]any{
		"kind":        PostgresListInstalledExtensionsToolKind,
		"source":      "my-instance",
		"description": "Lists installed extensions in the database.",
	}

	tools["list_available_extensions"] = map[string]any{
		"kind":        PostgresListAvailableExtensionsToolKind,
		"source":      "my-instance",
		"description": "Lists available extensions in the database.",
	}

	config["tools"] = tools
	return config
}

// Copied over from postgres.go
func initPostgresConnectionPool(host, port, user, pass, dbname string) (*pgxpool.Pool, error) {
	// urlExample := "postgres:dd//username:password@localhost:5432/database_name"
	url := &url.URL{
		Scheme: "postgres",
		User:   url.UserPassword(user, pass),
		Host:   fmt.Sprintf("%s:%s", host, port),
		Path:   dbname,
	}
	pool, err := pgxpool.New(context.Background(), url.String())
	if err != nil {
		return nil, fmt.Errorf("Unable to create connection pool: %w", err)
	}

	return pool, nil
}

func TestPostgres(t *testing.T) {
	sourceConfig := getPostgresVars(t)
	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
	defer cancel()

	var args []string

	pool, err := initPostgresConnectionPool(PostgresHost, PostgresPort, PostgresUser, PostgresPass, PostgresDatabase)
	if err != nil {
		t.Fatalf("unable to create postgres connection pool: %s", err)
	}

	// cleanup test environment
	tests.CleanupPostgresTables(t, ctx, pool)

	// create table name with UUID
	tableNameParam := "param_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
	tableNameAuth := "auth_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
	tableNameTemplateParam := "template_param_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")

	// set up data for param tool
	createParamTableStmt, insertParamTableStmt, paramToolStmt, idParamToolStmt, nameParamToolStmt, arrayToolStmt, paramTestParams := tests.GetPostgresSQLParamToolInfo(tableNameParam)
	teardownTable1 := tests.SetupPostgresSQLTable(t, ctx, pool, createParamTableStmt, insertParamTableStmt, tableNameParam, paramTestParams)
	defer teardownTable1(t)

	// set up data for auth tool
	createAuthTableStmt, insertAuthTableStmt, authToolStmt, authTestParams := tests.GetPostgresSQLAuthToolInfo(tableNameAuth)
	teardownTable2 := tests.SetupPostgresSQLTable(t, ctx, pool, createAuthTableStmt, insertAuthTableStmt, tableNameAuth, authTestParams)
	defer teardownTable2(t)

	// Write config into a file and pass it to command
	toolsFile := tests.GetToolsConfig(sourceConfig, PostgresToolKind, paramToolStmt, idParamToolStmt, nameParamToolStmt, arrayToolStmt, authToolStmt)
	toolsFile = tests.AddExecuteSqlConfig(t, toolsFile, "postgres-execute-sql")
	tmplSelectCombined, tmplSelectFilterCombined := tests.GetPostgresSQLTmplToolStatement()
	toolsFile = tests.AddTemplateParamConfig(t, toolsFile, PostgresToolKind, tmplSelectCombined, tmplSelectFilterCombined, "")

	toolsFile = addPrebuiltToolConfig(t, toolsFile)

	cmd, cleanup, err := tests.StartCmd(ctx, toolsFile, args...)
	if err != nil {
		t.Fatalf("command initialization returned an error: %s", err)
	}
	defer cleanup()

	waitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
	defer cancel()
	out, err := testutils.WaitForString(waitCtx, regexp.MustCompile(`Server ready to serve`), cmd.Out)
	if err != nil {
		t.Logf("toolbox command logs: \n%s", out)
		t.Fatalf("toolbox didn't start successfully: %s", err)
	}

	// Get configs for tests
	select1Want, mcpMyFailToolWant, createTableStatement, mcpSelect1Want := tests.GetPostgresWants()

	// Run tests
	tests.RunToolGetTest(t)
	tests.RunToolInvokeTest(t, select1Want)
	tests.RunMCPToolCallMethod(t, mcpMyFailToolWant, mcpSelect1Want)
	tests.RunExecuteSqlToolInvokeTest(t, createTableStatement, select1Want)
	tests.RunToolInvokeWithTemplateParameters(t, tableNameTemplateParam)

	// Run specific Postgres tool tests
	runPostgresListTablesTest(t, tableNameParam, tableNameAuth)
	runPostgresListActiveQueriesTest(t, ctx, pool)
	runPostgresListAvailableExtensionsTest(t)
	runPostgresListInstalledExtensionsTest(t)
}

func runPostgresListTablesTest(t *testing.T, tableNameParam, tableNameAuth string) {
	// TableNameParam columns to construct want
	paramTableColumns := fmt.Sprintf(`[
		{"data_type": "integer", "column_name": "id", "column_default": "nextval('%s_id_seq'::regclass)", "is_not_nullable": true, "ordinal_position": 1, "column_comment": null},
		{"data_type": "text", "column_name": "name", "column_default": null, "is_not_nullable": false, "ordinal_position": 2, "column_comment": null}
	]`, tableNameParam)

	// TableNameAuth columns to construct want
	authTableColumns := fmt.Sprintf(`[
		{"data_type": "integer", "column_name": "id", "column_default": "nextval('%s_id_seq'::regclass)", "is_not_nullable": true, "ordinal_position": 1, "column_comment": null},
		{"data_type": "text", "column_name": "name", "column_default": null, "is_not_nullable": false, "ordinal_position": 2, "column_comment": null},
		{"data_type": "text", "column_name": "email", "column_default": null, "is_not_nullable": false, "ordinal_position": 3, "column_comment": null}
	]`, tableNameAuth)

	const (
		// Template to construct detailed output want
		detailedObjectTemplate = `{
            "object_name": "%[1]s", "schema_name": "public",
            "object_details": {
                "owner": "%[3]s", "comment": null,
                "indexes": [{"is_primary": true, "is_unique": true, "index_name": "%[1]s_pkey", "index_method": "btree", "index_columns": ["id"], "index_definition": "CREATE UNIQUE INDEX %[1]s_pkey ON public.%[1]s USING btree (id)"}],
                "triggers": [], "columns": %[2]s, "object_name": "%[1]s", "object_type": "TABLE", "schema_name": "public",
                "constraints": [{"constraint_name": "%[1]s_pkey", "constraint_type": "PRIMARY KEY", "constraint_columns": ["id"], "constraint_definition": "PRIMARY KEY (id)", "foreign_key_referenced_table": null, "foreign_key_referenced_columns": null}]
            }
        }`

		// Template to construct simple output want
		simpleObjectTemplate = `{"object_name":"%s", "schema_name":"public", "object_details":{"name":"%s"}}`
	)

	// Helper to build json for detailed want
	getDetailedWant := func(tableName, columnJSON string) string {
		return fmt.Sprintf(detailedObjectTemplate, tableName, columnJSON, PostgresUser)
	}

	// Helper to build template for simple want
	getSimpleWant := func(tableName string) string {
		return fmt.Sprintf(simpleObjectTemplate, tableName, tableName)
	}

	invokeTcs := []struct {
		name           string
		api            string
		requestBody    io.Reader
		wantStatusCode int
		want           string
		isAllTables    bool
	}{
		{
			name:           "invoke list_tables all tables detailed output",
			api:            "http://127.0.0.1:5000/api/tool/list_tables/invoke",
			requestBody:    bytes.NewBuffer([]byte(`{"table_names": ""}`)),
			wantStatusCode: http.StatusOK,
			want:           fmt.Sprintf("[%s,%s]", getDetailedWant(tableNameAuth, authTableColumns), getDetailedWant(tableNameParam, paramTableColumns)),
			isAllTables:    true,
		},
		{
			name:           "invoke list_tables all tables simple output",
			api:            "http://127.0.0.1:5000/api/tool/list_tables/invoke",
			requestBody:    bytes.NewBuffer([]byte(`{"table_names": "", "output_format": "simple"}`)),
			wantStatusCode: http.StatusOK,
			want:           fmt.Sprintf("[%s,%s]", getSimpleWant(tableNameAuth), getSimpleWant(tableNameParam)),
			isAllTables:    true,
		},
		{
			name:           "invoke list_tables detailed output",
			api:            "http://127.0.0.1:5000/api/tool/list_tables/invoke",
			requestBody:    bytes.NewBuffer([]byte(fmt.Sprintf(`{"table_names": "%s"}`, tableNameAuth))),
			wantStatusCode: http.StatusOK,
			want:           fmt.Sprintf("[%s]", getDetailedWant(tableNameAuth, authTableColumns)),
		},
		{
			name:           "invoke list_tables simple output",
			api:            "http://127.0.0.1:5000/api/tool/list_tables/invoke",
			requestBody:    bytes.NewBuffer([]byte(fmt.Sprintf(`{"table_names": "%s", "output_format": "simple"}`, tableNameAuth))),
			wantStatusCode: http.StatusOK,
			want:           fmt.Sprintf("[%s]", getSimpleWant(tableNameAuth)),
		},
		{
			name:           "invoke list_tables with invalid output format",
			api:            "http://127.0.0.1:5000/api/tool/list_tables/invoke",
			requestBody:    bytes.NewBuffer([]byte(`{"table_names": "", "output_format": "abcd"}`)),
			wantStatusCode: http.StatusBadRequest,
		},
		{
			name:           "invoke list_tables with malformed table_names parameter",
			api:            "http://127.0.0.1:5000/api/tool/list_tables/invoke",
			requestBody:    bytes.NewBuffer([]byte(`{"table_names": 12345, "output_format": "detailed"}`)),
			wantStatusCode: http.StatusBadRequest,
		},
		{
			name:           "invoke list_tables with multiple table names",
			api:            "http://127.0.0.1:5000/api/tool/list_tables/invoke",
			requestBody:    bytes.NewBuffer([]byte(fmt.Sprintf(`{"table_names": "%s,%s"}`, tableNameParam, tableNameAuth))),
			wantStatusCode: http.StatusOK,
			want:           fmt.Sprintf("[%s,%s]", getDetailedWant(tableNameAuth, authTableColumns), getDetailedWant(tableNameParam, paramTableColumns)),
		},
		{
			name:           "invoke list_tables with non-existent table",
			api:            "http://127.0.0.1:5000/api/tool/list_tables/invoke",
			requestBody:    bytes.NewBuffer([]byte(`{"table_names": "non_existent_table"}`)),
			wantStatusCode: http.StatusOK,
			want:           `null`,
		},
		{
			name:           "invoke list_tables with one existing and one non-existent table",
			api:            "http://127.0.0.1:5000/api/tool/list_tables/invoke",
			requestBody:    bytes.NewBuffer([]byte(fmt.Sprintf(`{"table_names": "%s,non_existent_table"}`, tableNameParam))),
			wantStatusCode: http.StatusOK,
			want:           fmt.Sprintf("[%s]", getDetailedWant(tableNameParam, paramTableColumns)),
		},
	}
	for _, tc := range invokeTcs {
		t.Run(tc.name, func(t *testing.T) {
			req, err := http.NewRequest(http.MethodPost, tc.api, tc.requestBody)
			if err != nil {
				t.Fatalf("unable to create request: %s", err)
			}
			req.Header.Add("Content-type", "application/json")
			resp, err := http.DefaultClient.Do(req)
			if err != nil {
				t.Fatalf("unable to send request: %s", err)
			}
			defer resp.Body.Close()

			if resp.StatusCode != tc.wantStatusCode {
				bodyBytes, _ := io.ReadAll(resp.Body)
				t.Fatalf("response status code is not 200, got %d: %s", resp.StatusCode, string(bodyBytes))
			}

			if tc.wantStatusCode == http.StatusOK {
				var bodyWrapper map[string]json.RawMessage
				respBytes, err := io.ReadAll(resp.Body)
				if err != nil {
					t.Fatalf("error reading response body: %s", err)
				}

				if err := json.Unmarshal(respBytes, &bodyWrapper); err != nil {
					t.Fatalf("error parsing response wrapper: %s, body: %s", err, string(respBytes))
				}

				resultJSON, ok := bodyWrapper["result"]
				if !ok {
					t.Fatal("unable to find 'result' in response body")
				}

				var resultString string
				if err := json.Unmarshal(resultJSON, &resultString); err != nil {
					t.Fatalf("'result' is not a JSON-encoded string: %s", err)
				}

				var got, want []any

				if err := json.Unmarshal([]byte(resultString), &got); err != nil {
					t.Fatalf("failed to unmarshal actual result string: %v", err)
				}
				if err := json.Unmarshal([]byte(tc.want), &want); err != nil {
					t.Fatalf("failed to unmarshal expected want string: %v", err)
				}

				// Checking only the default public schema where the test tables are created to avoid brittle tests.
				if tc.isAllTables {
					var filteredGot []any
					for _, item := range got {
						if tableMap, ok := item.(map[string]interface{}); ok {
							if schema, ok := tableMap["schema_name"]; ok && schema == "public" {
								filteredGot = append(filteredGot, item)
							}
						}
					}
					got = filteredGot
				}

				sort.SliceStable(got, func(i, j int) bool {
					return fmt.Sprintf("%v", got[i]) < fmt.Sprintf("%v", got[j])
				})
				sort.SliceStable(want, func(i, j int) bool {
					return fmt.Sprintf("%v", want[i]) < fmt.Sprintf("%v", want[j])
				})

				if !reflect.DeepEqual(got, want) {
					t.Errorf("Unexpected result: got  %#v, want: %#v", got, want)
				}
			}
		})
	}
}

func runPostgresListActiveQueriesTest(t *testing.T, ctx context.Context, pool *pgxpool.Pool) {
	type queryListDetails struct {
		ProcessId        any    `json:"pid"`
		User             string `json:"user"`
		Datname          string `json:"datname"`
		ApplicationName  string `json:"application_name"`
		ClientAddress    string `json:"client_addr"`
		State            string `json:"state"`
		WaitEventType    string `json:"wait_event_type"`
		WaitEvent        string `json:"wait_event"`
		BackendStart     any    `json:"backend_start"`
		TransactionStart any    `json:"xact_start"`
		QueryStart       any    `json:"query_start"`
		QueryDuration    any    `json:"query_duration"`
		Query            string `json:"query"`
	}

	singleQueryWanted := queryListDetails{
		ProcessId:        any(nil),
		User:             "",
		Datname:          "",
		ApplicationName:  "",
		ClientAddress:    "",
		State:            "",
		WaitEventType:    "",
		WaitEvent:        "",
		BackendStart:     any(nil),
		TransactionStart: any(nil),
		QueryStart:       any(nil),
		QueryDuration:    any(nil),
		Query:            "SELECT pg_sleep(10);",
	}

	invokeTcs := []struct {
		name                string
		requestBody         io.Reader
		clientSleepSecs     int
		waitSecsBeforeCheck int
		wantStatusCode      int
		want                any
	}{
		{
			name:                "invoke list_active_queries when the system is idle",
			requestBody:         bytes.NewBufferString(`{}`),
			clientSleepSecs:     0,
			waitSecsBeforeCheck: 0,
			wantStatusCode:      http.StatusOK,
			want:                []queryListDetails(nil),
		},
		{
			name:                "invoke list_active_queries when there is 1 ongoing but lower than the threshold",
			requestBody:         bytes.NewBufferString(`{"min_duration": "100 seconds"}`),
			clientSleepSecs:     1,
			waitSecsBeforeCheck: 1,
			wantStatusCode:      http.StatusOK,
			want:                []queryListDetails(nil),
		},
		{
			name:                "invoke list_active_queries when 1 ongoing query should show up",
			requestBody:         bytes.NewBufferString(`{"min_duration": "1 seconds"}`),
			clientSleepSecs:     10,
			waitSecsBeforeCheck: 5,
			wantStatusCode:      http.StatusOK,
			want:                []queryListDetails{singleQueryWanted},
		},
	}

	var wg sync.WaitGroup
	for _, tc := range invokeTcs {
		t.Run(tc.name, func(t *testing.T) {
			if tc.clientSleepSecs > 0 {
				wg.Add(1)

				go func() {
					defer wg.Done()

					err := pool.Ping(ctx)
					if err != nil {
						t.Errorf("unable to connect to test database: %s", err)
						return
					}
					_, err = pool.Exec(ctx, fmt.Sprintf("SELECT pg_sleep(%d);", tc.clientSleepSecs))
					if err != nil {
						t.Errorf("Executing 'SELECT pg_sleep' failed: %s", err)
					}
				}()
			}

			if tc.waitSecsBeforeCheck > 0 {
				time.Sleep(time.Duration(tc.waitSecsBeforeCheck) * time.Second)
			}

			const api = "http://127.0.0.1:5000/api/tool/list_active_queries/invoke"
			req, err := http.NewRequest(http.MethodPost, api, tc.requestBody)
			if err != nil {
				t.Fatalf("unable to create request: %v", err)
			}
			req.Header.Add("Content-type", "application/json")

			resp, err := http.DefaultClient.Do(req)
			if err != nil {
				t.Fatalf("unable to send request: %v", err)
			}
			defer resp.Body.Close()

			if resp.StatusCode != tc.wantStatusCode {
				body, _ := io.ReadAll(resp.Body)
				t.Fatalf("wrong status code: got %d, want %d, body: %s", resp.StatusCode, tc.wantStatusCode, string(body))
			}
			if tc.wantStatusCode != http.StatusOK {
				return
			}

			var bodyWrapper struct {
				Result json.RawMessage `json:"result"`
			}
			if err := json.NewDecoder(resp.Body).Decode(&bodyWrapper); err != nil {
				t.Fatalf("error decoding response wrapper: %v", err)
			}

			var resultString string
			if err := json.Unmarshal(bodyWrapper.Result, &resultString); err != nil {
				resultString = string(bodyWrapper.Result)
			}

			var got any
			var details []queryListDetails
			if err := json.Unmarshal([]byte(resultString), &details); err != nil {
				t.Fatalf("failed to unmarshal nested ObjectDetails string: %v", err)
			}
			got = details

			if diff := cmp.Diff(tc.want, got, cmp.Comparer(func(a, b queryListDetails) bool {
				return a.Query == b.Query
			})); diff != "" {
				t.Errorf("Unexpected result: got %#v, want: %#v", got, tc.want)
			}
		})
	}
	wg.Wait()
}

func runPostgresListAvailableExtensionsTest(t *testing.T) {
	invokeTcs := []struct {
		name           string
		api            string
		requestBody    io.Reader
		wantStatusCode int
	}{
		{
			name:           "invoke list_available_extensions output",
			api:            "http://127.0.0.1:5000/api/tool/list_available_extensions/invoke",
			wantStatusCode: http.StatusOK,
			requestBody:    bytes.NewBuffer([]byte(`{}`)),
		},
	}
	for _, tc := range invokeTcs {
		t.Run(tc.name, func(t *testing.T) {
			req, err := http.NewRequest(http.MethodPost, tc.api, tc.requestBody)
			if err != nil {
				t.Fatalf("unable to create request: %s", err)
			}
			req.Header.Add("Content-type", "application/json")
			resp, err := http.DefaultClient.Do(req)
			if err != nil {
				t.Fatalf("unable to send request: %s", err)
			}
			defer resp.Body.Close()

			if resp.StatusCode != tc.wantStatusCode {
				bodyBytes, _ := io.ReadAll(resp.Body)
				t.Fatalf("response status code is not 200, got %d: %s", resp.StatusCode, string(bodyBytes))
			}

			// Intentionally not adding the output check as output depends on the postgres instance used where the the functional test runs.
			// Adding the check will make the test flaky.
		})
	}
}

func runPostgresListInstalledExtensionsTest(t *testing.T) {
	invokeTcs := []struct {
		name           string
		api            string
		requestBody    io.Reader
		wantStatusCode int
	}{
		{
			name:           "invoke list_installed_extensions output",
			api:            "http://127.0.0.1:5000/api/tool/list_installed_extensions/invoke",
			wantStatusCode: http.StatusOK,
			requestBody:    bytes.NewBuffer([]byte(`{}`)),
		},
	}
	for _, tc := range invokeTcs {
		t.Run(tc.name, func(t *testing.T) {
			req, err := http.NewRequest(http.MethodPost, tc.api, tc.requestBody)
			if err != nil {
				t.Fatalf("unable to create request: %s", err)
			}
			req.Header.Add("Content-type", "application/json")
			resp, err := http.DefaultClient.Do(req)
			if err != nil {
				t.Fatalf("unable to send request: %s", err)
			}
			defer resp.Body.Close()

			if resp.StatusCode != tc.wantStatusCode {
				bodyBytes, _ := io.ReadAll(resp.Body)
				t.Fatalf("response status code is not 200, got %d: %s", resp.StatusCode, string(bodyBytes))
			}

			// Intentionally not adding the output check as output depends on the postgres instance used where the the functional test runs.
			// Adding the check will make the test flaky.
		})
	}
}

```

--------------------------------------------------------------------------------
/internal/tools/neo4j/neo4jschema/neo4jschema.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package neo4jschema

import (
	"context"
	"fmt"
	"sync"
	"time"

	"github.com/goccy/go-yaml"
	"github.com/googleapis/genai-toolbox/internal/sources"
	neo4jsc "github.com/googleapis/genai-toolbox/internal/sources/neo4j"
	"github.com/googleapis/genai-toolbox/internal/tools"
	"github.com/googleapis/genai-toolbox/internal/tools/neo4j/neo4jschema/cache"
	"github.com/googleapis/genai-toolbox/internal/tools/neo4j/neo4jschema/helpers"
	"github.com/googleapis/genai-toolbox/internal/tools/neo4j/neo4jschema/types"
	"github.com/neo4j/neo4j-go-driver/v5/neo4j"
)

// kind defines the unique identifier for this tool.
const kind string = "neo4j-schema"

// init registers the tool with the application's tool registry when the package is initialized.
func init() {
	if !tools.Register(kind, newConfig) {
		panic(fmt.Sprintf("tool kind %q already registered", kind))
	}
}

// newConfig decodes a YAML configuration into a Config struct.
// This function is called by the tool registry to create a new configuration object.
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
	actual := Config{Name: name}
	if err := decoder.DecodeContext(ctx, &actual); err != nil {
		return nil, err
	}
	return actual, nil
}

// compatibleSource defines the interface a data source must implement to be used by this tool.
// It ensures that the source can provide a Neo4j driver and database name.
type compatibleSource interface {
	Neo4jDriver() neo4j.DriverWithContext
	Neo4jDatabase() string
}

// Statically verify that our compatible source implementation is valid.
var _ compatibleSource = &neo4jsc.Source{}

// compatibleSources lists the kinds of sources that are compatible with this tool.
var compatibleSources = [...]string{neo4jsc.SourceKind}

// Config holds the configuration settings for the Neo4j schema tool.
// These settings are typically read from a YAML file.
type Config struct {
	Name               string   `yaml:"name" validate:"required"`
	Kind               string   `yaml:"kind" validate:"required"`
	Source             string   `yaml:"source" validate:"required"`
	Description        string   `yaml:"description" validate:"required"`
	AuthRequired       []string `yaml:"authRequired"`
	CacheExpireMinutes *int     `yaml:"cacheExpireMinutes,omitempty"` // Cache expiration time in minutes.
}

// Statically verify that Config implements the tools.ToolConfig interface.
var _ tools.ToolConfig = Config{}

// ToolConfigKind returns the kind of this tool configuration.
func (cfg Config) ToolConfigKind() string {
	return kind
}

// Initialize sets up the tool with its dependencies and returns a ready-to-use Tool instance.
func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
	// Verify that the specified source exists.
	rawS, ok := srcs[cfg.Source]
	if !ok {
		return nil, fmt.Errorf("no source named %q configured", cfg.Source)
	}

	// Verify the source is of a compatible kind.
	s, ok := rawS.(compatibleSource)
	if !ok {
		return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
	}

	parameters := tools.Parameters{}
	mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)

	// Set a default cache expiration if not provided in the configuration.
	if cfg.CacheExpireMinutes == nil {
		defaultExpiration := cache.DefaultExpiration // Default to 60 minutes
		cfg.CacheExpireMinutes = &defaultExpiration
	}

	// Finish tool setup by creating the Tool instance.
	t := Tool{
		Name:               cfg.Name,
		Kind:               kind,
		AuthRequired:       cfg.AuthRequired,
		Driver:             s.Neo4jDriver(),
		Database:           s.Neo4jDatabase(),
		cache:              cache.NewCache(),
		cacheExpireMinutes: cfg.CacheExpireMinutes,
		manifest:           tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired},
		mcpManifest:        mcpManifest,
	}
	return t, nil
}

// Statically verify that Tool implements the tools.Tool interface.
var _ tools.Tool = Tool{}

// Tool represents the Neo4j schema extraction tool.
// It holds the Neo4j driver, database information, and a cache for the schema.
type Tool struct {
	Name               string   `yaml:"name"`
	Kind               string   `yaml:"kind"`
	AuthRequired       []string `yaml:"authRequired"`
	Driver             neo4j.DriverWithContext
	Database           string
	cache              *cache.Cache
	cacheExpireMinutes *int
	manifest           tools.Manifest
	mcpManifest        tools.McpManifest
}

// Invoke executes the tool's main logic: fetching the Neo4j schema.
// It first checks the cache for a valid schema before extracting it from the database.
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
	// Check if a valid schema is already in the cache.
	if cachedSchema, ok := t.cache.Get("schema"); ok {
		if schema, ok := cachedSchema.(*types.SchemaInfo); ok {
			return schema, nil
		}
	}

	// If not cached, extract the schema from the database.
	schema, err := t.extractSchema(ctx)
	if err != nil {
		return nil, fmt.Errorf("failed to extract database schema: %w", err)
	}

	// Cache the newly extracted schema for future use.
	expiration := time.Duration(*t.cacheExpireMinutes) * time.Minute
	t.cache.Set("schema", schema, expiration)

	return schema, nil
}

// ParseParams is a placeholder as this tool does not require input parameters.
func (t Tool) ParseParams(data map[string]any, claimsMap map[string]map[string]any) (tools.ParamValues, error) {
	return tools.ParamValues{}, nil
}

// Manifest returns the tool's manifest, which describes its purpose and parameters.
func (t Tool) Manifest() tools.Manifest {
	return t.manifest
}

// McpManifest returns the machine-consumable manifest for the tool.
func (t Tool) McpManifest() tools.McpManifest {
	return t.mcpManifest
}

// Authorized checks if the tool is authorized to run based on the provided authentication services.
func (t Tool) Authorized(verifiedAuthServices []string) bool {
	return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
}

func (t Tool) RequiresClientAuthorization() bool {
	return false
}

// checkAPOCProcedures verifies if essential APOC procedures are available in the database.
// It returns true only if all required procedures are found.
func (t Tool) checkAPOCProcedures(ctx context.Context) (bool, error) {
	proceduresToCheck := []string{"apoc.meta.schema", "apoc.meta.cypher.types"}

	session := t.Driver.NewSession(ctx, neo4j.SessionConfig{DatabaseName: t.Database})
	defer session.Close(ctx)

	// This query efficiently counts how many of the specified procedures exist.
	query := "SHOW PROCEDURES YIELD name WHERE name IN $procs RETURN count(name) AS procCount"
	params := map[string]any{"procs": proceduresToCheck}

	result, err := session.Run(ctx, query, params)
	if err != nil {
		return false, fmt.Errorf("failed to execute procedure check query: %w", err)
	}

	record, err := result.Single(ctx)
	if err != nil {
		return false, fmt.Errorf("failed to retrieve single result for procedure check: %w", err)
	}

	rawCount, found := record.Get("procCount")
	if !found {
		return false, fmt.Errorf("field 'procCount' not found in result record")
	}

	procCount, ok := rawCount.(int64)
	if !ok {
		return false, fmt.Errorf("expected 'procCount' to be of type int64, but got %T", rawCount)
	}

	// Return true only if the number of found procedures matches the number we were looking for.
	return procCount == int64(len(proceduresToCheck)), nil
}

// extractSchema orchestrates the concurrent extraction of different parts of the database schema.
// It runs several extraction tasks in parallel for efficiency.
func (t Tool) extractSchema(ctx context.Context) (*types.SchemaInfo, error) {
	schema := &types.SchemaInfo{}
	var mu sync.Mutex

	// Define the different schema extraction tasks.
	tasks := []struct {
		name string
		fn   func() error
	}{
		{
			name: "database-info",
			fn: func() error {
				dbInfo, err := t.extractDatabaseInfo(ctx)
				if err != nil {
					return fmt.Errorf("failed to extract database info: %w", err)
				}
				mu.Lock()
				defer mu.Unlock()
				schema.DatabaseInfo = *dbInfo
				return nil
			},
		},
		{
			name: "schema-extraction",
			fn: func() error {
				// Check if APOC procedures are available.
				hasAPOC, err := t.checkAPOCProcedures(ctx)
				if err != nil {
					return fmt.Errorf("failed to check APOC procedures: %w", err)
				}

				var nodeLabels []types.NodeLabel
				var relationships []types.Relationship
				var stats *types.Statistics

				// Use APOC if available for a more detailed schema; otherwise, use native queries.
				if hasAPOC {
					nodeLabels, relationships, stats, err = t.GetAPOCSchema(ctx)
				} else {
					nodeLabels, relationships, stats, err = t.GetSchemaWithoutAPOC(ctx, 100)
				}
				if err != nil {
					return fmt.Errorf("failed to get schema: %w", err)
				}

				mu.Lock()
				defer mu.Unlock()
				schema.NodeLabels = nodeLabels
				schema.Relationships = relationships
				schema.Statistics = *stats
				return nil
			},
		},
		{
			name: "constraints",
			fn: func() error {
				constraints, err := t.extractConstraints(ctx)
				if err != nil {
					return fmt.Errorf("failed to extract constraints: %w", err)
				}
				mu.Lock()
				defer mu.Unlock()
				schema.Constraints = constraints
				return nil
			},
		},
		{
			name: "indexes",
			fn: func() error {
				indexes, err := t.extractIndexes(ctx)
				if err != nil {
					return fmt.Errorf("failed to extract indexes: %w", err)
				}
				mu.Lock()
				defer mu.Unlock()
				schema.Indexes = indexes
				return nil
			},
		},
	}

	var wg sync.WaitGroup
	errCh := make(chan error, len(tasks))

	// Execute all tasks concurrently.
	for _, task := range tasks {
		wg.Add(1)
		go func(task struct {
			name string
			fn   func() error
		}) {
			defer wg.Done()
			if err := task.fn(); err != nil {
				errCh <- err
			}
		}(task)
	}

	wg.Wait()
	close(errCh)

	// Collect any errors that occurred during the concurrent tasks.
	for err := range errCh {
		if err != nil {
			schema.Errors = append(schema.Errors, err.Error())
		}
	}
	return schema, nil
}

// GetAPOCSchema extracts schema information using the APOC library, which provides detailed metadata.
func (t Tool) GetAPOCSchema(ctx context.Context) ([]types.NodeLabel, []types.Relationship, *types.Statistics, error) {
	var nodeLabels []types.NodeLabel
	var relationships []types.Relationship
	stats := &types.Statistics{
		NodesByLabel:        make(map[string]int64),
		RelationshipsByType: make(map[string]int64),
		PropertiesByLabel:   make(map[string]int64),
		PropertiesByRelType: make(map[string]int64),
	}

	var mu sync.Mutex
	var firstErr error
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()

	handleError := func(err error) {
		mu.Lock()
		defer mu.Unlock()
		if firstErr == nil {
			firstErr = err
			cancel() // Cancel other operations on the first error.
		}
	}

	tasks := []struct {
		name string
		fn   func(session neo4j.SessionWithContext) error
	}{
		{
			name: "apoc-schema",
			fn: func(session neo4j.SessionWithContext) error {
				result, err := session.Run(ctx, "CALL apoc.meta.schema({sample: 10}) YIELD value RETURN value", nil)
				if err != nil {
					return fmt.Errorf("failed to run APOC schema query: %w", err)
				}
				if !result.Next(ctx) {
					return fmt.Errorf("no results from APOC schema query")
				}
				schemaMap, ok := result.Record().Values[0].(map[string]any)
				if !ok {
					return fmt.Errorf("unexpected result format from APOC schema query: %T", result.Record().Values[0])
				}
				apocSchema, err := helpers.MapToAPOCSchema(schemaMap)
				if err != nil {
					return fmt.Errorf("failed to convert schema map to APOCSchemaResult: %w", err)
				}
				nodes, _, apocStats := helpers.ProcessAPOCSchema(apocSchema)
				mu.Lock()
				defer mu.Unlock()
				nodeLabels = nodes
				stats.TotalNodes = apocStats.TotalNodes
				stats.TotalProperties += apocStats.TotalProperties
				stats.NodesByLabel = apocStats.NodesByLabel
				stats.PropertiesByLabel = apocStats.PropertiesByLabel
				return nil
			},
		},
		{
			name: "apoc-relationships",
			fn: func(session neo4j.SessionWithContext) error {
				query := `
					MATCH (startNode)-[rel]->(endNode)
					WITH
					  labels(startNode)[0] AS startNode,
					  type(rel) AS relType,
					  apoc.meta.cypher.types(rel) AS relProperties,
					  labels(endNode)[0] AS endNode,
					  count(*) AS count
					RETURN relType, startNode, endNode, relProperties, count`
				result, err := session.Run(ctx, query, nil)
				if err != nil {
					return fmt.Errorf("failed to extract relationships: %w", err)
				}
				for result.Next(ctx) {
					record := result.Record()
					relType, startNode, endNode := record.Values[0].(string), record.Values[1].(string), record.Values[2].(string)
					properties, count := record.Values[3].(map[string]any), record.Values[4].(int64)

					if relType == "" || count == 0 {
						continue
					}
					relationship := types.Relationship{Type: relType, StartNode: startNode, EndNode: endNode, Count: count, Properties: []types.PropertyInfo{}}
					for prop, propType := range properties {
						relationship.Properties = append(relationship.Properties, types.PropertyInfo{Name: prop, Types: []string{propType.(string)}})
					}
					mu.Lock()
					relationships = append(relationships, relationship)
					stats.RelationshipsByType[relType] += count
					stats.TotalRelationships += count
					propCount := int64(len(relationship.Properties))
					stats.TotalProperties += propCount
					stats.PropertiesByRelType[relType] += propCount
					mu.Unlock()
				}
				mu.Lock()
				defer mu.Unlock()
				if len(stats.RelationshipsByType) == 0 {
					stats.RelationshipsByType = nil
				}
				if len(stats.PropertiesByRelType) == 0 {
					stats.PropertiesByRelType = nil
				}
				return nil
			},
		},
	}

	var wg sync.WaitGroup
	wg.Add(len(tasks))
	for _, task := range tasks {
		go func(task struct {
			name string
			fn   func(session neo4j.SessionWithContext) error
		}) {
			defer wg.Done()
			session := t.Driver.NewSession(ctx, neo4j.SessionConfig{DatabaseName: t.Database})
			defer session.Close(ctx)
			if err := task.fn(session); err != nil {
				handleError(fmt.Errorf("task %s failed: %w", task.name, err))
			}
		}(task)
	}
	wg.Wait()

	if firstErr != nil {
		return nil, nil, nil, firstErr
	}
	return nodeLabels, relationships, stats, nil
}

// GetSchemaWithoutAPOC extracts schema information using native Cypher queries.
// This serves as a fallback for databases without APOC installed.
func (t Tool) GetSchemaWithoutAPOC(ctx context.Context, sampleSize int) ([]types.NodeLabel, []types.Relationship, *types.Statistics, error) {
	nodePropsMap := make(map[string]map[string]map[string]bool)
	relPropsMap := make(map[string]map[string]map[string]bool)
	nodeCounts := make(map[string]int64)
	relCounts := make(map[string]int64)
	relConnectivity := make(map[string]types.RelConnectivityInfo)

	var mu sync.Mutex
	var firstErr error
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()

	handleError := func(err error) {
		mu.Lock()
		defer mu.Unlock()
		if firstErr == nil {
			firstErr = err
			cancel()
		}
	}

	tasks := []struct {
		name string
		fn   func(session neo4j.SessionWithContext) error
	}{
		{
			name: "node-schema",
			fn: func(session neo4j.SessionWithContext) error {
				countResult, err := session.Run(ctx, `MATCH (n) UNWIND labels(n) AS label RETURN label, count(*) AS count ORDER BY count DESC`, nil)
				if err != nil {
					return fmt.Errorf("node count query failed: %w", err)
				}
				var labelsList []string
				mu.Lock()
				for countResult.Next(ctx) {
					record := countResult.Record()
					label, count := record.Values[0].(string), record.Values[1].(int64)
					nodeCounts[label] = count
					labelsList = append(labelsList, label)
				}
				mu.Unlock()
				if err = countResult.Err(); err != nil {
					return fmt.Errorf("node count result error: %w", err)
				}

				for _, label := range labelsList {
					propQuery := fmt.Sprintf(`MATCH (n:%s) WITH n LIMIT $sampleSize UNWIND keys(n) AS key WITH key, n[key] AS value WHERE value IS NOT NULL RETURN key, COLLECT(DISTINCT valueType(value)) AS types`, label)
					propResult, err := session.Run(ctx, propQuery, map[string]any{"sampleSize": sampleSize})
					if err != nil {
						return fmt.Errorf("node properties query for label %s failed: %w", label, err)
					}
					mu.Lock()
					if nodePropsMap[label] == nil {
						nodePropsMap[label] = make(map[string]map[string]bool)
					}
					for propResult.Next(ctx) {
						record := propResult.Record()
						key, types := record.Values[0].(string), record.Values[1].([]any)
						if nodePropsMap[label][key] == nil {
							nodePropsMap[label][key] = make(map[string]bool)
						}
						for _, tp := range types {
							nodePropsMap[label][key][tp.(string)] = true
						}
					}
					mu.Unlock()
					if err = propResult.Err(); err != nil {
						return fmt.Errorf("node properties result error for label %s: %w", label, err)
					}
				}
				return nil
			},
		},
		{
			name: "relationship-schema",
			fn: func(session neo4j.SessionWithContext) error {
				relQuery := `
					MATCH (start)-[r]->(end)
					WITH type(r) AS relType, labels(start) AS startLabels, labels(end) AS endLabels, count(*) AS count
					RETURN relType, CASE WHEN size(startLabels) > 0 THEN startLabels[0] ELSE null END AS startLabel, CASE WHEN size(endLabels) > 0 THEN endLabels[0] ELSE null END AS endLabel, sum(count) AS totalCount
					ORDER BY totalCount DESC`
				relResult, err := session.Run(ctx, relQuery, nil)
				if err != nil {
					return fmt.Errorf("relationship count query failed: %w", err)
				}
				var relTypesList []string
				mu.Lock()
				for relResult.Next(ctx) {
					record := relResult.Record()
					relType := record.Values[0].(string)
					startLabel := ""
					if record.Values[1] != nil {
						startLabel = record.Values[1].(string)
					}
					endLabel := ""
					if record.Values[2] != nil {
						endLabel = record.Values[2].(string)
					}
					count := record.Values[3].(int64)
					relCounts[relType] = count
					relTypesList = append(relTypesList, relType)
					if existing, ok := relConnectivity[relType]; !ok || count > existing.Count {
						relConnectivity[relType] = types.RelConnectivityInfo{StartNode: startLabel, EndNode: endLabel, Count: count}
					}
				}
				mu.Unlock()
				if err = relResult.Err(); err != nil {
					return fmt.Errorf("relationship count result error: %w", err)
				}

				for _, relType := range relTypesList {
					propQuery := fmt.Sprintf(`MATCH ()-[r:%s]->() WITH r LIMIT $sampleSize WHERE size(keys(r)) > 0 UNWIND keys(r) AS key WITH key, r[key] AS value WHERE value IS NOT NULL RETURN key, COLLECT(DISTINCT valueType(value)) AS types`, relType)
					propResult, err := session.Run(ctx, propQuery, map[string]any{"sampleSize": sampleSize})
					if err != nil {
						return fmt.Errorf("relationship properties query for type %s failed: %w", relType, err)
					}
					mu.Lock()
					if relPropsMap[relType] == nil {
						relPropsMap[relType] = make(map[string]map[string]bool)
					}
					for propResult.Next(ctx) {
						record := propResult.Record()
						key, propTypes := record.Values[0].(string), record.Values[1].([]any)
						if relPropsMap[relType][key] == nil {
							relPropsMap[relType][key] = make(map[string]bool)
						}
						for _, t := range propTypes {
							relPropsMap[relType][key][t.(string)] = true
						}
					}
					mu.Unlock()
					if err = propResult.Err(); err != nil {
						return fmt.Errorf("relationship properties result error for type %s: %w", relType, err)
					}
				}
				return nil
			},
		},
	}

	var wg sync.WaitGroup
	wg.Add(len(tasks))
	for _, task := range tasks {
		go func(task struct {
			name string
			fn   func(session neo4j.SessionWithContext) error
		}) {
			defer wg.Done()
			session := t.Driver.NewSession(ctx, neo4j.SessionConfig{DatabaseName: t.Database})
			defer session.Close(ctx)
			if err := task.fn(session); err != nil {
				handleError(fmt.Errorf("task %s failed: %w", task.name, err))
			}
		}(task)
	}
	wg.Wait()

	if firstErr != nil {
		return nil, nil, nil, firstErr
	}

	nodeLabels, relationships, stats := helpers.ProcessNonAPOCSchema(nodeCounts, nodePropsMap, relCounts, relPropsMap, relConnectivity)
	return nodeLabels, relationships, stats, nil
}

// extractDatabaseInfo retrieves general information about the Neo4j database instance.
func (t Tool) extractDatabaseInfo(ctx context.Context) (*types.DatabaseInfo, error) {
	session := t.Driver.NewSession(ctx, neo4j.SessionConfig{DatabaseName: t.Database})
	defer session.Close(ctx)

	result, err := session.Run(ctx, "CALL dbms.components() YIELD name, versions, edition", nil)
	if err != nil {
		return nil, err
	}

	dbInfo := &types.DatabaseInfo{}
	if result.Next(ctx) {
		record := result.Record()
		dbInfo.Name = record.Values[0].(string)
		if versions, ok := record.Values[1].([]any); ok && len(versions) > 0 {
			dbInfo.Version = versions[0].(string)
		}
		dbInfo.Edition = record.Values[2].(string)
	}
	return dbInfo, result.Err()
}

// extractConstraints fetches all schema constraints from the database.
func (t Tool) extractConstraints(ctx context.Context) ([]types.Constraint, error) {
	session := t.Driver.NewSession(ctx, neo4j.SessionConfig{DatabaseName: t.Database})
	defer session.Close(ctx)

	result, err := session.Run(ctx, "SHOW CONSTRAINTS", nil)
	if err != nil {
		return nil, err
	}

	var constraints []types.Constraint
	for result.Next(ctx) {
		record := result.Record().AsMap()
		constraint := types.Constraint{
			Name:       helpers.GetStringValue(record["name"]),
			Type:       helpers.GetStringValue(record["type"]),
			EntityType: helpers.GetStringValue(record["entityType"]),
		}
		if labels, ok := record["labelsOrTypes"].([]any); ok && len(labels) > 0 {
			constraint.Label = labels[0].(string)
		}
		if props, ok := record["properties"].([]any); ok {
			constraint.Properties = helpers.ConvertToStringSlice(props)
		}
		constraints = append(constraints, constraint)
	}
	return constraints, result.Err()
}

// extractIndexes fetches all schema indexes from the database.
func (t Tool) extractIndexes(ctx context.Context) ([]types.Index, error) {
	session := t.Driver.NewSession(ctx, neo4j.SessionConfig{DatabaseName: t.Database})
	defer session.Close(ctx)

	result, err := session.Run(ctx, "SHOW INDEXES", nil)
	if err != nil {
		return nil, err
	}

	var indexes []types.Index
	for result.Next(ctx) {
		record := result.Record().AsMap()
		index := types.Index{
			Name:       helpers.GetStringValue(record["name"]),
			State:      helpers.GetStringValue(record["state"]),
			Type:       helpers.GetStringValue(record["type"]),
			EntityType: helpers.GetStringValue(record["entityType"]),
		}
		if labels, ok := record["labelsOrTypes"].([]any); ok && len(labels) > 0 {
			index.Label = labels[0].(string)
		}
		if props, ok := record["properties"].([]any); ok {
			index.Properties = helpers.ConvertToStringSlice(props)
		}
		indexes = append(indexes, index)
	}
	return indexes, result.Err()
}

```

--------------------------------------------------------------------------------
/tests/mongodb/mongodb_integration_test.go:
--------------------------------------------------------------------------------

```go
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package mongodb

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"regexp"
	"testing"
	"time"

	"github.com/googleapis/genai-toolbox/internal/testutils"
	"github.com/googleapis/genai-toolbox/tests"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

var (
	MongoDbSourceKind   = "mongodb"
	MongoDbToolKind     = "mongodb-find"
	MongoDbUri          = os.Getenv("MONGODB_URI")
	MongoDbDatabase     = os.Getenv("MONGODB_DATABASE")
	ServiceAccountEmail = os.Getenv("SERVICE_ACCOUNT_EMAIL")
)

func getMongoDBVars(t *testing.T) map[string]any {
	switch "" {
	case MongoDbUri:
		t.Fatal("'MongoDbUri' not set")
	case MongoDbDatabase:
		t.Fatal("'MongoDbDatabase' not set")
	}
	return map[string]any{
		"kind": MongoDbSourceKind,
		"uri":  MongoDbUri,
	}
}

func initMongoDbDatabase(ctx context.Context, uri, database string) (*mongo.Database, error) {
	// Create a new mongodb Database
	client, err := mongo.Connect(ctx, options.Client().ApplyURI(uri))
	if err != nil {
		return nil, fmt.Errorf("unable to connect to mongodb: %s", err)
	}
	err = client.Ping(ctx, nil)
	if err != nil {
		return nil, fmt.Errorf("unable to connect to mongodb: %s", err)
	}
	return client.Database(database), nil
}

func TestMongoDBToolEndpoints(t *testing.T) {
	sourceConfig := getMongoDBVars(t)
	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
	defer cancel()

	var args []string

	database, err := initMongoDbDatabase(ctx, MongoDbUri, MongoDbDatabase)
	if err != nil {
		t.Fatalf("unable to create MongoDB connection: %s", err)
	}

	// set up data for param tool
	teardownDB := setupMongoDB(t, ctx, database)
	defer teardownDB(t)

	// Write config into a file and pass it to command
	toolsFile := getMongoDBToolsConfig(sourceConfig, MongoDbToolKind)

	cmd, cleanup, err := tests.StartCmd(ctx, toolsFile, args...)
	if err != nil {
		t.Fatalf("command initialization returned an error: %s", err)
	}
	defer cleanup()

	waitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
	defer cancel()
	out, err := testutils.WaitForString(waitCtx, regexp.MustCompile(`Server ready to serve`), cmd.Out)
	if err != nil {
		t.Logf("toolbox command logs: \n%s", out)
		t.Fatalf("toolbox didn't start successfully: %s", err)
	}

	// Get configs for tests
	select1Want := `[{"_id":3,"id":3,"name":"Sid"}]`
	myToolId3NameAliceWant := `[{"_id":5,"id":3,"name":"Alice"}]`
	myToolById4Want := `[{"_id":4,"id":4,"name":null}]`
	mcpMyFailToolWant := `invalid JSON input: missing colon after key `
	mcpMyToolId3NameAliceWant := `{"jsonrpc":"2.0","id":"my-simple-tool","result":{"content":[{"type":"text","text":"{\"_id\":5,\"id\":3,\"name\":\"Alice\"}"}]}}`

	// Run tests
	tests.RunToolGetTest(t)
	tests.RunToolInvokeTest(t, select1Want,
		tests.WithMyToolId3NameAliceWant(myToolId3NameAliceWant),
		tests.WithMyArrayToolWant(myToolId3NameAliceWant),
		tests.WithMyToolById4Want(myToolById4Want),
	)
	tests.RunMCPToolCallMethod(t, mcpMyFailToolWant, select1Want,
		tests.WithMcpMyToolId3NameAliceWant(mcpMyToolId3NameAliceWant),
	)

	delete1Want := "1"
	deleteManyWant := "2"
	runToolDeleteInvokeTest(t, delete1Want, deleteManyWant)

	insert1Want := `["68666e1035bb36bf1b4d47fb"]`
	insertManyWant := `["68667a6436ec7d0363668db7","68667a6436ec7d0363668db8","68667a6436ec7d0363668db9"]`
	runToolInsertInvokeTest(t, insert1Want, insertManyWant)

	update1Want := "1"
	updateManyWant := "[2,0,2]"
	runToolUpdateInvokeTest(t, update1Want, updateManyWant)

	aggregate1Want := `[{"id":2}]`
	aggregateManyWant := `[{"id":500},{"id":501}]`
	runToolAggregateInvokeTest(t, aggregate1Want, aggregateManyWant)
}

func runToolDeleteInvokeTest(t *testing.T, delete1Want, deleteManyWant string) {
	// Test tool invoke endpoint
	invokeTcs := []struct {
		name          string
		api           string
		requestHeader map[string]string
		requestBody   io.Reader
		want          string
		isErr         bool
	}{
		{
			name:          "invoke my-delete-one-tool",
			api:           "http://127.0.0.1:5000/api/tool/my-delete-one-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{ "id" : 100 }`)),
			want:          delete1Want,
			isErr:         false,
		},
		{
			name:          "invoke my-delete-many-tool",
			api:           "http://127.0.0.1:5000/api/tool/my-delete-many-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{ "id" : 101 }`)),
			want:          deleteManyWant,
			isErr:         false,
		},
	}

	for _, tc := range invokeTcs {

		t.Run(tc.name, func(t *testing.T) {
			// Send Tool invocation request
			req, err := http.NewRequest(http.MethodPost, tc.api, tc.requestBody)
			if err != nil {
				t.Fatalf("unable to create request: %s", err)
			}
			req.Header.Add("Content-type", "application/json")
			for k, v := range tc.requestHeader {
				req.Header.Add(k, v)
			}
			resp, err := http.DefaultClient.Do(req)
			if err != nil {
				t.Fatalf("unable to send request: %s", err)
			}
			defer resp.Body.Close()

			if resp.StatusCode != http.StatusOK {
				if tc.isErr {
					return
				}
				bodyBytes, _ := io.ReadAll(resp.Body)
				t.Fatalf("response status code is not 200, got %d: %s", resp.StatusCode, string(bodyBytes))
			}

			// Check response body
			var body map[string]interface{}
			err = json.NewDecoder(resp.Body).Decode(&body)
			if err != nil {
				t.Fatalf("error parsing response body")
			}

			got, ok := body["result"].(string)
			if !ok {
				t.Fatalf("unable to find result in response body")
			}

			if got != tc.want {
				t.Fatalf("unexpected value: got %q, want %q", got, tc.want)
			}
		})
	}
}

func runToolInsertInvokeTest(t *testing.T, insert1Want, insertManyWant string) {
	// Test tool invoke endpoint
	invokeTcs := []struct {
		name          string
		api           string
		requestHeader map[string]string
		requestBody   io.Reader
		want          string
		isErr         bool
	}{
		{
			name:          "invoke my-insert-one-tool",
			api:           "http://127.0.0.1:5000/api/tool/my-insert-one-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{ "data" : "{ \"_id\": { \"$oid\": \"68666e1035bb36bf1b4d47fb\" },  \"id\" : 200 }" }"`)),
			want:          insert1Want,
			isErr:         false,
		},
		{
			name:          "invoke my-insert-many-tool",
			api:           "http://127.0.0.1:5000/api/tool/my-insert-many-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{ "data" : "[{ \"_id\": { \"$oid\": \"68667a6436ec7d0363668db7\"} , \"id\" : 201 }, { \"_id\" : { \"$oid\": \"68667a6436ec7d0363668db8\"}, \"id\" : 202 }, { \"_id\": { \"$oid\": \"68667a6436ec7d0363668db9\"}, \"id\": 203 }]" }`)),
			want:          insertManyWant,
			isErr:         false,
		},
	}

	for _, tc := range invokeTcs {

		t.Run(tc.name, func(t *testing.T) {
			// Send Tool invocation request
			req, err := http.NewRequest(http.MethodPost, tc.api, tc.requestBody)
			if err != nil {
				t.Fatalf("unable to create request: %s", err)
			}
			req.Header.Add("Content-type", "application/json")
			for k, v := range tc.requestHeader {
				req.Header.Add(k, v)
			}
			resp, err := http.DefaultClient.Do(req)
			if err != nil {
				t.Fatalf("unable to send request: %s", err)
			}
			defer resp.Body.Close()

			if resp.StatusCode != http.StatusOK {
				if tc.isErr {
					return
				}
				bodyBytes, _ := io.ReadAll(resp.Body)
				t.Fatalf("response status code is not 200, got %d: %s", resp.StatusCode, string(bodyBytes))
			}

			// Check response body
			var body map[string]interface{}
			err = json.NewDecoder(resp.Body).Decode(&body)
			if err != nil {
				t.Fatalf("error parsing response body")
			}

			got, ok := body["result"].(string)
			if !ok {
				t.Fatalf("unable to find result in response body")
			}

			if got != tc.want {
				t.Fatalf("unexpected value: got %q, want %q", got, tc.want)
			}
		})
	}
}

func runToolUpdateInvokeTest(t *testing.T, update1Want, updateManyWant string) {
	// Test tool invoke endpoint
	invokeTcs := []struct {
		name          string
		api           string
		requestHeader map[string]string
		requestBody   io.Reader
		want          string
		isErr         bool
	}{
		{
			name:          "invoke my-update-one-tool",
			api:           "http://127.0.0.1:5000/api/tool/my-update-one-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{ "id": 300, "name": "Bob" }`)),
			want:          update1Want,
			isErr:         false,
		},
		{
			name:          "invoke my-update-many-tool",
			api:           "http://127.0.0.1:5000/api/tool/my-update-many-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{ "id": 400, "name" : "Alice" }`)),
			want:          updateManyWant,
			isErr:         false,
		},
	}

	for _, tc := range invokeTcs {

		t.Run(tc.name, func(t *testing.T) {
			// Send Tool invocation request
			req, err := http.NewRequest(http.MethodPost, tc.api, tc.requestBody)
			if err != nil {
				t.Fatalf("unable to create request: %s", err)
			}
			req.Header.Add("Content-type", "application/json")
			for k, v := range tc.requestHeader {
				req.Header.Add(k, v)
			}
			resp, err := http.DefaultClient.Do(req)
			if err != nil {
				t.Fatalf("unable to send request: %s", err)
			}
			defer resp.Body.Close()

			if resp.StatusCode != http.StatusOK {
				if tc.isErr {
					return
				}
				bodyBytes, _ := io.ReadAll(resp.Body)
				t.Fatalf("response status code is not 200, got %d: %s", resp.StatusCode, string(bodyBytes))
			}

			// Check response body
			var body map[string]interface{}
			err = json.NewDecoder(resp.Body).Decode(&body)
			if err != nil {
				t.Fatalf("error parsing response body")
			}

			got, ok := body["result"].(string)
			if !ok {
				t.Fatalf("unable to find result in response body")
			}

			if got != tc.want {
				t.Fatalf("unexpected value: got %q, want %q", got, tc.want)
			}
		})
	}
}
func runToolAggregateInvokeTest(t *testing.T, aggregate1Want string, aggregateManyWant string) {
	// Test tool invoke endpoint
	invokeTcs := []struct {
		name          string
		api           string
		requestHeader map[string]string
		requestBody   io.Reader
		want          string
		isErr         bool
	}{
		{
			name:          "invoke my-aggregate-tool",
			api:           "http://127.0.0.1:5000/api/tool/my-aggregate-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{ "name": "Jane" }`)),
			want:          aggregate1Want,
			isErr:         false,
		},
		{
			name:          "invoke my-aggregate-tool",
			api:           "http://127.0.0.1:5000/api/tool/my-aggregate-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{ "name" : "ToBeAggregated" }`)),
			want:          aggregateManyWant,
			isErr:         false,
		},
		{
			name:          "invoke my-read-only-aggregate-tool",
			api:           "http://127.0.0.1:5000/api/tool/my-read-only-aggregate-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{ "name" : "ToBeAggregated" }`)),
			want:          "",
			isErr:         true,
		},
		{
			name:          "invoke my-read-write-aggregate-tool",
			api:           "http://127.0.0.1:5000/api/tool/my-read-write-aggregate-tool/invoke",
			requestHeader: map[string]string{},
			requestBody:   bytes.NewBuffer([]byte(`{ "name" : "ToBeAggregated" }`)),
			want:          "[]",
			isErr:         false,
		},
	}

	for _, tc := range invokeTcs {

		t.Run(tc.name, func(t *testing.T) {
			// Send Tool invocation request
			req, err := http.NewRequest(http.MethodPost, tc.api, tc.requestBody)
			if err != nil {
				t.Fatalf("unable to create request: %s", err)
			}
			req.Header.Add("Content-type", "application/json")
			for k, v := range tc.requestHeader {
				req.Header.Add(k, v)
			}
			resp, err := http.DefaultClient.Do(req)
			if err != nil {
				t.Fatalf("unable to send request: %s", err)
			}
			defer resp.Body.Close()

			if resp.StatusCode != http.StatusOK {
				if tc.isErr {
					return
				}
				bodyBytes, _ := io.ReadAll(resp.Body)
				t.Fatalf("response status code is not 200, got %d: %s", resp.StatusCode, string(bodyBytes))
			}

			// Check response body
			var body map[string]interface{}
			err = json.NewDecoder(resp.Body).Decode(&body)
			if err != nil {
				t.Fatalf("error parsing response body")
			}

			got, ok := body["result"].(string)
			if !ok {
				t.Fatalf("unable to find result in response body")
			}

			if got != tc.want {
				t.Fatalf("unexpected value: got %q, want %q", got, tc.want)
			}
		})
	}
}

func setupMongoDB(t *testing.T, ctx context.Context, database *mongo.Database) func(*testing.T) {
	collectionName := "test_collection"

	documents := []map[string]any{
		{"_id": 1, "id": 1, "name": "Alice", "email": ServiceAccountEmail},
		{"_id": 1, "id": 2, "name": "FakeAlice", "email": "[email protected]"},
		{"_id": 2, "id": 2, "name": "Jane"},
		{"_id": 3, "id": 3, "name": "Sid"},
		{"_id": 4, "id": 4, "name": nil},
		{"_id": 5, "id": 3, "name": "Alice", "email": "[email protected]"},
		{"_id": 6, "id": 100, "name": "ToBeDeleted", "email": "[email protected]"},
		{"_id": 7, "id": 101, "name": "ToBeDeleted", "email": "[email protected]"},
		{"_id": 8, "id": 101, "name": "ToBeDeleted", "email": "[email protected]"},
		{"_id": 9, "id": 300, "name": "ToBeUpdatedToBob", "email": "[email protected]"},
		{"_id": 10, "id": 400, "name": "ToBeUpdatedToAlice", "email": "[email protected]"},
		{"_id": 11, "id": 400, "name": "ToBeUpdatedToAlice", "email": "[email protected]"},
		{"_id": 12, "id": 500, "name": "ToBeAggregated", "email": "[email protected]"},
		{"_id": 13, "id": 501, "name": "ToBeAggregated", "email": "[email protected]"},
	}
	for _, doc := range documents {
		_, err := database.Collection(collectionName).InsertOne(ctx, doc)
		if err != nil {
			t.Fatalf("unable to insert test data: %s", err)
		}
	}

	return func(t *testing.T) {
		// tear down test
		err := database.Collection(collectionName).Drop(ctx)
		if err != nil {
			t.Errorf("Teardown failed: %s", err)
		}
	}

}

func getMongoDBToolsConfig(sourceConfig map[string]any, toolKind string) map[string]any {
	toolsFile := map[string]any{
		"sources": map[string]any{
			"my-instance": sourceConfig,
		},
		"authServices": map[string]any{
			"my-google-auth": map[string]any{
				"kind":     "google",
				"clientId": tests.ClientId,
			},
		},
		"tools": map[string]any{
			"my-simple-tool": map[string]any{
				"kind":           "mongodb-find-one",
				"source":         "my-instance",
				"description":    "Simple tool to test end to end functionality.",
				"collection":     "test_collection",
				"filterPayload":  `{ "_id" : 3 }`,
				"filterParams":   []any{},
				"projectPayload": `{ "_id": 1, "id": 1, "name" : 1 }`,
				"database":       MongoDbDatabase,
				"limit":          1,
				"sort":           `{ "id": 1 }`,
			},
			"my-tool": map[string]any{
				"kind":          toolKind,
				"source":        "my-instance",
				"description":   "Tool to test invocation with params.",
				"authRequired":  []string{},
				"collection":    "test_collection",
				"filterPayload": `{ "id" : {{ .id }}, "name" : {{json .name }} }`,
				"filterParams": []map[string]any{
					{
						"name":        "id",
						"type":        "integer",
						"description": "user id",
					},
					{
						"name":        "name",
						"type":        "string",
						"description": "user name",
					},
				},
				"projectPayload": `{ "_id": 1, "id": 1, "name" : 1 }`,
				"database":       MongoDbDatabase,
			},
			"my-tool-by-id": map[string]any{
				"kind":          toolKind,
				"source":        "my-instance",
				"description":   "Tool to test invocation with params.",
				"authRequired":  []string{},
				"collection":    "test_collection",
				"filterPayload": `{ "id" : {{ .id }} }`,
				"filterParams": []map[string]any{
					{
						"name":        "id",
						"type":        "integer",
						"description": "user id",
					},
				},
				"projectPayload": `{ "_id": 1, "id": 1, "name" : 1 }`,
				"database":       MongoDbDatabase,
			},
			"my-tool-by-name": map[string]any{
				"kind":          toolKind,
				"source":        "my-instance",
				"description":   "Tool to test invocation with params.",
				"authRequired":  []string{},
				"collection":    "test_collection",
				"filterPayload": `{ "name" : {{ .name }} }`,
				"filterParams": []map[string]any{
					{
						"name":        "name",
						"type":        "string",
						"description": "user name",
						"required":    false,
					},
				},
				"projectPayload": `{ "_id": 1, "id": 1, "name" : 1 }`,
				"database":       MongoDbDatabase,
			},
			"my-array-tool": map[string]any{
				"kind":          toolKind,
				"source":        "my-instance",
				"description":   "Tool to test invocation with array.",
				"authRequired":  []string{},
				"collection":    "test_collection",
				"filterPayload": `{ "name": { "$in": {{json .nameArray}} }, "_id": 5 })`,
				"filterParams": []map[string]any{
					{
						"name":        "nameArray",
						"type":        "array",
						"description": "user names",
						"items": map[string]any{
							"name":        "username",
							"type":        "string",
							"description": "string item"},
					},
				},
				"projectPayload": `{ "_id": 1, "id": 1, "name" : 1 }`,
				"database":       MongoDbDatabase,
			},
			"my-auth-tool": map[string]any{
				"kind":          toolKind,
				"source":        "my-instance",
				"description":   "Tool to test authenticated parameters.",
				"authRequired":  []string{},
				"collection":    "test_collection",
				"filterPayload": `{ "email" : {{json .email }} }`,
				"filterParams": []map[string]any{
					{
						"name":        "email",
						"type":        "string",
						"description": "user email",
						"authServices": []map[string]string{
							{
								"name":  "my-google-auth",
								"field": "email",
							},
						},
					},
				},
				"projectPayload": `{ "_id": 0, "name" : 1 }`,
				"database":       MongoDbDatabase,
			},
			"my-auth-required-tool": map[string]any{
				"kind":        toolKind,
				"source":      "my-instance",
				"description": "Tool to test auth required invocation.",
				"authRequired": []string{
					"my-google-auth",
				},
				"collection":    "test_collection",
				"filterPayload": `{ "_id": 3, "id": 3 }`,
				"filterParams":  []any{},
				"database":      MongoDbDatabase,
			},
			"my-fail-tool": map[string]any{
				"kind":          toolKind,
				"source":        "my-instance",
				"description":   "Tool to test statement with incorrect syntax.",
				"authRequired":  []string{},
				"collection":    "test_collection",
				"filterPayload": `{ "id" ; 1 }"}`,
				"filterParams":  []any{},
				"database":      MongoDbDatabase,
			},
			"my-delete-one-tool": map[string]any{
				"kind":          "mongodb-delete-one",
				"source":        "my-instance",
				"description":   "Tool to test deleting an entry.",
				"authRequired":  []string{},
				"collection":    "test_collection",
				"filterPayload": `{ "id" : 100 }"}`,
				"filterParams":  []any{},
				"database":      MongoDbDatabase,
			},
			"my-delete-many-tool": map[string]any{
				"kind":          "mongodb-delete-many",
				"source":        "my-instance",
				"description":   "Tool to test deleting multiple entries.",
				"authRequired":  []string{},
				"collection":    "test_collection",
				"filterPayload": `{ "id" : 101 }"}`,
				"filterParams":  []any{},
				"database":      MongoDbDatabase,
			},
			"my-insert-one-tool": map[string]any{
				"kind":         "mongodb-insert-one",
				"source":       "my-instance",
				"description":  "Tool to test inserting an entry.",
				"authRequired": []string{},
				"collection":   "test_collection",
				"canonical":    true,
				"database":     MongoDbDatabase,
			},
			"my-insert-many-tool": map[string]any{
				"kind":         "mongodb-insert-many",
				"source":       "my-instance",
				"description":  "Tool to test inserting multiple entries.",
				"authRequired": []string{},
				"collection":   "test_collection",
				"canonical":    true,
				"database":     MongoDbDatabase,
			},
			"my-update-one-tool": map[string]any{
				"kind":          "mongodb-update-one",
				"source":        "my-instance",
				"description":   "Tool to test updating an entry.",
				"authRequired":  []string{},
				"collection":    "test_collection",
				"canonical":     true,
				"filterPayload": `{ "id" : {{ .id }} }`,
				"filterParams": []map[string]any{
					{
						"name":        "id",
						"type":        "integer",
						"description": "id",
					},
				},
				"updatePayload": `{ "$set" : { "name": {{json .name}} } }`,
				"updateParams": []map[string]any{
					{
						"name":        "name",
						"type":        "string",
						"description": "user name",
					},
				},
				"database": MongoDbDatabase,
			},
			"my-update-many-tool": map[string]any{
				"kind":          "mongodb-update-many",
				"source":        "my-instance",
				"description":   "Tool to test updating multiple entries.",
				"authRequired":  []string{},
				"collection":    "test_collection",
				"canonical":     true,
				"filterPayload": `{ "id" : {{ .id }} }`,
				"filterParams": []map[string]any{
					{
						"name":        "id",
						"type":        "integer",
						"description": "id",
					},
				},
				"updatePayload": `{ "$set" : { "name": {{json .name}} } }`,
				"updateParams": []map[string]any{
					{
						"name":        "name",
						"type":        "string",
						"description": "user name",
					},
				},
				"database": MongoDbDatabase,
			},
			"my-aggregate-tool": map[string]any{
				"kind":            "mongodb-aggregate",
				"source":          "my-instance",
				"description":     "Tool to test an aggregation.",
				"authRequired":    []string{},
				"collection":      "test_collection",
				"canonical":       true,
				"pipelinePayload": `[{ "$match" : { "name": {{json .name}} } }, { "$project" : { "id" : 1, "_id" : 0 }}]`,
				"pipelineParams": []map[string]any{
					{
						"name":        "name",
						"type":        "string",
						"description": "user name",
					},
				},
				"database": MongoDbDatabase,
			},
			"my-read-only-aggregate-tool": map[string]any{
				"kind":            "mongodb-aggregate",
				"source":          "my-instance",
				"description":     "Tool to test an aggregation.",
				"authRequired":    []string{},
				"collection":      "test_collection",
				"canonical":       true,
				"readOnly":        true,
				"pipelinePayload": `[{ "$match" : { "name": {{json .name}} } }, { "$out" : "target_collection" }]`,
				"pipelineParams": []map[string]any{
					{
						"name":        "name",
						"type":        "string",
						"description": "user name",
					},
				},
				"database": MongoDbDatabase,
			},
			"my-read-write-aggregate-tool": map[string]any{
				"kind":            "mongodb-aggregate",
				"source":          "my-instance",
				"description":     "Tool to test an aggregation.",
				"authRequired":    []string{},
				"collection":      "test_collection",
				"canonical":       true,
				"readOnly":        false,
				"pipelinePayload": `[{ "$match" : { "name": {{json .name}} } }, { "$out" : "target_collection" }]`,
				"pipelineParams": []map[string]any{
					{
						"name":        "name",
						"type":        "string",
						"description": "user name",
					},
				},
				"database": MongoDbDatabase,
			},
		},
	}

	return toolsFile

}

```
Page 26/35FirstPrevNextLast