This is page 7 of 33. 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-get-dashboards.md │ │ │ ├── looker-get-dimensions.md │ │ │ ├── looker-get-explores.md │ │ │ ├── looker-get-filters.md │ │ │ ├── looker-get-looks.md │ │ │ ├── looker-get-measures.md │ │ │ ├── looker-get-models.md │ │ │ ├── looker-get-parameters.md │ │ │ ├── looker-health-analyze.md │ │ │ ├── looker-health-pulse.md │ │ │ ├── looker-health-vacuum.md │ │ │ ├── looker-make-dashboard.md │ │ │ ├── looker-make-look.md │ │ │ ├── looker-query-sql.md │ │ │ ├── looker-query-url.md │ │ │ ├── looker-query.md │ │ │ └── looker-run-look.md │ │ ├── mongodb │ │ │ ├── _index.md │ │ │ ├── mongodb-aggregate.md │ │ │ ├── mongodb-delete-many.md │ │ │ ├── mongodb-delete-one.md │ │ │ ├── mongodb-find-one.md │ │ │ ├── mongodb-find.md │ │ │ ├── mongodb-insert-many.md │ │ │ ├── mongodb-insert-one.md │ │ │ ├── mongodb-update-many.md │ │ │ └── mongodb-update-one.md │ │ ├── mssql │ │ │ ├── _index.md │ │ │ ├── mssql-execute-sql.md │ │ │ ├── mssql-list-tables.md │ │ │ └── mssql-sql.md │ │ ├── mysql │ │ │ ├── _index.md │ │ │ ├── mysql-execute-sql.md │ │ │ ├── mysql-list-active-queries.md │ │ │ ├── mysql-list-table-fragmentation.md │ │ │ ├── mysql-list-tables-missing-unique-indexes.md │ │ │ ├── mysql-list-tables.md │ │ │ └── mysql-sql.md │ │ ├── neo4j │ │ │ ├── _index.md │ │ │ ├── neo4j-cypher.md │ │ │ ├── neo4j-execute-cypher.md │ │ │ └── neo4j-schema.md │ │ ├── oceanbase │ │ │ ├── _index.md │ │ │ ├── oceanbase-execute-sql.md │ │ │ └── oceanbase-sql.md │ │ ├── oracle │ │ │ ├── _index.md │ │ │ ├── oracle-execute-sql.md │ │ │ └── oracle-sql.md │ │ ├── postgres │ │ │ ├── _index.md │ │ │ ├── postgres-execute-sql.md │ │ │ ├── postgres-list-active-queries.md │ │ │ ├── postgres-list-available-extensions.md │ │ │ ├── postgres-list-installed-extensions.md │ │ │ ├── postgres-list-tables.md │ │ │ └── postgres-sql.md │ │ ├── redis │ │ │ ├── _index.md │ │ │ └── redis.md │ │ ├── spanner │ │ │ ├── _index.md │ │ │ ├── spanner-execute-sql.md │ │ │ ├── spanner-list-tables.md │ │ │ └── spanner-sql.md │ │ ├── sqlite │ │ │ ├── _index.md │ │ │ ├── sqlite-execute-sql.md │ │ │ └── sqlite-sql.md │ │ ├── tidb │ │ │ ├── _index.md │ │ │ ├── tidb-execute-sql.md │ │ │ └── tidb-sql.md │ │ ├── trino │ │ │ ├── _index.md │ │ │ ├── trino-execute-sql.md │ │ │ └── trino-sql.md │ │ ├── utility │ │ │ ├── _index.md │ │ │ └── wait.md │ │ ├── valkey │ │ │ ├── _index.md │ │ │ └── valkey.md │ │ └── yuagbytedb │ │ ├── _index.md │ │ └── yugabytedb-sql.md │ ├── samples │ │ ├── _index.md │ │ ├── alloydb │ │ │ ├── _index.md │ │ │ ├── ai-nl │ │ │ │ ├── alloydb_ai_nl.ipynb │ │ │ │ └── index.md │ │ │ └── mcp_quickstart.md │ │ ├── bigquery │ │ │ ├── _index.md │ │ │ ├── colab_quickstart_bigquery.ipynb │ │ │ ├── local_quickstart.md │ │ │ └── mcp_quickstart │ │ │ ├── _index.md │ │ │ ├── inspector_tools.png │ │ │ └── inspector.png │ │ └── looker │ │ ├── _index.md │ │ ├── looker_gemini_oauth │ │ │ ├── _index.md │ │ │ ├── authenticated.png │ │ │ ├── authorize.png │ │ │ └── registration.png │ │ ├── looker_gemini.md │ │ └── looker_mcp_inspector │ │ ├── _index.md │ │ ├── inspector_tools.png │ │ └── inspector.png │ └── sdks │ ├── _index.md │ ├── go-sdk.md │ ├── js-sdk.md │ └── python-sdk.md ├── gemini-extension.json ├── go.mod ├── go.sum ├── internal │ ├── auth │ │ ├── auth.go │ │ └── google │ │ └── google.go │ ├── log │ │ ├── handler.go │ │ ├── log_test.go │ │ ├── log.go │ │ └── logger.go │ ├── prebuiltconfigs │ │ ├── prebuiltconfigs_test.go │ │ ├── prebuiltconfigs.go │ │ └── tools │ │ ├── alloydb-postgres-admin.yaml │ │ ├── alloydb-postgres-observability.yaml │ │ ├── alloydb-postgres.yaml │ │ ├── bigquery.yaml │ │ ├── clickhouse.yaml │ │ ├── cloud-sql-mssql-admin.yaml │ │ ├── cloud-sql-mssql-observability.yaml │ │ ├── cloud-sql-mssql.yaml │ │ ├── cloud-sql-mysql-admin.yaml │ │ ├── cloud-sql-mysql-observability.yaml │ │ ├── cloud-sql-mysql.yaml │ │ ├── cloud-sql-postgres-admin.yaml │ │ ├── cloud-sql-postgres-observability.yaml │ │ ├── cloud-sql-postgres.yaml │ │ ├── dataplex.yaml │ │ ├── firestore.yaml │ │ ├── looker-conversational-analytics.yaml │ │ ├── looker.yaml │ │ ├── mssql.yaml │ │ ├── mysql.yaml │ │ ├── neo4j.yaml │ │ ├── oceanbase.yaml │ │ ├── postgres.yaml │ │ ├── spanner-postgres.yaml │ │ ├── spanner.yaml │ │ └── sqlite.yaml │ ├── server │ │ ├── api_test.go │ │ ├── api.go │ │ ├── common_test.go │ │ ├── config.go │ │ ├── mcp │ │ │ ├── jsonrpc │ │ │ │ ├── jsonrpc_test.go │ │ │ │ └── jsonrpc.go │ │ │ ├── mcp.go │ │ │ ├── util │ │ │ │ └── lifecycle.go │ │ │ ├── v20241105 │ │ │ │ ├── method.go │ │ │ │ └── types.go │ │ │ ├── v20250326 │ │ │ │ ├── method.go │ │ │ │ └── types.go │ │ │ └── v20250618 │ │ │ ├── method.go │ │ │ └── types.go │ │ ├── mcp_test.go │ │ ├── mcp.go │ │ ├── server_test.go │ │ ├── server.go │ │ ├── static │ │ │ ├── assets │ │ │ │ └── mcptoolboxlogo.png │ │ │ ├── css │ │ │ │ └── style.css │ │ │ ├── index.html │ │ │ ├── js │ │ │ │ ├── auth.js │ │ │ │ ├── loadTools.js │ │ │ │ ├── mainContent.js │ │ │ │ ├── navbar.js │ │ │ │ ├── runTool.js │ │ │ │ ├── toolDisplay.js │ │ │ │ ├── tools.js │ │ │ │ └── toolsets.js │ │ │ ├── tools.html │ │ │ └── toolsets.html │ │ ├── web_test.go │ │ └── web.go │ ├── sources │ │ ├── alloydbadmin │ │ │ ├── alloydbadmin_test.go │ │ │ └── alloydbadmin.go │ │ ├── alloydbpg │ │ │ ├── alloydb_pg_test.go │ │ │ └── alloydb_pg.go │ │ ├── bigquery │ │ │ ├── bigquery_test.go │ │ │ └── bigquery.go │ │ ├── bigtable │ │ │ ├── bigtable_test.go │ │ │ └── bigtable.go │ │ ├── cassandra │ │ │ ├── cassandra_test.go │ │ │ └── cassandra.go │ │ ├── clickhouse │ │ │ ├── clickhouse_test.go │ │ │ └── clickhouse.go │ │ ├── cloudmonitoring │ │ │ ├── cloud_monitoring_test.go │ │ │ └── cloud_monitoring.go │ │ ├── cloudsqladmin │ │ │ ├── cloud_sql_admin_test.go │ │ │ └── cloud_sql_admin.go │ │ ├── cloudsqlmssql │ │ │ ├── cloud_sql_mssql_test.go │ │ │ └── cloud_sql_mssql.go │ │ ├── cloudsqlmysql │ │ │ ├── cloud_sql_mysql_test.go │ │ │ └── cloud_sql_mysql.go │ │ ├── cloudsqlpg │ │ │ ├── cloud_sql_pg_test.go │ │ │ └── cloud_sql_pg.go │ │ ├── couchbase │ │ │ ├── couchbase_test.go │ │ │ └── couchbase.go │ │ ├── dataplex │ │ │ ├── dataplex_test.go │ │ │ └── dataplex.go │ │ ├── dgraph │ │ │ ├── dgraph_test.go │ │ │ └── dgraph.go │ │ ├── dialect.go │ │ ├── firebird │ │ │ ├── firebird_test.go │ │ │ └── firebird.go │ │ ├── firestore │ │ │ ├── firestore_test.go │ │ │ └── firestore.go │ │ ├── http │ │ │ ├── http_test.go │ │ │ └── http.go │ │ ├── ip_type.go │ │ ├── looker │ │ │ ├── looker_test.go │ │ │ └── looker.go │ │ ├── mongodb │ │ │ ├── mongodb_test.go │ │ │ └── mongodb.go │ │ ├── mssql │ │ │ ├── mssql_test.go │ │ │ └── mssql.go │ │ ├── mysql │ │ │ ├── mysql_test.go │ │ │ └── mysql.go │ │ ├── neo4j │ │ │ ├── neo4j_test.go │ │ │ └── neo4j.go │ │ ├── oceanbase │ │ │ ├── oceanbase_test.go │ │ │ └── oceanbase.go │ │ ├── oracle │ │ │ └── oracle.go │ │ ├── postgres │ │ │ ├── postgres_test.go │ │ │ └── postgres.go │ │ ├── redis │ │ │ ├── redis_test.go │ │ │ └── redis.go │ │ ├── sources.go │ │ ├── spanner │ │ │ ├── spanner_test.go │ │ │ └── spanner.go │ │ ├── sqlite │ │ │ ├── sqlite_test.go │ │ │ └── sqlite.go │ │ ├── tidb │ │ │ ├── tidb_test.go │ │ │ └── tidb.go │ │ ├── trino │ │ │ ├── trino_test.go │ │ │ └── trino.go │ │ ├── util.go │ │ ├── valkey │ │ │ ├── valkey_test.go │ │ │ └── valkey.go │ │ └── yugabytedb │ │ ├── yugabytedb_test.go │ │ └── yugabytedb.go │ ├── telemetry │ │ ├── instrumentation.go │ │ └── telemetry.go │ ├── testutils │ │ └── testutils.go │ ├── tools │ │ ├── alloydb │ │ │ ├── alloydbcreatecluster │ │ │ │ ├── alloydbcreatecluster_test.go │ │ │ │ └── alloydbcreatecluster.go │ │ │ ├── alloydbcreateinstance │ │ │ │ ├── alloydbcreateinstance_test.go │ │ │ │ └── alloydbcreateinstance.go │ │ │ ├── alloydbcreateuser │ │ │ │ ├── alloydbcreateuser_test.go │ │ │ │ └── alloydbcreateuser.go │ │ │ ├── alloydbgetcluster │ │ │ │ ├── alloydbgetcluster_test.go │ │ │ │ └── alloydbgetcluster.go │ │ │ ├── alloydbgetinstance │ │ │ │ ├── alloydbgetinstance_test.go │ │ │ │ └── alloydbgetinstance.go │ │ │ ├── alloydbgetuser │ │ │ │ ├── alloydbgetuser_test.go │ │ │ │ └── alloydbgetuser.go │ │ │ ├── alloydblistclusters │ │ │ │ ├── alloydblistclusters_test.go │ │ │ │ └── alloydblistclusters.go │ │ │ ├── alloydblistinstances │ │ │ │ ├── alloydblistinstances_test.go │ │ │ │ └── alloydblistinstances.go │ │ │ ├── alloydblistusers │ │ │ │ ├── alloydblistusers_test.go │ │ │ │ └── alloydblistusers.go │ │ │ └── alloydbwaitforoperation │ │ │ ├── alloydbwaitforoperation_test.go │ │ │ └── alloydbwaitforoperation.go │ │ ├── alloydbainl │ │ │ ├── alloydbainl_test.go │ │ │ └── alloydbainl.go │ │ ├── bigquery │ │ │ ├── bigqueryanalyzecontribution │ │ │ │ ├── bigqueryanalyzecontribution_test.go │ │ │ │ └── bigqueryanalyzecontribution.go │ │ │ ├── bigquerycommon │ │ │ │ ├── table_name_parser_test.go │ │ │ │ ├── table_name_parser.go │ │ │ │ └── util.go │ │ │ ├── bigqueryconversationalanalytics │ │ │ │ ├── bigqueryconversationalanalytics_test.go │ │ │ │ └── bigqueryconversationalanalytics.go │ │ │ ├── bigqueryexecutesql │ │ │ │ ├── bigqueryexecutesql_test.go │ │ │ │ └── bigqueryexecutesql.go │ │ │ ├── bigqueryforecast │ │ │ │ ├── bigqueryforecast_test.go │ │ │ │ └── bigqueryforecast.go │ │ │ ├── bigquerygetdatasetinfo │ │ │ │ ├── bigquerygetdatasetinfo_test.go │ │ │ │ └── bigquerygetdatasetinfo.go │ │ │ ├── bigquerygettableinfo │ │ │ │ ├── bigquerygettableinfo_test.go │ │ │ │ └── bigquerygettableinfo.go │ │ │ ├── bigquerylistdatasetids │ │ │ │ ├── bigquerylistdatasetids_test.go │ │ │ │ └── bigquerylistdatasetids.go │ │ │ ├── bigquerylisttableids │ │ │ │ ├── bigquerylisttableids_test.go │ │ │ │ └── bigquerylisttableids.go │ │ │ ├── bigquerysearchcatalog │ │ │ │ ├── bigquerysearchcatalog_test.go │ │ │ │ └── bigquerysearchcatalog.go │ │ │ └── bigquerysql │ │ │ ├── bigquerysql_test.go │ │ │ └── bigquerysql.go │ │ ├── bigtable │ │ │ ├── bigtable_test.go │ │ │ └── bigtable.go │ │ ├── cassandra │ │ │ └── cassandracql │ │ │ ├── cassandracql_test.go │ │ │ └── cassandracql.go │ │ ├── clickhouse │ │ │ ├── clickhouseexecutesql │ │ │ │ ├── clickhouseexecutesql_test.go │ │ │ │ └── clickhouseexecutesql.go │ │ │ ├── clickhouselistdatabases │ │ │ │ ├── clickhouselistdatabases_test.go │ │ │ │ └── clickhouselistdatabases.go │ │ │ ├── clickhouselisttables │ │ │ │ ├── clickhouselisttables_test.go │ │ │ │ └── clickhouselisttables.go │ │ │ └── clickhousesql │ │ │ ├── clickhousesql_test.go │ │ │ └── clickhousesql.go │ │ ├── cloudmonitoring │ │ │ ├── cloudmonitoring_test.go │ │ │ └── cloudmonitoring.go │ │ ├── cloudsql │ │ │ ├── cloudsqlcreatedatabase │ │ │ │ ├── cloudsqlcreatedatabase_test.go │ │ │ │ └── cloudsqlcreatedatabase.go │ │ │ ├── cloudsqlcreateusers │ │ │ │ ├── cloudsqlcreateusers_test.go │ │ │ │ └── cloudsqlcreateusers.go │ │ │ ├── cloudsqlgetinstances │ │ │ │ ├── cloudsqlgetinstances_test.go │ │ │ │ └── cloudsqlgetinstances.go │ │ │ ├── cloudsqllistdatabases │ │ │ │ ├── cloudsqllistdatabases_test.go │ │ │ │ └── cloudsqllistdatabases.go │ │ │ ├── cloudsqllistinstances │ │ │ │ ├── cloudsqllistinstances_test.go │ │ │ │ └── cloudsqllistinstances.go │ │ │ └── cloudsqlwaitforoperation │ │ │ ├── cloudsqlwaitforoperation_test.go │ │ │ └── cloudsqlwaitforoperation.go │ │ ├── cloudsqlmssql │ │ │ └── cloudsqlmssqlcreateinstance │ │ │ ├── cloudsqlmssqlcreateinstance_test.go │ │ │ └── cloudsqlmssqlcreateinstance.go │ │ ├── cloudsqlmysql │ │ │ └── cloudsqlmysqlcreateinstance │ │ │ ├── cloudsqlmysqlcreateinstance_test.go │ │ │ └── cloudsqlmysqlcreateinstance.go │ │ ├── cloudsqlpg │ │ │ └── cloudsqlpgcreateinstances │ │ │ ├── cloudsqlpgcreateinstances_test.go │ │ │ └── cloudsqlpgcreateinstances.go │ │ ├── common_test.go │ │ ├── common.go │ │ ├── couchbase │ │ │ ├── couchbase_test.go │ │ │ └── couchbase.go │ │ ├── dataform │ │ │ └── dataformcompilelocal │ │ │ ├── dataformcompilelocal_test.go │ │ │ └── dataformcompilelocal.go │ │ ├── dataplex │ │ │ ├── dataplexlookupentry │ │ │ │ ├── dataplexlookupentry_test.go │ │ │ │ └── dataplexlookupentry.go │ │ │ ├── dataplexsearchaspecttypes │ │ │ │ ├── dataplexsearchaspecttypes_test.go │ │ │ │ └── dataplexsearchaspecttypes.go │ │ │ └── dataplexsearchentries │ │ │ ├── dataplexsearchentries_test.go │ │ │ └── dataplexsearchentries.go │ │ ├── dgraph │ │ │ ├── dgraph_test.go │ │ │ └── dgraph.go │ │ ├── firebird │ │ │ ├── firebirdexecutesql │ │ │ │ ├── firebirdexecutesql_test.go │ │ │ │ └── firebirdexecutesql.go │ │ │ └── firebirdsql │ │ │ ├── firebirdsql_test.go │ │ │ └── firebirdsql.go │ │ ├── firestore │ │ │ ├── firestoreadddocuments │ │ │ │ ├── firestoreadddocuments_test.go │ │ │ │ └── firestoreadddocuments.go │ │ │ ├── firestoredeletedocuments │ │ │ │ ├── firestoredeletedocuments_test.go │ │ │ │ └── firestoredeletedocuments.go │ │ │ ├── firestoregetdocuments │ │ │ │ ├── firestoregetdocuments_test.go │ │ │ │ └── firestoregetdocuments.go │ │ │ ├── firestoregetrules │ │ │ │ ├── firestoregetrules_test.go │ │ │ │ └── firestoregetrules.go │ │ │ ├── firestorelistcollections │ │ │ │ ├── firestorelistcollections_test.go │ │ │ │ └── firestorelistcollections.go │ │ │ ├── firestorequery │ │ │ │ ├── firestorequery_test.go │ │ │ │ └── firestorequery.go │ │ │ ├── firestorequerycollection │ │ │ │ ├── firestorequerycollection_test.go │ │ │ │ └── firestorequerycollection.go │ │ │ ├── firestoreupdatedocument │ │ │ │ ├── firestoreupdatedocument_test.go │ │ │ │ └── firestoreupdatedocument.go │ │ │ ├── firestorevalidaterules │ │ │ │ ├── firestorevalidaterules_test.go │ │ │ │ └── firestorevalidaterules.go │ │ │ └── util │ │ │ ├── converter_test.go │ │ │ ├── converter.go │ │ │ ├── validator_test.go │ │ │ └── validator.go │ │ ├── http │ │ │ ├── http_test.go │ │ │ └── http.go │ │ ├── http_method.go │ │ ├── looker │ │ │ ├── lookeradddashboardelement │ │ │ │ ├── lookeradddashboardelement_test.go │ │ │ │ └── lookeradddashboardelement.go │ │ │ ├── lookercommon │ │ │ │ ├── lookercommon_test.go │ │ │ │ └── lookercommon.go │ │ │ ├── lookerconversationalanalytics │ │ │ │ ├── lookerconversationalanalytics_test.go │ │ │ │ └── lookerconversationalanalytics.go │ │ │ ├── lookergetdashboards │ │ │ │ ├── lookergetdashboards_test.go │ │ │ │ └── lookergetdashboards.go │ │ │ ├── lookergetdimensions │ │ │ │ ├── lookergetdimensions_test.go │ │ │ │ └── lookergetdimensions.go │ │ │ ├── lookergetexplores │ │ │ │ ├── lookergetexplores_test.go │ │ │ │ └── lookergetexplores.go │ │ │ ├── lookergetfilters │ │ │ │ ├── lookergetfilters_test.go │ │ │ │ └── lookergetfilters.go │ │ │ ├── lookergetlooks │ │ │ │ ├── lookergetlooks_test.go │ │ │ │ └── lookergetlooks.go │ │ │ ├── lookergetmeasures │ │ │ │ ├── lookergetmeasures_test.go │ │ │ │ └── lookergetmeasures.go │ │ │ ├── lookergetmodels │ │ │ │ ├── lookergetmodels_test.go │ │ │ │ └── lookergetmodels.go │ │ │ ├── lookergetparameters │ │ │ │ ├── lookergetparameters_test.go │ │ │ │ └── lookergetparameters.go │ │ │ ├── lookerhealthanalyze │ │ │ │ ├── lookerhealthanalyze_test.go │ │ │ │ └── lookerhealthanalyze.go │ │ │ ├── lookerhealthpulse │ │ │ │ ├── lookerhealthpulse_test.go │ │ │ │ └── lookerhealthpulse.go │ │ │ ├── lookerhealthvacuum │ │ │ │ ├── lookerhealthvacuum_test.go │ │ │ │ └── lookerhealthvacuum.go │ │ │ ├── lookermakedashboard │ │ │ │ ├── lookermakedashboard_test.go │ │ │ │ └── lookermakedashboard.go │ │ │ ├── lookermakelook │ │ │ │ ├── lookermakelook_test.go │ │ │ │ └── lookermakelook.go │ │ │ ├── lookerquery │ │ │ │ ├── lookerquery_test.go │ │ │ │ └── lookerquery.go │ │ │ ├── lookerquerysql │ │ │ │ ├── lookerquerysql_test.go │ │ │ │ └── lookerquerysql.go │ │ │ ├── lookerqueryurl │ │ │ │ ├── lookerqueryurl_test.go │ │ │ │ └── lookerqueryurl.go │ │ │ └── lookerrunlook │ │ │ ├── lookerrunlook_test.go │ │ │ └── lookerrunlook.go │ │ ├── mongodb │ │ │ ├── mongodbaggregate │ │ │ │ ├── mongodbaggregate_test.go │ │ │ │ └── mongodbaggregate.go │ │ │ ├── mongodbdeletemany │ │ │ │ ├── mongodbdeletemany_test.go │ │ │ │ └── mongodbdeletemany.go │ │ │ ├── mongodbdeleteone │ │ │ │ ├── mongodbdeleteone_test.go │ │ │ │ └── mongodbdeleteone.go │ │ │ ├── mongodbfind │ │ │ │ ├── mongodbfind_test.go │ │ │ │ └── mongodbfind.go │ │ │ ├── mongodbfindone │ │ │ │ ├── mongodbfindone_test.go │ │ │ │ └── mongodbfindone.go │ │ │ ├── mongodbinsertmany │ │ │ │ ├── mongodbinsertmany_test.go │ │ │ │ └── mongodbinsertmany.go │ │ │ ├── mongodbinsertone │ │ │ │ ├── mongodbinsertone_test.go │ │ │ │ └── mongodbinsertone.go │ │ │ ├── mongodbupdatemany │ │ │ │ ├── mongodbupdatemany_test.go │ │ │ │ └── mongodbupdatemany.go │ │ │ └── mongodbupdateone │ │ │ ├── mongodbupdateone_test.go │ │ │ └── mongodbupdateone.go │ │ ├── mssql │ │ │ ├── mssqlexecutesql │ │ │ │ ├── mssqlexecutesql_test.go │ │ │ │ └── mssqlexecutesql.go │ │ │ ├── mssqllisttables │ │ │ │ ├── mssqllisttables_test.go │ │ │ │ └── mssqllisttables.go │ │ │ └── mssqlsql │ │ │ ├── mssqlsql_test.go │ │ │ └── mssqlsql.go │ │ ├── mysql │ │ │ ├── mysqlcommon │ │ │ │ └── mysqlcommon.go │ │ │ ├── mysqlexecutesql │ │ │ │ ├── mysqlexecutesql_test.go │ │ │ │ └── mysqlexecutesql.go │ │ │ ├── mysqllistactivequeries │ │ │ │ ├── mysqllistactivequeries_test.go │ │ │ │ └── mysqllistactivequeries.go │ │ │ ├── mysqllisttablefragmentation │ │ │ │ ├── mysqllisttablefragmentation_test.go │ │ │ │ └── mysqllisttablefragmentation.go │ │ │ ├── mysqllisttables │ │ │ │ ├── mysqllisttables_test.go │ │ │ │ └── mysqllisttables.go │ │ │ ├── mysqllisttablesmissinguniqueindexes │ │ │ │ ├── mysqllisttablesmissinguniqueindexes_test.go │ │ │ │ └── mysqllisttablesmissinguniqueindexes.go │ │ │ └── mysqlsql │ │ │ ├── mysqlsql_test.go │ │ │ └── mysqlsql.go │ │ ├── neo4j │ │ │ ├── neo4jcypher │ │ │ │ ├── neo4jcypher_test.go │ │ │ │ └── neo4jcypher.go │ │ │ ├── neo4jexecutecypher │ │ │ │ ├── classifier │ │ │ │ │ ├── classifier_test.go │ │ │ │ │ └── classifier.go │ │ │ │ ├── neo4jexecutecypher_test.go │ │ │ │ └── neo4jexecutecypher.go │ │ │ └── neo4jschema │ │ │ ├── cache │ │ │ │ ├── cache_test.go │ │ │ │ └── cache.go │ │ │ ├── helpers │ │ │ │ ├── helpers_test.go │ │ │ │ └── helpers.go │ │ │ ├── neo4jschema_test.go │ │ │ ├── neo4jschema.go │ │ │ └── types │ │ │ └── types.go │ │ ├── oceanbase │ │ │ ├── oceanbaseexecutesql │ │ │ │ ├── oceanbaseexecutesql_test.go │ │ │ │ └── oceanbaseexecutesql.go │ │ │ └── oceanbasesql │ │ │ ├── oceanbasesql_test.go │ │ │ └── oceanbasesql.go │ │ ├── oracle │ │ │ ├── oracleexecutesql │ │ │ │ └── oracleexecutesql.go │ │ │ └── oraclesql │ │ │ └── oraclesql.go │ │ ├── parameters_test.go │ │ ├── parameters.go │ │ ├── postgres │ │ │ ├── postgresexecutesql │ │ │ │ ├── postgresexecutesql_test.go │ │ │ │ └── postgresexecutesql.go │ │ │ ├── postgreslistactivequeries │ │ │ │ ├── postgreslistactivequeries_test.go │ │ │ │ └── postgreslistactivequeries.go │ │ │ ├── postgreslistavailableextensions │ │ │ │ ├── postgreslistavailableextensions_test.go │ │ │ │ └── postgreslistavailableextensions.go │ │ │ ├── postgreslistinstalledextensions │ │ │ │ ├── postgreslistinstalledextensions_test.go │ │ │ │ └── postgreslistinstalledextensions.go │ │ │ ├── postgreslisttables │ │ │ │ ├── postgreslisttables_test.go │ │ │ │ └── postgreslisttables.go │ │ │ └── postgressql │ │ │ ├── postgressql_test.go │ │ │ └── postgressql.go │ │ ├── redis │ │ │ ├── redis_test.go │ │ │ └── redis.go │ │ ├── spanner │ │ │ ├── spannerexecutesql │ │ │ │ ├── spannerexecutesql_test.go │ │ │ │ └── spannerexecutesql.go │ │ │ ├── spannerlisttables │ │ │ │ ├── spannerlisttables_test.go │ │ │ │ └── spannerlisttables.go │ │ │ └── spannersql │ │ │ ├── spanner_test.go │ │ │ └── spannersql.go │ │ ├── sqlite │ │ │ ├── sqliteexecutesql │ │ │ │ ├── sqliteexecutesql_test.go │ │ │ │ └── sqliteexecutesql.go │ │ │ └── sqlitesql │ │ │ ├── sqlitesql_test.go │ │ │ └── sqlitesql.go │ │ ├── tidb │ │ │ ├── tidbexecutesql │ │ │ │ ├── tidbexecutesql_test.go │ │ │ │ └── tidbexecutesql.go │ │ │ └── tidbsql │ │ │ ├── tidbsql_test.go │ │ │ └── tidbsql.go │ │ ├── tools_test.go │ │ ├── tools.go │ │ ├── toolsets.go │ │ ├── trino │ │ │ ├── trinoexecutesql │ │ │ │ ├── trinoexecutesql_test.go │ │ │ │ └── trinoexecutesql.go │ │ │ └── trinosql │ │ │ ├── trinosql_test.go │ │ │ └── trinosql.go │ │ ├── utility │ │ │ └── wait │ │ │ ├── wait_test.go │ │ │ └── wait.go │ │ ├── valkey │ │ │ ├── valkey_test.go │ │ │ └── valkey.go │ │ └── yugabytedbsql │ │ ├── yugabytedbsql_test.go │ │ └── yugabytedbsql.go │ └── util │ └── util.go ├── LICENSE ├── logo.png ├── main.go ├── MCP-TOOLBOX-EXTENSION.md ├── README.md └── tests ├── alloydb │ ├── alloydb_integration_test.go │ └── alloydb_wait_for_operation_test.go ├── alloydbainl │ └── alloydb_ai_nl_integration_test.go ├── alloydbpg │ └── alloydb_pg_integration_test.go ├── auth.go ├── bigquery │ └── bigquery_integration_test.go ├── bigtable │ └── bigtable_integration_test.go ├── cassandra │ └── cassandra_integration_test.go ├── clickhouse │ └── clickhouse_integration_test.go ├── cloudmonitoring │ └── cloud_monitoring_integration_test.go ├── cloudsql │ ├── cloud_sql_create_database_test.go │ ├── cloud_sql_create_users_test.go │ ├── cloud_sql_get_instances_test.go │ ├── cloud_sql_list_databases_test.go │ ├── cloudsql_list_instances_test.go │ └── cloudsql_wait_for_operation_test.go ├── cloudsqlmssql │ ├── cloud_sql_mssql_create_instance_integration_test.go │ └── cloud_sql_mssql_integration_test.go ├── cloudsqlmysql │ ├── cloud_sql_mysql_create_instance_integration_test.go │ └── cloud_sql_mysql_integration_test.go ├── cloudsqlpg │ ├── cloud_sql_pg_create_instances_test.go │ └── cloud_sql_pg_integration_test.go ├── common.go ├── couchbase │ └── couchbase_integration_test.go ├── dataform │ └── dataform_integration_test.go ├── dataplex │ └── dataplex_integration_test.go ├── dgraph │ └── dgraph_integration_test.go ├── firebird │ └── firebird_integration_test.go ├── firestore │ └── firestore_integration_test.go ├── http │ └── http_integration_test.go ├── looker │ └── looker_integration_test.go ├── mongodb │ └── mongodb_integration_test.go ├── mssql │ └── mssql_integration_test.go ├── mysql │ └── mysql_integration_test.go ├── neo4j │ └── neo4j_integration_test.go ├── oceanbase │ └── oceanbase_integration_test.go ├── option.go ├── oracle │ └── oracle_integration_test.go ├── postgres │ └── postgres_integration_test.go ├── redis │ └── redis_test.go ├── server.go ├── source.go ├── spanner │ └── spanner_integration_test.go ├── sqlite │ └── sqlite_integration_test.go ├── tidb │ └── tidb_integration_test.go ├── tool.go ├── trino │ └── trino_integration_test.go ├── utility │ └── wait_integration_test.go ├── valkey │ └── valkey_test.go └── yugabytedb └── yugabytedb_integration_test.go ``` # Files -------------------------------------------------------------------------------- /internal/sources/clickhouse/clickhouse.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 clickhouse import ( "context" "database/sql" "fmt" "net/url" "time" _ "github.com/ClickHouse/clickhouse-go/v2" "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/sources" "go.opentelemetry.io/otel/trace" ) const SourceKind string = "clickhouse" // 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 } return actual, nil } type Config struct { Name string `yaml:"name" validate:"required"` Kind string `yaml:"kind" validate:"required"` Host string `yaml:"host" validate:"required"` Port string `yaml:"port" validate:"required"` Database string `yaml:"database" validate:"required"` User string `yaml:"user" validate:"required"` Password string `yaml:"password"` Protocol string `yaml:"protocol"` Secure bool `yaml:"secure"` } func (r Config) SourceConfigKind() string { return SourceKind } func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) { pool, err := initClickHouseConnectionPool(ctx, tracer, r.Name, r.Host, r.Port, r.User, r.Password, r.Database, r.Protocol, r.Secure) if err != nil { return nil, fmt.Errorf("unable to create pool: %w", err) } err = pool.PingContext(ctx) if err != nil { return nil, fmt.Errorf("unable to connect successfully: %w", err) } s := &Source{ Name: r.Name, Kind: SourceKind, Pool: pool, } return s, nil } var _ sources.Source = &Source{} type Source struct { Name string `yaml:"name"` Kind string `yaml:"kind"` Pool *sql.DB } func (s *Source) SourceKind() string { return SourceKind } func (s *Source) ClickHousePool() *sql.DB { return s.Pool } func validateConfig(protocol string) error { validProtocols := map[string]bool{"http": true, "https": true} if protocol != "" && !validProtocols[protocol] { return fmt.Errorf("invalid protocol: %s, must be one of: http, https", protocol) } return nil } func initClickHouseConnectionPool(ctx context.Context, tracer trace.Tracer, name, host, port, user, pass, dbname, protocol string, secure bool) (*sql.DB, error) { //nolint:all // Reassigned ctx ctx, span := sources.InitConnectionSpan(ctx, tracer, SourceKind, name) defer span.End() if protocol == "" { protocol = "https" } if err := validateConfig(protocol); err != nil { return nil, err } encodedUser := url.QueryEscape(user) encodedPass := url.QueryEscape(pass) var dsn string scheme := protocol if protocol == "http" && secure { scheme = "https" } dsn = fmt.Sprintf("%s://%s:%s@%s:%s/%s", scheme, encodedUser, encodedPass, host, port, dbname) if scheme == "https" { dsn += "?secure=true&skip_verify=false" } pool, err := sql.Open("clickhouse", dsn) if err != nil { return nil, fmt.Errorf("sql.Open: %w", err) } pool.SetMaxOpenConns(25) pool.SetMaxIdleConns(5) pool.SetConnMaxLifetime(5 * time.Minute) return pool, nil } ``` -------------------------------------------------------------------------------- /docs/en/resources/tools/mongodb/mongodb-find-one.md: -------------------------------------------------------------------------------- ```markdown --- title: "mongodb-find-one" type: docs weight: 1 description: > A "mongodb-find-one" tool finds and retrieves a single document from a MongoDB collection. aliases: - /resources/tools/mongodb-find-one --- ## About A `mongodb-find-one` tool is used to retrieve the **first single document** that matches a specified filter from a MongoDB collection. If multiple documents match the filter, you can use `sort` options to control which document is returned. Otherwise, the selection is not guaranteed. The tool returns a single JSON object representing the document, wrapped in a JSON array. This tool is compatible with the following source kind: * [`mongodb`](../../sources/mongodb.md) --- ## Example Here's a common use case: finding a specific user by their unique email address and returning their profile information, while excluding sensitive fields like the password hash. ```yaml tools: get_user_profile: kind: mongodb-find-one source: my-mongo-source description: Retrieves a user's profile by their email address. database: user_data collection: profiles filterPayload: | { "email": {{json .email}} } filterParams: - name: email type: string description: The email address of the user to find. projectPayload: | { "password_hash": 0, "login_history": 0 } ``` ## Reference | **field** | **type** | **required** | **description** | |:---------------|:---------|:-------------|:---------------------------------------------------------------------------------------------------------------------------------------------| | kind | string | true | Must be `mongodb-find-one`. | | 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 to query. | | collection | string | true | The name of the MongoDB collection to query. | | filterPayload | string | true | The MongoDB query filter document to select the document. Uses `{{json .param_name}}` for templating. | | filterParams | list | true | A list of parameter objects that define the variables used in the `filterPayload`. | | projectPayload | string | false | An optional MongoDB projection document to specify which fields to include (1) or exclude (0) in the result. | | projectParams | list | false | A list of parameter objects for the `projectPayload`. | | sortPayload | string | false | An optional MongoDB sort document. Useful for selecting which document to return if the filter matches multiple (e.g., get the most recent). | | sortParams | list | false | A list of parameter objects for the `sortPayload`. | ``` -------------------------------------------------------------------------------- /internal/server/static/js/mainContent.js: -------------------------------------------------------------------------------- ```javascript // Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /** * Renders the main content area into the HTML. * @param {string} containerId The ID of the DOM element to inject the content into. * @param {string} idString The id of the item inside the main content area. */ function renderMainContent(containerId, idString, instructionContent) { const mainContentContainer = document.getElementById(containerId); if (!mainContentContainer) { console.error(`Content container with ID "${containerId}" not found.`); return; } const idAttribute = idString ? `id="${idString}"` : ''; const contentHTML = ` <div class="main-content-area"> <div class="top-bar"> </div> <main class="content" ${idAttribute}"> ${instructionContent} </main> </div> `; mainContentContainer.innerHTML = contentHTML; } function getHomepageInstructions() { return ` <div class="resource-instructions"> <h1 class="resource-title">Welcome to Toolbox UI</h1> <p class="resource-intro">Toolbox UI is a built-in web interface that allows users to visually inspect and test out configured resources such as tools and toolsets. To get started, select a resource from the navigation tab to the left.</p> <a href="https://googleapis.github.io/genai-toolbox/how-to/use-toolbox-ui/" class="btn btn--externalDocs" target="_blank" rel="noopener noreferrer">Toolbox UI Documentation</a> </div> `; } function getToolInstructions() { return ` <div class="resource-instructions"> <h1 class="resource-title">Tools</h1> <p class="resource-intro">To inspect and test a tool, please click on one of your tools to the left.</p> <h2 class="resource-subtitle">What are Tools?</h2> <p class="resource-description"> Tools define actions an agent can take, such as running a SQL statement or interacting with a source. You can define Tools as a map in the <code>tools</code> section of your <code>tools.yaml</code> file. <br><br> Some tools also use <strong>parameters</strong>. Parameters for each Tool will define what inputs the agent will need to provide to invoke them. </p> <a href="https://googleapis.github.io/genai-toolbox/resources/tools/" class="btn btn--externalDocs" target="_blank" rel="noopener noreferrer">Tools Documentation</a> </div> `; } function getToolsetInstructions() { return ` <div class="resource-instructions"> <h1 class="resource-title">Toolsets</h1> <p class="resource-intro">To inspect a specific toolset, please enter the name of a toolset and press search.</p> <h2 class="resource-subtitle">What are Toolsets?</h2> <p class="resource-description"> Toolsets define groups of tools an agent can access. You can define Toolsets as a map in the <code>toolsets</code> section of your <code>tools.yaml</code> file. Toolsets may only include valid tools that are also defined in your <code>tools.yaml</code> file. </p> <a href="https://googleapis.github.io/genai-toolbox/getting-started/configure/#toolsets" class="btn btn--externalDocs" target="_blank" rel="noopener noreferrer">Toolsets Documentation</a> </div> `; } ``` -------------------------------------------------------------------------------- /internal/sources/cloudsqlmysql/cloud_sql_mysql.go: -------------------------------------------------------------------------------- ```go // Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cloudsqlmysql import ( "context" "database/sql" "fmt" "net/url" "slices" "cloud.google.com/go/cloudsqlconn/mysql/mysql" "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/sources" "github.com/googleapis/genai-toolbox/internal/util" "go.opentelemetry.io/otel/trace" ) const SourceKind string = "cloud-sql-mysql" // 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, IPType: "public"} // Default IPType 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"` Project string `yaml:"project" validate:"required"` Region string `yaml:"region" validate:"required"` Instance string `yaml:"instance" validate:"required"` IPType sources.IPType `yaml:"ipType"` User string `yaml:"user" validate:"required"` Password string `yaml:"password" validate:"required"` Database string `yaml:"database" validate:"required"` } func (r Config) SourceConfigKind() string { return SourceKind } func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) { pool, err := initCloudSQLMySQLConnectionPool(ctx, tracer, r.Name, r.Project, r.Region, r.Instance, r.IPType.String(), r.User, r.Password, r.Database) if err != nil { return nil, fmt.Errorf("unable to create pool: %w", err) } err = pool.PingContext(ctx) if err != nil { return nil, fmt.Errorf("unable to connect successfully: %w", err) } s := &Source{ Name: r.Name, Kind: SourceKind, Pool: pool, } return s, nil } var _ sources.Source = &Source{} type Source struct { Name string `yaml:"name"` Kind string `yaml:"kind"` Pool *sql.DB } func (s *Source) SourceKind() string { return SourceKind } func (s *Source) MySQLPool() *sql.DB { return s.Pool } func initCloudSQLMySQLConnectionPool(ctx context.Context, tracer trace.Tracer, name, project, region, instance, ipType, user, pass, dbname string) (*sql.DB, error) { //nolint:all // Reassigned ctx ctx, span := sources.InitConnectionSpan(ctx, tracer, SourceKind, name) defer span.End() // Create a new dialer with options userAgent, err := util.UserAgentFromContext(ctx) if err != nil { return nil, err } opts, err := sources.GetCloudSQLOpts(ipType, userAgent, false) if err != nil { return nil, err } if !slices.Contains(sql.Drivers(), "cloudsql-mysql") { _, err = mysql.RegisterDriver("cloudsql-mysql", opts...) if err != nil { return nil, fmt.Errorf("unable to register driver: %w", err) } } // Tell the driver to use the Cloud SQL Go Connector to create connections dsn := fmt.Sprintf("%s:%s@cloudsql-mysql(%s:%s:%s)/%s?connectionAttributes=program_name:%s", user, pass, project, region, instance, dbname, url.QueryEscape(userAgent)) db, err := sql.Open( "cloudsql-mysql", dsn, ) if err != nil { return nil, err } return db, nil } ``` -------------------------------------------------------------------------------- /docs/en/about/faq.md: -------------------------------------------------------------------------------- ```markdown --- title: "FAQ" type: docs weight: 2 description: Frequently asked questions about Toolbox. --- ## How can I deploy or run Toolbox? MCP Toolbox for Databases is open-source and can be run or deployed to a multitude of environments. For convenience, we release [compiled binaries and docker images][release-notes] (but you can always compile yourself as well!). For detailed instructions, check out these resources: - [Quickstart: How to Run Locally](../getting-started/local_quickstart.md) - [Deploy to Cloud Run](../how-to/deploy_toolbox.md) [release-notes]: https://github.com/googleapis/genai-toolbox/releases/ ## Do I need a Google Cloud account/project to get started with Toolbox? Nope! While some of the sources Toolbox connects to may require GCP credentials, Toolbox doesn't require them and can connect to a bunch of different resources that don't. ## Does Toolbox take contributions from external users? Absolutely! Please check out our [DEVELOPER.md][] for instructions on how to get started developing _on_ Toolbox instead of with it, and the [CONTRIBUTING.md][] for instructions on completing the CLA and getting a PR accepted. [DEVELOPER.md]: https://github.com/googleapis/genai-toolbox/blob/main/DEVELOPER.md [CONTRIBUTING.MD]: https://github.com/googleapis/genai-toolbox/blob/main/CONTRIBUTING.md ## Can Toolbox support a feature to let me do _$FOO_? Maybe? The best place to start is by [opening an issue][github-issue] for discussion (or seeing if there is already one open), so we can better understand your use case and the best way to solve it. Generally we aim to prioritize the most popular issues, so make sure to +1 ones you are the most interested in. [github-issue]: https://github.com/googleapis/genai-toolbox/issues ## Can Toolbox be used for non-database tools? Currently, Toolbox is primarily focused on making it easier to create and develop tools focused on interacting with Databases. We believe that there are a lot of unique problems when interacting with Databases for Gen AI use cases, and want to prioritize solving those first. However, we've also received feedback that supporting more generic HTTP or GRPC tools might be helpful in assisting with migrating to Toolbox or in accomplishing more complicated workflows. We're looking into what that might best look like in Toolbox. ## Can I use _$BAR_ orchestration framework to use tools from Toolbox? Currently, Toolbox only supports a limited number of client SDKs at our initial launch. We are investigating support for more frameworks as well as more general approaches for users without a framework -- look forward to seeing an update soon. ## Why does Toolbox use a server-client architecture pattern? Toolbox's server-client architecture allows us to more easily support a wide variety of languages and frameworks with a centralized implementation. It also allows us to tackle problems like connection pooling, auth, or caching more completely than entirely client-side solutions. ## Why was Toolbox written in Go? While a large part of the Gen AI Ecosystem is predominately Python, we opted to use Go. We chose Go because it's still easy and simple to use, but also easier to write fast, efficient, and concurrent servers. Additionally, given the server-client architecture, we can still meet many developers where they are with clients in their preferred language. As Gen AI matures, we want developers to be able to use Toolbox on the serving path of mission critical applications. It's easier to build the needed robustness, performance and scalability in Go than in Python. ## Is Toolbox compatible with Model Context Protocol (MCP)? Yes! Toolbox is compatible with [Anthropic's Model Context Protocol (MCP)](https://modelcontextprotocol.io/). Please checkout [Connect via MCP](../how-to/connect_via_mcp.md) on how to connect to Toolbox with an MCP client. ``` -------------------------------------------------------------------------------- /docs/en/resources/tools/sqlite/sqlite-sql.md: -------------------------------------------------------------------------------- ```markdown --- title: "sqlite-sql" type: docs weight: 1 description: > Execute SQL statements against a SQLite database. aliases: - /resources/tools/sqlite-sql --- ## About A `sqlite-sql` tool executes SQL statements against a SQLite database. It's compatible with any of the following sources: - [sqlite](../../sources/sqlite.md) SQLite uses the `?` placeholder for parameters in SQL statements. Parameters are bound in the order they are provided. The statement field supports any valid SQLite SQL statement, including `SELECT`, `INSERT`, `UPDATE`, `DELETE`, `CREATE/ALTER/DROP` table statements, and other DDL statements. ### 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-users: kind: sqlite-sql source: my-sqlite-db description: Search users by name and age parameters: - name: name type: string description: The name to search for - name: min_age type: integer description: Minimum age statement: SELECT * FROM users WHERE name LIKE ? AND age >= ? ``` ### 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: sqlite-sql source: my-sqlite-db statement: | SELECT * FROM {{.tableName}}; description: | Use this tool to list all information from a specific table. Example: {{ "tableName": "flights", }} templateParameters: - name: tableName type: string description: Table to select from ``` ## Reference | **field** | **type** | **required** | **description** | |--------------------|:------------------------------------------------:|:------------:|--------------------------------------------------------------------------------------------------------------------------------------------| | kind | string | true | Must be "sqlite-sql". | | source | string | true | Name of the source the SQLite source configuration. | | description | string | true | Description of the tool that is passed to the LLM. | | statement | string | true | The 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. | ``` -------------------------------------------------------------------------------- /docs/en/resources/sources/redis.md: -------------------------------------------------------------------------------- ```markdown --- title: "Redis" linkTitle: "Redis" type: docs weight: 1 description: > Redis is a in-memory data structure store. --- ## About Redis is a in-memory data structure store, used as a database, cache, and message broker. It supports data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, and geospatial indexes with radius queries. If you are new to Redis, you can find installation and getting started guides on the [official Redis website](https://redis.io/docs/). ## Available Tools - [`redis`](../tools/redis/redis.md) Run Redis commands and interact with key-value pairs. ## Requirements ### Redis [AUTH string][auth] is a password for connection to Redis. If you have the `requirepass` directive set in your Redis configuration, incoming client connections must authenticate in order to connect. Specify your AUTH string in the password field: ```yaml sources: my-redis-instance: kind: redis address: - 127.0.0.1:6379 username: ${MY_USER_NAME} password: ${MY_AUTH_STRING} # Omit this field if you don't have a password. # database: 0 # clusterEnabled: false # useGCPIAM: false ``` {{< notice tip >}} Use environment variable replacement with the format ${ENV_NAME} instead of hardcoding your secrets into the configuration file. {{< /notice >}} ### Memorystore For Redis Memorystore standalone instances support authentication using an [AUTH][auth] string. Here is an example tools.yaml config with [AUTH][auth] enabled: ```yaml sources: my-redis-cluster-instance: kind: memorystore-redis address: - 127.0.0.1:6379 password: ${MY_AUTH_STRING} # useGCPIAM: false # clusterEnabled: false ``` Memorystore Redis Cluster supports IAM authentication instead. Grant your account the required [IAM role][iam] and make sure to set `useGCPIAM` to `true`. Here is an example tools.yaml config for Memorystore Redis Cluster instances using IAM authentication: ```yaml sources: my-redis-cluster-instance: kind: memorystore-redis address: - 127.0.0.1:6379 useGCPIAM: true clusterEnabled: true ``` [iam]: https://cloud.google.com/memorystore/docs/cluster/about-iam-auth ## Reference | **field** | **type** | **required** | **description** | |----------------|:--------:|:------------:|---------------------------------------------------------------------------------------------------------------------------------| | kind | string | true | Must be "memorystore-redis". | | address | string | true | Primary endpoint for the Memorystore Redis instance to connect to. | | username | string | false | If you are using a non-default user, specify the user name here. If you are using Memorystore for Redis, leave this field blank | | password | string | false | If you have [Redis AUTH][auth] enabled, specify the AUTH string here | | database | int | false | The Redis database to connect to. Not applicable for cluster enabled instances. The default database is `0`. | | clusterEnabled | bool | false | Set it to `true` if using a Redis Cluster instance. Defaults to `false`. | | useGCPIAM | string | false | Set it to `true` if you are using GCP's IAM authentication. Defaults to `false`. | [auth]: https://cloud.google.com/memorystore/docs/redis/about-redis-auth ``` -------------------------------------------------------------------------------- /internal/sources/valkey/valkey_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 valkey_test import ( "strings" "testing" yaml "github.com/goccy/go-yaml" "github.com/google/go-cmp/cmp" "github.com/googleapis/genai-toolbox/internal/server" "github.com/googleapis/genai-toolbox/internal/sources" "github.com/googleapis/genai-toolbox/internal/sources/valkey" "github.com/googleapis/genai-toolbox/internal/testutils" ) func TestParseFromYamlValkey(t *testing.T) { tcs := []struct { desc string in string want server.SourceConfigs }{ { desc: "default setting", in: ` sources: my-valkey-instance: kind: valkey address: - 127.0.0.1 `, want: map[string]sources.SourceConfig{ "my-valkey-instance": valkey.Config{ Name: "my-valkey-instance", Kind: valkey.SourceKind, Address: []string{"127.0.0.1"}, Username: "", Password: "", Database: 0, UseGCPIAM: false, DisableCache: false, }, }, }, { desc: "advanced example", in: ` sources: my-valkey-instance: kind: valkey address: - 127.0.0.1 database: 1 username: user password: pass useGCPIAM: true disableCache: true `, want: map[string]sources.SourceConfig{ "my-valkey-instance": valkey.Config{ Name: "my-valkey-instance", Kind: valkey.SourceKind, Address: []string{"127.0.0.1"}, Username: "user", Password: "pass", Database: 1, UseGCPIAM: true, DisableCache: true, }, }, }, } for _, tc := range tcs { t.Run(tc.desc, func(t *testing.T) { got := struct { Sources server.SourceConfigs `yaml:"sources"` }{} // Parse contents err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got) if err != nil { t.Fatalf("unable to unmarshal: %s", err) } if !cmp.Equal(tc.want, got.Sources) { t.Fatalf("incorrect parse: want %v, got %v", tc.want, got.Sources) } }) } } func TestFailParseFromYaml(t *testing.T) { tcs := []struct { desc string in string err string }{ { desc: "invalid database", in: ` sources: my-valkey-instance: kind: valkey project: my-project address: - 127.0.0.1 database: my-db useGCPIAM: false `, err: "cannot unmarshal string into Go struct field .Sources of type int", }, { desc: "extra field", in: ` sources: my-valkey-instance: kind: valkey address: - 127.0.0.1 project: proj database: 1 `, err: "unable to parse source \"my-valkey-instance\" as \"valkey\": [5:1] unknown field \"project\"", }, { desc: "missing required field", in: ` sources: my-valkey-instance: kind: valkey `, err: "unable to parse source \"my-valkey-instance\" as \"valkey\": Key: 'Config.Address' Error:Field validation for 'Address' failed on the 'required' tag", }, } for _, tc := range tcs { t.Run(tc.desc, func(t *testing.T) { got := struct { Sources server.SourceConfigs `yaml:"sources"` }{} // Parse contents err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got) if err == nil { t.Fatalf("expect parsing to fail") } errStr := err.Error() if !strings.Contains(errStr, tc.err) { t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err) } }) } } ``` -------------------------------------------------------------------------------- /internal/tools/mongodb/mongodbfind/mongodbfind_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 mongodbfind_test import ( "strings" "testing" "github.com/googleapis/genai-toolbox/internal/tools" "github.com/googleapis/genai-toolbox/internal/tools/mongodb/mongodbfind" yaml "github.com/goccy/go-yaml" "github.com/google/go-cmp/cmp" "github.com/googleapis/genai-toolbox/internal/server" "github.com/googleapis/genai-toolbox/internal/testutils" ) func TestParseFromYamlMongoQuery(t *testing.T) { ctx, err := testutils.ContextWithNewLogger() if err != nil { t.Fatalf("unexpected error: %s", err) } tcs := []struct { desc string in string want server.ToolConfigs }{ { desc: "basic example", in: ` tools: example_tool: kind: mongodb-find source: my-instance description: some description database: test_db collection: test_coll filterPayload: | { name: {{json .name}} } filterParams: - name: name type: string description: small description projectPayload: | { name: 1, age: 1 } projectParams: [] sortPayload: | { timestamp: -1 } sortParams: [] `, want: server.ToolConfigs{ "example_tool": mongodbfind.Config{ Name: "example_tool", Kind: "mongodb-find", Source: "my-instance", AuthRequired: []string{}, Database: "test_db", Collection: "test_coll", Description: "some description", FilterPayload: "{ name: {{json .name}} }\n", FilterParams: tools.Parameters{ &tools.StringParameter{ CommonParameter: tools.CommonParameter{ Name: "name", Type: "string", Desc: "small description", }, }, }, ProjectPayload: "{ name: 1, age: 1 }\n", ProjectParams: tools.Parameters{}, SortPayload: "{ timestamp: -1 }\n", SortParams: tools.Parameters{}, }, }, }, } for _, tc := range tcs { t.Run(tc.desc, func(t *testing.T) { got := struct { Tools server.ToolConfigs `yaml:"tools"` }{} // Parse contents err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got) if err != nil { t.Fatalf("unable to unmarshal: %s", err) } if diff := cmp.Diff(tc.want, got.Tools); diff != "" { t.Fatalf("incorrect parse: diff %v", diff) } }) } } func TestFailParseFromYamlMongoQuery(t *testing.T) { ctx, err := testutils.ContextWithNewLogger() if err != nil { t.Fatalf("unexpected error: %s", err) } tcs := []struct { desc string in string err string }{ { desc: "Invalid method", in: ` tools: example_tool: kind: mongodb-find source: my-instance description: some description collection: test_coll filterPayload: | { name : {{json .name}} } `, err: `unable to parse tool "example_tool" as kind "mongodb-find"`, }, } for _, tc := range tcs { t.Run(tc.desc, func(t *testing.T) { got := struct { Tools server.ToolConfigs `yaml:"tools"` }{} // Parse contents err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got) if err == nil { t.Fatalf("expect parsing to fail") } errStr := err.Error() if !strings.Contains(errStr, tc.err) { t.Fatalf("unexpected error string: got %q, want substring %q", errStr, tc.err) } }) } } ``` -------------------------------------------------------------------------------- /internal/tools/mongodb/mongodbfindone/mongodbfindone_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 mongodbfindone_test import ( "strings" "testing" "github.com/googleapis/genai-toolbox/internal/tools" "github.com/googleapis/genai-toolbox/internal/tools/mongodb/mongodbfindone" yaml "github.com/goccy/go-yaml" "github.com/google/go-cmp/cmp" "github.com/googleapis/genai-toolbox/internal/server" "github.com/googleapis/genai-toolbox/internal/testutils" ) func TestParseFromYamlMongoQuery(t *testing.T) { ctx, err := testutils.ContextWithNewLogger() if err != nil { t.Fatalf("unexpected error: %s", err) } tcs := []struct { desc string in string want server.ToolConfigs }{ { desc: "basic example", in: ` tools: example_tool: kind: mongodb-find-one source: my-instance description: some description database: test_db collection: test_coll filterPayload: | { name: {{json .name}} } filterParams: - name: name type: string description: small description projectPayload: | { name: 1, age: 1 } projectParams: [] sortPayload: | { timestamp: -1 } sortParams: [] `, want: server.ToolConfigs{ "example_tool": mongodbfindone.Config{ Name: "example_tool", Kind: "mongodb-find-one", Source: "my-instance", AuthRequired: []string{}, Database: "test_db", Collection: "test_coll", Description: "some description", FilterPayload: "{ name: {{json .name}} }\n", FilterParams: tools.Parameters{ &tools.StringParameter{ CommonParameter: tools.CommonParameter{ Name: "name", Type: "string", Desc: "small description", }, }, }, ProjectPayload: "{ name: 1, age: 1 }\n", ProjectParams: tools.Parameters{}, SortPayload: "{ timestamp: -1 }\n", SortParams: tools.Parameters{}, }, }, }, } for _, tc := range tcs { t.Run(tc.desc, func(t *testing.T) { got := struct { Tools server.ToolConfigs `yaml:"tools"` }{} // Parse contents err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got) if err != nil { t.Fatalf("unable to unmarshal: %s", err) } if diff := cmp.Diff(tc.want, got.Tools); diff != "" { t.Fatalf("incorrect parse: diff %v", diff) } }) } } func TestFailParseFromYamlMongoQuery(t *testing.T) { ctx, err := testutils.ContextWithNewLogger() if err != nil { t.Fatalf("unexpected error: %s", err) } tcs := []struct { desc string in string err string }{ { desc: "Invalid method", in: ` tools: example_tool: kind: mongodb-find-one source: my-instance description: some description collection: test_coll filterPayload: | { name : {{json .name}} } `, err: `unable to parse tool "example_tool" as kind "mongodb-find-one"`, }, } for _, tc := range tcs { t.Run(tc.desc, func(t *testing.T) { got := struct { Tools server.ToolConfigs `yaml:"tools"` }{} // Parse contents err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got) if err == nil { t.Fatalf("expect parsing to fail") } errStr := err.Error() if !strings.Contains(errStr, tc.err) { t.Fatalf("unexpected error string: got %q, want substring %q", errStr, tc.err) } }) } } ``` -------------------------------------------------------------------------------- /internal/sources/oceanbase/oceanbase_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 oceanbase_test import ( "testing" yaml "github.com/goccy/go-yaml" "github.com/google/go-cmp/cmp" "github.com/googleapis/genai-toolbox/internal/server" "github.com/googleapis/genai-toolbox/internal/sources/oceanbase" "github.com/googleapis/genai-toolbox/internal/testutils" ) // Test parsing OceanBase source config from YAML. func TestParseFromYamlOceanBase(t *testing.T) { tcs := []struct { desc string in string want server.SourceConfigs }{ { desc: "basic example", in: ` sources: my-oceanbase-instance: kind: oceanbase host: 0.0.0.0 port: 2881 database: ob_db user: ob_user password: ob_pass `, want: server.SourceConfigs{ "my-oceanbase-instance": oceanbase.Config{ Name: "my-oceanbase-instance", Kind: oceanbase.SourceKind, Host: "0.0.0.0", Port: "2881", Database: "ob_db", User: "ob_user", Password: "ob_pass", }, }, }, { desc: "with query timeout", in: ` sources: my-oceanbase-instance: kind: oceanbase host: 0.0.0.0 port: 2881 database: ob_db user: ob_user password: ob_pass queryTimeout: 30s `, want: server.SourceConfigs{ "my-oceanbase-instance": oceanbase.Config{ Name: "my-oceanbase-instance", Kind: oceanbase.SourceKind, Host: "0.0.0.0", Port: "2881", Database: "ob_db", User: "ob_user", Password: "ob_pass", QueryTimeout: "30s", }, }, }, } for _, tc := range tcs { t.Run(tc.desc, func(t *testing.T) { got := struct { Sources server.SourceConfigs `yaml:"sources"` }{} // Parse contents err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got) if err != nil { t.Fatalf("unable to unmarshal: %s", err) } if !cmp.Equal(tc.want, got.Sources) { t.Fatalf("incorrect parse: want %v, got %v", tc.want, got.Sources) } }) } } // Test parsing failure cases for OceanBase source config. func TestFailParseFromYamlOceanBase(t *testing.T) { tcs := []struct { desc string in string err string }{ { desc: "extra field", in: ` sources: my-oceanbase-instance: kind: oceanbase host: 0.0.0.0 port: 2881 database: ob_db user: ob_user password: ob_pass foo: bar `, err: "unable to parse source \"my-oceanbase-instance\" as \"oceanbase\": [2:1] unknown field \"foo\"\n 1 | database: ob_db\n> 2 | foo: bar\n ^\n 3 | host: 0.0.0.0\n 4 | kind: oceanbase\n 5 | password: ob_pass\n 6 | ", }, { desc: "missing required field", in: ` sources: my-oceanbase-instance: kind: oceanbase port: 2881 database: ob_db user: ob_user password: ob_pass `, err: "unable to parse source \"my-oceanbase-instance\" as \"oceanbase\": Key: 'Config.Host' Error:Field validation for 'Host' failed on the 'required' tag", }, } for _, tc := range tcs { t.Run(tc.desc, func(t *testing.T) { got := struct { Sources server.SourceConfigs `yaml:"sources"` }{} // Parse contents err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got) if err == nil { t.Fatalf("expect parsing to fail") } errStr := err.Error() if errStr != tc.err { t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err) } }) } } ``` -------------------------------------------------------------------------------- /internal/sources/postgres/postgres.go: -------------------------------------------------------------------------------- ```go // Copyright 2024 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package postgres import ( "context" "fmt" "net/url" "strings" "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/sources" "github.com/googleapis/genai-toolbox/internal/util" "github.com/jackc/pgx/v5/pgxpool" "go.opentelemetry.io/otel/trace" ) const SourceKind string = "postgres" // 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 } return actual, nil } type Config struct { Name string `yaml:"name" validate:"required"` Kind string `yaml:"kind" validate:"required"` Host string `yaml:"host" validate:"required"` Port string `yaml:"port" validate:"required"` User string `yaml:"user" validate:"required"` Password string `yaml:"password" validate:"required"` Database string `yaml:"database" validate:"required"` QueryParams map[string]string `yaml:"queryParams"` } func (r Config) SourceConfigKind() string { return SourceKind } func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) { pool, err := initPostgresConnectionPool(ctx, tracer, r.Name, r.Host, r.Port, r.User, r.Password, r.Database, r.QueryParams) if err != nil { return nil, fmt.Errorf("unable to create pool: %w", err) } err = pool.Ping(ctx) if err != nil { return nil, fmt.Errorf("unable to connect successfully: %w", err) } s := &Source{ Name: r.Name, Kind: SourceKind, Pool: pool, } return s, nil } var _ sources.Source = &Source{} type Source struct { Name string `yaml:"name"` Kind string `yaml:"kind"` Pool *pgxpool.Pool } func (s *Source) SourceKind() string { return SourceKind } func (s *Source) PostgresPool() *pgxpool.Pool { return s.Pool } func initPostgresConnectionPool(ctx context.Context, tracer trace.Tracer, name, host, port, user, pass, dbname string, queryParams map[string]string) (*pgxpool.Pool, error) { //nolint:all // Reassigned ctx ctx, span := sources.InitConnectionSpan(ctx, tracer, SourceKind, name) defer span.End() userAgent, err := util.UserAgentFromContext(ctx) if err != nil { userAgent = "genai-toolbox" } if queryParams == nil { // Initialize the map before using it queryParams = make(map[string]string) } if _, ok := queryParams["application_name"]; !ok { queryParams["application_name"] = userAgent } // urlExample := "postgres:dd//username:password@localhost:5432/database_name" url := &url.URL{ Scheme: "postgres", User: url.UserPassword(user, pass), Host: fmt.Sprintf("%s:%s", host, port), Path: dbname, RawQuery: ConvertParamMapToRawQuery(queryParams), } pool, err := pgxpool.New(ctx, url.String()) if err != nil { return nil, fmt.Errorf("unable to create connection pool: %w", err) } return pool, nil } func ConvertParamMapToRawQuery(queryParams map[string]string) string { queryArray := []string{} for k, v := range queryParams { queryArray = append(queryArray, fmt.Sprintf("%s=%s", k, v)) } return strings.Join(queryArray, "&") } ``` -------------------------------------------------------------------------------- /internal/sources/cassandra/cassandra.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 cassandra import ( "context" "fmt" "github.com/goccy/go-yaml" "github.com/gocql/gocql" "github.com/googleapis/genai-toolbox/internal/sources" "go.opentelemetry.io/otel/trace" ) const SourceKind string = "cassandra" 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 } return actual, nil } type Config struct { Name string `yaml:"name" validate:"required"` Kind string `yaml:"kind" validate:"required"` Hosts []string `yaml:"hosts" validate:"required"` Keyspace string `yaml:"keyspace"` ProtoVersion int `yaml:"protoVersion"` Username string `yaml:"username"` Password string `yaml:"password"` CAPath string `yaml:"caPath"` CertPath string `yaml:"certPath"` KeyPath string `yaml:"keyPath"` EnableHostVerification bool `yaml:"enableHostVerification"` } // Initialize implements sources.SourceConfig. func (c Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) { session, err := initCassandraSession(ctx, tracer, c) if err != nil { return nil, fmt.Errorf("unable to create session: %v", err) } s := &Source{ Name: c.Name, Kind: SourceKind, Session: session, } return s, nil } // SourceConfigKind implements sources.SourceConfig. func (c Config) SourceConfigKind() string { return SourceKind } var _ sources.SourceConfig = Config{} type Source struct { Name string `yaml:"name"` Kind string `yaml:"kind"` Session *gocql.Session } // CassandraSession implements cassandra.compatibleSource. func (s *Source) CassandraSession() *gocql.Session { return s.Session } // SourceKind implements sources.Source. func (s Source) SourceKind() string { return SourceKind } var _ sources.Source = &Source{} func initCassandraSession(ctx context.Context, tracer trace.Tracer, c Config) (*gocql.Session, error) { //nolint:all // Reassigned ctx ctx, span := sources.InitConnectionSpan(ctx, tracer, SourceKind, c.Name) defer span.End() // Validate authentication configuration if c.Password != "" && c.Username == "" { return nil, fmt.Errorf("invalid Cassandra configuration: password provided without a username") } cluster := gocql.NewCluster(c.Hosts...) cluster.ProtoVersion = c.ProtoVersion cluster.Keyspace = c.Keyspace // Configure authentication if username is provided if c.Username != "" { cluster.Authenticator = gocql.PasswordAuthenticator{ Username: c.Username, Password: c.Password, } } // Configure SSL options if any are specified if c.CAPath != "" || c.CertPath != "" || c.KeyPath != "" || c.EnableHostVerification { cluster.SslOpts = &gocql.SslOptions{ CaPath: c.CAPath, CertPath: c.CertPath, KeyPath: c.KeyPath, EnableHostVerification: c.EnableHostVerification, } } // Create session session, err := cluster.CreateSession() if err != nil { return nil, fmt.Errorf("failed to create Cassandra session: %w", err) } return session, nil } ``` -------------------------------------------------------------------------------- /internal/tools/common.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 tools import ( "bytes" "encoding/json" "fmt" "regexp" "text/template" ) var validName = regexp.MustCompile(`^[a-zA-Z0-9_-]*$`) func IsValidName(s string) bool { return validName.MatchString(s) } // ConvertAnySliceToTyped a []any to typed slice ([]string, []int, []float etc.) func ConvertAnySliceToTyped(s []any, itemType string) (any, error) { var typedSlice any switch itemType { case "string": tempSlice := make([]string, len(s)) for j, item := range s { s, ok := item.(string) if !ok { return nil, fmt.Errorf("expected item at index %d to be string, got %T", j, item) } tempSlice[j] = s } typedSlice = tempSlice case "integer": tempSlice := make([]int64, len(s)) for j, item := range s { i, ok := item.(int) if !ok { return nil, fmt.Errorf("expected item at index %d to be integer, got %T", j, item) } tempSlice[j] = int64(i) } typedSlice = tempSlice case "float": tempSlice := make([]float64, len(s)) for j, item := range s { f, ok := item.(float64) if !ok { return nil, fmt.Errorf("expected item at index %d to be float, got %T", j, item) } tempSlice[j] = f } typedSlice = tempSlice case "boolean": tempSlice := make([]bool, len(s)) for j, item := range s { b, ok := item.(bool) if !ok { return nil, fmt.Errorf("expected item at index %d to be boolean, got %T", j, item) } tempSlice[j] = b } typedSlice = tempSlice } return typedSlice, nil } // convertParamToJSON is a Go template helper function to convert a parameter to JSON formatted string. func convertParamToJSON(param any) (string, error) { jsonData, err := json.Marshal(param) if err != nil { return "", fmt.Errorf("failed to marshal param to JSON: %w", err) } return string(jsonData), nil } // PopulateTemplateWithJSON populate a Go template with a custom `json` array formatter func PopulateTemplateWithJSON(templateName, templateString string, data map[string]any) (string, error) { return PopulateTemplateWithFunc(templateName, templateString, data, template.FuncMap{ "json": convertParamToJSON, }) } // PopulateTemplate populate a Go template with no custom formatters func PopulateTemplate(templateName, templateString string, data map[string]any) (string, error) { return PopulateTemplateWithFunc(templateName, templateString, data, nil) } // PopulateTemplateWithFunc populate a Go template with provided functions func PopulateTemplateWithFunc(templateName, templateString string, data map[string]any, funcMap template.FuncMap) (string, error) { tmpl := template.New(templateName) if funcMap != nil { tmpl = tmpl.Funcs(funcMap) } parsedTmpl, err := tmpl.Parse(templateString) if err != nil { return "", fmt.Errorf("error parsing template '%s': %w", templateName, err) } var result bytes.Buffer if err := parsedTmpl.Execute(&result, data); err != nil { return "", fmt.Errorf("error executing template '%s': %w", templateName, err) } return result.String(), nil } // CheckDuplicateParameters verify there are no duplicate parameter names func CheckDuplicateParameters(ps Parameters) error { seenNames := make(map[string]bool) for _, p := range ps { pName := p.GetName() if _, exists := seenNames[pName]; exists { return fmt.Errorf("parameter name must be unique across all parameter fields. Duplicate parameter: %s", pName) } seenNames[pName] = true } return nil } ``` -------------------------------------------------------------------------------- /internal/tools/cloudmonitoring/cloudmonitoring_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 cloudmonitoring_test import ( "strings" "testing" yaml "github.com/goccy/go-yaml" "github.com/google/go-cmp/cmp" "github.com/googleapis/genai-toolbox/internal/server" "github.com/googleapis/genai-toolbox/internal/testutils" "github.com/googleapis/genai-toolbox/internal/tools/cloudmonitoring" ) func TestParseFromYamlCloudMonitoring(t *testing.T) { ctx, err := testutils.ContextWithNewLogger() if err != nil { t.Fatalf("unexpected error: %s", err) } tcs := []struct { desc string in string want server.ToolConfigs }{ { desc: "basic example", in: ` tools: example_tool: kind: cloud-monitoring-query-prometheus source: my-instance description: some description `, want: server.ToolConfigs{ "example_tool": cloudmonitoring.Config{ Name: "example_tool", Kind: "cloud-monitoring-query-prometheus", Source: "my-instance", Description: "some description", AuthRequired: []string{}, }, }, }, { desc: "advanced example", in: ` tools: example_tool: kind: cloud-monitoring-query-prometheus source: my-instance description: some description authRequired: - my-google-auth-service - other-auth-service `, want: server.ToolConfigs{ "example_tool": cloudmonitoring.Config{ Name: "example_tool", Kind: "cloud-monitoring-query-prometheus", Source: "my-instance", Description: "some description", AuthRequired: []string{"my-google-auth-service", "other-auth-service"}, }, }, }, } for _, tc := range tcs { t.Run(tc.desc, func(t *testing.T) { got := struct { Tools server.ToolConfigs `yaml:"tools"` }{} // Parse contents err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got) if err != nil { t.Fatalf("unable to unmarshal: %s", err) } if diff := cmp.Diff(tc.want, got.Tools); diff != "" { t.Fatalf("incorrect parse: diff %v", diff) } }) } } func TestFailParseFromYamlCloudMonitoring(t *testing.T) { ctx, err := testutils.ContextWithNewLogger() if err != nil { t.Fatalf("unexpected error: %s", err) } tcs := []struct { desc string in string err string }{ { desc: "Invalid kind", in: ` tools: example_tool: kind: invalid-kind source: my-instance description: some description `, err: `unknown tool kind: "invalid-kind"`, }, { desc: "missing source", in: ` tools: example_tool: kind: cloud-monitoring-query-prometheus description: some description `, err: `Key: 'Config.Source' Error:Field validation for 'Source' failed on the 'required' tag`, }, { desc: "missing description", in: ` tools: example_tool: kind: cloud-monitoring-query-prometheus source: my-instance `, err: `Key: 'Config.Description' Error:Field validation for 'Description' failed on the 'required' tag`, }, } for _, tc := range tcs { t.Run(tc.desc, func(t *testing.T) { got := struct { Tools server.ToolConfigs `yaml:"tools"` }{} // Parse contents err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got) if err == nil { t.Fatalf("expect parsing to fail") } errStr := err.Error() if !strings.Contains(errStr, tc.err) { t.Fatalf("unexpected error string: got %q, want substring %q", errStr, tc.err) } }) } } ``` -------------------------------------------------------------------------------- /internal/sources/cloudmonitoring/cloud_monitoring.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" "fmt" "net/http" "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/sources" "github.com/googleapis/genai-toolbox/internal/util" "go.opentelemetry.io/otel/trace" "golang.org/x/oauth2" "golang.org/x/oauth2/google" monitoring "google.golang.org/api/monitoring/v3" ) const SourceKind string = "cloud-monitoring" type userAgentRoundTripper struct { userAgent string next http.RoundTripper } func (rt *userAgentRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { newReq := *req newReq.Header = make(http.Header) for k, v := range req.Header { newReq.Header[k] = v } ua := newReq.Header.Get("User-Agent") if ua == "" { newReq.Header.Set("User-Agent", rt.userAgent) } else { newReq.Header.Set("User-Agent", ua+" "+rt.userAgent) } return rt.next.RoundTrip(&newReq) } // 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 } return actual, nil } type Config struct { Name string `yaml:"name" validate:"required"` Kind string `yaml:"kind" validate:"required"` UseClientOAuth bool `yaml:"useClientOAuth"` } func (r Config) SourceConfigKind() string { return SourceKind } // Initialize initializes a Cloud Monitoring Source instance. func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) { ua, err := util.UserAgentFromContext(ctx) if err != nil { return nil, fmt.Errorf("error in User Agent retrieval: %s", err) } var client *http.Client if r.UseClientOAuth { client = &http.Client{ Transport: &userAgentRoundTripper{ userAgent: ua, next: http.DefaultTransport, }, } } else { // Use Application Default Credentials creds, err := google.FindDefaultCredentials(ctx, monitoring.MonitoringScope) if err != nil { return nil, fmt.Errorf("failed to find default credentials: %w", err) } baseClient := oauth2.NewClient(ctx, creds.TokenSource) baseClient.Transport = &userAgentRoundTripper{ userAgent: ua, next: baseClient.Transport, } client = baseClient } s := &Source{ Name: r.Name, Kind: SourceKind, BaseURL: "https://monitoring.googleapis.com", Client: client, UserAgent: ua, UseClientOAuth: r.UseClientOAuth, } return s, nil } var _ sources.Source = &Source{} type Source struct { Name string `yaml:"name"` Kind string `yaml:"kind"` BaseURL string `yaml:"baseUrl"` Client *http.Client UserAgent string UseClientOAuth bool } func (s *Source) SourceKind() string { return SourceKind } func (s *Source) GetClient(ctx context.Context, accessToken string) (*http.Client, error) { if s.UseClientOAuth { if accessToken == "" { return nil, fmt.Errorf("client-side OAuth is enabled but no access token was provided") } token := &oauth2.Token{AccessToken: accessToken} return oauth2.NewClient(ctx, oauth2.StaticTokenSource(token)), nil } return s.Client, nil } func (s *Source) UseClientAuthorization() bool { return s.UseClientOAuth } ``` -------------------------------------------------------------------------------- /docs/en/resources/tools/mongodb/mongodb-find.md: -------------------------------------------------------------------------------- ```markdown --- title: "mongodb-find" type: docs weight: 1 description: > A "mongodb-find" tool finds and retrieves documents from a MongoDB collection. aliases: - /resources/tools/mongodb-find --- ## About A `mongodb-find` tool is used to query a MongoDB collection and retrieve documents that match a specified filter. It's a flexible tool that allows you to shape the output by selecting specific fields (**projection**), ordering the results (**sorting**), and restricting the number of documents returned (**limiting**). The tool returns a JSON array of the documents found. This tool is compatible with the following source kind: * [`mongodb`](../../sources/mongodb.md) ## Example Here's an example that finds up to 10 users from the `customers` collection who live in a specific city. The results are sorted by their last name, and only their first name, last name, and email are returned. ```yaml tools: find_local_customers: kind: mongodb-find source: my-mongo-source description: Finds customers by city, sorted by last name. database: crm collection: customers limit: 10 filterPayload: | { "address.city": {{json .city}} } filterParams: - name: city type: string description: The city to search for customers in. projectPayload: | { "first_name": 1, "last_name": 1, "email": 1, "_id": 0 } sortPayload: | { "last_name": {{json .sort_order}} } sortParams: - name: sort_order type: integer description: The sort order (1 for ascending, -1 for descending). ``` ## Reference | **field** | **type** | **required** | **description** | |:---------------|:---------|:-------------|:----------------------------------------------------------------------------------------------------------------------------| | kind | string | true | Must be `mongodb-find`. | | 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 to query. | | collection | string | true | The name of the MongoDB collection to query. | | filterPayload | string | true | The MongoDB query filter document to select which documents to return. Uses `{{json .param_name}}` for templating. | | filterParams | list | true | A list of parameter objects that define the variables used in the `filterPayload`. | | projectPayload | string | false | An optional MongoDB projection document to specify which fields to include (1) or exclude (0) in the results. | | projectParams | list | false | A list of parameter objects for the `projectPayload`. | | sortPayload | string | false | An optional MongoDB sort document to define the order of the returned documents. Use 1 for ascending and -1 for descending. | | sortParams | list | false | A list of parameter objects for the `sortPayload`. | | limit | integer | false | An optional integer specifying the maximum number of documents to return. | ``` -------------------------------------------------------------------------------- /internal/sources/http/http_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 http_test import ( "testing" yaml "github.com/goccy/go-yaml" "github.com/google/go-cmp/cmp" "github.com/googleapis/genai-toolbox/internal/server" "github.com/googleapis/genai-toolbox/internal/sources" "github.com/googleapis/genai-toolbox/internal/sources/http" "github.com/googleapis/genai-toolbox/internal/testutils" ) func TestParseFromYamlHttp(t *testing.T) { tcs := []struct { desc string in string want server.SourceConfigs }{ { desc: "basic example", in: ` sources: my-http-instance: kind: http baseUrl: http://test_server/ `, want: map[string]sources.SourceConfig{ "my-http-instance": http.Config{ Name: "my-http-instance", Kind: http.SourceKind, BaseURL: "http://test_server/", Timeout: "30s", DisableSslVerification: false, }, }, }, { desc: "advanced example", in: ` sources: my-http-instance: kind: http baseUrl: http://test_server/ timeout: 10s headers: Authorization: test_header Custom-Header: custom queryParams: api-key: test_api_key param: param-value disableSslVerification: true `, want: map[string]sources.SourceConfig{ "my-http-instance": http.Config{ Name: "my-http-instance", Kind: http.SourceKind, BaseURL: "http://test_server/", Timeout: "10s", DefaultHeaders: map[string]string{"Authorization": "test_header", "Custom-Header": "custom"}, QueryParams: map[string]string{"api-key": "test_api_key", "param": "param-value"}, DisableSslVerification: true, }, }, }, } for _, tc := range tcs { t.Run(tc.desc, func(t *testing.T) { got := struct { Sources server.SourceConfigs `yaml:"sources"` }{} // Parse contents err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got) if err != nil { t.Fatalf("unable to unmarshal: %s", err) } if !cmp.Equal(tc.want, got.Sources) { t.Fatalf("incorrect parse: want %v, got %v", tc.want, got.Sources) } }) } } func TestFailParseFromYaml(t *testing.T) { tcs := []struct { desc string in string err string }{ { desc: "extra field", in: ` sources: my-http-instance: kind: http baseUrl: http://test_server/ timeout: 10s headers: Authorization: test_header queryParams: api-key: test_api_key project: test-project `, err: "unable to parse source \"my-http-instance\" as \"http\": [5:1] unknown field \"project\"\n 2 | headers:\n 3 | Authorization: test_header\n 4 | kind: http\n> 5 | project: test-project\n ^\n 6 | queryParams:\n 7 | api-key: test_api_key\n 8 | timeout: 10s", }, { desc: "missing required field", in: ` sources: my-http-instance: baseUrl: http://test_server/ `, err: "missing 'kind' field for source \"my-http-instance\"", }, } for _, tc := range tcs { t.Run(tc.desc, func(t *testing.T) { got := struct { Sources server.SourceConfigs `yaml:"sources"` }{} // Parse contents err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got) if err == nil { t.Fatalf("expect parsing to fail") } errStr := err.Error() if errStr != tc.err { t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err) } }) } } ``` -------------------------------------------------------------------------------- /internal/tools/alloydbainl/alloydbainl_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 alloydbainl_test import ( "testing" yaml "github.com/goccy/go-yaml" "github.com/google/go-cmp/cmp" "github.com/googleapis/genai-toolbox/internal/server" "github.com/googleapis/genai-toolbox/internal/testutils" "github.com/googleapis/genai-toolbox/internal/tools" "github.com/googleapis/genai-toolbox/internal/tools/alloydbainl" ) func TestParseFromYamlAlloyDBNLA(t *testing.T) { ctx, err := testutils.ContextWithNewLogger() if err != nil { t.Fatalf("unexpected error: %s", err) } tcs := []struct { desc string in string want server.ToolConfigs }{ { desc: "basic example", in: ` tools: example_tool: kind: alloydb-ai-nl source: my-alloydb-instance description: AlloyDB natural language query tool nlConfig: 'my_nl_config' authRequired: - my-google-auth-service nlConfigParameters: - name: user_id type: string description: user_id to use authServices: - name: my-google-auth-service field: sub `, want: server.ToolConfigs{ "example_tool": alloydbainl.Config{ Name: "example_tool", Kind: "alloydb-ai-nl", Source: "my-alloydb-instance", Description: "AlloyDB natural language query tool", NLConfig: "my_nl_config", AuthRequired: []string{"my-google-auth-service"}, NLConfigParameters: []tools.Parameter{ tools.NewStringParameterWithAuth("user_id", "user_id to use", []tools.ParamAuthService{{Name: "my-google-auth-service", Field: "sub"}}), }, }, }, }, { desc: "with multiple parameters", in: ` tools: complex_tool: kind: alloydb-ai-nl source: my-alloydb-instance description: AlloyDB natural language query tool with multiple parameters nlConfig: 'complex_nl_config' authRequired: - my-google-auth-service - other-auth-service nlConfigParameters: - name: user_id type: string description: user_id to use authServices: - name: my-google-auth-service field: sub - name: user_email type: string description: user_email to use authServices: - name: my-google-auth-service field: user_email `, want: server.ToolConfigs{ "complex_tool": alloydbainl.Config{ Name: "complex_tool", Kind: "alloydb-ai-nl", Source: "my-alloydb-instance", Description: "AlloyDB natural language query tool with multiple parameters", NLConfig: "complex_nl_config", AuthRequired: []string{"my-google-auth-service", "other-auth-service"}, NLConfigParameters: []tools.Parameter{ tools.NewStringParameterWithAuth("user_id", "user_id to use", []tools.ParamAuthService{{Name: "my-google-auth-service", Field: "sub"}}), tools.NewStringParameterWithAuth("user_email", "user_email to use", []tools.ParamAuthService{{Name: "my-google-auth-service", Field: "user_email"}}), }, }, }, }, } for _, tc := range tcs { t.Run(tc.desc, func(t *testing.T) { got := struct { Tools server.ToolConfigs `yaml:"tools"` }{} // Parse contents err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got) if err != nil { t.Fatalf("unable to unmarshal: %s", err) } if diff := cmp.Diff(tc.want, got.Tools); diff != "" { t.Fatalf("incorrect parse: diff %v", diff) } }) } } ``` -------------------------------------------------------------------------------- /tests/redis/redis_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 redis import ( "context" "fmt" "os" "regexp" "testing" "time" "github.com/googleapis/genai-toolbox/internal/testutils" "github.com/googleapis/genai-toolbox/tests" "github.com/redis/go-redis/v9" ) var ( RedisSourceKind = "redis" RedisToolKind = "redis" RedisAddress = os.Getenv("REDIS_ADDRESS") RedisPass = os.Getenv("REDIS_PASS") ) func getRedisVars(t *testing.T) map[string]any { switch "" { case RedisAddress: t.Fatal("'REDIS_ADDRESS' not set") case RedisPass: t.Fatal("'REDIS_PASS' not set") } return map[string]any{ "kind": RedisSourceKind, "address": []string{RedisAddress}, "password": RedisPass, } } func initRedisClient(ctx context.Context, address, pass string) (*redis.Client, error) { // Create a new Redis client standaloneClient := redis.NewClient(&redis.Options{ Addr: address, PoolSize: 10, ConnMaxIdleTime: 60 * time.Second, MinIdleConns: 1, Password: pass, }) _, err := standaloneClient.Ping(ctx).Result() if err != nil { return nil, fmt.Errorf("unable to connect to redis: %s", err) } return standaloneClient, nil } func TestRedisToolEndpoints(t *testing.T) { sourceConfig := getRedisVars(t) ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() var args []string client, err := initRedisClient(ctx, RedisAddress, RedisPass) if err != nil { t.Fatalf("unable to create Redis connection: %s", err) } // set up data for param tool teardownDB := setupRedisDB(t, ctx, client) defer teardownDB(t) // Write config into a file and pass it to command toolsFile := tests.GetRedisValkeyToolsConfig(sourceConfig, RedisToolKind) cmd, cleanup, err := tests.StartCmd(ctx, toolsFile, args...) if err != nil { t.Fatalf("command initialization returned an error: %s", err) } defer cleanup() waitCtx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() out, err := testutils.WaitForString(waitCtx, regexp.MustCompile(`Server ready to serve`), cmd.Out) if err != nil { t.Logf("toolbox command logs: \n%s", out) t.Fatalf("toolbox didn't start successfully: %s", err) } // Get configs for tests select1Want, mcpMyFailToolWant, invokeParamWant, invokeIdNullWant, nullWant, mcpSelect1Want, mcpInvokeParamWant := tests.GetRedisValkeyWants() // Run tests tests.RunToolGetTest(t) tests.RunToolInvokeTest(t, select1Want, tests.WithMyToolId3NameAliceWant(invokeParamWant), tests.WithMyArrayToolWant(invokeParamWant), tests.WithMyToolById4Want(invokeIdNullWant), tests.WithNullWant(nullWant), ) tests.RunMCPToolCallMethod(t, mcpMyFailToolWant, mcpSelect1Want, tests.WithMcpMyToolId3NameAliceWant(mcpInvokeParamWant), ) } func setupRedisDB(t *testing.T, ctx context.Context, client *redis.Client) func(*testing.T) { keys := []string{"row1", "row2", "row3", "row4", "null"} commands := [][]any{ {"HSET", keys[0], "id", 1, "name", "Alice"}, {"HSET", keys[1], "id", 2, "name", "Jane"}, {"HSET", keys[2], "id", 3, "name", "Sid"}, {"HSET", keys[3], "id", 4, "name", nil}, {"SET", keys[4], "null"}, {"HSET", tests.ServiceAccountEmail, "name", "Alice"}, } for _, c := range commands { resp := client.Do(ctx, c...) if err := resp.Err(); err != nil { t.Fatalf("unable to insert test data: %s", err) } } return func(t *testing.T) { // tear down test _, err := client.Del(ctx, keys...).Result() if err != nil { t.Errorf("Teardown failed: %s", err) } } } ``` -------------------------------------------------------------------------------- /internal/server/mcp/jsonrpc/jsonrpc.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 jsonrpc // JSONRPC_VERSION is the version of JSON-RPC used by MCP. const JSONRPC_VERSION = "2.0" // Standard JSON-RPC error codes const ( PARSE_ERROR = -32700 INVALID_REQUEST = -32600 METHOD_NOT_FOUND = -32601 INVALID_PARAMS = -32602 INTERNAL_ERROR = -32603 ) // ProgressToken is used to associate progress notifications with the original request. type ProgressToken interface{} // RequestId is a uniquely identifying ID for a request in JSON-RPC. // It can be any JSON-serializable value, typically a number or string. type RequestId interface{} // Request represents a bidirectional message with method and parameters expecting a response. type Request struct { Method string `json:"method"` Params struct { Meta struct { // If specified, the caller is requesting out-of-band progress // notifications for this request (as represented by // notifications/progress). The value of this parameter is an // opaque token that will be attached to any subsequent // notifications. The receiver is not obligated to provide these // notifications. ProgressToken ProgressToken `json:"progressToken,omitempty"` } `json:"_meta,omitempty"` } `json:"params,omitempty"` } // JSONRPCRequest represents a request that expects a response. type JSONRPCRequest struct { Jsonrpc string `json:"jsonrpc"` Id RequestId `json:"id"` Request Params any `json:"params,omitempty"` } // Notification is a one-way message requiring no response. type Notification struct { Method string `json:"method"` Params struct { Meta map[string]interface{} `json:"_meta,omitempty"` } `json:"params,omitempty"` } // JSONRPCNotification represents a notification which does not expect a response. type JSONRPCNotification struct { Jsonrpc string `json:"jsonrpc"` Notification } // Result represents a response for the request query. type Result struct { // This result property is reserved by the protocol to allow clients and // servers to attach additional metadata to their responses. Meta map[string]interface{} `json:"_meta,omitempty"` } // JSONRPCResponse represents a successful (non-error) response to a request. type JSONRPCResponse struct { Jsonrpc string `json:"jsonrpc"` Id RequestId `json:"id"` Result interface{} `json:"result"` } // Error represents the error content. type Error struct { // The error type that occurred. Code int `json:"code"` // A short description of the error. The message SHOULD be limited // to a concise single sentence. Message string `json:"message"` // Additional information about the error. The value of this member // is defined by the sender (e.g. detailed error information, nested errors etc.). Data interface{} `json:"data,omitempty"` } // JSONRPCError represents a non-successful (error) response to a request. type JSONRPCError struct { Jsonrpc string `json:"jsonrpc"` Id RequestId `json:"id"` Error Error `json:"error"` } // Generic baseMessage could either be a JSONRPCNotification or JSONRPCRequest type BaseMessage struct { Jsonrpc string `json:"jsonrpc"` Method string `json:"method"` Id RequestId `json:"id,omitempty"` } // NewError is the standard JSONRPC response sent back when an error has been encountered. func NewError(id RequestId, code int, message string, data any) JSONRPCError { return JSONRPCError{ Jsonrpc: JSONRPC_VERSION, Id: id, Error: Error{ Code: code, Message: message, Data: data, }, } } ``` -------------------------------------------------------------------------------- /internal/sources/mysql/mysql.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 mysql import ( "context" "database/sql" "fmt" "net/url" "time" _ "github.com/go-sql-driver/mysql" "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/sources" "github.com/googleapis/genai-toolbox/internal/util" "go.opentelemetry.io/otel/trace" ) const SourceKind string = "mysql" // 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 } return actual, nil } type Config struct { Name string `yaml:"name" validate:"required"` Kind string `yaml:"kind" validate:"required"` Host string `yaml:"host" validate:"required"` Port string `yaml:"port" validate:"required"` User string `yaml:"user" validate:"required"` Password string `yaml:"password" validate:"required"` Database string `yaml:"database" validate:"required"` QueryTimeout string `yaml:"queryTimeout"` QueryParams map[string]string `yaml:"queryParams"` } func (r Config) SourceConfigKind() string { return SourceKind } func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) { pool, err := initMySQLConnectionPool(ctx, tracer, r.Name, r.Host, r.Port, r.User, r.Password, r.Database, r.QueryTimeout, r.QueryParams) if err != nil { return nil, fmt.Errorf("unable to create pool: %w", err) } err = pool.PingContext(ctx) if err != nil { return nil, fmt.Errorf("unable to connect successfully: %w", err) } s := &Source{ Name: r.Name, Kind: SourceKind, Pool: pool, } return s, nil } var _ sources.Source = &Source{} type Source struct { Name string `yaml:"name"` Kind string `yaml:"kind"` Pool *sql.DB } func (s *Source) SourceKind() string { return SourceKind } func (s *Source) MySQLPool() *sql.DB { return s.Pool } func initMySQLConnectionPool(ctx context.Context, tracer trace.Tracer, name, host, port, user, pass, dbname, queryTimeout string, queryParams map[string]string) (*sql.DB, error) { //nolint:all // Reassigned ctx ctx, span := sources.InitConnectionSpan(ctx, tracer, SourceKind, name) defer span.End() // Build query parameters via url.Values for deterministic order and proper escaping. values := url.Values{} // Derive readTimeout from queryTimeout when provided. if queryTimeout != "" { timeout, err := time.ParseDuration(queryTimeout) if err != nil { return nil, fmt.Errorf("invalid queryTimeout %q: %w", queryTimeout, err) } values.Set("readTimeout", timeout.String()) } // Custom user parameters for k, v := range queryParams { if v == "" { continue // skip empty values } values.Set(k, v) } userAgent, err := util.UserAgentFromContext(ctx) if err != nil { return nil, err } dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?parseTime=true&connectionAttributes=program_name:%s", user, pass, host, port, dbname, url.QueryEscape(userAgent)) if enc := values.Encode(); enc != "" { dsn += "&" + enc } // Interact with the driver directly as you normally would pool, err := sql.Open("mysql", dsn) if err != nil { return nil, fmt.Errorf("sql.Open: %w", err) } return pool, nil } ``` -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- ```yaml # 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. name: tests on: push: branches: - "main" pull_request: pull_request_target: types: [labeled] # Declare default permissions as read only. permissions: read-all jobs: integration: # run job on proper workflow event triggers (skip job for pull_request event from forks and only run pull_request_target for "tests: run" label) if: "${{ (github.event.action != 'labeled' && github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name) || github.event.label.name == 'tests: run' }}" name: unit tests runs-on: ${{ matrix.os }} strategy: matrix: os: [macos-latest, windows-latest, ubuntu-latest] fail-fast: false permissions: contents: "read" issues: "write" pull-requests: "write" steps: - name: Remove PR label if: "${{ github.event.action == 'labeled' && github.event.label.name == 'tests: run' }}" uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | try { await github.rest.issues.removeLabel({ name: 'tests: run', owner: context.repo.owner, repo: context.repo.repo, issue_number: context.payload.pull_request.number }); } catch (e) { console.log('Failed to remove label. Another job may have already removed it!'); } - name: Setup Go uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 with: go-version: "1.24" - name: Checkout code uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: ref: ${{ github.event.pull_request.head.sha }} repository: ${{ github.event.pull_request.head.repo.full_name }} token: ${{ secrets.GITHUB_TOKEN }} - name: Install dependencies run: go get . - name: Build run: go build -v ./... - name: Run tests with coverage if: ${{ runner.os == 'Linux' }} env: GOTOOLCHAIN: go1.25.0+auto run: | source_dir="./internal/sources/*" tool_dir="./internal/tools/*" auth_dir="./internal/auth/*" int_test_dir="./tests/*" included_packages=$(go list ./... | grep -v -e "$source_dir" -e "$tool_dir" -e "$auth_dir" -e "$int_test_dir") go test -race -cover -coverprofile=coverage.out -v $included_packages go test -race -v ./internal/sources/... ./internal/tools/... ./internal/auth/... - name: Run tests without coverage if: ${{ runner.os != 'Linux' }} run: | go test -race -v ./internal/... ./cmd/... - name: Check coverage if: ${{ runner.os == 'Linux' }} run: | FILE_TO_EXCLUDE="github.com/googleapis/genai-toolbox/internal/server/config.go" ESCAPED_PATH=$(echo "$FILE_TO_EXCLUDE" | sed 's/\//\\\//g; s/\./\\\./g') sed -i "/^${ESCAPED_PATH}:/d" coverage.out total_coverage=$(go tool cover -func=coverage.out | grep "total:" | awk '{print $3}') echo "Total coverage: $total_coverage" coverage_numeric=$(echo "$total_coverage" | sed 's/%//') if (( $(echo "$coverage_numeric < 40" | bc -l) )); then echo "Coverage failure: total coverage($total_coverage) is below 40%." exit 1 fi ``` -------------------------------------------------------------------------------- /tests/dataform/dataform_integration_test.go: -------------------------------------------------------------------------------- ```go // Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package dataformcompilelocal import ( "context" "fmt" "net/http" "os" "os/exec" "path/filepath" "regexp" "strings" "testing" "time" "github.com/googleapis/genai-toolbox/internal/testutils" "github.com/googleapis/genai-toolbox/tests" ) // setupTestProject creates a minimal dataform project using the 'dataform init' CLI. // It returns the path to the directory and a cleanup function. func setupTestProject(t *testing.T) (string, func()) { tmpDir, err := os.MkdirTemp("", "dataform-project-*") if err != nil { t.Fatalf("Failed to create temp dir: %v", err) } cleanup := func() { os.RemoveAll(tmpDir) } cmd := exec.Command("dataform", "init", tmpDir, "test-project-id", "US") if output, err := cmd.CombinedOutput(); err != nil { cleanup() t.Fatalf("Failed to run 'dataform init': %v\nOutput: %s", err, string(output)) } definitionsDir := filepath.Join(tmpDir, "definitions") exampleSQLX := `config { type: "table" } SELECT 1 AS test_col` err = os.WriteFile(filepath.Join(definitionsDir, "example.sqlx"), []byte(exampleSQLX), 0644) if err != nil { cleanup() t.Fatalf("Failed to write example.sqlx: %v", err) } return tmpDir, cleanup } func TestDataformCompileTool(t *testing.T) { if _, err := exec.LookPath("dataform"); err != nil { t.Skip("dataform CLI not found in $PATH, skipping integration test") } projectDir, cleanupProject := setupTestProject(t) defer cleanupProject() toolsFile := map[string]any{ "tools": map[string]any{ "my-dataform-compiler": map[string]any{ "kind": "dataform-compile-local", "description": "Tool to compile dataform projects", }, }, } ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) defer cancel() cmd, cleanupServer, err := tests.StartCmd(ctx, toolsFile) if err != nil { t.Fatalf("command initialization returned an error: %s", err) } defer cleanupServer() waitCtx, cancelWait := context.WithTimeout(ctx, 30*time.Second) defer cancelWait() out, err := testutils.WaitForString(waitCtx, regexp.MustCompile(`Server ready to serve`), cmd.Out) if err != nil { t.Logf("toolbox command logs: \n%s", out) t.Fatalf("toolbox didn't start successfully: %s", err) } nonExistentDir := filepath.Join(os.TempDir(), "non-existent-dir") testCases := []struct { name string reqBody string wantStatus int wantBody string // Substring to check for in the response }{ { name: "success case", reqBody: fmt.Sprintf(`{"project_dir":"%s"}`, projectDir), wantStatus: http.StatusOK, wantBody: "test_col", }, { name: "missing parameter", reqBody: `{}`, wantStatus: http.StatusBadRequest, wantBody: `parameter \"project_dir\" is required`, }, { name: "non-existent directory", reqBody: fmt.Sprintf(`{"project_dir":"%s"}`, nonExistentDir), wantStatus: http.StatusBadRequest, wantBody: "error executing dataform compile", }, } api := "http://127.0.0.1:5000/api/tool/my-dataform-compiler/invoke" for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { resp, bodyBytes := tests.RunRequest(t, http.MethodPost, api, strings.NewReader(tc.reqBody), nil) if resp.StatusCode != tc.wantStatus { t.Fatalf("unexpected status: got %d, want %d. Body: %s", resp.StatusCode, tc.wantStatus, string(bodyBytes)) } if tc.wantBody != "" && !strings.Contains(string(bodyBytes), tc.wantBody) { t.Fatalf("expected body to contain %q, got: %s", tc.wantBody, string(bodyBytes)) } }) } } ``` -------------------------------------------------------------------------------- /docs/en/getting-started/quickstart/go/openAI/quickstart.go: -------------------------------------------------------------------------------- ```go package main import ( "context" "encoding/json" "fmt" "log" "github.com/googleapis/mcp-toolbox-sdk-go/core" openai "github.com/openai/openai-go" ) // ConvertToOpenAITool converts a ToolboxTool into the go-openai library's Tool format. func ConvertToOpenAITool(toolboxTool *core.ToolboxTool) openai.ChatCompletionToolParam { // Get the input schema jsonSchemaBytes, err := toolboxTool.InputSchema() if err != nil { return openai.ChatCompletionToolParam{} } // Unmarshal the JSON bytes into FunctionParameters var paramsSchema openai.FunctionParameters if err := json.Unmarshal(jsonSchemaBytes, ¶msSchema); err != nil { return openai.ChatCompletionToolParam{} } // Create and return the final tool parameter struct. return openai.ChatCompletionToolParam{ Function: openai.FunctionDefinitionParam{ Name: toolboxTool.Name(), Description: openai.String(toolboxTool.Description()), Parameters: paramsSchema, }, } } const systemPrompt = ` You're a helpful hotel assistant. You handle hotel searching, booking, and cancellations. When the user searches for a hotel, mention its name, id, location and price tier. Always mention hotel ids while performing any searches. This is very important for any operations. For any bookings or cancellations, please provide the appropriate confirmation. Be sure to update checkin or checkout dates if mentioned by the user. Don't ask for confirmations from the user. ` var queries = []string{ "Find hotels in Basel with Basel in its name.", "Can you book the hotel Hilton Basel for me?", "Oh wait, this is too expensive. Please cancel it and book the Hyatt Regency instead.", "My check in dates would be from April 10, 2024 to April 19, 2024.", } func main() { // Setup ctx := context.Background() toolboxURL := "http://localhost:5000" openAIClient := openai.NewClient() // Initialize the MCP Toolbox client. toolboxClient, err := core.NewToolboxClient(toolboxURL) if err != nil { log.Fatalf("Failed to create Toolbox client: %v", err) } // Load the tools using the MCP Toolbox SDK. tools, err := toolboxClient.LoadToolset("my-toolset", ctx) if err != nil { log.Fatalf("Failed to load tool : %v\nMake sure your Toolbox server is running and the tool is configured.", err) } openAITools := make([]openai.ChatCompletionToolParam, len(tools)) toolsMap := make(map[string]*core.ToolboxTool, len(tools)) for i, tool := range tools { // Convert the Toolbox tool into the openAI FunctionDeclaration format. openAITools[i] = ConvertToOpenAITool(tool) // Add tool to a map for lookup later toolsMap[tool.Name()] = tool } params := openai.ChatCompletionNewParams{ Messages: []openai.ChatCompletionMessageParamUnion{ openai.SystemMessage(systemPrompt), }, Tools: openAITools, Seed: openai.Int(0), Model: openai.ChatModelGPT4o, } for _, query := range queries { params.Messages = append(params.Messages, openai.UserMessage(query)) // Make initial chat completion request completion, err := openAIClient.Chat.Completions.New(ctx, params) if err != nil { panic(err) } toolCalls := completion.Choices[0].Message.ToolCalls // Return early if there are no tool calls if len(toolCalls) == 0 { log.Println("No function call") } // If there was a function call, continue the conversation params.Messages = append(params.Messages, completion.Choices[0].Message.ToParam()) for _, toolCall := range toolCalls { toolName := toolCall.Function.Name toolToInvoke := toolsMap[toolName] var args map[string]any err := json.Unmarshal([]byte(toolCall.Function.Arguments), &args) if err != nil { panic(err) } result, err := toolToInvoke.Invoke(ctx, args) if err != nil { log.Fatal("Could not invoke tool", err) } params.Messages = append(params.Messages, openai.ToolMessage(result.(string), toolCall.ID)) } completion, err = openAIClient.Chat.Completions.New(ctx, params) if err != nil { panic(err) } params.Messages = append(params.Messages, openai.AssistantMessage(query)) fmt.Println("\n", completion.Choices[0].Message.Content) } } ``` -------------------------------------------------------------------------------- /docs/en/getting-started/quickstart/shared/configure_toolbox.md: -------------------------------------------------------------------------------- ```markdown <!-- This file has been used in local_quickstart.md, local_quickstart_go.md & local_quickstart_js.md --> <!-- [START configure_toolbox] --> In this section, we will download Toolbox, configure our tools in a `tools.yaml`, and then run the Toolbox server. 1. Download the latest version of Toolbox as a binary: {{< notice tip >}} Select the [correct binary](https://github.com/googleapis/genai-toolbox/releases) corresponding to your OS and CPU architecture. {{< /notice >}} <!-- {x-release-please-start-version} --> ```bash export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64 curl -O https://storage.googleapis.com/genai-toolbox/v0.17.0/$OS/toolbox ``` <!-- {x-release-please-end} --> 1. Make the binary executable: ```bash chmod +x toolbox ``` 1. Write the following into a `tools.yaml` file. Be sure to update any fields such as `user`, `password`, or `database` that you may have customized in the previous step. {{< notice tip >}} In practice, use environment variable replacement with the format ${ENV_NAME} instead of hardcoding your secrets into the configuration file. {{< /notice >}} ```yaml sources: my-pg-source: kind: postgres host: 127.0.0.1 port: 5432 database: toolbox_db user: ${USER_NAME} password: ${PASSWORD} tools: search-hotels-by-name: kind: postgres-sql source: my-pg-source description: Search for hotels based on name. parameters: - name: name type: string description: The name of the hotel. statement: SELECT * FROM hotels WHERE name ILIKE '%' || $1 || '%'; search-hotels-by-location: kind: postgres-sql source: my-pg-source description: Search for hotels based on location. parameters: - name: location type: string description: The location of the hotel. statement: SELECT * FROM hotels WHERE location ILIKE '%' || $1 || '%'; book-hotel: kind: postgres-sql source: my-pg-source description: >- Book a hotel by its ID. If the hotel is successfully booked, returns a NULL, raises an error if not. parameters: - name: hotel_id type: string description: The ID of the hotel to book. statement: UPDATE hotels SET booked = B'1' WHERE id = $1; update-hotel: kind: postgres-sql source: my-pg-source description: >- Update a hotel's check-in and check-out dates by its ID. Returns a message indicating whether the hotel was successfully updated or not. parameters: - name: hotel_id type: string description: The ID of the hotel to update. - name: checkin_date type: string description: The new check-in date of the hotel. - name: checkout_date type: string description: The new check-out date of the hotel. statement: >- UPDATE hotels SET checkin_date = CAST($2 as date), checkout_date = CAST($3 as date) WHERE id = $1; cancel-hotel: kind: postgres-sql source: my-pg-source description: Cancel a hotel by its ID. parameters: - name: hotel_id type: string description: The ID of the hotel to cancel. statement: UPDATE hotels SET booked = B'0' WHERE id = $1; toolsets: my-toolset: - search-hotels-by-name - search-hotels-by-location - book-hotel - update-hotel - cancel-hotel ``` For more info on tools, check out the `Resources` section of the docs. 1. Run the Toolbox server, pointing to the `tools.yaml` file created earlier: ```bash ./toolbox --tools-file "tools.yaml" ``` {{< notice note >}} Toolbox enables dynamic reloading by default. To disable, use the `--disable-reload` flag. {{< /notice >}} <!-- [END configure_toolbox] --> ``` -------------------------------------------------------------------------------- /internal/prebuiltconfigs/tools/firestore.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: firestore-source: kind: firestore project: ${FIRESTORE_PROJECT} database: ${FIRESTORE_DATABASE:} tools: get_documents: kind: firestore-get-documents source: firestore-source description: Gets multiple documents from Firestore by their paths add_documents: kind: firestore-add-documents source: firestore-source description: | Adds a new document to a Firestore collection. Please follow the best practices : 1. Always use typed values in the documentData: Every field must be wrapped with its appropriate type indicator (e.g., {"stringValue": "text"}) 2. Integer values can be strings in the documentData: The tool accepts integer values as strings (e.g., {"integerValue": "1500"}) 3. Use returnData sparingly: Only set to true when you need to verify the exact data that was written 4. Validate data before sending: Ensure your data matches Firestore's native JSON format 5. Handle timestamps properly: Use RFC3339 format for timestamp strings 6. Base64 encode binary data: Binary data must be base64 encoded in the bytesValue field 7. Consider security rules: Ensure your Firestore security rules allow document creation in the target collection update_document: kind: firestore-update-document source: firestore-source description: | Updates an existing document in Firestore. Supports both full document updates and selective field updates using an update mask. Please follow the best practices: 1. Use update masks for precision: When you only need to update specific fields, use the updateMask parameter to avoid unintended changes 2. Always use typed values in the documentData: Every field must be wrapped with its appropriate type indicator (e.g., {"stringValue": "text"}) 3. Delete fields using update mask: To delete fields, include them in the updateMask but omit them from documentData 4. Integer values can be strings: The tool accepts integer values as strings (e.g., {"integerValue": "1500"}) 5. Use returnData sparingly: Only set to true when you need to verify the exact data after the update 6. Handle timestamps properly: Use RFC3339 format for timestamp strings 7. Consider security rules: Ensure your Firestore security rules allow document updates list_collections: kind: firestore-list-collections source: firestore-source description: List Firestore collections for a given parent path delete_documents: kind: firestore-delete-documents source: firestore-source description: Delete multiple documents from Firestore query_collection: kind: firestore-query-collection source: firestore-source description: | Retrieves one or more Firestore documents from a collection in a database in the current project by a collection with a full document path. Use this if you know the exact path of a collection and the filtering clause you would like for the document. get_rules: kind: firestore-get-rules source: firestore-source description: Retrieves the active Firestore security rules for the current project validate_rules: kind: firestore-validate-rules source: firestore-source description: Checks the provided Firestore Rules source for syntax and validation errors. Provide the source code to validate. toolsets: firestore_database_tools: - get_documents - add_documents - update_document - list_collections - delete_documents - query_collection - get_rules - validate_rules ``` -------------------------------------------------------------------------------- /docs/en/getting-started/quickstart/js/genAI/quickstart.js: -------------------------------------------------------------------------------- ```javascript import { GoogleGenAI } from "@google/genai"; import { ToolboxClient } from "@toolbox-sdk/core"; const TOOLBOX_URL = "http://127.0.0.1:5000"; // Update if needed const GOOGLE_API_KEY = process.env.GOOGLE_API_KEY || 'your-api-key'; // Replace it with your API key const prompt = ` You're a helpful hotel assistant. You handle hotel searching, booking, and cancellations. When the user searches for a hotel, you MUST use the available tools to find information. Mention its name, id, location and price tier. Always mention hotel id while performing any searches. This is very important for any operations. For any bookings or cancellations, please provide the appropriate confirmation. Be sure to update checkin or checkout dates if mentioned by the user. Don't ask for confirmations from the user. `; const queries = [ "Find hotels in Basel with Basel in its name.", "Can you book the Hilton Basel for me?", "Oh wait, this is too expensive. Please cancel it and book the Hyatt Regency instead.", "My check in dates would be from April 10, 2024 to April 19, 2024.", ]; function mapZodTypeToOpenAPIType(zodTypeName) { console.log(zodTypeName) const typeMap = { 'ZodString': 'string', 'ZodNumber': 'number', 'ZodBoolean': 'boolean', 'ZodArray': 'array', 'ZodObject': 'object', }; return typeMap[zodTypeName] || 'string'; } export async function main() { const toolboxClient = new ToolboxClient(TOOLBOX_URL); const toolboxTools = await toolboxClient.loadToolset("my-toolset"); const geminiTools = [{ functionDeclarations: toolboxTools.map(tool => { const schema = tool.getParamSchema(); const properties = {}; const required = []; for (const [key, param] of Object.entries(schema.shape)) { properties[key] = { type: mapZodTypeToOpenAPIType(param.constructor.name), description: param.description || '', }; required.push(key) } return { name: tool.getName(), description: tool.getDescription(), parameters: { type: 'object', properties, required }, }; }) }]; const genAI = new GoogleGenAI({ apiKey: GOOGLE_API_KEY }); const chat = genAI.chats.create({ model: "gemini-2.5-flash", config: { systemInstruction: prompt, tools: geminiTools, } }); for (const query of queries) { let currentResult = await chat.sendMessage({ message: query }); let finalResponseGiven = false while (!finalResponseGiven) { const response = currentResult; const functionCalls = response.functionCalls || []; if (functionCalls.length === 0) { console.log(response.text) finalResponseGiven = true; } else { const toolResponses = []; for (const call of functionCalls) { const toolName = call.name const toolToExecute = toolboxTools.find(t => t.getName() === toolName); if (toolToExecute) { try { const functionResult = await toolToExecute(call.args); toolResponses.push({ functionResponse: { name: call.name, response: { result: functionResult } } }); } catch (e) { console.error(`Error executing tool '${toolName}':`, e); toolResponses.push({ functionResponse: { name: call.name, response: { error: e.message } } }); } } } currentResult = await chat.sendMessage({ message: toolResponses }); } } } } main(); ``` -------------------------------------------------------------------------------- /internal/sources/alloydbadmin/alloydbadmin.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 alloydbadmin import ( "context" "fmt" "net/http" "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/sources" "github.com/googleapis/genai-toolbox/internal/util" "go.opentelemetry.io/otel/trace" "golang.org/x/oauth2" "golang.org/x/oauth2/google" alloydbrestapi "google.golang.org/api/alloydb/v1" "google.golang.org/api/option" ) const SourceKind string = "alloydb-admin" type userAgentRoundTripper struct { userAgent string next http.RoundTripper } func (rt *userAgentRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { newReq := *req newReq.Header = make(http.Header) for k, v := range req.Header { newReq.Header[k] = v } ua := newReq.Header.Get("User-Agent") if ua == "" { newReq.Header.Set("User-Agent", rt.userAgent) } else { newReq.Header.Set("User-Agent", ua+" "+rt.userAgent) } return rt.next.RoundTrip(&newReq) } // 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 } return actual, nil } type Config struct { Name string `yaml:"name" validate:"required"` Kind string `yaml:"kind" validate:"required"` UseClientOAuth bool `yaml:"useClientOAuth"` } func (r Config) SourceConfigKind() string { return SourceKind } func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) { ua, err := util.UserAgentFromContext(ctx) if err != nil { fmt.Printf("Error in User Agent retrieval: %s", err) } var client *http.Client if r.UseClientOAuth { client = &http.Client{ Transport: &userAgentRoundTripper{ userAgent: ua, next: http.DefaultTransport, }, } } else { // Use Application Default Credentials creds, err := google.FindDefaultCredentials(ctx, alloydbrestapi.CloudPlatformScope) if err != nil { return nil, fmt.Errorf("failed to find default credentials: %w", err) } baseClient := oauth2.NewClient(ctx, creds.TokenSource) baseClient.Transport = &userAgentRoundTripper{ userAgent: ua, next: baseClient.Transport, } client = baseClient } service, err := alloydbrestapi.NewService(ctx, option.WithHTTPClient(client)) if err != nil { return nil, fmt.Errorf("error creating new alloydb service: %w", err) } s := &Source{ Name: r.Name, Kind: SourceKind, BaseURL: "https://alloydb.googleapis.com", Service: service, UseClientOAuth: r.UseClientOAuth, } return s, nil } var _ sources.Source = &Source{} type Source struct { Name string `yaml:"name"` Kind string `yaml:"kind"` BaseURL string Service *alloydbrestapi.Service UseClientOAuth bool } func (s *Source) SourceKind() string { return SourceKind } func (s *Source) GetService(ctx context.Context, accessToken string) (*alloydbrestapi.Service, error) { if s.UseClientOAuth { token := &oauth2.Token{AccessToken: accessToken} client := oauth2.NewClient(ctx, oauth2.StaticTokenSource(token)) service, err := alloydbrestapi.NewService(ctx, option.WithHTTPClient(client)) if err != nil { return nil, fmt.Errorf("error creating new alloydb service: %w", err) } return service, nil } return s.Service, nil } func (s *Source) UseClientAuthorization() bool { return s.UseClientOAuth } ``` -------------------------------------------------------------------------------- /internal/sources/firestore/firestore.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 firestore import ( "context" "fmt" "cloud.google.com/go/firestore" "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/sources" "github.com/googleapis/genai-toolbox/internal/util" "go.opentelemetry.io/otel/trace" "google.golang.org/api/firebaserules/v1" "google.golang.org/api/option" ) const SourceKind string = "firestore" // 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 } return actual, nil } type Config struct { // Firestore configs Name string `yaml:"name" validate:"required"` Kind string `yaml:"kind" validate:"required"` Project string `yaml:"project" validate:"required"` Database string `yaml:"database"` // Optional, defaults to "(default)" } func (r Config) SourceConfigKind() string { // Returns Firestore source kind return SourceKind } func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) { // Initializes a Firestore source client, err := initFirestoreConnection(ctx, tracer, r.Name, r.Project, r.Database) if err != nil { return nil, err } // Initialize Firebase Rules client rulesClient, err := initFirebaseRulesConnection(ctx, r.Project) if err != nil { return nil, fmt.Errorf("failed to initialize Firebase Rules client: %w", err) } s := &Source{ Name: r.Name, Kind: SourceKind, Client: client, RulesClient: rulesClient, ProjectId: r.Project, DatabaseId: r.Database, } return s, nil } var _ sources.Source = &Source{} type Source struct { // Firestore struct with client Name string `yaml:"name"` Kind string `yaml:"kind"` Client *firestore.Client RulesClient *firebaserules.Service ProjectId string `yaml:"projectId"` DatabaseId string `yaml:"databaseId"` } func (s *Source) SourceKind() string { // Returns Firestore source kind return SourceKind } func (s *Source) FirestoreClient() *firestore.Client { return s.Client } func (s *Source) FirebaseRulesClient() *firebaserules.Service { return s.RulesClient } func (s *Source) GetProjectId() string { return s.ProjectId } func (s *Source) GetDatabaseId() string { return s.DatabaseId } func initFirestoreConnection( ctx context.Context, tracer trace.Tracer, name string, project string, database string, ) (*firestore.Client, error) { ctx, span := sources.InitConnectionSpan(ctx, tracer, SourceKind, name) defer span.End() userAgent, err := util.UserAgentFromContext(ctx) if err != nil { return nil, err } // If database is not specified, use the default database if database == "" { database = "(default)" } // Create the Firestore client client, err := firestore.NewClientWithDatabase(ctx, project, database, option.WithUserAgent(userAgent)) if err != nil { return nil, fmt.Errorf("failed to create Firestore client for project %q and database %q: %w", project, database, err) } return client, nil } func initFirebaseRulesConnection( ctx context.Context, project string, ) (*firebaserules.Service, error) { // Create the Firebase Rules client rulesClient, err := firebaserules.NewService(ctx) if err != nil { return nil, fmt.Errorf("failed to create Firebase Rules client for project %q: %w", project, err) } return rulesClient, nil } ``` -------------------------------------------------------------------------------- /internal/tools/couchbase/couchbase_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 couchbase_test import ( "testing" "github.com/googleapis/genai-toolbox/internal/tools/couchbase" yaml "github.com/goccy/go-yaml" "github.com/google/go-cmp/cmp" "github.com/googleapis/genai-toolbox/internal/server" "github.com/googleapis/genai-toolbox/internal/testutils" "github.com/googleapis/genai-toolbox/internal/tools" ) func TestParseFromYamlCouchbase(t *testing.T) { tcs := []struct { desc string in string want server.ToolConfigs }{ { desc: "basic example", in: ` tools: example_tool: kind: couchbase-sql source: my-couchbase-instance description: some tool description statement: | select * from hotel WHERE name = $hotel; parameters: - name: hotel type: string description: hotel parameter description `, want: server.ToolConfigs{ "example_tool": couchbase.Config{ Name: "example_tool", Kind: "couchbase-sql", AuthRequired: []string{}, Source: "my-couchbase-instance", Description: "some tool description", Statement: "select * from hotel WHERE name = $hotel;\n", Parameters: []tools.Parameter{ tools.NewStringParameter("hotel", "hotel parameter description"), }, }, }, }, } for _, tc := range tcs { t.Run(tc.desc, func(t *testing.T) { got := struct { Tools server.ToolConfigs `yaml:"tools"` }{} // Create a context with a logger ctx, err := testutils.ContextWithNewLogger() if err != nil { t.Fatalf("unable to create context with logger: %s", err) } // Parse contents with context err = yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got) if err != nil { t.Fatalf("unable to unmarshal: %s", err) } if diff := cmp.Diff(tc.want, got.Tools); diff != "" { t.Fatalf("incorrect parse: diff %v", diff) } }) } } func TestParseFromYamlWithTemplateMssql(t *testing.T) { ctx, err := testutils.ContextWithNewLogger() if err != nil { t.Fatalf("unexpected error: %s", err) } tcs := []struct { desc string in string want server.ToolConfigs }{ { desc: "basic example", in: ` tools: example_tool: kind: couchbase-sql source: my-couchbase-instance description: some tool description statement: | select * from {{.tableName}} WHERE name = $hotel; parameters: - name: hotel type: string description: hotel parameter description templateParameters: - name: tableName type: string description: The table to select hotels from. `, want: server.ToolConfigs{ "example_tool": couchbase.Config{ Name: "example_tool", Kind: "couchbase-sql", AuthRequired: []string{}, Source: "my-couchbase-instance", Description: "some tool description", Statement: "select * from {{.tableName}} WHERE name = $hotel;\n", Parameters: []tools.Parameter{ tools.NewStringParameter("hotel", "hotel parameter description"), }, TemplateParameters: []tools.Parameter{ tools.NewStringParameter("tableName", "The table to select hotels from."), }, }, }, }, } for _, tc := range tcs { t.Run(tc.desc, func(t *testing.T) { got := struct { Tools server.ToolConfigs `yaml:"tools"` }{} // Parse contents err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got) if err != nil { t.Fatalf("unable to unmarshal: %s", err) } if diff := cmp.Diff(tc.want, got.Tools); diff != "" { t.Fatalf("incorrect parse: diff %v", diff) } }) } } ``` -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- ```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 # # https://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. name: 🐞 Bug Report description: File a report for unexpected or undesired behavior. title: "<brief summary of what bug or error was observed>" labels: ["type: bug"] type: "bug" body: - type: markdown attributes: value: | Thanks for helping us improve! 🙏 Please answer these questions and provide as much information as possible about your problem. - id: preamble type: checkboxes attributes: label: Prerequisites description: | Please run through the following list and make sure you've tried the usual "quick fixes": - Search the [current open issues](https://github.com/googleapis/genai-toolbox/issues) - Update to the [latest version of Toolbox](https://github.com/googleapis/genai-toolbox/releases) options: - label: "I've searched the current open issues" required: true - label: "I've updated to the latest version of Toolbox" - type: input id: version attributes: label: Toolbox version description: | What version of Toolbox are you using (`toolbox --version`)? e.g. - toolbox version 0.3.0 - us-central1-docker.pkg.dev/database-toolbox/toolbox/toolbox:0.3.0 placeholder: ex. toolbox version 0.3.0 validations: required: true - type: textarea id: environment attributes: label: Environment description: "Let us know some details about the environment in which you are seeing the bug!" value: | 1. OS type and version: (output of `uname -a`) 2. How are you running Toolbox: - As a downloaded binary (e.g. from `curl -O https://storage.googleapis.com/genai-toolbox/v$VERSION/linux/amd64/toolbox`) - As a container (e.g. from `us-central1-docker.pkg.dev/database-toolbox/toolbox/toolbox:$VERSION`) - Compiled from source (include the command used to build) - type: textarea id: client attributes: label: Client description: "How are you connecting to Toolbox?" value: | 1. Client: <name and link to the client are you using> 2. Version: <what exact version of the client are you using> 3. Example: If possible, please include your code of configuration: ```python # Code goes here! ``` - id: expected-behavior type: textarea attributes: label: Expected Behavior description: | Please enter a detailed description of the behavior you expected, and any information about what behavior you noticed and why it is defective or unintentional. validations: required: true - id: current-behavior type: textarea attributes: label: Current Behavior description: "Please enter a detailed description of the behavior you encountered instead." validations: required: true - type: textarea id: repro attributes: label: Steps to reproduce? description: | How can we reproduce this bug? Please walk us through it step by step, with as much relevant detail as possible. A 'minimal' reproduction is preferred, which means removing as much of the examples as possible so only the minimum required to run and reproduce the bug is left. value: | 1. ? 2. ? 3. ? ... validations: required: true - type: textarea id: additional-details attributes: label: Additional Details description: | Any other information you want us to know? Things such as tools config, server logs, etc. can be included here. ``` -------------------------------------------------------------------------------- /internal/server/mcp/mcp.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 mcp import ( "context" "encoding/json" "fmt" "net/http" "slices" "github.com/googleapis/genai-toolbox/internal/auth" "github.com/googleapis/genai-toolbox/internal/server/mcp/jsonrpc" mcputil "github.com/googleapis/genai-toolbox/internal/server/mcp/util" v20241105 "github.com/googleapis/genai-toolbox/internal/server/mcp/v20241105" v20250326 "github.com/googleapis/genai-toolbox/internal/server/mcp/v20250326" v20250618 "github.com/googleapis/genai-toolbox/internal/server/mcp/v20250618" "github.com/googleapis/genai-toolbox/internal/tools" ) // LATEST_PROTOCOL_VERSION is the latest version of the MCP protocol supported. // Update the version used in InitializeResponse when this value is updated. const LATEST_PROTOCOL_VERSION = v20250618.PROTOCOL_VERSION // SUPPORTED_PROTOCOL_VERSIONS is the MCP protocol versions that are supported. var SUPPORTED_PROTOCOL_VERSIONS = []string{ v20241105.PROTOCOL_VERSION, v20250326.PROTOCOL_VERSION, v20250618.PROTOCOL_VERSION, } // InitializeResponse runs capability negotiation and protocol version agreement. // This is the Initialization phase of the lifecycle for MCP client-server connections. // Always start with the latest protocol version supported. func InitializeResponse(ctx context.Context, id jsonrpc.RequestId, body []byte, toolboxVersion string) (any, string, error) { var req mcputil.InitializeRequest if err := json.Unmarshal(body, &req); err != nil { err = fmt.Errorf("invalid mcp initialize request: %w", err) return jsonrpc.NewError(id, jsonrpc.INVALID_REQUEST, err.Error(), nil), "", err } var protocolVersion string v := req.Params.ProtocolVersion if slices.Contains(SUPPORTED_PROTOCOL_VERSIONS, v) { protocolVersion = v } else { protocolVersion = LATEST_PROTOCOL_VERSION } toolsListChanged := false result := mcputil.InitializeResult{ ProtocolVersion: protocolVersion, Capabilities: mcputil.ServerCapabilities{ Tools: &mcputil.ListChanged{ ListChanged: &toolsListChanged, }, }, ServerInfo: mcputil.Implementation{ BaseMetadata: mcputil.BaseMetadata{ Name: mcputil.SERVER_NAME, }, Version: toolboxVersion, }, } res := jsonrpc.JSONRPCResponse{ Jsonrpc: jsonrpc.JSONRPC_VERSION, Id: id, Result: result, } return res, protocolVersion, nil } // NotificationHandler process notifications request. It MUST NOT send a response. // Currently Toolbox does not process any notifications. func NotificationHandler(ctx context.Context, body []byte) error { var notification jsonrpc.JSONRPCNotification if err := json.Unmarshal(body, ¬ification); err != nil { return fmt.Errorf("invalid notification request: %w", err) } return nil } // ProcessMethod returns a response for the request. // This is the Operation phase of the lifecycle for MCP client-server connections. func ProcessMethod(ctx context.Context, mcpVersion string, id jsonrpc.RequestId, method string, toolset tools.Toolset, tools map[string]tools.Tool, authServices map[string]auth.AuthService, body []byte, header http.Header) (any, error) { switch mcpVersion { case v20250618.PROTOCOL_VERSION: return v20250618.ProcessMethod(ctx, id, method, toolset, tools, authServices, body, header) case v20250326.PROTOCOL_VERSION: return v20250326.ProcessMethod(ctx, id, method, toolset, tools, authServices, body, header) default: return v20241105.ProcessMethod(ctx, id, method, toolset, tools, authServices, body, header) } } // VerifyProtocolVersion verifies if the version string is valid. func VerifyProtocolVersion(version string) bool { return slices.Contains(SUPPORTED_PROTOCOL_VERSIONS, version) } ``` -------------------------------------------------------------------------------- /.ci/continuous.release.cloudbuild.yaml: -------------------------------------------------------------------------------- ```yaml # 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. steps: - id: "build-docker" name: "gcr.io/cloud-builders/docker" waitFor: ['-'] script: | #!/usr/bin/env bash docker buildx create --name container-builder --driver docker-container --bootstrap --use docker buildx build --platform linux/amd64,linux/arm64 --build-arg COMMIT_SHA=$(git rev-parse --short HEAD) -t ${_DOCKER_URI}:$REF_NAME --push . - id: "install-dependencies" name: golang:1 waitFor: ['-'] env: - 'GOPATH=/gopath' volumes: - name: 'go' path: '/gopath' script: | go get -d ./... - id: "build-linux-amd64" name: golang:1 waitFor: - "install-dependencies" env: - 'GOPATH=/gopath' volumes: - name: 'go' path: '/gopath' script: | #!/usr/bin/env bash CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ go build -ldflags "-X github.com/googleapis/genai-toolbox/cmd.commitSha=$(git rev-parse --short HEAD)" -o toolbox.linux.amd64 - id: "store-linux-amd64" name: "gcr.io/cloud-builders/gcloud:latest" waitFor: - "build-linux-amd64" script: | #!/usr/bin/env bash gcloud storage cp toolbox.linux.amd64 gs://$_BUCKET_NAME/$REF_NAME/linux/amd64/toolbox - id: "build-darwin-arm64" name: golang:1 waitFor: - "install-dependencies" env: - 'GOPATH=/gopath' volumes: - name: 'go' path: '/gopath' script: | #!/usr/bin/env bash CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 \ go build -ldflags "-X github.com/googleapis/genai-toolbox/cmd.commitSha=$(git rev-parse --short HEAD)" -o toolbox.darwin.arm64 - id: "store-darwin-arm64" name: "gcr.io/cloud-builders/gcloud:latest" waitFor: - "build-darwin-arm64" script: | #!/usr/bin/env bash gcloud storage cp toolbox.darwin.arm64 gs://$_BUCKET_NAME/$REF_NAME/darwin/arm64/toolbox - id: "build-darwin-amd64" name: golang:1 waitFor: - "install-dependencies" env: - 'GOPATH=/gopath' volumes: - name: 'go' path: '/gopath' script: | #!/usr/bin/env bash CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 \ go build -ldflags "-X github.com/googleapis/genai-toolbox/cmd.commitSha=$(git rev-parse --short HEAD)" -o toolbox.darwin.amd64 - id: "store-darwin-amd64" name: "gcr.io/cloud-builders/gcloud:latest" waitFor: - "build-darwin-amd64" script: | #!/usr/bin/env bash gcloud storage cp toolbox.darwin.amd64 gs://$_BUCKET_NAME/$REF_NAME/darwin/amd64/toolbox - id: "build-windows-amd64" name: golang:1 waitFor: - "install-dependencies" env: - 'GOPATH=/gopath' volumes: - name: 'go' path: '/gopath' script: | #!/usr/bin/env bash CGO_ENABLED=0 GOOS=windows GOARCH=amd64 \ go build -ldflags "-X github.com/googleapis/genai-toolbox/cmd.commitSha=$(git rev-parse --short HEAD)" -o toolbox.windows.amd64 - id: "store-windows-amd64" name: "gcr.io/cloud-builders/gcloud:latest" waitFor: - "build-windows-amd64" script: | #!/usr/bin/env bash gcloud storage cp toolbox.windows.amd64 gs://$_BUCKET_NAME/$REF_NAME/windows/amd64/toolbox.exe options: automapSubstitutions: true dynamicSubstitutions: true logging: CLOUD_LOGGING_ONLY # Necessary for custom service account machineType: 'E2_HIGHCPU_32' substitutions: _REGION: us-central1 _AR_HOSTNAME: ${_REGION}-docker.pkg.dev _AR_REPO_NAME: toolbox-dev _BUCKET_NAME: genai-toolbox-dev _DOCKER_URI: ${_AR_HOSTNAME}/${PROJECT_ID}/${_AR_REPO_NAME}/toolbox ``` -------------------------------------------------------------------------------- /internal/sources/cloudsqladmin/cloud_sql_admin.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 cloudsqladmin import ( "context" "fmt" "net/http" "github.com/goccy/go-yaml" "github.com/googleapis/genai-toolbox/internal/sources" "github.com/googleapis/genai-toolbox/internal/util" "go.opentelemetry.io/otel/trace" "golang.org/x/oauth2" "golang.org/x/oauth2/google" "google.golang.org/api/option" sqladmin "google.golang.org/api/sqladmin/v1" ) const SourceKind string = "cloud-sql-admin" type userAgentRoundTripper struct { userAgent string next http.RoundTripper } func (rt *userAgentRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { newReq := *req newReq.Header = make(http.Header) for k, v := range req.Header { newReq.Header[k] = v } ua := newReq.Header.Get("User-Agent") if ua == "" { newReq.Header.Set("User-Agent", rt.userAgent) } else { newReq.Header.Set("User-Agent", ua+" "+rt.userAgent) } return rt.next.RoundTrip(&newReq) } // 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 } return actual, nil } type Config struct { Name string `yaml:"name" validate:"required"` Kind string `yaml:"kind" validate:"required"` UseClientOAuth bool `yaml:"useClientOAuth"` } func (r Config) SourceConfigKind() string { return SourceKind } // Initialize initializes a CloudSQL Admin Source instance. func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) { ua, err := util.UserAgentFromContext(ctx) if err != nil { return nil, fmt.Errorf("error in User Agent retrieval: %s", err) } var client *http.Client if r.UseClientOAuth { client = &http.Client{ Transport: &userAgentRoundTripper{ userAgent: ua, next: http.DefaultTransport, }, } } else { // Use Application Default Credentials creds, err := google.FindDefaultCredentials(ctx, sqladmin.SqlserviceAdminScope) if err != nil { return nil, fmt.Errorf("failed to find default credentials: %w", err) } baseClient := oauth2.NewClient(ctx, creds.TokenSource) baseClient.Transport = &userAgentRoundTripper{ userAgent: ua, next: baseClient.Transport, } client = baseClient } service, err := sqladmin.NewService(ctx, option.WithHTTPClient(client)) if err != nil { return nil, fmt.Errorf("error creating new sqladmin service: %w", err) } s := &Source{ Name: r.Name, Kind: SourceKind, BaseURL: "https://sqladmin.googleapis.com", Service: service, UseClientOAuth: r.UseClientOAuth, } return s, nil } var _ sources.Source = &Source{} type Source struct { Name string `yaml:"name"` Kind string `yaml:"kind"` BaseURL string Service *sqladmin.Service UseClientOAuth bool } func (s *Source) SourceKind() string { return SourceKind } func (s *Source) GetService(ctx context.Context, accessToken string) (*sqladmin.Service, error) { if s.UseClientOAuth { token := &oauth2.Token{AccessToken: accessToken} client := oauth2.NewClient(ctx, oauth2.StaticTokenSource(token)) service, err := sqladmin.NewService(ctx, option.WithHTTPClient(client)) if err != nil { return nil, fmt.Errorf("error creating new sqladmin service: %w", err) } return service, nil } return s.Service, nil } func (s *Source) UseClientAuthorization() bool { return s.UseClientOAuth } ```