This is page 12 of 33. Use http://codebase.md/googleapis/genai-toolbox?lines=false&page={x} to view the full context. # Directory Structure ``` ├── .ci │ ├── continuous.release.cloudbuild.yaml │ ├── generate_release_table.sh │ ├── integration.cloudbuild.yaml │ ├── quickstart_test │ │ ├── go.integration.cloudbuild.yaml │ │ ├── js.integration.cloudbuild.yaml │ │ ├── py.integration.cloudbuild.yaml │ │ ├── run_go_tests.sh │ │ ├── run_js_tests.sh │ │ ├── run_py_tests.sh │ │ └── setup_hotels_sample.sql │ ├── test_with_coverage.sh │ └── versioned.release.cloudbuild.yaml ├── .github │ ├── auto-label.yaml │ ├── blunderbuss.yml │ ├── CODEOWNERS │ ├── header-checker-lint.yml │ ├── ISSUE_TEMPLATE │ │ ├── bug_report.yml │ │ ├── config.yml │ │ ├── feature_request.yml │ │ └── question.yml │ ├── label-sync.yml │ ├── labels.yaml │ ├── PULL_REQUEST_TEMPLATE.md │ ├── release-please.yml │ ├── renovate.json5 │ ├── sync-repo-settings.yaml │ └── workflows │ ├── cloud_build_failure_reporter.yml │ ├── deploy_dev_docs.yaml │ ├── deploy_previous_version_docs.yaml │ ├── deploy_versioned_docs.yaml │ ├── docs_deploy.yaml │ ├── docs_preview_clean.yaml │ ├── docs_preview_deploy.yaml │ ├── lint.yaml │ ├── schedule_reporter.yml │ ├── sync-labels.yaml │ └── tests.yaml ├── .gitignore ├── .gitmodules ├── .golangci.yaml ├── .hugo │ ├── archetypes │ │ └── default.md │ ├── assets │ │ ├── icons │ │ │ └── logo.svg │ │ └── scss │ │ ├── _styles_project.scss │ │ └── _variables_project.scss │ ├── go.mod │ ├── go.sum │ ├── hugo.toml │ ├── layouts │ │ ├── _default │ │ │ └── home.releases.releases │ │ ├── index.llms-full.txt │ │ ├── index.llms.txt │ │ ├── partials │ │ │ ├── hooks │ │ │ │ └── head-end.html │ │ │ ├── navbar-version-selector.html │ │ │ ├── page-meta-links.html │ │ │ └── td │ │ │ └── render-heading.html │ │ ├── robot.txt │ │ └── shortcodes │ │ ├── include.html │ │ ├── ipynb.html │ │ └── regionInclude.html │ ├── package-lock.json │ ├── package.json │ └── static │ ├── favicons │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ └── favicon.ico │ └── js │ └── w3.js ├── CHANGELOG.md ├── cmd │ ├── options_test.go │ ├── options.go │ ├── root_test.go │ ├── root.go │ └── version.txt ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── DEVELOPER.md ├── Dockerfile ├── docs │ └── en │ ├── _index.md │ ├── about │ │ ├── _index.md │ │ └── faq.md │ ├── concepts │ │ ├── _index.md │ │ └── telemetry │ │ ├── index.md │ │ ├── telemetry_flow.png │ │ └── telemetry_traces.png │ ├── getting-started │ │ ├── _index.md │ │ ├── colab_quickstart.ipynb │ │ ├── configure.md │ │ ├── introduction │ │ │ ├── _index.md │ │ │ └── architecture.png │ │ ├── local_quickstart_go.md │ │ ├── local_quickstart_js.md │ │ ├── local_quickstart.md │ │ ├── mcp_quickstart │ │ │ ├── _index.md │ │ │ ├── inspector_tools.png │ │ │ └── inspector.png │ │ └── quickstart │ │ ├── go │ │ │ ├── genAI │ │ │ │ ├── go.mod │ │ │ │ ├── go.sum │ │ │ │ └── quickstart.go │ │ │ ├── genkit │ │ │ │ ├── go.mod │ │ │ │ ├── go.sum │ │ │ │ └── quickstart.go │ │ │ ├── langchain │ │ │ │ ├── go.mod │ │ │ │ ├── go.sum │ │ │ │ └── quickstart.go │ │ │ ├── openAI │ │ │ │ ├── go.mod │ │ │ │ ├── go.sum │ │ │ │ └── quickstart.go │ │ │ └── quickstart_test.go │ │ ├── golden.txt │ │ ├── js │ │ │ ├── genAI │ │ │ │ ├── package-lock.json │ │ │ │ ├── package.json │ │ │ │ └── quickstart.js │ │ │ ├── genkit │ │ │ │ ├── package-lock.json │ │ │ │ ├── package.json │ │ │ │ └── quickstart.js │ │ │ ├── langchain │ │ │ │ ├── package-lock.json │ │ │ │ ├── package.json │ │ │ │ └── quickstart.js │ │ │ ├── llamaindex │ │ │ │ ├── package-lock.json │ │ │ │ ├── package.json │ │ │ │ └── quickstart.js │ │ │ └── quickstart.test.js │ │ ├── python │ │ │ ├── __init__.py │ │ │ ├── adk │ │ │ │ ├── quickstart.py │ │ │ │ └── requirements.txt │ │ │ ├── core │ │ │ │ ├── quickstart.py │ │ │ │ └── requirements.txt │ │ │ ├── langchain │ │ │ │ ├── quickstart.py │ │ │ │ └── requirements.txt │ │ │ ├── llamaindex │ │ │ │ ├── quickstart.py │ │ │ │ └── requirements.txt │ │ │ └── quickstart_test.py │ │ └── shared │ │ ├── cloud_setup.md │ │ ├── configure_toolbox.md │ │ └── database_setup.md │ ├── how-to │ │ ├── _index.md │ │ ├── connect_via_geminicli.md │ │ ├── connect_via_mcp.md │ │ ├── connect-ide │ │ │ ├── _index.md │ │ │ ├── alloydb_pg_admin_mcp.md │ │ │ ├── alloydb_pg_mcp.md │ │ │ ├── bigquery_mcp.md │ │ │ ├── cloud_sql_mssql_admin_mcp.md │ │ │ ├── cloud_sql_mssql_mcp.md │ │ │ ├── cloud_sql_mysql_admin_mcp.md │ │ │ ├── cloud_sql_mysql_mcp.md │ │ │ ├── cloud_sql_pg_admin_mcp.md │ │ │ ├── cloud_sql_pg_mcp.md │ │ │ ├── firestore_mcp.md │ │ │ ├── looker_mcp.md │ │ │ ├── mssql_mcp.md │ │ │ ├── mysql_mcp.md │ │ │ ├── neo4j_mcp.md │ │ │ ├── postgres_mcp.md │ │ │ ├── spanner_mcp.md │ │ │ └── sqlite_mcp.md │ │ ├── deploy_docker.md │ │ ├── deploy_gke.md │ │ ├── deploy_toolbox.md │ │ ├── export_telemetry.md │ │ └── toolbox-ui │ │ ├── edit-headers.gif │ │ ├── edit-headers.png │ │ ├── index.md │ │ ├── optional-param-checked.png │ │ ├── optional-param-unchecked.png │ │ ├── run-tool.gif │ │ ├── tools.png │ │ └── toolsets.png │ ├── reference │ │ ├── _index.md │ │ ├── cli.md │ │ └── prebuilt-tools.md │ ├── resources │ │ ├── _index.md │ │ ├── authServices │ │ │ ├── _index.md │ │ │ └── google.md │ │ ├── sources │ │ │ ├── _index.md │ │ │ ├── alloydb-admin.md │ │ │ ├── alloydb-pg.md │ │ │ ├── bigquery.md │ │ │ ├── bigtable.md │ │ │ ├── cassandra.md │ │ │ ├── clickhouse.md │ │ │ ├── cloud-monitoring.md │ │ │ ├── cloud-sql-admin.md │ │ │ ├── cloud-sql-mssql.md │ │ │ ├── cloud-sql-mysql.md │ │ │ ├── cloud-sql-pg.md │ │ │ ├── couchbase.md │ │ │ ├── dataplex.md │ │ │ ├── dgraph.md │ │ │ ├── firebird.md │ │ │ ├── firestore.md │ │ │ ├── http.md │ │ │ ├── looker.md │ │ │ ├── mongodb.md │ │ │ ├── mssql.md │ │ │ ├── mysql.md │ │ │ ├── neo4j.md │ │ │ ├── oceanbase.md │ │ │ ├── oracle.md │ │ │ ├── postgres.md │ │ │ ├── redis.md │ │ │ ├── spanner.md │ │ │ ├── sqlite.md │ │ │ ├── tidb.md │ │ │ ├── trino.md │ │ │ ├── valkey.md │ │ │ └── yugabytedb.md │ │ └── tools │ │ ├── _index.md │ │ ├── alloydb │ │ │ ├── _index.md │ │ │ ├── alloydb-create-cluster.md │ │ │ ├── alloydb-create-instance.md │ │ │ ├── alloydb-create-user.md │ │ │ ├── alloydb-get-cluster.md │ │ │ ├── alloydb-get-instance.md │ │ │ ├── alloydb-get-user.md │ │ │ ├── alloydb-list-clusters.md │ │ │ ├── alloydb-list-instances.md │ │ │ ├── alloydb-list-users.md │ │ │ └── alloydb-wait-for-operation.md │ │ ├── alloydbainl │ │ │ ├── _index.md │ │ │ └── alloydb-ai-nl.md │ │ ├── bigquery │ │ │ ├── _index.md │ │ │ ├── bigquery-analyze-contribution.md │ │ │ ├── bigquery-conversational-analytics.md │ │ │ ├── bigquery-execute-sql.md │ │ │ ├── bigquery-forecast.md │ │ │ ├── bigquery-get-dataset-info.md │ │ │ ├── bigquery-get-table-info.md │ │ │ ├── bigquery-list-dataset-ids.md │ │ │ ├── bigquery-list-table-ids.md │ │ │ ├── bigquery-search-catalog.md │ │ │ └── bigquery-sql.md │ │ ├── bigtable │ │ │ ├── _index.md │ │ │ └── bigtable-sql.md │ │ ├── cassandra │ │ │ ├── _index.md │ │ │ └── cassandra-cql.md │ │ ├── clickhouse │ │ │ ├── _index.md │ │ │ ├── clickhouse-execute-sql.md │ │ │ ├── clickhouse-list-databases.md │ │ │ ├── clickhouse-list-tables.md │ │ │ └── clickhouse-sql.md │ │ ├── cloudmonitoring │ │ │ ├── _index.md │ │ │ └── cloud-monitoring-query-prometheus.md │ │ ├── cloudsql │ │ │ ├── _index.md │ │ │ ├── cloudsqlcreatedatabase.md │ │ │ ├── cloudsqlcreateusers.md │ │ │ ├── cloudsqlgetinstances.md │ │ │ ├── cloudsqllistdatabases.md │ │ │ ├── cloudsqllistinstances.md │ │ │ ├── cloudsqlmssqlcreateinstance.md │ │ │ ├── cloudsqlmysqlcreateinstance.md │ │ │ ├── cloudsqlpgcreateinstances.md │ │ │ └── cloudsqlwaitforoperation.md │ │ ├── couchbase │ │ │ ├── _index.md │ │ │ └── couchbase-sql.md │ │ ├── dataform │ │ │ ├── _index.md │ │ │ └── dataform-compile-local.md │ │ ├── dataplex │ │ │ ├── _index.md │ │ │ ├── dataplex-lookup-entry.md │ │ │ ├── dataplex-search-aspect-types.md │ │ │ └── dataplex-search-entries.md │ │ ├── dgraph │ │ │ ├── _index.md │ │ │ └── dgraph-dql.md │ │ ├── firebird │ │ │ ├── _index.md │ │ │ ├── firebird-execute-sql.md │ │ │ └── firebird-sql.md │ │ ├── firestore │ │ │ ├── _index.md │ │ │ ├── firestore-add-documents.md │ │ │ ├── firestore-delete-documents.md │ │ │ ├── firestore-get-documents.md │ │ │ ├── firestore-get-rules.md │ │ │ ├── firestore-list-collections.md │ │ │ ├── firestore-query-collection.md │ │ │ ├── firestore-query.md │ │ │ ├── firestore-update-document.md │ │ │ └── firestore-validate-rules.md │ │ ├── http │ │ │ ├── _index.md │ │ │ └── http.md │ │ ├── looker │ │ │ ├── _index.md │ │ │ ├── looker-add-dashboard-element.md │ │ │ ├── looker-conversational-analytics.md │ │ │ ├── looker-get-dashboards.md │ │ │ ├── looker-get-dimensions.md │ │ │ ├── looker-get-explores.md │ │ │ ├── looker-get-filters.md │ │ │ ├── looker-get-looks.md │ │ │ ├── looker-get-measures.md │ │ │ ├── looker-get-models.md │ │ │ ├── looker-get-parameters.md │ │ │ ├── looker-health-analyze.md │ │ │ ├── looker-health-pulse.md │ │ │ ├── looker-health-vacuum.md │ │ │ ├── looker-make-dashboard.md │ │ │ ├── looker-make-look.md │ │ │ ├── looker-query-sql.md │ │ │ ├── looker-query-url.md │ │ │ ├── looker-query.md │ │ │ └── looker-run-look.md │ │ ├── mongodb │ │ │ ├── _index.md │ │ │ ├── mongodb-aggregate.md │ │ │ ├── mongodb-delete-many.md │ │ │ ├── mongodb-delete-one.md │ │ │ ├── mongodb-find-one.md │ │ │ ├── mongodb-find.md │ │ │ ├── mongodb-insert-many.md │ │ │ ├── mongodb-insert-one.md │ │ │ ├── mongodb-update-many.md │ │ │ └── mongodb-update-one.md │ │ ├── mssql │ │ │ ├── _index.md │ │ │ ├── mssql-execute-sql.md │ │ │ ├── mssql-list-tables.md │ │ │ └── mssql-sql.md │ │ ├── mysql │ │ │ ├── _index.md │ │ │ ├── mysql-execute-sql.md │ │ │ ├── mysql-list-active-queries.md │ │ │ ├── mysql-list-table-fragmentation.md │ │ │ ├── mysql-list-tables-missing-unique-indexes.md │ │ │ ├── mysql-list-tables.md │ │ │ └── mysql-sql.md │ │ ├── neo4j │ │ │ ├── _index.md │ │ │ ├── neo4j-cypher.md │ │ │ ├── neo4j-execute-cypher.md │ │ │ └── neo4j-schema.md │ │ ├── oceanbase │ │ │ ├── _index.md │ │ │ ├── oceanbase-execute-sql.md │ │ │ └── oceanbase-sql.md │ │ ├── oracle │ │ │ ├── _index.md │ │ │ ├── oracle-execute-sql.md │ │ │ └── oracle-sql.md │ │ ├── postgres │ │ │ ├── _index.md │ │ │ ├── postgres-execute-sql.md │ │ │ ├── postgres-list-active-queries.md │ │ │ ├── postgres-list-available-extensions.md │ │ │ ├── postgres-list-installed-extensions.md │ │ │ ├── postgres-list-tables.md │ │ │ └── postgres-sql.md │ │ ├── redis │ │ │ ├── _index.md │ │ │ └── redis.md │ │ ├── spanner │ │ │ ├── _index.md │ │ │ ├── spanner-execute-sql.md │ │ │ ├── spanner-list-tables.md │ │ │ └── spanner-sql.md │ │ ├── sqlite │ │ │ ├── _index.md │ │ │ ├── sqlite-execute-sql.md │ │ │ └── sqlite-sql.md │ │ ├── tidb │ │ │ ├── _index.md │ │ │ ├── tidb-execute-sql.md │ │ │ └── tidb-sql.md │ │ ├── trino │ │ │ ├── _index.md │ │ │ ├── trino-execute-sql.md │ │ │ └── trino-sql.md │ │ ├── utility │ │ │ ├── _index.md │ │ │ └── wait.md │ │ ├── valkey │ │ │ ├── _index.md │ │ │ └── valkey.md │ │ └── yuagbytedb │ │ ├── _index.md │ │ └── yugabytedb-sql.md │ ├── samples │ │ ├── _index.md │ │ ├── alloydb │ │ │ ├── _index.md │ │ │ ├── ai-nl │ │ │ │ ├── alloydb_ai_nl.ipynb │ │ │ │ └── index.md │ │ │ └── mcp_quickstart.md │ │ ├── bigquery │ │ │ ├── _index.md │ │ │ ├── colab_quickstart_bigquery.ipynb │ │ │ ├── local_quickstart.md │ │ │ └── mcp_quickstart │ │ │ ├── _index.md │ │ │ ├── inspector_tools.png │ │ │ └── inspector.png │ │ └── looker │ │ ├── _index.md │ │ ├── looker_gemini_oauth │ │ │ ├── _index.md │ │ │ ├── authenticated.png │ │ │ ├── authorize.png │ │ │ └── registration.png │ │ ├── looker_gemini.md │ │ └── looker_mcp_inspector │ │ ├── _index.md │ │ ├── inspector_tools.png │ │ └── inspector.png │ └── sdks │ ├── _index.md │ ├── go-sdk.md │ ├── js-sdk.md │ └── python-sdk.md ├── gemini-extension.json ├── go.mod ├── go.sum ├── internal │ ├── auth │ │ ├── auth.go │ │ └── google │ │ └── google.go │ ├── log │ │ ├── handler.go │ │ ├── log_test.go │ │ ├── log.go │ │ └── logger.go │ ├── prebuiltconfigs │ │ ├── prebuiltconfigs_test.go │ │ ├── prebuiltconfigs.go │ │ └── tools │ │ ├── alloydb-postgres-admin.yaml │ │ ├── alloydb-postgres-observability.yaml │ │ ├── alloydb-postgres.yaml │ │ ├── bigquery.yaml │ │ ├── clickhouse.yaml │ │ ├── cloud-sql-mssql-admin.yaml │ │ ├── cloud-sql-mssql-observability.yaml │ │ ├── cloud-sql-mssql.yaml │ │ ├── cloud-sql-mysql-admin.yaml │ │ ├── cloud-sql-mysql-observability.yaml │ │ ├── cloud-sql-mysql.yaml │ │ ├── cloud-sql-postgres-admin.yaml │ │ ├── cloud-sql-postgres-observability.yaml │ │ ├── cloud-sql-postgres.yaml │ │ ├── dataplex.yaml │ │ ├── firestore.yaml │ │ ├── looker-conversational-analytics.yaml │ │ ├── looker.yaml │ │ ├── mssql.yaml │ │ ├── mysql.yaml │ │ ├── neo4j.yaml │ │ ├── oceanbase.yaml │ │ ├── postgres.yaml │ │ ├── spanner-postgres.yaml │ │ ├── spanner.yaml │ │ └── sqlite.yaml │ ├── server │ │ ├── api_test.go │ │ ├── api.go │ │ ├── common_test.go │ │ ├── config.go │ │ ├── mcp │ │ │ ├── jsonrpc │ │ │ │ ├── jsonrpc_test.go │ │ │ │ └── jsonrpc.go │ │ │ ├── mcp.go │ │ │ ├── util │ │ │ │ └── lifecycle.go │ │ │ ├── v20241105 │ │ │ │ ├── method.go │ │ │ │ └── types.go │ │ │ ├── v20250326 │ │ │ │ ├── method.go │ │ │ │ └── types.go │ │ │ └── v20250618 │ │ │ ├── method.go │ │ │ └── types.go │ │ ├── mcp_test.go │ │ ├── mcp.go │ │ ├── server_test.go │ │ ├── server.go │ │ ├── static │ │ │ ├── assets │ │ │ │ └── mcptoolboxlogo.png │ │ │ ├── css │ │ │ │ └── style.css │ │ │ ├── index.html │ │ │ ├── js │ │ │ │ ├── auth.js │ │ │ │ ├── loadTools.js │ │ │ │ ├── mainContent.js │ │ │ │ ├── navbar.js │ │ │ │ ├── runTool.js │ │ │ │ ├── toolDisplay.js │ │ │ │ ├── tools.js │ │ │ │ └── toolsets.js │ │ │ ├── tools.html │ │ │ └── toolsets.html │ │ ├── web_test.go │ │ └── web.go │ ├── sources │ │ ├── alloydbadmin │ │ │ ├── alloydbadmin_test.go │ │ │ └── alloydbadmin.go │ │ ├── alloydbpg │ │ │ ├── alloydb_pg_test.go │ │ │ └── alloydb_pg.go │ │ ├── bigquery │ │ │ ├── bigquery_test.go │ │ │ └── bigquery.go │ │ ├── bigtable │ │ │ ├── bigtable_test.go │ │ │ └── bigtable.go │ │ ├── cassandra │ │ │ ├── cassandra_test.go │ │ │ └── cassandra.go │ │ ├── clickhouse │ │ │ ├── clickhouse_test.go │ │ │ └── clickhouse.go │ │ ├── cloudmonitoring │ │ │ ├── cloud_monitoring_test.go │ │ │ └── cloud_monitoring.go │ │ ├── cloudsqladmin │ │ │ ├── cloud_sql_admin_test.go │ │ │ └── cloud_sql_admin.go │ │ ├── cloudsqlmssql │ │ │ ├── cloud_sql_mssql_test.go │ │ │ └── cloud_sql_mssql.go │ │ ├── cloudsqlmysql │ │ │ ├── cloud_sql_mysql_test.go │ │ │ └── cloud_sql_mysql.go │ │ ├── cloudsqlpg │ │ │ ├── cloud_sql_pg_test.go │ │ │ └── cloud_sql_pg.go │ │ ├── couchbase │ │ │ ├── couchbase_test.go │ │ │ └── couchbase.go │ │ ├── dataplex │ │ │ ├── dataplex_test.go │ │ │ └── dataplex.go │ │ ├── dgraph │ │ │ ├── dgraph_test.go │ │ │ └── dgraph.go │ │ ├── dialect.go │ │ ├── firebird │ │ │ ├── firebird_test.go │ │ │ └── firebird.go │ │ ├── firestore │ │ │ ├── firestore_test.go │ │ │ └── firestore.go │ │ ├── http │ │ │ ├── http_test.go │ │ │ └── http.go │ │ ├── ip_type.go │ │ ├── looker │ │ │ ├── looker_test.go │ │ │ └── looker.go │ │ ├── mongodb │ │ │ ├── mongodb_test.go │ │ │ └── mongodb.go │ │ ├── mssql │ │ │ ├── mssql_test.go │ │ │ └── mssql.go │ │ ├── mysql │ │ │ ├── mysql_test.go │ │ │ └── mysql.go │ │ ├── neo4j │ │ │ ├── neo4j_test.go │ │ │ └── neo4j.go │ │ ├── oceanbase │ │ │ ├── oceanbase_test.go │ │ │ └── oceanbase.go │ │ ├── oracle │ │ │ └── oracle.go │ │ ├── postgres │ │ │ ├── postgres_test.go │ │ │ └── postgres.go │ │ ├── redis │ │ │ ├── redis_test.go │ │ │ └── redis.go │ │ ├── sources.go │ │ ├── spanner │ │ │ ├── spanner_test.go │ │ │ └── spanner.go │ │ ├── sqlite │ │ │ ├── sqlite_test.go │ │ │ └── sqlite.go │ │ ├── tidb │ │ │ ├── tidb_test.go │ │ │ └── tidb.go │ │ ├── trino │ │ │ ├── trino_test.go │ │ │ └── trino.go │ │ ├── util.go │ │ ├── valkey │ │ │ ├── valkey_test.go │ │ │ └── valkey.go │ │ └── yugabytedb │ │ ├── yugabytedb_test.go │ │ └── yugabytedb.go │ ├── telemetry │ │ ├── instrumentation.go │ │ └── telemetry.go │ ├── testutils │ │ └── testutils.go │ ├── tools │ │ ├── alloydb │ │ │ ├── alloydbcreatecluster │ │ │ │ ├── alloydbcreatecluster_test.go │ │ │ │ └── alloydbcreatecluster.go │ │ │ ├── alloydbcreateinstance │ │ │ │ ├── alloydbcreateinstance_test.go │ │ │ │ └── alloydbcreateinstance.go │ │ │ ├── alloydbcreateuser │ │ │ │ ├── alloydbcreateuser_test.go │ │ │ │ └── alloydbcreateuser.go │ │ │ ├── alloydbgetcluster │ │ │ │ ├── alloydbgetcluster_test.go │ │ │ │ └── alloydbgetcluster.go │ │ │ ├── alloydbgetinstance │ │ │ │ ├── alloydbgetinstance_test.go │ │ │ │ └── alloydbgetinstance.go │ │ │ ├── alloydbgetuser │ │ │ │ ├── alloydbgetuser_test.go │ │ │ │ └── alloydbgetuser.go │ │ │ ├── alloydblistclusters │ │ │ │ ├── alloydblistclusters_test.go │ │ │ │ └── alloydblistclusters.go │ │ │ ├── alloydblistinstances │ │ │ │ ├── alloydblistinstances_test.go │ │ │ │ └── alloydblistinstances.go │ │ │ ├── alloydblistusers │ │ │ │ ├── alloydblistusers_test.go │ │ │ │ └── alloydblistusers.go │ │ │ └── alloydbwaitforoperation │ │ │ ├── alloydbwaitforoperation_test.go │ │ │ └── alloydbwaitforoperation.go │ │ ├── alloydbainl │ │ │ ├── alloydbainl_test.go │ │ │ └── alloydbainl.go │ │ ├── bigquery │ │ │ ├── bigqueryanalyzecontribution │ │ │ │ ├── bigqueryanalyzecontribution_test.go │ │ │ │ └── bigqueryanalyzecontribution.go │ │ │ ├── bigquerycommon │ │ │ │ ├── table_name_parser_test.go │ │ │ │ ├── table_name_parser.go │ │ │ │ └── util.go │ │ │ ├── bigqueryconversationalanalytics │ │ │ │ ├── bigqueryconversationalanalytics_test.go │ │ │ │ └── bigqueryconversationalanalytics.go │ │ │ ├── bigqueryexecutesql │ │ │ │ ├── bigqueryexecutesql_test.go │ │ │ │ └── bigqueryexecutesql.go │ │ │ ├── bigqueryforecast │ │ │ │ ├── bigqueryforecast_test.go │ │ │ │ └── bigqueryforecast.go │ │ │ ├── bigquerygetdatasetinfo │ │ │ │ ├── bigquerygetdatasetinfo_test.go │ │ │ │ └── bigquerygetdatasetinfo.go │ │ │ ├── bigquerygettableinfo │ │ │ │ ├── bigquerygettableinfo_test.go │ │ │ │ └── bigquerygettableinfo.go │ │ │ ├── bigquerylistdatasetids │ │ │ │ ├── bigquerylistdatasetids_test.go │ │ │ │ └── bigquerylistdatasetids.go │ │ │ ├── bigquerylisttableids │ │ │ │ ├── bigquerylisttableids_test.go │ │ │ │ └── bigquerylisttableids.go │ │ │ ├── bigquerysearchcatalog │ │ │ │ ├── bigquerysearchcatalog_test.go │ │ │ │ └── bigquerysearchcatalog.go │ │ │ └── bigquerysql │ │ │ ├── bigquerysql_test.go │ │ │ └── bigquerysql.go │ │ ├── bigtable │ │ │ ├── bigtable_test.go │ │ │ └── bigtable.go │ │ ├── cassandra │ │ │ └── cassandracql │ │ │ ├── cassandracql_test.go │ │ │ └── cassandracql.go │ │ ├── clickhouse │ │ │ ├── clickhouseexecutesql │ │ │ │ ├── clickhouseexecutesql_test.go │ │ │ │ └── clickhouseexecutesql.go │ │ │ ├── clickhouselistdatabases │ │ │ │ ├── clickhouselistdatabases_test.go │ │ │ │ └── clickhouselistdatabases.go │ │ │ ├── clickhouselisttables │ │ │ │ ├── clickhouselisttables_test.go │ │ │ │ └── clickhouselisttables.go │ │ │ └── clickhousesql │ │ │ ├── clickhousesql_test.go │ │ │ └── clickhousesql.go │ │ ├── cloudmonitoring │ │ │ ├── cloudmonitoring_test.go │ │ │ └── cloudmonitoring.go │ │ ├── cloudsql │ │ │ ├── cloudsqlcreatedatabase │ │ │ │ ├── cloudsqlcreatedatabase_test.go │ │ │ │ └── cloudsqlcreatedatabase.go │ │ │ ├── cloudsqlcreateusers │ │ │ │ ├── cloudsqlcreateusers_test.go │ │ │ │ └── cloudsqlcreateusers.go │ │ │ ├── cloudsqlgetinstances │ │ │ │ ├── cloudsqlgetinstances_test.go │ │ │ │ └── cloudsqlgetinstances.go │ │ │ ├── cloudsqllistdatabases │ │ │ │ ├── cloudsqllistdatabases_test.go │ │ │ │ └── cloudsqllistdatabases.go │ │ │ ├── cloudsqllistinstances │ │ │ │ ├── cloudsqllistinstances_test.go │ │ │ │ └── cloudsqllistinstances.go │ │ │ └── cloudsqlwaitforoperation │ │ │ ├── cloudsqlwaitforoperation_test.go │ │ │ └── cloudsqlwaitforoperation.go │ │ ├── cloudsqlmssql │ │ │ └── cloudsqlmssqlcreateinstance │ │ │ ├── cloudsqlmssqlcreateinstance_test.go │ │ │ └── cloudsqlmssqlcreateinstance.go │ │ ├── cloudsqlmysql │ │ │ └── cloudsqlmysqlcreateinstance │ │ │ ├── cloudsqlmysqlcreateinstance_test.go │ │ │ └── cloudsqlmysqlcreateinstance.go │ │ ├── cloudsqlpg │ │ │ └── cloudsqlpgcreateinstances │ │ │ ├── cloudsqlpgcreateinstances_test.go │ │ │ └── cloudsqlpgcreateinstances.go │ │ ├── common_test.go │ │ ├── common.go │ │ ├── couchbase │ │ │ ├── couchbase_test.go │ │ │ └── couchbase.go │ │ ├── dataform │ │ │ └── dataformcompilelocal │ │ │ ├── dataformcompilelocal_test.go │ │ │ └── dataformcompilelocal.go │ │ ├── dataplex │ │ │ ├── dataplexlookupentry │ │ │ │ ├── dataplexlookupentry_test.go │ │ │ │ └── dataplexlookupentry.go │ │ │ ├── dataplexsearchaspecttypes │ │ │ │ ├── dataplexsearchaspecttypes_test.go │ │ │ │ └── dataplexsearchaspecttypes.go │ │ │ └── dataplexsearchentries │ │ │ ├── dataplexsearchentries_test.go │ │ │ └── dataplexsearchentries.go │ │ ├── dgraph │ │ │ ├── dgraph_test.go │ │ │ └── dgraph.go │ │ ├── firebird │ │ │ ├── firebirdexecutesql │ │ │ │ ├── firebirdexecutesql_test.go │ │ │ │ └── firebirdexecutesql.go │ │ │ └── firebirdsql │ │ │ ├── firebirdsql_test.go │ │ │ └── firebirdsql.go │ │ ├── firestore │ │ │ ├── firestoreadddocuments │ │ │ │ ├── firestoreadddocuments_test.go │ │ │ │ └── firestoreadddocuments.go │ │ │ ├── firestoredeletedocuments │ │ │ │ ├── firestoredeletedocuments_test.go │ │ │ │ └── firestoredeletedocuments.go │ │ │ ├── firestoregetdocuments │ │ │ │ ├── firestoregetdocuments_test.go │ │ │ │ └── firestoregetdocuments.go │ │ │ ├── firestoregetrules │ │ │ │ ├── firestoregetrules_test.go │ │ │ │ └── firestoregetrules.go │ │ │ ├── firestorelistcollections │ │ │ │ ├── firestorelistcollections_test.go │ │ │ │ └── firestorelistcollections.go │ │ │ ├── firestorequery │ │ │ │ ├── firestorequery_test.go │ │ │ │ └── firestorequery.go │ │ │ ├── firestorequerycollection │ │ │ │ ├── firestorequerycollection_test.go │ │ │ │ └── firestorequerycollection.go │ │ │ ├── firestoreupdatedocument │ │ │ │ ├── firestoreupdatedocument_test.go │ │ │ │ └── firestoreupdatedocument.go │ │ │ ├── firestorevalidaterules │ │ │ │ ├── firestorevalidaterules_test.go │ │ │ │ └── firestorevalidaterules.go │ │ │ └── util │ │ │ ├── converter_test.go │ │ │ ├── converter.go │ │ │ ├── validator_test.go │ │ │ └── validator.go │ │ ├── http │ │ │ ├── http_test.go │ │ │ └── http.go │ │ ├── http_method.go │ │ ├── looker │ │ │ ├── lookeradddashboardelement │ │ │ │ ├── lookeradddashboardelement_test.go │ │ │ │ └── lookeradddashboardelement.go │ │ │ ├── lookercommon │ │ │ │ ├── lookercommon_test.go │ │ │ │ └── lookercommon.go │ │ │ ├── lookerconversationalanalytics │ │ │ │ ├── lookerconversationalanalytics_test.go │ │ │ │ └── lookerconversationalanalytics.go │ │ │ ├── lookergetdashboards │ │ │ │ ├── lookergetdashboards_test.go │ │ │ │ └── lookergetdashboards.go │ │ │ ├── lookergetdimensions │ │ │ │ ├── lookergetdimensions_test.go │ │ │ │ └── lookergetdimensions.go │ │ │ ├── lookergetexplores │ │ │ │ ├── lookergetexplores_test.go │ │ │ │ └── lookergetexplores.go │ │ │ ├── lookergetfilters │ │ │ │ ├── lookergetfilters_test.go │ │ │ │ └── lookergetfilters.go │ │ │ ├── lookergetlooks │ │ │ │ ├── lookergetlooks_test.go │ │ │ │ └── lookergetlooks.go │ │ │ ├── lookergetmeasures │ │ │ │ ├── lookergetmeasures_test.go │ │ │ │ └── lookergetmeasures.go │ │ │ ├── lookergetmodels │ │ │ │ ├── lookergetmodels_test.go │ │ │ │ └── lookergetmodels.go │ │ │ ├── lookergetparameters │ │ │ │ ├── lookergetparameters_test.go │ │ │ │ └── lookergetparameters.go │ │ │ ├── lookerhealthanalyze │ │ │ │ ├── lookerhealthanalyze_test.go │ │ │ │ └── lookerhealthanalyze.go │ │ │ ├── lookerhealthpulse │ │ │ │ ├── lookerhealthpulse_test.go │ │ │ │ └── lookerhealthpulse.go │ │ │ ├── lookerhealthvacuum │ │ │ │ ├── lookerhealthvacuum_test.go │ │ │ │ └── lookerhealthvacuum.go │ │ │ ├── lookermakedashboard │ │ │ │ ├── lookermakedashboard_test.go │ │ │ │ └── lookermakedashboard.go │ │ │ ├── lookermakelook │ │ │ │ ├── lookermakelook_test.go │ │ │ │ └── lookermakelook.go │ │ │ ├── lookerquery │ │ │ │ ├── lookerquery_test.go │ │ │ │ └── lookerquery.go │ │ │ ├── lookerquerysql │ │ │ │ ├── lookerquerysql_test.go │ │ │ │ └── lookerquerysql.go │ │ │ ├── lookerqueryurl │ │ │ │ ├── lookerqueryurl_test.go │ │ │ │ └── lookerqueryurl.go │ │ │ └── lookerrunlook │ │ │ ├── lookerrunlook_test.go │ │ │ └── lookerrunlook.go │ │ ├── mongodb │ │ │ ├── mongodbaggregate │ │ │ │ ├── mongodbaggregate_test.go │ │ │ │ └── mongodbaggregate.go │ │ │ ├── mongodbdeletemany │ │ │ │ ├── mongodbdeletemany_test.go │ │ │ │ └── mongodbdeletemany.go │ │ │ ├── mongodbdeleteone │ │ │ │ ├── mongodbdeleteone_test.go │ │ │ │ └── mongodbdeleteone.go │ │ │ ├── mongodbfind │ │ │ │ ├── mongodbfind_test.go │ │ │ │ └── mongodbfind.go │ │ │ ├── mongodbfindone │ │ │ │ ├── mongodbfindone_test.go │ │ │ │ └── mongodbfindone.go │ │ │ ├── mongodbinsertmany │ │ │ │ ├── mongodbinsertmany_test.go │ │ │ │ └── mongodbinsertmany.go │ │ │ ├── mongodbinsertone │ │ │ │ ├── mongodbinsertone_test.go │ │ │ │ └── mongodbinsertone.go │ │ │ ├── mongodbupdatemany │ │ │ │ ├── mongodbupdatemany_test.go │ │ │ │ └── mongodbupdatemany.go │ │ │ └── mongodbupdateone │ │ │ ├── mongodbupdateone_test.go │ │ │ └── mongodbupdateone.go │ │ ├── mssql │ │ │ ├── mssqlexecutesql │ │ │ │ ├── mssqlexecutesql_test.go │ │ │ │ └── mssqlexecutesql.go │ │ │ ├── mssqllisttables │ │ │ │ ├── mssqllisttables_test.go │ │ │ │ └── mssqllisttables.go │ │ │ └── mssqlsql │ │ │ ├── mssqlsql_test.go │ │ │ └── mssqlsql.go │ │ ├── mysql │ │ │ ├── mysqlcommon │ │ │ │ └── mysqlcommon.go │ │ │ ├── mysqlexecutesql │ │ │ │ ├── mysqlexecutesql_test.go │ │ │ │ └── mysqlexecutesql.go │ │ │ ├── mysqllistactivequeries │ │ │ │ ├── mysqllistactivequeries_test.go │ │ │ │ └── mysqllistactivequeries.go │ │ │ ├── mysqllisttablefragmentation │ │ │ │ ├── mysqllisttablefragmentation_test.go │ │ │ │ └── mysqllisttablefragmentation.go │ │ │ ├── mysqllisttables │ │ │ │ ├── mysqllisttables_test.go │ │ │ │ └── mysqllisttables.go │ │ │ ├── mysqllisttablesmissinguniqueindexes │ │ │ │ ├── mysqllisttablesmissinguniqueindexes_test.go │ │ │ │ └── mysqllisttablesmissinguniqueindexes.go │ │ │ └── mysqlsql │ │ │ ├── mysqlsql_test.go │ │ │ └── mysqlsql.go │ │ ├── neo4j │ │ │ ├── neo4jcypher │ │ │ │ ├── neo4jcypher_test.go │ │ │ │ └── neo4jcypher.go │ │ │ ├── neo4jexecutecypher │ │ │ │ ├── classifier │ │ │ │ │ ├── classifier_test.go │ │ │ │ │ └── classifier.go │ │ │ │ ├── neo4jexecutecypher_test.go │ │ │ │ └── neo4jexecutecypher.go │ │ │ └── neo4jschema │ │ │ ├── cache │ │ │ │ ├── cache_test.go │ │ │ │ └── cache.go │ │ │ ├── helpers │ │ │ │ ├── helpers_test.go │ │ │ │ └── helpers.go │ │ │ ├── neo4jschema_test.go │ │ │ ├── neo4jschema.go │ │ │ └── types │ │ │ └── types.go │ │ ├── oceanbase │ │ │ ├── oceanbaseexecutesql │ │ │ │ ├── oceanbaseexecutesql_test.go │ │ │ │ └── oceanbaseexecutesql.go │ │ │ └── oceanbasesql │ │ │ ├── oceanbasesql_test.go │ │ │ └── oceanbasesql.go │ │ ├── oracle │ │ │ ├── oracleexecutesql │ │ │ │ └── oracleexecutesql.go │ │ │ └── oraclesql │ │ │ └── oraclesql.go │ │ ├── parameters_test.go │ │ ├── parameters.go │ │ ├── postgres │ │ │ ├── postgresexecutesql │ │ │ │ ├── postgresexecutesql_test.go │ │ │ │ └── postgresexecutesql.go │ │ │ ├── postgreslistactivequeries │ │ │ │ ├── postgreslistactivequeries_test.go │ │ │ │ └── postgreslistactivequeries.go │ │ │ ├── postgreslistavailableextensions │ │ │ │ ├── postgreslistavailableextensions_test.go │ │ │ │ └── postgreslistavailableextensions.go │ │ │ ├── postgreslistinstalledextensions │ │ │ │ ├── postgreslistinstalledextensions_test.go │ │ │ │ └── postgreslistinstalledextensions.go │ │ │ ├── postgreslisttables │ │ │ │ ├── postgreslisttables_test.go │ │ │ │ └── postgreslisttables.go │ │ │ └── postgressql │ │ │ ├── postgressql_test.go │ │ │ └── postgressql.go │ │ ├── redis │ │ │ ├── redis_test.go │ │ │ └── redis.go │ │ ├── spanner │ │ │ ├── spannerexecutesql │ │ │ │ ├── spannerexecutesql_test.go │ │ │ │ └── spannerexecutesql.go │ │ │ ├── spannerlisttables │ │ │ │ ├── spannerlisttables_test.go │ │ │ │ └── spannerlisttables.go │ │ │ └── spannersql │ │ │ ├── spanner_test.go │ │ │ └── spannersql.go │ │ ├── sqlite │ │ │ ├── sqliteexecutesql │ │ │ │ ├── sqliteexecutesql_test.go │ │ │ │ └── sqliteexecutesql.go │ │ │ └── sqlitesql │ │ │ ├── sqlitesql_test.go │ │ │ └── sqlitesql.go │ │ ├── tidb │ │ │ ├── tidbexecutesql │ │ │ │ ├── tidbexecutesql_test.go │ │ │ │ └── tidbexecutesql.go │ │ │ └── tidbsql │ │ │ ├── tidbsql_test.go │ │ │ └── tidbsql.go │ │ ├── tools_test.go │ │ ├── tools.go │ │ ├── toolsets.go │ │ ├── trino │ │ │ ├── trinoexecutesql │ │ │ │ ├── trinoexecutesql_test.go │ │ │ │ └── trinoexecutesql.go │ │ │ └── trinosql │ │ │ ├── trinosql_test.go │ │ │ └── trinosql.go │ │ ├── utility │ │ │ └── wait │ │ │ ├── wait_test.go │ │ │ └── wait.go │ │ ├── valkey │ │ │ ├── valkey_test.go │ │ │ └── valkey.go │ │ └── yugabytedbsql │ │ ├── yugabytedbsql_test.go │ │ └── yugabytedbsql.go │ └── util │ └── util.go ├── LICENSE ├── logo.png ├── main.go ├── MCP-TOOLBOX-EXTENSION.md ├── README.md └── tests ├── alloydb │ ├── alloydb_integration_test.go │ └── alloydb_wait_for_operation_test.go ├── alloydbainl │ └── alloydb_ai_nl_integration_test.go ├── alloydbpg │ └── alloydb_pg_integration_test.go ├── auth.go ├── bigquery │ └── bigquery_integration_test.go ├── bigtable │ └── bigtable_integration_test.go ├── cassandra │ └── cassandra_integration_test.go ├── clickhouse │ └── clickhouse_integration_test.go ├── cloudmonitoring │ └── cloud_monitoring_integration_test.go ├── cloudsql │ ├── cloud_sql_create_database_test.go │ ├── cloud_sql_create_users_test.go │ ├── cloud_sql_get_instances_test.go │ ├── cloud_sql_list_databases_test.go │ ├── cloudsql_list_instances_test.go │ └── cloudsql_wait_for_operation_test.go ├── cloudsqlmssql │ ├── cloud_sql_mssql_create_instance_integration_test.go │ └── cloud_sql_mssql_integration_test.go ├── cloudsqlmysql │ ├── cloud_sql_mysql_create_instance_integration_test.go │ └── cloud_sql_mysql_integration_test.go ├── cloudsqlpg │ ├── cloud_sql_pg_create_instances_test.go │ └── cloud_sql_pg_integration_test.go ├── common.go ├── couchbase │ └── couchbase_integration_test.go ├── dataform │ └── dataform_integration_test.go ├── dataplex │ └── dataplex_integration_test.go ├── dgraph │ └── dgraph_integration_test.go ├── firebird │ └── firebird_integration_test.go ├── firestore │ └── firestore_integration_test.go ├── http │ └── http_integration_test.go ├── looker │ └── looker_integration_test.go ├── mongodb │ └── mongodb_integration_test.go ├── mssql │ └── mssql_integration_test.go ├── mysql │ └── mysql_integration_test.go ├── neo4j │ └── neo4j_integration_test.go ├── oceanbase │ └── oceanbase_integration_test.go ├── option.go ├── oracle │ └── oracle_integration_test.go ├── postgres │ └── postgres_integration_test.go ├── redis │ └── redis_test.go ├── server.go ├── source.go ├── spanner │ └── spanner_integration_test.go ├── sqlite │ └── sqlite_integration_test.go ├── tidb │ └── tidb_integration_test.go ├── tool.go ├── trino │ └── trino_integration_test.go ├── utility │ └── wait_integration_test.go ├── valkey │ └── valkey_test.go └── yugabytedb └── yugabytedb_integration_test.go ``` # Files -------------------------------------------------------------------------------- /tests/yugabytedb/yugabytedb_integration_test.go: -------------------------------------------------------------------------------- ```go // Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package yugabytedb import ( "context" "fmt" "os" "regexp" "strings" "testing" "time" "github.com/google/uuid" "github.com/googleapis/genai-toolbox/internal/testutils" "github.com/googleapis/genai-toolbox/tests" "github.com/yugabyte/pgx/v5/pgxpool" ) var ( YBDB_SOURCE_KIND = "yugabytedb" YBDB_TOOL_KIND = "yugabytedb-sql" YBDB_DATABASE = os.Getenv("YUGABYTEDB_DATABASE") YBDB_HOST = os.Getenv("YUGABYTEDB_HOST") YBDB_PORT = os.Getenv("YUGABYTEDB_PORT") YBDB_USER = os.Getenv("YUGABYTEDB_USER") YBDB_PASS = os.Getenv("YUGABYTEDB_PASS") YBDB_LB = os.Getenv("YUGABYTEDB_LOADBALANCE") ) func getYBVars(t *testing.T) map[string]any { switch "" { case YBDB_DATABASE: t.Fatal("'YUGABYTEDB_DATABASE' not set") case YBDB_HOST: t.Fatal("'YUGABYTEDB_HOST' not set") case YBDB_PORT: t.Fatal("'YUGABYTEDB_PORT' not set") case YBDB_USER: t.Fatal("'YUGABYTEDB_USER' not set") case YBDB_PASS: t.Fatal("'YUGABYTEDB_PASS' not set") case YBDB_LB: fmt.Printf("YUGABYTEDB_LOADBALANCE value not set. Setting default value: false") YBDB_LB = "false" } return map[string]any{ "kind": YBDB_SOURCE_KIND, "host": YBDB_HOST, "port": YBDB_PORT, "database": YBDB_DATABASE, "user": YBDB_USER, "password": YBDB_PASS, "loadBalance": YBDB_LB, } } func initYBConnectionPool(host, port, user, pass, dbname, loadBalance string) (*pgxpool.Pool, error) { dsn := fmt.Sprintf("postgres://%s:%s@%s:%s/%s?load_balance=%s", user, pass, host, port, dbname, loadBalance) pool, err := pgxpool.New(context.Background(), dsn) if err != nil { return nil, fmt.Errorf("unable to create YugabyteDB connection pool: %w", err) } return pool, nil } // SetupYugabyteDBSQLTable creates and inserts data into a table of tool // compatible with yugabytedb-sql tool func SetupYugabyteDBSQLTable(t *testing.T, ctx context.Context, pool *pgxpool.Pool, create_statement, insert_statement, tableName string, params []any) func(*testing.T) { err := pool.Ping(ctx) if err != nil { t.Fatalf("unable to connect to test database: %s", err) } // Create table _, err = pool.Query(ctx, create_statement) if err != nil { t.Fatalf("unable to create test table %s: %s", tableName, err) } // Insert test data _, err = pool.Query(ctx, insert_statement, params...) if err != nil { t.Fatalf("unable to insert test data: %s", err) } return func(t *testing.T) { // tear down test _, err = pool.Exec(ctx, fmt.Sprintf("DROP TABLE %s;", tableName)) if err != nil { t.Errorf("Teardown failed: %s", err) } } } func TestYugabyteDB(t *testing.T) { sourceConfig := getYBVars(t) ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() var args []string pool, err := initYBConnectionPool(YBDB_HOST, YBDB_PORT, YBDB_USER, YBDB_PASS, YBDB_DATABASE, YBDB_LB) if err != nil { t.Fatalf("unable to create YugabyteDB connection pool: %s", err) } tableNameParam := "param_table_" + strings.ReplaceAll(uuid.New().String(), "-", "") tableNameAuth := "auth_table_" + strings.ReplaceAll(uuid.New().String(), "-", "") tableNameTemplateParam := "template_param_table_" + strings.ReplaceAll(uuid.New().String(), "-", "") createParamTableStmt, insertParamTableStmt, paramToolStmt, idParamToolStmt, nameParamToolStmt, arrayToolStmt, paramTestParams := tests.GetPostgresSQLParamToolInfo(tableNameParam) teardownTable1 := SetupYugabyteDBSQLTable(t, ctx, pool, createParamTableStmt, insertParamTableStmt, tableNameParam, paramTestParams) defer teardownTable1(t) createAuthTableStmt, insertAuthTableStmt, authToolStmt, authTestParams := tests.GetPostgresSQLAuthToolInfo(tableNameAuth) teardownTable2 := SetupYugabyteDBSQLTable(t, ctx, pool, createAuthTableStmt, insertAuthTableStmt, tableNameAuth, authTestParams) defer teardownTable2(t) toolsFile := tests.GetToolsConfig(sourceConfig, YBDB_TOOL_KIND, paramToolStmt, idParamToolStmt, nameParamToolStmt, arrayToolStmt, authToolStmt) tmplSelectCombined, tmplSelectFilterCombined := tests.GetPostgresSQLTmplToolStatement() toolsFile = tests.AddTemplateParamConfig(t, toolsFile, YBDB_TOOL_KIND, tmplSelectCombined, tmplSelectFilterCombined, "") cmd, cleanup, err := tests.StartCmd(ctx, toolsFile, args...) if err != nil { t.Fatalf("command initialization returned an error: %s", err) } defer cleanup() waitCtx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() out, err := testutils.WaitForString(waitCtx, regexp.MustCompile(`Server ready to serve`), cmd.Out) if err != nil { t.Logf("toolbox command logs: \n%s", out) t.Fatalf("toolbox didn't start successfully: %s", err) } select1Want, mcpMyFailToolWant, _, mcpSelect1Want := tests.GetPostgresWants() tests.RunToolGetTest(t) tests.RunToolInvokeTest(t, select1Want) tests.RunMCPToolCallMethod(t, mcpMyFailToolWant, mcpSelect1Want) tests.RunToolInvokeWithTemplateParameters(t, tableNameTemplateParam) } ``` -------------------------------------------------------------------------------- /internal/tools/cassandra/cassandracql/cassandracql.go: -------------------------------------------------------------------------------- ```go // Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cassandracql import ( "context" "fmt" yaml "github.com/goccy/go-yaml" "github.com/gocql/gocql" "github.com/googleapis/genai-toolbox/internal/sources" "github.com/googleapis/genai-toolbox/internal/sources/cassandra" "github.com/googleapis/genai-toolbox/internal/tools" ) const kind string = "cassandra-cql" func init() { if !tools.Register(kind, newConfig) { panic(fmt.Sprintf("tool kind %q already registered", kind)) } } func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) { actual := Config{Name: name} if err := decoder.DecodeContext(ctx, &actual); err != nil { return nil, err } return actual, nil } type compatibleSource interface { CassandraSession() *gocql.Session } var _ compatibleSource = &cassandra.Source{} var compatibleSources = [...]string{cassandra.SourceKind} type Config struct { Name string `yaml:"name" validate:"required"` Kind string `yaml:"kind" validate:"required"` Source string `yaml:"source" validate:"required"` Description string `yaml:"description" validate:"required"` Statement string `yaml:"statement" validate:"required"` AuthRequired []string `yaml:"authRequired"` Parameters tools.Parameters `yaml:"parameters"` TemplateParameters tools.Parameters `yaml:"templateParameters"` } // Initialize implements tools.ToolConfig. func (c Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { // verify source exists rawS, ok := srcs[c.Source] if !ok { return nil, fmt.Errorf("no source named %q configured", c.Source) } // verify the source is compatible s, ok := rawS.(compatibleSource) if !ok { return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources) } allParameters, paramManifest, err := tools.ProcessParameters(c.TemplateParameters, c.Parameters) if err != nil { return nil, err } mcpManifest := tools.GetMcpManifest(c.Name, c.Description, c.AuthRequired, allParameters) t := Tool{ Name: c.Name, Kind: kind, Parameters: c.Parameters, TemplateParameters: c.TemplateParameters, AllParams: allParameters, Statement: c.Statement, AuthRequired: c.AuthRequired, Session: s.CassandraSession(), manifest: tools.Manifest{Description: c.Description, Parameters: paramManifest, AuthRequired: c.AuthRequired}, mcpManifest: mcpManifest, } return t, nil } // ToolConfigKind implements tools.ToolConfig. func (c Config) ToolConfigKind() string { return kind } var _ tools.ToolConfig = Config{} type Tool struct { Name string `yaml:"name"` Kind string `yaml:"kind"` AuthRequired []string `yaml:"authRequired"` Parameters tools.Parameters `yaml:"parameters"` TemplateParameters tools.Parameters `yaml:"templateParameters"` AllParams tools.Parameters `yaml:"allParams"` Session *gocql.Session Statement string manifest tools.Manifest mcpManifest tools.McpManifest } // RequiresClientAuthorization implements tools.Tool. func (t Tool) RequiresClientAuthorization() bool { return false } // Authorized implements tools.Tool. func (t Tool) Authorized(verifiedAuthServices []string) bool { return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices) } // Invoke implements tools.Tool. func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { paramsMap := params.AsMap() newStatement, err := tools.ResolveTemplateParams(t.TemplateParameters, t.Statement, paramsMap) if err != nil { return nil, fmt.Errorf("unable to extract template params %w", err) } newParams, err := tools.GetParams(t.Parameters, paramsMap) if err != nil { return nil, fmt.Errorf("unable to extract standard params %w", err) } sliceParams := newParams.AsSlice() iter := t.Session.Query(newStatement, sliceParams...).WithContext(ctx).Iter() // Create a slice to store the out var out []map[string]interface{} // Scan results into a map and append to the slice for { row := make(map[string]interface{}) // Create a new map for each row if !iter.MapScan(row) { break // No more rows } out = append(out, row) } if err := iter.Close(); err != nil { return nil, fmt.Errorf("unable to parse rows: %w", err) } return out, nil } // Manifest implements tools.Tool. func (t Tool) Manifest() tools.Manifest { return t.manifest } // McpManifest implements tools.Tool. func (t Tool) McpManifest() tools.McpManifest { return t.mcpManifest } // ParseParams implements tools.Tool. func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { return tools.ParseParams(t.AllParams, data, claims) } var _ tools.Tool = Tool{} ``` -------------------------------------------------------------------------------- /internal/tools/couchbase/couchbase.go: -------------------------------------------------------------------------------- ```go // Copyright 2024 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package couchbase import ( "context" "encoding/json" "fmt" "github.com/couchbase/gocb/v2" yaml "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/sources" "github.com/googleapis/genai-toolbox/internal/sources/couchbase" "github.com/googleapis/genai-toolbox/internal/tools" ) const kind string = "couchbase-sql" func init() { if !tools.Register(kind, newConfig) { panic(fmt.Sprintf("tool kind %q already registered", kind)) } } func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) { actual := Config{Name: name} if err := decoder.DecodeContext(ctx, &actual); err != nil { return nil, err } return actual, nil } type compatibleSource interface { CouchbaseScope() *gocb.Scope CouchbaseQueryScanConsistency() uint } // validate compatible sources are still compatible var _ compatibleSource = &couchbase.Source{} var compatibleSources = [...]string{couchbase.SourceKind} type Config struct { Name string `yaml:"name" validate:"required"` Kind string `yaml:"kind" validate:"required"` Source string `yaml:"source" validate:"required"` Description string `yaml:"description" validate:"required"` Statement string `yaml:"statement" validate:"required"` AuthRequired []string `yaml:"authRequired"` Parameters tools.Parameters `yaml:"parameters"` TemplateParameters tools.Parameters `yaml:"templateParameters"` } // validate interface var _ tools.ToolConfig = Config{} func (cfg Config) ToolConfigKind() string { return kind } func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { // verify source exists rawS, ok := srcs[cfg.Source] if !ok { return nil, fmt.Errorf("no source named %q configured", cfg.Source) } // verify the source is compatible s, ok := rawS.(compatibleSource) if !ok { return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources) } allParameters, paramManifest, err := tools.ProcessParameters(cfg.TemplateParameters, cfg.Parameters) if err != nil { return nil, err } mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, allParameters) // finish tool setup t := Tool{ Name: cfg.Name, Kind: kind, Parameters: cfg.Parameters, TemplateParameters: cfg.TemplateParameters, AllParams: allParameters, Statement: cfg.Statement, Scope: s.CouchbaseScope(), QueryScanConsistency: s.CouchbaseQueryScanConsistency(), AuthRequired: cfg.AuthRequired, manifest: tools.Manifest{Description: cfg.Description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired}, mcpManifest: mcpManifest, } return t, nil } // validate interface var _ tools.Tool = Tool{} type Tool struct { Name string `yaml:"name"` Kind string `yaml:"kind"` Parameters tools.Parameters `yaml:"parameters"` TemplateParameters tools.Parameters `yaml:"templateParameters"` AllParams tools.Parameters `yaml:"allParams"` AuthRequired []string `yaml:"authRequired"` Scope *gocb.Scope QueryScanConsistency uint Statement string manifest tools.Manifest mcpManifest tools.McpManifest } func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { namedParamsMap := params.AsMap() newStatement, err := tools.ResolveTemplateParams(t.TemplateParameters, t.Statement, namedParamsMap) if err != nil { return nil, fmt.Errorf("unable to extract template params %w", err) } newParams, err := tools.GetParams(t.Parameters, namedParamsMap) if err != nil { return nil, fmt.Errorf("unable to extract standard params %w", err) } results, err := t.Scope.Query(newStatement, &gocb.QueryOptions{ ScanConsistency: gocb.QueryScanConsistency(t.QueryScanConsistency), NamedParameters: newParams.AsMap(), }) if err != nil { return nil, fmt.Errorf("unable to execute query: %w", err) } var out []any for results.Next() { var result json.RawMessage err := results.Row(&result) if err != nil { return nil, fmt.Errorf("error processing row: %w", err) } out = append(out, result) } return out, nil } func (t Tool) ParseParams(data map[string]any, claimsMap map[string]map[string]any) (tools.ParamValues, error) { return tools.ParseParams(t.AllParams, data, claimsMap) } func (t Tool) Manifest() tools.Manifest { return t.manifest } func (t Tool) McpManifest() tools.McpManifest { return t.mcpManifest } func (t Tool) Authorized(verifiedAuthSources []string) bool { return tools.IsAuthorized(t.AuthRequired, verifiedAuthSources) } func (t Tool) RequiresClientAuthorization() bool { return false } ``` -------------------------------------------------------------------------------- /docs/en/resources/sources/cloud-sql-mysql.md: -------------------------------------------------------------------------------- ```markdown --- title: "Cloud SQL for MySQL" linkTitle: "Cloud SQL (MySQL)" type: docs weight: 1 description: > Cloud SQL for MySQL is a fully-managed database service for MySQL. --- ## About [Cloud SQL for MySQL][csql-mysql-docs] is a fully-managed database service that helps you set up, maintain, manage, and administer your MySQL relational databases on Google Cloud Platform. If you are new to Cloud SQL for MySQL, you can try [creating and connecting to a database by following these instructions][csql-mysql-quickstart]. [csql-mysql-docs]: https://cloud.google.com/sql/docs/mysql [csql-mysql-quickstart]: https://cloud.google.com/sql/docs/mysql/connect-instance-local-computer ## Available Tools - [`mysql-sql`](../tools/mysql/mysql-sql.md) Execute pre-defined prepared SQL queries in Cloud SQL for MySQL. - [`mysql-execute-sql`](../tools/mysql/mysql-execute-sql.md) Run parameterized SQL queries in Cloud SQL for MySQL. - [`mysql-list-active-queries`](../tools/mysql/mysql-list-active-queries.md) List active queries in Cloud SQL for MySQL. - [`mysql-list-tables`](../tools/mysql/mysql-list-tables.md) List tables in a Cloud SQL for MySQL database. - [`mysql-list-tables-missing-unique-indexes`](../tools/mysql/mysql-list-tables-missing-unique-indexes.md) List tables in a Cloud SQL for MySQL database that do not have primary or unique indices. - [`mysql-list-table-fragmentation`](../tools/mysql/mysql-list-table-fragmentation.md) List table fragmentation in Cloud SQL for MySQL tables. ### Pre-built Configurations - [Cloud SQL for MySQL using MCP](https://googleapis.github.io/genai-toolbox/how-to/connect-ide/cloud_sql_mysql_mcp/) Connect your IDE to Cloud SQL for MySQL using Toolbox. ## Requirements ### IAM Permissions By default, this source uses the [Cloud SQL Go Connector][csql-go-conn] to authorize and establish mTLS connections to your Cloud SQL instance. The Go connector uses your [Application Default Credentials (ADC)][adc] to authorize your connection to Cloud SQL. In addition to [setting the ADC for your server][set-adc], you need to ensure the IAM identity has been given the following IAM roles (or corresponding permissions): - `roles/cloudsql.client` {{< notice tip >}} If you are connecting from Compute Engine, make sure your VM also has the [proper scope](https://cloud.google.com/compute/docs/access/service-accounts#accesscopesiam) to connect using the Cloud SQL Admin API. {{< /notice >}} [csql-go-conn]: https://github.com/GoogleCloudPlatform/cloud-sql-go-connector [adc]: https://cloud.google.com/docs/authentication#adc [set-adc]: https://cloud.google.com/docs/authentication/provide-credentials-adc ### Networking Cloud SQL supports connecting over both from external networks via the internet ([public IP][public-ip]), and internal networks ([private IP][private-ip]). For more information on choosing between the two options, see the Cloud SQL page [Connection overview][conn-overview]. You can configure the `ipType` parameter in your source configuration to `public` or `private` to match your cluster's configuration. Regardless of which you choose, all connections use IAM-based authorization and are encrypted with mTLS. [private-ip]: https://cloud.google.com/sql/docs/mysql/configure-private-ip [public-ip]: https://cloud.google.com/sql/docs/mysql/configure-ip [conn-overview]: https://cloud.google.com/sql/docs/mysql/connect-overview ### Database User Currently, this source only uses standard authentication. You will need to [create a MySQL user][cloud-sql-users] to login to the database with. [cloud-sql-users]: https://cloud.google.com/sql/docs/mysql/create-manage-users ## Example ```yaml sources: my-cloud-sql-mysql-source: kind: cloud-sql-mysql project: my-project-id region: us-central1 instance: my-instance database: my_db user: ${USER_NAME} password: ${PASSWORD} # ipType: "private" ``` {{< notice tip >}} Use environment variable replacement with the format ${ENV_NAME} instead of hardcoding your secrets into the configuration file. {{< /notice >}} ## Reference | **field** | **type** | **required** | **description** | |-----------|:--------:|:------------:|------------------------------------------------------------------------------------------------------| | kind | string | true | Must be "cloud-sql-mysql". | | project | string | true | Id of the GCP project that the cluster was created in (e.g. "my-project-id"). | | region | string | true | Name of the GCP region that the cluster was created in (e.g. "us-central1"). | | instance | string | true | Name of the Cloud SQL instance within the cluster (e.g. "my-instance"). | | database | string | true | Name of the MySQL database to connect to (e.g. "my_db"). | | user | string | true | Name of the MySQL user to connect as (e.g. "my-pg-user"). | | password | string | true | Password of the MySQL user (e.g. "my-password"). | | ipType | string | false | IP Type of the Cloud SQL instance, must be either `public`, `private`, or `psc`. Default: `public`. | ``` -------------------------------------------------------------------------------- /docs/en/how-to/connect_via_mcp.md: -------------------------------------------------------------------------------- ```markdown --- title: "Connect via MCP Client" type: docs weight: 1 description: > How to connect to Toolbox from a MCP Client. --- ## Toolbox SDKs vs Model Context Protocol (MCP) Toolbox now supports connections via both the native Toolbox SDKs and via [Model Context Protocol (MCP)](https://modelcontextprotocol.io/). However, Toolbox has several features which are not supported in the MCP specification (such as Authenticated Parameters and Authorized invocation). We recommend using the native SDKs over MCP clients to leverage these features. The native SDKs can be combined with MCP clients in many cases. ### Protocol Versions Toolbox currently supports the following versions of MCP specification: * [2025-06-18](https://modelcontextprotocol.io/specification/2025-06-18) * [2025-03-26](https://modelcontextprotocol.io/specification/2025-03-26) * [2024-11-05](https://modelcontextprotocol.io/specification/2024-11-05) ### Toolbox AuthZ/AuthN Not Supported by MCP The auth implementation in Toolbox is not supported in MCP's auth specification. This includes: * [Authenticated Parameters](../resources/tools/#authenticated-parameters) * [Authorized Invocations](../resources/tools/#authorized-invocations) ## Connecting to Toolbox with an MCP client ### Before you begin {{< notice note >}} MCP is only compatible with Toolbox version 0.3.0 and above. {{< /notice >}} 1. [Install](../getting-started/introduction/#installing-the-server) Toolbox version 0.3.0+. 1. Make sure you've set up and initialized your database. 1. [Set up](../getting-started/configure.md) your `tools.yaml` file. ### Connecting via Standard Input/Output (stdio) Toolbox supports the [stdio](https://modelcontextprotocol.io/docs/concepts/transports#standard-input%2Foutput-stdio) transport protocol. Users that wish to use stdio will have to include the `--stdio` flag when running Toolbox. ```bash ./toolbox --stdio ``` When running with stdio, Toolbox will listen via stdio instead of acting as a remote HTTP server. Logs will be set to the `warn` level by default. `debug` and `info` logs are not supported with stdio. {{< notice note >}} Toolbox enables dynamic reloading by default. To disable, use the `--disable-reload` flag. {{< /notice >}} ### Connecting via HTTP Toolbox supports the HTTP transport protocol with and without SSE. {{< tabpane text=true >}} {{% tab header="HTTP with SSE (deprecated)" lang="en" %}} Add the following configuration to your MCP client configuration: ```bash { "mcpServers": { "toolbox": { "type": "sse", "url": "http://127.0.0.1:5000/mcp/sse", } } } ``` If you would like to connect to a specific toolset, replace `url` with `"http://127.0.0.1:5000/mcp/{toolset_name}/sse"`. HTTP with SSE is only supported in version `2024-11-05` and is currently deprecated. {{% /tab %}} {{% tab header="Streamable HTTP" lang="en" %}} Add the following configuration to your MCP client configuration: ```bash { "mcpServers": { "toolbox": { "type": "http", "url": "http://127.0.0.1:5000/mcp", } } } ``` If you would like to connect to a specific toolset, replace `url` with `"http://127.0.0.1:5000/mcp/{toolset_name}"`. {{% /tab %}} {{< /tabpane >}} ### Using the MCP Inspector with Toolbox Use MCP [Inspector](https://github.com/modelcontextprotocol/inspector) for testing and debugging Toolbox server. {{< tabpane text=true >}} {{% tab header="STDIO" lang="en" %}} 1. Run Inspector with Toolbox as a subprocess: ```bash npx @modelcontextprotocol/inspector ./toolbox --stdio ``` 1. For `Transport Type` dropdown menu, select `STDIO`. 1. In `Command`, make sure that it is set to :`./toolbox` (or the correct path to where the Toolbox binary is installed). 1. In `Arguments`, make sure that it's filled with `--stdio`. 1. Click the `Connect` button. It might take awhile to spin up Toolbox. Voila! You should be able to inspect your toolbox tools! {{% /tab %}} {{% tab header="HTTP with SSE (deprecated)" lang="en" %}} 1. [Run Toolbox](../getting-started/introduction/#running-the-server). 1. In a separate terminal, run Inspector directly through `npx`: ```bash npx @modelcontextprotocol/inspector ``` 1. For `Transport Type` dropdown menu, select `SSE`. 1. For `URL`, type in `http://127.0.0.1:5000/mcp/sse` to use all tool or `http//127.0.0.1:5000/mcp/{toolset_name}/sse` to use a specific toolset. 1. Click the `Connect` button. Voila! You should be able to inspect your toolbox tools! {{% /tab %}} {{% tab header="Streamable HTTP" lang="en" %}} 1. [Run Toolbox](../getting-started/introduction/#running-the-server). 1. In a separate terminal, run Inspector directly through `npx`: ```bash npx @modelcontextprotocol/inspector ``` 1. For `Transport Type` dropdown menu, select `Streamable HTTP`. 1. For `URL`, type in `http://127.0.0.1:5000/mcp` to use all tool or `http//127.0.0.1:5000/mcp/{toolset_name}` to use a specific toolset. 1. Click the `Connect` button. Voila! You should be able to inspect your toolbox tools! {{% /tab %}} {{< /tabpane >}} ### Tested Clients | Client | SSE Works | MCP Config Docs | |--------|--------|--------| | Claude Desktop | ✅ | <https://modelcontextprotocol.io/quickstart/user#1-download-claude-for-desktop> | | MCP Inspector | ✅ | <https://github.com/modelcontextprotocol/inspector> | | Cursor | ✅ | <https://docs.cursor.com/context/model-context-protocol> | | Windsurf | ✅ | <https://docs.windsurf.com/windsurf/mcp> | | VS Code (Insiders) | ✅ | <https://code.visualstudio.com/docs/copilot/chat/mcp-servers> | ``` -------------------------------------------------------------------------------- /internal/tools/bigquery/bigquerygetdatasetinfo/bigquerygetdatasetinfo.go: -------------------------------------------------------------------------------- ```go // Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package bigquerygetdatasetinfo import ( "context" "fmt" bigqueryapi "cloud.google.com/go/bigquery" yaml "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/sources" bigqueryds "github.com/googleapis/genai-toolbox/internal/sources/bigquery" "github.com/googleapis/genai-toolbox/internal/tools" ) const kind string = "bigquery-get-dataset-info" const projectKey string = "project" const datasetKey string = "dataset" func init() { if !tools.Register(kind, newConfig) { panic(fmt.Sprintf("tool kind %q already registered", kind)) } } func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) { actual := Config{Name: name} if err := decoder.DecodeContext(ctx, &actual); err != nil { return nil, err } return actual, nil } type compatibleSource interface { BigQueryProject() string BigQueryClient() *bigqueryapi.Client BigQueryClientCreator() bigqueryds.BigqueryClientCreator UseClientAuthorization() bool } // validate compatible sources are still compatible var _ compatibleSource = &bigqueryds.Source{} var compatibleSources = [...]string{bigqueryds.SourceKind} type Config struct { Name string `yaml:"name" validate:"required"` Kind string `yaml:"kind" validate:"required"` Source string `yaml:"source" validate:"required"` Description string `yaml:"description" validate:"required"` AuthRequired []string `yaml:"authRequired"` } // validate interface var _ tools.ToolConfig = Config{} func (cfg Config) ToolConfigKind() string { return kind } func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { // verify source exists rawS, ok := srcs[cfg.Source] if !ok { return nil, fmt.Errorf("no source named %q configured", cfg.Source) } // verify the source is compatible s, ok := rawS.(compatibleSource) if !ok { return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources) } projectParameter := tools.NewStringParameterWithDefault(projectKey, s.BigQueryProject(), "The Google Cloud project ID containing the dataset.") datasetParameter := tools.NewStringParameter(datasetKey, "The dataset to get metadata information.") parameters := tools.Parameters{projectParameter, datasetParameter} mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters) // finish tool setup t := Tool{ Name: cfg.Name, Kind: kind, Parameters: parameters, AuthRequired: cfg.AuthRequired, UseClientOAuth: s.UseClientAuthorization(), ClientCreator: s.BigQueryClientCreator(), Client: s.BigQueryClient(), manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired}, mcpManifest: mcpManifest, } return t, nil } // validate interface var _ tools.Tool = Tool{} type Tool struct { Name string `yaml:"name"` Kind string `yaml:"kind"` AuthRequired []string `yaml:"authRequired"` UseClientOAuth bool `yaml:"useClientOAuth"` Parameters tools.Parameters `yaml:"parameters"` Client *bigqueryapi.Client ClientCreator bigqueryds.BigqueryClientCreator Statement string manifest tools.Manifest mcpManifest tools.McpManifest } func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { mapParams := params.AsMap() projectId, ok := mapParams[projectKey].(string) if !ok { return nil, fmt.Errorf("invalid or missing '%s' parameter; expected a string", projectKey) } datasetId, ok := mapParams[datasetKey].(string) if !ok { return nil, fmt.Errorf("invalid or missing '%s' parameter; expected a string", datasetKey) } bqClient := t.Client var err error // Initialize new client if using user OAuth token if t.UseClientOAuth { tokenStr, err := accessToken.ParseBearerToken() if err != nil { return nil, fmt.Errorf("error parsing access token: %w", err) } bqClient, _, err = t.ClientCreator(tokenStr, false) if err != nil { return nil, fmt.Errorf("error creating client from OAuth access token: %w", err) } } dsHandle := bqClient.DatasetInProject(projectId, datasetId) metadata, err := dsHandle.Metadata(ctx) if err != nil { return nil, fmt.Errorf("failed to get metadata for dataset %s (in project %s): %w", datasetId, bqClient.Project(), err) } return metadata, nil } func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { return tools.ParseParams(t.Parameters, data, claims) } func (t Tool) Manifest() tools.Manifest { return t.manifest } func (t Tool) McpManifest() tools.McpManifest { return t.mcpManifest } func (t Tool) Authorized(verifiedAuthServices []string) bool { return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices) } func (t Tool) RequiresClientAuthorization() bool { return t.UseClientOAuth } ``` -------------------------------------------------------------------------------- /internal/tools/postgres/postgressql/postgressql.go: -------------------------------------------------------------------------------- ```go // Copyright 2024 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package postgressql import ( "context" "fmt" yaml "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/sources" "github.com/googleapis/genai-toolbox/internal/sources/alloydbpg" "github.com/googleapis/genai-toolbox/internal/sources/cloudsqlpg" "github.com/googleapis/genai-toolbox/internal/sources/postgres" "github.com/googleapis/genai-toolbox/internal/tools" "github.com/jackc/pgx/v5/pgxpool" ) const kind string = "postgres-sql" func init() { if !tools.Register(kind, newConfig) { panic(fmt.Sprintf("tool kind %q already registered", kind)) } } func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) { actual := Config{Name: name} if err := decoder.DecodeContext(ctx, &actual); err != nil { return nil, err } return actual, nil } type compatibleSource interface { PostgresPool() *pgxpool.Pool } // validate compatible sources are still compatible var _ compatibleSource = &alloydbpg.Source{} var _ compatibleSource = &cloudsqlpg.Source{} var _ compatibleSource = &postgres.Source{} var compatibleSources = [...]string{alloydbpg.SourceKind, cloudsqlpg.SourceKind, postgres.SourceKind} type Config struct { Name string `yaml:"name" validate:"required"` Kind string `yaml:"kind" validate:"required"` Source string `yaml:"source" validate:"required"` Description string `yaml:"description" validate:"required"` Statement string `yaml:"statement" validate:"required"` AuthRequired []string `yaml:"authRequired"` Parameters tools.Parameters `yaml:"parameters"` TemplateParameters tools.Parameters `yaml:"templateParameters"` } // validate interface var _ tools.ToolConfig = Config{} func (cfg Config) ToolConfigKind() string { return kind } func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { // verify source exists rawS, ok := srcs[cfg.Source] if !ok { return nil, fmt.Errorf("no source named %q configured", cfg.Source) } // verify the source is compatible s, ok := rawS.(compatibleSource) if !ok { return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources) } allParameters, paramManifest, err := tools.ProcessParameters(cfg.TemplateParameters, cfg.Parameters) if err != nil { return nil, err } mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, allParameters) // finish tool setup t := Tool{ Name: cfg.Name, Kind: kind, Parameters: cfg.Parameters, TemplateParameters: cfg.TemplateParameters, AllParams: allParameters, Statement: cfg.Statement, AuthRequired: cfg.AuthRequired, Pool: s.PostgresPool(), manifest: tools.Manifest{Description: cfg.Description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired}, mcpManifest: mcpManifest, } return t, nil } // validate interface var _ tools.Tool = Tool{} type Tool struct { Name string `yaml:"name"` Kind string `yaml:"kind"` AuthRequired []string `yaml:"authRequired"` Parameters tools.Parameters `yaml:"parameters"` TemplateParameters tools.Parameters `yaml:"templateParameters"` AllParams tools.Parameters `yaml:"allParams"` Pool *pgxpool.Pool Statement string manifest tools.Manifest mcpManifest tools.McpManifest } func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { paramsMap := params.AsMap() newStatement, err := tools.ResolveTemplateParams(t.TemplateParameters, t.Statement, paramsMap) if err != nil { return nil, fmt.Errorf("unable to extract template params %w", err) } newParams, err := tools.GetParams(t.Parameters, paramsMap) if err != nil { return nil, fmt.Errorf("unable to extract standard params %w", err) } sliceParams := newParams.AsSlice() results, err := t.Pool.Query(ctx, newStatement, sliceParams...) if err != nil { return nil, fmt.Errorf("unable to execute query: %w", err) } fields := results.FieldDescriptions() var out []any for results.Next() { v, err := results.Values() if err != nil { return nil, fmt.Errorf("unable to parse row: %w", err) } vMap := make(map[string]any) for i, f := range fields { vMap[f.Name] = v[i] } out = append(out, vMap) } return out, nil } func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { return tools.ParseParams(t.AllParams, data, claims) } func (t Tool) Manifest() tools.Manifest { return t.manifest } func (t Tool) McpManifest() tools.McpManifest { return t.mcpManifest } func (t Tool) Authorized(verifiedAuthServices []string) bool { return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices) } func (t Tool) RequiresClientAuthorization() bool { return false } ``` -------------------------------------------------------------------------------- /internal/tools/firestore/firestorelistcollections/firestorelistcollections.go: -------------------------------------------------------------------------------- ```go // Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package firestorelistcollections import ( "context" "fmt" firestoreapi "cloud.google.com/go/firestore" yaml "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/sources" firestoreds "github.com/googleapis/genai-toolbox/internal/sources/firestore" "github.com/googleapis/genai-toolbox/internal/tools" "github.com/googleapis/genai-toolbox/internal/tools/firestore/util" ) const kind string = "firestore-list-collections" const parentPathKey string = "parentPath" func init() { if !tools.Register(kind, newConfig) { panic(fmt.Sprintf("tool kind %q already registered", kind)) } } func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) { actual := Config{Name: name} if err := decoder.DecodeContext(ctx, &actual); err != nil { return nil, err } return actual, nil } type compatibleSource interface { FirestoreClient() *firestoreapi.Client } // validate compatible sources are still compatible var _ compatibleSource = &firestoreds.Source{} var compatibleSources = [...]string{firestoreds.SourceKind} type Config struct { Name string `yaml:"name" validate:"required"` Kind string `yaml:"kind" validate:"required"` Source string `yaml:"source" validate:"required"` Description string `yaml:"description" validate:"required"` AuthRequired []string `yaml:"authRequired"` } // validate interface var _ tools.ToolConfig = Config{} func (cfg Config) ToolConfigKind() string { return kind } func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { // verify source exists rawS, ok := srcs[cfg.Source] if !ok { return nil, fmt.Errorf("no source named %q configured", cfg.Source) } // verify the source is compatible s, ok := rawS.(compatibleSource) if !ok { return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources) } emptyString := "" parentPathParameter := tools.NewStringParameterWithDefault(parentPathKey, emptyString, "Relative parent document path to list subcollections from (e.g., 'users/userId'). If not provided, lists root collections. Note: This is a relative path, NOT an absolute path like 'projects/{project_id}/databases/{database_id}/documents/...'") parameters := tools.Parameters{parentPathParameter} mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters) // finish tool setup t := Tool{ Name: cfg.Name, Kind: kind, Parameters: parameters, AuthRequired: cfg.AuthRequired, Client: s.FirestoreClient(), manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired}, mcpManifest: mcpManifest, } return t, nil } // validate interface var _ tools.Tool = Tool{} type Tool struct { Name string `yaml:"name"` Kind string `yaml:"kind"` AuthRequired []string `yaml:"authRequired"` Parameters tools.Parameters `yaml:"parameters"` Client *firestoreapi.Client manifest tools.Manifest mcpManifest tools.McpManifest } func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { mapParams := params.AsMap() var collectionRefs []*firestoreapi.CollectionRef var err error // Check if parentPath is provided parentPath, hasParent := mapParams[parentPathKey].(string) if hasParent && parentPath != "" { // Validate parent document path if err := util.ValidateDocumentPath(parentPath); err != nil { return nil, fmt.Errorf("invalid parent document path: %w", err) } // List subcollections of the specified document docRef := t.Client.Doc(parentPath) collectionRefs, err = docRef.Collections(ctx).GetAll() if err != nil { return nil, fmt.Errorf("failed to list subcollections of document %q: %w", parentPath, err) } } else { // List root collections collectionRefs, err = t.Client.Collections(ctx).GetAll() if err != nil { return nil, fmt.Errorf("failed to list root collections: %w", err) } } // Convert collection references to response data results := make([]any, len(collectionRefs)) for i, collRef := range collectionRefs { collData := make(map[string]any) collData["id"] = collRef.ID collData["path"] = collRef.Path // If this is a subcollection, include parent information if collRef.Parent != nil { collData["parent"] = collRef.Parent.Path } results[i] = collData } return results, nil } func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { return tools.ParseParams(t.Parameters, data, claims) } func (t Tool) Manifest() tools.Manifest { return t.manifest } func (t Tool) McpManifest() tools.McpManifest { return t.mcpManifest } func (t Tool) Authorized(verifiedAuthServices []string) bool { return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices) } func (t Tool) RequiresClientAuthorization() bool { return false } ``` -------------------------------------------------------------------------------- /internal/tools/tidb/tidbexecutesql/tidbexecutesql.go: -------------------------------------------------------------------------------- ```go // Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package tidbexecutesql import ( "context" "database/sql" "fmt" yaml "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/sources" "github.com/googleapis/genai-toolbox/internal/sources/tidb" "github.com/googleapis/genai-toolbox/internal/tools" "github.com/googleapis/genai-toolbox/internal/util" ) const kind string = "tidb-execute-sql" func init() { if !tools.Register(kind, newConfig) { panic(fmt.Sprintf("tool kind %q already registered", kind)) } } func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) { actual := Config{Name: name} if err := decoder.DecodeContext(ctx, &actual); err != nil { return nil, err } return actual, nil } type compatibleSource interface { TiDBPool() *sql.DB } // validate compatible sources are still compatible var _ compatibleSource = &tidb.Source{} var compatibleSources = [...]string{tidb.SourceKind} type Config struct { Name string `yaml:"name" validate:"required"` Kind string `yaml:"kind" validate:"required"` Source string `yaml:"source" validate:"required"` Description string `yaml:"description" validate:"required"` AuthRequired []string `yaml:"authRequired"` } // validate interface var _ tools.ToolConfig = Config{} func (cfg Config) ToolConfigKind() string { return kind } func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { // verify source exists rawS, ok := srcs[cfg.Source] if !ok { return nil, fmt.Errorf("no source named %q configured", cfg.Source) } // verify the source is compatible s, ok := rawS.(compatibleSource) if !ok { return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources) } sqlParameter := tools.NewStringParameter("sql", "The sql to execute.") parameters := tools.Parameters{sqlParameter} mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters) // finish tool setup t := Tool{ Name: cfg.Name, Kind: kind, Parameters: parameters, AuthRequired: cfg.AuthRequired, Pool: s.TiDBPool(), manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired}, mcpManifest: mcpManifest, } return t, nil } // validate interface var _ tools.Tool = Tool{} type Tool struct { Name string `yaml:"name"` Kind string `yaml:"kind"` AuthRequired []string `yaml:"authRequired"` Parameters tools.Parameters `yaml:"parameters"` Pool *sql.DB manifest tools.Manifest mcpManifest tools.McpManifest } func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { paramsMap := params.AsMap() sql, ok := paramsMap["sql"].(string) if !ok { return nil, fmt.Errorf("unable to get cast %s", paramsMap["sql"]) } // Log the query executed for debugging. logger, err := util.LoggerFromContext(ctx) if err != nil { return nil, fmt.Errorf("error getting logger: %s", err) } logger.DebugContext(ctx, "executing `%s` tool query: %s", kind, sql) results, err := t.Pool.QueryContext(ctx, sql) if err != nil { return nil, fmt.Errorf("unable to execute query: %w", err) } defer results.Close() cols, err := results.Columns() if err != nil { return nil, fmt.Errorf("unable to retrieve rows column name: %w", err) } // create an array of values for each column, which can be re-used to scan each row rawValues := make([]any, len(cols)) values := make([]any, len(cols)) for i := range rawValues { values[i] = &rawValues[i] } colTypes, err := results.ColumnTypes() if err != nil { return nil, fmt.Errorf("unable to get column types: %w", err) } var out []any for results.Next() { err := results.Scan(values...) if err != nil { return nil, fmt.Errorf("unable to parse row: %w", err) } vMap := make(map[string]any) for i, name := range cols { val := rawValues[i] if val == nil { vMap[name] = nil continue } // mysql driver return []uint8 type for "TEXT", "VARCHAR", and "NVARCHAR" // we'll need to cast it back to string switch colTypes[i].DatabaseTypeName() { case "TEXT", "VARCHAR", "NVARCHAR": vMap[name] = string(val.([]byte)) default: vMap[name] = val } } out = append(out, vMap) } if err := results.Err(); err != nil { return nil, fmt.Errorf("errors encountered during row iteration: %w", err) } return out, nil } func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { return tools.ParseParams(t.Parameters, data, claims) } func (t Tool) Manifest() tools.Manifest { return t.manifest } func (t Tool) McpManifest() tools.McpManifest { return t.mcpManifest } func (t Tool) Authorized(verifiedAuthServices []string) bool { return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices) } func (t Tool) RequiresClientAuthorization() bool { return false } ``` -------------------------------------------------------------------------------- /internal/tools/trino/trinosql/trinosql_test.go: -------------------------------------------------------------------------------- ```go // Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package trinosql_test import ( "testing" yaml "github.com/goccy/go-yaml" "github.com/google/go-cmp/cmp" "github.com/googleapis/genai-toolbox/internal/server" "github.com/googleapis/genai-toolbox/internal/testutils" "github.com/googleapis/genai-toolbox/internal/tools" "github.com/googleapis/genai-toolbox/internal/tools/trino/trinosql" ) func TestParseFromYamlTrino(t *testing.T) { ctx, err := testutils.ContextWithNewLogger() if err != nil { t.Fatalf("unexpected error: %s", err) } tcs := []struct { desc string in string want server.ToolConfigs }{ { desc: "basic example", in: ` tools: example_tool: kind: trino-sql source: my-trino-instance description: some description statement: | SELECT * FROM catalog.schema.table WHERE id = ?; authRequired: - my-google-auth-service - other-auth-service parameters: - name: id type: string description: ID to filter by authServices: - name: my-google-auth-service field: user_id - name: other-auth-service field: user_id `, want: server.ToolConfigs{ "example_tool": trinosql.Config{ Name: "example_tool", Kind: "trino-sql", Source: "my-trino-instance", Description: "some description", Statement: "SELECT * FROM catalog.schema.table WHERE id = ?;\n", AuthRequired: []string{"my-google-auth-service", "other-auth-service"}, Parameters: []tools.Parameter{ tools.NewStringParameterWithAuth("id", "ID to filter by", []tools.ParamAuthService{{Name: "my-google-auth-service", Field: "user_id"}, {Name: "other-auth-service", Field: "user_id"}}), }, }, }, }, } for _, tc := range tcs { t.Run(tc.desc, func(t *testing.T) { got := struct { Tools server.ToolConfigs `yaml:"tools"` }{} // Parse contents err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got) if err != nil { t.Fatalf("unable to unmarshal: %s", err) } if diff := cmp.Diff(tc.want, got.Tools); diff != "" { t.Fatalf("incorrect parse: diff %v", diff) } }) } } func TestParseFromYamlWithTemplateParamsTrino(t *testing.T) { ctx, err := testutils.ContextWithNewLogger() if err != nil { t.Fatalf("unexpected error: %s", err) } tcs := []struct { desc string in string want server.ToolConfigs }{ { desc: "basic example", in: ` tools: example_tool: kind: trino-sql source: my-trino-instance description: some description statement: | SELECT * FROM {{ .catalog }}.{{ .schema }}.{{ .tableName }} WHERE country = ?; authRequired: - my-google-auth-service - other-auth-service parameters: - name: country type: string description: some description authServices: - name: my-google-auth-service field: user_id - name: other-auth-service field: user_id templateParameters: - name: catalog type: string description: The catalog to query from. - name: schema type: string description: The schema to query from. - name: tableName type: string description: The table to select data from. - name: fieldArray type: array description: The columns to return for the query. items: name: column type: string description: A column name that will be returned from the query. `, want: server.ToolConfigs{ "example_tool": trinosql.Config{ Name: "example_tool", Kind: "trino-sql", Source: "my-trino-instance", Description: "some description", Statement: "SELECT * FROM {{ .catalog }}.{{ .schema }}.{{ .tableName }} WHERE country = ?;\n", AuthRequired: []string{"my-google-auth-service", "other-auth-service"}, Parameters: []tools.Parameter{ tools.NewStringParameterWithAuth("country", "some description", []tools.ParamAuthService{{Name: "my-google-auth-service", Field: "user_id"}, {Name: "other-auth-service", Field: "user_id"}}), }, TemplateParameters: []tools.Parameter{ tools.NewStringParameter("catalog", "The catalog to query from."), tools.NewStringParameter("schema", "The schema to query from."), tools.NewStringParameter("tableName", "The table to select data from."), tools.NewArrayParameter("fieldArray", "The columns to return for the query.", tools.NewStringParameter("column", "A column name that will be returned from the query.")), }, }, }, }, } for _, tc := range tcs { t.Run(tc.desc, func(t *testing.T) { got := struct { Tools server.ToolConfigs `yaml:"tools"` }{} // Parse contents err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got) if err != nil { t.Fatalf("unable to unmarshal: %s", err) } if diff := cmp.Diff(tc.want, got.Tools); diff != "" { t.Fatalf("incorrect parse: diff %v", diff) } }) } } ``` -------------------------------------------------------------------------------- /internal/tools/spanner/spannerexecutesql/spannerexecutesql.go: -------------------------------------------------------------------------------- ```go // Copyright 2024 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package spannerexecutesql import ( "context" "fmt" "cloud.google.com/go/spanner" yaml "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/sources" spannerdb "github.com/googleapis/genai-toolbox/internal/sources/spanner" "github.com/googleapis/genai-toolbox/internal/tools" "github.com/googleapis/genai-toolbox/internal/util" "google.golang.org/api/iterator" ) const kind string = "spanner-execute-sql" func init() { if !tools.Register(kind, newConfig) { panic(fmt.Sprintf("tool kind %q already registered", kind)) } } func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) { actual := Config{Name: name} if err := decoder.DecodeContext(ctx, &actual); err != nil { return nil, err } return actual, nil } type compatibleSource interface { SpannerClient() *spanner.Client DatabaseDialect() string } // validate compatible sources are still compatible var _ compatibleSource = &spannerdb.Source{} var compatibleSources = [...]string{spannerdb.SourceKind} type Config struct { Name string `yaml:"name" validate:"required"` Kind string `yaml:"kind" validate:"required"` Source string `yaml:"source" validate:"required"` Description string `yaml:"description" validate:"required"` AuthRequired []string `yaml:"authRequired"` ReadOnly bool `yaml:"readOnly"` } // validate interface var _ tools.ToolConfig = Config{} func (cfg Config) ToolConfigKind() string { return kind } func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { // verify source exists rawS, ok := srcs[cfg.Source] if !ok { return nil, fmt.Errorf("no source named %q configured", cfg.Source) } // verify the source is compatible s, ok := rawS.(compatibleSource) if !ok { return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources) } sqlParameter := tools.NewStringParameter("sql", "The sql to execute.") parameters := tools.Parameters{sqlParameter} mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters) // finish tool setup t := Tool{ Name: cfg.Name, Kind: kind, Parameters: parameters, AuthRequired: cfg.AuthRequired, ReadOnly: cfg.ReadOnly, Client: s.SpannerClient(), dialect: s.DatabaseDialect(), manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired}, mcpManifest: mcpManifest, } return t, nil } // validate interface var _ tools.Tool = Tool{} type Tool struct { Name string `yaml:"name"` Kind string `yaml:"kind"` AuthRequired []string `yaml:"authRequired"` Parameters tools.Parameters `yaml:"parameters"` ReadOnly bool `yaml:"readOnly"` Client *spanner.Client dialect string manifest tools.Manifest mcpManifest tools.McpManifest } // processRows iterates over the spanner.RowIterator and converts each row to a map[string]any. func processRows(iter *spanner.RowIterator) ([]any, error) { var out []any defer iter.Stop() for { row, err := iter.Next() if err == iterator.Done { break } if err != nil { return nil, fmt.Errorf("unable to parse row: %w", err) } vMap := make(map[string]any) cols := row.ColumnNames() for i, c := range cols { vMap[c] = row.ColumnValue(i) } out = append(out, vMap) } return out, nil } func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { paramsMap := params.AsMap() sql, ok := paramsMap["sql"].(string) if !ok { return nil, fmt.Errorf("unable to get cast %s", paramsMap["sql"]) } // Log the query executed for debugging. logger, err := util.LoggerFromContext(ctx) if err != nil { return nil, fmt.Errorf("error getting logger: %s", err) } logger.DebugContext(ctx, "executing `%s` tool query: %s", kind, sql) var results []any var opErr error stmt := spanner.Statement{SQL: sql} if t.ReadOnly { iter := t.Client.Single().Query(ctx, stmt) results, opErr = processRows(iter) } else { _, opErr = t.Client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error { var err error iter := txn.Query(ctx, stmt) results, err = processRows(iter) if err != nil { return err } return nil }) } if opErr != nil { return nil, fmt.Errorf("unable to execute query: %w", opErr) } return results, nil } func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { return tools.ParseParams(t.Parameters, data, claims) } func (t Tool) Manifest() tools.Manifest { return t.manifest } func (t Tool) McpManifest() tools.McpManifest { return t.mcpManifest } func (t Tool) Authorized(verifiedAuthServices []string) bool { return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices) } func (t Tool) RequiresClientAuthorization() bool { return false } ``` -------------------------------------------------------------------------------- /internal/tools/mssql/mssqlexecutesql/mssqlexecutesql.go: -------------------------------------------------------------------------------- ```go // Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package mssqlexecutesql import ( "context" "database/sql" "fmt" yaml "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/sources" "github.com/googleapis/genai-toolbox/internal/sources/cloudsqlmssql" "github.com/googleapis/genai-toolbox/internal/sources/mssql" "github.com/googleapis/genai-toolbox/internal/tools" "github.com/googleapis/genai-toolbox/internal/util" ) const kind string = "mssql-execute-sql" func init() { if !tools.Register(kind, newConfig) { panic(fmt.Sprintf("tool kind %q already registered", kind)) } } func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) { actual := Config{Name: name} if err := decoder.DecodeContext(ctx, &actual); err != nil { return nil, err } return actual, nil } type compatibleSource interface { MSSQLDB() *sql.DB } // validate compatible sources are still compatible var _ compatibleSource = &cloudsqlmssql.Source{} var _ compatibleSource = &mssql.Source{} var compatibleSources = [...]string{cloudsqlmssql.SourceKind, mssql.SourceKind} type Config struct { Name string `yaml:"name" validate:"required"` Kind string `yaml:"kind" validate:"required"` Source string `yaml:"source" validate:"required"` Description string `yaml:"description" validate:"required"` AuthRequired []string `yaml:"authRequired"` } // validate interface var _ tools.ToolConfig = Config{} func (cfg Config) ToolConfigKind() string { return kind } func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { // verify source exists rawS, ok := srcs[cfg.Source] if !ok { return nil, fmt.Errorf("no source named %q configured", cfg.Source) } // verify the source is compatible s, ok := rawS.(compatibleSource) if !ok { return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources) } sqlParameter := tools.NewStringParameter("sql", "The sql to execute.") parameters := tools.Parameters{sqlParameter} mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters) // finish tool setup t := Tool{ Name: cfg.Name, Kind: kind, Parameters: parameters, AuthRequired: cfg.AuthRequired, Pool: s.MSSQLDB(), manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired}, mcpManifest: mcpManifest, } return t, nil } // validate interface var _ tools.Tool = Tool{} type Tool struct { Name string `yaml:"name"` Kind string `yaml:"kind"` AuthRequired []string `yaml:"authRequired"` Parameters tools.Parameters `yaml:"parameters"` Pool *sql.DB manifest tools.Manifest mcpManifest tools.McpManifest } func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { paramsMap := params.AsMap() sql, ok := paramsMap["sql"].(string) if !ok { return nil, fmt.Errorf("unable to get cast %s", paramsMap["sql"]) } // Log the query executed for debugging. logger, err := util.LoggerFromContext(ctx) if err != nil { return nil, fmt.Errorf("error getting logger: %s", err) } logger.DebugContext(ctx, "executing `%s` tool query: %s", kind, sql) results, err := t.Pool.QueryContext(ctx, sql) if err != nil { return nil, fmt.Errorf("unable to execute query: %w", err) } defer results.Close() cols, err := results.Columns() // If Columns() errors, it might be a DDL/DML without an OUTPUT clause. // We proceed, and results.Err() will catch actual query execution errors. // 'out' will remain nil if cols is empty or err is not nil here. var out []any if err == nil && len(cols) > 0 { // create an array of values for each column, which can be re-used to scan each row rawValues := make([]any, len(cols)) values := make([]any, len(cols)) for i := range rawValues { values[i] = &rawValues[i] } for results.Next() { scanErr := results.Scan(values...) if scanErr != nil { return nil, fmt.Errorf("unable to parse row: %w", scanErr) } vMap := make(map[string]any) for i, name := range cols { vMap[name] = rawValues[i] } out = append(out, vMap) } } // Check for errors from iterating over rows or from the query execution itself. // results.Close() is handled by defer. if err := results.Err(); err != nil { return nil, fmt.Errorf("errors encountered during query execution or row processing: %w", err) } return out, nil } func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { return tools.ParseParams(t.Parameters, data, claims) } func (t Tool) Manifest() tools.Manifest { return t.manifest } func (t Tool) McpManifest() tools.McpManifest { return t.mcpManifest } func (t Tool) Authorized(verifiedAuthServices []string) bool { return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices) } func (t Tool) RequiresClientAuthorization() bool { return false } ``` -------------------------------------------------------------------------------- /internal/tools/oceanbase/oceanbaseexecutesql/oceanbaseexecutesql.go: -------------------------------------------------------------------------------- ```go // Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package oceanbaseexecutesql import ( "context" "database/sql" "fmt" yaml "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/sources" "github.com/googleapis/genai-toolbox/internal/sources/oceanbase" "github.com/googleapis/genai-toolbox/internal/tools" "github.com/googleapis/genai-toolbox/internal/tools/mysql/mysqlcommon" ) const kind string = "oceanbase-execute-sql" func init() { if !tools.Register(kind, newConfig) { panic(fmt.Sprintf("tool kind %q already registered", kind)) } } type compatibleSource interface { OceanBasePool() *sql.DB } // validate compatible sources are still compatible var _ compatibleSource = &oceanbase.Source{} var compatibleSources = [...]string{oceanbase.SourceKind} type Config struct { Name string `yaml:"name" validate:"required"` Kind string `yaml:"kind" validate:"required"` Source string `yaml:"source" validate:"required"` Description string `yaml:"description" validate:"required"` AuthRequired []string `yaml:"authRequired"` } // validate interface var _ tools.ToolConfig = Config{} func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) { actual := Config{Name: name} if err := decoder.DecodeContext(ctx, &actual); err != nil { return nil, err } return actual, nil } func (cfg Config) ToolConfigKind() string { return kind } func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { // verify source exists rawS, ok := srcs[cfg.Source] if !ok { return nil, fmt.Errorf("no source named %q configured", cfg.Source) } // verify the source is compatible s, ok := rawS.(compatibleSource) if !ok { return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources) } sqlParameter := tools.NewStringParameter("sql", "The sql to execute.") parameters := tools.Parameters{sqlParameter} mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters) // finish tool setup t := Tool{ Name: cfg.Name, Kind: kind, Parameters: parameters, AuthRequired: cfg.AuthRequired, Pool: s.OceanBasePool(), manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired}, mcpManifest: mcpManifest, } return t, nil } // validate interface var _ tools.Tool = Tool{} type Tool struct { Name string `yaml:"name"` Kind string `yaml:"kind"` AuthRequired []string `yaml:"authRequired"` Parameters tools.Parameters `yaml:"parameters"` Pool *sql.DB manifest tools.Manifest mcpManifest tools.McpManifest } // Invoke executes the SQL statement provided in the parameters. func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { sliceParams := params.AsSlice() sqlStr, ok := sliceParams[0].(string) if !ok { return nil, fmt.Errorf("unable to get cast %s", sliceParams[0]) } results, err := t.Pool.QueryContext(ctx, sqlStr) if err != nil { return nil, fmt.Errorf("unable to execute query: %w", err) } defer results.Close() cols, err := results.Columns() if err != nil { return nil, fmt.Errorf("unable to retrieve rows column name: %w", err) } // create an array of values for each column, which can be re-used to scan each row rawValues := make([]any, len(cols)) values := make([]any, len(cols)) for i := range rawValues { values[i] = &rawValues[i] } colTypes, err := results.ColumnTypes() if err != nil { return nil, fmt.Errorf("unable to get column types: %w", err) } var out []any for results.Next() { err := results.Scan(values...) if err != nil { return nil, fmt.Errorf("unable to parse row: %w", err) } vMap := make(map[string]any) for i, name := range cols { val := rawValues[i] if val == nil { vMap[name] = nil continue } // oceanbase uses mysql driver vMap[name], err = mysqlcommon.ConvertToType(colTypes[i], val) if err != nil { return nil, fmt.Errorf("errors encountered when converting values: %w", err) } } out = append(out, vMap) } if err := results.Err(); err != nil { return nil, fmt.Errorf("errors encountered during row iteration: %w", err) } return out, nil } // ParseParams parses the input parameters for the tool. func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { return tools.ParseParams(t.Parameters, data, claims) } // Manifest returns the tool manifest. func (t Tool) Manifest() tools.Manifest { return t.manifest } // McpManifest returns the MCP manifest for the tool. func (t Tool) McpManifest() tools.McpManifest { return t.mcpManifest } // Authorized checks if the tool is authorized for the given auth services. func (t Tool) Authorized(verifiedAuthServices []string) bool { return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices) } func (t Tool) RequiresClientAuthorization() bool { return false } ``` -------------------------------------------------------------------------------- /tests/cloudsql/cloud_sql_list_databases_test.go: -------------------------------------------------------------------------------- ```go // Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cloudsql import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "net/http/httptest" "net/url" "reflect" "regexp" "strings" "testing" "time" "github.com/googleapis/genai-toolbox/internal/testutils" "github.com/googleapis/genai-toolbox/tests" ) var ( listDatabasesToolKind = "cloud-sql-list-databases" ) type listDatabasesTransport struct { transport http.RoundTripper url *url.URL } func (t *listDatabasesTransport) RoundTrip(req *http.Request) (*http.Response, error) { if strings.HasPrefix(req.URL.String(), "https://sqladmin.googleapis.com") { req.URL.Scheme = t.url.Scheme req.URL.Host = t.url.Host } return t.transport.RoundTrip(req) } type masterListDatabasesHandler struct { t *testing.T } func (h *masterListDatabasesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if !strings.Contains(r.UserAgent(), "genai-toolbox/") { h.t.Errorf("User-Agent header not found") } response := map[string]any{ "items": []map[string]any{ { "name": "db1", "charset": "utf8", "collation": "utf8_general_ci", }, { "name": "db2", "charset": "utf8mb4", "collation": "utf8mb4_unicode_ci", }, }, } statusCode := http.StatusOK w.Header().Set("Content-Type", "application/json") w.WriteHeader(statusCode) if err := json.NewEncoder(w).Encode(response); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } } func TestListDatabasesToolEndpoints(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() handler := &masterListDatabasesHandler{t: t} server := httptest.NewServer(handler) defer server.Close() serverURL, err := url.Parse(server.URL) if err != nil { t.Fatalf("failed to parse server URL: %v", err) } originalTransport := http.DefaultClient.Transport if originalTransport == nil { originalTransport = http.DefaultTransport } http.DefaultClient.Transport = &listDatabasesTransport{ transport: originalTransport, url: serverURL, } t.Cleanup(func() { http.DefaultClient.Transport = originalTransport }) var args []string toolsFile := getListDatabasesToolsConfig() cmd, cleanup, err := tests.StartCmd(ctx, toolsFile, args...) if err != nil { t.Fatalf("command initialization returned an error: %s", err) } defer cleanup() waitCtx, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel() out, err := testutils.WaitForString(waitCtx, regexp.MustCompile(`Server ready to serve`), cmd.Out) if err != nil { t.Logf("toolbox command logs: \n%s", out) t.Fatalf("toolbox didn't start successfully: %s", err) } tcs := []struct { name string toolName string body string want string expectError bool errorStatus int }{ { name: "successful databases listing", toolName: "list-databases", body: `{"project": "p1", "instance": "i1"}`, want: `[{"name":"db1","charset":"utf8","collation":"utf8_general_ci"},{"name":"db2","charset":"utf8mb4","collation":"utf8mb4_unicode_ci"}]`, }, { name: "missing instance", toolName: "list-databases", body: `{"project": "p1"}`, expectError: true, errorStatus: http.StatusBadRequest, }, } for _, tc := range tcs { tc := tc t.Run(tc.name, func(t *testing.T) { api := fmt.Sprintf("http://127.0.0.1:5000/api/tool/%s/invoke", tc.toolName) req, err := http.NewRequest(http.MethodPost, api, bytes.NewBufferString(tc.body)) if err != nil { t.Fatalf("unable to create request: %s", err) } req.Header.Add("Content-type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatalf("unable to send request: %s", err) } defer resp.Body.Close() if tc.expectError { if resp.StatusCode != tc.errorStatus { bodyBytes, _ := io.ReadAll(resp.Body) t.Fatalf("expected status %d but got %d: %s", tc.errorStatus, resp.StatusCode, string(bodyBytes)) } return } if resp.StatusCode != http.StatusOK { bodyBytes, _ := io.ReadAll(resp.Body) t.Fatalf("response status code is not 200, got %d: %s", resp.StatusCode, string(bodyBytes)) } var result struct { Result string `json:"result"` } if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { t.Fatalf("failed to decode response: %v", err) } var got, want []map[string]any if err := json.Unmarshal([]byte(result.Result), &got); err != nil { t.Fatalf("failed to unmarshal result: %v", err) } if err := json.Unmarshal([]byte(tc.want), &want); err != nil { t.Fatalf("failed to unmarshal want: %v", err) } if !reflect.DeepEqual(got, want) { t.Fatalf("unexpected result: got %+v, want %+v", got, want) } }) } } func getListDatabasesToolsConfig() map[string]any { return map[string]any{ "sources": map[string]any{ "my-cloud-sql-source": map[string]any{ "kind": "cloud-sql-admin", }, }, "tools": map[string]any{ "list-databases": map[string]any{ "kind": listDatabasesToolKind, "source": "my-cloud-sql-source", }, }, } } ``` -------------------------------------------------------------------------------- /internal/sources/cloudsqlpg/cloud_sql_pg_test.go: -------------------------------------------------------------------------------- ```go // Copyright 2024 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cloudsqlpg_test import ( "testing" yaml "github.com/goccy/go-yaml" "github.com/google/go-cmp/cmp" "github.com/googleapis/genai-toolbox/internal/server" "github.com/googleapis/genai-toolbox/internal/sources/cloudsqlpg" "github.com/googleapis/genai-toolbox/internal/testutils" ) func TestParseFromYamlCloudSQLPg(t *testing.T) { tcs := []struct { desc string in string want server.SourceConfigs }{ { desc: "basic example", in: ` sources: my-pg-instance: kind: cloud-sql-postgres project: my-project region: my-region instance: my-instance database: my_db user: my_user password: my_pass `, want: server.SourceConfigs{ "my-pg-instance": cloudsqlpg.Config{ Name: "my-pg-instance", Kind: cloudsqlpg.SourceKind, Project: "my-project", Region: "my-region", Instance: "my-instance", IPType: "public", Database: "my_db", User: "my_user", Password: "my_pass", }, }, }, { desc: "public ipType", in: ` sources: my-pg-instance: kind: cloud-sql-postgres project: my-project region: my-region instance: my-instance ipType: Public database: my_db user: my_user password: my_pass `, want: server.SourceConfigs{ "my-pg-instance": cloudsqlpg.Config{ Name: "my-pg-instance", Kind: cloudsqlpg.SourceKind, Project: "my-project", Region: "my-region", Instance: "my-instance", IPType: "public", Database: "my_db", User: "my_user", Password: "my_pass", }, }, }, { desc: "private ipType", in: ` sources: my-pg-instance: kind: cloud-sql-postgres project: my-project region: my-region instance: my-instance ipType: private database: my_db user: my_user password: my_pass `, want: server.SourceConfigs{ "my-pg-instance": cloudsqlpg.Config{ Name: "my-pg-instance", Kind: cloudsqlpg.SourceKind, Project: "my-project", Region: "my-region", Instance: "my-instance", IPType: "private", Database: "my_db", User: "my_user", Password: "my_pass", }, }, }, { desc: "psc ipType", in: ` sources: my-pg-instance: kind: cloud-sql-postgres project: my-project region: my-region instance: my-instance ipType: psc database: my_db user: my_user password: my_pass `, want: server.SourceConfigs{ "my-pg-instance": cloudsqlpg.Config{ Name: "my-pg-instance", Kind: cloudsqlpg.SourceKind, Project: "my-project", Region: "my-region", Instance: "my-instance", IPType: "psc", Database: "my_db", User: "my_user", Password: "my_pass", }, }, }, } for _, tc := range tcs { t.Run(tc.desc, func(t *testing.T) { got := struct { Sources server.SourceConfigs `yaml:"sources"` }{} // Parse contents err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got) if err != nil { t.Fatalf("unable to unmarshal: %s", err) } if !cmp.Equal(tc.want, got.Sources) { t.Fatalf("incorrect parse: want %v, got %v", tc.want, got.Sources) } }) } } func TestFailParseFromYaml(t *testing.T) { tcs := []struct { desc string in string err string }{ { desc: "invalid ipType", in: ` sources: my-pg-instance: kind: cloud-sql-postgres project: my-project region: my-region instance: my-instance ipType: fail database: my_db user: my_user password: my_pass `, err: "unable to parse source \"my-pg-instance\" as \"cloud-sql-postgres\": ipType invalid: must be one of \"public\", \"private\", or \"psc\"", }, { desc: "extra field", in: ` sources: my-pg-instance: kind: cloud-sql-postgres project: my-project region: my-region instance: my-instance database: my_db user: my_user password: my_pass foo: bar `, err: "unable to parse source \"my-pg-instance\" as \"cloud-sql-postgres\": [2:1] unknown field \"foo\"\n 1 | database: my_db\n> 2 | foo: bar\n ^\n 3 | instance: my-instance\n 4 | kind: cloud-sql-postgres\n 5 | password: my_pass\n 6 | ", }, { desc: "missing required field", in: ` sources: my-pg-instance: kind: cloud-sql-postgres region: my-region instance: my-instance database: my_db user: my_user password: my_pass `, err: "unable to parse source \"my-pg-instance\" as \"cloud-sql-postgres\": Key: 'Config.Project' Error:Field validation for 'Project' failed on the 'required' tag", }, } for _, tc := range tcs { t.Run(tc.desc, func(t *testing.T) { got := struct { Sources server.SourceConfigs `yaml:"sources"` }{} // Parse contents err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got) if err == nil { t.Fatalf("expect parsing to fail") } errStr := err.Error() if errStr != tc.err { t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err) } }) } } ``` -------------------------------------------------------------------------------- /internal/tools/yugabytedbsql/yugabytedbsql_test.go: -------------------------------------------------------------------------------- ```go // Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package yugabytedbsql_test import ( "testing" yaml "github.com/goccy/go-yaml" "github.com/google/go-cmp/cmp" "github.com/googleapis/genai-toolbox/internal/server" "github.com/googleapis/genai-toolbox/internal/testutils" "github.com/googleapis/genai-toolbox/internal/tools" "github.com/googleapis/genai-toolbox/internal/tools/yugabytedbsql" ) func TestParseFromYamlYugabyteDBSQL(t *testing.T) { ctx, err := testutils.ContextWithNewLogger() if err != nil { t.Fatalf("unexpected error: %s", err) } tcs := []struct { desc string in string want server.ToolConfigs }{ { desc: "basic valid config", in: ` tools: hotel_search: kind: yugabytedb-sql source: yb-source description: search hotels by city statement: | SELECT * FROM hotels WHERE city = $1; authRequired: - auth-service-a - auth-service-b parameters: - name: city type: string description: city name authServices: - name: auth-service-a field: user_id - name: auth-service-b field: user_id `, want: server.ToolConfigs{ "hotel_search": yugabytedbsql.Config{ Name: "hotel_search", Kind: "yugabytedb-sql", Source: "yb-source", Description: "search hotels by city", Statement: "SELECT * FROM hotels WHERE city = $1;\n", AuthRequired: []string{"auth-service-a", "auth-service-b"}, Parameters: []tools.Parameter{ tools.NewStringParameterWithAuth("city", "city name", []tools.ParamAuthService{ {Name: "auth-service-a", Field: "user_id"}, {Name: "auth-service-b", Field: "user_id"}, }, ), }, }, }, }, } for _, tc := range tcs { t.Run(tc.desc, func(t *testing.T) { got := struct { Tools server.ToolConfigs `yaml:"tools"` }{} err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got) if err != nil { t.Fatalf("unable to unmarshal: %s", err) } if diff := cmp.Diff(tc.want, got.Tools); diff != "" { t.Fatalf("incorrect parse: diff %v", diff) } }) } } func TestFailParseFromYamlYugabyteDBSQL(t *testing.T) { ctx, err := testutils.ContextWithNewLogger() if err != nil { t.Fatalf("unexpected error: %s", err) } cases := []struct { desc string in string }{ { desc: "missing required field (statement)", in: ` tools: tool1: kind: yugabytedb-sql source: yb-source description: incomplete config `, }, { desc: "unknown field (foo)", in: ` tools: tool2: kind: yugabytedb-sql source: yb-source description: test statement: SELECT 1; foo: bar `, }, } for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { cfg := struct { Tools server.ToolConfigs `yaml:"tools"` }{} err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &cfg) if err == nil { t.Fatalf("expected error but got none") } }) } } func TestParseFromYamlWithTemplateParamsYugabyteDB(t *testing.T) { ctx, err := testutils.ContextWithNewLogger() if err != nil { t.Fatalf("unexpected error: %s", err) } tcs := []struct { desc string in string want server.ToolConfigs }{ { desc: "basic example", in: ` tools: example_tool: kind: yugabytedb-sql source: my-yb-instance description: some description statement: | SELECT * FROM SQL_STATEMENT; parameters: - name: name type: string description: some description templateParameters: - name: tableName type: string description: The table to select hotels from. - name: fieldArray type: array description: The columns to return for the query. items: name: column type: string description: A column name that will be returned from the query. `, want: server.ToolConfigs{ "example_tool": yugabytedbsql.Config{ Name: "example_tool", Kind: "yugabytedb-sql", Source: "my-yb-instance", Description: "some description", Statement: "SELECT * FROM SQL_STATEMENT;\n", AuthRequired: []string{}, Parameters: []tools.Parameter{ tools.NewStringParameter("name", "some description"), }, TemplateParameters: []tools.Parameter{ tools.NewStringParameter("tableName", "The table to select hotels from."), tools.NewArrayParameter("fieldArray", "The columns to return for the query.", tools.NewStringParameter("column", "A column name that will be returned from the query.")), }, }, }, }, } for _, tc := range tcs { t.Run(tc.desc, func(t *testing.T) { got := struct { Tools server.ToolConfigs `yaml:"tools"` }{} // Parse contents err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got) if err != nil { t.Fatalf("unable to unmarshal: %s", err) } if diff := cmp.Diff(tc.want, got.Tools); diff != "" { t.Fatalf("incorrect parse: diff %v", diff) } }) } } ``` -------------------------------------------------------------------------------- /docs/en/resources/tools/firebird/firebird-sql.md: -------------------------------------------------------------------------------- ```markdown --- title: "firebird-sql" type: docs weight: 1 description: > A "firebird-sql" tool executes a pre-defined SQL statement against a Firebird database. aliases: - /resources/tools/firebird-sql --- ## About A `firebird-sql` tool executes a pre-defined SQL statement against a Firebird database. It's compatible with the following source: - [firebird](../sources/firebird.md) The specified SQL statement is executed as a [prepared statement][fb-prepare], and supports both positional parameters (`?`) and named parameters (`:param_name`). Parameters will be inserted according to their position or name. If template parameters are included, they will be resolved before the execution of the prepared statement. [fb-prepare]: https://firebirdsql.org/refdocs/langrefupd25-psql-execstat.html ## Example > **Note:** This tool uses parameterized queries to prevent SQL injections. > Query parameters can be used as substitutes for arbitrary expressions. > Parameters cannot be used as substitutes for identifiers, column names, table > names, or other parts of the query. ```yaml tools: search_flights_by_number: kind: firebird-sql source: my_firebird_db statement: | SELECT * FROM flights WHERE airline = ? AND flight_number = ? LIMIT 10 description: | Use this tool to get information for a specific flight. Takes an airline code and flight number and returns info on the flight. Do NOT use this tool with a flight id. Do NOT guess an airline code or flight number. A airline code is a code for an airline service consisting of two-character airline designator and followed by flight number, which is 1 to 4 digit number. For example, if given CY 0123, the airline is "CY", and flight_number is "123". Another example for this is DL 1234, the airline is "DL", and flight_number is "1234". If the tool returns more than one option choose the date closes to today. Example: {{ "airline": "CY", "flight_number": "888", }} Example: {{ "airline": "DL", "flight_number": "1234", }} parameters: - name: airline type: string description: Airline unique 2 letter identifier - name: flight_number type: string description: 1 to 4 digit number ``` ### Example with Named Parameters ```yaml tools: search_flights_by_airline: kind: firebird-sql source: my_firebird_db statement: | SELECT * FROM flights WHERE airline = :airline AND departure_date >= :start_date AND departure_date <= :end_date ORDER BY departure_date description: | Search for flights by airline within a date range using named parameters. parameters: - name: airline type: string description: Airline unique 2 letter identifier - name: start_date type: string description: Start date in YYYY-MM-DD format - name: end_date type: string description: End date in YYYY-MM-DD format ``` ### Example with Template Parameters > **Note:** This tool allows direct modifications to the SQL statement, > including identifiers, column names, and table names. **This makes it more > vulnerable to SQL injections**. Using basic parameters only (see above) is > recommended for performance and safety reasons. For more details, please check > [templateParameters](_index#template-parameters). ```yaml tools: list_table: kind: firebird-sql source: my_firebird_db statement: | SELECT * FROM {{.tableName}} description: | Use this tool to list all information from a specific table. Example: {{ "tableName": "flights", }} templateParameters: - name: tableName type: string description: Table to select from ``` ## Reference | **field** | **type** | **required** | **description** | |--------------------|:------------------------------------------------:|:------------:|--------------------------------------------------------------------------------------------------------------------------------------------| | kind | string | true | Must be "firebird-sql". | | source | string | true | Name of the source the SQL should execute on. | | description | string | true | Description of the tool that is passed to the LLM. | | statement | string | true | SQL statement to execute on. | | parameters | [parameters](_index#specifying-parameters) | false | List of [parameters](_index#specifying-parameters) that will be inserted into the SQL statement. | | templateParameters | [templateParameters](_index#template-parameters) | false | List of [templateParameters](_index#template-parameters) that will be inserted into the SQL statement before executing prepared statement. | ``` -------------------------------------------------------------------------------- /internal/tools/mysql/mysqlexecutesql/mysqlexecutesql.go: -------------------------------------------------------------------------------- ```go // Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package mysqlexecutesql import ( "context" "database/sql" "fmt" yaml "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/sources" "github.com/googleapis/genai-toolbox/internal/sources/cloudsqlmysql" "github.com/googleapis/genai-toolbox/internal/sources/mysql" "github.com/googleapis/genai-toolbox/internal/tools" "github.com/googleapis/genai-toolbox/internal/tools/mysql/mysqlcommon" "github.com/googleapis/genai-toolbox/internal/util" ) const kind string = "mysql-execute-sql" func init() { if !tools.Register(kind, newConfig) { panic(fmt.Sprintf("tool kind %q already registered", kind)) } } func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) { actual := Config{Name: name} if err := decoder.DecodeContext(ctx, &actual); err != nil { return nil, err } return actual, nil } type compatibleSource interface { MySQLPool() *sql.DB } // validate compatible sources are still compatible var _ compatibleSource = &cloudsqlmysql.Source{} var _ compatibleSource = &mysql.Source{} var compatibleSources = [...]string{cloudsqlmysql.SourceKind, mysql.SourceKind} type Config struct { Name string `yaml:"name" validate:"required"` Kind string `yaml:"kind" validate:"required"` Source string `yaml:"source" validate:"required"` Description string `yaml:"description" validate:"required"` AuthRequired []string `yaml:"authRequired"` } // validate interface var _ tools.ToolConfig = Config{} func (cfg Config) ToolConfigKind() string { return kind } func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { // verify source exists rawS, ok := srcs[cfg.Source] if !ok { return nil, fmt.Errorf("no source named %q configured", cfg.Source) } // verify the source is compatible s, ok := rawS.(compatibleSource) if !ok { return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources) } sqlParameter := tools.NewStringParameter("sql", "The sql to execute.") parameters := tools.Parameters{sqlParameter} mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters) // finish tool setup t := Tool{ Name: cfg.Name, Kind: kind, Parameters: parameters, AuthRequired: cfg.AuthRequired, Pool: s.MySQLPool(), manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired}, mcpManifest: mcpManifest, } return t, nil } // validate interface var _ tools.Tool = Tool{} type Tool struct { Name string `yaml:"name"` Kind string `yaml:"kind"` AuthRequired []string `yaml:"authRequired"` Parameters tools.Parameters `yaml:"parameters"` Pool *sql.DB manifest tools.Manifest mcpManifest tools.McpManifest } func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { paramsMap := params.AsMap() sql, ok := paramsMap["sql"].(string) if !ok { return nil, fmt.Errorf("unable to get cast %s", paramsMap["sql"]) } // Log the query executed for debugging. logger, err := util.LoggerFromContext(ctx) if err != nil { return nil, fmt.Errorf("error getting logger: %s", err) } logger.DebugContext(ctx, "executing `%s` tool query: %s", kind, sql) results, err := t.Pool.QueryContext(ctx, sql) if err != nil { return nil, fmt.Errorf("unable to execute query: %w", err) } defer results.Close() cols, err := results.Columns() if err != nil { return nil, fmt.Errorf("unable to retrieve rows column name: %w", err) } // create an array of values for each column, which can be re-used to scan each row rawValues := make([]any, len(cols)) values := make([]any, len(cols)) for i := range rawValues { values[i] = &rawValues[i] } colTypes, err := results.ColumnTypes() if err != nil { return nil, fmt.Errorf("unable to get column types: %w", err) } var out []any for results.Next() { err := results.Scan(values...) if err != nil { return nil, fmt.Errorf("unable to parse row: %w", err) } vMap := make(map[string]any) for i, name := range cols { val := rawValues[i] if val == nil { vMap[name] = nil continue } vMap[name], err = mysqlcommon.ConvertToType(colTypes[i], val) if err != nil { return nil, fmt.Errorf("errors encountered when converting values: %w", err) } } out = append(out, vMap) } if err := results.Err(); err != nil { return nil, fmt.Errorf("errors encountered during row iteration: %w", err) } return out, nil } func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { return tools.ParseParams(t.Parameters, data, claims) } func (t Tool) Manifest() tools.Manifest { return t.manifest } func (t Tool) McpManifest() tools.McpManifest { return t.mcpManifest } func (t Tool) Authorized(verifiedAuthServices []string) bool { return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices) } func (t Tool) RequiresClientAuthorization() bool { return false } ``` -------------------------------------------------------------------------------- /internal/tools/looker/lookergetlooks/lookergetlooks.go: -------------------------------------------------------------------------------- ```go // Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package lookergetlooks import ( "context" "fmt" yaml "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/sources" lookersrc "github.com/googleapis/genai-toolbox/internal/sources/looker" "github.com/googleapis/genai-toolbox/internal/tools" "github.com/googleapis/genai-toolbox/internal/tools/looker/lookercommon" "github.com/googleapis/genai-toolbox/internal/util" "github.com/looker-open-source/sdk-codegen/go/rtl" v4 "github.com/looker-open-source/sdk-codegen/go/sdk/v4" ) const kind string = "looker-get-looks" func init() { if !tools.Register(kind, newConfig) { panic(fmt.Sprintf("tool kind %q already registered", kind)) } } func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) { actual := Config{Name: name} if err := decoder.DecodeContext(ctx, &actual); err != nil { return nil, err } return actual, nil } type Config struct { Name string `yaml:"name" validate:"required"` Kind string `yaml:"kind" validate:"required"` Source string `yaml:"source" validate:"required"` Description string `yaml:"description" validate:"required"` AuthRequired []string `yaml:"authRequired"` } // validate interface var _ tools.ToolConfig = Config{} func (cfg Config) ToolConfigKind() string { return kind } func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { // verify source exists rawS, ok := srcs[cfg.Source] if !ok { return nil, fmt.Errorf("no source named %q configured", cfg.Source) } // verify the source is compatible s, ok := rawS.(*lookersrc.Source) if !ok { return nil, fmt.Errorf("invalid source for %q tool: source kind must be `looker`", kind) } titleParameter := tools.NewStringParameterWithDefault("title", "", "The title of the look.") descParameter := tools.NewStringParameterWithDefault("desc", "", "The description of the look.") limitParameter := tools.NewIntParameterWithDefault("limit", 100, "The number of looks to fetch. Default 100") offsetParameter := tools.NewIntParameterWithDefault("offset", 0, "The number of looks to skip before fetching. Default 0") parameters := tools.Parameters{ titleParameter, descParameter, limitParameter, offsetParameter, } mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters) // finish tool setup return Tool{ Name: cfg.Name, Kind: kind, Parameters: parameters, AuthRequired: cfg.AuthRequired, UseClientOAuth: s.UseClientOAuth, Client: s.Client, ApiSettings: s.ApiSettings, manifest: tools.Manifest{ Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired, }, mcpManifest: mcpManifest, }, nil } // validate interface var _ tools.Tool = Tool{} type Tool struct { Name string `yaml:"name"` Kind string `yaml:"kind"` UseClientOAuth bool Client *v4.LookerSDK ApiSettings *rtl.ApiSettings AuthRequired []string `yaml:"authRequired"` Parameters tools.Parameters `yaml:"parameters"` manifest tools.Manifest mcpManifest tools.McpManifest } func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { logger, err := util.LoggerFromContext(ctx) if err != nil { return nil, fmt.Errorf("unable to get logger from ctx: %s", err) } paramsMap := params.AsMap() title := paramsMap["title"].(string) title_ptr := &title if *title_ptr == "" { title_ptr = nil } desc := paramsMap["desc"].(string) desc_ptr := &desc if *desc_ptr == "" { desc_ptr = nil } limit := int64(paramsMap["limit"].(int)) offset := int64(paramsMap["offset"].(int)) sdk, err := lookercommon.GetLookerSDK(t.UseClientOAuth, t.ApiSettings, t.Client, accessToken) if err != nil { return nil, fmt.Errorf("error getting sdk: %w", err) } req := v4.RequestSearchLooks{ Title: title_ptr, Description: desc_ptr, Limit: &limit, Offset: &offset, } resp, err := sdk.SearchLooks(req, t.ApiSettings) if err != nil { return nil, fmt.Errorf("error making get_looks request: %s", err) } var data []any for _, v := range resp { logger.DebugContext(ctx, "Got response element of %v\n", v) vMap := make(map[string]any) if v.Id != nil { vMap["id"] = *v.Id } if v.Title != nil { vMap["title"] = *v.Title } if v.Description != nil { vMap["description"] = *v.Description } vMap["model_id"] = *v.Model.Id logger.DebugContext(ctx, "Converted to %v\n", vMap) data = append(data, vMap) } logger.DebugContext(ctx, "data = ", data) return data, nil } func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { return tools.ParseParams(t.Parameters, data, claims) } func (t Tool) Manifest() tools.Manifest { return t.manifest } func (t Tool) McpManifest() tools.McpManifest { return t.mcpManifest } func (t Tool) Authorized(verifiedAuthServices []string) bool { return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices) } func (t Tool) RequiresClientAuthorization() bool { return t.UseClientOAuth } ``` -------------------------------------------------------------------------------- /internal/sources/cloudsqlmysql/cloud_sql_mysql_test.go: -------------------------------------------------------------------------------- ```go // Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cloudsqlmysql_test import ( "testing" yaml "github.com/goccy/go-yaml" "github.com/google/go-cmp/cmp" "github.com/googleapis/genai-toolbox/internal/server" "github.com/googleapis/genai-toolbox/internal/sources/cloudsqlmysql" "github.com/googleapis/genai-toolbox/internal/testutils" ) func TestParseFromYamlCloudSQLMySQL(t *testing.T) { tcs := []struct { desc string in string want server.SourceConfigs }{ { desc: "basic example", in: ` sources: my-mysql-instance: kind: cloud-sql-mysql project: my-project region: my-region instance: my-instance database: my_db user: my_user password: my_pass `, want: server.SourceConfigs{ "my-mysql-instance": cloudsqlmysql.Config{ Name: "my-mysql-instance", Kind: cloudsqlmysql.SourceKind, Project: "my-project", Region: "my-region", Instance: "my-instance", IPType: "public", Database: "my_db", User: "my_user", Password: "my_pass", }, }, }, { desc: "public ipType", in: ` sources: my-mysql-instance: kind: cloud-sql-mysql project: my-project region: my-region instance: my-instance ipType: Public database: my_db user: my_user password: my_pass `, want: server.SourceConfigs{ "my-mysql-instance": cloudsqlmysql.Config{ Name: "my-mysql-instance", Kind: cloudsqlmysql.SourceKind, Project: "my-project", Region: "my-region", Instance: "my-instance", IPType: "public", Database: "my_db", User: "my_user", Password: "my_pass", }, }, }, { desc: "private ipType", in: ` sources: my-mysql-instance: kind: cloud-sql-mysql project: my-project region: my-region instance: my-instance ipType: private database: my_db user: my_user password: my_pass `, want: server.SourceConfigs{ "my-mysql-instance": cloudsqlmysql.Config{ Name: "my-mysql-instance", Kind: cloudsqlmysql.SourceKind, Project: "my-project", Region: "my-region", Instance: "my-instance", IPType: "private", Database: "my_db", User: "my_user", Password: "my_pass", }, }, }, { desc: "psc ipType", in: ` sources: my-mysql-instance: kind: cloud-sql-mysql project: my-project region: my-region instance: my-instance ipType: psc database: my_db user: my_user password: my_pass `, want: server.SourceConfigs{ "my-mysql-instance": cloudsqlmysql.Config{ Name: "my-mysql-instance", Kind: cloudsqlmysql.SourceKind, Project: "my-project", Region: "my-region", Instance: "my-instance", IPType: "psc", Database: "my_db", User: "my_user", Password: "my_pass", }, }, }, } for _, tc := range tcs { t.Run(tc.desc, func(t *testing.T) { got := struct { Sources server.SourceConfigs `yaml:"sources"` }{} // Parse contents err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got) if err != nil { t.Fatalf("unable to unmarshal: %s", err) } if !cmp.Equal(tc.want, got.Sources) { t.Fatalf("incorrect parse: want %v, got %v", tc.want, got.Sources) } }) } } func TestFailParseFromYaml(t *testing.T) { tcs := []struct { desc string in string err string }{ { desc: "invalid ipType", in: ` sources: my-mysql-instance: kind: cloud-sql-mysql project: my-project region: my-region instance: my-instance ipType: fail database: my_db user: my_user password: my_pass `, err: "unable to parse source \"my-mysql-instance\" as \"cloud-sql-mysql\": ipType invalid: must be one of \"public\", \"private\", or \"psc\"", }, { desc: "extra field", in: ` sources: my-mysql-instance: kind: cloud-sql-mysql project: my-project region: my-region instance: my-instance database: my_db user: my_user password: my_pass foo: bar `, err: "unable to parse source \"my-mysql-instance\" as \"cloud-sql-mysql\": [2:1] unknown field \"foo\"\n 1 | database: my_db\n> 2 | foo: bar\n ^\n 3 | instance: my-instance\n 4 | kind: cloud-sql-mysql\n 5 | password: my_pass\n 6 | ", }, { desc: "missing required field", in: ` sources: my-mysql-instance: kind: cloud-sql-mysql region: my-region instance: my-instance database: my_db user: my_user password: my_pass `, err: "unable to parse source \"my-mysql-instance\" as \"cloud-sql-mysql\": Key: 'Config.Project' Error:Field validation for 'Project' failed on the 'required' tag", }, } for _, tc := range tcs { t.Run(tc.desc, func(t *testing.T) { got := struct { Sources server.SourceConfigs `yaml:"sources"` }{} // Parse contents err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got) if err == nil { t.Fatalf("expect parsing to fail") } errStr := err.Error() if errStr != tc.err { t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err) } }) } } ``` -------------------------------------------------------------------------------- /internal/tools/sqlite/sqliteexecutesql/sqliteexecutesql.go: -------------------------------------------------------------------------------- ```go // Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package sqliteexecutesql import ( "context" "database/sql" "encoding/json" "fmt" yaml "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/sources" "github.com/googleapis/genai-toolbox/internal/sources/sqlite" "github.com/googleapis/genai-toolbox/internal/tools" "github.com/googleapis/genai-toolbox/internal/util" ) const kind string = "sqlite-execute-sql" func init() { if !tools.Register(kind, newConfig) { panic(fmt.Sprintf("tool kind %q already registered", kind)) } } func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) { actual := Config{Name: name} if err := decoder.DecodeContext(ctx, &actual); err != nil { return nil, err } return actual, nil } type compatibleSource interface { SQLiteDB() *sql.DB } // validate compatible sources are still compatible var _ compatibleSource = &sqlite.Source{} var compatibleSources = [...]string{sqlite.SourceKind} type Config struct { Name string `yaml:"name" validate:"required"` Kind string `yaml:"kind" validate:"required"` Source string `yaml:"source" validate:"required"` Description string `yaml:"description" validate:"required"` AuthRequired []string `yaml:"authRequired"` } // validate interface var _ tools.ToolConfig = Config{} func (cfg Config) ToolConfigKind() string { return kind } func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { // verify source exists rawS, ok := srcs[cfg.Source] if !ok { return nil, fmt.Errorf("no source named %q configured", cfg.Source) } // verify the source is compatible s, ok := rawS.(compatibleSource) if !ok { return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources) } sqlParameter := tools.NewStringParameter("sql", "The sql to execute.") parameters := tools.Parameters{sqlParameter} mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters) // finish tool setup t := Tool{ Name: cfg.Name, Kind: kind, Parameters: parameters, AuthRequired: cfg.AuthRequired, DB: s.SQLiteDB(), manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired}, mcpManifest: mcpManifest, } return t, nil } // validate interface var _ tools.Tool = Tool{} type Tool struct { Name string `yaml:"name"` Kind string `yaml:"kind"` AuthRequired []string `yaml:"authRequired"` Parameters tools.Parameters `yaml:"parameters"` DB *sql.DB manifest tools.Manifest mcpManifest tools.McpManifest } func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { sql, ok := params.AsMap()["sql"].(string) if !ok { return nil, fmt.Errorf("missing or invalid 'sql' parameter") } if sql == "" { return nil, fmt.Errorf("sql parameter cannot be empty") } // Log the query executed for debugging. logger, err := util.LoggerFromContext(ctx) if err != nil { return nil, fmt.Errorf("error getting logger: %s", err) } logger.DebugContext(ctx, "executing `%s` tool query: %s", kind, sql) results, err := t.DB.QueryContext(ctx, sql) if err != nil { return nil, fmt.Errorf("unable to execute query: %w", err) } cols, err := results.Columns() if err != nil { return nil, fmt.Errorf("unable to retrieve rows column name: %w", err) } // The sqlite driver does not support ColumnTypes, so we can't get the // underlying database type of the columns. We'll have to rely on the // generic `any` type and then handle the JSON data separately. // create an array of values for each column, which can be re-used to scan each row rawValues := make([]any, len(cols)) values := make([]any, len(cols)) for i := range rawValues { values[i] = &rawValues[i] } defer results.Close() var out []any for results.Next() { err := results.Scan(values...) if err != nil { return nil, fmt.Errorf("unable to parse row: %w", err) } vMap := make(map[string]any) for i, name := range cols { val := rawValues[i] if val == nil { vMap[name] = nil continue } // Handle JSON data if jsonString, ok := val.(string); ok { var unmarshaledData any if json.Unmarshal([]byte(jsonString), &unmarshaledData) == nil { vMap[name] = unmarshaledData continue } } vMap[name] = val } out = append(out, vMap) } if err := results.Err(); err != nil { return nil, fmt.Errorf("errors encountered during row iteration: %w", err) } if len(out) == 0 { return nil, nil } return out, nil } func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { return tools.ParseParams(t.Parameters, data, claims) } func (t Tool) Manifest() tools.Manifest { return t.manifest } func (t Tool) McpManifest() tools.McpManifest { return t.mcpManifest } func (t Tool) Authorized(verifiedAuthServices []string) bool { return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices) } func (t Tool) RequiresClientAuthorization() bool { return false } ``` -------------------------------------------------------------------------------- /internal/tools/cloudsql/cloudsqlcreateusers/cloudsqlcreateusers.go: -------------------------------------------------------------------------------- ```go // Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cloudsqlcreateusers import ( "context" "fmt" "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/sources" "github.com/googleapis/genai-toolbox/internal/sources/cloudsqladmin" "github.com/googleapis/genai-toolbox/internal/tools" sqladmin "google.golang.org/api/sqladmin/v1" ) const kind string = "cloud-sql-create-users" func init() { if !tools.Register(kind, newConfig) { panic(fmt.Sprintf("tool kind %q already registered", kind)) } } func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) { actual := Config{Name: name} if err := decoder.DecodeContext(ctx, &actual); err != nil { return nil, err } return actual, nil } // Config defines the configuration for the create-user tool. type Config struct { Name string `yaml:"name" validate:"required"` Kind string `yaml:"kind" validate:"required"` Source string `yaml:"source" validate:"required"` Description string `yaml:"description"` AuthRequired []string `yaml:"authRequired"` } // validate interface var _ tools.ToolConfig = Config{} // ToolConfigKind returns the kind of the tool. func (cfg Config) ToolConfigKind() string { return kind } // Initialize initializes the tool from the configuration. func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { rawS, ok := srcs[cfg.Source] if !ok { return nil, fmt.Errorf("no source named %q configured", cfg.Source) } s, ok := rawS.(*cloudsqladmin.Source) if !ok { return nil, fmt.Errorf("invalid source for %q tool: source kind must be `cloud-sql-admin`", kind) } allParameters := tools.Parameters{ tools.NewStringParameter("project", "The project ID"), tools.NewStringParameter("instance", "The ID of the instance where the user will be created."), tools.NewStringParameter("name", "The name for the new user. Must be unique within the instance."), tools.NewStringParameterWithRequired("password", "A secure password for the new user. Not required for IAM users.", false), tools.NewBooleanParameter("iamUser", "Set to true to create a Cloud IAM user."), } paramManifest := allParameters.Manifest() description := cfg.Description if description == "" { description = "Creates a new user in a Cloud SQL instance. Both built-in and IAM users are supported. IAM users require an email account as the user name. IAM is the more secure and recommended way to manage users. The agent should always ask the user what type of user they want to create. For more information, see https://cloud.google.com/sql/docs/postgres/add-manage-iam-users" } mcpManifest := tools.GetMcpManifest(cfg.Name, description, cfg.AuthRequired, allParameters) return Tool{ Name: cfg.Name, Kind: kind, AuthRequired: cfg.AuthRequired, Source: s, AllParams: allParameters, manifest: tools.Manifest{Description: cfg.Description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired}, mcpManifest: mcpManifest, }, nil } // Tool represents the create-user tool. type Tool struct { Name string `yaml:"name"` Kind string `yaml:"kind"` Description string `yaml:"description"` AuthRequired []string `yaml:"authRequired"` Source *cloudsqladmin.Source AllParams tools.Parameters `yaml:"allParams"` manifest tools.Manifest mcpManifest tools.McpManifest } // Invoke executes the tool's logic. func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { paramsMap := params.AsMap() project, ok := paramsMap["project"].(string) if !ok { return nil, fmt.Errorf("missing 'project' parameter") } instance, ok := paramsMap["instance"].(string) if !ok { return nil, fmt.Errorf("missing 'instance' parameter") } name, ok := paramsMap["name"].(string) if !ok { return nil, fmt.Errorf("missing 'name' parameter") } iamUser, _ := paramsMap["iamUser"].(bool) user := sqladmin.User{ Name: name, } if iamUser { user.Type = "CLOUD_IAM_USER" } else { user.Type = "BUILT_IN" password, ok := paramsMap["password"].(string) if !ok || password == "" { return nil, fmt.Errorf("missing 'password' parameter for non-IAM user") } user.Password = password } service, err := t.Source.GetService(ctx, string(accessToken)) if err != nil { return nil, err } resp, err := service.Users.Insert(project, instance, &user).Do() if err != nil { return nil, fmt.Errorf("error creating user: %w", err) } return resp, nil } // ParseParams parses the parameters for the tool. func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { return tools.ParseParams(t.AllParams, data, claims) } // Manifest returns the tool's manifest. func (t Tool) Manifest() tools.Manifest { return t.manifest } // McpManifest returns the tool's MCP manifest. func (t Tool) McpManifest() tools.McpManifest { return t.mcpManifest } // Authorized checks if the tool is authorized. func (t Tool) Authorized(verifiedAuthServices []string) bool { return true } func (t Tool) RequiresClientAuthorization() bool { return t.Source.UseClientAuthorization() } ``` -------------------------------------------------------------------------------- /internal/server/mcp/v20250326/types.go: -------------------------------------------------------------------------------- ```go // Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package v20250326 import ( "github.com/googleapis/genai-toolbox/internal/server/mcp/jsonrpc" "github.com/googleapis/genai-toolbox/internal/tools" ) // SERVER_NAME is the server name used in Implementation. const SERVER_NAME = "Toolbox" // PROTOCOL_VERSION is the version of the MCP protocol in this package. const PROTOCOL_VERSION = "2025-03-26" // methods that are supported. const ( PING = "ping" TOOLS_LIST = "tools/list" TOOLS_CALL = "tools/call" ) /* Empty result */ // EmptyResult represents a response that indicates success but carries no data. type EmptyResult jsonrpc.Result /* Pagination */ // Cursor is an opaque token used to represent a cursor for pagination. type Cursor string type PaginatedRequest struct { jsonrpc.Request Params struct { // An opaque token representing the current pagination position. // If provided, the server should return results starting after this cursor. Cursor Cursor `json:"cursor,omitempty"` } `json:"params,omitempty"` } type PaginatedResult struct { jsonrpc.Result // An opaque token representing the pagination position after the last returned result. // If present, there may be more results available. NextCursor Cursor `json:"nextCursor,omitempty"` } /* Tools */ // Sent from the client to request a list of tools the server has. type ListToolsRequest struct { PaginatedRequest } // The server's response to a tools/list request from the client. type ListToolsResult struct { PaginatedResult Tools []tools.McpManifest `json:"tools"` } // Used by the client to invoke a tool provided by the server. type CallToolRequest struct { jsonrpc.Request Params struct { Name string `json:"name"` Arguments map[string]any `json:"arguments,omitempty"` } `json:"params,omitempty"` } // The sender or recipient of messages and data in a conversation. type Role string const ( RoleUser Role = "user" RoleAssistant Role = "assistant" ) // Base for objects that include optional annotations for the client. // The client can use annotations to inform how objects are used or displayed type Annotated struct { Annotations *struct { // Describes who the intended customer of this object or data is. // It can include multiple entries to indicate content useful for multiple // audiences (e.g., `["user", "assistant"]`). Audience []Role `json:"audience,omitempty"` // Describes how important this data is for operating the server. // // A value of 1 means "most important," and indicates that the data is // effectively required, while 0 means "least important," and indicates that // the data is entirely optional. // // @TJS-type number // @minimum 0 // @maximum 1 Priority float64 `json:"priority,omitempty"` } `json:"annotations,omitempty"` } // TextContent represents text provided to or from an LLM. type TextContent struct { Annotated Type string `json:"type"` // The text content of the message. Text string `json:"text"` } // The server's response to a tool call. // // Any errors that originate from the tool SHOULD be reported inside the result // object, with `isError` set to true, _not_ as an MCP protocol-level error // response. Otherwise, the LLM would not be able to see that an error occurred // and self-correct. // // However, any errors in _finding_ the tool, an error indicating that the // server does not support tool calls, or any other exceptional conditions, // should be reported as an MCP error response. type CallToolResult struct { jsonrpc.Result // Could be either a TextContent, ImageContent, or EmbeddedResources // For Toolbox, we will only be sending TextContent Content []TextContent `json:"content"` // Whether the tool call ended in an error. // If not set, this is assumed to be false (the call was successful). IsError bool `json:"isError,omitempty"` } // Additional properties describing a Tool to clients. // // NOTE: all properties in ToolAnnotations are **hints**. // They are not guaranteed to provide a faithful description of // tool behavior (including descriptive properties like `title`). // // Clients should never make tool use decisions based on ToolAnnotations // received from untrusted servers. type ToolAnnotations struct { // A human-readable title for the tool. Title string `json:"title,omitempty"` // If true, the tool does not modify its environment. // Default: false ReadOnlyHint bool `json:"readOnlyHint,omitempty"` // If true, the tool may perform destructive updates to its environment. // If false, the tool performs only additive updates. // (This property is meaningful only when `readOnlyHint == false`) // Default: true DestructiveHint bool `json:"destructiveHint,omitempty"` // If true, calling the tool repeatedly with the same arguments // will have no additional effect on the its environment. // (This property is meaningful only when `readOnlyHint == false`) // Default: false IdempotentHint bool `json:"idempotentHint,omitempty"` // If true, this tool may interact with an "open world" of external // entities. If false, the tool's domain of interaction is closed. // For example, the world of a web search tool is open, whereas that // of a memory tool is not. // Default: true OpenWorldHint bool `json:"openWorldHint,omitempty"` } ``` -------------------------------------------------------------------------------- /internal/tools/looker/lookergetdashboards/lookergetdashboards.go: -------------------------------------------------------------------------------- ```go // Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package lookergetdashboards import ( "context" "fmt" yaml "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/sources" lookersrc "github.com/googleapis/genai-toolbox/internal/sources/looker" "github.com/googleapis/genai-toolbox/internal/tools" "github.com/googleapis/genai-toolbox/internal/tools/looker/lookercommon" "github.com/googleapis/genai-toolbox/internal/util" "github.com/looker-open-source/sdk-codegen/go/rtl" v4 "github.com/looker-open-source/sdk-codegen/go/sdk/v4" ) const kind string = "looker-get-dashboards" func init() { if !tools.Register(kind, newConfig) { panic(fmt.Sprintf("tool kind %q already registered", kind)) } } func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) { actual := Config{Name: name} if err := decoder.DecodeContext(ctx, &actual); err != nil { return nil, err } return actual, nil } type Config struct { Name string `yaml:"name" validate:"required"` Kind string `yaml:"kind" validate:"required"` Source string `yaml:"source" validate:"required"` Description string `yaml:"description" validate:"required"` AuthRequired []string `yaml:"authRequired"` } // validate interface var _ tools.ToolConfig = Config{} func (cfg Config) ToolConfigKind() string { return kind } func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { // verify source exists rawS, ok := srcs[cfg.Source] if !ok { return nil, fmt.Errorf("no source named %q configured", cfg.Source) } // verify the source is compatible s, ok := rawS.(*lookersrc.Source) if !ok { return nil, fmt.Errorf("invalid source for %q tool: source kind must be `looker`", kind) } titleParameter := tools.NewStringParameterWithDefault("title", "", "The title of the dashboard.") descParameter := tools.NewStringParameterWithDefault("desc", "", "The description of the dashboard.") limitParameter := tools.NewIntParameterWithDefault("limit", 100, "The number of dashboards to fetch. Default 100") offsetParameter := tools.NewIntParameterWithDefault("offset", 0, "The number of dashboards to skip before fetching. Default 0") parameters := tools.Parameters{ titleParameter, descParameter, limitParameter, offsetParameter, } mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters) // finish tool setup return Tool{ Name: cfg.Name, Kind: kind, Parameters: parameters, AuthRequired: cfg.AuthRequired, UseClientOAuth: s.UseClientOAuth, Client: s.Client, ApiSettings: s.ApiSettings, manifest: tools.Manifest{ Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired, }, mcpManifest: mcpManifest, }, nil } // validate interface var _ tools.Tool = Tool{} type Tool struct { Name string `yaml:"name"` Kind string `yaml:"kind"` UseClientOAuth bool Client *v4.LookerSDK ApiSettings *rtl.ApiSettings AuthRequired []string `yaml:"authRequired"` Parameters tools.Parameters `yaml:"parameters"` manifest tools.Manifest mcpManifest tools.McpManifest } func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { logger, err := util.LoggerFromContext(ctx) if err != nil { return nil, fmt.Errorf("unable to get logger from ctx: %s", err) } paramsMap := params.AsMap() title := paramsMap["title"].(string) title_ptr := &title if *title_ptr == "" { title_ptr = nil } desc := paramsMap["desc"].(string) desc_ptr := &desc if *desc_ptr == "" { desc_ptr = nil } limit := int64(paramsMap["limit"].(int)) offset := int64(paramsMap["offset"].(int)) sdk, err := lookercommon.GetLookerSDK(t.UseClientOAuth, t.ApiSettings, t.Client, accessToken) if err != nil { return nil, fmt.Errorf("error getting sdk: %w", err) } req := v4.RequestSearchDashboards{ Title: title_ptr, Description: desc_ptr, Limit: &limit, Offset: &offset, } logger.ErrorContext(ctx, "Making request %v", req) resp, err := sdk.SearchDashboards(req, t.ApiSettings) if err != nil { return nil, fmt.Errorf("error making get_dashboards request: %s", err) } logger.ErrorContext(ctx, "Got response %v", resp) var data []any for _, v := range resp { logger.DebugContext(ctx, "Got response element of %v\n", v) vMap := make(map[string]any) if v.Id != nil { vMap["id"] = *v.Id } if v.Title != nil { vMap["title"] = *v.Title } if v.Description != nil { vMap["description"] = *v.Description } logger.DebugContext(ctx, "Converted to %v\n", vMap) data = append(data, vMap) } logger.DebugContext(ctx, "data = ", data) return data, nil } func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { return tools.ParseParams(t.Parameters, data, claims) } func (t Tool) Manifest() tools.Manifest { return t.manifest } func (t Tool) McpManifest() tools.McpManifest { return t.mcpManifest } func (t Tool) Authorized(verifiedAuthServices []string) bool { return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices) } func (t Tool) RequiresClientAuthorization() bool { return t.UseClientOAuth } ```