This is page 10 of 35. Use http://codebase.md/googleapis/genai-toolbox?page={x} to view the full context. # Directory Structure ``` ├── .ci │ ├── continuous.release.cloudbuild.yaml │ ├── generate_release_table.sh │ ├── integration.cloudbuild.yaml │ ├── quickstart_test │ │ ├── go.integration.cloudbuild.yaml │ │ ├── js.integration.cloudbuild.yaml │ │ ├── py.integration.cloudbuild.yaml │ │ ├── run_go_tests.sh │ │ ├── run_js_tests.sh │ │ ├── run_py_tests.sh │ │ └── setup_hotels_sample.sql │ ├── test_with_coverage.sh │ └── versioned.release.cloudbuild.yaml ├── .github │ ├── auto-label.yaml │ ├── blunderbuss.yml │ ├── CODEOWNERS │ ├── header-checker-lint.yml │ ├── ISSUE_TEMPLATE │ │ ├── bug_report.yml │ │ ├── config.yml │ │ ├── feature_request.yml │ │ └── question.yml │ ├── label-sync.yml │ ├── labels.yaml │ ├── PULL_REQUEST_TEMPLATE.md │ ├── release-please.yml │ ├── renovate.json5 │ ├── sync-repo-settings.yaml │ └── workflows │ ├── cloud_build_failure_reporter.yml │ ├── deploy_dev_docs.yaml │ ├── deploy_previous_version_docs.yaml │ ├── deploy_versioned_docs.yaml │ ├── docs_deploy.yaml │ ├── docs_preview_clean.yaml │ ├── docs_preview_deploy.yaml │ ├── lint.yaml │ ├── schedule_reporter.yml │ ├── sync-labels.yaml │ └── tests.yaml ├── .gitignore ├── .gitmodules ├── .golangci.yaml ├── .hugo │ ├── archetypes │ │ └── default.md │ ├── assets │ │ ├── icons │ │ │ └── logo.svg │ │ └── scss │ │ ├── _styles_project.scss │ │ └── _variables_project.scss │ ├── go.mod │ ├── go.sum │ ├── hugo.toml │ ├── layouts │ │ ├── _default │ │ │ └── home.releases.releases │ │ ├── index.llms-full.txt │ │ ├── index.llms.txt │ │ ├── partials │ │ │ ├── hooks │ │ │ │ └── head-end.html │ │ │ ├── navbar-version-selector.html │ │ │ ├── page-meta-links.html │ │ │ └── td │ │ │ └── render-heading.html │ │ ├── robot.txt │ │ └── shortcodes │ │ ├── include.html │ │ ├── ipynb.html │ │ └── regionInclude.html │ ├── package-lock.json │ ├── package.json │ └── static │ ├── favicons │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ └── favicon.ico │ └── js │ └── w3.js ├── CHANGELOG.md ├── cmd │ ├── options_test.go │ ├── options.go │ ├── root_test.go │ ├── root.go │ └── version.txt ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── DEVELOPER.md ├── Dockerfile ├── docs │ └── en │ ├── _index.md │ ├── about │ │ ├── _index.md │ │ └── faq.md │ ├── concepts │ │ ├── _index.md │ │ └── telemetry │ │ ├── index.md │ │ ├── telemetry_flow.png │ │ └── telemetry_traces.png │ ├── getting-started │ │ ├── _index.md │ │ ├── colab_quickstart.ipynb │ │ ├── configure.md │ │ ├── introduction │ │ │ ├── _index.md │ │ │ └── architecture.png │ │ ├── local_quickstart_go.md │ │ ├── local_quickstart_js.md │ │ ├── local_quickstart.md │ │ ├── mcp_quickstart │ │ │ ├── _index.md │ │ │ ├── inspector_tools.png │ │ │ └── inspector.png │ │ └── quickstart │ │ ├── go │ │ │ ├── genAI │ │ │ │ ├── go.mod │ │ │ │ ├── go.sum │ │ │ │ └── quickstart.go │ │ │ ├── genkit │ │ │ │ ├── go.mod │ │ │ │ ├── go.sum │ │ │ │ └── quickstart.go │ │ │ ├── langchain │ │ │ │ ├── go.mod │ │ │ │ ├── go.sum │ │ │ │ └── quickstart.go │ │ │ ├── openAI │ │ │ │ ├── go.mod │ │ │ │ ├── go.sum │ │ │ │ └── quickstart.go │ │ │ └── quickstart_test.go │ │ ├── golden.txt │ │ ├── js │ │ │ ├── genAI │ │ │ │ ├── package-lock.json │ │ │ │ ├── package.json │ │ │ │ └── quickstart.js │ │ │ ├── genkit │ │ │ │ ├── package-lock.json │ │ │ │ ├── package.json │ │ │ │ └── quickstart.js │ │ │ ├── langchain │ │ │ │ ├── package-lock.json │ │ │ │ ├── package.json │ │ │ │ └── quickstart.js │ │ │ ├── llamaindex │ │ │ │ ├── package-lock.json │ │ │ │ ├── package.json │ │ │ │ └── quickstart.js │ │ │ └── quickstart.test.js │ │ ├── python │ │ │ ├── __init__.py │ │ │ ├── adk │ │ │ │ ├── quickstart.py │ │ │ │ └── requirements.txt │ │ │ ├── core │ │ │ │ ├── quickstart.py │ │ │ │ └── requirements.txt │ │ │ ├── langchain │ │ │ │ ├── quickstart.py │ │ │ │ └── requirements.txt │ │ │ ├── llamaindex │ │ │ │ ├── quickstart.py │ │ │ │ └── requirements.txt │ │ │ └── quickstart_test.py │ │ └── shared │ │ ├── cloud_setup.md │ │ ├── configure_toolbox.md │ │ └── database_setup.md │ ├── how-to │ │ ├── _index.md │ │ ├── connect_via_geminicli.md │ │ ├── connect_via_mcp.md │ │ ├── connect-ide │ │ │ ├── _index.md │ │ │ ├── alloydb_pg_admin_mcp.md │ │ │ ├── alloydb_pg_mcp.md │ │ │ ├── bigquery_mcp.md │ │ │ ├── cloud_sql_mssql_admin_mcp.md │ │ │ ├── cloud_sql_mssql_mcp.md │ │ │ ├── cloud_sql_mysql_admin_mcp.md │ │ │ ├── cloud_sql_mysql_mcp.md │ │ │ ├── cloud_sql_pg_admin_mcp.md │ │ │ ├── cloud_sql_pg_mcp.md │ │ │ ├── firestore_mcp.md │ │ │ ├── looker_mcp.md │ │ │ ├── mssql_mcp.md │ │ │ ├── mysql_mcp.md │ │ │ ├── neo4j_mcp.md │ │ │ ├── postgres_mcp.md │ │ │ ├── spanner_mcp.md │ │ │ └── sqlite_mcp.md │ │ ├── deploy_docker.md │ │ ├── deploy_gke.md │ │ ├── deploy_toolbox.md │ │ ├── export_telemetry.md │ │ └── toolbox-ui │ │ ├── edit-headers.gif │ │ ├── edit-headers.png │ │ ├── index.md │ │ ├── optional-param-checked.png │ │ ├── optional-param-unchecked.png │ │ ├── run-tool.gif │ │ ├── tools.png │ │ └── toolsets.png │ ├── reference │ │ ├── _index.md │ │ ├── cli.md │ │ └── prebuilt-tools.md │ ├── resources │ │ ├── _index.md │ │ ├── authServices │ │ │ ├── _index.md │ │ │ └── google.md │ │ ├── sources │ │ │ ├── _index.md │ │ │ ├── alloydb-admin.md │ │ │ ├── alloydb-pg.md │ │ │ ├── bigquery.md │ │ │ ├── bigtable.md │ │ │ ├── cassandra.md │ │ │ ├── clickhouse.md │ │ │ ├── cloud-monitoring.md │ │ │ ├── cloud-sql-admin.md │ │ │ ├── cloud-sql-mssql.md │ │ │ ├── cloud-sql-mysql.md │ │ │ ├── cloud-sql-pg.md │ │ │ ├── couchbase.md │ │ │ ├── dataplex.md │ │ │ ├── dgraph.md │ │ │ ├── firebird.md │ │ │ ├── firestore.md │ │ │ ├── http.md │ │ │ ├── looker.md │ │ │ ├── mongodb.md │ │ │ ├── mssql.md │ │ │ ├── mysql.md │ │ │ ├── neo4j.md │ │ │ ├── oceanbase.md │ │ │ ├── oracle.md │ │ │ ├── postgres.md │ │ │ ├── redis.md │ │ │ ├── spanner.md │ │ │ ├── sqlite.md │ │ │ ├── tidb.md │ │ │ ├── trino.md │ │ │ ├── valkey.md │ │ │ └── yugabytedb.md │ │ └── tools │ │ ├── _index.md │ │ ├── alloydb │ │ │ ├── _index.md │ │ │ ├── alloydb-create-cluster.md │ │ │ ├── alloydb-create-instance.md │ │ │ ├── alloydb-create-user.md │ │ │ ├── alloydb-get-cluster.md │ │ │ ├── alloydb-get-instance.md │ │ │ ├── alloydb-get-user.md │ │ │ ├── alloydb-list-clusters.md │ │ │ ├── alloydb-list-instances.md │ │ │ ├── alloydb-list-users.md │ │ │ └── alloydb-wait-for-operation.md │ │ ├── alloydbainl │ │ │ ├── _index.md │ │ │ └── alloydb-ai-nl.md │ │ ├── bigquery │ │ │ ├── _index.md │ │ │ ├── bigquery-analyze-contribution.md │ │ │ ├── bigquery-conversational-analytics.md │ │ │ ├── bigquery-execute-sql.md │ │ │ ├── bigquery-forecast.md │ │ │ ├── bigquery-get-dataset-info.md │ │ │ ├── bigquery-get-table-info.md │ │ │ ├── bigquery-list-dataset-ids.md │ │ │ ├── bigquery-list-table-ids.md │ │ │ ├── bigquery-search-catalog.md │ │ │ └── bigquery-sql.md │ │ ├── bigtable │ │ │ ├── _index.md │ │ │ └── bigtable-sql.md │ │ ├── cassandra │ │ │ ├── _index.md │ │ │ └── cassandra-cql.md │ │ ├── clickhouse │ │ │ ├── _index.md │ │ │ ├── clickhouse-execute-sql.md │ │ │ ├── clickhouse-list-databases.md │ │ │ ├── clickhouse-list-tables.md │ │ │ └── clickhouse-sql.md │ │ ├── cloudmonitoring │ │ │ ├── _index.md │ │ │ └── cloud-monitoring-query-prometheus.md │ │ ├── cloudsql │ │ │ ├── _index.md │ │ │ ├── cloudsqlcreatedatabase.md │ │ │ ├── cloudsqlcreateusers.md │ │ │ ├── cloudsqlgetinstances.md │ │ │ ├── cloudsqllistdatabases.md │ │ │ ├── cloudsqllistinstances.md │ │ │ ├── cloudsqlmssqlcreateinstance.md │ │ │ ├── cloudsqlmysqlcreateinstance.md │ │ │ ├── cloudsqlpgcreateinstances.md │ │ │ └── cloudsqlwaitforoperation.md │ │ ├── couchbase │ │ │ ├── _index.md │ │ │ └── couchbase-sql.md │ │ ├── dataform │ │ │ ├── _index.md │ │ │ └── dataform-compile-local.md │ │ ├── dataplex │ │ │ ├── _index.md │ │ │ ├── dataplex-lookup-entry.md │ │ │ ├── dataplex-search-aspect-types.md │ │ │ └── dataplex-search-entries.md │ │ ├── dgraph │ │ │ ├── _index.md │ │ │ └── dgraph-dql.md │ │ ├── firebird │ │ │ ├── _index.md │ │ │ ├── firebird-execute-sql.md │ │ │ └── firebird-sql.md │ │ ├── firestore │ │ │ ├── _index.md │ │ │ ├── firestore-add-documents.md │ │ │ ├── firestore-delete-documents.md │ │ │ ├── firestore-get-documents.md │ │ │ ├── firestore-get-rules.md │ │ │ ├── firestore-list-collections.md │ │ │ ├── firestore-query-collection.md │ │ │ ├── firestore-query.md │ │ │ ├── firestore-update-document.md │ │ │ └── firestore-validate-rules.md │ │ ├── http │ │ │ ├── _index.md │ │ │ └── http.md │ │ ├── looker │ │ │ ├── _index.md │ │ │ ├── looker-add-dashboard-element.md │ │ │ ├── looker-conversational-analytics.md │ │ │ ├── looker-create-project-file.md │ │ │ ├── looker-delete-project-file.md │ │ │ ├── looker-dev-mode.md │ │ │ ├── looker-get-dashboards.md │ │ │ ├── looker-get-dimensions.md │ │ │ ├── looker-get-explores.md │ │ │ ├── looker-get-filters.md │ │ │ ├── looker-get-looks.md │ │ │ ├── looker-get-measures.md │ │ │ ├── looker-get-models.md │ │ │ ├── looker-get-parameters.md │ │ │ ├── looker-get-project-file.md │ │ │ ├── looker-get-project-files.md │ │ │ ├── looker-get-projects.md │ │ │ ├── looker-health-analyze.md │ │ │ ├── looker-health-pulse.md │ │ │ ├── looker-health-vacuum.md │ │ │ ├── looker-make-dashboard.md │ │ │ ├── looker-make-look.md │ │ │ ├── looker-query-sql.md │ │ │ ├── looker-query-url.md │ │ │ ├── looker-query.md │ │ │ ├── looker-run-look.md │ │ │ └── looker-update-project-file.md │ │ ├── mongodb │ │ │ ├── _index.md │ │ │ ├── mongodb-aggregate.md │ │ │ ├── mongodb-delete-many.md │ │ │ ├── mongodb-delete-one.md │ │ │ ├── mongodb-find-one.md │ │ │ ├── mongodb-find.md │ │ │ ├── mongodb-insert-many.md │ │ │ ├── mongodb-insert-one.md │ │ │ ├── mongodb-update-many.md │ │ │ └── mongodb-update-one.md │ │ ├── mssql │ │ │ ├── _index.md │ │ │ ├── mssql-execute-sql.md │ │ │ ├── mssql-list-tables.md │ │ │ └── mssql-sql.md │ │ ├── mysql │ │ │ ├── _index.md │ │ │ ├── mysql-execute-sql.md │ │ │ ├── mysql-list-active-queries.md │ │ │ ├── mysql-list-table-fragmentation.md │ │ │ ├── mysql-list-tables-missing-unique-indexes.md │ │ │ ├── mysql-list-tables.md │ │ │ └── mysql-sql.md │ │ ├── neo4j │ │ │ ├── _index.md │ │ │ ├── neo4j-cypher.md │ │ │ ├── neo4j-execute-cypher.md │ │ │ └── neo4j-schema.md │ │ ├── oceanbase │ │ │ ├── _index.md │ │ │ ├── oceanbase-execute-sql.md │ │ │ └── oceanbase-sql.md │ │ ├── oracle │ │ │ ├── _index.md │ │ │ ├── oracle-execute-sql.md │ │ │ └── oracle-sql.md │ │ ├── postgres │ │ │ ├── _index.md │ │ │ ├── postgres-execute-sql.md │ │ │ ├── postgres-list-active-queries.md │ │ │ ├── postgres-list-available-extensions.md │ │ │ ├── postgres-list-installed-extensions.md │ │ │ ├── postgres-list-tables.md │ │ │ └── postgres-sql.md │ │ ├── redis │ │ │ ├── _index.md │ │ │ └── redis.md │ │ ├── spanner │ │ │ ├── _index.md │ │ │ ├── spanner-execute-sql.md │ │ │ ├── spanner-list-tables.md │ │ │ └── spanner-sql.md │ │ ├── sqlite │ │ │ ├── _index.md │ │ │ ├── sqlite-execute-sql.md │ │ │ └── sqlite-sql.md │ │ ├── tidb │ │ │ ├── _index.md │ │ │ ├── tidb-execute-sql.md │ │ │ └── tidb-sql.md │ │ ├── trino │ │ │ ├── _index.md │ │ │ ├── trino-execute-sql.md │ │ │ └── trino-sql.md │ │ ├── utility │ │ │ ├── _index.md │ │ │ └── wait.md │ │ ├── valkey │ │ │ ├── _index.md │ │ │ └── valkey.md │ │ └── yuagbytedb │ │ ├── _index.md │ │ └── yugabytedb-sql.md │ ├── samples │ │ ├── _index.md │ │ ├── alloydb │ │ │ ├── _index.md │ │ │ ├── ai-nl │ │ │ │ ├── alloydb_ai_nl.ipynb │ │ │ │ └── index.md │ │ │ └── mcp_quickstart.md │ │ ├── bigquery │ │ │ ├── _index.md │ │ │ ├── colab_quickstart_bigquery.ipynb │ │ │ ├── local_quickstart.md │ │ │ └── mcp_quickstart │ │ │ ├── _index.md │ │ │ ├── inspector_tools.png │ │ │ └── inspector.png │ │ └── looker │ │ ├── _index.md │ │ ├── looker_gemini_oauth │ │ │ ├── _index.md │ │ │ ├── authenticated.png │ │ │ ├── authorize.png │ │ │ └── registration.png │ │ ├── looker_gemini.md │ │ └── looker_mcp_inspector │ │ ├── _index.md │ │ ├── inspector_tools.png │ │ └── inspector.png │ └── sdks │ ├── _index.md │ ├── go-sdk.md │ ├── js-sdk.md │ └── python-sdk.md ├── gemini-extension.json ├── go.mod ├── go.sum ├── internal │ ├── auth │ │ ├── auth.go │ │ └── google │ │ └── google.go │ ├── log │ │ ├── handler.go │ │ ├── log_test.go │ │ ├── log.go │ │ └── logger.go │ ├── prebuiltconfigs │ │ ├── prebuiltconfigs_test.go │ │ ├── prebuiltconfigs.go │ │ └── tools │ │ ├── alloydb-postgres-admin.yaml │ │ ├── alloydb-postgres-observability.yaml │ │ ├── alloydb-postgres.yaml │ │ ├── bigquery.yaml │ │ ├── clickhouse.yaml │ │ ├── cloud-sql-mssql-admin.yaml │ │ ├── cloud-sql-mssql-observability.yaml │ │ ├── cloud-sql-mssql.yaml │ │ ├── cloud-sql-mysql-admin.yaml │ │ ├── cloud-sql-mysql-observability.yaml │ │ ├── cloud-sql-mysql.yaml │ │ ├── cloud-sql-postgres-admin.yaml │ │ ├── cloud-sql-postgres-observability.yaml │ │ ├── cloud-sql-postgres.yaml │ │ ├── dataplex.yaml │ │ ├── firestore.yaml │ │ ├── looker-conversational-analytics.yaml │ │ ├── looker.yaml │ │ ├── mssql.yaml │ │ ├── mysql.yaml │ │ ├── neo4j.yaml │ │ ├── oceanbase.yaml │ │ ├── postgres.yaml │ │ ├── spanner-postgres.yaml │ │ ├── spanner.yaml │ │ └── sqlite.yaml │ ├── server │ │ ├── api_test.go │ │ ├── api.go │ │ ├── common_test.go │ │ ├── config.go │ │ ├── mcp │ │ │ ├── jsonrpc │ │ │ │ ├── jsonrpc_test.go │ │ │ │ └── jsonrpc.go │ │ │ ├── mcp.go │ │ │ ├── util │ │ │ │ └── lifecycle.go │ │ │ ├── v20241105 │ │ │ │ ├── method.go │ │ │ │ └── types.go │ │ │ ├── v20250326 │ │ │ │ ├── method.go │ │ │ │ └── types.go │ │ │ └── v20250618 │ │ │ ├── method.go │ │ │ └── types.go │ │ ├── mcp_test.go │ │ ├── mcp.go │ │ ├── server_test.go │ │ ├── server.go │ │ ├── static │ │ │ ├── assets │ │ │ │ └── mcptoolboxlogo.png │ │ │ ├── css │ │ │ │ └── style.css │ │ │ ├── index.html │ │ │ ├── js │ │ │ │ ├── auth.js │ │ │ │ ├── loadTools.js │ │ │ │ ├── mainContent.js │ │ │ │ ├── navbar.js │ │ │ │ ├── runTool.js │ │ │ │ ├── toolDisplay.js │ │ │ │ ├── tools.js │ │ │ │ └── toolsets.js │ │ │ ├── tools.html │ │ │ └── toolsets.html │ │ ├── web_test.go │ │ └── web.go │ ├── sources │ │ ├── alloydbadmin │ │ │ ├── alloydbadmin_test.go │ │ │ └── alloydbadmin.go │ │ ├── alloydbpg │ │ │ ├── alloydb_pg_test.go │ │ │ └── alloydb_pg.go │ │ ├── bigquery │ │ │ ├── bigquery_test.go │ │ │ └── bigquery.go │ │ ├── bigtable │ │ │ ├── bigtable_test.go │ │ │ └── bigtable.go │ │ ├── cassandra │ │ │ ├── cassandra_test.go │ │ │ └── cassandra.go │ │ ├── clickhouse │ │ │ ├── clickhouse_test.go │ │ │ └── clickhouse.go │ │ ├── cloudmonitoring │ │ │ ├── cloud_monitoring_test.go │ │ │ └── cloud_monitoring.go │ │ ├── cloudsqladmin │ │ │ ├── cloud_sql_admin_test.go │ │ │ └── cloud_sql_admin.go │ │ ├── cloudsqlmssql │ │ │ ├── cloud_sql_mssql_test.go │ │ │ └── cloud_sql_mssql.go │ │ ├── cloudsqlmysql │ │ │ ├── cloud_sql_mysql_test.go │ │ │ └── cloud_sql_mysql.go │ │ ├── cloudsqlpg │ │ │ ├── cloud_sql_pg_test.go │ │ │ └── cloud_sql_pg.go │ │ ├── couchbase │ │ │ ├── couchbase_test.go │ │ │ └── couchbase.go │ │ ├── dataplex │ │ │ ├── dataplex_test.go │ │ │ └── dataplex.go │ │ ├── dgraph │ │ │ ├── dgraph_test.go │ │ │ └── dgraph.go │ │ ├── dialect.go │ │ ├── firebird │ │ │ ├── firebird_test.go │ │ │ └── firebird.go │ │ ├── firestore │ │ │ ├── firestore_test.go │ │ │ └── firestore.go │ │ ├── http │ │ │ ├── http_test.go │ │ │ └── http.go │ │ ├── ip_type.go │ │ ├── looker │ │ │ ├── looker_test.go │ │ │ └── looker.go │ │ ├── mongodb │ │ │ ├── mongodb_test.go │ │ │ └── mongodb.go │ │ ├── mssql │ │ │ ├── mssql_test.go │ │ │ └── mssql.go │ │ ├── mysql │ │ │ ├── mysql_test.go │ │ │ └── mysql.go │ │ ├── neo4j │ │ │ ├── neo4j_test.go │ │ │ └── neo4j.go │ │ ├── oceanbase │ │ │ ├── oceanbase_test.go │ │ │ └── oceanbase.go │ │ ├── oracle │ │ │ └── oracle.go │ │ ├── postgres │ │ │ ├── postgres_test.go │ │ │ └── postgres.go │ │ ├── redis │ │ │ ├── redis_test.go │ │ │ └── redis.go │ │ ├── sources.go │ │ ├── spanner │ │ │ ├── spanner_test.go │ │ │ └── spanner.go │ │ ├── sqlite │ │ │ ├── sqlite_test.go │ │ │ └── sqlite.go │ │ ├── tidb │ │ │ ├── tidb_test.go │ │ │ └── tidb.go │ │ ├── trino │ │ │ ├── trino_test.go │ │ │ └── trino.go │ │ ├── util.go │ │ ├── valkey │ │ │ ├── valkey_test.go │ │ │ └── valkey.go │ │ └── yugabytedb │ │ ├── yugabytedb_test.go │ │ └── yugabytedb.go │ ├── telemetry │ │ ├── instrumentation.go │ │ └── telemetry.go │ ├── testutils │ │ └── testutils.go │ ├── tools │ │ ├── alloydb │ │ │ ├── alloydbcreatecluster │ │ │ │ ├── alloydbcreatecluster_test.go │ │ │ │ └── alloydbcreatecluster.go │ │ │ ├── alloydbcreateinstance │ │ │ │ ├── alloydbcreateinstance_test.go │ │ │ │ └── alloydbcreateinstance.go │ │ │ ├── alloydbcreateuser │ │ │ │ ├── alloydbcreateuser_test.go │ │ │ │ └── alloydbcreateuser.go │ │ │ ├── alloydbgetcluster │ │ │ │ ├── alloydbgetcluster_test.go │ │ │ │ └── alloydbgetcluster.go │ │ │ ├── alloydbgetinstance │ │ │ │ ├── alloydbgetinstance_test.go │ │ │ │ └── alloydbgetinstance.go │ │ │ ├── alloydbgetuser │ │ │ │ ├── alloydbgetuser_test.go │ │ │ │ └── alloydbgetuser.go │ │ │ ├── alloydblistclusters │ │ │ │ ├── alloydblistclusters_test.go │ │ │ │ └── alloydblistclusters.go │ │ │ ├── alloydblistinstances │ │ │ │ ├── alloydblistinstances_test.go │ │ │ │ └── alloydblistinstances.go │ │ │ ├── alloydblistusers │ │ │ │ ├── alloydblistusers_test.go │ │ │ │ └── alloydblistusers.go │ │ │ └── alloydbwaitforoperation │ │ │ ├── alloydbwaitforoperation_test.go │ │ │ └── alloydbwaitforoperation.go │ │ ├── alloydbainl │ │ │ ├── alloydbainl_test.go │ │ │ └── alloydbainl.go │ │ ├── bigquery │ │ │ ├── bigqueryanalyzecontribution │ │ │ │ ├── bigqueryanalyzecontribution_test.go │ │ │ │ └── bigqueryanalyzecontribution.go │ │ │ ├── bigquerycommon │ │ │ │ ├── table_name_parser_test.go │ │ │ │ ├── table_name_parser.go │ │ │ │ └── util.go │ │ │ ├── bigqueryconversationalanalytics │ │ │ │ ├── bigqueryconversationalanalytics_test.go │ │ │ │ └── bigqueryconversationalanalytics.go │ │ │ ├── bigqueryexecutesql │ │ │ │ ├── bigqueryexecutesql_test.go │ │ │ │ └── bigqueryexecutesql.go │ │ │ ├── bigqueryforecast │ │ │ │ ├── bigqueryforecast_test.go │ │ │ │ └── bigqueryforecast.go │ │ │ ├── bigquerygetdatasetinfo │ │ │ │ ├── bigquerygetdatasetinfo_test.go │ │ │ │ └── bigquerygetdatasetinfo.go │ │ │ ├── bigquerygettableinfo │ │ │ │ ├── bigquerygettableinfo_test.go │ │ │ │ └── bigquerygettableinfo.go │ │ │ ├── bigquerylistdatasetids │ │ │ │ ├── bigquerylistdatasetids_test.go │ │ │ │ └── bigquerylistdatasetids.go │ │ │ ├── bigquerylisttableids │ │ │ │ ├── bigquerylisttableids_test.go │ │ │ │ └── bigquerylisttableids.go │ │ │ ├── bigquerysearchcatalog │ │ │ │ ├── bigquerysearchcatalog_test.go │ │ │ │ └── bigquerysearchcatalog.go │ │ │ └── bigquerysql │ │ │ ├── bigquerysql_test.go │ │ │ └── bigquerysql.go │ │ ├── bigtable │ │ │ ├── bigtable_test.go │ │ │ └── bigtable.go │ │ ├── cassandra │ │ │ └── cassandracql │ │ │ ├── cassandracql_test.go │ │ │ └── cassandracql.go │ │ ├── clickhouse │ │ │ ├── clickhouseexecutesql │ │ │ │ ├── clickhouseexecutesql_test.go │ │ │ │ └── clickhouseexecutesql.go │ │ │ ├── clickhouselistdatabases │ │ │ │ ├── clickhouselistdatabases_test.go │ │ │ │ └── clickhouselistdatabases.go │ │ │ ├── clickhouselisttables │ │ │ │ ├── clickhouselisttables_test.go │ │ │ │ └── clickhouselisttables.go │ │ │ └── clickhousesql │ │ │ ├── clickhousesql_test.go │ │ │ └── clickhousesql.go │ │ ├── cloudmonitoring │ │ │ ├── cloudmonitoring_test.go │ │ │ └── cloudmonitoring.go │ │ ├── cloudsql │ │ │ ├── cloudsqlcreatedatabase │ │ │ │ ├── cloudsqlcreatedatabase_test.go │ │ │ │ └── cloudsqlcreatedatabase.go │ │ │ ├── cloudsqlcreateusers │ │ │ │ ├── cloudsqlcreateusers_test.go │ │ │ │ └── cloudsqlcreateusers.go │ │ │ ├── cloudsqlgetinstances │ │ │ │ ├── cloudsqlgetinstances_test.go │ │ │ │ └── cloudsqlgetinstances.go │ │ │ ├── cloudsqllistdatabases │ │ │ │ ├── cloudsqllistdatabases_test.go │ │ │ │ └── cloudsqllistdatabases.go │ │ │ ├── cloudsqllistinstances │ │ │ │ ├── cloudsqllistinstances_test.go │ │ │ │ └── cloudsqllistinstances.go │ │ │ └── cloudsqlwaitforoperation │ │ │ ├── cloudsqlwaitforoperation_test.go │ │ │ └── cloudsqlwaitforoperation.go │ │ ├── cloudsqlmssql │ │ │ └── cloudsqlmssqlcreateinstance │ │ │ ├── cloudsqlmssqlcreateinstance_test.go │ │ │ └── cloudsqlmssqlcreateinstance.go │ │ ├── cloudsqlmysql │ │ │ └── cloudsqlmysqlcreateinstance │ │ │ ├── cloudsqlmysqlcreateinstance_test.go │ │ │ └── cloudsqlmysqlcreateinstance.go │ │ ├── cloudsqlpg │ │ │ └── cloudsqlpgcreateinstances │ │ │ ├── cloudsqlpgcreateinstances_test.go │ │ │ └── cloudsqlpgcreateinstances.go │ │ ├── common_test.go │ │ ├── common.go │ │ ├── couchbase │ │ │ ├── couchbase_test.go │ │ │ └── couchbase.go │ │ ├── dataform │ │ │ └── dataformcompilelocal │ │ │ ├── dataformcompilelocal_test.go │ │ │ └── dataformcompilelocal.go │ │ ├── dataplex │ │ │ ├── dataplexlookupentry │ │ │ │ ├── dataplexlookupentry_test.go │ │ │ │ └── dataplexlookupentry.go │ │ │ ├── dataplexsearchaspecttypes │ │ │ │ ├── dataplexsearchaspecttypes_test.go │ │ │ │ └── dataplexsearchaspecttypes.go │ │ │ └── dataplexsearchentries │ │ │ ├── dataplexsearchentries_test.go │ │ │ └── dataplexsearchentries.go │ │ ├── dgraph │ │ │ ├── dgraph_test.go │ │ │ └── dgraph.go │ │ ├── firebird │ │ │ ├── firebirdexecutesql │ │ │ │ ├── firebirdexecutesql_test.go │ │ │ │ └── firebirdexecutesql.go │ │ │ └── firebirdsql │ │ │ ├── firebirdsql_test.go │ │ │ └── firebirdsql.go │ │ ├── firestore │ │ │ ├── firestoreadddocuments │ │ │ │ ├── firestoreadddocuments_test.go │ │ │ │ └── firestoreadddocuments.go │ │ │ ├── firestoredeletedocuments │ │ │ │ ├── firestoredeletedocuments_test.go │ │ │ │ └── firestoredeletedocuments.go │ │ │ ├── firestoregetdocuments │ │ │ │ ├── firestoregetdocuments_test.go │ │ │ │ └── firestoregetdocuments.go │ │ │ ├── firestoregetrules │ │ │ │ ├── firestoregetrules_test.go │ │ │ │ └── firestoregetrules.go │ │ │ ├── firestorelistcollections │ │ │ │ ├── firestorelistcollections_test.go │ │ │ │ └── firestorelistcollections.go │ │ │ ├── firestorequery │ │ │ │ ├── firestorequery_test.go │ │ │ │ └── firestorequery.go │ │ │ ├── firestorequerycollection │ │ │ │ ├── firestorequerycollection_test.go │ │ │ │ └── firestorequerycollection.go │ │ │ ├── firestoreupdatedocument │ │ │ │ ├── firestoreupdatedocument_test.go │ │ │ │ └── firestoreupdatedocument.go │ │ │ ├── firestorevalidaterules │ │ │ │ ├── firestorevalidaterules_test.go │ │ │ │ └── firestorevalidaterules.go │ │ │ └── util │ │ │ ├── converter_test.go │ │ │ ├── converter.go │ │ │ ├── validator_test.go │ │ │ └── validator.go │ │ ├── http │ │ │ ├── http_test.go │ │ │ └── http.go │ │ ├── http_method.go │ │ ├── looker │ │ │ ├── lookeradddashboardelement │ │ │ │ ├── lookeradddashboardelement_test.go │ │ │ │ └── lookeradddashboardelement.go │ │ │ ├── lookercommon │ │ │ │ ├── lookercommon_test.go │ │ │ │ └── lookercommon.go │ │ │ ├── lookerconversationalanalytics │ │ │ │ ├── lookerconversationalanalytics_test.go │ │ │ │ └── lookerconversationalanalytics.go │ │ │ ├── lookercreateprojectfile │ │ │ │ ├── lookercreateprojectfile_test.go │ │ │ │ └── lookercreateprojectfile.go │ │ │ ├── lookerdeleteprojectfile │ │ │ │ ├── lookerdeleteprojectfile_test.go │ │ │ │ └── lookerdeleteprojectfile.go │ │ │ ├── lookerdevmode │ │ │ │ ├── lookerdevmode_test.go │ │ │ │ └── lookerdevmode.go │ │ │ ├── lookergetdashboards │ │ │ │ ├── lookergetdashboards_test.go │ │ │ │ └── lookergetdashboards.go │ │ │ ├── lookergetdimensions │ │ │ │ ├── lookergetdimensions_test.go │ │ │ │ └── lookergetdimensions.go │ │ │ ├── lookergetexplores │ │ │ │ ├── lookergetexplores_test.go │ │ │ │ └── lookergetexplores.go │ │ │ ├── lookergetfilters │ │ │ │ ├── lookergetfilters_test.go │ │ │ │ └── lookergetfilters.go │ │ │ ├── lookergetlooks │ │ │ │ ├── lookergetlooks_test.go │ │ │ │ └── lookergetlooks.go │ │ │ ├── lookergetmeasures │ │ │ │ ├── lookergetmeasures_test.go │ │ │ │ └── lookergetmeasures.go │ │ │ ├── lookergetmodels │ │ │ │ ├── lookergetmodels_test.go │ │ │ │ └── lookergetmodels.go │ │ │ ├── lookergetparameters │ │ │ │ ├── lookergetparameters_test.go │ │ │ │ └── lookergetparameters.go │ │ │ ├── lookergetprojectfile │ │ │ │ ├── lookergetprojectfile_test.go │ │ │ │ └── lookergetprojectfile.go │ │ │ ├── lookergetprojectfiles │ │ │ │ ├── lookergetprojectfiles_test.go │ │ │ │ └── lookergetprojectfiles.go │ │ │ ├── lookergetprojects │ │ │ │ ├── lookergetprojects_test.go │ │ │ │ └── lookergetprojects.go │ │ │ ├── lookerhealthanalyze │ │ │ │ ├── lookerhealthanalyze_test.go │ │ │ │ └── lookerhealthanalyze.go │ │ │ ├── lookerhealthpulse │ │ │ │ ├── lookerhealthpulse_test.go │ │ │ │ └── lookerhealthpulse.go │ │ │ ├── lookerhealthvacuum │ │ │ │ ├── lookerhealthvacuum_test.go │ │ │ │ └── lookerhealthvacuum.go │ │ │ ├── lookermakedashboard │ │ │ │ ├── lookermakedashboard_test.go │ │ │ │ └── lookermakedashboard.go │ │ │ ├── lookermakelook │ │ │ │ ├── lookermakelook_test.go │ │ │ │ └── lookermakelook.go │ │ │ ├── lookerquery │ │ │ │ ├── lookerquery_test.go │ │ │ │ └── lookerquery.go │ │ │ ├── lookerquerysql │ │ │ │ ├── lookerquerysql_test.go │ │ │ │ └── lookerquerysql.go │ │ │ ├── lookerqueryurl │ │ │ │ ├── lookerqueryurl_test.go │ │ │ │ └── lookerqueryurl.go │ │ │ ├── lookerrunlook │ │ │ │ ├── lookerrunlook_test.go │ │ │ │ └── lookerrunlook.go │ │ │ └── lookerupdateprojectfile │ │ │ ├── lookerupdateprojectfile_test.go │ │ │ └── lookerupdateprojectfile.go │ │ ├── mongodb │ │ │ ├── mongodbaggregate │ │ │ │ ├── mongodbaggregate_test.go │ │ │ │ └── mongodbaggregate.go │ │ │ ├── mongodbdeletemany │ │ │ │ ├── mongodbdeletemany_test.go │ │ │ │ └── mongodbdeletemany.go │ │ │ ├── mongodbdeleteone │ │ │ │ ├── mongodbdeleteone_test.go │ │ │ │ └── mongodbdeleteone.go │ │ │ ├── mongodbfind │ │ │ │ ├── mongodbfind_test.go │ │ │ │ └── mongodbfind.go │ │ │ ├── mongodbfindone │ │ │ │ ├── mongodbfindone_test.go │ │ │ │ └── mongodbfindone.go │ │ │ ├── mongodbinsertmany │ │ │ │ ├── mongodbinsertmany_test.go │ │ │ │ └── mongodbinsertmany.go │ │ │ ├── mongodbinsertone │ │ │ │ ├── mongodbinsertone_test.go │ │ │ │ └── mongodbinsertone.go │ │ │ ├── mongodbupdatemany │ │ │ │ ├── mongodbupdatemany_test.go │ │ │ │ └── mongodbupdatemany.go │ │ │ └── mongodbupdateone │ │ │ ├── mongodbupdateone_test.go │ │ │ └── mongodbupdateone.go │ │ ├── mssql │ │ │ ├── mssqlexecutesql │ │ │ │ ├── mssqlexecutesql_test.go │ │ │ │ └── mssqlexecutesql.go │ │ │ ├── mssqllisttables │ │ │ │ ├── mssqllisttables_test.go │ │ │ │ └── mssqllisttables.go │ │ │ └── mssqlsql │ │ │ ├── mssqlsql_test.go │ │ │ └── mssqlsql.go │ │ ├── mysql │ │ │ ├── mysqlcommon │ │ │ │ └── mysqlcommon.go │ │ │ ├── mysqlexecutesql │ │ │ │ ├── mysqlexecutesql_test.go │ │ │ │ └── mysqlexecutesql.go │ │ │ ├── mysqllistactivequeries │ │ │ │ ├── mysqllistactivequeries_test.go │ │ │ │ └── mysqllistactivequeries.go │ │ │ ├── mysqllisttablefragmentation │ │ │ │ ├── mysqllisttablefragmentation_test.go │ │ │ │ └── mysqllisttablefragmentation.go │ │ │ ├── mysqllisttables │ │ │ │ ├── mysqllisttables_test.go │ │ │ │ └── mysqllisttables.go │ │ │ ├── mysqllisttablesmissinguniqueindexes │ │ │ │ ├── mysqllisttablesmissinguniqueindexes_test.go │ │ │ │ └── mysqllisttablesmissinguniqueindexes.go │ │ │ └── mysqlsql │ │ │ ├── mysqlsql_test.go │ │ │ └── mysqlsql.go │ │ ├── neo4j │ │ │ ├── neo4jcypher │ │ │ │ ├── neo4jcypher_test.go │ │ │ │ └── neo4jcypher.go │ │ │ ├── neo4jexecutecypher │ │ │ │ ├── classifier │ │ │ │ │ ├── classifier_test.go │ │ │ │ │ └── classifier.go │ │ │ │ ├── neo4jexecutecypher_test.go │ │ │ │ └── neo4jexecutecypher.go │ │ │ └── neo4jschema │ │ │ ├── cache │ │ │ │ ├── cache_test.go │ │ │ │ └── cache.go │ │ │ ├── helpers │ │ │ │ ├── helpers_test.go │ │ │ │ └── helpers.go │ │ │ ├── neo4jschema_test.go │ │ │ ├── neo4jschema.go │ │ │ └── types │ │ │ └── types.go │ │ ├── oceanbase │ │ │ ├── oceanbaseexecutesql │ │ │ │ ├── oceanbaseexecutesql_test.go │ │ │ │ └── oceanbaseexecutesql.go │ │ │ └── oceanbasesql │ │ │ ├── oceanbasesql_test.go │ │ │ └── oceanbasesql.go │ │ ├── oracle │ │ │ ├── oracleexecutesql │ │ │ │ └── oracleexecutesql.go │ │ │ └── oraclesql │ │ │ └── oraclesql.go │ │ ├── parameters_test.go │ │ ├── parameters.go │ │ ├── postgres │ │ │ ├── postgresexecutesql │ │ │ │ ├── postgresexecutesql_test.go │ │ │ │ └── postgresexecutesql.go │ │ │ ├── postgreslistactivequeries │ │ │ │ ├── postgreslistactivequeries_test.go │ │ │ │ └── postgreslistactivequeries.go │ │ │ ├── postgreslistavailableextensions │ │ │ │ ├── postgreslistavailableextensions_test.go │ │ │ │ └── postgreslistavailableextensions.go │ │ │ ├── postgreslistinstalledextensions │ │ │ │ ├── postgreslistinstalledextensions_test.go │ │ │ │ └── postgreslistinstalledextensions.go │ │ │ ├── postgreslisttables │ │ │ │ ├── postgreslisttables_test.go │ │ │ │ └── postgreslisttables.go │ │ │ └── postgressql │ │ │ ├── postgressql_test.go │ │ │ └── postgressql.go │ │ ├── redis │ │ │ ├── redis_test.go │ │ │ └── redis.go │ │ ├── spanner │ │ │ ├── spannerexecutesql │ │ │ │ ├── spannerexecutesql_test.go │ │ │ │ └── spannerexecutesql.go │ │ │ ├── spannerlisttables │ │ │ │ ├── spannerlisttables_test.go │ │ │ │ └── spannerlisttables.go │ │ │ └── spannersql │ │ │ ├── spanner_test.go │ │ │ └── spannersql.go │ │ ├── sqlite │ │ │ ├── sqliteexecutesql │ │ │ │ ├── sqliteexecutesql_test.go │ │ │ │ └── sqliteexecutesql.go │ │ │ └── sqlitesql │ │ │ ├── sqlitesql_test.go │ │ │ └── sqlitesql.go │ │ ├── tidb │ │ │ ├── tidbexecutesql │ │ │ │ ├── tidbexecutesql_test.go │ │ │ │ └── tidbexecutesql.go │ │ │ └── tidbsql │ │ │ ├── tidbsql_test.go │ │ │ └── tidbsql.go │ │ ├── tools_test.go │ │ ├── tools.go │ │ ├── toolsets.go │ │ ├── trino │ │ │ ├── trinoexecutesql │ │ │ │ ├── trinoexecutesql_test.go │ │ │ │ └── trinoexecutesql.go │ │ │ └── trinosql │ │ │ ├── trinosql_test.go │ │ │ └── trinosql.go │ │ ├── utility │ │ │ └── wait │ │ │ ├── wait_test.go │ │ │ └── wait.go │ │ ├── valkey │ │ │ ├── valkey_test.go │ │ │ └── valkey.go │ │ └── yugabytedbsql │ │ ├── yugabytedbsql_test.go │ │ └── yugabytedbsql.go │ └── util │ └── util.go ├── LICENSE ├── logo.png ├── main.go ├── MCP-TOOLBOX-EXTENSION.md ├── README.md └── tests ├── alloydb │ ├── alloydb_integration_test.go │ └── alloydb_wait_for_operation_test.go ├── alloydbainl │ └── alloydb_ai_nl_integration_test.go ├── alloydbpg │ └── alloydb_pg_integration_test.go ├── auth.go ├── bigquery │ └── bigquery_integration_test.go ├── bigtable │ └── bigtable_integration_test.go ├── cassandra │ └── cassandra_integration_test.go ├── clickhouse │ └── clickhouse_integration_test.go ├── cloudmonitoring │ └── cloud_monitoring_integration_test.go ├── cloudsql │ ├── cloud_sql_create_database_test.go │ ├── cloud_sql_create_users_test.go │ ├── cloud_sql_get_instances_test.go │ ├── cloud_sql_list_databases_test.go │ ├── cloudsql_list_instances_test.go │ └── cloudsql_wait_for_operation_test.go ├── cloudsqlmssql │ ├── cloud_sql_mssql_create_instance_integration_test.go │ └── cloud_sql_mssql_integration_test.go ├── cloudsqlmysql │ ├── cloud_sql_mysql_create_instance_integration_test.go │ └── cloud_sql_mysql_integration_test.go ├── cloudsqlpg │ ├── cloud_sql_pg_create_instances_test.go │ └── cloud_sql_pg_integration_test.go ├── common.go ├── couchbase │ └── couchbase_integration_test.go ├── dataform │ └── dataform_integration_test.go ├── dataplex │ └── dataplex_integration_test.go ├── dgraph │ └── dgraph_integration_test.go ├── firebird │ └── firebird_integration_test.go ├── firestore │ └── firestore_integration_test.go ├── http │ └── http_integration_test.go ├── looker │ └── looker_integration_test.go ├── mongodb │ └── mongodb_integration_test.go ├── mssql │ └── mssql_integration_test.go ├── mysql │ └── mysql_integration_test.go ├── neo4j │ └── neo4j_integration_test.go ├── oceanbase │ └── oceanbase_integration_test.go ├── option.go ├── oracle │ └── oracle_integration_test.go ├── postgres │ └── postgres_integration_test.go ├── redis │ └── redis_test.go ├── server.go ├── source.go ├── spanner │ └── spanner_integration_test.go ├── sqlite │ └── sqlite_integration_test.go ├── tidb │ └── tidb_integration_test.go ├── tool.go ├── trino │ └── trino_integration_test.go ├── utility │ └── wait_integration_test.go ├── valkey │ └── valkey_test.go └── yugabytedb └── yugabytedb_integration_test.go ``` # Files -------------------------------------------------------------------------------- /internal/server/server_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 server_test import ( "context" "fmt" "io" "net/http" "os" "strings" "testing" "github.com/google/go-cmp/cmp" "github.com/googleapis/genai-toolbox/internal/auth" "github.com/googleapis/genai-toolbox/internal/log" "github.com/googleapis/genai-toolbox/internal/server" "github.com/googleapis/genai-toolbox/internal/sources" "github.com/googleapis/genai-toolbox/internal/sources/alloydbpg" "github.com/googleapis/genai-toolbox/internal/telemetry" "github.com/googleapis/genai-toolbox/internal/testutils" "github.com/googleapis/genai-toolbox/internal/tools" "github.com/googleapis/genai-toolbox/internal/util" ) func TestServe(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() addr, port := "127.0.0.1", 5000 cfg := server.ServerConfig{ Version: "0.0.0", Address: addr, Port: port, } otelShutdown, err := telemetry.SetupOTel(ctx, "0.0.0", "", false, "toolbox") if err != nil { t.Fatalf("unexpected error: %s", err) } defer func() { err := otelShutdown(ctx) if err != nil { t.Fatalf("unexpected error: %s", err) } }() testLogger, err := log.NewStdLogger(os.Stdout, os.Stderr, "info") if err != nil { t.Fatalf("unexpected error: %s", err) } ctx = util.WithLogger(ctx, testLogger) instrumentation, err := telemetry.CreateTelemetryInstrumentation(cfg.Version) if err != nil { t.Fatalf("unexpected error: %s", err) } ctx = util.WithInstrumentation(ctx, instrumentation) s, err := server.NewServer(ctx, cfg) if err != nil { t.Fatalf("unable to initialize server: %v", err) } err = s.Listen(ctx) if err != nil { t.Fatalf("unable to start server: %v", err) } // start server in background errCh := make(chan error) go func() { defer close(errCh) err = s.Serve(ctx) if err != nil { errCh <- err } }() url := fmt.Sprintf("http://%s:%d/", addr, port) resp, err := http.Get(url) if err != nil { t.Fatalf("error when sending a request: %s", err) } defer resp.Body.Close() if resp.StatusCode != 200 { t.Fatalf("response status code is not 200") } raw, err := io.ReadAll(resp.Body) if err != nil { t.Fatalf("error reading from request body: %s", err) } if got := string(raw); strings.Contains(got, "0.0.0") { t.Fatalf("version missing from output: %q", got) } } func TestUpdateServer(t *testing.T) { ctx, err := testutils.ContextWithNewLogger() if err != nil { t.Fatalf("error setting up logger: %s", err) } addr, port := "127.0.0.1", 5000 cfg := server.ServerConfig{ Version: "0.0.0", Address: addr, Port: port, } instrumentation, err := telemetry.CreateTelemetryInstrumentation(cfg.Version) if err != nil { t.Fatalf("unexpected error: %s", err) } ctx = util.WithInstrumentation(ctx, instrumentation) s, err := server.NewServer(ctx, cfg) if err != nil { t.Fatalf("error setting up server: %s", err) } newSources := map[string]sources.Source{ "example-source": &alloydbpg.Source{ Name: "example-alloydb-source", Kind: "alloydb-postgres", }, } newAuth := map[string]auth.AuthService{"example-auth": nil} newTools := map[string]tools.Tool{"example-tool": nil} newToolsets := map[string]tools.Toolset{ "example-toolset": { Name: "example-toolset", Tools: []*tools.Tool{}, }, } s.ResourceMgr.SetResources(newSources, newAuth, newTools, newToolsets) if err != nil { t.Errorf("error updating server: %s", err) } gotSource, _ := s.ResourceMgr.GetSource("example-source") if diff := cmp.Diff(gotSource, newSources["example-source"]); diff != "" { t.Errorf("error updating server, sources (-want +got):\n%s", diff) } gotAuthService, _ := s.ResourceMgr.GetAuthService("example-auth") if diff := cmp.Diff(gotAuthService, newAuth["example-auth"]); diff != "" { t.Errorf("error updating server, authServices (-want +got):\n%s", diff) } gotTool, _ := s.ResourceMgr.GetTool("example-tool") if diff := cmp.Diff(gotTool, newTools["example-tool"]); diff != "" { t.Errorf("error updating server, tools (-want +got):\n%s", diff) } gotToolset, _ := s.ResourceMgr.GetToolset("example-toolset") if diff := cmp.Diff(gotToolset, newToolsets["example-toolset"]); diff != "" { t.Errorf("error updating server, toolset (-want +got):\n%s", diff) } } ``` -------------------------------------------------------------------------------- /docs/en/resources/sources/oracle.md: -------------------------------------------------------------------------------- ```markdown --- title: "Oracle" type: docs weight: 1 description: > Oracle Database is a widely-used relational database management system. --- ## About [Oracle Database][oracle-docs] is a multi-model database management system produced and marketed by Oracle Corporation. It is commonly used for running online transaction processing (OLTP), data warehousing (DW), and mixed (OLTP & DW) database workloads. [oracle-docs]: https://www.oracle.com/database/ ## Available Tools - [`oracle-sql`](../tools/oracle/oracle-sql.md) Execute pre-defined prepared SQL queries in Oracle. - [`oracle-execute-sql`](../tools/oracle/oracle-execute-sql.md) Run parameterized SQL queries in Oracle. ## Requirements ### Database User This source uses standard authentication. You will need to [create an Oracle user][oracle-users] to log in to the database with the necessary permissions. [oracle-users]: https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/CREATE-USER.html ## Connection Methods You can configure the connection to your Oracle database using one of the following three methods. **You should only use one method** in your source configuration. ### Basic Connection (Host/Port/Service Name) This is the most straightforward method, where you provide the connection details as separate fields: - `host`: The IP address or hostname of the database server. - `port`: The port number the Oracle listener is running on (typically 1521). - `serviceName`: The service name for the database instance you wish to connect to. ### Connection String As an alternative, you can provide all the connection details in a single `connectionString`. This is a convenient way to consolidate the connection information. The typical format is `hostname:port/servicename`. ### TNS Alias For environments that use a `tnsnames.ora` configuration file, you can connect using a TNS (Transparent Network Substrate) alias. - `tnsAlias`: Specify the alias name defined in your `tnsnames.ora` file. - `tnsAdmin` (Optional): If your configuration file is not in a standard location, you can use this field to provide the path to the directory containing it. This setting will override the `TNS_ADMIN` environment variable. ## Example ```yaml sources: my-oracle-source: kind: oracle # --- Choose one connection method --- # 1. Host, Port, and Service Name host: 127.0.0.1 port: 1521 serviceName: XEPDB1 # 2. Direct Connection String connectionString: "127.0.0.1:1521/XEPDB1" # 3. TNS Alias (requires tnsnames.ora) tnsAlias: "MY_DB_ALIAS" tnsAdmin: "/opt/oracle/network/admin" # Optional: overrides TNS_ADMIN env var user: ${USER_NAME} password: ${PASSWORD} ``` {{< 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 "oracle". | | user | string | true | Name of the Oracle user to connect as (e.g. "my-oracle-user"). | | password | string | true | Password of the Oracle user (e.g. "my-password"). | | host | string | false | IP address or hostname to connect to (e.g. "127.0.0.1"). Required if not using `connectionString` or `tnsAlias`. | | port | integer | false | Port to connect to (e.g. "1521"). Required if not using `connectionString` or `tnsAlias`. | | serviceName | string | false | The Oracle service name of the database to connect to. Required if not using `connectionString` or `tnsAlias`. | | connectionString | string | false | A direct connection string (e.g. "hostname:port/servicename"). Use as an alternative to `host`, `port`, and `serviceName`. | | tnsAlias | string | false | A TNS alias from a `tnsnames.ora` file. Use as an alternative to `host`/`port` or `connectionString`. | | tnsAdmin | string | false | Path to the directory containing the `tnsnames.ora` file. This overrides the `TNS_ADMIN` environment variable if it is set. | ``` -------------------------------------------------------------------------------- /docs/en/getting-started/local_quickstart.md: -------------------------------------------------------------------------------- ```markdown --- title: "Python Quickstart (Local)" type: docs weight: 2 description: > How to get started running Toolbox locally with [Python](https://github.com/googleapis/mcp-toolbox-sdk-python), PostgreSQL, and [Agent Development Kit](https://google.github.io/adk-docs/), [LangGraph](https://www.langchain.com/langgraph), [LlamaIndex](https://www.llamaindex.ai/) or [GoogleGenAI](https://pypi.org/project/google-genai/). --- [](https://colab.research.google.com/github/googleapis/genai-toolbox/blob/main/docs/en/getting-started/colab_quickstart.ipynb) ## Before you begin This guide assumes you have already done the following: 1. Installed [Python 3.9+][install-python] (including [pip][install-pip] and your preferred virtual environment tool for managing dependencies e.g. [venv][install-venv]). 1. Installed [PostgreSQL 16+ and the `psql` client][install-postgres]. [install-python]: https://wiki.python.org/moin/BeginnersGuide/Download [install-pip]: https://pip.pypa.io/en/stable/installation/ [install-venv]: https://packaging.python.org/en/latest/tutorials/installing-packages/#creating-virtual-environments [install-postgres]: https://www.postgresql.org/download/ ### Cloud Setup (Optional) {{< regionInclude "quickstart/shared/cloud_setup.md" "cloud_setup" >}} ## Step 1: Set up your database {{< regionInclude "quickstart/shared/database_setup.md" "database_setup" >}} ## Step 2: Install and configure Toolbox {{< regionInclude "quickstart/shared/configure_toolbox.md" "configure_toolbox" >}} ## Step 3: Connect your agent to Toolbox In this section, we will write and run an agent that will load the Tools from Toolbox. {{< notice tip>}} If you prefer to experiment within a Google Colab environment, you can connect to a [local runtime](https://research.google.com/colaboratory/local-runtimes.html). {{< /notice >}} 1. In a new terminal, install the SDK package. {{< tabpane persist=header >}} {{< tab header="ADK" lang="bash" >}} pip install toolbox-core {{< /tab >}} {{< tab header="Langchain" lang="bash" >}} pip install toolbox-langchain {{< /tab >}} {{< tab header="LlamaIndex" lang="bash" >}} pip install toolbox-llamaindex {{< /tab >}} {{< tab header="Core" lang="bash" >}} pip install toolbox-core {{< /tab >}} {{< /tabpane >}} 1. Install other required dependencies: {{< tabpane persist=header >}} {{< tab header="ADK" lang="bash" >}} pip install google-adk {{< /tab >}} {{< tab header="Langchain" lang="bash" >}} # TODO(developer): replace with correct package if needed pip install langgraph langchain-google-vertexai # pip install langchain-google-genai # pip install langchain-anthropic {{< /tab >}} {{< tab header="LlamaIndex" lang="bash" >}} # TODO(developer): replace with correct package if needed pip install llama-index-llms-google-genai # pip install llama-index-llms-anthropic {{< /tab >}} {{< tab header="Core" lang="bash" >}} pip install google-genai {{< /tab >}} {{< /tabpane >}} 1. Create a new file named `hotel_agent.py` and copy the following code to create an agent: {{< tabpane persist=header >}} {{< tab header="ADK" lang="python" >}} {{< include "quickstart/python/adk/quickstart.py" >}} {{< /tab >}} {{< tab header="LangChain" lang="python" >}} {{< include "quickstart/python/langchain/quickstart.py" >}} {{< /tab >}} {{< tab header="LlamaIndex" lang="python" >}} {{< include "quickstart/python/llamaindex/quickstart.py" >}} {{< /tab >}} {{< tab header="Core" lang="python" >}} {{< include "quickstart/python/core/quickstart.py" >}} {{< /tab >}} {{< /tabpane >}} {{< tabpane text=true persist=header >}} {{% tab header="ADK" lang="en" %}} To learn more about Agent Development Kit, check out the [ADK documentation.](https://google.github.io/adk-docs/) {{% /tab %}} {{% tab header="Langchain" lang="en" %}} To learn more about Agents in LangChain, check out the [LangGraph Agent documentation.](https://langchain-ai.github.io/langgraph/reference/prebuilt/#langgraph.prebuilt.chat_agent_executor.create_react_agent) {{% /tab %}} {{% tab header="LlamaIndex" lang="en" %}} To learn more about Agents in LlamaIndex, check out the [LlamaIndex AgentWorkflow documentation.](https://docs.llamaindex.ai/en/stable/examples/agent/agent_workflow_basic/) {{% /tab %}} {{% tab header="Core" lang="en" %}} To learn more about tool calling with Google GenAI, check out the [Google GenAI Documentation](https://github.com/googleapis/python-genai?tab=readme-ov-file#manually-declare-and-invoke-a-function-for-function-calling). {{% /tab %}} {{< /tabpane >}} 1. Run your agent, and observe the results: ```sh python hotel_agent.py ``` {{< notice info >}} For more information, visit the [Python SDK repo](https://github.com/googleapis/mcp-toolbox-sdk-python). {{</ notice >}} ``` -------------------------------------------------------------------------------- /docs/en/resources/tools/mysql/mysql-sql.md: -------------------------------------------------------------------------------- ```markdown --- title: "mysql-sql" type: docs weight: 1 description: > A "mysql-sql" tool executes a pre-defined SQL statement against a MySQL database. aliases: - /resources/tools/mysql-sql --- ## About A `mysql-sql` tool executes a pre-defined SQL statement against a MySQL database. It's compatible with any of the following sources: - [cloud-sql-mysql](../../sources/cloud-sql-mysql.md) - [mysql](../../sources/mysql.md) The specified SQL statement is executed as a [prepared statement][mysql-prepare], and expects parameters in the SQL query to be in the form of placeholders `?`. [mysql-prepare]: https://dev.mysql.com/doc/refman/8.4/en/sql-prepared-statements.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: mysql-sql source: my-mysql-instance 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 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](..#template-parameters). ```yaml tools: list_table: kind: mysql-sql source: my-mysql-instance 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 "mysql-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](../#specifying-parameters) | false | List of [parameters](../#specifying-parameters) that will be inserted into the SQL statement. | | templateParameters | [templateParameters](..#template-parameters) | false | List of [templateParameters](..#template-parameters) that will be inserted into the SQL statement before executing prepared statement. | ``` -------------------------------------------------------------------------------- /internal/tools/alloydb/alloydblistclusters/alloydblistclusters.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 alloydblistclusters import ( "context" "fmt" yaml "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/sources" alloydbadmin "github.com/googleapis/genai-toolbox/internal/sources/alloydbadmin" "github.com/googleapis/genai-toolbox/internal/tools" ) const kind string = "alloydb-list-clusters" 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 } // Configuration for the list-clusters 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"` BaseURL string `yaml:"baseURL"` } // 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("source %q not found", cfg.Source) } s, ok := rawS.(*alloydbadmin.Source) if !ok { return nil, fmt.Errorf("invalid source for %q tool: source kind must be `%s`", kind, alloydbadmin.SourceKind) } allParameters := tools.Parameters{ tools.NewStringParameter("project", "The GCP project ID to list clusters for."), tools.NewStringParameterWithDefault("location", "-", "Optional: The location to list clusters in (e.g., 'us-central1'). Use '-' to list clusters across all locations.(Default: '-')"), } paramManifest := allParameters.Manifest() description := cfg.Description if description == "" { description = "Lists all AlloyDB clusters in a given project and location." } mcpManifest := tools.GetMcpManifest(cfg.Name, description, cfg.AuthRequired, allParameters) return Tool{ Name: cfg.Name, Kind: kind, Source: s, AllParams: allParameters, manifest: tools.Manifest{Description: description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired}, mcpManifest: mcpManifest, }, nil } // Tool represents the list-clusters tool. type Tool struct { Name string `yaml:"name"` Kind string `yaml:"kind"` Description string `yaml:"description"` Source *alloydbadmin.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("invalid or missing 'project' parameter; expected a string") } location, ok := paramsMap["location"].(string) if !ok { return nil, fmt.Errorf("invalid 'location' parameter; expected a string") } service, err := t.Source.GetService(ctx, string(accessToken)) if err != nil { return nil, err } urlString := fmt.Sprintf("projects/%s/locations/%s", project, location) resp, err := service.Projects.Locations.Clusters.List(urlString).Do() if err != nil { return nil, fmt.Errorf("error listing AlloyDB clusters: %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/tools/neo4j/neo4jschema/cache/cache_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 cache import ( "sync" "testing" "time" ) // TestCache_SetAndGet verifies the basic functionality of setting a value // and immediately retrieving it. func TestCache_SetAndGet(t *testing.T) { cache := NewCache() defer cache.Stop() key := "testKey" value := "testValue" cache.Set(key, value, 1*time.Minute) retrievedValue, found := cache.Get(key) if !found { t.Errorf("Expected to find key %q, but it was not found", key) } if retrievedValue != value { t.Errorf("Expected value %q, but got %q", value, retrievedValue) } } // TestCache_GetExpired tests that an item is not retrievable after it has expired. func TestCache_GetExpired(t *testing.T) { cache := NewCache() defer cache.Stop() key := "expiredKey" value := "expiredValue" // Set an item with a very short TTL. cache.Set(key, value, 1*time.Millisecond) time.Sleep(2 * time.Millisecond) // Wait for the item to expire. // Attempt to get the expired item. _, found := cache.Get(key) if found { t.Errorf("Expected key %q to be expired, but it was found", key) } } // TestCache_SetNoExpiration ensures that an item with a TTL of 0 or less // does not expire. func TestCache_SetNoExpiration(t *testing.T) { cache := NewCache() defer cache.Stop() key := "noExpireKey" value := "noExpireValue" cache.Set(key, value, 0) // Setting with 0 should mean no expiration. time.Sleep(5 * time.Millisecond) retrievedValue, found := cache.Get(key) if !found { t.Errorf("Expected to find key %q, but it was not found", key) } if retrievedValue != value { t.Errorf("Expected value %q, but got %q", value, retrievedValue) } } // TestCache_Janitor verifies that the janitor goroutine automatically removes // expired items from the cache. func TestCache_Janitor(t *testing.T) { // Initialize cache with a very short janitor interval for quick testing. cache := NewCache().WithJanitor(10 * time.Millisecond) defer cache.Stop() expiredKey := "expired" activeKey := "active" // Set one item that will expire and one that will not. cache.Set(expiredKey, "value", 1*time.Millisecond) cache.Set(activeKey, "value", 1*time.Hour) // Wait longer than the janitor interval to ensure it has a chance to run. time.Sleep(20 * time.Millisecond) // Check that the expired key has been removed. _, found := cache.Get(expiredKey) if found { t.Errorf("Expected janitor to clean up expired key %q, but it was found", expiredKey) } // Check that the active key is still present. _, found = cache.Get(activeKey) if !found { t.Errorf("Expected active key %q to be present, but it was not found", activeKey) } } // TestCache_Stop ensures that calling the Stop method does not cause a panic, // regardless of whether the janitor is running or not. It also tests idempotency. func TestCache_Stop(t *testing.T) { t.Run("Stop without janitor", func(t *testing.T) { cache := NewCache() // Test that calling Stop multiple times on a cache without a janitor is safe. cache.Stop() cache.Stop() }) t.Run("Stop with janitor", func(t *testing.T) { cache := NewCache().WithJanitor(1 * time.Minute) // Test that calling Stop multiple times on a cache with a janitor is safe. cache.Stop() cache.Stop() }) } // TestCache_Concurrent performs a stress test on the cache with concurrent // reads and writes to check for race conditions. func TestCache_Concurrent(t *testing.T) { cache := NewCache().WithJanitor(100 * time.Millisecond) defer cache.Stop() var wg sync.WaitGroup numGoroutines := 100 numOperations := 1000 // Start concurrent writer goroutines. for i := 0; i < numGoroutines; i++ { wg.Add(1) go func(g int) { defer wg.Done() for j := 0; j < numOperations; j++ { key := string(rune(g*numOperations + j)) value := g*numOperations + j cache.Set(key, value, 10*time.Second) } }(i) } // Start concurrent reader goroutines. for i := 0; i < numGoroutines; i++ { wg.Add(1) go func(g int) { defer wg.Done() for j := 0; j < numOperations; j++ { key := string(rune(g*numOperations + j)) cache.Get(key) // We don't check the result, just that access is safe. } }(i) } // Wait for all goroutines to complete. If a race condition exists, the Go // race detector (`go test -race`) will likely catch it. wg.Wait() } ``` -------------------------------------------------------------------------------- /internal/tools/alloydb/alloydbgetcluster/alloydbgetcluster.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 alloydbgetcluster import ( "context" "fmt" yaml "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/sources" alloydbadmin "github.com/googleapis/genai-toolbox/internal/sources/alloydbadmin" "github.com/googleapis/genai-toolbox/internal/tools" ) const kind string = "alloydb-get-cluster" 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 } // Configuration for the get-cluster 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"` BaseURL string `yaml:"baseURL"` } // 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("source %q not found", cfg.Source) } s, ok := rawS.(*alloydbadmin.Source) if !ok { return nil, fmt.Errorf("invalid source for %q tool: source kind must be `%s`", kind, alloydbadmin.SourceKind) } allParameters := tools.Parameters{ tools.NewStringParameter("project", "The GCP project ID."), tools.NewStringParameter("location", "The location of the cluster (e.g., 'us-central1')."), tools.NewStringParameter("cluster", "The ID of the cluster."), } paramManifest := allParameters.Manifest() description := cfg.Description if description == "" { description = "Retrieves details about a specific AlloyDB cluster." } mcpManifest := tools.GetMcpManifest(cfg.Name, description, cfg.AuthRequired, allParameters) return Tool{ Name: cfg.Name, Kind: kind, Source: s, AllParams: allParameters, manifest: tools.Manifest{Description: description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired}, mcpManifest: mcpManifest, }, nil } // Tool represents the get-cluster tool. type Tool struct { Name string `yaml:"name"` Kind string `yaml:"kind"` Source *alloydbadmin.Source AllParams tools.Parameters 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("invalid or missing 'project' parameter; expected a string") } location, ok := paramsMap["location"].(string) if !ok { return nil, fmt.Errorf("invalid 'location' parameter; expected a string") } cluster, ok := paramsMap["cluster"].(string) if !ok { return nil, fmt.Errorf("invalid 'cluster' parameter; expected a string") } service, err := t.Source.GetService(ctx, string(accessToken)) if err != nil { return nil, err } urlString := fmt.Sprintf("projects/%s/locations/%s/clusters/%s", project, location, cluster) resp, err := service.Projects.Locations.Clusters.Get(urlString).Do() if err != nil { return nil, fmt.Errorf("error getting AlloyDB cluster: %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/tools/firestore/firestoregetrules/firestoregetrules.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 firestoregetrules import ( "context" "fmt" 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" "google.golang.org/api/firebaserules/v1" ) const kind string = "firestore-get-rules" 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 { FirebaseRulesClient() *firebaserules.Service GetProjectId() string GetDatabaseId() string } // 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) } // No parameters needed for this tool parameters := tools.Parameters{} 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, RulesClient: s.FirebaseRulesClient(), ProjectId: s.GetProjectId(), DatabaseId: s.GetDatabaseId(), 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"` RulesClient *firebaserules.Service ProjectId string DatabaseId string manifest tools.Manifest mcpManifest tools.McpManifest } func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { // Get the latest release for Firestore releaseName := fmt.Sprintf("projects/%s/releases/cloud.firestore/%s", t.ProjectId, t.DatabaseId) release, err := t.RulesClient.Projects.Releases.Get(releaseName).Context(ctx).Do() if err != nil { return nil, fmt.Errorf("failed to get latest Firestore release: %w", err) } if release.RulesetName == "" { return nil, fmt.Errorf("no active Firestore rules were found in project '%s' and database '%s'", t.ProjectId, t.DatabaseId) } // Get the ruleset content ruleset, err := t.RulesClient.Projects.Rulesets.Get(release.RulesetName).Context(ctx).Do() if err != nil { return nil, fmt.Errorf("failed to get ruleset content: %w", err) } if ruleset.Source == nil || len(ruleset.Source.Files) == 0 { return nil, fmt.Errorf("no rules files found in ruleset") } return ruleset, 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/postgres/postgreslistinstalledextensions/postgreslistinstalledextensions.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 postgreslistinstalledextensions 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-list-installed-extensions" const listAvailableExtensionsQuery = ` SELECT e.extname AS name, e.extversion AS version, n.nspname AS schema, pg_get_userbyid(e.extowner) AS owner, c.description AS description FROM pg_catalog.pg_extension e LEFT JOIN pg_catalog.pg_namespace n ON n.oid = e.extnamespace LEFT JOIN pg_catalog.pg_description c ON c.objoid = e.oid AND c.classoid = 'pg_catalog.pg_extension'::pg_catalog.regclass ORDER BY 1; ` 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"` 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) } parameters := tools.Parameters{} mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters) // finish tool setup t := Tool{ Name: cfg.Name, Kind: cfg.Kind, AuthRequired: cfg.AuthRequired, Pool: s.PostgresPool(), 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"` Pool *pgxpool.Pool manifest tools.Manifest mcpManifest tools.McpManifest } func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { results, err := t.Pool.Query(ctx, listAvailableExtensionsQuery) 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.ParamValues{}, nil } 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 } ``` -------------------------------------------------------------------------------- /docs/en/resources/tools/couchbase/couchbase-sql.md: -------------------------------------------------------------------------------- ```markdown --- title: "couchbase-sql" type: docs weight: 1 description: > A "couchbase-sql" tool executes a pre-defined SQL statement against a Couchbase database. aliases: - /resources/tools/couchbase-sql --- ## About A `couchbase-sql` tool executes a pre-defined SQL statement against a Couchbase database. It's compatible with any of the following sources: - [couchbase](../../sources/couchbase.md) The specified SQL statement is executed as a parameterized statement, and specified parameters will be used according to their name: e.g. `$id`. ## 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_products_by_category: kind: couchbase-sql source: my-couchbase-instance statement: | SELECT p.name, p.price, p.description FROM products p WHERE p.category = $category AND p.price < $max_price ORDER BY p.price DESC LIMIT 10 description: | Use this tool to get a list of products for a specific category under a maximum price. Takes a category name, e.g. "Electronics" and a maximum price e.g 500 and returns a list of product names, prices, and descriptions. Do NOT use this tool with invalid category names. Do NOT guess a category name, Do NOT guess a price. Example: {{ "category": "Electronics", "max_price": 500 }} Example: {{ "category": "Furniture", "max_price": 1000 }} parameters: - name: category type: string description: Product category name - name: max_price type: integer description: Maximum price (positive integer) ``` ### 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](..#template-parameters). ```yaml tools: list_table: kind: couchbase-sql source: my-couchbase-instance 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 "couchbase-sql". | | source | string | true | Name of the source the SQL query should execute on. | | description | string | true | Description of the tool that is passed to the LLM. | | statement | string | true | SQL statement to execute | | parameters | [parameters](../#specifying-parameters) | false | List of [parameters](../#specifying-parameters) that will be used with the SQL statement. | | templateParameters | [templateParameters](..#template-parameters) | false | List of [templateParameters](..#template-parameters) that will be inserted into the SQL statement before executing prepared statement. | | authRequired | array[string] | false | List of auth services that are required to use this tool. | ``` -------------------------------------------------------------------------------- /internal/util/util.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 util import ( "bytes" "context" "encoding/json" "fmt" "io" "strings" "github.com/go-playground/validator/v10" yaml "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/log" "github.com/googleapis/genai-toolbox/internal/telemetry" ) // DecodeJSON decodes a given reader into an interface using the json decoder. func DecodeJSON(r io.Reader, v interface{}) error { defer io.Copy(io.Discard, r) //nolint:errcheck d := json.NewDecoder(r) // specify JSON numbers should get parsed to json.Number instead of float64 by default. // This prevents loss between floats/ints. d.UseNumber() return d.Decode(v) } // ConvertNumbers traverses an interface and converts all json.Number // instances to int64 or float64. func ConvertNumbers(data any) (any, error) { switch v := data.(type) { // If it's a map, recursively convert the values. case map[string]any: for key, val := range v { convertedVal, err := ConvertNumbers(val) if err != nil { return nil, err } v[key] = convertedVal } return v, nil // If it's a slice, recursively convert the elements. case []any: for i, val := range v { convertedVal, err := ConvertNumbers(val) if err != nil { return nil, err } v[i] = convertedVal } return v, nil // If it's a json.Number, convert it to float or int case json.Number: // Check for a decimal point to decide the type. if strings.Contains(v.String(), ".") { return v.Float64() } return v.Int64() // For all other types, return them as is. default: return data, nil } } var _ yaml.InterfaceUnmarshalerContext = &DelayedUnmarshaler{} // DelayedUnmarshaler is struct that saves the provided unmarshal function // passed to UnmarshalYAML so it can be re-used later once the target interface // is known. type DelayedUnmarshaler struct { unmarshal func(interface{}) error } func (d *DelayedUnmarshaler) UnmarshalYAML(ctx context.Context, unmarshal func(interface{}) error) error { d.unmarshal = unmarshal return nil } func (d *DelayedUnmarshaler) Unmarshal(v interface{}) error { if d.unmarshal == nil { return fmt.Errorf("nothing to unmarshal") } return d.unmarshal(v) } type contextKey string // userAgentKey is the key used to store userAgent within context const userAgentKey contextKey = "userAgent" // WithUserAgent adds a user agent into the context as a value func WithUserAgent(ctx context.Context, versionString string) context.Context { userAgent := "genai-toolbox/" + versionString return context.WithValue(ctx, userAgentKey, userAgent) } // UserAgentFromContext retrieves the user agent or return an error func UserAgentFromContext(ctx context.Context) (string, error) { if ua := ctx.Value(userAgentKey); ua != nil { return ua.(string), nil } else { return "", fmt.Errorf("unable to retrieve user agent") } } func NewStrictDecoder(v interface{}) (*yaml.Decoder, error) { b, err := yaml.Marshal(v) if err != nil { return nil, fmt.Errorf("fail to marshal %q: %w", v, err) } dec := yaml.NewDecoder( bytes.NewReader(b), yaml.Strict(), yaml.Validator(validator.New()), ) return dec, nil } // loggerKey is the key used to store logger within context const loggerKey contextKey = "logger" // WithLogger adds a logger into the context as a value func WithLogger(ctx context.Context, logger log.Logger) context.Context { return context.WithValue(ctx, loggerKey, logger) } // LoggerFromContext retrieves the logger or return an error func LoggerFromContext(ctx context.Context) (log.Logger, error) { if logger, ok := ctx.Value(loggerKey).(log.Logger); ok { return logger, nil } return nil, fmt.Errorf("unable to retrieve logger") } const instrumentationKey contextKey = "instrumentation" // WithInstrumentation adds an instrumentation into the context as a value func WithInstrumentation(ctx context.Context, instrumentation *telemetry.Instrumentation) context.Context { return context.WithValue(ctx, instrumentationKey, instrumentation) } // InstrumentationFromContext retrieves the instrumentation or return an error func InstrumentationFromContext(ctx context.Context) (*telemetry.Instrumentation, error) { if instrumentation, ok := ctx.Value(instrumentationKey).(*telemetry.Instrumentation); ok { return instrumentation, nil } return nil, fmt.Errorf("unable to retrieve instrumentation") } ``` -------------------------------------------------------------------------------- /internal/tools/looker/lookerdeleteprojectfile/lookerdeleteprojectfile.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 lookerdeleteprojectfile 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/looker-open-source/sdk-codegen/go/rtl" v4 "github.com/looker-open-source/sdk-codegen/go/sdk/v4" ) const kind string = "looker-delete-project-file" 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) } projectIdParameter := tools.NewStringParameter("project_id", "The id of the project containing the files") filePathParameter := tools.NewStringParameter("file_path", "The path of the file within the project") parameters := tools.Parameters{projectIdParameter, filePathParameter} 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) { sdk, err := lookercommon.GetLookerSDK(t.UseClientOAuth, t.ApiSettings, t.Client, accessToken) if err != nil { return nil, fmt.Errorf("error getting sdk: %w", err) } mapParams := params.AsMap() projectId, ok := mapParams["project_id"].(string) if !ok { return nil, fmt.Errorf("'project_id' must be a string, got %T", mapParams["project_id"]) } filePath, ok := mapParams["file_path"].(string) if !ok { return nil, fmt.Errorf("'file_path' must be a string, got %T", mapParams["file_path"]) } err = lookercommon.DeleteProjectFile(sdk, projectId, filePath, t.ApiSettings) if err != nil { return nil, fmt.Errorf("error making delete_project_file request: %s", err) } data := make(map[string]any) data["type"] = "text" data["text"] = fmt.Sprintf("deleted file %s in project %s", filePath, projectId) 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 } ``` -------------------------------------------------------------------------------- /docs/en/resources/tools/bigquery/bigquery-sql.md: -------------------------------------------------------------------------------- ```markdown --- title: "bigquery-sql" type: docs weight: 1 description: > A "bigquery-sql" tool executes a pre-defined SQL statement. aliases: - /resources/tools/bigquery-sql --- ## About A `bigquery-sql` tool executes a pre-defined SQL statement. It's compatible with the following sources: - [bigquery](../../sources/bigquery.md) The behavior of this tool is influenced by the `writeMode` setting on its `bigquery` source: - **`allowed` (default) and `blocked`:** These modes do not impose any restrictions on the `bigquery-sql` tool. The pre-defined SQL statement will be executed as-is. - **`protected`:** This mode enables session-based execution. The tool will operate within the same BigQuery session as other tools using the same source, allowing it to interact with temporary resources like `TEMP` tables created within that session. ### GoogleSQL BigQuery uses [GoogleSQL][bigquery-googlesql] for querying data. The integration with Toolbox supports this dialect. The specified SQL statement is executed, and parameters can be inserted into the query. BigQuery supports both named parameters (e.g., `@name`) and positional parameters (`?`), but they cannot be mixed in the same query. [bigquery-googlesql]: https://cloud.google.com/bigquery/docs/reference/standard-sql/ ## Example > **Note:** This tool uses [parameterized > queries](https://cloud.google.com/bigquery/docs/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: # Example: Querying a user table in BigQuery search_users_bq: kind: bigquery-sql source: my-bigquery-source statement: | SELECT id, name, email FROM `my-project.my-dataset.users` WHERE id = @id OR email = @email; description: | Use this tool to get information for a specific user. Takes an id number or a name and returns info on the user. Example: {{ "id": 123, "name": "Alice", }} parameters: - name: id type: integer description: User ID - name: email type: string description: Email address of the user ``` ### 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](../#template-parameters). ```yaml tools: list_table: kind: bigquery-sql source: my-bigquery-source 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 "bigquery-sql". | | source | string | true | Name of the source the GoogleSQL should execute on. | | description | string | true | Description of the tool that is passed to the LLM. | | statement | string | true | The GoogleSQL statement to execute. | | parameters | [parameters](../#specifying-parameters) | false | List of [parameters](../#specifying-parameters) that will be inserted into the SQL statement. | | templateParameters | [templateParameters](../#template-parameters) | false | List of [templateParameters](../#template-parameters) that will be inserted into the SQL statement before executing prepared statement. | ``` -------------------------------------------------------------------------------- /internal/server/web_test.go: -------------------------------------------------------------------------------- ```go package server import ( "io" "net/http" "net/http/httptest" "net/url" "strings" "testing" "github.com/go-chi/chi/v5" "github.com/go-goquery/goquery" ) // TestWebEndpoint tests the routes defined in webRouter mounted under /ui. func TestWebEndpoint(t *testing.T) { mainRouter := chi.NewRouter() webR, err := webRouter() if err != nil { t.Fatalf("Failed to create webRouter: %v", err) } mainRouter.Mount("/ui", webR) ts := httptest.NewServer(mainRouter) defer ts.Close() testCases := []struct { name string path string wantStatus int wantContentType string wantPageTitle string }{ { name: "web index page", path: "/ui", wantStatus: http.StatusOK, wantContentType: "text/html", wantPageTitle: "Toolbox UI", }, { name: "web index page with trailing slash", path: "/ui/", wantStatus: http.StatusOK, wantContentType: "text/html", wantPageTitle: "Toolbox UI", }, { name: "web tools page", path: "/ui/tools", wantStatus: http.StatusOK, wantContentType: "text/html", wantPageTitle: "Tools View", }, { name: "web tools page with trailing slash", path: "/ui/tools/", wantStatus: http.StatusOK, wantContentType: "text/html", wantPageTitle: "Tools View", }, { name: "web toolsets page", path: "/ui/toolsets", wantStatus: http.StatusOK, wantContentType: "text/html", wantPageTitle: "Toolsets View", }, { name: "web toolsets page with trailing slash", path: "/ui/toolsets/", wantStatus: http.StatusOK, wantContentType: "text/html", wantPageTitle: "Toolsets View", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { reqURL := ts.URL + tc.path req, err := http.NewRequest(http.MethodGet, reqURL, nil) if err != nil { t.Fatalf("Failed to create request: %v", err) } client := ts.Client() resp, err := client.Do(req) if err != nil { t.Fatalf("Failed to send request: %v", err) } defer resp.Body.Close() if resp.StatusCode != tc.wantStatus { body, _ := io.ReadAll(resp.Body) t.Fatalf("Unexpected status code for %s: got %d, want %d, body: %s", tc.path, resp.StatusCode, tc.wantStatus, string(body)) } contentType := resp.Header.Get("Content-Type") if !strings.HasPrefix(contentType, tc.wantContentType) { t.Errorf("Unexpected Content-Type header for %s: got %s, want prefix %s", tc.path, contentType, tc.wantContentType) } body, err := io.ReadAll(resp.Body) if err != nil { t.Fatalf("Failed to read response body: %v", err) } doc, err := goquery.NewDocumentFromReader(strings.NewReader(string(body))) if err != nil { t.Fatalf("Failed to parse HTML: %v", err) } gotPageTitle := doc.Find("title").Text() if gotPageTitle != tc.wantPageTitle { t.Errorf("Unexpected page title for %s: got %q, want %q", tc.path, gotPageTitle, tc.wantPageTitle) } pageURL := resp.Request.URL verifyLinkedResources(t, ts, pageURL, doc) }) } } // verifyLinkedResources checks that resources linked in the HTML are served correctly. func verifyLinkedResources(t *testing.T, ts *httptest.Server, pageURL *url.URL, doc *goquery.Document) { t.Helper() selectors := map[string]string{ "stylesheet": "link[rel=stylesheet]", "script": "script[src]", } attrMap := map[string]string{ "stylesheet": "href", "script": "src", } foundResource := false for resourceType, selector := range selectors { doc.Find(selector).Each(func(i int, s *goquery.Selection) { foundResource = true attrName := attrMap[resourceType] resourcePath, exists := s.Attr(attrName) if !exists || resourcePath == "" { t.Errorf("Resource element %s is missing attribute %s on page %s", selector, attrName, pageURL.String()) return } // Resolve the URL relative to the page URL resURL, err := url.Parse(resourcePath) if err != nil { t.Errorf("Failed to parse resource path %q on page %s: %v", resourcePath, pageURL.String(), err) return } absoluteResourceURL := pageURL.ResolveReference(resURL) // Skip external hosts if absoluteResourceURL.Host != pageURL.Host { t.Logf("Skipping resource on different host: %s", absoluteResourceURL.String()) return } resp, err := ts.Client().Get(absoluteResourceURL.String()) if err != nil { t.Errorf("Failed to GET %s resource %s: %v", resourceType, absoluteResourceURL.String(), err) return } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Errorf("Resource %s %s: expected status OK (200), but got %d", resourceType, absoluteResourceURL.String(), resp.StatusCode) } }) } if !foundResource { t.Logf("No stylesheet or script resources found to check on page %s", pageURL.String()) } } ``` -------------------------------------------------------------------------------- /internal/tools/looker/lookerdevmode/lookerdevmode.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 lookerdevmode 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-dev-mode" 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) } devModeParameter := tools.NewBooleanParameterWithDefault("devMode", true, "Whether to set Dev Mode.") parameters := tools.Parameters{devModeParameter} 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, ShowHiddenExplores: s.ShowHiddenExplores, }, 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 ShowHiddenExplores bool } 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) } mapParams := params.AsMap() devMode, ok := mapParams["devMode"].(bool) if !ok { return nil, fmt.Errorf("'devMode' must be a boolean, got %T", mapParams["devMode"]) } sdk, err := lookercommon.GetLookerSDK(t.UseClientOAuth, t.ApiSettings, t.Client, accessToken) if err != nil { return nil, fmt.Errorf("error getting sdk: %w", err) } var devModeString string if devMode { devModeString = "dev" } else { devModeString = "production" } req := v4.WriteApiSession{ WorkspaceId: &devModeString, } resp, err := sdk.UpdateSession(req, t.ApiSettings) if err != nil { return nil, fmt.Errorf("error setting/resetting dev mode: %w", err) } logger.DebugContext(ctx, "result = ", resp) return resp, 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/alloydb/alloydblistusers/alloydblistusers.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 alloydblistusers import ( "context" "fmt" yaml "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/sources" alloydbadmin "github.com/googleapis/genai-toolbox/internal/sources/alloydbadmin" "github.com/googleapis/genai-toolbox/internal/tools" ) const kind string = "alloydb-list-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 } // Configuration for the list-users 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"` BaseURL string `yaml:"baseURL"` } // 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("source %q not found", cfg.Source) } s, ok := rawS.(*alloydbadmin.Source) if !ok { return nil, fmt.Errorf("invalid source for %q tool: source kind must be `%s`", kind, alloydbadmin.SourceKind) } allParameters := tools.Parameters{ tools.NewStringParameter("project", "The GCP project ID."), tools.NewStringParameter("location", "The location of the cluster (e.g., 'us-central1')."), tools.NewStringParameter("cluster", "The ID of the cluster to list users from."), } paramManifest := allParameters.Manifest() description := cfg.Description if description == "" { description = "Lists all AlloyDB users in a given project, location and cluster." } mcpManifest := tools.GetMcpManifest(cfg.Name, description, cfg.AuthRequired, allParameters) return Tool{ Name: cfg.Name, Kind: kind, Source: s, AllParams: allParameters, manifest: tools.Manifest{Description: description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired}, mcpManifest: mcpManifest, }, nil } // Tool represents the list-users tool. type Tool struct { Name string `yaml:"name"` Kind string `yaml:"kind"` Description string `yaml:"description"` Source *alloydbadmin.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("invalid or missing 'project' parameter; expected a string") } location, ok := paramsMap["location"].(string) if !ok { return nil, fmt.Errorf("invalid 'location' parameter; expected a string") } cluster, ok := paramsMap["cluster"].(string) if !ok { return nil, fmt.Errorf("invalid 'cluster' parameter; expected a string") } service, err := t.Source.GetService(ctx, string(accessToken)) if err != nil { return nil, err } urlString := fmt.Sprintf("projects/%s/locations/%s/clusters/%s", project, location, cluster) resp, err := service.Projects.Locations.Clusters.Users.List(urlString).Do() if err != nil { return nil, fmt.Errorf("error listing AlloyDB users: %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/tools/mongodb/mongodbinsertmany/mongodbinsertmany.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 mongodbinsertmany import ( "context" "errors" "fmt" "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/sources" mongosrc "github.com/googleapis/genai-toolbox/internal/sources/mongodb" "github.com/googleapis/genai-toolbox/internal/tools" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) const kind string = "mongodb-insert-many" const paramDataKey = "data" 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"` AuthRequired []string `yaml:"authRequired" validate:"required"` Description string `yaml:"description" validate:"required"` Database string `yaml:"database" validate:"required"` Collection string `yaml:"collection" validate:"required"` Canonical bool `yaml:"canonical" validate:"required"` //i want to force the user to choose } // 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.(*mongosrc.Source) if !ok { return nil, fmt.Errorf("invalid source for %q tool: source kind must be `mongodb`", kind) } dataParam := tools.NewStringParameterWithRequired(paramDataKey, "the JSON payload to insert, should be a JSON array of documents", true) allParameters := tools.Parameters{dataParam} // Create Toolbox manifest paramManifest := allParameters.Manifest() if paramManifest == nil { paramManifest = make([]tools.ParameterManifest, 0) } // Create MCP manifest mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, allParameters) // finish tool setup return Tool{ Name: cfg.Name, Kind: kind, AuthRequired: cfg.AuthRequired, Collection: cfg.Collection, Canonical: cfg.Canonical, PayloadParams: allParameters, database: s.Client.Database(cfg.Database), manifest: tools.Manifest{Description: cfg.Description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired}, mcpManifest: mcpManifest, }, nil } // validate interface var _ tools.Tool = Tool{} type Tool struct { Name string `yaml:"name"` Kind string `yaml:"kind"` AuthRequired []string `yaml:"authRequired"` Description string `yaml:"description"` Collection string `yaml:"collection"` Canonical bool `yaml:"canonical" validation:"required"` //i want to force the user to choose PayloadParams tools.Parameters database *mongo.Database manifest tools.Manifest mcpManifest tools.McpManifest } func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { if len(params) == 0 { return nil, errors.New("no input found") } paramsMap := params.AsMap() var jsonData, ok = paramsMap[paramDataKey].(string) if !ok { return nil, errors.New("no input found") } var data = []any{} err := bson.UnmarshalExtJSON([]byte(jsonData), t.Canonical, &data) if err != nil { return nil, err } res, err := t.database.Collection(t.Collection).InsertMany(ctx, data, options.InsertMany()) if err != nil { return nil, err } return res.InsertedIDs, nil } func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { return tools.ParseParams(t.PayloadParams, 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 } ``` -------------------------------------------------------------------------------- /docs/en/resources/tools/trino/trino-sql.md: -------------------------------------------------------------------------------- ```markdown --- title: "trino-sql" type: docs weight: 1 description: > A "trino-sql" tool executes a pre-defined SQL statement against a Trino database. aliases: - /resources/tools/trino-sql --- ## About A `trino-sql` tool executes a pre-defined SQL statement against a Trino database. It's compatible with any of the following sources: - [trino](../../sources/trino.md) The specified SQL statement is executed as a [prepared statement][trino-prepare], and specified parameters will be inserted according to their position: e.g. `$1` will be the first parameter specified, `$2` will be the second parameter, and so on. If template parameters are included, they will be resolved before execution of the prepared statement. [trino-prepare]: https://trino.io/docs/current/sql/prepare.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_orders_by_region: kind: trino-sql source: my-trino-instance statement: | SELECT * FROM hive.sales.orders WHERE region = $1 AND order_date >= DATE($2) LIMIT 10 description: | Use this tool to get information for orders in a specific region. Takes a region code and date and returns info on the orders. Do NOT use this tool with an order id. Do NOT guess a region code or date. A region code is a code for a geographic region consisting of two-character region designator and followed by optional subregion. For example, if given US-WEST, the region is "US-WEST". Another example for this is EU-CENTRAL, the region is "EU-CENTRAL". If the tool returns more than one option choose the date closest to today. Example: {{ "region": "US-WEST", "order_date": "2024-01-01", }} Example: {{ "region": "EU-CENTRAL", "order_date": "2024-01-15", }} parameters: - name: region type: string description: Region unique identifier - name: order_date type: string description: Order 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](..#template-parameters). ```yaml tools: list_table: kind: trino-sql source: my-trino-instance statement: | SELECT * FROM {{.tableName}} description: | Use this tool to list all information from a specific table. Example: {{ "tableName": "hive.sales.orders", }} templateParameters: - name: tableName type: string description: Table to select from ``` ## Reference | **field** | **type** | **required** | **description** | |---------------------|:---------------------------------------------------------:|:------------:|--------------------------------------------------------------------------------------------------------------------------------------------| | kind | string | true | Must be "trino-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](../#specifying-parameters) | false | List of [parameters](../#specifying-parameters) that will be inserted into the SQL statement. | | templateParameters | [templateParameters](..#template-parameters) | false | List of [templateParameters](..#template-parameters) that will be inserted into the SQL statement before executing prepared statement. | ``` -------------------------------------------------------------------------------- /internal/tools/looker/lookergetmodels/lookergetmodels.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 lookergetmodels 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-models" 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) } parameters := tools.Parameters{} 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, ShowHiddenModels: s.ShowHiddenModels, }, 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 ShowHiddenModels bool } 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) } excludeEmpty := false excludeHidden := !t.ShowHiddenModels includeInternal := true 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.RequestAllLookmlModels{ ExcludeEmpty: &excludeEmpty, ExcludeHidden: &excludeHidden, IncludeInternal: &includeInternal, } resp, err := sdk.AllLookmlModels(req, t.ApiSettings) if err != nil { return nil, fmt.Errorf("error making get_models 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) vMap["label"] = *v.Label vMap["name"] = *v.Name vMap["project_name"] = *v.ProjectName 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.ParamValues{}, nil } 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/prebuiltconfigs/tools/sqlite.yaml: -------------------------------------------------------------------------------- ```yaml # 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. sources: sqlite-source: kind: sqlite database: ${SQLITE_DATABASE} tools: execute_sql: kind: sqlite-execute-sql source: sqlite-source description: Use this tool to execute SQL. list_tables: kind: sqlite-sql source: sqlite-source description: "Lists SQLite tables. Use 'output_format' ('simple'/'detailed') and 'table_names' (comma-separated or empty) to control output." statement: | WITH table_columns AS ( SELECT m.name AS table_name, json_group_array(json_object('column_name', ti.name, 'data_type', ti.type, 'ordinal_position', ti.cid, 'is_not_nullable', ti."notnull" = 1, 'column_default', ti.dflt_value, 'is_primary_key', ti.pk > 0)) AS details FROM sqlite_master AS m, pragma_table_info(m.name) AS ti WHERE m.type = 'table' AND m.name NOT LIKE 'sqlite_%' GROUP BY m.name ), table_constraints AS ( SELECT table_name, json_group_array(json(details)) AS details FROM ( SELECT m.name AS table_name, json_object('constraint_name', 'PRIMARY', 'constraint_type', 'PRIMARY KEY', 'constraint_columns', json_group_array(T.name)) AS details FROM sqlite_master AS m, pragma_table_info(m.name) AS T WHERE m.type = 'table' AND T.pk > 0 GROUP BY m.name HAVING COUNT(T.name) > 0 UNION ALL SELECT m.name, json_object('constraint_name', 'fk_' || m.name || '_' || F.id, 'constraint_type', 'FOREIGN KEY', 'constraint_columns', json_group_array(F."from"), 'foreign_key_referenced_table', F."table", 'foreign_key_referenced_columns', json_group_array(F."to")) FROM sqlite_master AS m, pragma_foreign_key_list(m.name) AS F WHERE m.type = 'table' GROUP BY m.name, F.id UNION ALL SELECT m.name, json_object('constraint_name', I.name, 'constraint_type', 'UNIQUE', 'constraint_columns', (SELECT json_group_array(C.name) FROM pragma_index_info(I.name) AS C ORDER BY C.seqno)) FROM sqlite_master AS m, pragma_index_list(m.name) AS I WHERE m.type = 'table' AND I."unique" = 1 AND I.origin != 'pk' ) GROUP BY table_name ), table_indexes AS ( SELECT m.name AS table_name, json_group_array(json_object('index_name', il.name, 'is_unique', il."unique" = 1, 'is_primary', il.origin = 'pk', 'index_columns', (SELECT json_group_array(ii.name) FROM pragma_index_info(il.name) AS ii))) AS details FROM sqlite_master AS m, pragma_index_list(m.name) AS il WHERE m.type = 'table' AND m.name NOT LIKE 'sqlite_%' GROUP BY m.name ), table_triggers AS ( SELECT tbl_name AS table_name, json_group_array(json_object('trigger_name', name, 'trigger_definition', sql)) AS details FROM sqlite_master WHERE type = 'trigger' GROUP BY tbl_name ) SELECT CASE WHEN '{{.output_format}}' = 'simple' THEN json_object('name', m.name) ELSE json_object( 'schema_name', 'main', 'object_name', m.name, 'object_type', m.type, 'columns', json(COALESCE(tc.details, '[]')), 'constraints', json(COALESCE(tcons.details, '[]')), 'indexes', json(COALESCE(ti.details, '[]')), 'triggers', json(COALESCE(tt.details, '[]')) ) END AS object_details FROM sqlite_master AS m LEFT JOIN table_columns tc ON m.name = tc.table_name LEFT JOIN table_constraints tcons ON m.name = tcons.table_name LEFT JOIN table_indexes ti ON m.name = ti.table_name LEFT JOIN table_triggers tt ON m.name = tt.table_name WHERE m.type = 'table' AND m.name NOT LIKE 'sqlite_%' {{if .table_names}} AND instr(',' || '{{.table_names}}' || ',', ',' || m.name || ',') > 0 {{end}}; templateParameters: - name: output_format type: string description: "Optional: Use 'simple' to return table names only or use 'detailed' to return the full information schema." default: "detailed" - name: table_names type: string description: "Optional: A comma-separated list of table names. If empty, details for all tables in user-accessible schemas will be listed." default: "" toolsets: sqlite_database_tools: - execute_sql - list_tables ``` -------------------------------------------------------------------------------- /internal/sources/oracle/oracle.go: -------------------------------------------------------------------------------- ```go // Copyright © 2025, Oracle and/or its affiliates. package oracle import ( "context" "database/sql" "fmt" "os" "strings" "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/sources" "github.com/googleapis/genai-toolbox/internal/util" _ "github.com/sijms/go-ora/v2" "go.opentelemetry.io/otel/trace" ) const SourceKind string = "oracle" // validate interface var _ sources.SourceConfig = Config{} func init() { if !sources.Register(SourceKind, newConfig) { panic(fmt.Sprintf("source kind %q already registered", SourceKind)) } } func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) { actual := Config{Name: name} if err := decoder.DecodeContext(ctx, &actual); err != nil { return nil, err } // Validate that we have one of: tns_alias, connection_string, or host+service_name if err := actual.validate(); err != nil { return nil, fmt.Errorf("invalid Oracle configuration: %w", err) } return actual, nil } type Config struct { Name string `yaml:"name" validate:"required"` Kind string `yaml:"kind" validate:"required"` ConnectionString string `yaml:"connectionString,omitempty"` // Direct connection string (hostname[:port]/servicename) TnsAlias string `yaml:"tnsAlias,omitempty"` // TNS alias from tnsnames.ora Host string `yaml:"host,omitempty"` // Optional when using connectionString/tnsAlias Port int `yaml:"port,omitempty"` // Explicit port support ServiceName string `yaml:"serviceName,omitempty"` // Optional when using connectionString/tnsAlias User string `yaml:"user" validate:"required"` Password string `yaml:"password" validate:"required"` TnsAdmin string `yaml:"tnsAdmin,omitempty"` // Optional: override TNS_ADMIN environment variable } // validate ensures we have one of: tns_alias, connection_string, or host+service_name func (c Config) validate() error { hasTnsAlias := strings.TrimSpace(c.TnsAlias) != "" hasConnStr := strings.TrimSpace(c.ConnectionString) != "" hasHostService := strings.TrimSpace(c.Host) != "" && strings.TrimSpace(c.ServiceName) != "" connectionMethods := 0 if hasTnsAlias { connectionMethods++ } if hasConnStr { connectionMethods++ } if hasHostService { connectionMethods++ } if connectionMethods == 0 { return fmt.Errorf("must provide one of: 'tns_alias', 'connection_string', or both 'host' and 'service_name'") } if connectionMethods > 1 { return fmt.Errorf("provide only one connection method: 'tns_alias', 'connection_string', or 'host'+'service_name'") } return nil } func (r Config) SourceConfigKind() string { return SourceKind } func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) { db, err := initOracleConnection(ctx, tracer, r) if err != nil { return nil, fmt.Errorf("unable to create Oracle connection: %w", err) } err = db.PingContext(ctx) if err != nil { return nil, fmt.Errorf("unable to connect to Oracle successfully: %w", err) } s := &Source{ Name: r.Name, Kind: SourceKind, DB: db, } return s, nil } var _ sources.Source = &Source{} type Source struct { Name string `yaml:"name"` Kind string `yaml:"kind"` DB *sql.DB } func (s *Source) SourceKind() string { return SourceKind } func (s *Source) OracleDB() *sql.DB { return s.DB } func initOracleConnection(ctx context.Context, tracer trace.Tracer, config Config) (*sql.DB, error) { //nolint:all // Reassigned ctx ctx, span := sources.InitConnectionSpan(ctx, tracer, SourceKind, config.Name) defer span.End() logger, err := util.LoggerFromContext(ctx) if err != nil { panic(err) } // Set TNS_ADMIN environment variable if specified in config. if config.TnsAdmin != "" { originalTnsAdmin := os.Getenv("TNS_ADMIN") os.Setenv("TNS_ADMIN", config.TnsAdmin) logger.DebugContext(ctx, fmt.Sprintf("Setting TNS_ADMIN to: %s\n", config.TnsAdmin)) // Restore original TNS_ADMIN after connection defer func() { if originalTnsAdmin != "" { os.Setenv("TNS_ADMIN", originalTnsAdmin) } else { os.Unsetenv("TNS_ADMIN") } }() } var serverString string if config.TnsAlias != "" { // Use TNS alias serverString = strings.TrimSpace(config.TnsAlias) } else if config.ConnectionString != "" { // Use provided connection string directly (hostname[:port]/servicename format) serverString = strings.TrimSpace(config.ConnectionString) } else { // Build connection string from host and service_name if config.Port > 0 { serverString = fmt.Sprintf("%s:%d/%s", config.Host, config.Port, config.ServiceName) } else { serverString = fmt.Sprintf("%s/%s", config.Host, config.ServiceName) } } connStr := fmt.Sprintf("oracle://%s:%s@%s", config.User, config.Password, serverString) db, err := sql.Open("oracle", connStr) if err != nil { return nil, fmt.Errorf("unable to open Oracle connection: %w", err) } return db, nil } ``` -------------------------------------------------------------------------------- /internal/tools/mongodb/mongodbinsertone/mongodbinsertone.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 mongodbinsertone import ( "context" "errors" "fmt" "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/sources" mongosrc "github.com/googleapis/genai-toolbox/internal/sources/mongodb" "github.com/googleapis/genai-toolbox/internal/tools" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) const kind string = "mongodb-insert-one" const dataParamsKey = "data" 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"` AuthRequired []string `yaml:"authRequired" validate:"required"` Description string `yaml:"description" validate:"required"` Database string `yaml:"database" validate:"required"` Collection string `yaml:"collection" validate:"required"` Canonical bool `yaml:"canonical" validate:"required"` //i want to force the user to choose } // 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.(*mongosrc.Source) if !ok { return nil, fmt.Errorf("invalid source for %q tool: source kind must be `mongodb`", kind) } payloadParams := tools.NewStringParameterWithRequired(dataParamsKey, "the JSON payload to insert, should be a JSON object", true) allParameters := tools.Parameters{payloadParams} // Create Toolbox manifest paramManifest := allParameters.Manifest() if paramManifest == nil { paramManifest = make([]tools.ParameterManifest, 0) } // Create MCP manifest mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, allParameters) // finish tool setup return Tool{ Name: cfg.Name, Kind: kind, AuthRequired: cfg.AuthRequired, Collection: cfg.Collection, Canonical: cfg.Canonical, PayloadParams: allParameters, database: s.Client.Database(cfg.Database), manifest: tools.Manifest{Description: cfg.Description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired}, mcpManifest: mcpManifest, }, nil } // validate interface var _ tools.Tool = Tool{} type Tool struct { Name string `yaml:"name"` Kind string `yaml:"kind"` AuthRequired []string `yaml:"authRequired"` Description string `yaml:"description"` Collection string `yaml:"collection"` Canonical bool `yaml:"canonical" validation:"required"` PayloadParams tools.Parameters `yaml:"payloadParams" validate:"required"` database *mongo.Database manifest tools.Manifest mcpManifest tools.McpManifest } func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { if len(params) == 0 { return nil, errors.New("no input found") } // use the first, assume it's a string var jsonData, ok = params[0].Value.(string) if !ok { return nil, errors.New("no input found") } var data any err := bson.UnmarshalExtJSON([]byte(jsonData), t.Canonical, &data) if err != nil { return nil, err } res, err := t.database.Collection(t.Collection).InsertOne(ctx, data, options.InsertOne()) if err != nil { return nil, err } return res.InsertedID, nil } func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { return tools.ParseParams(t.PayloadParams, 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/postgres/postgresexecutesql/postgresexecutesql.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 postgresexecutesql 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/googleapis/genai-toolbox/internal/util" "github.com/jackc/pgx/v5/pgxpool" ) const kind string = "postgres-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 { 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"` 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.PostgresPool(), 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 *pgxpool.Pool 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.Query(ctx, sql) 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.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/cloudsqllistdatabases/cloudsqllistdatabases.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 cloudsqllistdatabases import ( "context" "fmt" "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/sources" cloudsqladminsrc "github.com/googleapis/genai-toolbox/internal/sources/cloudsqladmin" "github.com/googleapis/genai-toolbox/internal/tools" ) const kind string = "cloud-sql-list-databases" 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 list-databases 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.(*cloudsqladminsrc.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 instance ID"), } paramManifest := allParameters.Manifest() description := cfg.Description if description == "" { description = "Lists all databases for a Cloud SQL instance." } 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: description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired}, mcpManifest: mcpManifest, }, nil } // Tool represents the list-databases tool. type Tool struct { Name string `yaml:"name"` Kind string `yaml:"kind"` Description string `yaml:"description"` AuthRequired []string `yaml:"authRequired"` AllParams tools.Parameters `yaml:"allParams"` Source *cloudsqladminsrc.Source 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") } service, err := t.Source.GetService(ctx, string(accessToken)) if err != nil { return nil, err } resp, err := service.Databases.List(project, instance).Do() if err != nil { return nil, fmt.Errorf("error listing databases: %w", err) } if resp.Items == nil { return []any{}, nil } type databaseInfo struct { Name string `json:"name"` Charset string `json:"charset"` Collation string `json:"collation"` } var databases []databaseInfo for _, item := range resp.Items { databases = append(databases, databaseInfo{ Name: item.Name, Charset: item.Charset, Collation: item.Collation, }) } return databases, 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/tools/cloudsql/cloudsqlcreatedatabase/cloudsqlcreatedatabase.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 cloudsqlcreatedatabase 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-database" 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-database 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 database will be created."), tools.NewStringParameter("name", "The name for the new database. Must be unique within the instance."), } paramManifest := allParameters.Manifest() description := cfg.Description if description == "" { description = "Creates a new database in a Cloud SQL instance." } 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-database 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") } database := sqladmin.Database{ Name: name, Project: project, Instance: instance, } service, err := t.Source.GetService(ctx, string(accessToken)) if err != nil { return nil, err } resp, err := service.Databases.Insert(project, instance, &database).Do() if err != nil { return nil, fmt.Errorf("error creating database: %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() } ``` -------------------------------------------------------------------------------- /docs/en/resources/tools/mongodb/mongodb-update-many.md: -------------------------------------------------------------------------------- ```markdown --- title: "mongodb-update-many" type: docs weight: 1 description: > A "mongodb-update-many" tool updates all documents in a MongoDB collection that match a filter. aliases: - /resources/tools/mongodb-update-many --- ## About A `mongodb-update-many` tool updates **all** documents within a specified MongoDB collection that match a given filter. It locates the documents using a `filterPayload` and applies the modifications defined in an `updatePayload`. The tool returns an array of three integers: `[ModifiedCount, UpsertedCount, MatchedCount]`. This tool is compatible with the following source kind: * [`mongodb`](../../sources/mongodb.md) --- ## Example Here's an example configuration. This tool applies a discount to all items within a specific category and also marks them as being on sale. ```yaml tools: apply_category_discount: kind: mongodb-update-many source: my-mongo-source description: Use this tool to apply a discount to all items in a given category. database: products collection: inventory filterPayload: | { "category": {{json .category_name}} } filterParams: - name: category_name type: string description: The category of items to update. updatePayload: | { "$mul": { "price": {{json .discount_multiplier}} }, "$set": { "on_sale": true } } updateParams: - name: discount_multiplier type: number description: The multiplier to apply to the price (e.g., 0.8 for a 20% discount). canonical: false upsert: false ``` ## Reference | **field** | **type** | **required** | **description** | |:--------------|:---------|:-------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | kind | string | true | Must be `mongodb-update-many`. | | source | string | true | The name of the `mongodb` source to use. | | description | string | true | A description of the tool that is passed to the LLM. | | database | string | true | The name of the MongoDB database containing the collection. | | collection | string | true | The name of the MongoDB collection in which to update documents. | | filterPayload | string | true | The MongoDB query filter document to select the documents for updating. It's written as a Go template, using `{{json .param_name}}` to insert parameters. | | filterParams | list | true | A list of parameter objects that define the variables used in the `filterPayload`. | | updatePayload | string | true | The MongoDB update document, It's written as a Go template, using `{{json .param_name}}` to insert parameters. | | updateParams | list | true | A list of parameter objects that define the variables used in the `updatePayload`. | | canonical | bool | true | Determines if the `filterPayload` and `updatePayload` strings are parsed using MongoDB's Canonical or Relaxed Extended JSON format. **Canonical** is stricter about type representation, while **Relaxed** is more lenient. | | upsert | bool | false | If `true`, a new document is created if no document matches the `filterPayload`. Defaults to `false`. | ``` -------------------------------------------------------------------------------- /internal/tools/cloudmonitoring/cloudmonitoring.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 cloudmonitoring import ( "context" "encoding/json" "fmt" "io" "net/http" "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/sources" cloudmonitoringsrc "github.com/googleapis/genai-toolbox/internal/sources/cloudmonitoring" "github.com/googleapis/genai-toolbox/internal/tools" ) const kind string = "cloud-monitoring-query-prometheus" 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.(*cloudmonitoringsrc.Source) if !ok { return nil, fmt.Errorf("invalid source for %q tool: source kind must be `cloudmonitoring`", kind) } // Define the parameters internally instead of from the config file. allParameters := tools.Parameters{ tools.NewStringParameterWithRequired("projectId", "The Id of the Google Cloud project.", true), tools.NewStringParameterWithRequired("query", "The promql query to execute.", true), } mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, allParameters) return Tool{ Name: cfg.Name, Kind: kind, Description: cfg.Description, AllParams: allParameters, BaseURL: s.BaseURL, UserAgent: s.UserAgent, Client: s.Client, manifest: tools.Manifest{Description: cfg.Description, Parameters: allParameters.Manifest()}, mcpManifest: mcpManifest, }, nil } // validate interface var _ tools.Tool = Tool{} type Tool struct { Name string `yaml:"name"` Kind string `yaml:"kind"` Description string `yaml:"description"` AllParams tools.Parameters `yaml:"allParams"` BaseURL string `yaml:"baseURL"` UserAgent string Client *http.Client manifest tools.Manifest mcpManifest tools.McpManifest } func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { paramsMap := params.AsMap() projectID, ok := paramsMap["projectId"].(string) if !ok { return nil, fmt.Errorf("projectId parameter not found or not a string") } query, ok := paramsMap["query"].(string) if !ok { return nil, fmt.Errorf("query parameter not found or not a string") } url := fmt.Sprintf("%s/v1/projects/%s/location/global/prometheus/api/v1/query", t.BaseURL, projectID) req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { return nil, err } q := req.URL.Query() q.Add("query", query) req.URL.RawQuery = q.Encode() req.Header.Set("User-Agent", t.UserAgent) resp, err := t.Client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response body: %w", err) } if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("request failed: %s, body: %s", resp.Status, string(body)) } if len(body) == 0 { return nil, nil } var result map[string]any if err := json.Unmarshal(body, &result); err != nil { return nil, fmt.Errorf("failed to unmarshal json: %w, body: %s", err, string(body)) } return result, 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 true } func (t Tool) RequiresClientAuthorization() bool { return false } ``` -------------------------------------------------------------------------------- /internal/tools/alloydb/alloydbgetuser/alloydbgetuser.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 alloydbgetuser import ( "context" "fmt" yaml "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/sources" alloydbadmin "github.com/googleapis/genai-toolbox/internal/sources/alloydbadmin" "github.com/googleapis/genai-toolbox/internal/tools" ) const kind string = "alloydb-get-user" 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 } // Configuration for the get-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"` BaseURL string `yaml:"baseURL"` } // 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("source %q not found", cfg.Source) } s, ok := rawS.(*alloydbadmin.Source) if !ok { return nil, fmt.Errorf("invalid source for %q tool: source kind must be `%s`", kind, alloydbadmin.SourceKind) } allParameters := tools.Parameters{ tools.NewStringParameter("project", "The GCP project ID."), tools.NewStringParameter("location", "The location of the cluster (e.g., 'us-central1')."), tools.NewStringParameter("cluster", "The ID of the cluster."), tools.NewStringParameter("user", "The ID of the user."), } paramManifest := allParameters.Manifest() description := cfg.Description if description == "" { description = "Retrieves details about a specific AlloyDB user." } mcpManifest := tools.GetMcpManifest(cfg.Name, description, cfg.AuthRequired, allParameters) return Tool{ Name: cfg.Name, Kind: kind, Source: s, AllParams: allParameters, manifest: tools.Manifest{Description: description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired}, mcpManifest: mcpManifest, }, nil } // Tool represents the get-user tool. type Tool struct { Name string `yaml:"name"` Kind string `yaml:"kind"` Source *alloydbadmin.Source AllParams tools.Parameters 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("invalid or missing 'project' parameter; expected a string") } location, ok := paramsMap["location"].(string) if !ok { return nil, fmt.Errorf("invalid 'location' parameter; expected a string") } cluster, ok := paramsMap["cluster"].(string) if !ok { return nil, fmt.Errorf("invalid 'cluster' parameter; expected a string") } user, ok := paramsMap["user"].(string) if !ok { return nil, fmt.Errorf("invalid 'user' parameter; expected a string") } service, err := t.Source.GetService(ctx, string(accessToken)) if err != nil { return nil, err } urlString := fmt.Sprintf("projects/%s/locations/%s/clusters/%s/users/%s", project, location, cluster, user) resp, err := service.Projects.Locations.Clusters.Users.Get(urlString).Do() if err != nil { return nil, fmt.Errorf("error getting AlloyDB 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() } ``` -------------------------------------------------------------------------------- /docs/en/resources/tools/mssql/mssql-sql.md: -------------------------------------------------------------------------------- ```markdown --- title: "mssql-sql" type: docs weight: 1 description: > A "mssql-sql" tool executes a pre-defined SQL statement against a SQL Server database. aliases: - /resources/tools/mssql-sql --- ## About A `mssql-sql` tool executes a pre-defined SQL statement against a SQL Server database. It's compatible with any of the following sources: - [cloud-sql-mssql](../../sources/cloud-sql-mssql.md) - [mssql](../../sources/mssql.md) Toolbox supports the [prepare statement syntax][prepare-statement] of MS SQL Server and expects parameters in the SQL query to be in the form of either `@Name` or `@p1` to `@pN` (ordinal position). ```go db.QueryContext(ctx, `select * from t where ID = @ID and Name = @p2;`, sql.Named("ID", 6), "Bob") ``` [prepare-statement]: https://learn.microsoft.com/sql/relational-databases/system-stored-procedures/sp-prepare-transact-sql?view=sql-server-ver16 ## 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: mssql-sql source: my-instance statement: | SELECT * FROM flights WHERE airline = @airline AND flight_number = @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 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](..#template-parameters). ```yaml tools: list_table: kind: mssql-sql source: my-instance 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 "mssql-sql". | | source | string | true | Name of the source the T-SQL statement should execute on. | | description | string | true | Description of the tool that is passed to the LLM. | | statement | string | true | SQL statement to execute. | | parameters | [parameters](../#specifying-parameters) | false | List of [parameters](../#specifying-parameters) that will be inserted into the SQL statement. | | templateParameters | [templateParameters](..#template-parameters) | false | List of [templateParameters](..#template-parameters) that will be inserted into the SQL statement before executing prepared statement. | ```