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

# Directory Structure

```
├── .ci
│   ├── continuous.release.cloudbuild.yaml
│   ├── generate_release_table.sh
│   ├── integration.cloudbuild.yaml
│   ├── quickstart_test
│   │   ├── go.integration.cloudbuild.yaml
│   │   ├── js.integration.cloudbuild.yaml
│   │   ├── py.integration.cloudbuild.yaml
│   │   ├── run_go_tests.sh
│   │   ├── run_js_tests.sh
│   │   ├── run_py_tests.sh
│   │   └── setup_hotels_sample.sql
│   ├── test_prompts_with_coverage.sh
│   ├── test_with_coverage.sh
│   └── versioned.release.cloudbuild.yaml
├── .gemini
│   └── config.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
│   ├── trusted-contribution.yml
│   └── workflows
│       ├── cloud_build_failure_reporter.yml
│       ├── deploy_dev_docs.yaml
│       ├── deploy_previous_version_docs.yaml
│       ├── deploy_versioned_docs.yaml
│       ├── docs_preview_clean.yaml
│       ├── docs_preview_deploy.yaml
│       ├── link_checker_workflow.yaml
│       ├── lint.yaml
│       ├── publish-mcp.yml
│       ├── 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
├── .lycheeignore
├── 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
│   ├── ALLOYDBADMIN_README.md
│   ├── ALLOYDBPG_README.md
│   ├── BIGQUERY_README.md
│   ├── CLOUDSQLMSSQL_README.md
│   ├── CLOUDSQLMSSQLADMIN_README.md
│   ├── CLOUDSQLMYSQL_README.md
│   ├── CLOUDSQLMYSQLADMIN_README.md
│   ├── CLOUDSQLPG_README.md
│   ├── CLOUDSQLPGADMIN_README.md
│   ├── DATAPLEX_README.md
│   ├── en
│   │   ├── _index.md
│   │   ├── about
│   │   │   ├── _index.md
│   │   │   └── faq.md
│   │   ├── blogs
│   │   │   └── _index.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
│   │   │   ├── prompts_quickstart_gemini_cli.md
│   │   │   └── quickstart
│   │   │       ├── go
│   │   │       │   ├── adkgo
│   │   │       │   │   ├── go.mod
│   │   │       │   │   ├── go.sum
│   │   │       │   │   └── 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
│   │   │       │   ├── adk
│   │   │       │   │   ├── package-lock.json
│   │   │       │   │   ├── package.json
│   │   │       │   │   └── quickstart.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_adk_agent.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
│   │   │   ├── embeddingModels
│   │   │   │   ├── _index.md
│   │   │   │   └── gemini.md
│   │   │   ├── prompts
│   │   │   │   ├── _index.md
│   │   │   │   └── custom
│   │   │   │       └── _index.md
│   │   │   ├── sources
│   │   │   │   ├── _index.md
│   │   │   │   ├── alloydb-admin.md
│   │   │   │   ├── alloydb-pg.md
│   │   │   │   ├── bigquery.md
│   │   │   │   ├── bigtable.md
│   │   │   │   ├── cassandra.md
│   │   │   │   ├── clickhouse.md
│   │   │   │   ├── cloud-gda.md
│   │   │   │   ├── cloud-healthcare.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
│   │   │   │   ├── elasticsearch.md
│   │   │   │   ├── firebird.md
│   │   │   │   ├── firestore.md
│   │   │   │   ├── http.md
│   │   │   │   ├── looker.md
│   │   │   │   ├── mariadb.md
│   │   │   │   ├── mindsdb.md
│   │   │   │   ├── mongodb.md
│   │   │   │   ├── mssql.md
│   │   │   │   ├── mysql.md
│   │   │   │   ├── neo4j.md
│   │   │   │   ├── oceanbase.md
│   │   │   │   ├── oracle.md
│   │   │   │   ├── postgres.md
│   │   │   │   ├── redis.md
│   │   │   │   ├── serverless-spark.md
│   │   │   │   ├── singlestore.md
│   │   │   │   ├── snowflake.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
│   │   │       ├── cloudgda
│   │   │       │   ├── _index.md
│   │   │       │   └── cloud-gda-query.md
│   │   │       ├── cloudhealthcare
│   │   │       │   ├── _index.md
│   │   │       │   ├── cloud-healthcare-fhir-fetch-page.md
│   │   │       │   ├── cloud-healthcare-fhir-patient-everything.md
│   │   │       │   ├── cloud-healthcare-fhir-patient-search.md
│   │   │       │   ├── cloud-healthcare-get-dataset.md
│   │   │       │   ├── cloud-healthcare-get-dicom-store-metrics.md
│   │   │       │   ├── cloud-healthcare-get-dicom-store.md
│   │   │       │   ├── cloud-healthcare-get-fhir-resource.md
│   │   │       │   ├── cloud-healthcare-get-fhir-store-metrics.md
│   │   │       │   ├── cloud-healthcare-get-fhir-store.md
│   │   │       │   ├── cloud-healthcare-list-dicom-stores.md
│   │   │       │   ├── cloud-healthcare-list-fhir-stores.md
│   │   │       │   ├── cloud-healthcare-retrieve-rendered-dicom-instance.md
│   │   │       │   ├── cloud-healthcare-search-dicom-instances.md
│   │   │       │   ├── cloud-healthcare-search-dicom-series.md
│   │   │       │   └── cloud-healthcare-search-dicom-studies.md
│   │   │       ├── cloudmonitoring
│   │   │       │   ├── _index.md
│   │   │       │   └── cloud-monitoring-query-prometheus.md
│   │   │       ├── cloudsql
│   │   │       │   ├── _index.md
│   │   │       │   ├── cloudsqlcloneinstance.md
│   │   │       │   ├── cloudsqlcreatedatabase.md
│   │   │       │   ├── cloudsqlcreateusers.md
│   │   │       │   ├── cloudsqlgetinstances.md
│   │   │       │   ├── cloudsqllistdatabases.md
│   │   │       │   ├── cloudsqllistinstances.md
│   │   │       │   ├── cloudsqlmssqlcreateinstance.md
│   │   │       │   ├── cloudsqlmysqlcreateinstance.md
│   │   │       │   ├── cloudsqlpgcreateinstances.md
│   │   │       │   ├── cloudsqlpgupgradeprecheck.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
│   │   │       ├── elasticsearch
│   │   │       │   ├── _index.md
│   │   │       │   └── elasticsearch-esql.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-add-dashboard-filter.md
│   │   │       │   ├── looker-conversational-analytics.md
│   │   │       │   ├── looker-create-project-file.md
│   │   │       │   ├── looker-delete-project-file.md
│   │   │       │   ├── looker-dev-mode.md
│   │   │       │   ├── looker-generate-embed-url.md
│   │   │       │   ├── looker-get-connection-databases.md
│   │   │       │   ├── looker-get-connection-schemas.md
│   │   │       │   ├── looker-get-connection-table-columns.md
│   │   │       │   ├── looker-get-connection-tables.md
│   │   │       │   ├── looker-get-connections.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-dashboard.md
│   │   │       │   ├── looker-run-look.md
│   │   │       │   └── looker-update-project-file.md
│   │   │       ├── mindsdb
│   │   │       │   ├── _index.md
│   │   │       │   ├── mindsdb-execute-sql.md
│   │   │       │   └── mindsdb-sql.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-get-query-plan.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-database-overview.md
│   │   │       │   ├── postgres-execute-sql.md
│   │   │       │   ├── postgres-get-column-cardinality.md
│   │   │       │   ├── postgres-list-active-queries.md
│   │   │       │   ├── postgres-list-available-extensions.md
│   │   │       │   ├── postgres-list-database-stats.md
│   │   │       │   ├── postgres-list-indexes.md
│   │   │       │   ├── postgres-list-installed-extensions.md
│   │   │       │   ├── postgres-list-locks.md
│   │   │       │   ├── postgres-list-pg-settings.md
│   │   │       │   ├── postgres-list-publication-tables.md
│   │   │       │   ├── postgres-list-query-stats.md
│   │   │       │   ├── postgres-list-roles.md
│   │   │       │   ├── postgres-list-schemas.md
│   │   │       │   ├── postgres-list-sequences.md
│   │   │       │   ├── postgres-list-stored-procedure.md
│   │   │       │   ├── postgres-list-table-stats.md
│   │   │       │   ├── postgres-list-tables.md
│   │   │       │   ├── postgres-list-tablespaces.md
│   │   │       │   ├── postgres-list-triggers.md
│   │   │       │   ├── postgres-list-views.md
│   │   │       │   ├── postgres-long-running-transactions.md
│   │   │       │   ├── postgres-replication-stats.md
│   │   │       │   └── postgres-sql.md
│   │   │       ├── redis
│   │   │       │   ├── _index.md
│   │   │       │   └── redis.md
│   │   │       ├── serverless-spark
│   │   │       │   ├── _index.md
│   │   │       │   ├── serverless-spark-cancel-batch.md
│   │   │       │   ├── serverless-spark-create-pyspark-batch.md
│   │   │       │   ├── serverless-spark-create-spark-batch.md
│   │   │       │   ├── serverless-spark-get-batch.md
│   │   │       │   └── serverless-spark-list-batches.md
│   │   │       ├── singlestore
│   │   │       │   ├── _index.md
│   │   │       │   ├── singlestore-execute-sql.md
│   │   │       │   └── singlestore-sql.md
│   │   │       ├── snowflake
│   │   │       │   ├── _index.md
│   │   │       │   ├── snowflake-execute-sql.md
│   │   │       │   └── snowflake-sql.md
│   │   │       ├── spanner
│   │   │       │   ├── _index.md
│   │   │       │   ├── spanner-execute-sql.md
│   │   │       │   ├── spanner-list-graphs.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
│   │   │   └── snowflake
│   │   │       ├── _index.md
│   │   │       ├── runme.py
│   │   │       ├── snowflake-config.yaml
│   │   │       ├── snowflake-env.sh
│   │   │       └── test-snowflake.sh
│   │   └── sdks
│   │       ├── _index.md
│   │       ├── go-sdk.md
│   │       ├── js-sdk.md
│   │       └── python-sdk.md
│   ├── LOOKER_README.md
│   ├── SPANNER_README.md
│   └── TOOLBOX_README.md
├── gemini-extension.json
├── go.mod
├── go.sum
├── internal
│   ├── auth
│   │   ├── auth.go
│   │   └── google
│   │       └── google.go
│   ├── embeddingmodels
│   │   ├── embeddingmodels.go
│   │   └── gemini
│   │       ├── gemini_test.go
│   │       └── gemini.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-healthcare.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
│   │       ├── elasticsearch.yaml
│   │       ├── firestore.yaml
│   │       ├── looker-conversational-analytics.yaml
│   │       ├── looker.yaml
│   │       ├── mindsdb.yaml
│   │       ├── mssql.yaml
│   │       ├── mysql.yaml
│   │       ├── neo4j.yaml
│   │       ├── oceanbase.yaml
│   │       ├── postgres.yaml
│   │       ├── serverless-spark.yaml
│   │       ├── singlestore.yaml
│   │       ├── snowflake.yaml
│   │       ├── spanner-postgres.yaml
│   │       ├── spanner.yaml
│   │       └── sqlite.yaml
│   ├── prompts
│   │   ├── arguments_test.go
│   │   ├── arguments.go
│   │   ├── custom
│   │   │   ├── custom_test.go
│   │   │   └── custom.go
│   │   ├── messages_test.go
│   │   ├── messages.go
│   │   ├── prompts_test.go
│   │   ├── prompts.go
│   │   ├── promptsets_test.go
│   │   └── promptsets.go
│   ├── 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
│   │   ├── resources
│   │   │   ├── resources_test.go
│   │   │   └── resources.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
│   │   │   └── cache.go
│   │   ├── bigtable
│   │   │   ├── bigtable_test.go
│   │   │   └── bigtable.go
│   │   ├── cassandra
│   │   │   ├── cassandra_test.go
│   │   │   └── cassandra.go
│   │   ├── clickhouse
│   │   │   ├── clickhouse_test.go
│   │   │   └── clickhouse.go
│   │   ├── cloudgda
│   │   │   ├── cloud_gda_test.go
│   │   │   └── cloud_gda.go
│   │   ├── cloudhealthcare
│   │   │   ├── cloud_healthcare_test.go
│   │   │   └── cloud_healthcare.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
│   │   ├── elasticsearch
│   │   │   ├── elasticsearch_test.go
│   │   │   └── elasticsearch.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
│   │   ├── mindsdb
│   │   │   ├── mindsdb_test.go
│   │   │   └── mindsdb.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_test.go
│   │   │   └── oracle.go
│   │   ├── postgres
│   │   │   ├── postgres_test.go
│   │   │   └── postgres.go
│   │   ├── redis
│   │   │   ├── redis_test.go
│   │   │   └── redis.go
│   │   ├── serverlessspark
│   │   │   ├── serverlessspark_test.go
│   │   │   ├── serverlessspark.go
│   │   │   ├── url_test.go
│   │   │   └── url.go
│   │   ├── singlestore
│   │   │   ├── singlestore_test.go
│   │   │   └── singlestore.go
│   │   ├── snowflake
│   │   │   ├── snowflake_test.go
│   │   │   └── snowflake.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
│   │   ├── cloudgda
│   │   │   ├── cloudgda_test.go
│   │   │   ├── cloudgda.go
│   │   │   └── types.go
│   │   ├── cloudhealthcare
│   │   │   ├── cloudhealthcarefhirfetchpage
│   │   │   │   ├── cloudhealthcarefhirfetchpage_test.go
│   │   │   │   └── cloudhealthcarefhirfetchpage.go
│   │   │   ├── cloudhealthcarefhirpatienteverything
│   │   │   │   ├── cloudhealthcarefhirpatienteverything_test.go
│   │   │   │   └── cloudhealthcarefhirpatienteverything.go
│   │   │   ├── cloudhealthcarefhirpatientsearch
│   │   │   │   ├── cloudhealthcarefhirpatientsearch_test.go
│   │   │   │   └── cloudhealthcarefhirpatientsearch.go
│   │   │   ├── cloudhealthcaregetdataset
│   │   │   │   ├── cloudhealthcaregetdataset_test.go
│   │   │   │   └── cloudhealthcaregetdataset.go
│   │   │   ├── cloudhealthcaregetdicomstore
│   │   │   │   ├── cloudhealthcaregetdicomstore_test.go
│   │   │   │   └── cloudhealthcaregetdicomstore.go
│   │   │   ├── cloudhealthcaregetdicomstoremetrics
│   │   │   │   ├── cloudhealthcaregetdicomstoremetrics_test.go
│   │   │   │   └── cloudhealthcaregetdicomstoremetrics.go
│   │   │   ├── cloudhealthcaregetfhirresource
│   │   │   │   ├── cloudhealthcaregetfhirresource_test.go
│   │   │   │   └── cloudhealthcaregetfhirresource.go
│   │   │   ├── cloudhealthcaregetfhirstore
│   │   │   │   ├── cloudhealthcaregetfhirstore_test.go
│   │   │   │   └── cloudhealthcaregetfhirstore.go
│   │   │   ├── cloudhealthcaregetfhirstoremetrics
│   │   │   │   ├── cloudhealthcaregetfhirstoremetrics_test.go
│   │   │   │   └── cloudhealthcaregetfhirstoremetrics.go
│   │   │   ├── cloudhealthcarelistdicomstores
│   │   │   │   ├── cloudhealthcarelistdicomstores_test.go
│   │   │   │   └── cloudhealthcarelistdicomstores.go
│   │   │   ├── cloudhealthcarelistfhirstores
│   │   │   │   ├── cloudhealthcarelistfhirstores_test.go
│   │   │   │   └── cloudhealthcarelistfhirstores.go
│   │   │   ├── cloudhealthcareretrieverendereddicominstance
│   │   │   │   ├── cloudhealthcareretrieverendereddicominstance_test.go
│   │   │   │   └── cloudhealthcareretrieverendereddicominstance.go
│   │   │   ├── cloudhealthcaresearchdicominstances
│   │   │   │   ├── cloudhealthcaresearchdicominstances_test.go
│   │   │   │   └── cloudhealthcaresearchdicominstances.go
│   │   │   ├── cloudhealthcaresearchdicomseries
│   │   │   │   ├── cloudhealthcaresearchdicomseries_test.go
│   │   │   │   └── cloudhealthcaresearchdicomseries.go
│   │   │   ├── cloudhealthcaresearchdicomstudies
│   │   │   │   ├── cloudhealthcaresearchdicomstudies_test.go
│   │   │   │   └── cloudhealthcaresearchdicomstudies.go
│   │   │   └── common
│   │   │       └── util.go
│   │   ├── cloudmonitoring
│   │   │   ├── cloudmonitoring_test.go
│   │   │   └── cloudmonitoring.go
│   │   ├── cloudsql
│   │   │   ├── cloudsqlcloneinstance
│   │   │   │   ├── cloudsqlcloneinstance_test.go
│   │   │   │   └── cloudsqlcloneinstance.go
│   │   │   ├── 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
│   │   │   └── cloudsqlpgupgradeprecheck
│   │   │       ├── cloudsqlpgupgradeprecheck_test.go
│   │   │       └── cloudsqlpgupgradeprecheck.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
│   │   ├── elasticsearch
│   │   │   └── elasticsearchesql
│   │   │       ├── elasticsearchesql_test.go
│   │   │       └── elasticsearchesql.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
│   │   │   ├── lookeradddashboardfilter
│   │   │   │   ├── lookeradddashboardfilter_test.go
│   │   │   │   └── lookeradddashboardfilter.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
│   │   │   ├── lookergenerateembedurl
│   │   │   │   ├── lookergenerateembedurl_test.go
│   │   │   │   └── lookergenerateembedurl.go
│   │   │   ├── lookergetconnectiondatabases
│   │   │   │   ├── lookergetconnectiondatabases_test.go
│   │   │   │   └── lookergetconnectiondatabases.go
│   │   │   ├── lookergetconnections
│   │   │   │   ├── lookergetconnections_test.go
│   │   │   │   └── lookergetconnections.go
│   │   │   ├── lookergetconnectionschemas
│   │   │   │   ├── lookergetconnectionschemas_test.go
│   │   │   │   └── lookergetconnectionschemas.go
│   │   │   ├── lookergetconnectiontablecolumns
│   │   │   │   ├── lookergetconnectiontablecolumns_test.go
│   │   │   │   └── lookergetconnectiontablecolumns.go
│   │   │   ├── lookergetconnectiontables
│   │   │   │   ├── lookergetconnectiontables_test.go
│   │   │   │   └── lookergetconnectiontables.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
│   │   │   ├── lookerrundashboard
│   │   │   │   ├── lookerrundashboard_test.go
│   │   │   │   └── lookerrundashboard.go
│   │   │   ├── lookerrunlook
│   │   │   │   ├── lookerrunlook_test.go
│   │   │   │   └── lookerrunlook.go
│   │   │   └── lookerupdateprojectfile
│   │   │       ├── lookerupdateprojectfile_test.go
│   │   │       └── lookerupdateprojectfile.go
│   │   ├── mindsdb
│   │   │   ├── mindsdbexecutesql
│   │   │   │   ├── mindsdbexecutesql_test.go
│   │   │   │   └── mindsdbexecutesql.go
│   │   │   └── mindsdbsql
│   │   │       ├── mindsdbsql_test.go
│   │   │       └── mindsdbsql.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
│   │   │   ├── mysqlgetqueryplan
│   │   │   │   ├── mysqlgetqueryplan_test.go
│   │   │   │   └── mysqlgetqueryplan.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_test.go
│   │   │   │   └── oracleexecutesql.go
│   │   │   └── oraclesql
│   │   │       ├── oraclesql_test.go
│   │   │       └── oraclesql.go
│   │   ├── postgres
│   │   │   ├── postgresdatabaseoverview
│   │   │   │   ├── postgresdatabaseoverview_test.go
│   │   │   │   └── postgresdatabaseoverview.go
│   │   │   ├── postgresexecutesql
│   │   │   │   ├── postgresexecutesql_test.go
│   │   │   │   └── postgresexecutesql.go
│   │   │   ├── postgresgetcolumncardinality
│   │   │   │   ├── postgresgetcolumncardinality_test.go
│   │   │   │   └── postgresgetcolumncardinality.go
│   │   │   ├── postgreslistactivequeries
│   │   │   │   ├── postgreslistactivequeries_test.go
│   │   │   │   └── postgreslistactivequeries.go
│   │   │   ├── postgreslistavailableextensions
│   │   │   │   ├── postgreslistavailableextensions_test.go
│   │   │   │   └── postgreslistavailableextensions.go
│   │   │   ├── postgreslistdatabasestats
│   │   │   │   ├── postgreslistdatabasestats_test.go
│   │   │   │   └── postgreslistdatabasestats.go
│   │   │   ├── postgreslistindexes
│   │   │   │   ├── postgreslistindexes_test.go
│   │   │   │   └── postgreslistindexes.go
│   │   │   ├── postgreslistinstalledextensions
│   │   │   │   ├── postgreslistinstalledextensions_test.go
│   │   │   │   └── postgreslistinstalledextensions.go
│   │   │   ├── postgreslistlocks
│   │   │   │   ├── postgreslistlocks_test.go
│   │   │   │   └── postgreslistlocks.go
│   │   │   ├── postgreslistpgsettings
│   │   │   │   ├── postgreslistpgsettings_test.go
│   │   │   │   └── postgreslistpgsettings.go
│   │   │   ├── postgreslistpublicationtables
│   │   │   │   ├── postgreslistpublicationtables_test.go
│   │   │   │   └── postgreslistpublicationtables.go
│   │   │   ├── postgreslistquerystats
│   │   │   │   ├── postgreslistquerystats_test.go
│   │   │   │   └── postgreslistquerystats.go
│   │   │   ├── postgreslistroles
│   │   │   │   ├── postgreslistroles_test.go
│   │   │   │   └── postgreslistroles.go
│   │   │   ├── postgreslistschemas
│   │   │   │   ├── postgreslistschemas_test.go
│   │   │   │   └── postgreslistschemas.go
│   │   │   ├── postgreslistsequences
│   │   │   │   ├── postgreslistsequences_test.go
│   │   │   │   └── postgreslistsequences.go
│   │   │   ├── postgresliststoredprocedure
│   │   │   │   ├── postgresliststoredprocedure_test.go
│   │   │   │   └── postgresliststoredprocedure.go
│   │   │   ├── postgreslisttables
│   │   │   │   ├── postgreslisttables_test.go
│   │   │   │   └── postgreslisttables.go
│   │   │   ├── postgreslisttablespaces
│   │   │   │   ├── postgreslisttablespaces_test.go
│   │   │   │   └── postgreslisttablespaces.go
│   │   │   ├── postgreslisttablestats
│   │   │   │   ├── postgreslisttablestats_test.go
│   │   │   │   └── postgreslisttablestats.go
│   │   │   ├── postgreslisttriggers
│   │   │   │   ├── postgreslisttriggers_test.go
│   │   │   │   └── postgreslisttriggers.go
│   │   │   ├── postgreslistviews
│   │   │   │   ├── postgreslistviews_test.go
│   │   │   │   └── postgreslistviews.go
│   │   │   ├── postgreslongrunningtransactions
│   │   │   │   ├── postgreslongrunningtransactions_test.go
│   │   │   │   └── postgreslongrunningtransactions.go
│   │   │   ├── postgresreplicationstats
│   │   │   │   ├── postgresreplicationstats_test.go
│   │   │   │   └── postgresreplicationstats.go
│   │   │   └── postgressql
│   │   │       ├── postgressql_test.go
│   │   │       └── postgressql.go
│   │   ├── redis
│   │   │   ├── redis_test.go
│   │   │   └── redis.go
│   │   ├── serverlessspark
│   │   │   ├── createbatch
│   │   │   │   ├── config.go
│   │   │   │   └── tool.go
│   │   │   ├── serverlesssparkcancelbatch
│   │   │   │   ├── serverlesssparkcancelbatch_test.go
│   │   │   │   └── serverlesssparkcancelbatch.go
│   │   │   ├── serverlesssparkcreatepysparkbatch
│   │   │   │   ├── serverlesssparkcreatepysparkbatch_test.go
│   │   │   │   └── serverlesssparkcreatepysparkbatch.go
│   │   │   ├── serverlesssparkcreatesparkbatch
│   │   │   │   ├── serverlesssparkcreatesparkbatch_test.go
│   │   │   │   └── serverlesssparkcreatesparkbatch.go
│   │   │   ├── serverlesssparkgetbatch
│   │   │   │   ├── serverlesssparkgetbatch_test.go
│   │   │   │   └── serverlesssparkgetbatch.go
│   │   │   ├── serverlesssparklistbatches
│   │   │   │   ├── serverlesssparklistbatches_test.go
│   │   │   │   └── serverlesssparklistbatches.go
│   │   │   └── testutils
│   │   │       └── testutils.go
│   │   ├── singlestore
│   │   │   ├── singlestoreexecutesql
│   │   │   │   ├── singlestoreexecutesql_test.go
│   │   │   │   └── singlestoreexecutesql.go
│   │   │   └── singlestoresql
│   │   │       ├── singlestoresql_test.go
│   │   │       └── singlestoresql.go
│   │   ├── snowflake
│   │   │   ├── snowflakeexecutesql
│   │   │   │   ├── snowflakeexecutesql_test.go
│   │   │   │   └── snowflakeexecutesql.go
│   │   │   └── snowflakesql
│   │   │       ├── snowflakesql_test.go
│   │   │       └── snowflakesql.go
│   │   ├── spanner
│   │   │   ├── spannerexecutesql
│   │   │   │   ├── spannerexecutesql_test.go
│   │   │   │   └── spannerexecutesql.go
│   │   │   ├── spannerlistgraphs
│   │   │   │   ├── spannerlistgraphs_test.go
│   │   │   │   └── spannerlistgraphs.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
│       ├── orderedmap
│       │   ├── orderedmap_test.go
│       │   └── orderedmap.go
│       ├── parameters
│       │   ├── common_test.go
│       │   ├── common.go
│       │   ├── parameters_test.go
│       │   └── parameters.go
│       └── util.go
├── LICENSE
├── logo.png
├── main.go
├── MCP-TOOLBOX-EXTENSION.md
├── README.md
├── server.json
└── 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
    ├── cloudgda
    │   └── cloud_gda_integration_test.go
    ├── cloudhealthcare
    │   └── cloud_healthcare_integration_test.go
    ├── cloudmonitoring
    │   └── cloud_monitoring_integration_test.go
    ├── cloudsql
    │   ├── cloud_sql_clone_instance_test.go
    │   ├── 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
    │   └── cloud_sql_pg_upgrade_precheck_test.go
    ├── common.go
    ├── couchbase
    │   └── couchbase_integration_test.go
    ├── dataform
    │   └── dataform_integration_test.go
    ├── dataplex
    │   └── dataplex_integration_test.go
    ├── dgraph
    │   └── dgraph_integration_test.go
    ├── elasticsearch
    │   └── elasticsearch_integration_test.go
    ├── firebird
    │   └── firebird_integration_test.go
    ├── firestore
    │   └── firestore_integration_test.go
    ├── http
    │   └── http_integration_test.go
    ├── looker
    │   └── looker_integration_test.go
    ├── mariadb
    │   └── mariadb_integration_test.go
    ├── mindsdb
    │   └── mindsdb_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
    ├── prompts
    │   └── custom
    │       └── prompts_integration_test.go
    ├── redis
    │   └── redis_test.go
    ├── server.go
    ├── serverlessspark
    │   └── serverless_spark_integration_test.go
    ├── singlestore
    │   └── singlestore_integration_test.go
    ├── snowflake
    │   └── snowflake_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

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

```markdown
  1 | ---
  2 | title: "looker-query-url"
  3 | type: docs
  4 | weight: 1
  5 | description: >
  6 |   "looker-query-url" generates a url link to a Looker explore.
  7 | aliases:
  8 | - /resources/tools/looker-query-url
  9 | ---
 10 | 
 11 | ## About
 12 | 
 13 | The `looker-query-url` generates a url link to an explore in
 14 | Looker so the query can be investigated further.
 15 | 
 16 | It's compatible with the following sources:
 17 | 
 18 | - [looker](../../sources/looker.md)
 19 | 
 20 | `looker-query-url` takes nine parameters:
 21 | 
 22 | 1. the `model`
 23 | 2. the `explore`
 24 | 3. the `fields` list
 25 | 4. an optional set of `filters`
 26 | 5. an optional set of `pivots`
 27 | 6. an optional set of `sorts`
 28 | 7. an optional `limit`
 29 | 8. an optional `tz`
 30 | 9. an optional `vis_config`
 31 | 
 32 | ## Example
 33 | 
 34 | ```yaml
 35 | tools:
 36 |     query_url:
 37 |         kind: looker-query-url
 38 |         source: looker-source
 39 |         description: |
 40 |           This tool generates a shareable URL for a Looker query, allowing users to
 41 |           explore the query further within the Looker UI. It returns the generated URL,
 42 |           along with the `query_id` and `slug`.
 43 | 
 44 |           Parameters:
 45 |           All query parameters (e.g., `model_name`, `explore_name`, `fields`, `pivots`,
 46 |           `filters`, `sorts`, `limit`, `query_timezone`) are the same as the `query` tool.
 47 | 
 48 |           Additionally, it accepts an optional `vis_config` parameter:
 49 |           - vis_config (optional): A JSON object that controls the default visualization
 50 |             settings for the generated query.
 51 | 
 52 |           vis_config Details:
 53 |           The `vis_config` object supports a wide range of properties for various chart types.
 54 |           Here are some notes on making visualizations.
 55 | 
 56 |           ### Cartesian Charts (Area, Bar, Column, Line, Scatter)
 57 | 
 58 |           These chart types share a large number of configuration options.
 59 | 
 60 |           **General**
 61 |           *   `type`: The type of visualization (`looker_area`, `looker_bar`, `looker_column`, `looker_line`, `looker_scatter`).
 62 |           *   `series_types`: Override the chart type for individual series.
 63 |           *   `show_view_names`: Display view names in labels and tooltips (`true`/`false`).
 64 |           *   `series_labels`: Provide custom names for series.
 65 | 
 66 |           **Styling & Colors**
 67 |           *   `colors`: An array of color values to be used for the chart series.
 68 |           *   `series_colors`: A mapping of series names to specific color values.
 69 |           *   `color_application`: Advanced controls for color palette application (collection, palette, reverse, etc.).
 70 |           *   `font_size`: Font size for labels (e.g., '12px').
 71 | 
 72 |           **Legend**
 73 |           *   `hide_legend`: Show or hide the chart legend (`true`/`false`).
 74 |           *   `legend_position`: Placement of the legend (`'center'`, `'left'`, `'right'`).
 75 | 
 76 |           **Axes**
 77 |           *   `swap_axes`: Swap the X and Y axes (`true`/`false`).
 78 |           *   `x_axis_scale`: Scale of the x-axis (`'auto'`, `'ordinal'`, `'linear'`, `'time'`).
 79 |           *   `x_axis_reversed`, `y_axis_reversed`: Reverse the direction of an axis (`true`/`false`).
 80 |           *   `x_axis_gridlines`, `y_axis_gridlines`: Display gridlines for an axis (`true`/`false`).
 81 |           *   `show_x_axis_label`, `show_y_axis_label`: Show or hide the axis title (`true`/`false`).
 82 |           *   `show_x_axis_ticks`, `show_y_axis_ticks`: Show or hide axis tick marks (`true`/`false`).
 83 |           *   `x_axis_label`, `y_axis_label`: Set a custom title for an axis.
 84 |           *   `x_axis_datetime_label`: A format string for datetime labels on the x-axis (e.g., `'%Y-%m'`).
 85 |           *   `x_padding_left`, `x_padding_right`: Adjust padding on the ends of the x-axis.
 86 |           *   `x_axis_label_rotation`, `x_axis_label_rotation_bar`: Set rotation for x-axis labels.
 87 |           *   `x_axis_zoom`, `y_axis_zoom`: Enable zooming on an axis (`true`/`false`).
 88 |           *   `y_axes`: An array of configuration objects for multiple y-axes.
 89 | 
 90 |           **Data & Series**
 91 |           *   `stacking`: How to stack series (`''` for none, `'normal'`, `'percent'`).
 92 |           *   `ordering`: Order of series in a stack (`'none'`, etc.).
 93 |           *   `limit_displayed_rows`: Enable or disable limiting the number of rows displayed (`true`/`false`).
 94 |           *   `limit_displayed_rows_values`: Configuration for the row limit (e.g., `{ "first_last": "first", "show_hide": "show", "num_rows": 10 }`).
 95 |           *   `discontinuous_nulls`: How to render null values in line charts (`true`/`false`).
 96 |           *   `point_style`: Style for points on line and area charts (`'none'`, `'circle'`, `'circle_outline'`).
 97 |           *   `series_point_styles`: Override point styles for individual series.
 98 |           *   `interpolation`: Line interpolation style (`'linear'`, `'monotone'`, `'step'`, etc.).
 99 |           *   `show_value_labels`: Display values on data points (`true`/`false`).
100 |           *   `label_value_format`: A format string for value labels.
101 |           *   `show_totals_labels`: Display total labels on stacked charts (`true`/`false`).
102 |           *   `totals_color`: Color for total labels.
103 |           *   `show_silhouette`: Display a "silhouette" of hidden series in stacked charts (`true`/`false`).
104 |           *   `hidden_series`: An array of series names to hide from the visualization.
105 | 
106 |           **Scatter/Bubble Specific**
107 |           *   `size_by_field`: The field used to determine the size of bubbles.
108 |           *   `color_by_field`: The field used to determine the color of bubbles.
109 |           *   `plot_size_by_field`: Whether to display the size-by field in the legend.
110 |           *   `cluster_points`: Group nearby points into clusters (`true`/`false`).
111 |           *   `quadrants_enabled`: Display quadrants on the chart (`true`/`false`).
112 |           *   `quadrant_properties`: Configuration for quadrant labels and colors.
113 |           *   `custom_quadrant_value_x`, `custom_quadrant_value_y`: Set quadrant boundaries as a percentage.
114 |           *   `custom_quadrant_point_x`, `custom_quadrant_point_y`: Set quadrant boundaries to a specific value.
115 | 
116 |           **Miscellaneous**
117 |           *   `reference_lines`: Configuration for displaying reference lines.
118 |           *   `trend_lines`: Configuration for displaying trend lines.
119 |           *   `trellis`: Configuration for creating trellis (small multiple) charts.
120 |           *   `crossfilterEnabled`, `crossfilters`: Configuration for cross-filtering interactions.
121 | 
122 |           ### Boxplot
123 | 
124 |           *   Inherits most of the Cartesian chart options.
125 |           *   `type`: Must be `looker_boxplot`.
126 | 
127 |           ### Funnel
128 | 
129 |           *   `type`: Must be `looker_funnel`.
130 |           *   `orientation`: How data is read (`'automatic'`, `'dataInRows'`, `'dataInColumns'`).
131 |           *   `percentType`: How percentages are calculated (`'percentOfMaxValue'`, `'percentOfPriorRow'`).
132 |           *   `labelPosition`, `valuePosition`, `percentPosition`: Placement of labels (`'left'`, `'right'`, `'inline'`, `'hidden'`).
133 |           *   `labelColor`, `labelColorEnabled`: Set a custom color for labels.
134 |           *   `labelOverlap`: Allow labels to overlap (`true`/`false`).
135 |           *   `barColors`: An array of colors for the funnel steps.
136 |           *   `color_application`: Advanced color palette controls.
137 |           *   `crossfilterEnabled`, `crossfilters`: Configuration for cross-filtering.
138 | 
139 |           ### Pie / Donut
140 | 
141 |           *   `type`: Must be `looker_pie`.
142 |           *   `value_labels`: Where to display values (`'legend'`, `'labels'`).
143 |           *   `label_type`: The format of data labels (`'labPer'`, `'labVal'`, `'lab'`, `'val'`, `'per'`).
144 |           *   `start_angle`, `end_angle`: The start and end angles of the pie chart.
145 |           *   `inner_radius`: The inner radius, used to create a donut chart.
146 |           *   `series_colors`, `series_labels`: Override colors and labels for specific slices.
147 |           *   `color_application`: Advanced color palette controls.
148 |           *   `crossfilterEnabled`, `crossfilters`: Configuration for cross-filtering.
149 |           *   `advanced_vis_config`: A string containing JSON for advanced Highcharts configuration.
150 | 
151 |           ### Waterfall
152 | 
153 |           *   Inherits most of the Cartesian chart options.
154 |           *   `type`: Must be `looker_waterfall`.
155 |           *   `up_color`: Color for positive (increasing) values.
156 |           *   `down_color`: Color for negative (decreasing) values.
157 |           *   `total_color`: Color for the total bar.
158 | 
159 |           ### Word Cloud
160 | 
161 |           *   `type`: Must be `looker_wordcloud`.
162 |           *   `rotation`: Enable random word rotation (`true`/`false`).
163 |           *   `colors`: An array of colors for the words.
164 |           *   `color_application`: Advanced color palette controls.
165 |           *   `crossfilterEnabled`, `crossfilters`: Configuration for cross-filtering.
166 | 
167 |           These are some sample vis_config settings.
168 | 
169 |           A bar chart -
170 |           {{
171 |             "defaults_version": 1,
172 |             "label_density": 25,
173 |             "legend_position": "center",
174 |             "limit_displayed_rows": false,
175 |             "ordering": "none",
176 |             "plot_size_by_field": false,
177 |             "point_style": "none",
178 |             "show_null_labels": false,
179 |             "show_silhouette": false,
180 |             "show_totals_labels": false,
181 |             "show_value_labels": false,
182 |             "show_view_names": false,
183 |             "show_x_axis_label": true,
184 |             "show_x_axis_ticks": true,
185 |             "show_y_axis_labels": true,
186 |             "show_y_axis_ticks": true,
187 |             "stacking": "normal",
188 |             "totals_color": "#808080",
189 |             "trellis": "",
190 |             "type": "looker_bar",
191 |             "x_axis_gridlines": false,
192 |             "x_axis_reversed": false,
193 |             "x_axis_scale": "auto",
194 |             "x_axis_zoom": true,
195 |             "y_axis_combined": true,
196 |             "y_axis_gridlines": true,
197 |             "y_axis_reversed": false,
198 |             "y_axis_scale_mode": "linear",
199 |             "y_axis_tick_density": "default",
200 |             "y_axis_tick_density_custom": 5,
201 |             "y_axis_zoom": true
202 |           }}
203 | 
204 |           A column chart with an option advanced_vis_config -
205 |           {{
206 |             "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: [], }",
207 |             "colors": [
208 |               "grey"
209 |             ],
210 |             "defaults_version": 1,
211 |             "hidden_fields": [],
212 |             "label_density": 25,
213 |             "legend_position": "center",
214 |             "limit_displayed_rows": false,
215 |             "note_display": "below",
216 |             "note_state": "collapsed",
217 |             "note_text": "Unsold inventory only",
218 |             "ordering": "none",
219 |             "plot_size_by_field": false,
220 |             "point_style": "none",
221 |             "series_colors": {},
222 |             "show_null_labels": false,
223 |             "show_silhouette": false,
224 |             "show_totals_labels": false,
225 |             "show_value_labels": true,
226 |             "show_view_names": false,
227 |             "show_x_axis_label": true,
228 |             "show_x_axis_ticks": true,
229 |             "show_y_axis_labels": true,
230 |             "show_y_axis_ticks": true,
231 |             "stacking": "normal",
232 |             "totals_color": "#808080",
233 |             "trellis": "",
234 |             "type": "looker_column",
235 |             "x_axis_gridlines": false,
236 |             "x_axis_reversed": false,
237 |             "x_axis_scale": "auto",
238 |             "x_axis_zoom": true,
239 |             "y_axes": [],
240 |             "y_axis_combined": true,
241 |             "y_axis_gridlines": true,
242 |             "y_axis_reversed": false,
243 |             "y_axis_scale_mode": "linear",
244 |             "y_axis_tick_density": "default",
245 |             "y_axis_tick_density_custom": 5,
246 |             "y_axis_zoom": true
247 |           }}
248 | 
249 |           A line chart -
250 |           {{
251 |             "defaults_version": 1,
252 |             "hidden_pivots": {},
253 |             "hidden_series": [],
254 |             "interpolation": "linear",
255 |             "label_density": 25,
256 |             "legend_position": "center",
257 |             "limit_displayed_rows": false,
258 |             "plot_size_by_field": false,
259 |             "point_style": "none",
260 |             "series_types": {},
261 |             "show_null_points": true,
262 |             "show_value_labels": false,
263 |             "show_view_names": false,
264 |             "show_x_axis_label": true,
265 |             "show_x_axis_ticks": true,
266 |             "show_y_axis_labels": true,
267 |             "show_y_axis_ticks": true,
268 |             "stacking": "",
269 |             "trellis": "",
270 |             "type": "looker_line",
271 |             "x_axis_gridlines": false,
272 |             "x_axis_reversed": false,
273 |             "x_axis_scale": "auto",
274 |             "y_axis_combined": true,
275 |             "y_axis_gridlines": true,
276 |             "y_axis_reversed": false,
277 |             "y_axis_scale_mode": "linear",
278 |             "y_axis_tick_density": "default",
279 |             "y_axis_tick_density_custom": 5
280 |           }}
281 | 
282 |           An area chart -
283 |           {{
284 |             "defaults_version": 1,
285 |             "interpolation": "linear",
286 |             "label_density": 25,
287 |             "legend_position": "center",
288 |             "limit_displayed_rows": false,
289 |             "plot_size_by_field": false,
290 |             "point_style": "none",
291 |             "series_types": {},
292 |             "show_null_points": true,
293 |             "show_silhouette": false,
294 |             "show_totals_labels": false,
295 |             "show_value_labels": false,
296 |             "show_view_names": false,
297 |             "show_x_axis_label": true,
298 |             "show_x_axis_ticks": true,
299 |             "show_y_axis_labels": true,
300 |             "show_y_axis_ticks": true,
301 |             "stacking": "normal",
302 |             "totals_color": "#808080",
303 |             "trellis": "",
304 |             "type": "looker_area",
305 |             "x_axis_gridlines": false,
306 |             "x_axis_reversed": false,
307 |             "x_axis_scale": "auto",
308 |             "x_axis_zoom": true,
309 |             "y_axis_combined": true,
310 |             "y_axis_gridlines": true,
311 |             "y_axis_reversed": false,
312 |             "y_axis_scale_mode": "linear",
313 |             "y_axis_tick_density": "default",
314 |             "y_axis_tick_density_custom": 5,
315 |             "y_axis_zoom": true
316 |           }}
317 | 
318 |           A scatter plot -
319 |           {{
320 |             "cluster_points": false,
321 |             "custom_quadrant_point_x": 5,
322 |             "custom_quadrant_point_y": 5,
323 |             "custom_value_label_column": "",
324 |             "custom_x_column": "",
325 |             "custom_y_column": "",
326 |             "defaults_version": 1,
327 |             "hidden_fields": [],
328 |             "hidden_pivots": {},
329 |             "hidden_points_if_no": [],
330 |             "hidden_series": [],
331 |             "interpolation": "linear",
332 |             "label_density": 25,
333 |             "legend_position": "center",
334 |             "limit_displayed_rows": false,
335 |             "limit_displayed_rows_values": {
336 |               "first_last": "first",
337 |               "num_rows": 0,
338 |               "show_hide": "hide"
339 |             },
340 |             "plot_size_by_field": false,
341 |             "point_style": "circle",
342 |             "quadrant_properties": {
343 |               "0": {
344 |                 "color": "",
345 |                 "label": "Quadrant 1"
346 |               },
347 |               "1": {
348 |                 "color": "",
349 |                 "label": "Quadrant 2"
350 |               },
351 |               "2": {
352 |                 "color": "",
353 |                 "label": "Quadrant 3"
354 |               },
355 |               "3": {
356 |                 "color": "",
357 |                 "label": "Quadrant 4"
358 |               }
359 |             },
360 |             "quadrants_enabled": false,
361 |             "series_labels": {},
362 |             "series_types": {},
363 |             "show_null_points": false,
364 |             "show_value_labels": false,
365 |             "show_view_names": true,
366 |             "show_x_axis_label": true,
367 |             "show_x_axis_ticks": true,
368 |             "show_y_axis_labels": true,
369 |             "show_y_axis_ticks": true,
370 |             "size_by_field": "roi",
371 |             "stacking": "normal",
372 |             "swap_axes": true,
373 |             "trellis": "",
374 |             "type": "looker_scatter",
375 |             "x_axis_gridlines": false,
376 |             "x_axis_reversed": false,
377 |             "x_axis_scale": "auto",
378 |             "x_axis_zoom": true,
379 |             "y_axes": [
380 |               {
381 |                 "label": "",
382 |                 "orientation": "bottom",
383 |                 "series": [
384 |                   {
385 |                     "axisId": "Channel_0 - average_of_roi_first",
386 |                     "id": "Channel_0 - average_of_roi_first",
387 |                     "name": "Channel_0"
388 |                   },
389 |                   {
390 |                     "axisId": "Channel_1 - average_of_roi_first",
391 |                     "id": "Channel_1 - average_of_roi_first",
392 |                     "name": "Channel_1"
393 |                   },
394 |                   {
395 |                     "axisId": "Channel_2 - average_of_roi_first",
396 |                     "id": "Channel_2 - average_of_roi_first",
397 |                     "name": "Channel_2"
398 |                   },
399 |                   {
400 |                     "axisId": "Channel_3 - average_of_roi_first",
401 |                     "id": "Channel_3 - average_of_roi_first",
402 |                     "name": "Channel_3"
403 |                   },
404 |                   {
405 |                     "axisId": "Channel_4 - average_of_roi_first",
406 |                     "id": "Channel_4 - average_of_roi_first",
407 |                     "name": "Channel_4"
408 |                   }
409 |                 ],
410 |                 "showLabels": true,
411 |                 "showValues": true,
412 |                 "tickDensity": "custom",
413 |                 "tickDensityCustom": 100,
414 |                 "type": "linear",
415 |                 "unpinAxis": false
416 |               }
417 |             ],
418 |             "y_axis_combined": true,
419 |             "y_axis_gridlines": true,
420 |             "y_axis_reversed": false,
421 |             "y_axis_scale_mode": "linear",
422 |             "y_axis_tick_density": "default",
423 |             "y_axis_tick_density_custom": 5,
424 |             "y_axis_zoom": true
425 |           }}
426 | 
427 |           A single record visualization -
428 |           {{
429 |             "defaults_version": 1,
430 |             "show_view_names": false,
431 |             "type": "looker_single_record"
432 |           }}
433 | 
434 |           A single value visualization -
435 |           {{
436 |             "comparison_reverse_colors": false,
437 |             "comparison_type": "value",                                                                                                                                            "conditional_formatting_include_nulls": false,                                                                                                                         "conditional_formatting_include_totals": false,
438 |             "custom_color": "#1A73E8",
439 |             "custom_color_enabled": true,
440 |             "defaults_version": 1,
441 |             "enable_conditional_formatting": false,
442 |             "series_types": {},
443 |             "show_comparison": false,
444 |             "show_comparison_label": true,
445 |             "show_single_value_title": true,
446 |             "single_value_title": "Total Clicks",
447 |             "type": "single_value"
448 |           }}
449 | 
450 |           A Pie chart -
451 |           {{
452 |             "defaults_version": 1,
453 |             "label_density": 25,
454 |             "label_type": "labPer",
455 |             "legend_position": "center",
456 |             "limit_displayed_rows": false,
457 |             "ordering": "none",
458 |             "plot_size_by_field": false,
459 |             "point_style": "none",
460 |             "series_types": {},
461 |             "show_null_labels": false,
462 |             "show_silhouette": false,
463 |             "show_totals_labels": false,
464 |             "show_value_labels": false,
465 |             "show_view_names": false,
466 |             "show_x_axis_label": true,
467 |             "show_x_axis_ticks": true,
468 |             "show_y_axis_labels": true,
469 |             "show_y_axis_ticks": true,
470 |             "stacking": "",
471 |             "totals_color": "#808080",
472 |             "trellis": "",
473 |             "type": "looker_pie",
474 |             "value_labels": "legend",
475 |             "x_axis_gridlines": false,
476 |             "x_axis_reversed": false,
477 |             "x_axis_scale": "auto",
478 |             "y_axis_combined": true,
479 |             "y_axis_gridlines": true,
480 |             "y_axis_reversed": false,
481 |             "y_axis_scale_mode": "linear",
482 |             "y_axis_tick_density": "default",
483 |             "y_axis_tick_density_custom": 5
484 |           }}
485 | 
486 |           The result is a JSON object with the id, slug, the url, and
487 |           the long_url.
488 | ```
489 | 
490 | ## Reference
491 | 
492 | | **field**   | **type** | **required** | **description**                                    |
493 | |-------------|:--------:|:------------:|----------------------------------------------------|
494 | | kind        |  string  |     true     | Must be "looker-query-url"                         |
495 | | source      |  string  |     true     | Name of the source the SQL should execute on.      |
496 | | description |  string  |     true     | Description of the tool that is passed to the LLM. |
497 | 
```

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

```javascript
  1 | // Copyright 2025 Google LLC
  2 | //
  3 | // Licensed under the Apache License, Version 2.0 (the "License");
  4 | // you may not use this file except in compliance with the License.
  5 | // You may obtain a copy of the License at
  6 | //
  7 | //     http://www.apache.org/licenses/LICENSE-2.0
  8 | //
  9 | // Unless required by applicable law or agreed to in writing, software
 10 | // distributed under the License is distributed on an "AS IS" BASIS,
 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12 | // See the License for the specific language governing permissions and
 13 | // limitations under the License.
 14 | 
 15 | import { handleRunTool, displayResults } from './runTool.js';
 16 | import { createGoogleAuthMethodItem } from './auth.js'
 17 | 
 18 | /**
 19 |  * Helper function to create form inputs for parameters.
 20 |  */
 21 | function createParamInput(param, toolId) {
 22 |     const paramItem = document.createElement('div');
 23 |     paramItem.className = 'param-item';
 24 | 
 25 |     const label = document.createElement('label');
 26 |     const INPUT_ID = `param-${toolId}-${param.name}`;
 27 |     const NAME_TEXT = document.createTextNode(param.name);
 28 |     label.setAttribute('for', INPUT_ID);
 29 |     label.appendChild(NAME_TEXT);
 30 | 
 31 |     const IS_AUTH_PARAM = param.authServices && param.authServices.length > 0;
 32 |     let additionalLabelText = '';
 33 |     if (IS_AUTH_PARAM) {
 34 |         additionalLabelText += ' (auth)';
 35 |     }
 36 |     if (!param.required) {
 37 |         additionalLabelText += ' (optional)';
 38 |     }
 39 | 
 40 |     if (additionalLabelText) {
 41 |         const additionalSpan = document.createElement('span');
 42 |         additionalSpan.textContent = additionalLabelText;
 43 |         additionalSpan.classList.add('param-label-extras');
 44 |         label.appendChild(additionalSpan);
 45 |     }
 46 |     paramItem.appendChild(label);
 47 | 
 48 |     const inputCheckboxWrapper = document.createElement('div');
 49 |     const inputContainer = document.createElement('div');
 50 |     inputCheckboxWrapper.className = 'input-checkbox-wrapper';
 51 |     inputContainer.className = 'param-input-element-container';
 52 | 
 53 |     // Build parameter's value input box.
 54 |     const PLACEHOLDER_LABEL = param.label;
 55 |     let inputElement;
 56 |     let boolValueLabel = null;
 57 | 
 58 |     if (param.type === 'textarea') {
 59 |         inputElement = document.createElement('textarea');
 60 |         inputElement.rows = 3;
 61 |         inputContainer.appendChild(inputElement);
 62 |     } else if(param.type === 'checkbox') {
 63 |         inputElement = document.createElement('input');
 64 |         inputElement.type = 'checkbox';
 65 |         inputElement.title = PLACEHOLDER_LABEL;
 66 |         inputElement.checked = false;
 67 | 
 68 |         // handle true/false label for boolean params
 69 |         boolValueLabel = document.createElement('span');
 70 |         boolValueLabel.className = 'checkbox-bool-label';
 71 |         boolValueLabel.textContent = inputElement.checked ? ' true' : ' false';
 72 | 
 73 |         inputContainer.appendChild(inputElement); 
 74 |         inputContainer.appendChild(boolValueLabel); 
 75 | 
 76 |         inputElement.addEventListener('change', () => {
 77 |             boolValueLabel.textContent = inputElement.checked ? ' true' : ' false';
 78 |         });
 79 |     } else {
 80 |         inputElement = document.createElement('input');
 81 |         inputElement.type = param.type;
 82 |         inputContainer.appendChild(inputElement);
 83 |     }
 84 | 
 85 |     inputElement.id = INPUT_ID;
 86 |     inputElement.name = param.name;
 87 |     inputElement.classList.add('param-input-element');
 88 | 
 89 |     if (IS_AUTH_PARAM) {
 90 |         inputElement.disabled = true;
 91 |         inputElement.classList.add('auth-param-input');
 92 |         if (param.type !== 'checkbox') {
 93 |             inputElement.placeholder = param.authServices;
 94 |         }
 95 |     } else if (param.type !== 'checkbox') {
 96 |         inputElement.placeholder = PLACEHOLDER_LABEL ? PLACEHOLDER_LABEL.trim() : '';
 97 |     }
 98 |     inputCheckboxWrapper.appendChild(inputContainer);
 99 | 
100 |     // create the "Include Param" checkbox
101 |     const INCLUDE_CHECKBOX_ID = `include-${INPUT_ID}`;
102 |     const includeContainer = document.createElement('div');
103 |     const includeCheckbox = document.createElement('input');
104 | 
105 |     includeContainer.className = 'include-param-container';
106 |     includeCheckbox.type = 'checkbox';
107 |     includeCheckbox.id = INCLUDE_CHECKBOX_ID;
108 |     includeCheckbox.name = `include-${param.name}`;
109 |     includeCheckbox.title = 'Include this parameter'; // Add a tooltip
110 | 
111 |     // default to checked, unless it's an optional parameter
112 |     includeCheckbox.checked = param.required;
113 | 
114 |     includeContainer.appendChild(includeCheckbox);
115 |     inputCheckboxWrapper.appendChild(includeContainer);
116 | 
117 |     paramItem.appendChild(inputCheckboxWrapper);
118 | 
119 |     // function to update UI based on checkbox state
120 |     const updateParamIncludedState = () => {
121 |         const isIncluded = includeCheckbox.checked;
122 |         if (isIncluded) {
123 |             paramItem.classList.remove('disabled-param');
124 |             if (!IS_AUTH_PARAM) {
125 |                  inputElement.disabled = false;
126 |             }
127 |             if (boolValueLabel) {
128 |                 boolValueLabel.classList.remove('disabled');
129 |             }
130 |         } else {
131 |             paramItem.classList.add('disabled-param');
132 |             inputElement.disabled = true;
133 |             if (boolValueLabel) {
134 |                 boolValueLabel.classList.add('disabled');
135 |             }
136 |         }
137 |     };
138 | 
139 |     // add event listener to the include checkbox
140 |     includeCheckbox.addEventListener('change', updateParamIncludedState);
141 |     updateParamIncludedState(); 
142 | 
143 |     return paramItem;
144 | }
145 | 
146 | /**
147 |  * Function to create the header editor popup modal.
148 |  * @param {string} toolId The unique identifier for the tool.
149 |  * @param {!Object<string, string>} currentHeaders The current headers.
150 |  * @param {function(!Object<string, string>): void} saveCallback A function to be
151 |  *     called when the "Save" button is clicked and the headers are successfully
152 |  *     parsed. The function receives the updated headers object as its argument.
153 |  * @return {!HTMLDivElement} The outermost div element of the created modal.
154 |  */
155 | function createHeaderEditorModal(toolId, currentHeaders, toolParameters, authRequired, saveCallback) {
156 |     const MODAL_ID = `header-modal-${toolId}`;
157 |     let modal = document.getElementById(MODAL_ID);
158 | 
159 |     if (modal) {
160 |         modal.remove(); 
161 |     }
162 | 
163 |     modal = document.createElement('div');
164 |     modal.id = MODAL_ID;
165 |     modal.className = 'header-modal';
166 | 
167 |     const modalContent = document.createElement('div');
168 |     const modalHeader = document.createElement('h5');
169 |     const headersTextarea = document.createElement('textarea');
170 | 
171 |     modalContent.className = 'header-modal-content';
172 |     modalHeader.textContent = 'Edit Request Headers';
173 |     headersTextarea.id = `headers-textarea-${toolId}`;
174 |     headersTextarea.className = 'headers-textarea';
175 |     headersTextarea.rows = 10;
176 |     headersTextarea.value = JSON.stringify(currentHeaders, null, 2);
177 | 
178 |     // handle authenticated params
179 |     const authProfileNames = new Set();
180 |     toolParameters.forEach(param => {
181 |         const isAuthParam = param.authServices && param.authServices.length > 0;
182 |         if (isAuthParam && param.authServices) {
183 |              param.authServices.forEach(name => authProfileNames.add(name));
184 |         }
185 |     });
186 | 
187 |     // handle authorized invocations
188 |     if (authRequired && authRequired.length > 0) {
189 |         authRequired.forEach(name => authProfileNames.add(name));
190 |     }
191 | 
192 |     modalContent.appendChild(modalHeader);
193 |     modalContent.appendChild(headersTextarea);
194 | 
195 |     if (authProfileNames.size > 0 || authRequired.length > 0) {
196 |         const authHelperSection = document.createElement('div');
197 |         authHelperSection.className = 'auth-helper-section';
198 |         const authList = document.createElement('div');
199 |         authList.className = 'auth-method-list';
200 | 
201 |         authProfileNames.forEach(profileName => {
202 |             const authItem = createGoogleAuthMethodItem(toolId, profileName);
203 |             authList.appendChild(authItem);
204 |         });
205 |         authHelperSection.appendChild(authList);
206 |         modalContent.appendChild(authHelperSection);
207 |     }
208 | 
209 |     const modalActions = document.createElement('div');
210 |     const closeButton = document.createElement('button');
211 |     const saveButton = document.createElement('button');
212 |     const authTokenDropdown = createAuthTokenInfoDropdown();
213 | 
214 |     modalActions.className = 'header-modal-actions';
215 |     closeButton.textContent = 'Close';
216 |     closeButton.className = 'btn btn--closeHeaders';
217 |     closeButton.addEventListener('click', () => closeHeaderEditor(toolId));
218 |     saveButton.textContent = 'Save';
219 |     saveButton.className = 'btn btn--saveHeaders';
220 |     saveButton.addEventListener('click', () => {
221 |         try {
222 |             const updatedHeaders = JSON.parse(headersTextarea.value);
223 |             saveCallback(updatedHeaders);
224 |             closeHeaderEditor(toolId);
225 |         } catch (e) {
226 |             alert('Invalid JSON format for headers.');
227 |             console.error("Header JSON parse error:", e);
228 |         }
229 |     });
230 | 
231 |     modalActions.appendChild(closeButton);
232 |     modalActions.appendChild(saveButton);
233 |     modalContent.appendChild(modalActions);
234 |     modalContent.appendChild(authTokenDropdown);
235 |     modal.appendChild(modalContent);
236 | 
237 |     return modal;
238 | }
239 | 
240 | /**
241 |  * Function to open the header popup.
242 |  */
243 | function openHeaderEditor(toolId) {
244 |     const modal = document.getElementById(`header-modal-${toolId}`);
245 |     if (modal) {
246 |         modal.style.display = 'block';
247 |     }
248 | }
249 | 
250 | /**
251 |  * Function to close the header popup.
252 |  */
253 | function closeHeaderEditor(toolId) {
254 |     const modal = document.getElementById(`header-modal-${toolId}`);
255 |     if (modal) {
256 |         modal.style.display = 'none';
257 |     }
258 | }
259 | 
260 | /**
261 |  * Creates a dropdown element showing information on how to extract Google auth tokens.
262 |  * @return {HTMLDetailsElement} The details element representing the dropdown.
263 |  */
264 | function createAuthTokenInfoDropdown() {
265 |     const details = document.createElement('details');
266 |     const summary = document.createElement('summary');
267 |     const content = document.createElement('div');
268 | 
269 |     details.className = 'auth-token-details';
270 |     details.appendChild(summary);
271 |     summary.textContent = 'How to extract Google OAuth ID Token manually';
272 |     content.className = 'auth-token-content';
273 | 
274 |     // auth instruction dropdown
275 |     const tabButtons = document.createElement('div');
276 |     const leftTab = document.createElement('button');
277 |     const rightTab = document.createElement('button');
278 |     
279 |     tabButtons.className = 'auth-tab-group';
280 |     leftTab.className = 'auth-tab-picker active';
281 |     leftTab.textContent = 'With Standard Account';
282 |     leftTab.setAttribute('data-tab', 'standard');
283 |     rightTab.className = 'auth-tab-picker';
284 |     rightTab.textContent = 'With Service Account';
285 |     rightTab.setAttribute('data-tab', 'service');
286 | 
287 |     tabButtons.appendChild(leftTab);
288 |     tabButtons.appendChild(rightTab);
289 |     content.appendChild(tabButtons);
290 | 
291 |     const tabContentContainer = document.createElement('div');
292 |     const standardAccInstructions = document.createElement('div');
293 |     const serviceAccInstructions = document.createElement('div');
294 | 
295 |     standardAccInstructions.id = 'auth-tab-standard';
296 |     standardAccInstructions.className = 'auth-tab-content active'; 
297 |     standardAccInstructions.innerHTML = AUTH_TOKEN_INSTRUCTIONS_STANDARD;
298 |     serviceAccInstructions.id = 'auth-tab-service';
299 |     serviceAccInstructions.className = 'auth-tab-content';
300 |     serviceAccInstructions.innerHTML = AUTH_TOKEN_INSTRUCTIONS_SERVICE_ACCOUNT;
301 | 
302 |     tabContentContainer.appendChild(standardAccInstructions);
303 |     tabContentContainer.appendChild(serviceAccInstructions);
304 |     content.appendChild(tabContentContainer);
305 | 
306 |     // switching tabs logic
307 |     const tabBtns = [leftTab, rightTab];
308 |     const tabContents = [standardAccInstructions, serviceAccInstructions];
309 | 
310 |     tabBtns.forEach(btn => {
311 |         btn.addEventListener('click', () => {
312 |             // deactivate all buttons and contents
313 |             tabBtns.forEach(b => b.classList.remove('active'));
314 |             tabContents.forEach(c => c.classList.remove('active'));
315 | 
316 |             btn.classList.add('active');
317 | 
318 |             const tabId = btn.getAttribute('data-tab');
319 |             const activeContent = content.querySelector(`#auth-tab-${tabId}`);
320 |             if (activeContent) {
321 |                 activeContent.classList.add('active');
322 |             }
323 |         });
324 |     });
325 | 
326 |     details.appendChild(content);
327 |     return details;
328 | }
329 | 
330 | /**
331 |  * Renders the tool display area.
332 |  */
333 | export function renderToolInterface(tool, containerElement) {
334 |     const TOOL_ID = tool.id;
335 |     containerElement.innerHTML = '';
336 | 
337 |     let lastResults = null;
338 |     let currentHeaders = {
339 |         "Content-Type": "application/json"
340 |     };
341 | 
342 |     // function to update lastResults so we can toggle json
343 |     const updateLastResults = (newResults) => {
344 |         lastResults = newResults;
345 |     };
346 |     const updateCurrentHeaders = (newHeaders) => {
347 |         currentHeaders = newHeaders;
348 |         const newModal = createHeaderEditorModal(TOOL_ID, currentHeaders, tool.parameters, tool.authRequired, updateCurrentHeaders);
349 |         containerElement.appendChild(newModal);
350 |     };
351 | 
352 |     const gridContainer = document.createElement('div');
353 |     gridContainer.className = 'tool-details-grid';
354 | 
355 |     const toolInfoContainer = document.createElement('div');
356 |     const nameBox = document.createElement('div');
357 |     const descBox = document.createElement('div');
358 | 
359 |     nameBox.className = 'tool-box tool-name';
360 |     nameBox.innerHTML = `<h5>Name:</h5><p>${tool.name}</p>`;
361 |     descBox.className = 'tool-box tool-description';
362 |     descBox.innerHTML = `<h5>Description:</h5><p>${tool.description}</p>`;
363 | 
364 |     toolInfoContainer.className = 'tool-info';
365 |     toolInfoContainer.appendChild(nameBox);
366 |     toolInfoContainer.appendChild(descBox);
367 |     gridContainer.appendChild(toolInfoContainer);
368 | 
369 |     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."
370 |     const paramsContainer = document.createElement('div');
371 |     const form = document.createElement('form');
372 |     const paramsHeader = document.createElement('div');
373 |     const disclaimerText = document.createElement('div');
374 | 
375 |     paramsContainer.className = 'tool-params tool-box';
376 |     paramsContainer.innerHTML = '<h5>Parameters:</h5>';
377 |     paramsHeader.className = 'params-header';
378 |     paramsContainer.appendChild(paramsHeader);
379 |     disclaimerText.textContent = DISLCAIMER_INFO;
380 |     disclaimerText.className = 'params-disclaimer'; 
381 |     paramsContainer.appendChild(disclaimerText);
382 | 
383 |     form.id = `tool-params-form-${TOOL_ID}`;
384 | 
385 |     tool.parameters.forEach(param => {
386 |         form.appendChild(createParamInput(param, TOOL_ID));
387 |     });
388 |     paramsContainer.appendChild(form);
389 |     gridContainer.appendChild(paramsContainer);
390 | 
391 |     containerElement.appendChild(gridContainer);
392 | 
393 |     const RESPONSE_AREA_ID = `tool-response-area-${TOOL_ID}`;
394 |     const runButtonContainer = document.createElement('div');
395 |     const editHeadersButton = document.createElement('button');
396 |     const runButton = document.createElement('button');
397 | 
398 |     editHeadersButton.className = 'btn btn--editHeaders';
399 |     editHeadersButton.textContent = 'Edit Headers';
400 |     editHeadersButton.addEventListener('click', () => openHeaderEditor(TOOL_ID));
401 |     runButtonContainer.className = 'run-button-container';
402 |     runButtonContainer.appendChild(editHeadersButton);
403 | 
404 |     runButton.className = 'btn btn--run';
405 |     runButton.textContent = 'Run Tool';
406 |     runButtonContainer.appendChild(runButton);
407 |     containerElement.appendChild(runButtonContainer);
408 | 
409 |     // response Area (bottom)
410 |     const responseContainer = document.createElement('div');
411 |     const responseHeaderControls = document.createElement('div');
412 |     const responseHeader = document.createElement('h5');
413 |     const responseArea = document.createElement('textarea');
414 | 
415 |     responseContainer.className = 'tool-response tool-box';
416 |     responseHeaderControls.className = 'response-header-controls';
417 |     responseHeader.textContent = 'Response:';
418 |     responseHeaderControls.appendChild(responseHeader);
419 | 
420 |     // prettify box
421 |     const PRETTIFY_ID = `prettify-${TOOL_ID}`;
422 |     const prettifyDiv = document.createElement('div');
423 |     const prettifyLabel = document.createElement('label');
424 |     const prettifyCheckbox = document.createElement('input');
425 | 
426 |     prettifyDiv.className = 'prettify-container';
427 |     prettifyLabel.setAttribute('for', PRETTIFY_ID);
428 |     prettifyLabel.textContent = 'Prettify JSON';
429 |     prettifyLabel.className = 'prettify-label';
430 | 
431 |     prettifyCheckbox.type = 'checkbox';
432 |     prettifyCheckbox.id = PRETTIFY_ID;
433 |     prettifyCheckbox.checked = true;
434 |     prettifyCheckbox.className = 'prettify-checkbox';
435 | 
436 |     prettifyDiv.appendChild(prettifyLabel);
437 |     prettifyDiv.appendChild(prettifyCheckbox);
438 | 
439 |     responseHeaderControls.appendChild(prettifyDiv);
440 |     responseContainer.appendChild(responseHeaderControls);
441 | 
442 |     responseArea.id = RESPONSE_AREA_ID;
443 |     responseArea.readOnly = true;
444 |     responseArea.placeholder = 'Results will appear here...';
445 |     responseArea.className = 'tool-response-area';
446 |     responseArea.rows = 10;
447 |     responseContainer.appendChild(responseArea);
448 | 
449 |     containerElement.appendChild(responseContainer);
450 | 
451 |     // create and append the header editor modal
452 |     const headerModal = createHeaderEditorModal(TOOL_ID, currentHeaders, tool.parameters, tool.authRequired, updateCurrentHeaders);
453 |     containerElement.appendChild(headerModal);
454 | 
455 |     prettifyCheckbox.addEventListener('change', () => {
456 |         if (lastResults) {
457 |             displayResults(lastResults, responseArea, prettifyCheckbox.checked);
458 |         }
459 |     });
460 | 
461 |     runButton.addEventListener('click', (event) => {
462 |         event.preventDefault();
463 |         handleRunTool(TOOL_ID, form, responseArea, tool.parameters, prettifyCheckbox, updateLastResults, currentHeaders);
464 |     });
465 | }
466 | 
467 | /**
468 |  * Checks if a specific parameter is marked as included for a given tool.
469 |  * @param {string} toolId The ID of the tool.
470 |  * @param {string} paramName The name of the parameter.
471 |  * @return {boolean|null} True if the parameter's include checkbox is checked,
472 |  *                         False if unchecked, Null if the checkbox element is not found.
473 |  */
474 | export function isParamIncluded(toolId, paramName) {
475 |     const inputId = `param-${toolId}-${paramName}`;
476 |     const includeCheckboxId = `include-${inputId}`;
477 |     const includeCheckbox = document.getElementById(includeCheckboxId);
478 | 
479 |     if (includeCheckbox && includeCheckbox.type === 'checkbox') {
480 |         return includeCheckbox.checked;
481 |     }
482 | 
483 |     console.warn(`Include checkbox not found for ID: ${includeCheckboxId}`);
484 |     return null;
485 | }
486 | 
487 | // Templates for inserting token retrieval instructions into edit header modal
488 | const AUTH_TOKEN_INSTRUCTIONS_SERVICE_ACCOUNT = `
489 |         <p>To obtain a Google OAuth ID token using a service account:</p>
490 |         <ol>
491 |             <li>Make sure you are on the intended SERVICE account (typically contain iam.gserviceaccount.com). Verify by running the command below.
492 |                 <pre><code>gcloud auth list</code></pre>
493 |             </li>
494 |             <li>Print an id token with the audience set to your clientID defined in tools file:
495 |                 <pre><code>gcloud auth print-identity-token --audiences=YOUR_CLIENT_ID_HERE</code></pre>
496 |             </li>
497 |             <li>Copy the output token.</li>
498 |             <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>
499 |                 <pre><code>{
500 |   "Content-Type": "application/json",
501 |   "my-google-auth_token": "YOUR_ID_TOKEN_HERE"
502 | }               </code></pre>
503 |             </li>
504 |         </ol>
505 |         <p>This token is typically short-lived.</p>`;
506 | 
507 | const AUTH_TOKEN_INSTRUCTIONS_STANDARD = `
508 |         <p>To obtain a Google OAuth ID token using a standard account:</p>
509 |         <ol>
510 |             <li>Make sure you are on your intended standard account. Verify by running the command below.
511 |                 <pre><code>gcloud auth list</code></pre>
512 |             </li>
513 |             <li>Within your Cloud Console, add the following link to the "Authorized Redirect URIs".</li>
514 |             <pre><code>https://developers.google.com/oauthplayground</code></pre>
515 |             <li>Go to the Google OAuth Playground site: <a href="https://developers.google.com/oauthplayground/" target="_blank">https://developers.google.com/oauthplayground/</a></li>
516 |             <li>In the top right settings menu, select "Use your own OAuth Credentials".</li>
517 |             <li>Input your clientID (from tools file), along with the client secret from Cloud Console.</li>
518 |             <li>Inside the Google OAuth Playground, select "Google OAuth2 API v2.</li>
519 |             <ul>
520 |                 <li>Select "Authorize APIs".</li>
521 |                 <li>Select "Exchange Authorization codes for tokens"</li>
522 |                 <li>Copy the id_token field provided in the response.</li>
523 |             </ul>
524 |             <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>
525 |                 <pre><code>{
526 |   "Content-Type": "application/json",
527 |   "my-google-auth_token": "YOUR_ID_TOKEN_HERE"
528 | }               </code></pre>
529 |             </li>
530 |         </ol>
531 |         <p>This token is typically short-lived.</p>`;
```

--------------------------------------------------------------------------------
/tests/mindsdb/mindsdb_integration_test.go:
--------------------------------------------------------------------------------

```go
  1 | // Copyright 2025 Google LLC
  2 | //
  3 | // Licensed under the Apache License, Version 2.0 (the "License");
  4 | // you may not use this file except in compliance with the License.
  5 | // You may obtain a copy of the License at
  6 | //
  7 | //     http://www.apache.org/licenses/LICENSE-2.0
  8 | //
  9 | // Unless required by applicable law or agreed to in writing, software
 10 | // distributed under the License is distributed on an "AS IS" BASIS,
 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12 | // See the License for the specific language governing permissions and
 13 | // limitations under the License.
 14 | 
 15 | package mindsdb
 16 | 
 17 | import (
 18 | 	"bytes"
 19 | 	"context"
 20 | 	"database/sql"
 21 | 	"fmt"
 22 | 	"net/http"
 23 | 	"os"
 24 | 	"regexp"
 25 | 	"strings"
 26 | 	"testing"
 27 | 	"time"
 28 | 
 29 | 	_ "github.com/go-sql-driver/mysql"
 30 | 	"github.com/google/uuid"
 31 | 	"github.com/googleapis/genai-toolbox/internal/testutils"
 32 | 	"github.com/googleapis/genai-toolbox/tests"
 33 | )
 34 | 
 35 | var (
 36 | 	MindsDBSourceKind = "mindsdb"
 37 | 	MindsDBToolKind   = "mindsdb-sql"
 38 | 	MindsDBDatabase   = os.Getenv("MINDSDB_DATABASE")
 39 | 	MindsDBHost       = os.Getenv("MINDSDB_HOST")
 40 | 	MindsDBPort       = os.Getenv("MINDSDB_PORT")
 41 | 	MindsDBUser       = os.Getenv("MINDSDB_USER")
 42 | 	MindsDBPass       = os.Getenv("MINDSDB_PASS")
 43 | )
 44 | 
 45 | func getMindsDBVars(t *testing.T) map[string]any {
 46 | 	switch "" {
 47 | 	case MindsDBDatabase:
 48 | 		t.Fatal("'MINDSDB_DATABASE' not set")
 49 | 	case MindsDBHost:
 50 | 		t.Fatal("'MINDSDB_HOST' not set")
 51 | 	case MindsDBPort:
 52 | 		t.Fatal("'MINDSDB_PORT' not set")
 53 | 	case MindsDBUser:
 54 | 		t.Fatal("'MINDSDB_USER' not set")
 55 | 	}
 56 | 
 57 | 	// MindsDBPass can be empty, but the env var must exist
 58 | 	if _, exists := os.LookupEnv("MINDSDB_PASS"); !exists {
 59 | 		t.Fatal("'MINDSDB_PASS' not set (can be empty)")
 60 | 	}
 61 | 
 62 | 	// Handle no-password authentication
 63 | 	mindsdbPassword := MindsDBPass
 64 | 	if mindsdbPassword == "none" {
 65 | 		mindsdbPassword = ""
 66 | 	}
 67 | 
 68 | 	return map[string]any{
 69 | 		"kind":     MindsDBSourceKind,
 70 | 		"host":     MindsDBHost,
 71 | 		"port":     MindsDBPort,
 72 | 		"database": MindsDBDatabase,
 73 | 		"user":     MindsDBUser,
 74 | 		"password": mindsdbPassword,
 75 | 	}
 76 | }
 77 | 
 78 | // initMindsDBConnectionPool creates a connection pool using MySQL protocol
 79 | func initMindsDBConnectionPool(host, port, user, pass, dbname string) (*sql.DB, error) {
 80 | 	dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?parseTime=true", user, pass, host, port, dbname)
 81 | 	pool, err := sql.Open("mysql", dsn)
 82 | 	if err != nil {
 83 | 		return nil, fmt.Errorf("sql.Open: %w", err)
 84 | 	}
 85 | 	return pool, nil
 86 | }
 87 | 
 88 | func TestMindsDBToolEndpoints(t *testing.T) {
 89 | 	sourceConfig := getMindsDBVars(t)
 90 | 	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
 91 | 	defer cancel()
 92 | 
 93 | 	var args []string
 94 | 
 95 | 	// Create unique table names with UUID
 96 | 	tableNameParam := "param_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
 97 | 	tableNameAuth := "auth_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
 98 | 	// Tool statements with ORDER BY for consistent results
 99 | 	paramToolStmt := fmt.Sprintf("SELECT * FROM files.%s WHERE id = ? OR name = ? ORDER BY id", tableNameParam)
100 | 	idParamToolStmt := fmt.Sprintf("SELECT * FROM files.%s WHERE id = ? ORDER BY id", tableNameParam)
101 | 	nameParamToolStmt := fmt.Sprintf("SELECT * FROM files.%s WHERE name = ? ORDER BY id", tableNameParam)
102 | 	authToolStmt := fmt.Sprintf("SELECT name FROM files.%s WHERE email = ? ORDER BY name", tableNameAuth)
103 | 
104 | 	toolsFile := map[string]any{
105 | 		"sources": map[string]any{
106 | 			"my-instance": sourceConfig,
107 | 		},
108 | 		"authServices": map[string]any{
109 | 			"my-google-auth": map[string]any{
110 | 				"kind":     "google",
111 | 				"clientId": tests.ClientId,
112 | 			},
113 | 		},
114 | 		"tools": map[string]any{
115 | 			"my-simple-tool": map[string]any{
116 | 				"kind":        MindsDBToolKind,
117 | 				"source":      "my-instance",
118 | 				"description": "Simple tool to test end to end functionality.",
119 | 				"statement":   "SELECT 1",
120 | 			},
121 | 			"my-tool": map[string]any{
122 | 				"kind":        MindsDBToolKind,
123 | 				"source":      "my-instance",
124 | 				"description": "Tool to test invocation with params.",
125 | 				"statement":   paramToolStmt,
126 | 				"parameters": []map[string]any{
127 | 					{
128 | 						"name":        "id",
129 | 						"type":        "integer",
130 | 						"description": "user ID",
131 | 					},
132 | 					{
133 | 						"name":        "name",
134 | 						"type":        "string",
135 | 						"description": "user name",
136 | 					},
137 | 				},
138 | 			},
139 | 			"my-tool-by-id": map[string]any{
140 | 				"kind":        MindsDBToolKind,
141 | 				"source":      "my-instance",
142 | 				"description": "Tool to test invocation with params.",
143 | 				"statement":   idParamToolStmt,
144 | 				"parameters": []map[string]any{
145 | 					{
146 | 						"name":        "id",
147 | 						"type":        "integer",
148 | 						"description": "user ID",
149 | 					},
150 | 				},
151 | 			},
152 | 			"my-tool-by-name": map[string]any{
153 | 				"kind":        MindsDBToolKind,
154 | 				"source":      "my-instance",
155 | 				"description": "Tool to test invocation with params.",
156 | 				"statement":   nameParamToolStmt,
157 | 				"parameters": []map[string]any{
158 | 					{
159 | 						"name":        "name",
160 | 						"type":        "string",
161 | 						"description": "user name",
162 | 						"required":    false,
163 | 					},
164 | 				},
165 | 			},
166 | 			"my-array-tool": map[string]any{
167 | 				"kind":        MindsDBToolKind,
168 | 				"source":      "my-instance",
169 | 				"description": "Tool to test invocation with array params.",
170 | 				"statement":   "SELECT 1 as id, 'Alice' as name UNION SELECT 3 as id, 'Sid' as name",
171 | 			},
172 | 			"my-auth-tool": map[string]any{
173 | 				"kind":        MindsDBToolKind,
174 | 				"source":      "my-instance",
175 | 				"description": "Tool to test authenticated parameters.",
176 | 				"statement":   authToolStmt,
177 | 				"parameters": []map[string]any{
178 | 					{
179 | 						"name":        "email",
180 | 						"type":        "string",
181 | 						"description": "user email",
182 | 						"authServices": []map[string]string{
183 | 							{
184 | 								"name":  "my-google-auth",
185 | 								"field": "email",
186 | 							},
187 | 						},
188 | 					},
189 | 				},
190 | 			},
191 | 			"my-auth-required-tool": map[string]any{
192 | 				"kind":        MindsDBToolKind,
193 | 				"source":      "my-instance",
194 | 				"description": "Tool to test auth required invocation.",
195 | 				"statement":   "SELECT 1",
196 | 				"authRequired": []string{
197 | 					"my-google-auth",
198 | 				},
199 | 			},
200 | 			"my-fail-tool": map[string]any{
201 | 				"kind":        MindsDBToolKind,
202 | 				"source":      "my-instance",
203 | 				"description": "Tool to test statement with incorrect syntax.",
204 | 				"statement":   "INVALID SQL STATEMENT",
205 | 			},
206 | 			"my-exec-sql-tool": map[string]any{
207 | 				"kind":        "mindsdb-execute-sql",
208 | 				"source":      "my-instance",
209 | 				"description": "Tool to execute sql",
210 | 			},
211 | 			"my-auth-exec-sql-tool": map[string]any{
212 | 				"kind":        "mindsdb-execute-sql",
213 | 				"source":      "my-instance",
214 | 				"description": "Tool to execute sql with auth",
215 | 				"authRequired": []string{
216 | 					"my-google-auth",
217 | 				},
218 | 			},
219 | 		},
220 | 	}
221 | 
222 | 	cmd, cleanup, err := tests.StartCmd(ctx, toolsFile, args...)
223 | 	if err != nil {
224 | 		t.Fatalf("command initialization returned an error: %s", err)
225 | 	}
226 | 	defer cleanup()
227 | 
228 | 	waitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
229 | 	defer cancel()
230 | 	out, err := testutils.WaitForString(waitCtx, regexp.MustCompile(`Server ready to serve`), cmd.Out)
231 | 	if err != nil {
232 | 		t.Logf("toolbox command logs: \n%s", out)
233 | 		t.Fatalf("toolbox didn't start successfully: %s", err)
234 | 	}
235 | 
236 | 	// Create connection pool and test tables with sample data
237 | 	pool, err := initMindsDBConnectionPool(MindsDBHost, MindsDBPort, MindsDBUser, MindsDBPass, MindsDBDatabase)
238 | 	if err != nil {
239 | 		t.Fatalf("unable to create MindsDB connection pool: %s", err)
240 | 	}
241 | 	defer pool.Close()
242 | 
243 | 	// Create param table: id=1:Alice, id=2:Jane, id=3:Sid, id=4:null
244 | 	createParamSQL := fmt.Sprintf("CREATE TABLE files.%s (SELECT 1 as id, 'Alice' as name UNION ALL SELECT 2, 'Jane' UNION ALL SELECT 3, 'Sid' UNION ALL SELECT 4, NULL)", tableNameParam)
245 | 	_, err = pool.ExecContext(ctx, createParamSQL)
246 | 	if err != nil {
247 | 		t.Fatalf("unable to create param table: %s", err)
248 | 	}
249 | 
250 | 	// Create auth table: id=1:Alice:test@..., id=2:Jane:jane@...
251 | 	createAuthSQL := fmt.Sprintf("CREATE TABLE files.%s (SELECT 1 as id, 'Alice' as name, '%s' as email UNION ALL SELECT 2, 'Jane', '[email protected]')", tableNameAuth, tests.ServiceAccountEmail)
252 | 	_, err = pool.ExecContext(ctx, createAuthSQL)
253 | 	if err != nil {
254 | 		t.Fatalf("unable to create auth table: %s", err)
255 | 	}
256 | 
257 | 	defer func() {
258 | 		_, _ = pool.ExecContext(ctx, fmt.Sprintf("DROP TABLE IF EXISTS files.%s", tableNameParam))
259 | 		_, _ = pool.ExecContext(ctx, fmt.Sprintf("DROP TABLE IF EXISTS files.%s", tableNameAuth))
260 | 	}()
261 | 
262 | 	select1Want := "[{\"1\":1}]"
263 | 
264 | 	// Run standard tool tests with MindsDB-specific expectations
265 | 	tests.RunToolGetTest(t)
266 | 	tests.RunToolInvokeTest(t, select1Want,
267 | 		tests.DisableArrayTest(), // MindsDB doesn't support array parameters
268 | 	)
269 | 
270 | 	t.Run("mindsdb_core_functionality", func(t *testing.T) {
271 | 		tests.RunToolInvokeSimpleTest(t, "my-simple-tool", select1Want)
272 | 		tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool", []byte(`{"sql": "SELECT 1"}`), select1Want)
273 | 		tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool", []byte(`{"sql": "SELECT 1+1 as result"}`), "[{\"result\":2}]")
274 | 		tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool", []byte(`{"sql": "SELECT 'hello' as greeting"}`), "[{\"greeting\":\"hello\"}]")
275 | 	})
276 | 
277 | 	t.Run("mindsdb_sql_tests", func(t *testing.T) {
278 | 		tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool", []byte(`{"sql": "SELECT 1"}`), select1Want)
279 | 		tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool", []byte(`{"sql": "SHOW DATABASES"}`), "")
280 | 		tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool", []byte(`{"sql": "SHOW TABLES"}`), "")
281 | 		tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool", []byte(`{"sql": "SELECT TABLE_NAME FROM information_schema.TABLES LIMIT 1"}`), "")
282 | 		tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool", []byte(`{"sql": "SELECT 1+1 as result"}`), "")
283 | 		tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool", []byte(`{"sql": "SELECT UPPER('hello') as result"}`), "")
284 | 		tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool", []byte(`{"sql": "SELECT NOW() as current_time"}`), "")
285 | 	})
286 | 
287 | 	// Test CREATE DATABASE (MindsDB's federated database capability)
288 | 	t.Run("mindsdb_create_database", func(t *testing.T) {
289 | 		tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool",
290 | 			[]byte(`{"sql": "DROP DATABASE IF EXISTS test_postgres_db"}`), "")
291 | 
292 | 		// Create external database integration using MindsDB's demo database
293 | 		createDBSQL := `CREATE DATABASE test_postgres_db WITH ENGINE = 'postgres', PARAMETERS = {'user': 'demo_user', 'password': 'demo_password', 'host': 'samples.mindsdb.com', 'port': '5432', 'database': 'demo', 'schema': 'demo_data'}`
294 | 		tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool",
295 | 			[]byte(`{"sql": "`+createDBSQL+`"}`), "")
296 | 
297 | 		tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool",
298 | 			[]byte(`{"sql": "SHOW DATABASES"}`), "")
299 | 
300 | 		tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool",
301 | 			[]byte(`{"sql": "SHOW TABLES FROM test_postgres_db"}`), "")
302 | 
303 | 		tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool",
304 | 			[]byte(`{"sql": "DROP DATABASE IF EXISTS test_postgres_db"}`), "")
305 | 	})
306 | 
307 | 	// Test MindsDB integration capabilities with product/review data
308 | 	t.Run("mindsdb_integration_demo", func(t *testing.T) {
309 | 		tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool",
310 | 			[]byte(`{"sql": "DROP TABLE IF EXISTS files.test_products"}`), "")
311 | 		tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool",
312 | 			[]byte(`{"sql": "DROP TABLE IF EXISTS files.test_reviews"}`), "")
313 | 
314 | 		// Create test tables with sample data
315 | 		createProductsSQL := `CREATE TABLE files.test_products (SELECT 'PROD001' as product_id, 'Laptop Computer' as product_name, 'Electronics' as category UNION ALL SELECT 'PROD002', 'Office Chair', 'Furniture' UNION ALL SELECT 'PROD003', 'Coffee Maker', 'Appliances' UNION ALL SELECT 'PROD004', 'Desk Lamp', 'Furniture')`
316 | 		tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool",
317 | 			[]byte(`{"sql": "`+createProductsSQL+`"}`), "")
318 | 
319 | 		createReviewsSQL := `CREATE TABLE files.test_reviews (SELECT 'PROD001' as product_id, 'Great laptop, very fast!' as review, 5 as rating UNION ALL SELECT 'PROD001', 'Good value for money', 4 UNION ALL SELECT 'PROD002', 'Very comfortable chair', 5 UNION ALL SELECT 'PROD002', 'Nice design but expensive', 3 UNION ALL SELECT 'PROD003', 'Makes excellent coffee', 5 UNION ALL SELECT 'PROD004', 'Bright light, perfect for reading', 4)`
320 | 		tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool",
321 | 			[]byte(`{"sql": "`+createReviewsSQL+`"}`), "")
322 | 
323 | 		t.Run("query_created_tables", func(t *testing.T) {
324 | 			tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool",
325 | 				[]byte(`{"sql": "SELECT * FROM files.test_products ORDER BY product_id"}`), "")
326 | 			tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool",
327 | 				[]byte(`{"sql": "SELECT * FROM files.test_reviews ORDER BY product_id, rating DESC"}`), "")
328 | 			tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool",
329 | 				[]byte(`{"sql": "SELECT category, COUNT(*) as product_count FROM files.test_products GROUP BY category ORDER BY category"}`), "")
330 | 			tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool",
331 | 				[]byte(`{"sql": "SELECT product_id, AVG(rating) as avg_rating FROM files.test_reviews GROUP BY product_id ORDER BY avg_rating DESC"}`), "")
332 | 		})
333 | 
334 | 		t.Run("cross_database_join", func(t *testing.T) {
335 | 			joinSQL := `SELECT p.product_name, p.category, r.review, r.rating FROM files.test_products p JOIN files.test_reviews r ON p.product_id = r.product_id WHERE r.rating >= 4 ORDER BY p.product_name, r.rating DESC`
336 | 			tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool",
337 | 				[]byte(`{"sql": "`+joinSQL+`"}`), "")
338 | 
339 | 			aggSQL := `SELECT p.category, COUNT(DISTINCT p.product_id) as product_count, COUNT(r.review) as review_count, AVG(r.rating) as avg_rating FROM files.test_products p LEFT JOIN files.test_reviews r ON p.product_id = r.product_id GROUP BY p.category ORDER BY avg_rating DESC`
340 | 			tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool",
341 | 				[]byte(`{"sql": "`+aggSQL+`"}`), "")
342 | 		})
343 | 
344 | 		t.Run("advanced_sql_features", func(t *testing.T) {
345 | 			subquerySQL := `SELECT p.product_name, p.category, AVG(r.rating) as avg_rating FROM files.test_products p JOIN files.test_reviews r ON p.product_id = r.product_id GROUP BY p.product_id, p.product_name, p.category HAVING AVG(r.rating) >= 4 ORDER BY avg_rating DESC`
346 | 			tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool",
347 | 				[]byte(`{"sql": "`+subquerySQL+`"}`), "")
348 | 
349 | 			caseSQL := `SELECT product_id, review, rating, CASE WHEN rating >= 5 THEN 'Excellent' WHEN rating >= 4 THEN 'Good' WHEN rating >= 3 THEN 'Average' ELSE 'Poor' END as rating_category FROM files.test_reviews ORDER BY rating DESC, product_id`
350 | 			tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool",
351 | 				[]byte(`{"sql": "`+caseSQL+`"}`), "")
352 | 		})
353 | 
354 | 		tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool",
355 | 			[]byte(`{"sql": "DROP TABLE IF EXISTS files.test_product_summary"}`), "")
356 | 
357 | 		t.Run("data_manipulation", func(t *testing.T) {
358 | 			summarySQL := `CREATE TABLE files.test_product_summary (SELECT p.product_id, p.product_name, p.category, COUNT(r.review) as total_reviews, AVG(r.rating) as avg_rating, MAX(r.rating) as max_rating, MIN(r.rating) as min_rating FROM files.test_products p LEFT JOIN files.test_reviews r ON p.product_id = r.product_id GROUP BY p.product_id, p.product_name, p.category)`
359 | 			tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool",
360 | 				[]byte(`{"sql": "`+summarySQL+`"}`), "")
361 | 
362 | 			tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool",
363 | 				[]byte(`{"sql": "SELECT * FROM files.test_product_summary ORDER BY avg_rating DESC"}`), "")
364 | 		})
365 | 
366 | 		tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool",
367 | 			[]byte(`{"sql": "DROP TABLE IF EXISTS files.test_products"}`), "")
368 | 		tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool",
369 | 			[]byte(`{"sql": "DROP TABLE IF EXISTS files.test_reviews"}`), "")
370 | 		tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool",
371 | 			[]byte(`{"sql": "DROP TABLE IF EXISTS files.test_product_summary"}`), "")
372 | 	})
373 | 
374 | 	// Test database integration and cross-database joins
375 | 	t.Run("mindsdb_create_database_integration", func(t *testing.T) {
376 | 		showDBSQL := `SHOW DATABASES`
377 | 		tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool",
378 | 			[]byte(`{"sql": "`+showDBSQL+`"}`), "")
379 | 
380 | 		tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool",
381 | 			[]byte(`{"sql": "SHOW TABLES FROM files"}`), "")
382 | 
383 | 		tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool",
384 | 			[]byte(`{"sql": "DROP TABLE IF EXISTS files.test_integration_data"}`), "")
385 | 		tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool",
386 | 			[]byte(`{"sql": "DROP TABLE IF EXISTS files.test_local_data"}`), "")
387 | 
388 | 		createIntegrationTableSQL := `CREATE TABLE files.test_integration_data (SELECT 1 as id, 'Data from integration' as description, CURDATE() as created_at UNION ALL SELECT 2, 'Another record', CURDATE() UNION ALL SELECT 3, 'Third record', CURDATE())`
389 | 		tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool",
390 | 			[]byte(`{"sql": "`+createIntegrationTableSQL+`"}`), "")
391 | 
392 | 		tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool",
393 | 			[]byte(`{"sql": "SELECT * FROM files.test_integration_data ORDER BY id"}`), "")
394 | 
395 | 		createLocalTableSQL := `CREATE TABLE files.test_local_data (SELECT 1 as id, 'Local metadata' as metadata UNION ALL SELECT 2, 'More metadata')`
396 | 		tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool",
397 | 			[]byte(`{"sql": "`+createLocalTableSQL+`"}`), "")
398 | 
399 | 		crossJoinSQL := `SELECT i.id, i.description, l.metadata FROM files.test_integration_data i LEFT JOIN files.test_local_data l ON i.id = l.id ORDER BY i.id`
400 | 		tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool",
401 | 			[]byte(`{"sql": "`+crossJoinSQL+`"}`), "")
402 | 
403 | 		tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool",
404 | 			[]byte(`{"sql": "DROP TABLE IF EXISTS files.test_integration_data"}`), "")
405 | 		tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool",
406 | 			[]byte(`{"sql": "DROP TABLE IF EXISTS files.test_local_data"}`), "")
407 | 	})
408 | 
409 | 	// Test data transformation with customer order data
410 | 	t.Run("mindsdb_data_transformation", func(t *testing.T) {
411 | 		tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool",
412 | 			[]byte(`{"sql": "DROP TABLE IF EXISTS files.test_orders"}`), "")
413 | 		tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool",
414 | 			[]byte(`{"sql": "DROP TABLE IF EXISTS files.test_customer_summary"}`), "")
415 | 
416 | 		createOrdersSQL := `CREATE TABLE files.test_orders (SELECT 1 as order_id, 'CUST001' as customer_id, 100.50 as amount, '2024-01-15' as order_date UNION ALL SELECT 2, 'CUST001', 250.00, '2024-02-20' UNION ALL SELECT 3, 'CUST002', 75.25, '2024-01-18' UNION ALL SELECT 4, 'CUST003', 500.00, '2024-03-10' UNION ALL SELECT 5, 'CUST002', 150.00, '2024-02-25')`
417 | 		tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool",
418 | 			[]byte(`{"sql": "`+createOrdersSQL+`"}`), "")
419 | 
420 | 		customerSummarySQL := `CREATE TABLE files.test_customer_summary (SELECT customer_id, COUNT(*) as total_orders, SUM(amount) as total_spent, AVG(amount) as avg_order_value, MIN(order_date) as first_order_date, MAX(order_date) as last_order_date FROM files.test_orders GROUP BY customer_id)`
421 | 		tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool",
422 | 			[]byte(`{"sql": "`+customerSummarySQL+`"}`), "")
423 | 
424 | 		tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool",
425 | 			[]byte(`{"sql": "SELECT * FROM files.test_customer_summary ORDER BY total_spent DESC"}`), "")
426 | 
427 | 		segmentSQL := `SELECT customer_id, total_spent, CASE WHEN total_spent >= 300 THEN 'High Value' WHEN total_spent >= 150 THEN 'Medium Value' ELSE 'Low Value' END as customer_segment FROM files.test_customer_summary ORDER BY total_spent DESC`
428 | 		tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool",
429 | 			[]byte(`{"sql": "`+segmentSQL+`"}`), "")
430 | 
431 | 		tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool",
432 | 			[]byte(`{"sql": "DROP TABLE IF EXISTS files.test_orders"}`), "")
433 | 		tests.RunToolInvokeParametersTest(t, "my-exec-sql-tool",
434 | 			[]byte(`{"sql": "DROP TABLE IF EXISTS files.test_customer_summary"}`), "")
435 | 	})
436 | 
437 | 	// Test error handling - these are expected to fail but exercise error paths
438 | 	t.Run("mindsdb_error_handling", func(t *testing.T) {
439 | 		// Test invalid SQL - expect this to fail with 400
440 | 		resp, err := http.Post("http://127.0.0.1:5000/api/tool/my-exec-sql-tool/invoke", "application/json", bytes.NewBuffer([]byte(`{"sql": "INVALID SQL QUERY"}`)))
441 | 		if err != nil {
442 | 			t.Fatalf("error when sending request: %s", err)
443 | 		}
444 | 		defer resp.Body.Close()
445 | 		if resp.StatusCode != http.StatusBadRequest {
446 | 			t.Logf("Expected 400 for invalid SQL, got %d (this exercises error handling)", resp.StatusCode)
447 | 		}
448 | 
449 | 		// Test empty SQL - expect this to fail with 400
450 | 		resp2, err := http.Post("http://127.0.0.1:5000/api/tool/my-exec-sql-tool/invoke", "application/json", bytes.NewBuffer([]byte(`{"sql": ""}`)))
451 | 		if err != nil {
452 | 			t.Fatalf("error when sending request: %s", err)
453 | 		}
454 | 		defer resp2.Body.Close()
455 | 		if resp2.StatusCode != http.StatusBadRequest {
456 | 			t.Logf("Expected 400 for empty SQL, got %d (this exercises error handling)", resp2.StatusCode)
457 | 		}
458 | 	})
459 | 
460 | 	// Test authentication - these are expected to fail but exercise auth code paths
461 | 	t.Run("mindsdb_auth_tests", func(t *testing.T) {
462 | 		// Test auth-required tool without auth - expect this to fail with 401
463 | 		resp, err := http.Post("http://127.0.0.1:5000/api/tool/my-auth-exec-sql-tool/invoke", "application/json", bytes.NewBuffer([]byte(`{"sql": "SELECT 1"}`)))
464 | 		if err != nil {
465 | 			t.Fatalf("error when sending request: %s", err)
466 | 		}
467 | 		defer resp.Body.Close()
468 | 		if resp.StatusCode != http.StatusUnauthorized {
469 | 			t.Logf("Expected 401 for missing auth, got %d (this exercises auth handling)", resp.StatusCode)
470 | 		}
471 | 	})
472 | }
473 | 
```

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

```go
  1 | // Copyright 2025 Google LLC
  2 | //
  3 | // Licensed under the Apache License, Version 2.0 (the "License");
  4 | // you may not use this file except in compliance with the License.
  5 | // You may obtain a copy of the License at
  6 | //
  7 | //     http://www.apache.org/licenses/LICENSE-2.0
  8 | //
  9 | // Unless required by applicable law or agreed to in writing, software
 10 | // distributed under the License is distributed on an "AS IS" BASIS,
 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12 | // See the License for the specific language governing permissions and
 13 | // limitations under the License.
 14 | 
 15 | package neo4jschema
 16 | 
 17 | import (
 18 | 	"context"
 19 | 	"fmt"
 20 | 	"sync"
 21 | 	"time"
 22 | 
 23 | 	"github.com/goccy/go-yaml"
 24 | 	"github.com/googleapis/genai-toolbox/internal/embeddingmodels"
 25 | 	"github.com/googleapis/genai-toolbox/internal/sources"
 26 | 	"github.com/googleapis/genai-toolbox/internal/tools"
 27 | 	"github.com/googleapis/genai-toolbox/internal/tools/neo4j/neo4jschema/cache"
 28 | 	"github.com/googleapis/genai-toolbox/internal/tools/neo4j/neo4jschema/helpers"
 29 | 	"github.com/googleapis/genai-toolbox/internal/tools/neo4j/neo4jschema/types"
 30 | 	"github.com/googleapis/genai-toolbox/internal/util/parameters"
 31 | 	"github.com/neo4j/neo4j-go-driver/v5/neo4j"
 32 | )
 33 | 
 34 | // kind defines the unique identifier for this tool.
 35 | const kind string = "neo4j-schema"
 36 | 
 37 | // init registers the tool with the application's tool registry when the package is initialized.
 38 | func init() {
 39 | 	if !tools.Register(kind, newConfig) {
 40 | 		panic(fmt.Sprintf("tool kind %q already registered", kind))
 41 | 	}
 42 | }
 43 | 
 44 | // newConfig decodes a YAML configuration into a Config struct.
 45 | // This function is called by the tool registry to create a new configuration object.
 46 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
 47 | 	actual := Config{Name: name}
 48 | 	if err := decoder.DecodeContext(ctx, &actual); err != nil {
 49 | 		return nil, err
 50 | 	}
 51 | 	return actual, nil
 52 | }
 53 | 
 54 | // compatibleSource defines the interface a data source must implement to be used by this tool.
 55 | // It ensures that the source can provide a Neo4j driver and database name.
 56 | type compatibleSource interface {
 57 | 	Neo4jDriver() neo4j.DriverWithContext
 58 | 	Neo4jDatabase() string
 59 | }
 60 | 
 61 | // Config holds the configuration settings for the Neo4j schema tool.
 62 | // These settings are typically read from a YAML file.
 63 | type Config struct {
 64 | 	Name               string   `yaml:"name" validate:"required"`
 65 | 	Kind               string   `yaml:"kind" validate:"required"`
 66 | 	Source             string   `yaml:"source" validate:"required"`
 67 | 	Description        string   `yaml:"description" validate:"required"`
 68 | 	AuthRequired       []string `yaml:"authRequired"`
 69 | 	CacheExpireMinutes *int     `yaml:"cacheExpireMinutes,omitempty"` // Cache expiration time in minutes.
 70 | }
 71 | 
 72 | // Statically verify that Config implements the tools.ToolConfig interface.
 73 | var _ tools.ToolConfig = Config{}
 74 | 
 75 | // ToolConfigKind returns the kind of this tool configuration.
 76 | func (cfg Config) ToolConfigKind() string {
 77 | 	return kind
 78 | }
 79 | 
 80 | // Initialize sets up the tool with its dependencies and returns a ready-to-use Tool instance.
 81 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
 82 | 
 83 | 	params := parameters.Parameters{}
 84 | 	mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, params, nil)
 85 | 
 86 | 	// Set a default cache expiration if not provided in the configuration.
 87 | 	if cfg.CacheExpireMinutes == nil {
 88 | 		defaultExpiration := cache.DefaultExpiration // Default to 60 minutes
 89 | 		cfg.CacheExpireMinutes = &defaultExpiration
 90 | 	}
 91 | 
 92 | 	// Finish tool setup by creating the Tool instance.
 93 | 	t := Tool{
 94 | 		Config:      cfg,
 95 | 		cache:       cache.NewCache(),
 96 | 		manifest:    tools.Manifest{Description: cfg.Description, Parameters: params.Manifest(), AuthRequired: cfg.AuthRequired},
 97 | 		mcpManifest: mcpManifest,
 98 | 	}
 99 | 	return t, nil
100 | }
101 | 
102 | // Statically verify that Tool implements the tools.Tool interface.
103 | var _ tools.Tool = Tool{}
104 | 
105 | // Tool represents the Neo4j schema extraction tool.
106 | // It holds the Neo4j driver, database information, and a cache for the schema.
107 | type Tool struct {
108 | 	Config
109 | 	cache       *cache.Cache
110 | 	manifest    tools.Manifest
111 | 	mcpManifest tools.McpManifest
112 | }
113 | 
114 | // Invoke executes the tool's main logic: fetching the Neo4j schema.
115 | // It first checks the cache for a valid schema before extracting it from the database.
116 | func (t Tool) Invoke(ctx context.Context, resourceMgr tools.SourceProvider, params parameters.ParamValues, accessToken tools.AccessToken) (any, error) {
117 | 	source, err := tools.GetCompatibleSource[compatibleSource](resourceMgr, t.Source, t.Name, t.Kind)
118 | 	if err != nil {
119 | 		return nil, err
120 | 	}
121 | 
122 | 	// Check if a valid schema is already in the cache.
123 | 	if cachedSchema, ok := t.cache.Get("schema"); ok {
124 | 		if schema, ok := cachedSchema.(*types.SchemaInfo); ok {
125 | 			return schema, nil
126 | 		}
127 | 	}
128 | 
129 | 	// If not cached, extract the schema from the database.
130 | 	schema, err := t.extractSchema(ctx, source)
131 | 	if err != nil {
132 | 		return nil, fmt.Errorf("failed to extract database schema: %w", err)
133 | 	}
134 | 
135 | 	// Cache the newly extracted schema for future use.
136 | 	expiration := time.Duration(*t.CacheExpireMinutes) * time.Minute
137 | 	t.cache.Set("schema", schema, expiration)
138 | 
139 | 	return schema, nil
140 | }
141 | 
142 | // ParseParams is a placeholder as this tool does not require input parameters.
143 | func (t Tool) ParseParams(data map[string]any, claimsMap map[string]map[string]any) (parameters.ParamValues, error) {
144 | 	return parameters.ParamValues{}, nil
145 | }
146 | 
147 | func (t Tool) EmbedParams(ctx context.Context, paramValues parameters.ParamValues, embeddingModelsMap map[string]embeddingmodels.EmbeddingModel) (parameters.ParamValues, error) {
148 | 	return parameters.ParamValues{}, nil
149 | }
150 | 
151 | // Manifest returns the tool's manifest, which describes its purpose and parameters.
152 | func (t Tool) Manifest() tools.Manifest {
153 | 	return t.manifest
154 | }
155 | 
156 | // McpManifest returns the machine-consumable manifest for the tool.
157 | func (t Tool) McpManifest() tools.McpManifest {
158 | 	return t.mcpManifest
159 | }
160 | 
161 | // Authorized checks if the tool is authorized to run based on the provided authentication services.
162 | func (t Tool) Authorized(verifiedAuthServices []string) bool {
163 | 	return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
164 | }
165 | 
166 | func (t Tool) RequiresClientAuthorization(resourceMgr tools.SourceProvider) (bool, error) {
167 | 	return false, nil
168 | }
169 | 
170 | // checkAPOCProcedures verifies if essential APOC procedures are available in the database.
171 | // It returns true only if all required procedures are found.
172 | func (t Tool) checkAPOCProcedures(ctx context.Context, source compatibleSource) (bool, error) {
173 | 	proceduresToCheck := []string{"apoc.meta.schema", "apoc.meta.cypher.types"}
174 | 
175 | 	session := source.Neo4jDriver().NewSession(ctx, neo4j.SessionConfig{DatabaseName: source.Neo4jDatabase()})
176 | 	defer session.Close(ctx)
177 | 
178 | 	// This query efficiently counts how many of the specified procedures exist.
179 | 	query := "SHOW PROCEDURES YIELD name WHERE name IN $procs RETURN count(name) AS procCount"
180 | 	params := map[string]any{"procs": proceduresToCheck}
181 | 
182 | 	result, err := session.Run(ctx, query, params)
183 | 	if err != nil {
184 | 		return false, fmt.Errorf("failed to execute procedure check query: %w", err)
185 | 	}
186 | 
187 | 	record, err := result.Single(ctx)
188 | 	if err != nil {
189 | 		return false, fmt.Errorf("failed to retrieve single result for procedure check: %w", err)
190 | 	}
191 | 
192 | 	rawCount, found := record.Get("procCount")
193 | 	if !found {
194 | 		return false, fmt.Errorf("field 'procCount' not found in result record")
195 | 	}
196 | 
197 | 	procCount, ok := rawCount.(int64)
198 | 	if !ok {
199 | 		return false, fmt.Errorf("expected 'procCount' to be of type int64, but got %T", rawCount)
200 | 	}
201 | 
202 | 	// Return true only if the number of found procedures matches the number we were looking for.
203 | 	return procCount == int64(len(proceduresToCheck)), nil
204 | }
205 | 
206 | // extractSchema orchestrates the concurrent extraction of different parts of the database schema.
207 | // It runs several extraction tasks in parallel for efficiency.
208 | func (t Tool) extractSchema(ctx context.Context, source compatibleSource) (*types.SchemaInfo, error) {
209 | 	schema := &types.SchemaInfo{}
210 | 	var mu sync.Mutex
211 | 
212 | 	// Define the different schema extraction tasks.
213 | 	tasks := []struct {
214 | 		name string
215 | 		fn   func() error
216 | 	}{
217 | 		{
218 | 			name: "database-info",
219 | 			fn: func() error {
220 | 				dbInfo, err := t.extractDatabaseInfo(ctx, source)
221 | 				if err != nil {
222 | 					return fmt.Errorf("failed to extract database info: %w", err)
223 | 				}
224 | 				mu.Lock()
225 | 				defer mu.Unlock()
226 | 				schema.DatabaseInfo = *dbInfo
227 | 				return nil
228 | 			},
229 | 		},
230 | 		{
231 | 			name: "schema-extraction",
232 | 			fn: func() error {
233 | 				// Check if APOC procedures are available.
234 | 				hasAPOC, err := t.checkAPOCProcedures(ctx, source)
235 | 				if err != nil {
236 | 					return fmt.Errorf("failed to check APOC procedures: %w", err)
237 | 				}
238 | 
239 | 				var nodeLabels []types.NodeLabel
240 | 				var relationships []types.Relationship
241 | 				var stats *types.Statistics
242 | 
243 | 				// Use APOC if available for a more detailed schema; otherwise, use native queries.
244 | 				if hasAPOC {
245 | 					nodeLabels, relationships, stats, err = t.GetAPOCSchema(ctx, source)
246 | 				} else {
247 | 					nodeLabels, relationships, stats, err = t.GetSchemaWithoutAPOC(ctx, source, 100)
248 | 				}
249 | 				if err != nil {
250 | 					return fmt.Errorf("failed to get schema: %w", err)
251 | 				}
252 | 
253 | 				mu.Lock()
254 | 				defer mu.Unlock()
255 | 				schema.NodeLabels = nodeLabels
256 | 				schema.Relationships = relationships
257 | 				schema.Statistics = *stats
258 | 				return nil
259 | 			},
260 | 		},
261 | 		{
262 | 			name: "constraints",
263 | 			fn: func() error {
264 | 				constraints, err := t.extractConstraints(ctx, source)
265 | 				if err != nil {
266 | 					return fmt.Errorf("failed to extract constraints: %w", err)
267 | 				}
268 | 				mu.Lock()
269 | 				defer mu.Unlock()
270 | 				schema.Constraints = constraints
271 | 				return nil
272 | 			},
273 | 		},
274 | 		{
275 | 			name: "indexes",
276 | 			fn: func() error {
277 | 				indexes, err := t.extractIndexes(ctx, source)
278 | 				if err != nil {
279 | 					return fmt.Errorf("failed to extract indexes: %w", err)
280 | 				}
281 | 				mu.Lock()
282 | 				defer mu.Unlock()
283 | 				schema.Indexes = indexes
284 | 				return nil
285 | 			},
286 | 		},
287 | 	}
288 | 
289 | 	var wg sync.WaitGroup
290 | 	errCh := make(chan error, len(tasks))
291 | 
292 | 	// Execute all tasks concurrently.
293 | 	for _, task := range tasks {
294 | 		wg.Add(1)
295 | 		go func(task struct {
296 | 			name string
297 | 			fn   func() error
298 | 		}) {
299 | 			defer wg.Done()
300 | 			if err := task.fn(); err != nil {
301 | 				errCh <- err
302 | 			}
303 | 		}(task)
304 | 	}
305 | 
306 | 	wg.Wait()
307 | 	close(errCh)
308 | 
309 | 	// Collect any errors that occurred during the concurrent tasks.
310 | 	for err := range errCh {
311 | 		if err != nil {
312 | 			schema.Errors = append(schema.Errors, err.Error())
313 | 		}
314 | 	}
315 | 	return schema, nil
316 | }
317 | 
318 | // GetAPOCSchema extracts schema information using the APOC library, which provides detailed metadata.
319 | func (t Tool) GetAPOCSchema(ctx context.Context, source compatibleSource) ([]types.NodeLabel, []types.Relationship, *types.Statistics, error) {
320 | 	var nodeLabels []types.NodeLabel
321 | 	var relationships []types.Relationship
322 | 	stats := &types.Statistics{
323 | 		NodesByLabel:        make(map[string]int64),
324 | 		RelationshipsByType: make(map[string]int64),
325 | 		PropertiesByLabel:   make(map[string]int64),
326 | 		PropertiesByRelType: make(map[string]int64),
327 | 	}
328 | 
329 | 	var mu sync.Mutex
330 | 	var firstErr error
331 | 	ctx, cancel := context.WithCancel(ctx)
332 | 	defer cancel()
333 | 
334 | 	handleError := func(err error) {
335 | 		mu.Lock()
336 | 		defer mu.Unlock()
337 | 		if firstErr == nil {
338 | 			firstErr = err
339 | 			cancel() // Cancel other operations on the first error.
340 | 		}
341 | 	}
342 | 
343 | 	tasks := []struct {
344 | 		name string
345 | 		fn   func(session neo4j.SessionWithContext) error
346 | 	}{
347 | 		{
348 | 			name: "apoc-schema",
349 | 			fn: func(session neo4j.SessionWithContext) error {
350 | 				result, err := session.Run(ctx, "CALL apoc.meta.schema({sample: 10}) YIELD value RETURN value", nil)
351 | 				if err != nil {
352 | 					return fmt.Errorf("failed to run APOC schema query: %w", err)
353 | 				}
354 | 				if !result.Next(ctx) {
355 | 					return fmt.Errorf("no results from APOC schema query")
356 | 				}
357 | 				schemaMap, ok := result.Record().Values[0].(map[string]any)
358 | 				if !ok {
359 | 					return fmt.Errorf("unexpected result format from APOC schema query: %T", result.Record().Values[0])
360 | 				}
361 | 				apocSchema, err := helpers.MapToAPOCSchema(schemaMap)
362 | 				if err != nil {
363 | 					return fmt.Errorf("failed to convert schema map to APOCSchemaResult: %w", err)
364 | 				}
365 | 				nodes, _, apocStats := helpers.ProcessAPOCSchema(apocSchema)
366 | 				mu.Lock()
367 | 				defer mu.Unlock()
368 | 				nodeLabels = nodes
369 | 				stats.TotalNodes = apocStats.TotalNodes
370 | 				stats.TotalProperties += apocStats.TotalProperties
371 | 				stats.NodesByLabel = apocStats.NodesByLabel
372 | 				stats.PropertiesByLabel = apocStats.PropertiesByLabel
373 | 				return nil
374 | 			},
375 | 		},
376 | 		{
377 | 			name: "apoc-relationships",
378 | 			fn: func(session neo4j.SessionWithContext) error {
379 | 				query := `
380 | 					MATCH (startNode)-[rel]->(endNode)
381 | 					WITH
382 | 					  labels(startNode)[0] AS startNode,
383 | 					  type(rel) AS relType,
384 | 					  apoc.meta.cypher.types(rel) AS relProperties,
385 | 					  labels(endNode)[0] AS endNode,
386 | 					  count(*) AS count
387 | 					RETURN relType, startNode, endNode, relProperties, count`
388 | 				result, err := session.Run(ctx, query, nil)
389 | 				if err != nil {
390 | 					return fmt.Errorf("failed to extract relationships: %w", err)
391 | 				}
392 | 				for result.Next(ctx) {
393 | 					record := result.Record()
394 | 					relType, startNode, endNode := record.Values[0].(string), record.Values[1].(string), record.Values[2].(string)
395 | 					properties, count := record.Values[3].(map[string]any), record.Values[4].(int64)
396 | 
397 | 					if relType == "" || count == 0 {
398 | 						continue
399 | 					}
400 | 					relationship := types.Relationship{Type: relType, StartNode: startNode, EndNode: endNode, Count: count, Properties: []types.PropertyInfo{}}
401 | 					for prop, propType := range properties {
402 | 						relationship.Properties = append(relationship.Properties, types.PropertyInfo{Name: prop, Types: []string{propType.(string)}})
403 | 					}
404 | 					mu.Lock()
405 | 					relationships = append(relationships, relationship)
406 | 					stats.RelationshipsByType[relType] += count
407 | 					stats.TotalRelationships += count
408 | 					propCount := int64(len(relationship.Properties))
409 | 					stats.TotalProperties += propCount
410 | 					stats.PropertiesByRelType[relType] += propCount
411 | 					mu.Unlock()
412 | 				}
413 | 				mu.Lock()
414 | 				defer mu.Unlock()
415 | 				if len(stats.RelationshipsByType) == 0 {
416 | 					stats.RelationshipsByType = nil
417 | 				}
418 | 				if len(stats.PropertiesByRelType) == 0 {
419 | 					stats.PropertiesByRelType = nil
420 | 				}
421 | 				return nil
422 | 			},
423 | 		},
424 | 	}
425 | 
426 | 	var wg sync.WaitGroup
427 | 	wg.Add(len(tasks))
428 | 	for _, task := range tasks {
429 | 		go func(task struct {
430 | 			name string
431 | 			fn   func(session neo4j.SessionWithContext) error
432 | 		}) {
433 | 			defer wg.Done()
434 | 			session := source.Neo4jDriver().NewSession(ctx, neo4j.SessionConfig{DatabaseName: source.Neo4jDatabase()})
435 | 			defer session.Close(ctx)
436 | 			if err := task.fn(session); err != nil {
437 | 				handleError(fmt.Errorf("task %s failed: %w", task.name, err))
438 | 			}
439 | 		}(task)
440 | 	}
441 | 	wg.Wait()
442 | 
443 | 	if firstErr != nil {
444 | 		return nil, nil, nil, firstErr
445 | 	}
446 | 	return nodeLabels, relationships, stats, nil
447 | }
448 | 
449 | // GetSchemaWithoutAPOC extracts schema information using native Cypher queries.
450 | // This serves as a fallback for databases without APOC installed.
451 | func (t Tool) GetSchemaWithoutAPOC(ctx context.Context, source compatibleSource, sampleSize int) ([]types.NodeLabel, []types.Relationship, *types.Statistics, error) {
452 | 	nodePropsMap := make(map[string]map[string]map[string]bool)
453 | 	relPropsMap := make(map[string]map[string]map[string]bool)
454 | 	nodeCounts := make(map[string]int64)
455 | 	relCounts := make(map[string]int64)
456 | 	relConnectivity := make(map[string]types.RelConnectivityInfo)
457 | 
458 | 	var mu sync.Mutex
459 | 	var firstErr error
460 | 	ctx, cancel := context.WithCancel(ctx)
461 | 	defer cancel()
462 | 
463 | 	handleError := func(err error) {
464 | 		mu.Lock()
465 | 		defer mu.Unlock()
466 | 		if firstErr == nil {
467 | 			firstErr = err
468 | 			cancel()
469 | 		}
470 | 	}
471 | 
472 | 	tasks := []struct {
473 | 		name string
474 | 		fn   func(session neo4j.SessionWithContext) error
475 | 	}{
476 | 		{
477 | 			name: "node-schema",
478 | 			fn: func(session neo4j.SessionWithContext) error {
479 | 				countResult, err := session.Run(ctx, `MATCH (n) UNWIND labels(n) AS label RETURN label, count(*) AS count ORDER BY count DESC`, nil)
480 | 				if err != nil {
481 | 					return fmt.Errorf("node count query failed: %w", err)
482 | 				}
483 | 				var labelsList []string
484 | 				mu.Lock()
485 | 				for countResult.Next(ctx) {
486 | 					record := countResult.Record()
487 | 					label, count := record.Values[0].(string), record.Values[1].(int64)
488 | 					nodeCounts[label] = count
489 | 					labelsList = append(labelsList, label)
490 | 				}
491 | 				mu.Unlock()
492 | 				if err = countResult.Err(); err != nil {
493 | 					return fmt.Errorf("node count result error: %w", err)
494 | 				}
495 | 
496 | 				for _, label := range labelsList {
497 | 					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)
498 | 					propResult, err := session.Run(ctx, propQuery, map[string]any{"sampleSize": sampleSize})
499 | 					if err != nil {
500 | 						return fmt.Errorf("node properties query for label %s failed: %w", label, err)
501 | 					}
502 | 					mu.Lock()
503 | 					if nodePropsMap[label] == nil {
504 | 						nodePropsMap[label] = make(map[string]map[string]bool)
505 | 					}
506 | 					for propResult.Next(ctx) {
507 | 						record := propResult.Record()
508 | 						key, types := record.Values[0].(string), record.Values[1].([]any)
509 | 						if nodePropsMap[label][key] == nil {
510 | 							nodePropsMap[label][key] = make(map[string]bool)
511 | 						}
512 | 						for _, tp := range types {
513 | 							nodePropsMap[label][key][tp.(string)] = true
514 | 						}
515 | 					}
516 | 					mu.Unlock()
517 | 					if err = propResult.Err(); err != nil {
518 | 						return fmt.Errorf("node properties result error for label %s: %w", label, err)
519 | 					}
520 | 				}
521 | 				return nil
522 | 			},
523 | 		},
524 | 		{
525 | 			name: "relationship-schema",
526 | 			fn: func(session neo4j.SessionWithContext) error {
527 | 				relQuery := `
528 | 					MATCH (start)-[r]->(end)
529 | 					WITH type(r) AS relType, labels(start) AS startLabels, labels(end) AS endLabels, count(*) AS count
530 | 					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
531 | 					ORDER BY totalCount DESC`
532 | 				relResult, err := session.Run(ctx, relQuery, nil)
533 | 				if err != nil {
534 | 					return fmt.Errorf("relationship count query failed: %w", err)
535 | 				}
536 | 				var relTypesList []string
537 | 				mu.Lock()
538 | 				for relResult.Next(ctx) {
539 | 					record := relResult.Record()
540 | 					relType := record.Values[0].(string)
541 | 					startLabel := ""
542 | 					if record.Values[1] != nil {
543 | 						startLabel = record.Values[1].(string)
544 | 					}
545 | 					endLabel := ""
546 | 					if record.Values[2] != nil {
547 | 						endLabel = record.Values[2].(string)
548 | 					}
549 | 					count := record.Values[3].(int64)
550 | 					relCounts[relType] = count
551 | 					relTypesList = append(relTypesList, relType)
552 | 					if existing, ok := relConnectivity[relType]; !ok || count > existing.Count {
553 | 						relConnectivity[relType] = types.RelConnectivityInfo{StartNode: startLabel, EndNode: endLabel, Count: count}
554 | 					}
555 | 				}
556 | 				mu.Unlock()
557 | 				if err = relResult.Err(); err != nil {
558 | 					return fmt.Errorf("relationship count result error: %w", err)
559 | 				}
560 | 
561 | 				for _, relType := range relTypesList {
562 | 					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)
563 | 					propResult, err := session.Run(ctx, propQuery, map[string]any{"sampleSize": sampleSize})
564 | 					if err != nil {
565 | 						return fmt.Errorf("relationship properties query for type %s failed: %w", relType, err)
566 | 					}
567 | 					mu.Lock()
568 | 					if relPropsMap[relType] == nil {
569 | 						relPropsMap[relType] = make(map[string]map[string]bool)
570 | 					}
571 | 					for propResult.Next(ctx) {
572 | 						record := propResult.Record()
573 | 						key, propTypes := record.Values[0].(string), record.Values[1].([]any)
574 | 						if relPropsMap[relType][key] == nil {
575 | 							relPropsMap[relType][key] = make(map[string]bool)
576 | 						}
577 | 						for _, t := range propTypes {
578 | 							relPropsMap[relType][key][t.(string)] = true
579 | 						}
580 | 					}
581 | 					mu.Unlock()
582 | 					if err = propResult.Err(); err != nil {
583 | 						return fmt.Errorf("relationship properties result error for type %s: %w", relType, err)
584 | 					}
585 | 				}
586 | 				return nil
587 | 			},
588 | 		},
589 | 	}
590 | 
591 | 	var wg sync.WaitGroup
592 | 	wg.Add(len(tasks))
593 | 	for _, task := range tasks {
594 | 		go func(task struct {
595 | 			name string
596 | 			fn   func(session neo4j.SessionWithContext) error
597 | 		}) {
598 | 			defer wg.Done()
599 | 			session := source.Neo4jDriver().NewSession(ctx, neo4j.SessionConfig{DatabaseName: source.Neo4jDatabase()})
600 | 			defer session.Close(ctx)
601 | 			if err := task.fn(session); err != nil {
602 | 				handleError(fmt.Errorf("task %s failed: %w", task.name, err))
603 | 			}
604 | 		}(task)
605 | 	}
606 | 	wg.Wait()
607 | 
608 | 	if firstErr != nil {
609 | 		return nil, nil, nil, firstErr
610 | 	}
611 | 
612 | 	nodeLabels, relationships, stats := helpers.ProcessNonAPOCSchema(nodeCounts, nodePropsMap, relCounts, relPropsMap, relConnectivity)
613 | 	return nodeLabels, relationships, stats, nil
614 | }
615 | 
616 | // extractDatabaseInfo retrieves general information about the Neo4j database instance.
617 | func (t Tool) extractDatabaseInfo(ctx context.Context, source compatibleSource) (*types.DatabaseInfo, error) {
618 | 	session := source.Neo4jDriver().NewSession(ctx, neo4j.SessionConfig{DatabaseName: source.Neo4jDatabase()})
619 | 	defer session.Close(ctx)
620 | 
621 | 	result, err := session.Run(ctx, "CALL dbms.components() YIELD name, versions, edition", nil)
622 | 	if err != nil {
623 | 		return nil, err
624 | 	}
625 | 
626 | 	dbInfo := &types.DatabaseInfo{}
627 | 	if result.Next(ctx) {
628 | 		record := result.Record()
629 | 		dbInfo.Name = record.Values[0].(string)
630 | 		if versions, ok := record.Values[1].([]any); ok && len(versions) > 0 {
631 | 			dbInfo.Version = versions[0].(string)
632 | 		}
633 | 		dbInfo.Edition = record.Values[2].(string)
634 | 	}
635 | 	return dbInfo, result.Err()
636 | }
637 | 
638 | // extractConstraints fetches all schema constraints from the database.
639 | func (t Tool) extractConstraints(ctx context.Context, source compatibleSource) ([]types.Constraint, error) {
640 | 	session := source.Neo4jDriver().NewSession(ctx, neo4j.SessionConfig{DatabaseName: source.Neo4jDatabase()})
641 | 	defer session.Close(ctx)
642 | 
643 | 	result, err := session.Run(ctx, "SHOW CONSTRAINTS", nil)
644 | 	if err != nil {
645 | 		return nil, err
646 | 	}
647 | 
648 | 	var constraints []types.Constraint
649 | 	for result.Next(ctx) {
650 | 		record := result.Record().AsMap()
651 | 		constraint := types.Constraint{
652 | 			Name:       helpers.GetStringValue(record["name"]),
653 | 			Type:       helpers.GetStringValue(record["type"]),
654 | 			EntityType: helpers.GetStringValue(record["entityType"]),
655 | 		}
656 | 		if labels, ok := record["labelsOrTypes"].([]any); ok && len(labels) > 0 {
657 | 			constraint.Label = labels[0].(string)
658 | 		}
659 | 		if props, ok := record["properties"].([]any); ok {
660 | 			constraint.Properties = helpers.ConvertToStringSlice(props)
661 | 		}
662 | 		constraints = append(constraints, constraint)
663 | 	}
664 | 	return constraints, result.Err()
665 | }
666 | 
667 | // extractIndexes fetches all schema indexes from the database.
668 | func (t Tool) extractIndexes(ctx context.Context, source compatibleSource) ([]types.Index, error) {
669 | 	session := source.Neo4jDriver().NewSession(ctx, neo4j.SessionConfig{DatabaseName: source.Neo4jDatabase()})
670 | 	defer session.Close(ctx)
671 | 
672 | 	result, err := session.Run(ctx, "SHOW INDEXES", nil)
673 | 	if err != nil {
674 | 		return nil, err
675 | 	}
676 | 
677 | 	var indexes []types.Index
678 | 	for result.Next(ctx) {
679 | 		record := result.Record().AsMap()
680 | 		index := types.Index{
681 | 			Name:       helpers.GetStringValue(record["name"]),
682 | 			State:      helpers.GetStringValue(record["state"]),
683 | 			Type:       helpers.GetStringValue(record["type"]),
684 | 			EntityType: helpers.GetStringValue(record["entityType"]),
685 | 		}
686 | 		if labels, ok := record["labelsOrTypes"].([]any); ok && len(labels) > 0 {
687 | 			index.Label = labels[0].(string)
688 | 		}
689 | 		if props, ok := record["properties"].([]any); ok {
690 | 			index.Properties = helpers.ConvertToStringSlice(props)
691 | 		}
692 | 		indexes = append(indexes, index)
693 | 	}
694 | 	return indexes, result.Err()
695 | }
696 | 
697 | func (t Tool) ToConfig() tools.ToolConfig {
698 | 	return t.Config
699 | }
700 | 
701 | func (t Tool) GetAuthTokenHeaderName(resourceMgr tools.SourceProvider) (string, error) {
702 | 	return "Authorization", nil
703 | }
704 | 
```
Page 57/76FirstPrevNextLast