This is page 7 of 45. Use http://codebase.md/googleapis/genai-toolbox?lines=true&page={x} to view the full context. # Directory Structure ``` ├── .ci │ ├── continuous.release.cloudbuild.yaml │ ├── generate_release_table.sh │ ├── integration.cloudbuild.yaml │ ├── quickstart_test │ │ ├── go.integration.cloudbuild.yaml │ │ ├── js.integration.cloudbuild.yaml │ │ ├── py.integration.cloudbuild.yaml │ │ ├── run_go_tests.sh │ │ ├── run_js_tests.sh │ │ ├── run_py_tests.sh │ │ └── setup_hotels_sample.sql │ ├── test_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/tools/looker/lookergetparameters/lookergetparameters_test.go: -------------------------------------------------------------------------------- ```go 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package lookergetparameters_test 16 | 17 | import ( 18 | "strings" 19 | "testing" 20 | 21 | yaml "github.com/goccy/go-yaml" 22 | "github.com/google/go-cmp/cmp" 23 | "github.com/googleapis/genai-toolbox/internal/server" 24 | "github.com/googleapis/genai-toolbox/internal/testutils" 25 | lkr "github.com/googleapis/genai-toolbox/internal/tools/looker/lookergetparameters" 26 | ) 27 | 28 | func TestParseFromYamlLookerGetParameters(t *testing.T) { 29 | ctx, err := testutils.ContextWithNewLogger() 30 | if err != nil { 31 | t.Fatalf("unexpected error: %s", err) 32 | } 33 | tcs := []struct { 34 | desc string 35 | in string 36 | want server.ToolConfigs 37 | }{ 38 | { 39 | desc: "basic example", 40 | in: ` 41 | tools: 42 | example_tool: 43 | kind: looker-get-parameters 44 | source: my-instance 45 | description: some description 46 | `, 47 | want: server.ToolConfigs{ 48 | "example_tool": lkr.Config{ 49 | Name: "example_tool", 50 | Kind: "looker-get-parameters", 51 | Source: "my-instance", 52 | Description: "some description", 53 | AuthRequired: []string{}, 54 | }, 55 | }, 56 | }, 57 | } 58 | for _, tc := range tcs { 59 | t.Run(tc.desc, func(t *testing.T) { 60 | got := struct { 61 | Tools server.ToolConfigs `yaml:"tools"` 62 | }{} 63 | // Parse contents 64 | err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got) 65 | if err != nil { 66 | t.Fatalf("unable to unmarshal: %s", err) 67 | } 68 | if diff := cmp.Diff(tc.want, got.Tools); diff != "" { 69 | t.Fatalf("incorrect parse: diff %v", diff) 70 | } 71 | }) 72 | } 73 | 74 | } 75 | 76 | func TestFailParseFromYamlLookerGetParameters(t *testing.T) { 77 | ctx, err := testutils.ContextWithNewLogger() 78 | if err != nil { 79 | t.Fatalf("unexpected error: %s", err) 80 | } 81 | tcs := []struct { 82 | desc string 83 | in string 84 | err string 85 | }{ 86 | { 87 | desc: "Invalid method", 88 | in: ` 89 | tools: 90 | example_tool: 91 | kind: looker-get-parameters 92 | source: my-instance 93 | method: GOT 94 | description: some description 95 | `, 96 | err: "unable to parse tool \"example_tool\" as kind \"looker-get-parameters\": [4:1] unknown field \"method\"\n 1 | authRequired: []\n 2 | description: some description\n 3 | kind: looker-get-parameters\n> 4 | method: GOT\n ^\n 5 | source: my-instance", 97 | }, 98 | } 99 | for _, tc := range tcs { 100 | t.Run(tc.desc, func(t *testing.T) { 101 | got := struct { 102 | Tools server.ToolConfigs `yaml:"tools"` 103 | }{} 104 | // Parse contents 105 | err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got) 106 | if err == nil { 107 | t.Fatalf("expect parsing to fail") 108 | } 109 | errStr := err.Error() 110 | if !strings.Contains(errStr, tc.err) { 111 | t.Fatalf("unexpected error string: got %q, want substring %q", errStr, tc.err) 112 | } 113 | }) 114 | } 115 | 116 | } 117 | ``` -------------------------------------------------------------------------------- /internal/sources/neo4j/neo4j_test.go: -------------------------------------------------------------------------------- ```go 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package neo4j_test 16 | 17 | import ( 18 | "testing" 19 | 20 | yaml "github.com/goccy/go-yaml" 21 | "github.com/google/go-cmp/cmp" 22 | "github.com/googleapis/genai-toolbox/internal/server" 23 | "github.com/googleapis/genai-toolbox/internal/sources/neo4j" 24 | "github.com/googleapis/genai-toolbox/internal/testutils" 25 | ) 26 | 27 | func TestParseFromYamlNeo4j(t *testing.T) { 28 | tcs := []struct { 29 | desc string 30 | in string 31 | want server.SourceConfigs 32 | }{ 33 | { 34 | desc: "basic example", 35 | in: ` 36 | sources: 37 | my-neo4j-instance: 38 | kind: neo4j 39 | uri: neo4j+s://my-host:7687 40 | database: my_db 41 | user: my_user 42 | password: my_pass 43 | `, 44 | want: server.SourceConfigs{ 45 | "my-neo4j-instance": neo4j.Config{ 46 | Name: "my-neo4j-instance", 47 | Kind: neo4j.SourceKind, 48 | Uri: "neo4j+s://my-host:7687", 49 | Database: "my_db", 50 | User: "my_user", 51 | Password: "my_pass", 52 | }, 53 | }, 54 | }, 55 | } 56 | for _, tc := range tcs { 57 | t.Run(tc.desc, func(t *testing.T) { 58 | got := struct { 59 | Sources server.SourceConfigs `yaml:"sources"` 60 | }{} 61 | // Parse contents 62 | err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got) 63 | if err != nil { 64 | t.Fatalf("unable to unmarshal: %s", err) 65 | } 66 | if !cmp.Equal(tc.want, got.Sources) { 67 | t.Fatalf("incorrect parse: want %v, got %v", tc.want, got.Sources) 68 | } 69 | }) 70 | } 71 | 72 | } 73 | 74 | func TestFailParseFromYaml(t *testing.T) { 75 | tcs := []struct { 76 | desc string 77 | in string 78 | err string 79 | }{ 80 | { 81 | desc: "extra field", 82 | in: ` 83 | sources: 84 | my-neo4j-instance: 85 | kind: neo4j 86 | uri: neo4j+s://my-host:7687 87 | database: my_db 88 | user: my_user 89 | password: my_pass 90 | foo: bar 91 | `, 92 | err: "unable to parse source \"my-neo4j-instance\" as \"neo4j\": [2:1] unknown field \"foo\"\n 1 | database: my_db\n> 2 | foo: bar\n ^\n 3 | kind: neo4j\n 4 | password: my_pass\n 5 | uri: neo4j+s://my-host:7687\n 6 | ", 93 | }, 94 | { 95 | desc: "missing required field", 96 | in: ` 97 | sources: 98 | my-neo4j-instance: 99 | kind: neo4j 100 | uri: neo4j+s://my-host:7687 101 | database: my_db 102 | user: my_user 103 | `, 104 | err: "unable to parse source \"my-neo4j-instance\" as \"neo4j\": Key: 'Config.Password' Error:Field validation for 'Password' failed on the 'required' tag", 105 | }, 106 | } 107 | for _, tc := range tcs { 108 | t.Run(tc.desc, func(t *testing.T) { 109 | got := struct { 110 | Sources server.SourceConfigs `yaml:"sources"` 111 | }{} 112 | // Parse contents 113 | err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got) 114 | if err == nil { 115 | t.Fatalf("expect parsing to fail") 116 | } 117 | errStr := err.Error() 118 | if errStr != tc.err { 119 | t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err) 120 | } 121 | }) 122 | } 123 | } 124 | ``` -------------------------------------------------------------------------------- /internal/tools/clickhouse/clickhouselisttables/clickhouselisttables_test.go: -------------------------------------------------------------------------------- ```go 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package clickhouse 16 | 17 | import ( 18 | "testing" 19 | 20 | yaml "github.com/goccy/go-yaml" 21 | "github.com/google/go-cmp/cmp" 22 | "github.com/googleapis/genai-toolbox/internal/server" 23 | "github.com/googleapis/genai-toolbox/internal/sources" 24 | "github.com/googleapis/genai-toolbox/internal/testutils" 25 | "github.com/googleapis/genai-toolbox/internal/tools" 26 | ) 27 | 28 | func TestListTablesConfigToolConfigKind(t *testing.T) { 29 | cfg := Config{} 30 | if cfg.ToolConfigKind() != listTablesKind { 31 | t.Errorf("expected %q, got %q", listTablesKind, cfg.ToolConfigKind()) 32 | } 33 | } 34 | 35 | func TestListTablesConfigInitializeMissingSource(t *testing.T) { 36 | cfg := Config{ 37 | Name: "test-list-tables", 38 | Kind: listTablesKind, 39 | Source: "missing-source", 40 | Description: "Test list tables tool", 41 | } 42 | 43 | srcs := map[string]sources.Source{} 44 | _, err := cfg.Initialize(srcs) 45 | if err == nil { 46 | t.Error("expected error for missing source") 47 | } 48 | } 49 | 50 | func TestParseFromYamlClickHouseListTables(t *testing.T) { 51 | ctx, err := testutils.ContextWithNewLogger() 52 | if err != nil { 53 | t.Fatalf("unexpected error: %s", err) 54 | } 55 | tcs := []struct { 56 | desc string 57 | in string 58 | want server.ToolConfigs 59 | }{ 60 | { 61 | desc: "basic example", 62 | in: ` 63 | tools: 64 | example_tool: 65 | kind: clickhouse-list-tables 66 | source: my-instance 67 | description: some description 68 | `, 69 | want: server.ToolConfigs{ 70 | "example_tool": Config{ 71 | Name: "example_tool", 72 | Kind: "clickhouse-list-tables", 73 | Source: "my-instance", 74 | Description: "some description", 75 | AuthRequired: []string{}, 76 | }, 77 | }, 78 | }, 79 | } 80 | for _, tc := range tcs { 81 | t.Run(tc.desc, func(t *testing.T) { 82 | got := struct { 83 | Tools server.ToolConfigs `yaml:"tools"` 84 | }{} 85 | err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got) 86 | if err != nil { 87 | t.Fatalf("unable to unmarshal: %s", err) 88 | } 89 | if diff := cmp.Diff(tc.want, got.Tools); diff != "" { 90 | t.Fatalf("incorrect parse: diff %v", diff) 91 | } 92 | }) 93 | } 94 | } 95 | 96 | func TestListTablesToolParseParams(t *testing.T) { 97 | databaseParam := tools.NewStringParameter("database", "The database to list tables from.") 98 | tool := Tool{ 99 | Parameters: tools.Parameters{databaseParam}, 100 | AllParams: tools.Parameters{databaseParam}, 101 | } 102 | 103 | params, err := tool.ParseParams(map[string]any{"database": "test_db"}, map[string]map[string]any{}) 104 | if err != nil { 105 | t.Errorf("unexpected error: %v", err) 106 | } 107 | 108 | if len(params) != 1 { 109 | t.Errorf("expected 1 parameter, got %d", len(params)) 110 | } 111 | 112 | mapParams := params.AsMap() 113 | if mapParams["database"] != "test_db" { 114 | t.Errorf("expected database parameter to be 'test_db', got %v", mapParams["database"]) 115 | } 116 | } 117 | ``` -------------------------------------------------------------------------------- /internal/tools/looker/lookeradddashboardelement/lookeradddashboardelement_test.go: -------------------------------------------------------------------------------- ```go 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package lookeradddashboardelement_test 16 | 17 | import ( 18 | "strings" 19 | "testing" 20 | 21 | yaml "github.com/goccy/go-yaml" 22 | "github.com/google/go-cmp/cmp" 23 | "github.com/googleapis/genai-toolbox/internal/server" 24 | "github.com/googleapis/genai-toolbox/internal/testutils" 25 | lkr "github.com/googleapis/genai-toolbox/internal/tools/looker/lookeradddashboardelement" 26 | ) 27 | 28 | func TestParseFromYamlLookerAddDashboardElement(t *testing.T) { 29 | ctx, err := testutils.ContextWithNewLogger() 30 | if err != nil { 31 | t.Fatalf("unexpected error: %s", err) 32 | } 33 | tcs := []struct { 34 | desc string 35 | in string 36 | want server.ToolConfigs 37 | }{ 38 | { 39 | desc: "basic example", 40 | in: ` 41 | tools: 42 | example_tool: 43 | kind: looker-add-dashboard-element 44 | source: my-instance 45 | description: some description 46 | `, 47 | want: server.ToolConfigs{ 48 | "example_tool": lkr.Config{ 49 | Name: "example_tool", 50 | Kind: "looker-add-dashboard-element", 51 | Source: "my-instance", 52 | Description: "some description", 53 | AuthRequired: []string{}, 54 | }, 55 | }, 56 | }, 57 | } 58 | for _, tc := range tcs { 59 | t.Run(tc.desc, func(t *testing.T) { 60 | got := struct { 61 | Tools server.ToolConfigs `yaml:"tools"` 62 | }{} 63 | // Parse contents 64 | err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got) 65 | if err != nil { 66 | t.Fatalf("unable to unmarshal: %s", err) 67 | } 68 | if diff := cmp.Diff(tc.want, got.Tools); diff != "" { 69 | t.Fatalf("incorrect parse: diff %v", diff) 70 | } 71 | }) 72 | } 73 | 74 | } 75 | 76 | func TestFailParseFromYamlLookerAddDashboardElement(t *testing.T) { 77 | ctx, err := testutils.ContextWithNewLogger() 78 | if err != nil { 79 | t.Fatalf("unexpected error: %s", err) 80 | } 81 | tcs := []struct { 82 | desc string 83 | in string 84 | err string 85 | }{ 86 | { 87 | desc: "Invalid method", 88 | in: ` 89 | tools: 90 | example_tool: 91 | kind: looker-add-dashboard-element 92 | source: my-instance 93 | method: GOT 94 | description: some description 95 | `, 96 | err: "unable to parse tool \"example_tool\" as kind \"looker-add-dashboard-element\": [4:1] unknown field \"method\"\n 1 | authRequired: []\n 2 | description: some description\n 3 | kind: looker-add-dashboard-element\n> 4 | method: GOT\n ^\n 5 | source: my-instance", 97 | }, 98 | } 99 | for _, tc := range tcs { 100 | t.Run(tc.desc, func(t *testing.T) { 101 | got := struct { 102 | Tools server.ToolConfigs `yaml:"tools"` 103 | }{} 104 | // Parse contents 105 | err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got) 106 | if err == nil { 107 | t.Fatalf("expect parsing to fail") 108 | } 109 | errStr := err.Error() 110 | if !strings.Contains(errStr, tc.err) { 111 | t.Fatalf("unexpected error string: got %q, want substring %q", errStr, tc.err) 112 | } 113 | }) 114 | } 115 | 116 | } 117 | ``` -------------------------------------------------------------------------------- /tests/cloudmonitoring/cloud_monitoring_integration_test.go: -------------------------------------------------------------------------------- ```go 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cloudmonitoring 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "net/http" 21 | "net/http/httptest" 22 | "testing" 23 | 24 | "github.com/google/go-cmp/cmp" 25 | "github.com/googleapis/genai-toolbox/internal/tools" 26 | "github.com/googleapis/genai-toolbox/internal/tools/cloudmonitoring" 27 | ) 28 | 29 | func TestTool_Invoke(t *testing.T) { 30 | t.Parallel() 31 | 32 | // Mock the monitoring server 33 | server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 34 | if r.URL.Path != "/v1/projects/test-project/location/global/prometheus/api/v1/query" { 35 | http.Error(w, "not found", http.StatusNotFound) 36 | return 37 | } 38 | query := r.URL.Query().Get("query") 39 | if query != "up" { 40 | http.Error(w, "bad request", http.StatusBadRequest) 41 | return 42 | } 43 | w.Header().Set("Content-Type", "application/json") 44 | fmt.Fprintln(w, `{"status":"success","data":{"resultType":"vector","result":[]}}`) 45 | })) 46 | defer server.Close() 47 | 48 | // Create a new observability tool 49 | tool := &cloudmonitoring.Tool{ 50 | Name: "test-cloudmonitoring", 51 | Kind: "cloud-monitoring-query-prometheus", 52 | Description: "Test Cloudmonitoring Tool", 53 | AllParams: tools.Parameters{}, 54 | BaseURL: server.URL, 55 | Client: &http.Client{}, 56 | } 57 | 58 | // Define the test parameters 59 | params := tools.ParamValues{ 60 | {Name: "projectId", Value: "test-project"}, 61 | {Name: "query", Value: "up"}, 62 | } 63 | 64 | // Invoke the tool 65 | result, err := tool.Invoke(context.Background(), params, "") 66 | if err != nil { 67 | t.Fatalf("Invoke() error = %v", err) 68 | } 69 | 70 | // Check the result 71 | expected := map[string]any{ 72 | "status": "success", 73 | "data": map[string]any{ 74 | "resultType": "vector", 75 | "result": []any{}, 76 | }, 77 | } 78 | if diff := cmp.Diff(expected, result); diff != "" { 79 | t.Errorf("Invoke() result mismatch (-want +got):\n%s", diff) 80 | } 81 | } 82 | 83 | func TestTool_Invoke_Error(t *testing.T) { 84 | t.Parallel() 85 | 86 | // Mock the monitoring server to return an error 87 | server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 88 | http.Error(w, "internal server error", http.StatusInternalServerError) 89 | })) 90 | defer server.Close() 91 | 92 | // Create a new observability tool 93 | tool := &cloudmonitoring.Tool{ 94 | Name: "test-cloudmonitoring", 95 | Kind: "clou-monitoring-query-prometheus", 96 | Description: "Test Cloudmonitoring Tool", 97 | AllParams: tools.Parameters{}, 98 | BaseURL: server.URL, 99 | Client: &http.Client{}, 100 | } 101 | 102 | // Define the test parameters 103 | params := tools.ParamValues{ 104 | {Name: "projectId", Value: "test-project"}, 105 | {Name: "query", Value: "up"}, 106 | } 107 | 108 | // Invoke the tool 109 | _, err := tool.Invoke(context.Background(), params, "") 110 | if err == nil { 111 | t.Fatal("Invoke() error = nil, want error") 112 | } 113 | } 114 | ``` -------------------------------------------------------------------------------- /internal/sources/firebird/firebird_test.go: -------------------------------------------------------------------------------- ```go 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package firebird_test 16 | 17 | import ( 18 | "testing" 19 | 20 | yaml "github.com/goccy/go-yaml" 21 | "github.com/google/go-cmp/cmp" 22 | "github.com/googleapis/genai-toolbox/internal/server" 23 | "github.com/googleapis/genai-toolbox/internal/sources/firebird" 24 | "github.com/googleapis/genai-toolbox/internal/testutils" 25 | ) 26 | 27 | func TestParseFromYamlFirebird(t *testing.T) { 28 | tcs := []struct { 29 | desc string 30 | in string 31 | want server.SourceConfigs 32 | }{ 33 | { 34 | desc: "basic example", 35 | in: ` 36 | sources: 37 | my-fdb-instance: 38 | kind: firebird 39 | host: my-host 40 | port: my-port 41 | database: my_db 42 | user: my_user 43 | password: my_pass 44 | `, 45 | want: server.SourceConfigs{ 46 | "my-fdb-instance": firebird.Config{ 47 | Name: "my-fdb-instance", 48 | Kind: firebird.SourceKind, 49 | Host: "my-host", 50 | Port: "my-port", 51 | Database: "my_db", 52 | User: "my_user", 53 | Password: "my_pass", 54 | }, 55 | }, 56 | }, 57 | } 58 | for _, tc := range tcs { 59 | t.Run(tc.desc, func(t *testing.T) { 60 | got := struct { 61 | Sources server.SourceConfigs `yaml:"sources"` 62 | }{} 63 | // Parse contents 64 | err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got) 65 | if err != nil { 66 | t.Fatalf("unable to unmarshal: %s", err) 67 | } 68 | if !cmp.Equal(tc.want, got.Sources) { 69 | t.Fatalf("incorrect parse: want %v, got %v", tc.want, got.Sources) 70 | } 71 | }) 72 | } 73 | 74 | } 75 | 76 | func TestFailParseFromYaml(t *testing.T) { 77 | tcs := []struct { 78 | desc string 79 | in string 80 | err string 81 | }{ 82 | { 83 | desc: "extra field", 84 | in: ` 85 | sources: 86 | my-fdb-instance: 87 | kind: firebird 88 | host: my-host 89 | port: my-port 90 | database: my_db 91 | user: my_user 92 | password: my_pass 93 | foo: bar 94 | `, 95 | err: "unable to parse source \"my-fdb-instance\" as \"firebird\": [2:1] unknown field \"foo\"\n 1 | database: my_db\n> 2 | foo: bar\n ^\n 3 | host: my-host\n 4 | kind: firebird\n 5 | password: my_pass\n 6 | ", 96 | }, 97 | { 98 | desc: "missing required field", 99 | in: ` 100 | sources: 101 | my-fdb-instance: 102 | kind: firebird 103 | host: my-host 104 | port: my-port 105 | database: my_db 106 | user: my_user 107 | `, 108 | err: "unable to parse source \"my-fdb-instance\" as \"firebird\": Key: 'Config.Password' Error:Field validation for 'Password' failed on the 'required' tag", 109 | }, 110 | } 111 | for _, tc := range tcs { 112 | t.Run(tc.desc, func(t *testing.T) { 113 | got := struct { 114 | Sources server.SourceConfigs `yaml:"sources"` 115 | }{} 116 | // Parse contents 117 | err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got) 118 | if err == nil { 119 | t.Fatalf("expect parsing to fail") 120 | } 121 | errStr := err.Error() 122 | if errStr != tc.err { 123 | t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err) 124 | } 125 | }) 126 | } 127 | } 128 | ``` -------------------------------------------------------------------------------- /docs/en/resources/tools/looker/looker-query.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: "looker-query" 3 | type: docs 4 | weight: 1 5 | description: > 6 | "looker-query" runs an inline query using the Looker 7 | semantic model. 8 | aliases: 9 | - /resources/tools/looker-query 10 | --- 11 | 12 | ## About 13 | 14 | The `looker-query` tool runs a query using the Looker 15 | semantic model. 16 | 17 | It's compatible with the following sources: 18 | 19 | - [looker](../../sources/looker.md) 20 | 21 | `looker-query` takes eight parameters: 22 | 23 | 1. the `model` 24 | 2. the `explore` 25 | 3. the `fields` list 26 | 4. an optional set of `filters` 27 | 5. an optional set of `pivots` 28 | 6. an optional set of `sorts` 29 | 7. an optional `limit` 30 | 8. an optional `tz` 31 | 32 | Starting in Looker v25.18, these queries can be identified in Looker's 33 | System Activity. In the History explore, use the field API Client Name 34 | to find MCP Toolbox queries. 35 | 36 | ## Example 37 | 38 | ```yaml 39 | tools: 40 | query: 41 | kind: looker-query 42 | source: looker-source 43 | description: | 44 | Query Tool 45 | 46 | This tool is used to run a query against the LookML model. The 47 | model, explore, and fields list must be specified. Pivots, 48 | filters and sorts are optional. 49 | 50 | The model can be found from the get_models tool. The explore 51 | can be found from the get_explores tool passing in the model. 52 | The fields can be found from the get_dimensions, get_measures, 53 | get_filters, and get_parameters tools, passing in the model 54 | and the explore. 55 | 56 | Provide a model_id and explore_name, then a list 57 | of fields. Optionally a list of pivots can be provided. 58 | The pivots must also be included in the fields list. 59 | 60 | Filters are provided as a map of {"field.id": "condition", 61 | "field.id2": "condition2", ...}. Do not put the field.id in 62 | quotes. Filter expressions can be found at 63 | https://cloud.google.com/looker/docs/filter-expressions. 64 | 65 | Sorts can be specified like [ "field.id desc 0" ]. 66 | 67 | An optional row limit can be added. If not provided the limit 68 | will default to 500. "-1" can be specified for unlimited. 69 | 70 | An optional query timezone can be added. The query_timezone to 71 | will default to that of the workstation where this MCP server 72 | is running, or Etc/UTC if that can't be determined. Not all 73 | models support custom timezones. 74 | 75 | The result of the query tool is JSON 76 | ``` 77 | 78 | ## Reference 79 | 80 | | **field** | **type** | **required** | **description** | 81 | |-------------|:------------------------------------------:|:------------:|--------------------------------------------------------------------------------------------------| 82 | | kind | string | true | Must be "looker-query" | 83 | | source | string | true | Name of the source the SQL should execute on. | 84 | | description | string | true | Description of the tool that is passed to the LLM. | 85 | ``` -------------------------------------------------------------------------------- /tests/server.go: -------------------------------------------------------------------------------- ```go 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package tests 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "io" 21 | "os" 22 | 23 | yaml "github.com/goccy/go-yaml" 24 | 25 | "github.com/googleapis/genai-toolbox/cmd" 26 | ) 27 | 28 | // tmpFileWithCleanup creates a temporary file with the content and returns the path and 29 | // a function to clean it up, or any errors encountered instead 30 | func tmpFileWithCleanup(content []byte) (string, func(), error) { 31 | // create a random file in the temp dir 32 | f, err := os.CreateTemp("", "*") // * indicates random string 33 | if err != nil { 34 | return "", nil, err 35 | } 36 | cleanup := func() { os.Remove(f.Name()) } 37 | 38 | if _, err := f.Write(content); err != nil { 39 | cleanup() 40 | return "", nil, err 41 | } 42 | if err := f.Close(); err != nil { 43 | cleanup() 44 | return "", nil, err 45 | } 46 | return f.Name(), cleanup, err 47 | } 48 | 49 | // CmdExec represents an invocation of a toolbox command. 50 | type CmdExec struct { 51 | Out io.ReadCloser 52 | 53 | cmd *cmd.Command 54 | cancel context.CancelFunc 55 | closers []io.Closer 56 | done chan bool // closed once the cmd is completed 57 | err error 58 | } 59 | 60 | // StartCmd returns a CmdExec representing a running instance of a toolbox command. 61 | func StartCmd(ctx context.Context, toolsFile map[string]any, args ...string) (*CmdExec, func(), error) { 62 | b, err := yaml.Marshal(toolsFile) 63 | if err != nil { 64 | return nil, nil, fmt.Errorf("unable to marshal tools file: %s", err) 65 | } 66 | path, cleanup, err := tmpFileWithCleanup(b) 67 | if err != nil { 68 | return nil, nil, fmt.Errorf("unable to write tools file: %s", err) 69 | } 70 | args = append(args, "--tools-file", path) 71 | 72 | ctx, cancel := context.WithCancel(ctx) 73 | // Open a pipe for tracking the output from the cmd 74 | pr, pw, err := os.Pipe() 75 | if err != nil { 76 | cancel() 77 | return nil, nil, fmt.Errorf("unable to open stdout pipe: %w", err) 78 | } 79 | 80 | c := cmd.NewCommand(cmd.WithStreams(pw, pw)) 81 | c.SetArgs(args) 82 | 83 | t := &CmdExec{ 84 | Out: pr, 85 | cmd: c, 86 | cancel: cancel, 87 | closers: []io.Closer{pr, pw}, 88 | done: make(chan bool), 89 | } 90 | 91 | // Start the command in the background 92 | go func() { 93 | defer close(t.done) 94 | defer cancel() 95 | t.err = c.ExecuteContext(ctx) 96 | }() 97 | return t, cleanup, nil 98 | 99 | } 100 | 101 | // Stop sends the TERM signal to the cmd and returns. 102 | func (c *CmdExec) Stop() { 103 | c.cancel() 104 | } 105 | 106 | // Waits until the execution is completed and returns any error from the result. 107 | func (c *CmdExec) Wait(ctx context.Context) error { 108 | select { 109 | case <-ctx.Done(): 110 | return ctx.Err() 111 | case <-c.done: 112 | return c.err 113 | } 114 | } 115 | 116 | // Done returns true if the command has exited. 117 | func (c *CmdExec) Done() bool { 118 | select { 119 | case <-c.done: 120 | return true 121 | default: 122 | } 123 | return false 124 | } 125 | 126 | // Close releases any resources associated with the instance. 127 | func (c *CmdExec) Close() { 128 | c.cancel() 129 | for _, c := range c.closers { 130 | c.Close() 131 | } 132 | } 133 | ``` -------------------------------------------------------------------------------- /internal/sources/oceanbase/oceanbase.go: -------------------------------------------------------------------------------- ```go 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package oceanbase 16 | 17 | import ( 18 | "context" 19 | "database/sql" 20 | "fmt" 21 | "time" 22 | 23 | _ "github.com/go-sql-driver/mysql" 24 | "github.com/goccy/go-yaml" 25 | "github.com/googleapis/genai-toolbox/internal/sources" 26 | "go.opentelemetry.io/otel/trace" 27 | ) 28 | 29 | const SourceKind string = "oceanbase" 30 | 31 | // validate interface 32 | var _ sources.SourceConfig = Config{} 33 | 34 | func init() { 35 | if !sources.Register(SourceKind, newConfig) { 36 | panic(fmt.Sprintf("source kind %q already registered", SourceKind)) 37 | } 38 | } 39 | 40 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) { 41 | actual := Config{Name: name} 42 | if err := decoder.DecodeContext(ctx, &actual); err != nil { 43 | return nil, err 44 | } 45 | return actual, nil 46 | } 47 | 48 | type Config struct { 49 | Name string `yaml:"name" validate:"required"` 50 | Kind string `yaml:"kind" validate:"required"` 51 | Host string `yaml:"host" validate:"required"` 52 | Port string `yaml:"port" validate:"required"` 53 | User string `yaml:"user" validate:"required"` 54 | Password string `yaml:"password" validate:"required"` 55 | Database string `yaml:"database" validate:"required"` 56 | QueryTimeout string `yaml:"queryTimeout"` 57 | } 58 | 59 | func (r Config) SourceConfigKind() string { 60 | return SourceKind 61 | } 62 | 63 | func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) { 64 | pool, err := initOceanBaseConnectionPool(ctx, tracer, r.Name, r.Host, r.Port, r.User, r.Password, r.Database, r.QueryTimeout) 65 | if err != nil { 66 | return nil, fmt.Errorf("unable to create pool: %w", err) 67 | } 68 | 69 | err = pool.PingContext(ctx) 70 | if err != nil { 71 | return nil, fmt.Errorf("unable to connect successfully: %w", err) 72 | } 73 | 74 | s := &Source{ 75 | Name: r.Name, 76 | Kind: SourceKind, 77 | Pool: pool, 78 | } 79 | return s, nil 80 | } 81 | 82 | var _ sources.Source = &Source{} 83 | 84 | type Source struct { 85 | Name string `yaml:"name"` 86 | Kind string `yaml:"kind"` 87 | Pool *sql.DB 88 | } 89 | 90 | func (s *Source) SourceKind() string { 91 | return SourceKind 92 | } 93 | 94 | func (s *Source) OceanBasePool() *sql.DB { 95 | return s.Pool 96 | } 97 | 98 | func initOceanBaseConnectionPool(ctx context.Context, tracer trace.Tracer, name, host, port, user, pass, dbname, queryTimeout string) (*sql.DB, error) { 99 | _, span := sources.InitConnectionSpan(ctx, tracer, SourceKind, name) 100 | defer span.End() 101 | 102 | dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?parseTime=true", user, pass, host, port, dbname) 103 | 104 | if queryTimeout != "" { 105 | timeout, err := time.ParseDuration(queryTimeout) 106 | if err != nil { 107 | return nil, fmt.Errorf("invalid queryTimeout %q: %w", queryTimeout, err) 108 | } 109 | dsn += "&readTimeout=" + timeout.String() 110 | } 111 | 112 | pool, err := sql.Open("mysql", dsn) 113 | if err != nil { 114 | return nil, fmt.Errorf("sql.Open: %w", err) 115 | } 116 | return pool, nil 117 | } 118 | ``` -------------------------------------------------------------------------------- /internal/telemetry/instrumentation.go: -------------------------------------------------------------------------------- ```go 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package telemetry 16 | 17 | import ( 18 | "fmt" 19 | 20 | "go.opentelemetry.io/otel" 21 | "go.opentelemetry.io/otel/metric" 22 | "go.opentelemetry.io/otel/trace" 23 | ) 24 | 25 | const ( 26 | TracerName = "github.com/googleapis/genai-toolbox/internal/opentel" 27 | MetricName = "github.com/googleapis/genai-toolbox/internal/opentel" 28 | 29 | toolsetGetCountName = "toolbox.server.toolset.get.count" 30 | toolGetCountName = "toolbox.server.tool.get.count" 31 | toolInvokeCountName = "toolbox.server.tool.invoke.count" 32 | mcpSseCountName = "toolbox.server.mcp.sse.count" 33 | mcpPostCountName = "toolbox.server.mcp.post.count" 34 | ) 35 | 36 | // Instrumentation defines the telemetry instrumentation for toolbox 37 | type Instrumentation struct { 38 | Tracer trace.Tracer 39 | meter metric.Meter 40 | ToolsetGet metric.Int64Counter 41 | ToolGet metric.Int64Counter 42 | ToolInvoke metric.Int64Counter 43 | McpSse metric.Int64Counter 44 | McpPost metric.Int64Counter 45 | } 46 | 47 | func CreateTelemetryInstrumentation(versionString string) (*Instrumentation, error) { 48 | tracer := otel.Tracer( 49 | TracerName, 50 | trace.WithInstrumentationVersion(versionString), 51 | ) 52 | 53 | meter := otel.Meter(MetricName, metric.WithInstrumentationVersion(versionString)) 54 | toolsetGet, err := meter.Int64Counter( 55 | toolsetGetCountName, 56 | metric.WithDescription("Number of toolset GET API calls."), 57 | metric.WithUnit("{call}"), 58 | ) 59 | if err != nil { 60 | return nil, fmt.Errorf("unable to create %s metric: %w", toolsetGetCountName, err) 61 | } 62 | 63 | toolGet, err := meter.Int64Counter( 64 | toolGetCountName, 65 | metric.WithDescription("Number of tool GET API calls."), 66 | metric.WithUnit("{call}"), 67 | ) 68 | if err != nil { 69 | return nil, fmt.Errorf("unable to create %s metric: %w", toolGetCountName, err) 70 | } 71 | 72 | toolInvoke, err := meter.Int64Counter( 73 | toolInvokeCountName, 74 | metric.WithDescription("Number of tool Invoke API calls."), 75 | metric.WithUnit("{call}"), 76 | ) 77 | if err != nil { 78 | return nil, fmt.Errorf("unable to create %s metric: %w", toolInvokeCountName, err) 79 | } 80 | 81 | mcpSse, err := meter.Int64Counter( 82 | mcpSseCountName, 83 | metric.WithDescription("Number of MCP SSE connection requests."), 84 | metric.WithUnit("{connection}"), 85 | ) 86 | if err != nil { 87 | return nil, fmt.Errorf("unable to create %s metric: %w", mcpSseCountName, err) 88 | } 89 | 90 | mcpPost, err := meter.Int64Counter( 91 | mcpPostCountName, 92 | metric.WithDescription("Number of MCP Post calls."), 93 | metric.WithUnit("{call}"), 94 | ) 95 | if err != nil { 96 | return nil, fmt.Errorf("unable to create %s metric: %w", mcpPostCountName, err) 97 | } 98 | 99 | instrumentation := &Instrumentation{ 100 | Tracer: tracer, 101 | meter: meter, 102 | ToolsetGet: toolsetGet, 103 | ToolGet: toolGet, 104 | ToolInvoke: toolInvoke, 105 | McpSse: mcpSse, 106 | McpPost: mcpPost, 107 | } 108 | return instrumentation, nil 109 | } 110 | ``` -------------------------------------------------------------------------------- /docs/en/resources/sources/tidb.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: "TiDB" 3 | type: docs 4 | weight: 1 5 | description: > 6 | TiDB is a distributed SQL database that combines the best of traditional RDBMS and NoSQL databases. 7 | 8 | --- 9 | 10 | ## About 11 | 12 | [TiDB][tidb-docs] is an open-source distributed SQL database that supports 13 | Hybrid Transactional and Analytical Processing (HTAP) workloads. It is 14 | MySQL-compatible and features horizontal scalability, strong consistency, and 15 | high availability. 16 | 17 | [tidb-docs]: https://docs.pingcap.com/tidb/stable 18 | 19 | ## Requirements 20 | 21 | ### Database User 22 | 23 | This source uses standard MySQL protocol authentication. You will need to 24 | [create a TiDB user][tidb-users] to login to the database with. 25 | 26 | For TiDB Cloud users, you can create database users through the TiDB Cloud 27 | console. 28 | 29 | [tidb-users]: https://docs.pingcap.com/tidb/stable/user-account-management 30 | 31 | ## SSL Configuration 32 | 33 | - TiDB Cloud 34 | 35 | For TiDB Cloud instances, SSL is automatically enabled when the hostname 36 | matches the TiDB Cloud pattern (`gateway*.*.*.tidbcloud.com`). You don't 37 | need to explicitly set `ssl: true` for TiDB Cloud connections. 38 | 39 | - Self-Hosted TiDB 40 | 41 | For self-hosted TiDB instances, you can optionally enable SSL by setting 42 | `ssl: true` in your configuration. 43 | 44 | ## Example 45 | 46 | - TiDB Cloud 47 | 48 | ```yaml 49 | sources: 50 | my-tidb-cloud-source: 51 | kind: tidb 52 | host: gateway01.us-west-2.prod.aws.tidbcloud.com 53 | port: 4000 54 | database: my_db 55 | user: ${TIDB_USERNAME} 56 | password: ${TIDB_PASSWORD} 57 | # SSL is automatically enabled for TiDB Cloud 58 | ``` 59 | 60 | - Self-Hosted TiDB 61 | 62 | ```yaml 63 | sources: 64 | my-tidb-source: 65 | kind: tidb 66 | host: 127.0.0.1 67 | port: 4000 68 | database: my_db 69 | user: ${TIDB_USERNAME} 70 | password: ${TIDB_PASSWORD} 71 | # ssl: true # Optional: enable SSL for secure connections 72 | ``` 73 | 74 | {{< notice tip >}} 75 | Use environment variable replacement with the format ${ENV_NAME} 76 | instead of hardcoding your secrets into the configuration file. 77 | {{< /notice >}} 78 | 79 | ## Reference 80 | 81 | | **field** | **type** | **required** | **description** | 82 | |-----------|:--------:|:------------:|--------------------------------------------------------------------------------------------| 83 | | kind | string | true | Must be "tidb". | 84 | | host | string | true | IP address or hostname to connect to (e.g. "127.0.0.1" or "gateway01.*.tidbcloud.com"). | 85 | | port | string | true | Port to connect to (typically "4000" for TiDB). | 86 | | database | string | true | Name of the TiDB database to connect to (e.g. "my_db"). | 87 | | user | string | true | Name of the TiDB user to connect as (e.g. "my-tidb-user"). | 88 | | password | string | true | Password of the TiDB user (e.g. "my-password"). | 89 | | ssl | boolean | false | Whether to use SSL/TLS encryption. Automatically enabled for TiDB Cloud instances. | 90 | ``` -------------------------------------------------------------------------------- /docs/en/resources/tools/firestore/firestore-validate-rules.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: "firestore-validate-rules" 3 | type: docs 4 | weight: 1 5 | description: > 6 | A "firestore-validate-rules" tool validates Firestore security rules syntax and semantic correctness without deploying them. It provides detailed error reporting with source positions and code snippets. 7 | aliases: 8 | - /resources/tools/firestore-validate-rules 9 | --- 10 | 11 | ## Overview 12 | 13 | The `firestore-validate-rules` tool validates Firestore security rules syntax 14 | and semantic correctness without deploying them. It provides detailed error 15 | reporting with source positions and code snippets. 16 | 17 | ## Configuration 18 | 19 | ```yaml 20 | tools: 21 | firestore-validate-rules: 22 | kind: firestore-validate-rules 23 | source: <firestore-source-name> 24 | description: "Checks the provided Firestore Rules source for syntax and validation errors" 25 | ``` 26 | 27 | ## Authentication 28 | 29 | This tool requires authentication if the source requires authentication. 30 | 31 | ## Parameters 32 | 33 | | **parameters** | **type** | **required** | **description** | 34 | |-----------------|:------------:|:------------:|----------------------------------------------| 35 | | source | string | true | The Firestore Rules source code to validate | 36 | 37 | ## Response 38 | 39 | The tool returns a `ValidationResult` object containing: 40 | 41 | ```json 42 | { 43 | "valid": "boolean", 44 | "issueCount": "number", 45 | "formattedIssues": "string", 46 | "rawIssues": [ 47 | { 48 | "sourcePosition": { 49 | "fileName": "string", 50 | "line": "number", 51 | "column": "number", 52 | "currentOffset": "number", 53 | "endOffset": "number" 54 | }, 55 | "description": "string", 56 | "severity": "string" 57 | } 58 | ] 59 | } 60 | ``` 61 | 62 | ## Example Usage 63 | 64 | ### Validate simple rules 65 | 66 | ```json 67 | { 68 | "source": "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /{document=**} {\n allow read, write: if true;\n }\n }\n}" 69 | } 70 | ``` 71 | 72 | ### Example response for valid rules 73 | 74 | ```json 75 | { 76 | "valid": true, 77 | "issueCount": 0, 78 | "formattedIssues": "✓ No errors detected. Rules are valid." 79 | } 80 | ``` 81 | 82 | ### Example response with errors 83 | 84 | ```json 85 | { 86 | "valid": false, 87 | "issueCount": 1, 88 | "formattedIssues": "Found 1 issue(s) in rules source:\n\nERROR: Unexpected token ';' [Ln 4, Col 32]\n```\n allow read, write: if true;;\n ^\n```", 89 | "rawIssues": [ 90 | { 91 | "sourcePosition": { 92 | "line": 4, 93 | "column": 32, 94 | "currentOffset": 105, 95 | "endOffset": 106 96 | }, 97 | "description": "Unexpected token ';'", 98 | "severity": "ERROR" 99 | } 100 | ] 101 | } 102 | ``` 103 | 104 | ## Error Handling 105 | 106 | The tool will return errors for: 107 | 108 | - Missing or empty `source` parameter 109 | - API errors when calling the Firebase Rules service 110 | - Network connectivity issues 111 | 112 | ## Use Cases 113 | 114 | 1. **Pre-deployment validation**: Validate rules before deploying to production 115 | 2. **CI/CD integration**: Integrate rules validation into your build pipeline 116 | 3. **Development workflow**: Quickly check rules syntax while developing 117 | 4. **Error debugging**: Get detailed error locations with code snippets 118 | 119 | ## Related Tools 120 | 121 | - [firestore-get-rules]({{< ref "firestore-get-rules" >}}): Retrieve current 122 | active rules 123 | - [firestore-query-collection]({{< ref "firestore-query-collection" >}}): Test 124 | rules by querying collections 125 | ``` -------------------------------------------------------------------------------- /internal/sources/dataplex/dataplex.go: -------------------------------------------------------------------------------- ```go 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package dataplex 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | 21 | dataplexapi "cloud.google.com/go/dataplex/apiv1" 22 | "github.com/goccy/go-yaml" 23 | "github.com/googleapis/genai-toolbox/internal/sources" 24 | "github.com/googleapis/genai-toolbox/internal/util" 25 | "go.opentelemetry.io/otel/trace" 26 | "golang.org/x/oauth2/google" 27 | "google.golang.org/api/option" 28 | ) 29 | 30 | const SourceKind string = "dataplex" 31 | 32 | // validate interface 33 | var _ sources.SourceConfig = Config{} 34 | 35 | func init() { 36 | if !sources.Register(SourceKind, newConfig) { 37 | panic(fmt.Sprintf("source kind %q already registered", SourceKind)) 38 | } 39 | } 40 | 41 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) { 42 | actual := Config{Name: name} 43 | if err := decoder.DecodeContext(ctx, &actual); err != nil { 44 | return nil, err 45 | } 46 | return actual, nil 47 | } 48 | 49 | type Config struct { 50 | // Dataplex configs 51 | Name string `yaml:"name" validate:"required"` 52 | Kind string `yaml:"kind" validate:"required"` 53 | Project string `yaml:"project" validate:"required"` 54 | } 55 | 56 | func (r Config) SourceConfigKind() string { 57 | // Returns Dataplex source kind 58 | return SourceKind 59 | } 60 | 61 | func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) { 62 | // Initializes a Dataplex source 63 | client, err := initDataplexConnection(ctx, tracer, r.Name, r.Project) 64 | if err != nil { 65 | return nil, err 66 | } 67 | s := &Source{ 68 | Name: r.Name, 69 | Kind: SourceKind, 70 | Client: client, 71 | Project: r.Project, 72 | } 73 | 74 | return s, nil 75 | } 76 | 77 | var _ sources.Source = &Source{} 78 | 79 | type Source struct { 80 | // Source struct with Dataplex client 81 | Name string `yaml:"name"` 82 | Kind string `yaml:"kind"` 83 | Client *dataplexapi.CatalogClient 84 | Project string `yaml:"project"` 85 | Location string `yaml:"location"` 86 | } 87 | 88 | func (s *Source) SourceKind() string { 89 | // Returns Dataplex source kind 90 | return SourceKind 91 | } 92 | 93 | func (s *Source) ProjectID() string { 94 | return s.Project 95 | } 96 | 97 | func (s *Source) CatalogClient() *dataplexapi.CatalogClient { 98 | return s.Client 99 | } 100 | 101 | func initDataplexConnection( 102 | ctx context.Context, 103 | tracer trace.Tracer, 104 | name string, 105 | project string, 106 | ) (*dataplexapi.CatalogClient, error) { 107 | ctx, span := sources.InitConnectionSpan(ctx, tracer, SourceKind, name) 108 | defer span.End() 109 | 110 | cred, err := google.FindDefaultCredentials(ctx) 111 | if err != nil { 112 | return nil, fmt.Errorf("failed to find default Google Cloud credentials: %w", err) 113 | } 114 | 115 | userAgent, err := util.UserAgentFromContext(ctx) 116 | if err != nil { 117 | return nil, err 118 | } 119 | 120 | client, err := dataplexapi.NewCatalogClient(ctx, option.WithUserAgent(userAgent), option.WithCredentials(cred)) 121 | if err != nil { 122 | return nil, fmt.Errorf("failed to create Dataplex client for project %q: %w", project, err) 123 | } 124 | return client, nil 125 | } 126 | ``` -------------------------------------------------------------------------------- /internal/sources/firestore/firestore_test.go: -------------------------------------------------------------------------------- ```go 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package firestore_test 16 | 17 | import ( 18 | "testing" 19 | 20 | yaml "github.com/goccy/go-yaml" 21 | "github.com/google/go-cmp/cmp" 22 | "github.com/googleapis/genai-toolbox/internal/server" 23 | "github.com/googleapis/genai-toolbox/internal/sources/firestore" 24 | "github.com/googleapis/genai-toolbox/internal/testutils" 25 | ) 26 | 27 | func TestParseFromYamlFirestore(t *testing.T) { 28 | tcs := []struct { 29 | desc string 30 | in string 31 | want server.SourceConfigs 32 | }{ 33 | { 34 | desc: "basic example with default database", 35 | in: ` 36 | sources: 37 | my-firestore: 38 | kind: firestore 39 | project: my-project 40 | `, 41 | want: server.SourceConfigs{ 42 | "my-firestore": firestore.Config{ 43 | Name: "my-firestore", 44 | Kind: firestore.SourceKind, 45 | Project: "my-project", 46 | Database: "", 47 | }, 48 | }, 49 | }, 50 | { 51 | desc: "with custom database", 52 | in: ` 53 | sources: 54 | my-firestore: 55 | kind: firestore 56 | project: my-project 57 | database: my-database 58 | `, 59 | want: server.SourceConfigs{ 60 | "my-firestore": firestore.Config{ 61 | Name: "my-firestore", 62 | Kind: firestore.SourceKind, 63 | Project: "my-project", 64 | Database: "my-database", 65 | }, 66 | }, 67 | }, 68 | } 69 | for _, tc := range tcs { 70 | t.Run(tc.desc, func(t *testing.T) { 71 | got := struct { 72 | Sources server.SourceConfigs `yaml:"sources"` 73 | }{} 74 | // Parse contents 75 | err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got) 76 | if err != nil { 77 | t.Fatalf("unable to unmarshal: %s", err) 78 | } 79 | if !cmp.Equal(tc.want, got.Sources) { 80 | t.Fatalf("incorrect parse: want %v, got %v", tc.want, got.Sources) 81 | } 82 | }) 83 | } 84 | } 85 | 86 | func TestFailParseFromYamlFirestore(t *testing.T) { 87 | tcs := []struct { 88 | desc string 89 | in string 90 | err string 91 | }{ 92 | { 93 | desc: "extra field", 94 | in: ` 95 | sources: 96 | my-firestore: 97 | kind: firestore 98 | project: my-project 99 | foo: bar 100 | `, 101 | err: "unable to parse source \"my-firestore\" as \"firestore\": [1:1] unknown field \"foo\"\n> 1 | foo: bar\n ^\n 2 | kind: firestore\n 3 | project: my-project", 102 | }, 103 | { 104 | desc: "missing required field", 105 | in: ` 106 | sources: 107 | my-firestore: 108 | kind: firestore 109 | database: my-database 110 | `, 111 | err: "unable to parse source \"my-firestore\" as \"firestore\": Key: 'Config.Project' Error:Field validation for 'Project' failed on the 'required' tag", 112 | }, 113 | } 114 | for _, tc := range tcs { 115 | t.Run(tc.desc, func(t *testing.T) { 116 | got := struct { 117 | Sources server.SourceConfigs `yaml:"sources"` 118 | }{} 119 | // Parse contents 120 | err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got) 121 | if err == nil { 122 | t.Fatalf("expect parsing to fail") 123 | } 124 | errStr := err.Error() 125 | if errStr != tc.err { 126 | t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err) 127 | } 128 | }) 129 | } 130 | } 131 | ``` -------------------------------------------------------------------------------- /internal/sources/alloydbadmin/alloydbadmin_test.go: -------------------------------------------------------------------------------- ```go 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package alloydbadmin_test 16 | 17 | import ( 18 | "testing" 19 | 20 | yaml "github.com/goccy/go-yaml" 21 | "github.com/google/go-cmp/cmp" 22 | "github.com/googleapis/genai-toolbox/internal/server" 23 | "github.com/googleapis/genai-toolbox/internal/sources" 24 | "github.com/googleapis/genai-toolbox/internal/sources/alloydbadmin" 25 | "github.com/googleapis/genai-toolbox/internal/testutils" 26 | ) 27 | 28 | func TestParseFromYamlAlloyDBAdmin(t *testing.T) { 29 | tcs := []struct { 30 | desc string 31 | in string 32 | want server.SourceConfigs 33 | }{ 34 | { 35 | desc: "basic example", 36 | in: ` 37 | sources: 38 | my-alloydb-admin-instance: 39 | kind: alloydb-admin 40 | `, 41 | want: map[string]sources.SourceConfig{ 42 | "my-alloydb-admin-instance": alloydbadmin.Config{ 43 | Name: "my-alloydb-admin-instance", 44 | Kind: alloydbadmin.SourceKind, 45 | UseClientOAuth: false, 46 | }, 47 | }, 48 | }, 49 | { 50 | desc: "use client auth example", 51 | in: ` 52 | sources: 53 | my-alloydb-admin-instance: 54 | kind: alloydb-admin 55 | useClientOAuth: true 56 | `, 57 | want: map[string]sources.SourceConfig{ 58 | "my-alloydb-admin-instance": alloydbadmin.Config{ 59 | Name: "my-alloydb-admin-instance", 60 | Kind: alloydbadmin.SourceKind, 61 | UseClientOAuth: true, 62 | }, 63 | }, 64 | }, 65 | } 66 | for _, tc := range tcs { 67 | t.Run(tc.desc, func(t *testing.T) { 68 | got := struct { 69 | Sources server.SourceConfigs `yaml:"sources"` 70 | }{} 71 | // Parse contents 72 | err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got) 73 | if err != nil { 74 | t.Fatalf("unable to unmarshal: %s", err) 75 | } 76 | if !cmp.Equal(tc.want, got.Sources) { 77 | t.Fatalf("incorrect parse: want %v, got %v", tc.want, got.Sources) 78 | } 79 | }) 80 | } 81 | } 82 | 83 | func TestFailParseFromYaml(t *testing.T) { 84 | tcs := []struct { 85 | desc string 86 | in string 87 | err string 88 | }{ 89 | { 90 | desc: "extra field", 91 | in: ` 92 | sources: 93 | my-alloydb-admin-instance: 94 | kind: alloydb-admin 95 | project: test-project 96 | `, 97 | err: "unable to parse source \"my-alloydb-admin-instance\" as \"alloydb-admin\": [2:1] unknown field \"project\"\n 1 | kind: alloydb-admin\n> 2 | project: test-project\n ^\n", 98 | }, 99 | { 100 | desc: "missing required field", 101 | in: ` 102 | sources: 103 | my-alloydb-admin-instance: 104 | useClientOAuth: true 105 | `, 106 | err: "missing 'kind' field for source \"my-alloydb-admin-instance\"", 107 | }, 108 | } 109 | for _, tc := range tcs { 110 | t.Run(tc.desc, func(t *testing.T) { 111 | got := struct { 112 | Sources server.SourceConfigs `yaml:"sources"` 113 | }{} 114 | // Parse contents 115 | err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got) 116 | if err == nil { 117 | t.Fatalf("expect parsing to fail") 118 | } 119 | errStr := err.Error() 120 | if errStr != tc.err { 121 | t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err) 122 | } 123 | }) 124 | } 125 | } 126 | ``` -------------------------------------------------------------------------------- /docs/en/samples/looker/looker_gemini.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: "Quickstart (MCP with Looker and Gemini-CLI)" 3 | type: docs 4 | weight: 2 5 | description: > 6 | How to get started running Toolbox with Gemini-CLI and Looker as the source. 7 | --- 8 | 9 | ## Overview 10 | 11 | [Model Context Protocol](https://modelcontextprotocol.io) is an open protocol 12 | that standardizes how applications provide context to LLMs. Check out this page 13 | on how to [connect to Toolbox via MCP](../../how-to/connect_via_mcp.md). 14 | 15 | ## Step 1: Get a Looker Client ID and Client Secret 16 | 17 | The Looker Client ID and Client Secret can be obtained from the Users page of 18 | your Looker instance. Refer to the documentation 19 | [here](https://cloud.google.com/looker/docs/api-auth#authentication_with_an_sdk). 20 | You may need to ask an administrator to get the Client ID and Client Secret 21 | for you. 22 | 23 | ## Step 2: Install and configure Toolbox 24 | 25 | In this section, we will download Toolbox and run the Toolbox server. 26 | 27 | 1. Download the latest version of Toolbox as a binary: 28 | 29 | {{< notice tip >}} 30 | Select the 31 | [correct binary](https://github.com/googleapis/genai-toolbox/releases) 32 | corresponding to your OS and CPU architecture. 33 | {{< /notice >}} 34 | <!-- {x-release-please-start-version} --> 35 | ```bash 36 | export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64 37 | curl -O https://storage.googleapis.com/genai-toolbox/v0.17.0/$OS/toolbox 38 | ``` 39 | <!-- {x-release-please-end} --> 40 | 41 | 1. Make the binary executable: 42 | 43 | ```bash 44 | chmod +x toolbox 45 | ``` 46 | 47 | 1. Edit the file `~/.gemini/settings.json` and add the following 48 | to the list of mcpServers. Use the Client Id and Client Secret 49 | you obtained earlier. The name of the server - here 50 | `looker-toolbox` - can be anything meaningful to you. 51 | 52 | ```json 53 | "mcpServers": { 54 | "looker-toolbox": { 55 | "command": "/path/to/toolbox", 56 | "args": [ 57 | "--stdio", 58 | "--prebuilt", 59 | "looker" 60 | ], 61 | "env": { 62 | "LOOKER_BASE_URL": "https://looker.example.com", 63 | "LOOKER_CLIENT_ID": "", 64 | "LOOKER_CLIENT_SECRET": "", 65 | "LOOKER_VERIFY_SSL": "true" 66 | } 67 | } 68 | } 69 | ``` 70 | 71 | In some instances you may need to append `:19999` to 72 | the LOOKER_BASE_URL. 73 | 74 | ## Step 3: Start Gemini-CLI 75 | 76 | 1. Run Gemini-CLI: 77 | 78 | ```bash 79 | npx https://github.com/google-gemini/gemini-cli 80 | ``` 81 | 82 | 1. Type `y` when it asks to download. 83 | 84 | 1. Log into Gemini-CLI 85 | 86 | 1. Enter the command `/mcp` and you should see a list of 87 | available tools like 88 | 89 | ``` 90 | ℹ Configured MCP servers: 91 | 92 | 🟢 looker-toolbox - Ready (10 tools) 93 | - looker-toolbox__get_models 94 | - looker-toolbox__query 95 | - looker-toolbox__get_looks 96 | - looker-toolbox__get_measures 97 | - looker-toolbox__get_filters 98 | - looker-toolbox__get_parameters 99 | - looker-toolbox__get_explores 100 | - looker-toolbox__query_sql 101 | - looker-toolbox__get_dimensions 102 | - looker-toolbox__run_look 103 | - looker-toolbox__query_url 104 | ``` 105 | 106 | 1. Start exploring your Looker instance with commands like 107 | `Find an explore to see orders` or `show me my current 108 | inventory broken down by item category`. 109 | 110 | 1. Gemini will prompt you for your approval before using 111 | a tool. You can approve all the tools at once or 112 | one at a time. 113 | ``` -------------------------------------------------------------------------------- /tests/utility/wait_integration_test.go: -------------------------------------------------------------------------------- ```go 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package utility 16 | 17 | import ( 18 | "bytes" 19 | "context" 20 | "encoding/json" 21 | "io" 22 | "net/http" 23 | "regexp" 24 | "testing" 25 | "time" 26 | 27 | "github.com/googleapis/genai-toolbox/internal/testutils" 28 | "github.com/googleapis/genai-toolbox/tests" 29 | ) 30 | 31 | func RunWaitTool(t *testing.T) { 32 | ctx, cancel := context.WithTimeout(context.Background(), time.Minute) 33 | defer cancel() 34 | 35 | args := []string{"--port", "5001"} 36 | 37 | toolsFile := map[string]any{ 38 | "tools": map[string]any{ 39 | "my-wait-for-tool": map[string]any{ 40 | "kind": "wait", 41 | "description": "Wait for a specified duration.", 42 | "timeout": "30s", 43 | }, 44 | }, 45 | } 46 | cmd, cleanup, err := tests.StartCmd(ctx, toolsFile, args...) 47 | if err != nil { 48 | t.Fatalf("command initialization returned an error: %s", err) 49 | } 50 | defer cleanup() 51 | 52 | waitCtx, cancel := context.WithTimeout(ctx, 10*time.Second) 53 | defer cancel() 54 | out, err := testutils.WaitForString(waitCtx, regexp.MustCompile(`Server ready to serve`), cmd.Out) 55 | if err != nil { 56 | t.Logf("toolbox command logs: \n%s", out) 57 | t.Fatalf("toolbox didn't start successfully: %s", err) 58 | } 59 | 60 | invokeTcs := []struct { 61 | name string 62 | api string 63 | requestBody io.Reader 64 | want string 65 | isErr bool 66 | }{ 67 | { 68 | name: "invoke my-wait-for-tool", 69 | api: "http://127.0.0.1:5001/api/tool/my-wait-for-tool/invoke", 70 | requestBody: bytes.NewBuffer([]byte(`{"duration": "1s"}`)), 71 | want: `["Wait for 1s completed successfully."]`, 72 | isErr: false, 73 | }, 74 | { 75 | name: "invoke my-wait-for-tool with invalid duration", 76 | api: "http://127.0.0.1:5001/api/tool/my-wait-for-tool/invoke", 77 | requestBody: bytes.NewBuffer([]byte(`{"duration": "invalid"}`)), 78 | isErr: true, 79 | }, 80 | } 81 | for _, tc := range invokeTcs { 82 | t.Run(tc.name, func(t *testing.T) { 83 | req, err := http.NewRequest(http.MethodPost, tc.api, tc.requestBody) 84 | if err != nil { 85 | t.Fatalf("unable to create request: %s", err) 86 | } 87 | req.Header.Add("Content-type", "application/json") 88 | resp, err := http.DefaultClient.Do(req) 89 | if err != nil { 90 | t.Fatalf("unable to send request: %s", err) 91 | } 92 | defer resp.Body.Close() 93 | 94 | if resp.StatusCode != http.StatusOK { 95 | if tc.isErr { 96 | return 97 | } 98 | bodyBytes, _ := io.ReadAll(resp.Body) 99 | t.Fatalf("response status code is not 200, got %d: %s", resp.StatusCode, string(bodyBytes)) 100 | } 101 | 102 | var body map[string]interface{} 103 | err = json.NewDecoder(resp.Body).Decode(&body) 104 | if err != nil { 105 | t.Fatalf("error parsing response body") 106 | } 107 | 108 | got, ok := body["result"].(string) 109 | if !ok { 110 | t.Fatalf("unable to find result in response body") 111 | } 112 | 113 | if got != tc.want { 114 | t.Fatalf("unexpected value: got %q, want %q", got, tc.want) 115 | } 116 | }) 117 | } 118 | } 119 | ``` -------------------------------------------------------------------------------- /docs/en/resources/tools/looker/looker-query-sql.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: "looker-query-sql" 3 | type: docs 4 | weight: 1 5 | description: > 6 | "looker-query-sql" generates a sql query using the Looker 7 | semantic model. 8 | aliases: 9 | - /resources/tools/looker-query-sql 10 | --- 11 | 12 | ## About 13 | 14 | The `looker-query-sql` generates a sql query using the Looker 15 | semantic model. 16 | 17 | It's compatible with the following sources: 18 | 19 | - [looker](../../sources/looker.md) 20 | 21 | `looker-query-sql` takes eight parameters: 22 | 23 | 1. the `model` 24 | 2. the `explore` 25 | 3. the `fields` list 26 | 4. an optional set of `filters` 27 | 5. an optional set of `pivots` 28 | 6. an optional set of `sorts` 29 | 7. an optional `limit` 30 | 8. an optional `tz` 31 | 32 | Starting in Looker v25.18, these queries can be identified in Looker's 33 | System Activity. In the History explore, use the field API Client Name 34 | to find MCP Toolbox queries. 35 | 36 | ## Example 37 | 38 | ```yaml 39 | tools: 40 | query_sql: 41 | kind: looker-query-sql 42 | source: looker-source 43 | description: | 44 | Query SQL Tool 45 | 46 | This tool is used to generate a sql query against the LookML model. The 47 | model, explore, and fields list must be specified. Pivots, 48 | filters and sorts are optional. 49 | 50 | The model can be found from the get_models tool. The explore 51 | can be found from the get_explores tool passing in the model. 52 | The fields can be found from the get_dimensions, get_measures, 53 | get_filters, and get_parameters tools, passing in the model 54 | and the explore. 55 | 56 | Provide a model_id and explore_name, then a list 57 | of fields. Optionally a list of pivots can be provided. 58 | The pivots must also be included in the fields list. 59 | 60 | Filters are provided as a map of {"field.id": "condition", 61 | "field.id2": "condition2", ...}. Do not put the field.id in 62 | quotes. Filter expressions can be found at 63 | https://cloud.google.com/looker/docs/filter-expressions. 64 | 65 | Sorts can be specified like [ "field.id desc 0" ]. 66 | 67 | An optional row limit can be added. If not provided the limit 68 | will default to 500. "-1" can be specified for unlimited. 69 | 70 | An optional query timezone can be added. The query_timezone to 71 | will default to that of the workstation where this MCP server 72 | is running, or Etc/UTC if that can't be determined. Not all 73 | models support custom timezones. 74 | 75 | The result of the query tool is the sql string. 76 | ``` 77 | 78 | ## Reference 79 | 80 | | **field** | **type** | **required** | **description** | 81 | |-------------|:------------------------------------------:|:------------:|--------------------------------------------------------------------------------------------------| 82 | | kind | string | true | Must be "looker-query-sql" | 83 | | source | string | true | Name of the source the SQL should execute on. | 84 | | description | string | true | Description of the tool that is passed to the LLM. | 85 | ``` -------------------------------------------------------------------------------- /docs/en/resources/tools/bigquery/bigquery-execute-sql.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: "bigquery-execute-sql" 3 | type: docs 4 | weight: 1 5 | description: > 6 | A "bigquery-execute-sql" tool executes a SQL statement against BigQuery. 7 | aliases: 8 | - /resources/tools/bigquery-execute-sql 9 | --- 10 | 11 | ## About 12 | 13 | A `bigquery-execute-sql` tool executes a SQL statement against BigQuery. 14 | It's compatible with the following sources: 15 | 16 | - [bigquery](../../sources/bigquery.md) 17 | 18 | `bigquery-execute-sql` accepts the following parameters: 19 | - **`sql`** (required): The GoogleSQL statement to execute. 20 | - **`dry_run`** (optional): If set to `true`, the query is validated but not run, 21 | returning information about the execution instead. Defaults to `false`. 22 | 23 | The behavior of this tool is influenced by the `writeMode` setting on its `bigquery` source: 24 | 25 | - **`allowed` (default):** All SQL statements are permitted. 26 | - **`blocked`:** Only `SELECT` statements are allowed. Any other type of statement (e.g., `INSERT`, `UPDATE`, `CREATE`) will be rejected. 27 | - **`protected`:** This mode enables session-based execution. `SELECT` statements can be used on all tables, while write operations are allowed only for the session's temporary dataset (e.g., `CREATE TEMP TABLE ...`). This prevents modifications to permanent datasets while allowing stateful, multi-step operations within a secure session. 28 | 29 | The tool's behavior is influenced by the `allowedDatasets` restriction on the 30 | `bigquery` source. Similar to `writeMode`, this setting provides an additional layer of security by controlling which datasets can be accessed: 31 | 32 | - **Without `allowedDatasets` restriction:** The tool can execute any valid GoogleSQL 33 | query. 34 | - **With `allowedDatasets` restriction:** Before execution, the tool performs a dry run 35 | to analyze the query. 36 | It will reject the query if it attempts to access any table outside the 37 | allowed `datasets` list. To enforce this restriction, the following operations 38 | are also disallowed: 39 | - **Dataset-level operations** (e.g., `CREATE SCHEMA`, `ALTER SCHEMA`). 40 | - **Unanalyzable operations** where the accessed tables cannot be determined 41 | statically (e.g., `EXECUTE IMMEDIATE`, `CREATE PROCEDURE`, `CALL`). 42 | 43 | > **Note:** This tool is intended for developer assistant workflows with human-in-the-loop and shouldn't be used for production agents. 44 | 45 | ## Example 46 | 47 | ```yaml 48 | tools: 49 | execute_sql_tool: 50 | kind: bigquery-execute-sql 51 | source: my-bigquery-source 52 | description: Use this tool to execute sql statement. 53 | ``` 54 | 55 | ## Reference 56 | 57 | | **field** | **type** | **required** | **description** | 58 | |-------------|:------------------------------------------:|:------------:|--------------------------------------------------------------------------------------------------| 59 | | kind | string | true | Must be "bigquery-execute-sql". | 60 | | source | string | true | Name of the source the SQL should execute on. | 61 | | description | string | true | Description of the tool that is passed to the LLM. | 62 | ``` -------------------------------------------------------------------------------- /.hugo/hugo.toml: -------------------------------------------------------------------------------- ```toml 1 | title = 'MCP Toolbox for Databases' 2 | relativeURLs = false 3 | 4 | languageCode = 'en-us' 5 | defaultContentLanguage = "en" 6 | defaultContentLanguageInSubdir = false 7 | 8 | enableGitInfo = true 9 | enableRobotsTXT = true 10 | 11 | ignoreFiles = ["quickstart/shared", "quickstart/python", "quickstart/js", "quickstart/go"] 12 | 13 | [languages] 14 | [languages.en] 15 | languageName ="English" 16 | weight = 1 17 | 18 | [module] 19 | proxy = "direct" 20 | [module.hugoVersion] 21 | extended = true 22 | min = "0.146.0" 23 | [[module.mounts]] 24 | source = "../docs/en" 25 | target = 'content' 26 | [[module.imports]] 27 | path = "github.com/google/docsy" 28 | disable = false 29 | [[module.imports]] 30 | path = "github.com/martignoni/hugo-notice" 31 | 32 | [params] 33 | description = "MCP Toolbox for Databases is an open source MCP server for databases. It enables you to develop tools easier, faster, and more securely by handling the complexities such as connection pooling, authentication, and more." 34 | copyright = "Google LLC" 35 | github_repo = "https://github.com/googleapis/genai-toolbox" 36 | github_project_repo = "https://github.com/googleapis/genai-toolbox" 37 | github_subdir = "docs" 38 | offlineSearch = true 39 | version_menu = "Releases" 40 | [params.ui] 41 | ul_show = 100 42 | showLightDarkModeMenu = true 43 | breadcrumb_disable = true 44 | sidebar_menu_foldable = true 45 | sidebar_menu_compact = false 46 | 47 | [[params.versions]] 48 | version = "Dev" 49 | url = "https://googleapis.github.io/genai-toolbox/dev/" 50 | 51 | # Add a new version block here before every release 52 | # The order of versions in this file is mirrored into the dropdown 53 | 54 | [[params.versions]] 55 | version = "v0.17.0" 56 | url = "https://googleapis.github.io/genai-toolbox/v0.17.0/" 57 | 58 | [[params.versions]] 59 | version = "v0.16.0" 60 | url = "https://googleapis.github.io/genai-toolbox/v0.16.0/" 61 | 62 | [[params.versions]] 63 | version = "v0.15.0" 64 | url = "https://googleapis.github.io/genai-toolbox/v0.15.0/" 65 | 66 | [[params.versions]] 67 | version = "v0.14.0" 68 | url = "https://googleapis.github.io/genai-toolbox/v0.14.0/" 69 | 70 | [[params.versions]] 71 | version = "v0.13.0" 72 | url = "https://googleapis.github.io/genai-toolbox/v0.13.0/" 73 | 74 | [[params.versions]] 75 | version = "v0.12.0" 76 | url = "https://googleapis.github.io/genai-toolbox/v0.12.0/" 77 | 78 | [[params.versions]] 79 | version = "v0.11.0" 80 | url = "https://googleapis.github.io/genai-toolbox/v0.11.0/" 81 | 82 | [[params.versions]] 83 | version = "v0.10.0" 84 | url = "https://googleapis.github.io/genai-toolbox/v0.10.0/" 85 | 86 | [[params.versions]] 87 | version = "v0.9.0" 88 | url = "https://googleapis.github.io/genai-toolbox/v0.9.0/" 89 | 90 | [[params.versions]] 91 | version = "v0.8.0" 92 | url = "https://googleapis.github.io/genai-toolbox/v0.8.0/" 93 | 94 | 95 | [[menu.main]] 96 | name = "GitHub" 97 | weight = 50 98 | url = "https://github.com/googleapis/genai-toolbox" 99 | pre = "<i class='fa-brands fa-github'></i>" 100 | 101 | [markup.goldmark.renderer] 102 | unsafe= true 103 | 104 | [markup.highlight] 105 | noClasses = false 106 | style = "tango" 107 | 108 | [outputFormats] 109 | [outputFormats.LLMS] 110 | mediaType = "text/plain" 111 | baseName = "llms" 112 | isPlainText = true 113 | root = true 114 | [outputFormats.LLMS-FULL] 115 | mediaType = "text/plain" 116 | baseName = "llms-full" 117 | isPlainText = true 118 | root = true 119 | [outputFormats.releases] 120 | baseName = 'releases' 121 | isPlainText = true 122 | mediaType = 'text/releases' 123 | 124 | [mediaTypes."text/releases"] 125 | suffixes = ["releases"] 126 | 127 | [outputs] 128 | home = ["HTML", "RSS", "LLMS", "LLMS-FULL", "releases"] 129 | ``` -------------------------------------------------------------------------------- /docs/en/resources/sources/oceanbase.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: "OceanBase" 3 | type: docs 4 | weight: 1 5 | description: > 6 | OceanBase is a distributed relational database that provides high availability, scalability, and compatibility with MySQL. 7 | --- 8 | 9 | ## About 10 | 11 | [OceanBase][oceanbase-docs] is a distributed relational database management 12 | system (RDBMS) that provides high availability, scalability, and strong 13 | consistency. It's designed to handle large-scale data processing and is 14 | compatible with MySQL, making it easy for developers to migrate from MySQL to 15 | OceanBase. 16 | 17 | [oceanbase-docs]: https://www.oceanbase.com/ 18 | 19 | ## Requirements 20 | 21 | ### Database User 22 | 23 | This source only uses standard authentication. You will need to create an 24 | OceanBase user to login to the database with. OceanBase supports 25 | MySQL-compatible user management syntax. 26 | 27 | ### Network Connectivity 28 | 29 | Ensure that your application can connect to the OceanBase cluster. OceanBase 30 | typically runs on ports 2881 (for MySQL protocol) or 3881 (for MySQL protocol 31 | with SSL). 32 | 33 | ## Example 34 | 35 | ```yaml 36 | sources: 37 | my-oceanbase-source: 38 | kind: oceanbase 39 | host: 127.0.0.1 40 | port: 2881 41 | database: my_db 42 | user: ${USER_NAME} 43 | password: ${PASSWORD} 44 | queryTimeout: 30s # Optional: query timeout duration 45 | ``` 46 | 47 | {{< notice tip >}} 48 | Use environment variable replacement with the format ${ENV_NAME} 49 | instead of hardcoding your secrets into the configuration file. 50 | {{< /notice >}} 51 | 52 | ## Reference 53 | 54 | | **field** | **type** | **required** | **description** | 55 | | ------------ | :------: | :----------: |-------------------------------------------------------------------------------------------------| 56 | | kind | string | true | Must be "oceanbase". | 57 | | host | string | true | IP address to connect to (e.g. "127.0.0.1"). | 58 | | port | string | true | Port to connect to (e.g. "2881"). | 59 | | database | string | true | Name of the OceanBase database to connect to (e.g. "my_db"). | 60 | | user | string | true | Name of the OceanBase user to connect as (e.g. "my-oceanbase-user"). | 61 | | password | string | true | Password of the OceanBase user (e.g. "my-password"). | 62 | | queryTimeout | string | false | Maximum time to wait for query execution (e.g. "30s", "2m"). By default, no timeout is applied. | 63 | 64 | ## Features 65 | 66 | ### MySQL Compatibility 67 | 68 | OceanBase is highly compatible with MySQL, supporting most MySQL SQL syntax, 69 | data types, and functions. This makes it easy to migrate existing MySQL 70 | applications to OceanBase. 71 | 72 | ### High Availability 73 | 74 | OceanBase provides automatic failover and data replication across multiple 75 | nodes, ensuring high availability and data durability. 76 | 77 | ### Scalability 78 | 79 | OceanBase can scale horizontally by adding more nodes to the cluster, making it 80 | suitable for large-scale applications. 81 | 82 | ### Strong Consistency 83 | 84 | OceanBase provides strong consistency guarantees, ensuring that all transactions 85 | are ACID compliant. ``` -------------------------------------------------------------------------------- /docs/en/resources/sources/clickhouse.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: "ClickHouse" 3 | type: docs 4 | weight: 1 5 | description: > 6 | ClickHouse is an open-source, OLTP database. 7 | 8 | --- 9 | 10 | ## About 11 | 12 | [ClickHouse][clickhouse-docs] is a fast, open-source, column-oriented database 13 | 14 | [clickhouse-docs]: https://clickhouse.com/docs 15 | 16 | ## Available Tools 17 | 18 | - [`clickhouse-execute-sql`](../tools/clickhouse/clickhouse-execute-sql.md) 19 | Execute parameterized SQL queries in ClickHouse with query logging. 20 | 21 | - [`clickhouse-sql`](../tools/clickhouse/clickhouse-sql.md) 22 | Execute SQL queries as prepared statements in ClickHouse. 23 | 24 | 25 | ## Requirements 26 | 27 | ### Database User 28 | 29 | This source uses standard ClickHouse authentication. You will need to [create a 30 | ClickHouse user][clickhouse-users] (or with [ClickHouse 31 | Cloud][clickhouse-cloud]) to connect to the database with. The user should have 32 | appropriate permissions for the operations you plan to perform. 33 | 34 | [clickhouse-cloud]: 35 | https://clickhouse.com/docs/getting-started/quick-start/cloud#connect-with-your-app 36 | [clickhouse-users]: https://clickhouse.com/docs/en/sql-reference/statements/create/user 37 | 38 | ### Network Access 39 | 40 | ClickHouse supports multiple protocols: 41 | 42 | - **HTTPS protocol** (default port 8443) - Secure HTTP access (default) 43 | - **HTTP protocol** (default port 8123) - Good for web-based access 44 | 45 | ## Example 46 | 47 | ### Secure Connection Example 48 | 49 | ```yaml 50 | sources: 51 | secure-clickhouse-source: 52 | kind: clickhouse 53 | host: clickhouse.example.com 54 | port: "8443" 55 | database: analytics 56 | user: ${CLICKHOUSE_USER} 57 | password: ${CLICKHOUSE_PASSWORD} 58 | protocol: https 59 | secure: true 60 | ``` 61 | 62 | ### HTTP Protocol Example 63 | 64 | ```yaml 65 | sources: 66 | http-clickhouse-source: 67 | kind: clickhouse 68 | host: localhost 69 | port: "8123" 70 | database: logs 71 | user: ${CLICKHOUSE_USER} 72 | password: ${CLICKHOUSE_PASSWORD} 73 | protocol: http 74 | secure: false 75 | ``` 76 | 77 | {{< notice tip >}} 78 | Use environment variable replacement with the format ${ENV_NAME} 79 | instead of hardcoding your secrets into the configuration file. 80 | {{< /notice >}} 81 | 82 | ## Reference 83 | 84 | | **field** | **type** | **required** | **description** | 85 | |-----------|:--------:|:------------:|-------------------------------------------------------------------------------------| 86 | | kind | string | true | Must be "clickhouse". | 87 | | host | string | true | IP address or hostname to connect to (e.g. "127.0.0.1" or "clickhouse.example.com") | 88 | | port | string | true | Port to connect to (e.g. "8443" for HTTPS, "8123" for HTTP) | 89 | | database | string | true | Name of the ClickHouse database to connect to (e.g. "my_database"). | 90 | | user | string | true | Name of the ClickHouse user to connect as (e.g. "analytics_user"). | 91 | | password | string | false | Password of the ClickHouse user (e.g. "my-password"). | 92 | | protocol | string | false | Connection protocol: "https" (default) or "http". | 93 | | secure | boolean | false | Whether to use a secure connection (TLS). Default: false. | 94 | ``` -------------------------------------------------------------------------------- /internal/tools/utility/wait/wait.go: -------------------------------------------------------------------------------- ```go 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package wait 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "time" 21 | 22 | yaml "github.com/goccy/go-yaml" 23 | "github.com/googleapis/genai-toolbox/internal/sources" 24 | "github.com/googleapis/genai-toolbox/internal/tools" 25 | ) 26 | 27 | const kind string = "wait" 28 | 29 | func init() { 30 | if !tools.Register(kind, newConfig) { 31 | panic(fmt.Sprintf("tool kind %q already registered", kind)) 32 | } 33 | } 34 | 35 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) { 36 | actual := Config{Name: name} 37 | if err := decoder.DecodeContext(ctx, &actual); err != nil { 38 | return nil, err 39 | } 40 | return actual, nil 41 | } 42 | 43 | type Config struct { 44 | Name string `yaml:"name" validate:"required"` 45 | Kind string `yaml:"kind" validate:"required"` 46 | Description string `yaml:"description" validate:"required"` 47 | Timeout string `yaml:"timeout" validate:"required"` 48 | AuthRequired []string `yaml:"authRequired"` 49 | } 50 | 51 | var _ tools.ToolConfig = Config{} 52 | 53 | func (cfg Config) ToolConfigKind() string { 54 | return kind 55 | } 56 | 57 | func (cfg Config) Initialize(_ map[string]sources.Source) (tools.Tool, error) { 58 | durationParameter := tools.NewStringParameter("duration", "The duration to wait for, specified as a string (e.g., '10s', '2m', '1h').") 59 | parameters := tools.Parameters{durationParameter} 60 | 61 | mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters) 62 | 63 | t := Tool{ 64 | Name: cfg.Name, 65 | Kind: kind, 66 | Parameters: parameters, 67 | manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired}, 68 | mcpManifest: mcpManifest, 69 | } 70 | return t, nil 71 | } 72 | 73 | // validate interface 74 | var _ tools.Tool = Tool{} 75 | 76 | type Tool struct { 77 | Name string 78 | Kind string 79 | Parameters tools.Parameters 80 | manifest tools.Manifest 81 | mcpManifest tools.McpManifest 82 | } 83 | 84 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { 85 | paramsMap := params.AsMap() 86 | 87 | durationStr, ok := paramsMap["duration"].(string) 88 | if !ok { 89 | return nil, fmt.Errorf("duration parameter is not a string") 90 | } 91 | 92 | totalDuration, err := time.ParseDuration(durationStr) 93 | if err != nil { 94 | return nil, fmt.Errorf("invalid duration format: %w", err) 95 | } 96 | 97 | time.Sleep(totalDuration) 98 | 99 | return fmt.Sprintf("Wait for %v completed successfully.", totalDuration), nil 100 | } 101 | 102 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { 103 | return tools.ParseParams(t.Parameters, data, claims) 104 | } 105 | 106 | func (t Tool) Manifest() tools.Manifest { 107 | return t.manifest 108 | } 109 | 110 | func (t Tool) McpManifest() tools.McpManifest { 111 | return t.mcpManifest 112 | } 113 | 114 | func (t Tool) Authorized(verifiedAuthServices []string) bool { 115 | return true 116 | } 117 | 118 | func (t Tool) RequiresClientAuthorization() bool { 119 | return false 120 | } 121 | ``` -------------------------------------------------------------------------------- /docs/en/resources/sources/cassandra.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: "Cassandra" 3 | type: docs 4 | weight: 1 5 | description: > 6 | Apache Cassandra is a NoSQL distributed database known for its horizontal scalability, distributed architecture, and flexible schema definition. 7 | --- 8 | 9 | ## About 10 | 11 | [Apache Cassandra][cassandra-docs] is a NoSQL distributed database. By design, NoSQL databases are lightweight, open-source, non-relational, and largely distributed. Counted among their strengths are horizontal scalability, distributed architectures, and a flexible approach to schema definition. 12 | 13 | [cassandra-docs]: https://cassandra.apache.org/ 14 | 15 | ## Available Tools 16 | 17 | - [`cassandra-cql`](../tools/cassandra/cassandra-cql.md) 18 | Run parameterized CQL queries in Cassandra. 19 | 20 | 21 | ## Example 22 | 23 | ```yaml 24 | sources: 25 | my-cassandra-source: 26 | kind: cassandra 27 | hosts: 28 | - 127.0.0.1 29 | keyspace: my_keyspace 30 | protoVersion: 4 31 | username: ${USER_NAME} 32 | password: ${PASSWORD} 33 | caPath: /path/to/ca.crt # Optional: path to CA certificate 34 | certPath: /path/to/client.crt # Optional: path to client certificate 35 | keyPath: /path/to/client.key # Optional: path to client key 36 | enableHostVerification: true # Optional: enable host verification 37 | ``` 38 | 39 | {{< notice tip >}} 40 | Use environment variable replacement with the format ${ENV_NAME} 41 | instead of hardcoding your secrets into the configuration file. 42 | {{< /notice >}} 43 | 44 | ## Reference 45 | 46 | | **field** | **type** | **required** | **description** | 47 | |------------------------|:---------:|:------------:|-------------------------------------------------------------------------------------------------------| 48 | | kind | string | true | Must be "cassandra". | 49 | | hosts | string[] | true | List of IP addresses to connect to (e.g., ["192.168.1.1:9042", "192.168.1.2:9042","192.168.1.3:9042"]). The default port is 9042 if not specified. | 50 | | keyspace | string | true | Name of the Cassandra keyspace to connect to (e.g., "my_keyspace"). | 51 | | protoVersion | integer | false | Protocol version for the Cassandra connection (e.g., 4). | 52 | | username | string | false | Name of the Cassandra user to connect as (e.g., "my-cassandra-user"). | 53 | | password | string | false | Password of the Cassandra user (e.g., "my-password"). | 54 | | caPath | string | false | Path to the CA certificate for SSL/TLS (e.g., "/path/to/ca.crt"). | 55 | | certPath | string | false | Path to the client certificate for SSL/TLS (e.g., "/path/to/client.crt"). | 56 | | keyPath | string | false | Path to the client key for SSL/TLS (e.g., "/path/to/client.key"). | 57 | | enableHostVerification | boolean | false | Enable host verification for SSL/TLS (e.g., true). By default, host verification is disabled. | 58 | ``` -------------------------------------------------------------------------------- /docs/en/resources/tools/cloudmonitoring/cloud-monitoring-query-prometheus.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: cloud-monitoring-query-prometheus 3 | type: docs 4 | weight: 1 5 | description: The "cloud-monitoring-query-prometheus" tool fetches time series metrics for a project using a given prometheus query. 6 | --- 7 | 8 | The `cloud-monitoring-query-prometheus` tool fetches timeseries metrics data 9 | from Google Cloud Monitoring for a project using a given prometheus query. 10 | 11 | ## About 12 | 13 | The `cloud-monitoring-query-prometheus` tool allows you to query all metrics 14 | available in Google Cloud Monitoring using the Prometheus Query Language 15 | (PromQL). 16 | It's compatible with any of the following sources: 17 | 18 | - [cloud-monitoring](../../sources/cloud-monitoring.md) 19 | 20 | ## Prerequisites 21 | 22 | To use this tool, you need to have the following IAM role on your Google Cloud 23 | project: 24 | 25 | - `roles/monitoring.viewer` 26 | 27 | ## Arguments 28 | 29 | | Name | Type | Description | 30 | |-------------|--------|----------------------------------| 31 | | `projectId` | string | The Google Cloud project ID. | 32 | | `query` | string | The Prometheus query to execute. | 33 | 34 | ## Use Cases 35 | 36 | - **Ad-hoc analysis:** Quickly investigate performance issues by executing 37 | direct promql queries for a database instance. 38 | - **Prebuilt Configs:** Use the already added prebuilt tools mentioned in 39 | prebuilt-tools.md to query the databases system/query level metrics. 40 | 41 | Here are some common use cases for the `cloud-monitoring-query-prometheus` tool: 42 | 43 | - **Monitoring resource utilization:** Track CPU, memory, and disk usage for 44 | your database instance (Can use the [prebuilt 45 | tools](../../../reference/prebuilt-tools.md)). 46 | - **Monitoring query performance:** Monitor latency, execution_time, wait_time 47 | for database instance or even for the queries running (Can use the [prebuilt 48 | tools](../../../reference/prebuilt-tools.md)). 49 | - **System Health:** Get the overall system health for the database instance 50 | (Can use the [prebuilt tools](../../../reference/prebuilt-tools.md)). 51 | 52 | ## Examples 53 | 54 | Here are some examples of how to use the `cloud-monitoring-query-prometheus` 55 | tool. 56 | 57 | 58 | ```yaml 59 | tools: 60 | get_wait_time_metrics: 61 | kind: cloud-monitoring-query-prometheus 62 | source: cloud-monitoring-source 63 | description: | 64 | This tool fetches system wait time information for AlloyDB cluster, instance. Get the `projectID`, `clusterID` and `instanceID` from the user intent. To use this tool, you must provide the Google Cloud `projectId` and a PromQL `query`. 65 | Generate `query` using these metric details: 66 | metric: `alloydb.googleapis.com/instance/postgresql/wait_time`, monitored_resource: `alloydb.googleapis.com/Instance`. labels: `cluster_id`, `instance_id`, `wait_event_type`, `wait_event_name`. 67 | Basic time series example promql query: `avg_over_time({"__name__"="alloydb.googleapis.com/instance/postgresql/wait_time","monitored_resource"="alloydb.googleapis.com/Instance","instance_id"="alloydb-instance"}[5m])` 68 | ``` 69 | 70 | ## Reference 71 | | **field** | **type** | **required** | **description** | 72 | |-------------|:--------:|:------------:|------------------------------------------------------| 73 | | kind | string | true | Must be cloud-monitoring-query-prometheus. | 74 | | source | string | true | The name of an `cloud-monitoring` source. | 75 | | description | string | true | Description of the tool that is passed to the agent. | 76 | ``` -------------------------------------------------------------------------------- /internal/sources/valkey/valkey.go: -------------------------------------------------------------------------------- ```go 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package valkey 15 | 16 | import ( 17 | "context" 18 | "fmt" 19 | "log" 20 | 21 | "github.com/goccy/go-yaml" 22 | "github.com/googleapis/genai-toolbox/internal/sources" 23 | "github.com/valkey-io/valkey-go" 24 | "go.opentelemetry.io/otel/trace" 25 | ) 26 | 27 | const SourceKind string = "valkey" 28 | 29 | // validate interface 30 | var _ sources.SourceConfig = Config{} 31 | 32 | func init() { 33 | if !sources.Register(SourceKind, newConfig) { 34 | panic(fmt.Sprintf("source kind %q already registered", SourceKind)) 35 | } 36 | } 37 | 38 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) { 39 | actual := Config{Name: name} 40 | if err := decoder.DecodeContext(ctx, &actual); err != nil { 41 | return nil, err 42 | } 43 | return actual, nil 44 | } 45 | 46 | type Config struct { 47 | Name string `yaml:"name" validate:"required"` 48 | Kind string `yaml:"kind" validate:"required"` 49 | Address []string `yaml:"address" validate:"required"` 50 | Username string `yaml:"username"` 51 | Password string `yaml:"password"` 52 | Database int `yaml:"database"` 53 | UseGCPIAM bool `yaml:"useGCPIAM"` 54 | DisableCache bool `yaml:"disableCache"` 55 | } 56 | 57 | func (r Config) SourceConfigKind() string { 58 | return SourceKind 59 | } 60 | 61 | func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) { 62 | 63 | client, err := initValkeyClient(ctx, r) 64 | if err != nil { 65 | return nil, fmt.Errorf("error initializing Valkey client: %s", err) 66 | } 67 | s := &Source{ 68 | Name: r.Name, 69 | Kind: SourceKind, 70 | Client: client, 71 | } 72 | return s, nil 73 | } 74 | 75 | func initValkeyClient(ctx context.Context, r Config) (valkey.Client, error) { 76 | var authFn func(valkey.AuthCredentialsContext) (valkey.AuthCredentials, error) 77 | if r.UseGCPIAM { 78 | // Pass in an access token getter fn for IAM auth 79 | authFn = func(valkey.AuthCredentialsContext) (valkey.AuthCredentials, error) { 80 | token, err := sources.GetIAMAccessToken(ctx) 81 | creds := valkey.AuthCredentials{Username: "default", Password: token} 82 | if err != nil { 83 | return creds, err 84 | } 85 | return creds, nil 86 | } 87 | } 88 | 89 | client, err := valkey.NewClient(valkey.ClientOption{ 90 | InitAddress: r.Address, 91 | SelectDB: r.Database, 92 | Username: r.Username, 93 | Password: r.Password, 94 | AuthCredentialsFn: authFn, 95 | DisableCache: r.DisableCache, 96 | }) 97 | 98 | if err != nil { 99 | log.Fatalf("error creating Valkey client: %v", err) 100 | } 101 | 102 | // Ping the server to check connectivity 103 | pingCmd := client.B().Ping().Build() 104 | _, err = client.Do(ctx, pingCmd).ToString() 105 | if err != nil { 106 | log.Fatalf("Failed to execute PING command: %v", err) 107 | } 108 | return client, nil 109 | } 110 | 111 | var _ sources.Source = &Source{} 112 | 113 | type Source struct { 114 | Name string `yaml:"name"` 115 | Kind string `yaml:"kind"` 116 | Client valkey.Client 117 | } 118 | 119 | func (s *Source) SourceKind() string { 120 | return SourceKind 121 | } 122 | 123 | func (s *Source) ValkeyClient() valkey.Client { 124 | return s.Client 125 | } 126 | ``` -------------------------------------------------------------------------------- /.github/labels.yaml: -------------------------------------------------------------------------------- ```yaml 1 | # Copyright 2024 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | - name: duplicate 16 | color: ededed 17 | description: "" 18 | 19 | - name: 'type: bug' 20 | color: db4437 21 | description: Error or flaw in code with unintended results or allowing sub-optimal 22 | usage patterns. 23 | - name: 'type: cleanup' 24 | color: c5def5 25 | description: An internal cleanup or hygiene concern. 26 | - name: 'type: docs' 27 | color: 0000A0 28 | description: Improvement to the documentation for an API. 29 | - name: 'type: feature request' 30 | color: c5def5 31 | description: ‘Nice-to-have’ improvement, new feature or different behavior or design. 32 | - name: 'type: process' 33 | color: c5def5 34 | description: A process-related concern. May include testing, release, or the like. 35 | - name: 'type: question' 36 | color: c5def5 37 | description: Request for information or clarification. 38 | 39 | - name: 'priority: p0' 40 | color: b60205 41 | description: Highest priority. Critical issue. P0 implies highest priority. 42 | - name: 'priority: p1' 43 | color: ffa03e 44 | description: Important issue which blocks shipping the next release. Will be fixed 45 | prior to next release. 46 | - name: 'priority: p2' 47 | color: fef2c0 48 | description: Moderately-important priority. Fix may not be included in next release. 49 | - name: 'priority: p3' 50 | color: ffffc7 51 | description: Desirable enhancement or fix. May not be included in next release. 52 | 53 | - name: 'do not merge' 54 | color: d93f0b 55 | description: Indicates a pull request not ready for merge, due to either quality 56 | or timing. 57 | 58 | - name: 'autorelease: pending' 59 | color: ededed 60 | description: Release please needs to do its work on this. 61 | - name: 'autorelease: triggered' 62 | color: ededed 63 | description: Release please has triggered a release for this. 64 | - name: 'autorelease: tagged' 65 | color: ededed 66 | description: Release please has completed a release for this. 67 | 68 | - name: 'blunderbuss: assign' 69 | color: 3DED97 70 | description: Have blunderbuss assign this to someone new. 71 | 72 | - name: 'tests: run' 73 | color: 3DED97 74 | description: Label to trigger Github Action tests. 75 | 76 | - name: 'docs: deploy-preview' 77 | color: BFDADC 78 | description: Label to trigger Github Action docs preview. 79 | 80 | - name: 'status: help wanted' 81 | color: 8befd7 82 | description: 'Status: Unplanned work open to contributions from the community.' 83 | - name: 'status: feedback wanted' 84 | color: 8befd7 85 | description: 'Status: waiting for feedback from community or issue author.' 86 | 87 | - name: 'status: waiting for response' 88 | color: 8befd7 89 | description: 'Status: reviewer is awaiting feedback or responses from the author before proceeding.' 90 | 91 | - name: 'release candidate' 92 | color: 32CD32 93 | description: 'Use label to signal PR should be included in the next release.' 94 | 95 | # Product Labels 96 | - name: 'product: bigquery' 97 | color: 5065c7 98 | description: 'Product: Assigned to the BigQuery team.' 99 | # Product Labels 100 | - name: 'product: looker' 101 | color: 5065c7 102 | description: 'Product: Assigned to the Looker team.' 103 | ``` -------------------------------------------------------------------------------- /docs/en/resources/tools/neo4j/neo4j-cypher.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: "neo4j-cypher" 3 | type: docs 4 | weight: 1 5 | description: > 6 | A "neo4j-cypher" tool executes a pre-defined cypher statement against a Neo4j 7 | database. 8 | aliases: 9 | - /resources/tools/neo4j-cypher 10 | --- 11 | 12 | ## About 13 | 14 | A `neo4j-cypher` tool executes a pre-defined Cypher statement against a Neo4j 15 | database. It's compatible with any of the following sources: 16 | 17 | - [neo4j](../../sources/neo4j.md) 18 | 19 | The specified Cypher statement is executed as a [parameterized 20 | statement][neo4j-parameters], and specified parameters will be used according to 21 | their name: e.g. `$id`. 22 | 23 | > **Note:** This tool uses parameterized queries to prevent SQL injections. 24 | > Query parameters can be used as substitutes for arbitrary expressions. 25 | > Parameters cannot be used as substitutes for identifiers, column names, table 26 | > names, or other parts of the query. 27 | 28 | [neo4j-parameters]: 29 | https://neo4j.com/docs/cypher-manual/current/syntax/parameters/ 30 | 31 | ## Example 32 | 33 | ```yaml 34 | tools: 35 | search_movies_by_actor: 36 | kind: neo4j-cypher 37 | source: my-neo4j-movies-instance 38 | statement: | 39 | MATCH (m:Movie)<-[:ACTED_IN]-(p:Person) 40 | WHERE p.name = $name AND m.year > $year 41 | RETURN m.title, m.year 42 | LIMIT 10 43 | description: | 44 | Use this tool to get a list of movies for a specific actor and a given minimum release year. 45 | Takes a full actor name, e.g. "Tom Hanks" and a year e.g 1993 and returns a list of movie titles and release years. 46 | Do NOT use this tool with a movie title. Do NOT guess an actor name, Do NOT guess a year. 47 | A actor name is a fully qualified name with first and last name separated by a space. 48 | For example, if given "Hanks, Tom" the actor name is "Tom Hanks". 49 | If the tool returns more than one option choose the most recent movies. 50 | Example: 51 | {{ 52 | "name": "Meg Ryan", 53 | "year": 1993 54 | }} 55 | Example: 56 | {{ 57 | "name": "Clint Eastwood", 58 | "year": 2000 59 | }} 60 | parameters: 61 | - name: name 62 | type: string 63 | description: Full actor name, "firstname lastname" 64 | - name: year 65 | type: integer 66 | description: 4 digit number starting in 1900 up to the current year 67 | ``` 68 | 69 | ## Reference 70 | 71 | | **field** | **type** | **required** | **description** | 72 | |-------------|:------------------------------------------:|:------------:|-------------------------------------------------------------------------------------------------| 73 | | kind | string | true | Must be "neo4j-cypher". | 74 | | source | string | true | Name of the source the Cypher query should execute on. | 75 | | description | string | true | Description of the tool that is passed to the LLM. | 76 | | statement | string | true | Cypher statement to execute | 77 | | parameters | [parameters](../#specifying-parameters) | false | List of [parameters](../#specifying-parameters) that will be used with the Cypher statement. | 78 | ``` -------------------------------------------------------------------------------- /internal/sources/cloudsqladmin/cloud_sql_admin_test.go: -------------------------------------------------------------------------------- ```go 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cloudsqladmin_test 16 | 17 | import ( 18 | "testing" 19 | 20 | yaml "github.com/goccy/go-yaml" 21 | "github.com/google/go-cmp/cmp" 22 | "github.com/googleapis/genai-toolbox/internal/server" 23 | "github.com/googleapis/genai-toolbox/internal/sources" 24 | "github.com/googleapis/genai-toolbox/internal/sources/cloudsqladmin" 25 | "github.com/googleapis/genai-toolbox/internal/testutils" 26 | ) 27 | 28 | func TestParseFromYamlCloudSQLAdmin(t *testing.T) { 29 | t.Parallel() 30 | tcs := []struct { 31 | desc string 32 | in string 33 | want server.SourceConfigs 34 | }{ 35 | { 36 | desc: "basic example", 37 | in: ` 38 | sources: 39 | my-cloud-sql-admin-instance: 40 | kind: cloud-sql-admin 41 | `, 42 | want: map[string]sources.SourceConfig{ 43 | "my-cloud-sql-admin-instance": cloudsqladmin.Config{ 44 | Name: "my-cloud-sql-admin-instance", 45 | Kind: cloudsqladmin.SourceKind, 46 | UseClientOAuth: false, 47 | }, 48 | }, 49 | }, 50 | { 51 | desc: "use client auth example", 52 | in: ` 53 | sources: 54 | my-cloud-sql-admin-instance: 55 | kind: cloud-sql-admin 56 | useClientOAuth: true 57 | `, 58 | want: map[string]sources.SourceConfig{ 59 | "my-cloud-sql-admin-instance": cloudsqladmin.Config{ 60 | Name: "my-cloud-sql-admin-instance", 61 | Kind: cloudsqladmin.SourceKind, 62 | UseClientOAuth: true, 63 | }, 64 | }, 65 | }, 66 | } 67 | for _, tc := range tcs { 68 | tc := tc 69 | t.Run(tc.desc, func(t *testing.T) { 70 | t.Parallel() 71 | got := struct { 72 | Sources server.SourceConfigs `yaml:"sources"` 73 | }{} 74 | // Parse contents 75 | err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got) 76 | if err != nil { 77 | t.Fatalf("unable to unmarshal: %s", err) 78 | } 79 | if !cmp.Equal(tc.want, got.Sources) { 80 | t.Fatalf("incorrect parse: want %v, got %v", tc.want, got.Sources) 81 | } 82 | }) 83 | } 84 | } 85 | 86 | func TestFailParseFromYaml(t *testing.T) { 87 | t.Parallel() 88 | tcs := []struct { 89 | desc string 90 | in string 91 | err string 92 | }{ 93 | { 94 | desc: "extra field", 95 | in: ` 96 | sources: 97 | my-cloud-sql-admin-instance: 98 | kind: cloud-sql-admin 99 | project: test-project 100 | `, 101 | err: `unable to parse source "my-cloud-sql-admin-instance" as "cloud-sql-admin": [2:1] unknown field "project" 102 | 1 | kind: cloud-sql-admin 103 | > 2 | project: test-project 104 | ^ 105 | `, 106 | }, 107 | { 108 | desc: "missing required field", 109 | in: ` 110 | sources: 111 | my-cloud-sql-admin-instance: 112 | useClientOAuth: true 113 | `, 114 | err: "missing 'kind' field for source \"my-cloud-sql-admin-instance\"", 115 | }, 116 | } 117 | for _, tc := range tcs { 118 | tc := tc 119 | t.Run(tc.desc, func(t *testing.T) { 120 | t.Parallel() 121 | got := struct { 122 | Sources server.SourceConfigs `yaml:"sources"` 123 | }{} 124 | // Parse contents 125 | err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got) 126 | if err == nil { 127 | t.Fatalf("expect parsing to fail") 128 | } 129 | errStr := err.Error() 130 | if errStr != tc.err { 131 | t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err) 132 | } 133 | }) 134 | } 135 | } 136 | ``` -------------------------------------------------------------------------------- /docs/en/resources/sources/bigtable.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: "Bigtable" 3 | type: docs 4 | weight: 1 5 | description: > 6 | Bigtable is a low-latency NoSQL database service for machine learning, operational analytics, and user-facing operations. It's a wide-column, key-value store that can scale to billions of rows and thousands of columns. With Bigtable, you can replicate your data to regions across the world for high availability and data resiliency. 7 | 8 | --- 9 | 10 | # Bigtable Source 11 | 12 | [Bigtable][bigtable-docs] is a low-latency NoSQL database service for machine 13 | learning, operational analytics, and user-facing operations. It's a wide-column, 14 | key-value store that can scale to billions of rows and thousands of columns. 15 | With Bigtable, you can replicate your data to regions across the world for high 16 | availability and data resiliency. 17 | 18 | If you are new to Bigtable, you can try to [create an instance and write data 19 | with the cbt CLI][bigtable-quickstart-with-cli]. 20 | 21 | You can use [GoogleSQL statements][bigtable-googlesql] to query your Bigtable 22 | data. GoogleSQL is an ANSI-compliant structured query language (SQL) that is 23 | also implemented for other Google Cloud services. SQL queries are handled by 24 | cluster nodes in the same way as NoSQL data requests. Therefore, the same best 25 | practices apply when creating SQL queries to run against your Bigtable data, 26 | such as avoiding full table scans or complex filters. 27 | 28 | [bigtable-docs]: https://cloud.google.com/bigtable/docs 29 | [bigtable-quickstart-with-cli]: 30 | https://cloud.google.com/bigtable/docs/create-instance-write-data-cbt-cli 31 | 32 | [bigtable-googlesql]: 33 | https://cloud.google.com/bigtable/docs/googlesql-overview 34 | 35 | ## Available Tools 36 | 37 | - [`bigtable-sql`](../tools/bigtable/bigtable-sql.md) 38 | Run SQL-like queries over Bigtable rows. 39 | 40 | ## Requirements 41 | 42 | ### IAM Permissions 43 | 44 | Bigtable uses [Identity and Access Management (IAM)][iam-overview] to control 45 | user and group access to Bigtable resources at the project, instance, table, and 46 | backup level. Toolbox will use your [Application Default Credentials (ADC)][adc] 47 | to authorize and authenticate when interacting with [Bigtable][bigtable-docs]. 48 | 49 | In addition to [setting the ADC for your server][set-adc], you need to ensure 50 | the IAM identity has been given the correct IAM permissions for the query 51 | provided. See [Apply IAM roles][grant-permissions] for more information on 52 | applying IAM permissions and roles to an identity. 53 | 54 | [iam-overview]: https://cloud.google.com/bigtable/docs/access-control 55 | [adc]: https://cloud.google.com/docs/authentication#adc 56 | [set-adc]: https://cloud.google.com/docs/authentication/provide-credentials-adc 57 | [grant-permissions]: https://cloud.google.com/bigtable/docs/access-control#iam-management-instance 58 | 59 | ## Example 60 | 61 | ```yaml 62 | sources: 63 | my-bigtable-source: 64 | kind: "bigtable" 65 | project: "my-project-id" 66 | instance: "test-instance" 67 | ``` 68 | 69 | ## Reference 70 | 71 | | **field** | **type** | **required** | **description** | 72 | |-----------|:--------:|:------------:|-------------------------------------------------------------------------------| 73 | | kind | string | true | Must be "bigtable". | 74 | | project | string | true | Id of the GCP project that the cluster was created in (e.g. "my-project-id"). | 75 | | instance | string | true | Name of the Bigtable instance. | 76 | ``` -------------------------------------------------------------------------------- /docs/en/resources/sources/mysql.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: "MySQL" 3 | type: docs 4 | weight: 1 5 | description: > 6 | MySQL is a relational database management system that stores and manages data. 7 | --- 8 | 9 | ## About 10 | 11 | [MySQL][mysql-docs] is a relational database management system (RDBMS) that 12 | stores and manages data. It's a popular choice for developers because of its 13 | reliability, performance, and ease of use. 14 | 15 | [mysql-docs]: https://www.mysql.com/ 16 | 17 | ## Available Tools 18 | 19 | - [`mysql-sql`](../tools/mysql/mysql-sql.md) 20 | Execute pre-defined prepared SQL queries in MySQL. 21 | 22 | - [`mysql-execute-sql`](../tools/mysql/mysql-execute-sql.md) 23 | Run parameterized SQL queries in MySQL. 24 | 25 | - [`mysql-list-active-queries`](../tools/mysql/mysql-list-active-queries.md) 26 | List active queries in MySQL. 27 | 28 | - [`mysql-list-tables`](../tools/mysql/mysql-list-tables.md) 29 | List tables in a MySQL database. 30 | 31 | - [`mysql-list-tables-missing-unique-indexes`](../tools/mysql/mysql-list-tables-missing-unique-indexes.md) 32 | List tables in a MySQL database that do not have primary or unique indices. 33 | 34 | - [`mysql-list-table-fragmentation`](../tools/mysql/mysql-list-table-fragmentation.md) 35 | List table fragmentation in MySQL tables. 36 | 37 | ## Requirements 38 | 39 | ### Database User 40 | 41 | This source only uses standard authentication. You will need to [create a 42 | MySQL user][mysql-users] to login to the database with. 43 | 44 | [mysql-users]: https://dev.mysql.com/doc/refman/8.4/en/user-names.html 45 | 46 | ## Example 47 | 48 | ```yaml 49 | sources: 50 | my-mysql-source: 51 | kind: mysql 52 | host: 127.0.0.1 53 | port: 3306 54 | database: my_db 55 | user: ${USER_NAME} 56 | password: ${PASSWORD} 57 | # Optional TLS and other driver parameters. For example, enable preferred TLS: 58 | # queryParams: 59 | # tls: preferred 60 | queryTimeout: 30s # Optional: query timeout duration 61 | ``` 62 | 63 | {{< notice tip >}} 64 | Use environment variable replacement with the format ${ENV_NAME} 65 | instead of hardcoding your secrets into the configuration file. 66 | {{< /notice >}} 67 | 68 | ## Reference 69 | 70 | | **field** | **type** | **required** | **description** | 71 | | ------------ | :------: | :----------: | ----------------------------------------------------------------------------------------------- | 72 | | kind | string | true | Must be "mysql". | 73 | | host | string | true | IP address to connect to (e.g. "127.0.0.1"). | 74 | | port | string | true | Port to connect to (e.g. "3306"). | 75 | | database | string | true | Name of the MySQL database to connect to (e.g. "my_db"). | 76 | | user | string | true | Name of the MySQL user to connect as (e.g. "my-mysql-user"). | 77 | | password | string | true | Password of the MySQL user (e.g. "my-password"). | 78 | | queryTimeout | string | false | Maximum time to wait for query execution (e.g. "30s", "2m"). By default, no timeout is applied. | 79 | | queryParams | map<string,string> | false | Arbitrary DSN parameters passed to the driver (e.g. `tls: preferred`, `charset: utf8mb4`). Useful for enabling TLS or other connection options. | 80 | ```