This is page 13 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/firestore/firestoregetrules/firestoregetrules.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 firestoregetrules 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | 21 | yaml "github.com/goccy/go-yaml" 22 | "github.com/googleapis/genai-toolbox/internal/sources" 23 | firestoreds "github.com/googleapis/genai-toolbox/internal/sources/firestore" 24 | "github.com/googleapis/genai-toolbox/internal/tools" 25 | "google.golang.org/api/firebaserules/v1" 26 | ) 27 | 28 | const kind string = "firestore-get-rules" 29 | 30 | func init() { 31 | if !tools.Register(kind, newConfig) { 32 | panic(fmt.Sprintf("tool kind %q already registered", kind)) 33 | } 34 | } 35 | 36 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) { 37 | actual := Config{Name: name} 38 | if err := decoder.DecodeContext(ctx, &actual); err != nil { 39 | return nil, err 40 | } 41 | return actual, nil 42 | } 43 | 44 | type compatibleSource interface { 45 | FirebaseRulesClient() *firebaserules.Service 46 | GetProjectId() string 47 | GetDatabaseId() string 48 | } 49 | 50 | // validate compatible sources are still compatible 51 | var _ compatibleSource = &firestoreds.Source{} 52 | 53 | var compatibleSources = [...]string{firestoreds.SourceKind} 54 | 55 | type Config struct { 56 | Name string `yaml:"name" validate:"required"` 57 | Kind string `yaml:"kind" validate:"required"` 58 | Source string `yaml:"source" validate:"required"` 59 | Description string `yaml:"description" validate:"required"` 60 | AuthRequired []string `yaml:"authRequired"` 61 | } 62 | 63 | // validate interface 64 | var _ tools.ToolConfig = Config{} 65 | 66 | func (cfg Config) ToolConfigKind() string { 67 | return kind 68 | } 69 | 70 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { 71 | // verify source exists 72 | rawS, ok := srcs[cfg.Source] 73 | if !ok { 74 | return nil, fmt.Errorf("no source named %q configured", cfg.Source) 75 | } 76 | 77 | // verify the source is compatible 78 | s, ok := rawS.(compatibleSource) 79 | if !ok { 80 | return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources) 81 | } 82 | 83 | // No parameters needed for this tool 84 | parameters := tools.Parameters{} 85 | 86 | mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters) 87 | 88 | // finish tool setup 89 | t := Tool{ 90 | Name: cfg.Name, 91 | Kind: kind, 92 | Parameters: parameters, 93 | AuthRequired: cfg.AuthRequired, 94 | RulesClient: s.FirebaseRulesClient(), 95 | ProjectId: s.GetProjectId(), 96 | DatabaseId: s.GetDatabaseId(), 97 | manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired}, 98 | mcpManifest: mcpManifest, 99 | } 100 | return t, nil 101 | } 102 | 103 | // validate interface 104 | var _ tools.Tool = Tool{} 105 | 106 | type Tool struct { 107 | Name string `yaml:"name"` 108 | Kind string `yaml:"kind"` 109 | AuthRequired []string `yaml:"authRequired"` 110 | Parameters tools.Parameters `yaml:"parameters"` 111 | 112 | RulesClient *firebaserules.Service 113 | ProjectId string 114 | DatabaseId string 115 | manifest tools.Manifest 116 | mcpManifest tools.McpManifest 117 | } 118 | 119 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { 120 | // Get the latest release for Firestore 121 | releaseName := fmt.Sprintf("projects/%s/releases/cloud.firestore/%s", t.ProjectId, t.DatabaseId) 122 | release, err := t.RulesClient.Projects.Releases.Get(releaseName).Context(ctx).Do() 123 | if err != nil { 124 | return nil, fmt.Errorf("failed to get latest Firestore release: %w", err) 125 | } 126 | 127 | if release.RulesetName == "" { 128 | return nil, fmt.Errorf("no active Firestore rules were found in project '%s' and database '%s'", t.ProjectId, t.DatabaseId) 129 | } 130 | 131 | // Get the ruleset content 132 | ruleset, err := t.RulesClient.Projects.Rulesets.Get(release.RulesetName).Context(ctx).Do() 133 | if err != nil { 134 | return nil, fmt.Errorf("failed to get ruleset content: %w", err) 135 | } 136 | 137 | if ruleset.Source == nil || len(ruleset.Source.Files) == 0 { 138 | return nil, fmt.Errorf("no rules files found in ruleset") 139 | } 140 | 141 | return ruleset, nil 142 | } 143 | 144 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { 145 | return tools.ParseParams(t.Parameters, data, claims) 146 | } 147 | 148 | func (t Tool) Manifest() tools.Manifest { 149 | return t.manifest 150 | } 151 | 152 | func (t Tool) McpManifest() tools.McpManifest { 153 | return t.mcpManifest 154 | } 155 | 156 | func (t Tool) Authorized(verifiedAuthServices []string) bool { 157 | return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices) 158 | } 159 | 160 | func (t Tool) RequiresClientAuthorization() bool { 161 | return false 162 | } 163 | ``` -------------------------------------------------------------------------------- /internal/tools/postgres/postgreslistinstalledextensions/postgreslistinstalledextensions.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 postgreslistinstalledextensions 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | 21 | yaml "github.com/goccy/go-yaml" 22 | "github.com/googleapis/genai-toolbox/internal/sources" 23 | "github.com/googleapis/genai-toolbox/internal/sources/alloydbpg" 24 | "github.com/googleapis/genai-toolbox/internal/sources/cloudsqlpg" 25 | "github.com/googleapis/genai-toolbox/internal/sources/postgres" 26 | "github.com/googleapis/genai-toolbox/internal/tools" 27 | "github.com/jackc/pgx/v5/pgxpool" 28 | ) 29 | 30 | const kind string = "postgres-list-installed-extensions" 31 | 32 | const listAvailableExtensionsQuery = ` 33 | SELECT 34 | e.extname AS name, 35 | e.extversion AS version, 36 | n.nspname AS schema, 37 | pg_get_userbyid(e.extowner) AS owner, 38 | c.description AS description 39 | FROM 40 | pg_catalog.pg_extension e 41 | LEFT JOIN 42 | pg_catalog.pg_namespace n 43 | ON 44 | n.oid = e.extnamespace 45 | LEFT JOIN 46 | pg_catalog.pg_description c 47 | ON 48 | c.objoid = e.oid 49 | AND c.classoid = 'pg_catalog.pg_extension'::pg_catalog.regclass 50 | ORDER BY 1; 51 | ` 52 | 53 | func init() { 54 | if !tools.Register(kind, newConfig) { 55 | panic(fmt.Sprintf("tool kind %q already registered", kind)) 56 | } 57 | } 58 | 59 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) { 60 | actual := Config{Name: name} 61 | if err := decoder.DecodeContext(ctx, &actual); err != nil { 62 | return nil, err 63 | } 64 | return actual, nil 65 | } 66 | 67 | type compatibleSource interface { 68 | PostgresPool() *pgxpool.Pool 69 | } 70 | 71 | // validate compatible sources are still compatible 72 | var _ compatibleSource = &alloydbpg.Source{} 73 | var _ compatibleSource = &cloudsqlpg.Source{} 74 | var _ compatibleSource = &postgres.Source{} 75 | 76 | var compatibleSources = [...]string{alloydbpg.SourceKind, cloudsqlpg.SourceKind, postgres.SourceKind} 77 | 78 | type Config struct { 79 | Name string `yaml:"name" validate:"required"` 80 | Kind string `yaml:"kind" validate:"required"` 81 | Source string `yaml:"source" validate:"required"` 82 | Description string `yaml:"description" validate:"required"` 83 | AuthRequired []string `yaml:"authRequired"` 84 | } 85 | 86 | // validate interface 87 | var _ tools.ToolConfig = Config{} 88 | 89 | func (cfg Config) ToolConfigKind() string { 90 | return kind 91 | } 92 | 93 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { 94 | // verify source exists 95 | rawS, ok := srcs[cfg.Source] 96 | if !ok { 97 | return nil, fmt.Errorf("no source named %q configured", cfg.Source) 98 | } 99 | 100 | // verify the source is compatible 101 | s, ok := rawS.(compatibleSource) 102 | if !ok { 103 | return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources) 104 | } 105 | 106 | parameters := tools.Parameters{} 107 | mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters) 108 | 109 | // finish tool setup 110 | t := Tool{ 111 | Name: cfg.Name, 112 | Kind: cfg.Kind, 113 | AuthRequired: cfg.AuthRequired, 114 | Pool: s.PostgresPool(), 115 | manifest: tools.Manifest{ 116 | Description: cfg.Description, 117 | Parameters: parameters.Manifest(), 118 | AuthRequired: cfg.AuthRequired, 119 | }, 120 | mcpManifest: mcpManifest, 121 | } 122 | return t, nil 123 | } 124 | 125 | // validate interface 126 | var _ tools.Tool = Tool{} 127 | 128 | type Tool struct { 129 | Name string `yaml:"name"` 130 | Kind string `yaml:"kind"` 131 | AuthRequired []string `yaml:"authRequired"` 132 | Pool *pgxpool.Pool 133 | manifest tools.Manifest 134 | mcpManifest tools.McpManifest 135 | } 136 | 137 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { 138 | results, err := t.Pool.Query(ctx, listAvailableExtensionsQuery) 139 | if err != nil { 140 | return nil, fmt.Errorf("unable to execute query: %w", err) 141 | } 142 | 143 | fields := results.FieldDescriptions() 144 | 145 | var out []any 146 | for results.Next() { 147 | v, err := results.Values() 148 | if err != nil { 149 | return nil, fmt.Errorf("unable to parse row: %w", err) 150 | } 151 | vMap := make(map[string]any) 152 | for i, f := range fields { 153 | vMap[f.Name] = v[i] 154 | } 155 | out = append(out, vMap) 156 | } 157 | 158 | return out, nil 159 | } 160 | 161 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { 162 | return tools.ParamValues{}, nil 163 | } 164 | 165 | func (t Tool) Manifest() tools.Manifest { 166 | return t.manifest 167 | } 168 | 169 | func (t Tool) McpManifest() tools.McpManifest { 170 | return t.mcpManifest 171 | } 172 | 173 | func (t Tool) Authorized(verifiedAuthServices []string) bool { 174 | return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices) 175 | } 176 | 177 | func (t Tool) RequiresClientAuthorization() bool { 178 | return false 179 | } 180 | ``` -------------------------------------------------------------------------------- /docs/en/resources/tools/couchbase/couchbase-sql.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: "couchbase-sql" 3 | type: docs 4 | weight: 1 5 | description: > 6 | A "couchbase-sql" tool executes a pre-defined SQL statement against a Couchbase 7 | database. 8 | aliases: 9 | - /resources/tools/couchbase-sql 10 | --- 11 | 12 | ## About 13 | 14 | A `couchbase-sql` tool executes a pre-defined SQL statement against a Couchbase 15 | database. It's compatible with any of the following sources: 16 | 17 | - [couchbase](../../sources/couchbase.md) 18 | 19 | The specified SQL statement is executed as a parameterized statement, and specified 20 | parameters will be used according to their name: e.g. `$id`. 21 | 22 | ## Example 23 | 24 | > **Note:** This tool uses parameterized queries to prevent SQL injections. 25 | > Query parameters can be used as substitutes for arbitrary expressions. 26 | > Parameters cannot be used as substitutes for identifiers, column names, table 27 | > names, or other parts of the query. 28 | 29 | ```yaml 30 | tools: 31 | search_products_by_category: 32 | kind: couchbase-sql 33 | source: my-couchbase-instance 34 | statement: | 35 | SELECT p.name, p.price, p.description 36 | FROM products p 37 | WHERE p.category = $category AND p.price < $max_price 38 | ORDER BY p.price DESC 39 | LIMIT 10 40 | description: | 41 | Use this tool to get a list of products for a specific category under a maximum price. 42 | Takes a category name, e.g. "Electronics" and a maximum price e.g 500 and returns a list of product names, prices, and descriptions. 43 | Do NOT use this tool with invalid category names. Do NOT guess a category name, Do NOT guess a price. 44 | Example: 45 | {{ 46 | "category": "Electronics", 47 | "max_price": 500 48 | }} 49 | Example: 50 | {{ 51 | "category": "Furniture", 52 | "max_price": 1000 53 | }} 54 | parameters: 55 | - name: category 56 | type: string 57 | description: Product category name 58 | - name: max_price 59 | type: integer 60 | description: Maximum price (positive integer) 61 | ``` 62 | 63 | ### Example with Template Parameters 64 | 65 | > **Note:** This tool allows direct modifications to the SQL statement, 66 | > including identifiers, column names, and table names. **This makes it more 67 | > vulnerable to SQL injections**. Using basic parameters only (see above) is 68 | > recommended for performance and safety reasons. For more details, please check 69 | > [templateParameters](..#template-parameters). 70 | 71 | ```yaml 72 | tools: 73 | list_table: 74 | kind: couchbase-sql 75 | source: my-couchbase-instance 76 | statement: | 77 | SELECT * FROM {{.tableName}}; 78 | description: | 79 | Use this tool to list all information from a specific table. 80 | Example: 81 | {{ 82 | "tableName": "flights", 83 | }} 84 | templateParameters: 85 | - name: tableName 86 | type: string 87 | description: Table to select from 88 | ``` 89 | 90 | ## Reference 91 | 92 | | **field** | **type** | **required** | **description** | 93 | |--------------------|:------------------------------------------------:|:------------:|--------------------------------------------------------------------------------------------------------------------------------------------| 94 | | kind | string | true | Must be "couchbase-sql". | 95 | | source | string | true | Name of the source the SQL query should execute on. | 96 | | description | string | true | Description of the tool that is passed to the LLM. | 97 | | statement | string | true | SQL statement to execute | 98 | | parameters | [parameters](../#specifying-parameters) | false | List of [parameters](../#specifying-parameters) that will be used with the SQL statement. | 99 | | templateParameters | [templateParameters](..#template-parameters) | false | List of [templateParameters](..#template-parameters) that will be inserted into the SQL statement before executing prepared statement. | 100 | | authRequired | array[string] | false | List of auth services that are required to use this tool. | 101 | ``` -------------------------------------------------------------------------------- /internal/util/util.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 util 15 | 16 | import ( 17 | "bytes" 18 | "context" 19 | "encoding/json" 20 | "fmt" 21 | "io" 22 | "strings" 23 | 24 | "github.com/go-playground/validator/v10" 25 | yaml "github.com/goccy/go-yaml" 26 | "github.com/googleapis/genai-toolbox/internal/log" 27 | "github.com/googleapis/genai-toolbox/internal/telemetry" 28 | ) 29 | 30 | // DecodeJSON decodes a given reader into an interface using the json decoder. 31 | func DecodeJSON(r io.Reader, v interface{}) error { 32 | defer io.Copy(io.Discard, r) //nolint:errcheck 33 | d := json.NewDecoder(r) 34 | // specify JSON numbers should get parsed to json.Number instead of float64 by default. 35 | // This prevents loss between floats/ints. 36 | d.UseNumber() 37 | return d.Decode(v) 38 | } 39 | 40 | // ConvertNumbers traverses an interface and converts all json.Number 41 | // instances to int64 or float64. 42 | func ConvertNumbers(data any) (any, error) { 43 | switch v := data.(type) { 44 | // If it's a map, recursively convert the values. 45 | case map[string]any: 46 | for key, val := range v { 47 | convertedVal, err := ConvertNumbers(val) 48 | if err != nil { 49 | return nil, err 50 | } 51 | v[key] = convertedVal 52 | } 53 | return v, nil 54 | 55 | // If it's a slice, recursively convert the elements. 56 | case []any: 57 | for i, val := range v { 58 | convertedVal, err := ConvertNumbers(val) 59 | if err != nil { 60 | return nil, err 61 | } 62 | v[i] = convertedVal 63 | } 64 | return v, nil 65 | 66 | // If it's a json.Number, convert it to float or int 67 | case json.Number: 68 | // Check for a decimal point to decide the type. 69 | if strings.Contains(v.String(), ".") { 70 | return v.Float64() 71 | } 72 | return v.Int64() 73 | 74 | // For all other types, return them as is. 75 | default: 76 | return data, nil 77 | } 78 | } 79 | 80 | var _ yaml.InterfaceUnmarshalerContext = &DelayedUnmarshaler{} 81 | 82 | // DelayedUnmarshaler is struct that saves the provided unmarshal function 83 | // passed to UnmarshalYAML so it can be re-used later once the target interface 84 | // is known. 85 | type DelayedUnmarshaler struct { 86 | unmarshal func(interface{}) error 87 | } 88 | 89 | func (d *DelayedUnmarshaler) UnmarshalYAML(ctx context.Context, unmarshal func(interface{}) error) error { 90 | d.unmarshal = unmarshal 91 | return nil 92 | } 93 | 94 | func (d *DelayedUnmarshaler) Unmarshal(v interface{}) error { 95 | if d.unmarshal == nil { 96 | return fmt.Errorf("nothing to unmarshal") 97 | } 98 | return d.unmarshal(v) 99 | } 100 | 101 | type contextKey string 102 | 103 | // userAgentKey is the key used to store userAgent within context 104 | const userAgentKey contextKey = "userAgent" 105 | 106 | // WithUserAgent adds a user agent into the context as a value 107 | func WithUserAgent(ctx context.Context, versionString string) context.Context { 108 | userAgent := "genai-toolbox/" + versionString 109 | return context.WithValue(ctx, userAgentKey, userAgent) 110 | } 111 | 112 | // UserAgentFromContext retrieves the user agent or return an error 113 | func UserAgentFromContext(ctx context.Context) (string, error) { 114 | if ua := ctx.Value(userAgentKey); ua != nil { 115 | return ua.(string), nil 116 | } else { 117 | return "", fmt.Errorf("unable to retrieve user agent") 118 | } 119 | } 120 | 121 | func NewStrictDecoder(v interface{}) (*yaml.Decoder, error) { 122 | b, err := yaml.Marshal(v) 123 | if err != nil { 124 | return nil, fmt.Errorf("fail to marshal %q: %w", v, err) 125 | } 126 | 127 | dec := yaml.NewDecoder( 128 | bytes.NewReader(b), 129 | yaml.Strict(), 130 | yaml.Validator(validator.New()), 131 | ) 132 | return dec, nil 133 | } 134 | 135 | // loggerKey is the key used to store logger within context 136 | const loggerKey contextKey = "logger" 137 | 138 | // WithLogger adds a logger into the context as a value 139 | func WithLogger(ctx context.Context, logger log.Logger) context.Context { 140 | return context.WithValue(ctx, loggerKey, logger) 141 | } 142 | 143 | // LoggerFromContext retrieves the logger or return an error 144 | func LoggerFromContext(ctx context.Context) (log.Logger, error) { 145 | if logger, ok := ctx.Value(loggerKey).(log.Logger); ok { 146 | return logger, nil 147 | } 148 | return nil, fmt.Errorf("unable to retrieve logger") 149 | } 150 | 151 | const instrumentationKey contextKey = "instrumentation" 152 | 153 | // WithInstrumentation adds an instrumentation into the context as a value 154 | func WithInstrumentation(ctx context.Context, instrumentation *telemetry.Instrumentation) context.Context { 155 | return context.WithValue(ctx, instrumentationKey, instrumentation) 156 | } 157 | 158 | // InstrumentationFromContext retrieves the instrumentation or return an error 159 | func InstrumentationFromContext(ctx context.Context) (*telemetry.Instrumentation, error) { 160 | if instrumentation, ok := ctx.Value(instrumentationKey).(*telemetry.Instrumentation); ok { 161 | return instrumentation, nil 162 | } 163 | return nil, fmt.Errorf("unable to retrieve instrumentation") 164 | } 165 | ``` -------------------------------------------------------------------------------- /docs/en/resources/tools/bigquery/bigquery-sql.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: "bigquery-sql" 3 | type: docs 4 | weight: 1 5 | description: > 6 | A "bigquery-sql" tool executes a pre-defined SQL statement. 7 | aliases: 8 | - /resources/tools/bigquery-sql 9 | --- 10 | 11 | ## About 12 | 13 | A `bigquery-sql` tool executes a pre-defined SQL statement. It's compatible with 14 | the following sources: 15 | 16 | - [bigquery](../../sources/bigquery.md) 17 | 18 | The behavior of this tool is influenced by the `writeMode` setting on its `bigquery` source: 19 | 20 | - **`allowed` (default) and `blocked`:** These modes do not impose any restrictions on the `bigquery-sql` tool. The pre-defined SQL statement will be executed as-is. 21 | - **`protected`:** This mode enables session-based execution. The tool will operate within the same BigQuery session as other tools using the same source, allowing it to interact with temporary resources like `TEMP` tables created within that session. 22 | 23 | ### GoogleSQL 24 | 25 | BigQuery uses [GoogleSQL][bigquery-googlesql] for querying data. The integration 26 | with Toolbox supports this dialect. The specified SQL statement is executed, and 27 | parameters can be inserted into the query. BigQuery supports both named parameters 28 | (e.g., `@name`) and positional parameters (`?`), but they cannot be mixed in the 29 | same query. 30 | 31 | [bigquery-googlesql]: https://cloud.google.com/bigquery/docs/reference/standard-sql/ 32 | 33 | ## Example 34 | 35 | > **Note:** This tool uses [parameterized 36 | > queries](https://cloud.google.com/bigquery/docs/parameterized-queries) to 37 | > prevent SQL injections. Query parameters can be used as substitutes for 38 | > arbitrary expressions. Parameters cannot be used as substitutes for 39 | > identifiers, column names, table names, or other parts of the query. 40 | 41 | ```yaml 42 | tools: 43 | # Example: Querying a user table in BigQuery 44 | search_users_bq: 45 | kind: bigquery-sql 46 | source: my-bigquery-source 47 | statement: | 48 | SELECT 49 | id, 50 | name, 51 | email 52 | FROM 53 | `my-project.my-dataset.users` 54 | WHERE 55 | id = @id OR email = @email; 56 | description: | 57 | Use this tool to get information for a specific user. 58 | Takes an id number or a name and returns info on the user. 59 | 60 | Example: 61 | {{ 62 | "id": 123, 63 | "name": "Alice", 64 | }} 65 | parameters: 66 | - name: id 67 | type: integer 68 | description: User ID 69 | - name: email 70 | type: string 71 | description: Email address of the user 72 | ``` 73 | 74 | ### Example with Template Parameters 75 | 76 | > **Note:** This tool allows direct modifications to the SQL statement, 77 | > including identifiers, column names, and table names. **This makes it more 78 | > vulnerable to SQL injections**. Using basic parameters only (see above) is 79 | > recommended for performance and safety reasons. For more details, please check 80 | > [templateParameters](../#template-parameters). 81 | 82 | ```yaml 83 | tools: 84 | list_table: 85 | kind: bigquery-sql 86 | source: my-bigquery-source 87 | statement: | 88 | SELECT * FROM {{.tableName}}; 89 | description: | 90 | Use this tool to list all information from a specific table. 91 | Example: 92 | {{ 93 | "tableName": "flights", 94 | }} 95 | templateParameters: 96 | - name: tableName 97 | type: string 98 | description: Table to select from 99 | ``` 100 | 101 | ## Reference 102 | 103 | | **field** | **type** | **required** | **description** | 104 | |--------------------|:------------------------------------------------:|:------------:|--------------------------------------------------------------------------------------------------------------------------------------------| 105 | | kind | string | true | Must be "bigquery-sql". | 106 | | source | string | true | Name of the source the GoogleSQL should execute on. | 107 | | description | string | true | Description of the tool that is passed to the LLM. | 108 | | statement | string | true | The GoogleSQL statement to execute. | 109 | | parameters | [parameters](../#specifying-parameters) | false | List of [parameters](../#specifying-parameters) that will be inserted into the SQL statement. | 110 | | templateParameters | [templateParameters](../#template-parameters) | false | List of [templateParameters](../#template-parameters) that will be inserted into the SQL statement before executing prepared statement. | 111 | ``` -------------------------------------------------------------------------------- /internal/server/web_test.go: -------------------------------------------------------------------------------- ```go 1 | package server 2 | 3 | import ( 4 | "io" 5 | "net/http" 6 | "net/http/httptest" 7 | "net/url" 8 | "strings" 9 | "testing" 10 | 11 | "github.com/go-chi/chi/v5" 12 | "github.com/go-goquery/goquery" 13 | ) 14 | 15 | // TestWebEndpoint tests the routes defined in webRouter mounted under /ui. 16 | func TestWebEndpoint(t *testing.T) { 17 | mainRouter := chi.NewRouter() 18 | webR, err := webRouter() 19 | if err != nil { 20 | t.Fatalf("Failed to create webRouter: %v", err) 21 | } 22 | mainRouter.Mount("/ui", webR) 23 | 24 | ts := httptest.NewServer(mainRouter) 25 | defer ts.Close() 26 | 27 | testCases := []struct { 28 | name string 29 | path string 30 | wantStatus int 31 | wantContentType string 32 | wantPageTitle string 33 | }{ 34 | { 35 | name: "web index page", 36 | path: "/ui", 37 | wantStatus: http.StatusOK, 38 | wantContentType: "text/html", 39 | wantPageTitle: "Toolbox UI", 40 | }, 41 | { 42 | name: "web index page with trailing slash", 43 | path: "/ui/", 44 | wantStatus: http.StatusOK, 45 | wantContentType: "text/html", 46 | wantPageTitle: "Toolbox UI", 47 | }, 48 | { 49 | name: "web tools page", 50 | path: "/ui/tools", 51 | wantStatus: http.StatusOK, 52 | wantContentType: "text/html", 53 | wantPageTitle: "Tools View", 54 | }, 55 | { 56 | name: "web tools page with trailing slash", 57 | path: "/ui/tools/", 58 | wantStatus: http.StatusOK, 59 | wantContentType: "text/html", 60 | wantPageTitle: "Tools View", 61 | }, 62 | { 63 | name: "web toolsets page", 64 | path: "/ui/toolsets", 65 | wantStatus: http.StatusOK, 66 | wantContentType: "text/html", 67 | wantPageTitle: "Toolsets View", 68 | }, 69 | { 70 | name: "web toolsets page with trailing slash", 71 | path: "/ui/toolsets/", 72 | wantStatus: http.StatusOK, 73 | wantContentType: "text/html", 74 | wantPageTitle: "Toolsets View", 75 | }, 76 | } 77 | 78 | for _, tc := range testCases { 79 | t.Run(tc.name, func(t *testing.T) { 80 | reqURL := ts.URL + tc.path 81 | req, err := http.NewRequest(http.MethodGet, reqURL, nil) 82 | if err != nil { 83 | t.Fatalf("Failed to create request: %v", err) 84 | } 85 | 86 | client := ts.Client() 87 | resp, err := client.Do(req) 88 | if err != nil { 89 | t.Fatalf("Failed to send request: %v", err) 90 | } 91 | defer resp.Body.Close() 92 | 93 | if resp.StatusCode != tc.wantStatus { 94 | body, _ := io.ReadAll(resp.Body) 95 | t.Fatalf("Unexpected status code for %s: got %d, want %d, body: %s", tc.path, resp.StatusCode, tc.wantStatus, string(body)) 96 | } 97 | 98 | contentType := resp.Header.Get("Content-Type") 99 | if !strings.HasPrefix(contentType, tc.wantContentType) { 100 | t.Errorf("Unexpected Content-Type header for %s: got %s, want prefix %s", tc.path, contentType, tc.wantContentType) 101 | } 102 | 103 | body, err := io.ReadAll(resp.Body) 104 | if err != nil { 105 | t.Fatalf("Failed to read response body: %v", err) 106 | } 107 | 108 | doc, err := goquery.NewDocumentFromReader(strings.NewReader(string(body))) 109 | if err != nil { 110 | t.Fatalf("Failed to parse HTML: %v", err) 111 | } 112 | 113 | gotPageTitle := doc.Find("title").Text() 114 | if gotPageTitle != tc.wantPageTitle { 115 | t.Errorf("Unexpected page title for %s: got %q, want %q", tc.path, gotPageTitle, tc.wantPageTitle) 116 | } 117 | 118 | pageURL := resp.Request.URL 119 | verifyLinkedResources(t, ts, pageURL, doc) 120 | }) 121 | } 122 | } 123 | 124 | // verifyLinkedResources checks that resources linked in the HTML are served correctly. 125 | func verifyLinkedResources(t *testing.T, ts *httptest.Server, pageURL *url.URL, doc *goquery.Document) { 126 | t.Helper() 127 | 128 | selectors := map[string]string{ 129 | "stylesheet": "link[rel=stylesheet]", 130 | "script": "script[src]", 131 | } 132 | 133 | attrMap := map[string]string{ 134 | "stylesheet": "href", 135 | "script": "src", 136 | } 137 | 138 | foundResource := false 139 | for resourceType, selector := range selectors { 140 | doc.Find(selector).Each(func(i int, s *goquery.Selection) { 141 | foundResource = true 142 | attrName := attrMap[resourceType] 143 | resourcePath, exists := s.Attr(attrName) 144 | if !exists || resourcePath == "" { 145 | t.Errorf("Resource element %s is missing attribute %s on page %s", selector, attrName, pageURL.String()) 146 | return 147 | } 148 | 149 | // Resolve the URL relative to the page URL 150 | resURL, err := url.Parse(resourcePath) 151 | if err != nil { 152 | t.Errorf("Failed to parse resource path %q on page %s: %v", resourcePath, pageURL.String(), err) 153 | return 154 | } 155 | absoluteResourceURL := pageURL.ResolveReference(resURL) 156 | 157 | // Skip external hosts 158 | if absoluteResourceURL.Host != pageURL.Host { 159 | t.Logf("Skipping resource on different host: %s", absoluteResourceURL.String()) 160 | return 161 | } 162 | 163 | resp, err := ts.Client().Get(absoluteResourceURL.String()) 164 | if err != nil { 165 | t.Errorf("Failed to GET %s resource %s: %v", resourceType, absoluteResourceURL.String(), err) 166 | return 167 | } 168 | defer resp.Body.Close() 169 | 170 | if resp.StatusCode != http.StatusOK { 171 | t.Errorf("Resource %s %s: expected status OK (200), but got %d", resourceType, absoluteResourceURL.String(), resp.StatusCode) 172 | } 173 | }) 174 | } 175 | 176 | if !foundResource { 177 | t.Logf("No stylesheet or script resources found to check on page %s", pageURL.String()) 178 | } 179 | } 180 | ``` -------------------------------------------------------------------------------- /internal/tools/alloydb/alloydblistusers/alloydblistusers.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 alloydblistusers 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | 21 | yaml "github.com/goccy/go-yaml" 22 | "github.com/googleapis/genai-toolbox/internal/sources" 23 | alloydbadmin "github.com/googleapis/genai-toolbox/internal/sources/alloydbadmin" 24 | "github.com/googleapis/genai-toolbox/internal/tools" 25 | ) 26 | 27 | const kind string = "alloydb-list-users" 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 | // Configuration for the list-users tool. 44 | type Config struct { 45 | Name string `yaml:"name" validate:"required"` 46 | Kind string `yaml:"kind" validate:"required"` 47 | Source string `yaml:"source" validate:"required"` 48 | Description string `yaml:"description"` 49 | AuthRequired []string `yaml:"authRequired"` 50 | BaseURL string `yaml:"baseURL"` 51 | } 52 | 53 | // validate interface 54 | var _ tools.ToolConfig = Config{} 55 | 56 | // ToolConfigKind returns the kind of the tool. 57 | func (cfg Config) ToolConfigKind() string { 58 | return kind 59 | } 60 | 61 | // Initialize initializes the tool from the configuration. 62 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { 63 | rawS, ok := srcs[cfg.Source] 64 | if !ok { 65 | return nil, fmt.Errorf("source %q not found", cfg.Source) 66 | } 67 | 68 | s, ok := rawS.(*alloydbadmin.Source) 69 | if !ok { 70 | return nil, fmt.Errorf("invalid source for %q tool: source kind must be `%s`", kind, alloydbadmin.SourceKind) 71 | } 72 | 73 | allParameters := tools.Parameters{ 74 | tools.NewStringParameter("project", "The GCP project ID."), 75 | tools.NewStringParameter("location", "The location of the cluster (e.g., 'us-central1')."), 76 | tools.NewStringParameter("cluster", "The ID of the cluster to list users from."), 77 | } 78 | paramManifest := allParameters.Manifest() 79 | 80 | description := cfg.Description 81 | if description == "" { 82 | description = "Lists all AlloyDB users in a given project, location and cluster." 83 | } 84 | mcpManifest := tools.GetMcpManifest(cfg.Name, description, cfg.AuthRequired, allParameters) 85 | 86 | return Tool{ 87 | Name: cfg.Name, 88 | Kind: kind, 89 | Source: s, 90 | AllParams: allParameters, 91 | manifest: tools.Manifest{Description: description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired}, 92 | mcpManifest: mcpManifest, 93 | }, nil 94 | } 95 | 96 | // Tool represents the list-users tool. 97 | type Tool struct { 98 | Name string `yaml:"name"` 99 | Kind string `yaml:"kind"` 100 | Description string `yaml:"description"` 101 | 102 | Source *alloydbadmin.Source 103 | AllParams tools.Parameters `yaml:"allParams"` 104 | 105 | manifest tools.Manifest 106 | mcpManifest tools.McpManifest 107 | } 108 | 109 | // Invoke executes the tool's logic. 110 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { 111 | paramsMap := params.AsMap() 112 | 113 | project, ok := paramsMap["project"].(string) 114 | if !ok { 115 | return nil, fmt.Errorf("invalid or missing 'project' parameter; expected a string") 116 | } 117 | location, ok := paramsMap["location"].(string) 118 | if !ok { 119 | return nil, fmt.Errorf("invalid 'location' parameter; expected a string") 120 | } 121 | cluster, ok := paramsMap["cluster"].(string) 122 | if !ok { 123 | return nil, fmt.Errorf("invalid 'cluster' parameter; expected a string") 124 | } 125 | 126 | service, err := t.Source.GetService(ctx, string(accessToken)) 127 | if err != nil { 128 | return nil, err 129 | } 130 | 131 | urlString := fmt.Sprintf("projects/%s/locations/%s/clusters/%s", project, location, cluster) 132 | 133 | resp, err := service.Projects.Locations.Clusters.Users.List(urlString).Do() 134 | if err != nil { 135 | return nil, fmt.Errorf("error listing AlloyDB users: %w", err) 136 | } 137 | 138 | return resp, nil 139 | } 140 | 141 | // ParseParams parses the parameters for the tool. 142 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { 143 | return tools.ParseParams(t.AllParams, data, claims) 144 | } 145 | 146 | // Manifest returns the tool's manifest. 147 | func (t Tool) Manifest() tools.Manifest { 148 | return t.manifest 149 | } 150 | 151 | // McpManifest returns the tool's MCP manifest. 152 | func (t Tool) McpManifest() tools.McpManifest { 153 | return t.mcpManifest 154 | } 155 | 156 | // Authorized checks if the tool is authorized. 157 | func (t Tool) Authorized(verifiedAuthServices []string) bool { 158 | return true 159 | } 160 | 161 | func (t Tool) RequiresClientAuthorization() bool { 162 | return t.Source.UseClientAuthorization() 163 | } 164 | ``` -------------------------------------------------------------------------------- /internal/tools/mongodb/mongodbinsertmany/mongodbinsertmany.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 mongodbinsertmany 15 | 16 | import ( 17 | "context" 18 | "errors" 19 | "fmt" 20 | 21 | "github.com/goccy/go-yaml" 22 | "github.com/googleapis/genai-toolbox/internal/sources" 23 | mongosrc "github.com/googleapis/genai-toolbox/internal/sources/mongodb" 24 | "github.com/googleapis/genai-toolbox/internal/tools" 25 | "go.mongodb.org/mongo-driver/bson" 26 | "go.mongodb.org/mongo-driver/mongo" 27 | "go.mongodb.org/mongo-driver/mongo/options" 28 | ) 29 | 30 | const kind string = "mongodb-insert-many" 31 | 32 | const paramDataKey = "data" 33 | 34 | func init() { 35 | if !tools.Register(kind, newConfig) { 36 | panic(fmt.Sprintf("tool kind %q already registered", kind)) 37 | } 38 | } 39 | 40 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, 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 | Source string `yaml:"source" validate:"required"` 52 | AuthRequired []string `yaml:"authRequired" validate:"required"` 53 | Description string `yaml:"description" validate:"required"` 54 | Database string `yaml:"database" validate:"required"` 55 | Collection string `yaml:"collection" validate:"required"` 56 | Canonical bool `yaml:"canonical" validate:"required"` //i want to force the user to choose 57 | } 58 | 59 | // validate interface 60 | var _ tools.ToolConfig = Config{} 61 | 62 | func (cfg Config) ToolConfigKind() string { 63 | return kind 64 | } 65 | 66 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { 67 | // verify source exists 68 | rawS, ok := srcs[cfg.Source] 69 | if !ok { 70 | return nil, fmt.Errorf("no source named %q configured", cfg.Source) 71 | } 72 | 73 | // verify the source is compatible 74 | s, ok := rawS.(*mongosrc.Source) 75 | if !ok { 76 | return nil, fmt.Errorf("invalid source for %q tool: source kind must be `mongodb`", kind) 77 | } 78 | 79 | dataParam := tools.NewStringParameterWithRequired(paramDataKey, "the JSON payload to insert, should be a JSON array of documents", true) 80 | 81 | allParameters := tools.Parameters{dataParam} 82 | 83 | // Create Toolbox manifest 84 | paramManifest := allParameters.Manifest() 85 | 86 | if paramManifest == nil { 87 | paramManifest = make([]tools.ParameterManifest, 0) 88 | } 89 | 90 | // Create MCP manifest 91 | mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, allParameters) 92 | // finish tool setup 93 | return Tool{ 94 | Name: cfg.Name, 95 | Kind: kind, 96 | AuthRequired: cfg.AuthRequired, 97 | Collection: cfg.Collection, 98 | Canonical: cfg.Canonical, 99 | PayloadParams: allParameters, 100 | database: s.Client.Database(cfg.Database), 101 | manifest: tools.Manifest{Description: cfg.Description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired}, 102 | mcpManifest: mcpManifest, 103 | }, nil 104 | } 105 | 106 | // validate interface 107 | var _ tools.Tool = Tool{} 108 | 109 | type Tool struct { 110 | Name string `yaml:"name"` 111 | Kind string `yaml:"kind"` 112 | AuthRequired []string `yaml:"authRequired"` 113 | Description string `yaml:"description"` 114 | Collection string `yaml:"collection"` 115 | Canonical bool `yaml:"canonical" validation:"required"` //i want to force the user to choose 116 | PayloadParams tools.Parameters 117 | 118 | database *mongo.Database 119 | manifest tools.Manifest 120 | mcpManifest tools.McpManifest 121 | } 122 | 123 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { 124 | if len(params) == 0 { 125 | return nil, errors.New("no input found") 126 | } 127 | 128 | paramsMap := params.AsMap() 129 | 130 | var jsonData, ok = paramsMap[paramDataKey].(string) 131 | if !ok { 132 | return nil, errors.New("no input found") 133 | } 134 | 135 | var data = []any{} 136 | err := bson.UnmarshalExtJSON([]byte(jsonData), t.Canonical, &data) 137 | if err != nil { 138 | return nil, err 139 | } 140 | 141 | res, err := t.database.Collection(t.Collection).InsertMany(ctx, data, options.InsertMany()) 142 | if err != nil { 143 | return nil, err 144 | } 145 | 146 | return res.InsertedIDs, nil 147 | } 148 | 149 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { 150 | return tools.ParseParams(t.PayloadParams, data, claims) 151 | } 152 | 153 | func (t Tool) Manifest() tools.Manifest { 154 | return t.manifest 155 | } 156 | 157 | func (t Tool) McpManifest() tools.McpManifest { 158 | return t.mcpManifest 159 | } 160 | 161 | func (t Tool) Authorized(verifiedAuthServices []string) bool { 162 | return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices) 163 | } 164 | 165 | func (t Tool) RequiresClientAuthorization() bool { 166 | return false 167 | } 168 | ``` -------------------------------------------------------------------------------- /docs/en/resources/tools/trino/trino-sql.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: "trino-sql" 3 | type: docs 4 | weight: 1 5 | description: > 6 | A "trino-sql" tool executes a pre-defined SQL statement against a Trino 7 | database. 8 | aliases: 9 | - /resources/tools/trino-sql 10 | --- 11 | 12 | ## About 13 | 14 | A `trino-sql` tool executes a pre-defined SQL statement against a Trino 15 | database. It's compatible with any of the following sources: 16 | 17 | - [trino](../../sources/trino.md) 18 | 19 | The specified SQL statement is executed as a [prepared statement][trino-prepare], 20 | and specified parameters will be inserted according to their position: e.g. `$1` 21 | will be the first parameter specified, `$2` will be the second parameter, and so 22 | on. If template parameters are included, they will be resolved before execution 23 | of the prepared statement. 24 | 25 | [trino-prepare]: https://trino.io/docs/current/sql/prepare.html 26 | 27 | ## Example 28 | 29 | > **Note:** This tool uses parameterized queries to prevent SQL injections. 30 | > Query parameters can be used as substitutes for arbitrary expressions. 31 | > Parameters cannot be used as substitutes for identifiers, column names, table 32 | > names, or other parts of the query. 33 | 34 | ```yaml 35 | tools: 36 | search_orders_by_region: 37 | kind: trino-sql 38 | source: my-trino-instance 39 | statement: | 40 | SELECT * FROM hive.sales.orders 41 | WHERE region = $1 42 | AND order_date >= DATE($2) 43 | LIMIT 10 44 | description: | 45 | Use this tool to get information for orders in a specific region. 46 | Takes a region code and date and returns info on the orders. 47 | Do NOT use this tool with an order id. Do NOT guess a region code or date. 48 | A region code is a code for a geographic region consisting of two-character 49 | region designator and followed by optional subregion. 50 | For example, if given US-WEST, the region is "US-WEST". 51 | Another example for this is EU-CENTRAL, the region is "EU-CENTRAL". 52 | If the tool returns more than one option choose the date closest to today. 53 | Example: 54 | {{ 55 | "region": "US-WEST", 56 | "order_date": "2024-01-01", 57 | }} 58 | Example: 59 | {{ 60 | "region": "EU-CENTRAL", 61 | "order_date": "2024-01-15", 62 | }} 63 | parameters: 64 | - name: region 65 | type: string 66 | description: Region unique identifier 67 | - name: order_date 68 | type: string 69 | description: Order date in YYYY-MM-DD format 70 | ``` 71 | 72 | ### Example with Template Parameters 73 | 74 | > **Note:** This tool allows direct modifications to the SQL statement, 75 | > including identifiers, column names, and table names. **This makes it more 76 | > vulnerable to SQL injections**. Using basic parameters only (see above) is 77 | > recommended for performance and safety reasons. For more details, please check 78 | > [templateParameters](..#template-parameters). 79 | 80 | ```yaml 81 | tools: 82 | list_table: 83 | kind: trino-sql 84 | source: my-trino-instance 85 | statement: | 86 | SELECT * FROM {{.tableName}} 87 | description: | 88 | Use this tool to list all information from a specific table. 89 | Example: 90 | {{ 91 | "tableName": "hive.sales.orders", 92 | }} 93 | templateParameters: 94 | - name: tableName 95 | type: string 96 | description: Table to select from 97 | ``` 98 | 99 | ## Reference 100 | 101 | | **field** | **type** | **required** | **description** | 102 | |---------------------|:---------------------------------------------------------:|:------------:|--------------------------------------------------------------------------------------------------------------------------------------------| 103 | | kind | string | true | Must be "trino-sql". | 104 | | source | string | true | Name of the source the SQL should execute on. | 105 | | description | string | true | Description of the tool that is passed to the LLM. | 106 | | statement | string | true | SQL statement to execute on. | 107 | | parameters | [parameters](../#specifying-parameters) | false | List of [parameters](../#specifying-parameters) that will be inserted into the SQL statement. | 108 | | templateParameters | [templateParameters](..#template-parameters) | false | List of [templateParameters](..#template-parameters) that will be inserted into the SQL statement before executing prepared statement. | 109 | ``` -------------------------------------------------------------------------------- /internal/tools/looker/lookergetmodels/lookergetmodels.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 lookergetmodels 15 | 16 | import ( 17 | "context" 18 | "fmt" 19 | 20 | yaml "github.com/goccy/go-yaml" 21 | "github.com/googleapis/genai-toolbox/internal/sources" 22 | lookersrc "github.com/googleapis/genai-toolbox/internal/sources/looker" 23 | "github.com/googleapis/genai-toolbox/internal/tools" 24 | "github.com/googleapis/genai-toolbox/internal/tools/looker/lookercommon" 25 | "github.com/googleapis/genai-toolbox/internal/util" 26 | 27 | "github.com/looker-open-source/sdk-codegen/go/rtl" 28 | v4 "github.com/looker-open-source/sdk-codegen/go/sdk/v4" 29 | ) 30 | 31 | const kind string = "looker-get-models" 32 | 33 | func init() { 34 | if !tools.Register(kind, newConfig) { 35 | panic(fmt.Sprintf("tool kind %q already registered", kind)) 36 | } 37 | } 38 | 39 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) { 40 | actual := Config{Name: name} 41 | if err := decoder.DecodeContext(ctx, &actual); err != nil { 42 | return nil, err 43 | } 44 | return actual, nil 45 | } 46 | 47 | type Config struct { 48 | Name string `yaml:"name" validate:"required"` 49 | Kind string `yaml:"kind" validate:"required"` 50 | Source string `yaml:"source" validate:"required"` 51 | Description string `yaml:"description" validate:"required"` 52 | AuthRequired []string `yaml:"authRequired"` 53 | } 54 | 55 | // validate interface 56 | var _ tools.ToolConfig = Config{} 57 | 58 | func (cfg Config) ToolConfigKind() string { 59 | return kind 60 | } 61 | 62 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { 63 | // verify source exists 64 | rawS, ok := srcs[cfg.Source] 65 | if !ok { 66 | return nil, fmt.Errorf("no source named %q configured", cfg.Source) 67 | } 68 | 69 | // verify the source is compatible 70 | s, ok := rawS.(*lookersrc.Source) 71 | if !ok { 72 | return nil, fmt.Errorf("invalid source for %q tool: source kind must be `looker`", kind) 73 | } 74 | 75 | parameters := tools.Parameters{} 76 | 77 | mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters) 78 | 79 | // finish tool setup 80 | return Tool{ 81 | Name: cfg.Name, 82 | Kind: kind, 83 | Parameters: parameters, 84 | AuthRequired: cfg.AuthRequired, 85 | UseClientOAuth: s.UseClientOAuth, 86 | Client: s.Client, 87 | ApiSettings: s.ApiSettings, 88 | manifest: tools.Manifest{ 89 | Description: cfg.Description, 90 | Parameters: parameters.Manifest(), 91 | AuthRequired: cfg.AuthRequired, 92 | }, 93 | mcpManifest: mcpManifest, 94 | ShowHiddenModels: s.ShowHiddenModels, 95 | }, nil 96 | } 97 | 98 | // validate interface 99 | var _ tools.Tool = Tool{} 100 | 101 | type Tool struct { 102 | Name string `yaml:"name"` 103 | Kind string `yaml:"kind"` 104 | UseClientOAuth bool 105 | Client *v4.LookerSDK 106 | ApiSettings *rtl.ApiSettings 107 | AuthRequired []string `yaml:"authRequired"` 108 | Parameters tools.Parameters `yaml:"parameters"` 109 | manifest tools.Manifest 110 | mcpManifest tools.McpManifest 111 | ShowHiddenModels bool 112 | } 113 | 114 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { 115 | logger, err := util.LoggerFromContext(ctx) 116 | if err != nil { 117 | return nil, fmt.Errorf("unable to get logger from ctx: %s", err) 118 | } 119 | 120 | excludeEmpty := false 121 | excludeHidden := !t.ShowHiddenModels 122 | includeInternal := true 123 | 124 | sdk, err := lookercommon.GetLookerSDK(t.UseClientOAuth, t.ApiSettings, t.Client, accessToken) 125 | if err != nil { 126 | return nil, fmt.Errorf("error getting sdk: %w", err) 127 | } 128 | req := v4.RequestAllLookmlModels{ 129 | ExcludeEmpty: &excludeEmpty, 130 | ExcludeHidden: &excludeHidden, 131 | IncludeInternal: &includeInternal, 132 | } 133 | resp, err := sdk.AllLookmlModels(req, t.ApiSettings) 134 | if err != nil { 135 | return nil, fmt.Errorf("error making get_models request: %s", err) 136 | } 137 | 138 | var data []any 139 | for _, v := range resp { 140 | logger.DebugContext(ctx, "Got response element of %v\n", v) 141 | vMap := make(map[string]any) 142 | vMap["label"] = *v.Label 143 | vMap["name"] = *v.Name 144 | vMap["project_name"] = *v.ProjectName 145 | logger.DebugContext(ctx, "Converted to %v\n", vMap) 146 | data = append(data, vMap) 147 | } 148 | logger.DebugContext(ctx, "data = ", data) 149 | 150 | return data, nil 151 | } 152 | 153 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { 154 | return tools.ParamValues{}, nil 155 | } 156 | 157 | func (t Tool) Manifest() tools.Manifest { 158 | return t.manifest 159 | } 160 | 161 | func (t Tool) McpManifest() tools.McpManifest { 162 | return t.mcpManifest 163 | } 164 | 165 | func (t Tool) Authorized(verifiedAuthServices []string) bool { 166 | return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices) 167 | } 168 | 169 | func (t Tool) RequiresClientAuthorization() bool { 170 | return t.UseClientOAuth 171 | } 172 | ``` -------------------------------------------------------------------------------- /internal/prebuiltconfigs/tools/sqlite.yaml: -------------------------------------------------------------------------------- ```yaml 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 | sources: 16 | sqlite-source: 17 | kind: sqlite 18 | database: ${SQLITE_DATABASE} 19 | tools: 20 | execute_sql: 21 | kind: sqlite-execute-sql 22 | source: sqlite-source 23 | description: Use this tool to execute SQL. 24 | list_tables: 25 | kind: sqlite-sql 26 | source: sqlite-source 27 | description: "Lists SQLite tables. Use 'output_format' ('simple'/'detailed') and 'table_names' (comma-separated or empty) to control output." 28 | statement: | 29 | WITH table_columns AS ( 30 | SELECT 31 | m.name AS table_name, 32 | json_group_array(json_object('column_name', ti.name, 'data_type', ti.type, 'ordinal_position', ti.cid, 'is_not_nullable', ti."notnull" = 1, 'column_default', ti.dflt_value, 'is_primary_key', ti.pk > 0)) AS details 33 | FROM sqlite_master AS m, pragma_table_info(m.name) AS ti 34 | WHERE m.type = 'table' AND m.name NOT LIKE 'sqlite_%' 35 | GROUP BY m.name 36 | ), 37 | table_constraints AS ( 38 | SELECT 39 | table_name, 40 | json_group_array(json(details)) AS details 41 | FROM ( 42 | SELECT m.name AS table_name, json_object('constraint_name', 'PRIMARY', 'constraint_type', 'PRIMARY KEY', 'constraint_columns', json_group_array(T.name)) AS details 43 | FROM sqlite_master AS m, pragma_table_info(m.name) AS T 44 | WHERE m.type = 'table' AND T.pk > 0 45 | GROUP BY m.name 46 | HAVING COUNT(T.name) > 0 47 | UNION ALL 48 | SELECT m.name, json_object('constraint_name', 'fk_' || m.name || '_' || F.id, 'constraint_type', 'FOREIGN KEY', 'constraint_columns', json_group_array(F."from"), 'foreign_key_referenced_table', F."table", 'foreign_key_referenced_columns', json_group_array(F."to")) 49 | FROM sqlite_master AS m, pragma_foreign_key_list(m.name) AS F 50 | WHERE m.type = 'table' 51 | GROUP BY m.name, F.id 52 | UNION ALL 53 | SELECT m.name, json_object('constraint_name', I.name, 'constraint_type', 'UNIQUE', 'constraint_columns', (SELECT json_group_array(C.name) FROM pragma_index_info(I.name) AS C ORDER BY C.seqno)) 54 | FROM sqlite_master AS m, pragma_index_list(m.name) AS I 55 | WHERE m.type = 'table' AND I."unique" = 1 AND I.origin != 'pk' 56 | ) 57 | GROUP BY table_name 58 | ), 59 | table_indexes AS ( 60 | SELECT 61 | m.name AS table_name, 62 | json_group_array(json_object('index_name', il.name, 'is_unique', il."unique" = 1, 'is_primary', il.origin = 'pk', 'index_columns', (SELECT json_group_array(ii.name) FROM pragma_index_info(il.name) AS ii))) AS details 63 | FROM sqlite_master AS m, pragma_index_list(m.name) AS il 64 | WHERE m.type = 'table' AND m.name NOT LIKE 'sqlite_%' 65 | GROUP BY m.name 66 | ), 67 | table_triggers AS ( 68 | SELECT 69 | tbl_name AS table_name, 70 | json_group_array(json_object('trigger_name', name, 'trigger_definition', sql)) AS details 71 | FROM sqlite_master 72 | WHERE type = 'trigger' 73 | GROUP BY tbl_name 74 | ) 75 | SELECT 76 | CASE 77 | WHEN '{{.output_format}}' = 'simple' THEN json_object('name', m.name) 78 | ELSE json_object( 79 | 'schema_name', 'main', 80 | 'object_name', m.name, 81 | 'object_type', m.type, 82 | 'columns', json(COALESCE(tc.details, '[]')), 83 | 'constraints', json(COALESCE(tcons.details, '[]')), 84 | 'indexes', json(COALESCE(ti.details, '[]')), 85 | 'triggers', json(COALESCE(tt.details, '[]')) 86 | ) 87 | END AS object_details 88 | FROM 89 | sqlite_master AS m 90 | LEFT JOIN table_columns tc ON m.name = tc.table_name 91 | LEFT JOIN table_constraints tcons ON m.name = tcons.table_name 92 | LEFT JOIN table_indexes ti ON m.name = ti.table_name 93 | LEFT JOIN table_triggers tt ON m.name = tt.table_name 94 | WHERE 95 | m.type = 'table' 96 | AND m.name NOT LIKE 'sqlite_%' 97 | {{if .table_names}} 98 | AND instr(',' || '{{.table_names}}' || ',', ',' || m.name || ',') > 0 99 | {{end}}; 100 | templateParameters: 101 | - name: output_format 102 | type: string 103 | description: "Optional: Use 'simple' to return table names only or use 'detailed' to return the full information schema." 104 | default: "detailed" 105 | - name: table_names 106 | type: string 107 | description: "Optional: A comma-separated list of table names. If empty, details for all tables in user-accessible schemas will be listed." 108 | default: "" 109 | toolsets: 110 | sqlite_database_tools: 111 | - execute_sql 112 | - list_tables 113 | ``` -------------------------------------------------------------------------------- /internal/sources/oracle/oracle.go: -------------------------------------------------------------------------------- ```go 1 | // Copyright © 2025, Oracle and/or its affiliates. 2 | package oracle 3 | 4 | import ( 5 | "context" 6 | "database/sql" 7 | "fmt" 8 | "os" 9 | "strings" 10 | 11 | "github.com/goccy/go-yaml" 12 | "github.com/googleapis/genai-toolbox/internal/sources" 13 | "github.com/googleapis/genai-toolbox/internal/util" 14 | _ "github.com/sijms/go-ora/v2" 15 | "go.opentelemetry.io/otel/trace" 16 | ) 17 | 18 | const SourceKind string = "oracle" 19 | 20 | // validate interface 21 | var _ sources.SourceConfig = Config{} 22 | 23 | func init() { 24 | if !sources.Register(SourceKind, newConfig) { 25 | panic(fmt.Sprintf("source kind %q already registered", SourceKind)) 26 | } 27 | } 28 | 29 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) { 30 | actual := Config{Name: name} 31 | if err := decoder.DecodeContext(ctx, &actual); err != nil { 32 | return nil, err 33 | } 34 | 35 | // Validate that we have one of: tns_alias, connection_string, or host+service_name 36 | if err := actual.validate(); err != nil { 37 | return nil, fmt.Errorf("invalid Oracle configuration: %w", err) 38 | } 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 | ConnectionString string `yaml:"connectionString,omitempty"` // Direct connection string (hostname[:port]/servicename) 47 | TnsAlias string `yaml:"tnsAlias,omitempty"` // TNS alias from tnsnames.ora 48 | Host string `yaml:"host,omitempty"` // Optional when using connectionString/tnsAlias 49 | Port int `yaml:"port,omitempty"` // Explicit port support 50 | ServiceName string `yaml:"serviceName,omitempty"` // Optional when using connectionString/tnsAlias 51 | User string `yaml:"user" validate:"required"` 52 | Password string `yaml:"password" validate:"required"` 53 | TnsAdmin string `yaml:"tnsAdmin,omitempty"` // Optional: override TNS_ADMIN environment variable 54 | } 55 | 56 | // validate ensures we have one of: tns_alias, connection_string, or host+service_name 57 | func (c Config) validate() error { 58 | hasTnsAlias := strings.TrimSpace(c.TnsAlias) != "" 59 | hasConnStr := strings.TrimSpace(c.ConnectionString) != "" 60 | hasHostService := strings.TrimSpace(c.Host) != "" && strings.TrimSpace(c.ServiceName) != "" 61 | 62 | connectionMethods := 0 63 | if hasTnsAlias { 64 | connectionMethods++ 65 | } 66 | if hasConnStr { 67 | connectionMethods++ 68 | } 69 | if hasHostService { 70 | connectionMethods++ 71 | } 72 | 73 | if connectionMethods == 0 { 74 | return fmt.Errorf("must provide one of: 'tns_alias', 'connection_string', or both 'host' and 'service_name'") 75 | } 76 | 77 | if connectionMethods > 1 { 78 | return fmt.Errorf("provide only one connection method: 'tns_alias', 'connection_string', or 'host'+'service_name'") 79 | } 80 | 81 | return nil 82 | } 83 | 84 | func (r Config) SourceConfigKind() string { 85 | return SourceKind 86 | } 87 | 88 | func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) { 89 | db, err := initOracleConnection(ctx, tracer, r) 90 | if err != nil { 91 | return nil, fmt.Errorf("unable to create Oracle connection: %w", err) 92 | } 93 | 94 | err = db.PingContext(ctx) 95 | if err != nil { 96 | return nil, fmt.Errorf("unable to connect to Oracle successfully: %w", err) 97 | } 98 | 99 | s := &Source{ 100 | Name: r.Name, 101 | Kind: SourceKind, 102 | DB: db, 103 | } 104 | return s, nil 105 | } 106 | 107 | var _ sources.Source = &Source{} 108 | 109 | type Source struct { 110 | Name string `yaml:"name"` 111 | Kind string `yaml:"kind"` 112 | DB *sql.DB 113 | } 114 | 115 | func (s *Source) SourceKind() string { 116 | return SourceKind 117 | } 118 | 119 | func (s *Source) OracleDB() *sql.DB { 120 | return s.DB 121 | } 122 | 123 | func initOracleConnection(ctx context.Context, tracer trace.Tracer, config Config) (*sql.DB, error) { 124 | //nolint:all // Reassigned ctx 125 | ctx, span := sources.InitConnectionSpan(ctx, tracer, SourceKind, config.Name) 126 | defer span.End() 127 | 128 | logger, err := util.LoggerFromContext(ctx) 129 | if err != nil { 130 | panic(err) 131 | } 132 | 133 | // Set TNS_ADMIN environment variable if specified in config. 134 | if config.TnsAdmin != "" { 135 | originalTnsAdmin := os.Getenv("TNS_ADMIN") 136 | os.Setenv("TNS_ADMIN", config.TnsAdmin) 137 | logger.DebugContext(ctx, fmt.Sprintf("Setting TNS_ADMIN to: %s\n", config.TnsAdmin)) 138 | // Restore original TNS_ADMIN after connection 139 | defer func() { 140 | if originalTnsAdmin != "" { 141 | os.Setenv("TNS_ADMIN", originalTnsAdmin) 142 | } else { 143 | os.Unsetenv("TNS_ADMIN") 144 | } 145 | }() 146 | } 147 | 148 | var serverString string 149 | if config.TnsAlias != "" { 150 | // Use TNS alias 151 | serverString = strings.TrimSpace(config.TnsAlias) 152 | } else if config.ConnectionString != "" { 153 | // Use provided connection string directly (hostname[:port]/servicename format) 154 | serverString = strings.TrimSpace(config.ConnectionString) 155 | } else { 156 | // Build connection string from host and service_name 157 | if config.Port > 0 { 158 | serverString = fmt.Sprintf("%s:%d/%s", config.Host, config.Port, config.ServiceName) 159 | } else { 160 | serverString = fmt.Sprintf("%s/%s", config.Host, config.ServiceName) 161 | } 162 | } 163 | 164 | connStr := fmt.Sprintf("oracle://%s:%s@%s", 165 | config.User, config.Password, serverString) 166 | 167 | db, err := sql.Open("oracle", connStr) 168 | if err != nil { 169 | return nil, fmt.Errorf("unable to open Oracle connection: %w", err) 170 | } 171 | 172 | return db, nil 173 | } 174 | ``` -------------------------------------------------------------------------------- /internal/tools/mongodb/mongodbinsertone/mongodbinsertone.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 mongodbinsertone 15 | 16 | import ( 17 | "context" 18 | "errors" 19 | "fmt" 20 | 21 | "github.com/goccy/go-yaml" 22 | "github.com/googleapis/genai-toolbox/internal/sources" 23 | mongosrc "github.com/googleapis/genai-toolbox/internal/sources/mongodb" 24 | "github.com/googleapis/genai-toolbox/internal/tools" 25 | "go.mongodb.org/mongo-driver/bson" 26 | "go.mongodb.org/mongo-driver/mongo" 27 | "go.mongodb.org/mongo-driver/mongo/options" 28 | ) 29 | 30 | const kind string = "mongodb-insert-one" 31 | 32 | const dataParamsKey = "data" 33 | 34 | func init() { 35 | if !tools.Register(kind, newConfig) { 36 | panic(fmt.Sprintf("tool kind %q already registered", kind)) 37 | } 38 | } 39 | 40 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, 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 | Source string `yaml:"source" validate:"required"` 52 | AuthRequired []string `yaml:"authRequired" validate:"required"` 53 | Description string `yaml:"description" validate:"required"` 54 | Database string `yaml:"database" validate:"required"` 55 | Collection string `yaml:"collection" validate:"required"` 56 | Canonical bool `yaml:"canonical" validate:"required"` //i want to force the user to choose 57 | } 58 | 59 | // validate interface 60 | var _ tools.ToolConfig = Config{} 61 | 62 | func (cfg Config) ToolConfigKind() string { 63 | return kind 64 | } 65 | 66 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { 67 | // verify source exists 68 | rawS, ok := srcs[cfg.Source] 69 | if !ok { 70 | return nil, fmt.Errorf("no source named %q configured", cfg.Source) 71 | } 72 | 73 | // verify the source is compatible 74 | s, ok := rawS.(*mongosrc.Source) 75 | if !ok { 76 | return nil, fmt.Errorf("invalid source for %q tool: source kind must be `mongodb`", kind) 77 | } 78 | 79 | payloadParams := tools.NewStringParameterWithRequired(dataParamsKey, "the JSON payload to insert, should be a JSON object", true) 80 | 81 | allParameters := tools.Parameters{payloadParams} 82 | 83 | // Create Toolbox manifest 84 | paramManifest := allParameters.Manifest() 85 | 86 | if paramManifest == nil { 87 | paramManifest = make([]tools.ParameterManifest, 0) 88 | } 89 | 90 | // Create MCP manifest 91 | mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, allParameters) 92 | 93 | // finish tool setup 94 | return Tool{ 95 | Name: cfg.Name, 96 | Kind: kind, 97 | AuthRequired: cfg.AuthRequired, 98 | Collection: cfg.Collection, 99 | Canonical: cfg.Canonical, 100 | PayloadParams: allParameters, 101 | database: s.Client.Database(cfg.Database), 102 | manifest: tools.Manifest{Description: cfg.Description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired}, 103 | mcpManifest: mcpManifest, 104 | }, nil 105 | } 106 | 107 | // validate interface 108 | var _ tools.Tool = Tool{} 109 | 110 | type Tool struct { 111 | Name string `yaml:"name"` 112 | Kind string `yaml:"kind"` 113 | AuthRequired []string `yaml:"authRequired"` 114 | Description string `yaml:"description"` 115 | Collection string `yaml:"collection"` 116 | Canonical bool `yaml:"canonical" validation:"required"` 117 | PayloadParams tools.Parameters `yaml:"payloadParams" validate:"required"` 118 | 119 | database *mongo.Database 120 | manifest tools.Manifest 121 | mcpManifest tools.McpManifest 122 | } 123 | 124 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { 125 | if len(params) == 0 { 126 | return nil, errors.New("no input found") 127 | } 128 | // use the first, assume it's a string 129 | var jsonData, ok = params[0].Value.(string) 130 | if !ok { 131 | return nil, errors.New("no input found") 132 | } 133 | 134 | var data any 135 | err := bson.UnmarshalExtJSON([]byte(jsonData), t.Canonical, &data) 136 | if err != nil { 137 | return nil, err 138 | } 139 | 140 | res, err := t.database.Collection(t.Collection).InsertOne(ctx, data, options.InsertOne()) 141 | if err != nil { 142 | return nil, err 143 | } 144 | 145 | return res.InsertedID, nil 146 | } 147 | 148 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { 149 | return tools.ParseParams(t.PayloadParams, data, claims) 150 | } 151 | 152 | func (t Tool) Manifest() tools.Manifest { 153 | return t.manifest 154 | } 155 | 156 | func (t Tool) McpManifest() tools.McpManifest { 157 | return t.mcpManifest 158 | } 159 | 160 | func (t Tool) Authorized(verifiedAuthServices []string) bool { 161 | return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices) 162 | } 163 | 164 | func (t Tool) RequiresClientAuthorization() bool { 165 | return false 166 | } 167 | ``` -------------------------------------------------------------------------------- /internal/tools/postgres/postgresexecutesql/postgresexecutesql.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 postgresexecutesql 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | 21 | yaml "github.com/goccy/go-yaml" 22 | "github.com/googleapis/genai-toolbox/internal/sources" 23 | "github.com/googleapis/genai-toolbox/internal/sources/alloydbpg" 24 | "github.com/googleapis/genai-toolbox/internal/sources/cloudsqlpg" 25 | "github.com/googleapis/genai-toolbox/internal/sources/postgres" 26 | "github.com/googleapis/genai-toolbox/internal/tools" 27 | "github.com/googleapis/genai-toolbox/internal/util" 28 | "github.com/jackc/pgx/v5/pgxpool" 29 | ) 30 | 31 | const kind string = "postgres-execute-sql" 32 | 33 | func init() { 34 | if !tools.Register(kind, newConfig) { 35 | panic(fmt.Sprintf("tool kind %q already registered", kind)) 36 | } 37 | } 38 | 39 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) { 40 | actual := Config{Name: name} 41 | if err := decoder.DecodeContext(ctx, &actual); err != nil { 42 | return nil, err 43 | } 44 | return actual, nil 45 | } 46 | 47 | type compatibleSource interface { 48 | PostgresPool() *pgxpool.Pool 49 | } 50 | 51 | // validate compatible sources are still compatible 52 | var _ compatibleSource = &alloydbpg.Source{} 53 | var _ compatibleSource = &cloudsqlpg.Source{} 54 | var _ compatibleSource = &postgres.Source{} 55 | 56 | var compatibleSources = [...]string{alloydbpg.SourceKind, cloudsqlpg.SourceKind, postgres.SourceKind} 57 | 58 | type Config struct { 59 | Name string `yaml:"name" validate:"required"` 60 | Kind string `yaml:"kind" validate:"required"` 61 | Source string `yaml:"source" validate:"required"` 62 | Description string `yaml:"description" validate:"required"` 63 | AuthRequired []string `yaml:"authRequired"` 64 | } 65 | 66 | // validate interface 67 | var _ tools.ToolConfig = Config{} 68 | 69 | func (cfg Config) ToolConfigKind() string { 70 | return kind 71 | } 72 | 73 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { 74 | // verify source exists 75 | rawS, ok := srcs[cfg.Source] 76 | if !ok { 77 | return nil, fmt.Errorf("no source named %q configured", cfg.Source) 78 | } 79 | 80 | // verify the source is compatible 81 | s, ok := rawS.(compatibleSource) 82 | if !ok { 83 | return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources) 84 | } 85 | 86 | sqlParameter := tools.NewStringParameter("sql", "The sql to execute.") 87 | parameters := tools.Parameters{sqlParameter} 88 | 89 | mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters) 90 | 91 | // finish tool setup 92 | t := Tool{ 93 | Name: cfg.Name, 94 | Kind: kind, 95 | Parameters: parameters, 96 | AuthRequired: cfg.AuthRequired, 97 | Pool: s.PostgresPool(), 98 | manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired}, 99 | mcpManifest: mcpManifest, 100 | } 101 | return t, nil 102 | } 103 | 104 | // validate interface 105 | var _ tools.Tool = Tool{} 106 | 107 | type Tool struct { 108 | Name string `yaml:"name"` 109 | Kind string `yaml:"kind"` 110 | AuthRequired []string `yaml:"authRequired"` 111 | Parameters tools.Parameters `yaml:"parameters"` 112 | 113 | Pool *pgxpool.Pool 114 | manifest tools.Manifest 115 | mcpManifest tools.McpManifest 116 | } 117 | 118 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { 119 | paramsMap := params.AsMap() 120 | sql, ok := paramsMap["sql"].(string) 121 | if !ok { 122 | return nil, fmt.Errorf("unable to get cast %s", paramsMap["sql"]) 123 | } 124 | // Log the query executed for debugging. 125 | logger, err := util.LoggerFromContext(ctx) 126 | if err != nil { 127 | return nil, fmt.Errorf("error getting logger: %s", err) 128 | } 129 | logger.DebugContext(ctx, "executing `%s` tool query: %s", kind, sql) 130 | 131 | results, err := t.Pool.Query(ctx, sql) 132 | if err != nil { 133 | return nil, fmt.Errorf("unable to execute query: %w", err) 134 | } 135 | 136 | fields := results.FieldDescriptions() 137 | 138 | var out []any 139 | for results.Next() { 140 | v, err := results.Values() 141 | if err != nil { 142 | return nil, fmt.Errorf("unable to parse row: %w", err) 143 | } 144 | vMap := make(map[string]any) 145 | for i, f := range fields { 146 | vMap[f.Name] = v[i] 147 | } 148 | out = append(out, vMap) 149 | } 150 | 151 | return out, nil 152 | } 153 | 154 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { 155 | return tools.ParseParams(t.Parameters, data, claims) 156 | } 157 | 158 | func (t Tool) Manifest() tools.Manifest { 159 | return t.manifest 160 | } 161 | 162 | func (t Tool) McpManifest() tools.McpManifest { 163 | return t.mcpManifest 164 | } 165 | 166 | func (t Tool) Authorized(verifiedAuthServices []string) bool { 167 | return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices) 168 | } 169 | 170 | func (t Tool) RequiresClientAuthorization() bool { 171 | return false 172 | } 173 | ``` -------------------------------------------------------------------------------- /internal/tools/cloudsql/cloudsqllistdatabases/cloudsqllistdatabases.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 cloudsqllistdatabases 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | 21 | "github.com/goccy/go-yaml" 22 | "github.com/googleapis/genai-toolbox/internal/sources" 23 | cloudsqladminsrc "github.com/googleapis/genai-toolbox/internal/sources/cloudsqladmin" 24 | "github.com/googleapis/genai-toolbox/internal/tools" 25 | ) 26 | 27 | const kind string = "cloud-sql-list-databases" 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 | // Config defines the configuration for the list-databases tool. 44 | type Config struct { 45 | Name string `yaml:"name" validate:"required"` 46 | Kind string `yaml:"kind" validate:"required"` 47 | Source string `yaml:"source" validate:"required"` 48 | Description string `yaml:"description"` 49 | AuthRequired []string `yaml:"authRequired"` 50 | } 51 | 52 | // validate interface 53 | var _ tools.ToolConfig = Config{} 54 | 55 | // ToolConfigKind returns the kind of the tool. 56 | func (cfg Config) ToolConfigKind() string { 57 | return kind 58 | } 59 | 60 | // Initialize initializes the tool from the configuration. 61 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { 62 | rawS, ok := srcs[cfg.Source] 63 | if !ok { 64 | return nil, fmt.Errorf("no source named %q configured", cfg.Source) 65 | } 66 | s, ok := rawS.(*cloudsqladminsrc.Source) 67 | if !ok { 68 | return nil, fmt.Errorf("invalid source for %q tool: source kind must be `cloud-sql-admin`", kind) 69 | } 70 | 71 | allParameters := tools.Parameters{ 72 | tools.NewStringParameter("project", "The project ID"), 73 | tools.NewStringParameter("instance", "The instance ID"), 74 | } 75 | paramManifest := allParameters.Manifest() 76 | 77 | description := cfg.Description 78 | if description == "" { 79 | description = "Lists all databases for a Cloud SQL instance." 80 | } 81 | mcpManifest := tools.GetMcpManifest(cfg.Name, description, cfg.AuthRequired, allParameters) 82 | 83 | return Tool{ 84 | Name: cfg.Name, 85 | Kind: kind, 86 | AuthRequired: cfg.AuthRequired, 87 | Source: s, 88 | AllParams: allParameters, 89 | manifest: tools.Manifest{Description: description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired}, 90 | mcpManifest: mcpManifest, 91 | }, nil 92 | } 93 | 94 | // Tool represents the list-databases tool. 95 | type Tool struct { 96 | Name string `yaml:"name"` 97 | Kind string `yaml:"kind"` 98 | Description string `yaml:"description"` 99 | AuthRequired []string `yaml:"authRequired"` 100 | 101 | AllParams tools.Parameters `yaml:"allParams"` 102 | Source *cloudsqladminsrc.Source 103 | manifest tools.Manifest 104 | mcpManifest tools.McpManifest 105 | } 106 | 107 | // Invoke executes the tool's logic. 108 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { 109 | paramsMap := params.AsMap() 110 | 111 | project, ok := paramsMap["project"].(string) 112 | if !ok { 113 | return nil, fmt.Errorf("missing 'project' parameter") 114 | } 115 | instance, ok := paramsMap["instance"].(string) 116 | if !ok { 117 | return nil, fmt.Errorf("missing 'instance' parameter") 118 | } 119 | 120 | service, err := t.Source.GetService(ctx, string(accessToken)) 121 | if err != nil { 122 | return nil, err 123 | } 124 | 125 | resp, err := service.Databases.List(project, instance).Do() 126 | if err != nil { 127 | return nil, fmt.Errorf("error listing databases: %w", err) 128 | } 129 | 130 | if resp.Items == nil { 131 | return []any{}, nil 132 | } 133 | 134 | type databaseInfo struct { 135 | Name string `json:"name"` 136 | Charset string `json:"charset"` 137 | Collation string `json:"collation"` 138 | } 139 | 140 | var databases []databaseInfo 141 | for _, item := range resp.Items { 142 | databases = append(databases, databaseInfo{ 143 | Name: item.Name, 144 | Charset: item.Charset, 145 | Collation: item.Collation, 146 | }) 147 | } 148 | 149 | return databases, nil 150 | } 151 | 152 | // ParseParams parses the parameters for the tool. 153 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { 154 | return tools.ParseParams(t.AllParams, data, claims) 155 | } 156 | 157 | // Manifest returns the tool's manifest. 158 | func (t Tool) Manifest() tools.Manifest { 159 | return t.manifest 160 | } 161 | 162 | // McpManifest returns the tool's MCP manifest. 163 | func (t Tool) McpManifest() tools.McpManifest { 164 | return t.mcpManifest 165 | } 166 | 167 | // Authorized checks if the tool is authorized. 168 | func (t Tool) Authorized(verifiedAuthServices []string) bool { 169 | return true 170 | } 171 | 172 | func (t Tool) RequiresClientAuthorization() bool { 173 | return t.Source.UseClientAuthorization() 174 | } 175 | ``` -------------------------------------------------------------------------------- /internal/tools/cloudsql/cloudsqlcreatedatabase/cloudsqlcreatedatabase.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 cloudsqlcreatedatabase 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | 21 | "github.com/goccy/go-yaml" 22 | "github.com/googleapis/genai-toolbox/internal/sources" 23 | "github.com/googleapis/genai-toolbox/internal/sources/cloudsqladmin" 24 | "github.com/googleapis/genai-toolbox/internal/tools" 25 | sqladmin "google.golang.org/api/sqladmin/v1" 26 | ) 27 | 28 | const kind string = "cloud-sql-create-database" 29 | 30 | func init() { 31 | if !tools.Register(kind, newConfig) { 32 | panic(fmt.Sprintf("tool kind %q already registered", kind)) 33 | } 34 | } 35 | 36 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) { 37 | actual := Config{Name: name} 38 | if err := decoder.DecodeContext(ctx, &actual); err != nil { 39 | return nil, err 40 | } 41 | return actual, nil 42 | } 43 | 44 | // Config defines the configuration for the create-database tool. 45 | type Config struct { 46 | Name string `yaml:"name" validate:"required"` 47 | Kind string `yaml:"kind" validate:"required"` 48 | Source string `yaml:"source" validate:"required"` 49 | Description string `yaml:"description"` 50 | AuthRequired []string `yaml:"authRequired"` 51 | } 52 | 53 | // validate interface 54 | var _ tools.ToolConfig = Config{} 55 | 56 | // ToolConfigKind returns the kind of the tool. 57 | func (cfg Config) ToolConfigKind() string { 58 | return kind 59 | } 60 | 61 | // Initialize initializes the tool from the configuration. 62 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { 63 | rawS, ok := srcs[cfg.Source] 64 | if !ok { 65 | return nil, fmt.Errorf("no source named %q configured", cfg.Source) 66 | } 67 | s, ok := rawS.(*cloudsqladmin.Source) 68 | if !ok { 69 | return nil, fmt.Errorf("invalid source for %q tool: source kind must be `cloud-sql-admin`", kind) 70 | } 71 | 72 | allParameters := tools.Parameters{ 73 | tools.NewStringParameter("project", "The project ID"), 74 | tools.NewStringParameter("instance", "The ID of the instance where the database will be created."), 75 | tools.NewStringParameter("name", "The name for the new database. Must be unique within the instance."), 76 | } 77 | paramManifest := allParameters.Manifest() 78 | 79 | description := cfg.Description 80 | if description == "" { 81 | description = "Creates a new database in a Cloud SQL instance." 82 | } 83 | mcpManifest := tools.GetMcpManifest(cfg.Name, description, cfg.AuthRequired, allParameters) 84 | 85 | return Tool{ 86 | Name: cfg.Name, 87 | Kind: kind, 88 | AuthRequired: cfg.AuthRequired, 89 | Source: s, 90 | AllParams: allParameters, 91 | manifest: tools.Manifest{Description: cfg.Description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired}, 92 | mcpManifest: mcpManifest, 93 | }, nil 94 | } 95 | 96 | // Tool represents the create-database tool. 97 | type Tool struct { 98 | Name string `yaml:"name"` 99 | Kind string `yaml:"kind"` 100 | Description string `yaml:"description"` 101 | AuthRequired []string `yaml:"authRequired"` 102 | 103 | Source *cloudsqladmin.Source 104 | AllParams tools.Parameters `yaml:"allParams"` 105 | manifest tools.Manifest 106 | mcpManifest tools.McpManifest 107 | } 108 | 109 | // Invoke executes the tool's logic. 110 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { 111 | paramsMap := params.AsMap() 112 | 113 | project, ok := paramsMap["project"].(string) 114 | if !ok { 115 | return nil, fmt.Errorf("missing 'project' parameter") 116 | } 117 | instance, ok := paramsMap["instance"].(string) 118 | if !ok { 119 | return nil, fmt.Errorf("missing 'instance' parameter") 120 | } 121 | name, ok := paramsMap["name"].(string) 122 | if !ok { 123 | return nil, fmt.Errorf("missing 'name' parameter") 124 | } 125 | 126 | database := sqladmin.Database{ 127 | Name: name, 128 | Project: project, 129 | Instance: instance, 130 | } 131 | 132 | service, err := t.Source.GetService(ctx, string(accessToken)) 133 | if err != nil { 134 | return nil, err 135 | } 136 | 137 | resp, err := service.Databases.Insert(project, instance, &database).Do() 138 | if err != nil { 139 | return nil, fmt.Errorf("error creating database: %w", err) 140 | } 141 | 142 | return resp, nil 143 | } 144 | 145 | // ParseParams parses the parameters for the tool. 146 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { 147 | return tools.ParseParams(t.AllParams, data, claims) 148 | } 149 | 150 | // Manifest returns the tool's manifest. 151 | func (t Tool) Manifest() tools.Manifest { 152 | return t.manifest 153 | } 154 | 155 | // McpManifest returns the tool's MCP manifest. 156 | func (t Tool) McpManifest() tools.McpManifest { 157 | return t.mcpManifest 158 | } 159 | 160 | // Authorized checks if the tool is authorized. 161 | func (t Tool) Authorized(verifiedAuthServices []string) bool { 162 | return true 163 | } 164 | 165 | func (t Tool) RequiresClientAuthorization() bool { 166 | return t.Source.UseClientAuthorization() 167 | } 168 | ``` -------------------------------------------------------------------------------- /docs/en/resources/tools/mongodb/mongodb-update-many.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: "mongodb-update-many" 3 | type: docs 4 | weight: 1 5 | description: > 6 | A "mongodb-update-many" tool updates all documents in a MongoDB collection that match a filter. 7 | aliases: 8 | - /resources/tools/mongodb-update-many 9 | --- 10 | 11 | ## About 12 | 13 | A `mongodb-update-many` tool updates **all** documents within a specified 14 | MongoDB collection that match a given filter. It locates the documents using a 15 | `filterPayload` and applies the modifications defined in an `updatePayload`. 16 | 17 | The tool returns an array of three integers: `[ModifiedCount, UpsertedCount, 18 | MatchedCount]`. 19 | 20 | This tool is compatible with the following source kind: 21 | 22 | * [`mongodb`](../../sources/mongodb.md) 23 | 24 | --- 25 | 26 | ## Example 27 | 28 | Here's an example configuration. This tool applies a discount to all items 29 | within a specific category and also marks them as being on sale. 30 | 31 | ```yaml 32 | tools: 33 | apply_category_discount: 34 | kind: mongodb-update-many 35 | source: my-mongo-source 36 | description: Use this tool to apply a discount to all items in a given category. 37 | database: products 38 | collection: inventory 39 | filterPayload: | 40 | { "category": {{json .category_name}} } 41 | filterParams: 42 | - name: category_name 43 | type: string 44 | description: The category of items to update. 45 | updatePayload: | 46 | { 47 | "$mul": { "price": {{json .discount_multiplier}} }, 48 | "$set": { "on_sale": true } 49 | } 50 | updateParams: 51 | - name: discount_multiplier 52 | type: number 53 | description: The multiplier to apply to the price (e.g., 0.8 for a 20% discount). 54 | canonical: false 55 | upsert: false 56 | ``` 57 | 58 | ## Reference 59 | 60 | | **field** | **type** | **required** | **description** | 61 | |:--------------|:---------|:-------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 62 | | kind | string | true | Must be `mongodb-update-many`. | 63 | | source | string | true | The name of the `mongodb` source to use. | 64 | | description | string | true | A description of the tool that is passed to the LLM. | 65 | | database | string | true | The name of the MongoDB database containing the collection. | 66 | | collection | string | true | The name of the MongoDB collection in which to update documents. | 67 | | filterPayload | string | true | The MongoDB query filter document to select the documents for updating. It's written as a Go template, using `{{json .param_name}}` to insert parameters. | 68 | | filterParams | list | true | A list of parameter objects that define the variables used in the `filterPayload`. | 69 | | updatePayload | string | true | The MongoDB update document, It's written as a Go template, using `{{json .param_name}}` to insert parameters. | 70 | | updateParams | list | true | A list of parameter objects that define the variables used in the `updatePayload`. | 71 | | canonical | bool | true | Determines if the `filterPayload` and `updatePayload` strings are parsed using MongoDB's Canonical or Relaxed Extended JSON format. **Canonical** is stricter about type representation, while **Relaxed** is more lenient. | 72 | | upsert | bool | false | If `true`, a new document is created if no document matches the `filterPayload`. Defaults to `false`. | 73 | ``` -------------------------------------------------------------------------------- /internal/tools/cloudmonitoring/cloudmonitoring.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 | "encoding/json" 20 | "fmt" 21 | "io" 22 | "net/http" 23 | 24 | "github.com/goccy/go-yaml" 25 | "github.com/googleapis/genai-toolbox/internal/sources" 26 | cloudmonitoringsrc "github.com/googleapis/genai-toolbox/internal/sources/cloudmonitoring" 27 | "github.com/googleapis/genai-toolbox/internal/tools" 28 | ) 29 | 30 | const kind string = "cloud-monitoring-query-prometheus" 31 | 32 | func init() { 33 | if !tools.Register(kind, newConfig) { 34 | panic(fmt.Sprintf("tool kind %q already registered", kind)) 35 | } 36 | } 37 | 38 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, 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 | Source string `yaml:"source" validate:"required"` 50 | Description string `yaml:"description" validate:"required"` 51 | AuthRequired []string `yaml:"authRequired"` 52 | } 53 | 54 | // validate interface 55 | var _ tools.ToolConfig = Config{} 56 | 57 | func (cfg Config) ToolConfigKind() string { 58 | return kind 59 | } 60 | 61 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { 62 | // verify source exists 63 | rawS, ok := srcs[cfg.Source] 64 | if !ok { 65 | return nil, fmt.Errorf("no source named %q configured", cfg.Source) 66 | } 67 | 68 | // verify the source is compatible 69 | s, ok := rawS.(*cloudmonitoringsrc.Source) 70 | if !ok { 71 | return nil, fmt.Errorf("invalid source for %q tool: source kind must be `cloudmonitoring`", kind) 72 | } 73 | 74 | // Define the parameters internally instead of from the config file. 75 | allParameters := tools.Parameters{ 76 | tools.NewStringParameterWithRequired("projectId", "The Id of the Google Cloud project.", true), 77 | tools.NewStringParameterWithRequired("query", "The promql query to execute.", true), 78 | } 79 | 80 | mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, allParameters) 81 | 82 | return Tool{ 83 | Name: cfg.Name, 84 | Kind: kind, 85 | Description: cfg.Description, 86 | AllParams: allParameters, 87 | BaseURL: s.BaseURL, 88 | UserAgent: s.UserAgent, 89 | Client: s.Client, 90 | manifest: tools.Manifest{Description: cfg.Description, Parameters: allParameters.Manifest()}, 91 | mcpManifest: mcpManifest, 92 | }, nil 93 | } 94 | 95 | // validate interface 96 | var _ tools.Tool = Tool{} 97 | 98 | type Tool struct { 99 | Name string `yaml:"name"` 100 | Kind string `yaml:"kind"` 101 | Description string `yaml:"description"` 102 | AllParams tools.Parameters `yaml:"allParams"` 103 | BaseURL string `yaml:"baseURL"` 104 | UserAgent string 105 | Client *http.Client 106 | manifest tools.Manifest 107 | mcpManifest tools.McpManifest 108 | } 109 | 110 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { 111 | paramsMap := params.AsMap() 112 | projectID, ok := paramsMap["projectId"].(string) 113 | if !ok { 114 | return nil, fmt.Errorf("projectId parameter not found or not a string") 115 | } 116 | query, ok := paramsMap["query"].(string) 117 | if !ok { 118 | return nil, fmt.Errorf("query parameter not found or not a string") 119 | } 120 | 121 | url := fmt.Sprintf("%s/v1/projects/%s/location/global/prometheus/api/v1/query", t.BaseURL, projectID) 122 | 123 | req, err := http.NewRequest(http.MethodGet, url, nil) 124 | if err != nil { 125 | return nil, err 126 | } 127 | 128 | q := req.URL.Query() 129 | q.Add("query", query) 130 | req.URL.RawQuery = q.Encode() 131 | 132 | req.Header.Set("User-Agent", t.UserAgent) 133 | 134 | resp, err := t.Client.Do(req) 135 | if err != nil { 136 | return nil, err 137 | } 138 | defer resp.Body.Close() 139 | 140 | body, err := io.ReadAll(resp.Body) 141 | if err != nil { 142 | return nil, fmt.Errorf("failed to read response body: %w", err) 143 | } 144 | 145 | if resp.StatusCode != http.StatusOK { 146 | return nil, fmt.Errorf("request failed: %s, body: %s", resp.Status, string(body)) 147 | } 148 | 149 | if len(body) == 0 { 150 | return nil, nil 151 | } 152 | 153 | var result map[string]any 154 | if err := json.Unmarshal(body, &result); err != nil { 155 | return nil, fmt.Errorf("failed to unmarshal json: %w, body: %s", err, string(body)) 156 | } 157 | 158 | return result, nil 159 | } 160 | 161 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { 162 | return tools.ParseParams(t.AllParams, data, claims) 163 | } 164 | 165 | func (t Tool) Manifest() tools.Manifest { 166 | return t.manifest 167 | } 168 | 169 | func (t Tool) McpManifest() tools.McpManifest { 170 | return t.mcpManifest 171 | } 172 | 173 | func (t Tool) Authorized(verifiedAuthServices []string) bool { 174 | return true 175 | } 176 | 177 | func (t Tool) RequiresClientAuthorization() bool { 178 | return false 179 | } 180 | ``` -------------------------------------------------------------------------------- /internal/tools/alloydb/alloydbgetuser/alloydbgetuser.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 alloydbgetuser 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | 21 | yaml "github.com/goccy/go-yaml" 22 | "github.com/googleapis/genai-toolbox/internal/sources" 23 | alloydbadmin "github.com/googleapis/genai-toolbox/internal/sources/alloydbadmin" 24 | "github.com/googleapis/genai-toolbox/internal/tools" 25 | ) 26 | 27 | const kind string = "alloydb-get-user" 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 | // Configuration for the get-user tool. 44 | type Config struct { 45 | Name string `yaml:"name" validate:"required"` 46 | Kind string `yaml:"kind" validate:"required"` 47 | Source string `yaml:"source" validate:"required"` 48 | Description string `yaml:"description"` 49 | AuthRequired []string `yaml:"authRequired"` 50 | BaseURL string `yaml:"baseURL"` 51 | } 52 | 53 | // validate interface 54 | var _ tools.ToolConfig = Config{} 55 | 56 | // ToolConfigKind returns the kind of the tool. 57 | func (cfg Config) ToolConfigKind() string { 58 | return kind 59 | } 60 | 61 | // Initialize initializes the tool from the configuration. 62 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { 63 | rawS, ok := srcs[cfg.Source] 64 | if !ok { 65 | return nil, fmt.Errorf("source %q not found", cfg.Source) 66 | } 67 | 68 | s, ok := rawS.(*alloydbadmin.Source) 69 | if !ok { 70 | return nil, fmt.Errorf("invalid source for %q tool: source kind must be `%s`", kind, alloydbadmin.SourceKind) 71 | } 72 | 73 | allParameters := tools.Parameters{ 74 | tools.NewStringParameter("project", "The GCP project ID."), 75 | tools.NewStringParameter("location", "The location of the cluster (e.g., 'us-central1')."), 76 | tools.NewStringParameter("cluster", "The ID of the cluster."), 77 | tools.NewStringParameter("user", "The ID of the user."), 78 | } 79 | paramManifest := allParameters.Manifest() 80 | 81 | description := cfg.Description 82 | if description == "" { 83 | description = "Retrieves details about a specific AlloyDB user." 84 | } 85 | mcpManifest := tools.GetMcpManifest(cfg.Name, description, cfg.AuthRequired, allParameters) 86 | 87 | return Tool{ 88 | Name: cfg.Name, 89 | Kind: kind, 90 | Source: s, 91 | AllParams: allParameters, 92 | manifest: tools.Manifest{Description: description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired}, 93 | mcpManifest: mcpManifest, 94 | }, nil 95 | } 96 | 97 | // Tool represents the get-user tool. 98 | type Tool struct { 99 | Name string `yaml:"name"` 100 | Kind string `yaml:"kind"` 101 | 102 | Source *alloydbadmin.Source 103 | AllParams tools.Parameters 104 | 105 | manifest tools.Manifest 106 | mcpManifest tools.McpManifest 107 | } 108 | 109 | // Invoke executes the tool's logic. 110 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { 111 | paramsMap := params.AsMap() 112 | 113 | project, ok := paramsMap["project"].(string) 114 | if !ok { 115 | return nil, fmt.Errorf("invalid or missing 'project' parameter; expected a string") 116 | } 117 | location, ok := paramsMap["location"].(string) 118 | if !ok { 119 | return nil, fmt.Errorf("invalid 'location' parameter; expected a string") 120 | } 121 | cluster, ok := paramsMap["cluster"].(string) 122 | if !ok { 123 | return nil, fmt.Errorf("invalid 'cluster' parameter; expected a string") 124 | } 125 | user, ok := paramsMap["user"].(string) 126 | if !ok { 127 | return nil, fmt.Errorf("invalid 'user' parameter; expected a string") 128 | } 129 | 130 | service, err := t.Source.GetService(ctx, string(accessToken)) 131 | if err != nil { 132 | return nil, err 133 | } 134 | 135 | urlString := fmt.Sprintf("projects/%s/locations/%s/clusters/%s/users/%s", project, location, cluster, user) 136 | 137 | resp, err := service.Projects.Locations.Clusters.Users.Get(urlString).Do() 138 | if err != nil { 139 | return nil, fmt.Errorf("error getting AlloyDB user: %w", err) 140 | } 141 | 142 | return resp, nil 143 | } 144 | 145 | // ParseParams parses the parameters for the tool. 146 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { 147 | return tools.ParseParams(t.AllParams, data, claims) 148 | } 149 | 150 | // Manifest returns the tool's manifest. 151 | func (t Tool) Manifest() tools.Manifest { 152 | return t.manifest 153 | } 154 | 155 | // McpManifest returns the tool's MCP manifest. 156 | func (t Tool) McpManifest() tools.McpManifest { 157 | return t.mcpManifest 158 | } 159 | 160 | // Authorized checks if the tool is authorized. 161 | func (t Tool) Authorized(verifiedAuthServices []string) bool { 162 | return true 163 | } 164 | 165 | func (t Tool) RequiresClientAuthorization() bool { 166 | return t.Source.UseClientAuthorization() 167 | } 168 | ``` -------------------------------------------------------------------------------- /docs/en/resources/tools/mssql/mssql-sql.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: "mssql-sql" 3 | type: docs 4 | weight: 1 5 | description: > 6 | A "mssql-sql" tool executes a pre-defined SQL statement against a SQL Server 7 | database. 8 | aliases: 9 | - /resources/tools/mssql-sql 10 | --- 11 | 12 | ## About 13 | 14 | A `mssql-sql` tool executes a pre-defined SQL statement against a SQL Server 15 | database. It's compatible with any of the following sources: 16 | 17 | - [cloud-sql-mssql](../../sources/cloud-sql-mssql.md) 18 | - [mssql](../../sources/mssql.md) 19 | 20 | Toolbox supports the [prepare statement syntax][prepare-statement] of MS SQL 21 | Server and expects parameters in the SQL query to be in the form of either 22 | `@Name` or `@p1` to `@pN` (ordinal position). 23 | 24 | ```go 25 | db.QueryContext(ctx, `select * from t where ID = @ID and Name = @p2;`, sql.Named("ID", 6), "Bob") 26 | ``` 27 | 28 | [prepare-statement]: https://learn.microsoft.com/sql/relational-databases/system-stored-procedures/sp-prepare-transact-sql?view=sql-server-ver16 29 | 30 | ## Example 31 | 32 | > **Note:** This tool uses parameterized queries to prevent SQL injections. 33 | > Query parameters can be used as substitutes for arbitrary expressions. 34 | > Parameters cannot be used as substitutes for identifiers, column names, table 35 | > names, or other parts of the query. 36 | 37 | ```yaml 38 | tools: 39 | search_flights_by_number: 40 | kind: mssql-sql 41 | source: my-instance 42 | statement: | 43 | SELECT * FROM flights 44 | WHERE airline = @airline 45 | AND flight_number = @flight_number 46 | LIMIT 10 47 | description: | 48 | Use this tool to get information for a specific flight. 49 | Takes an airline code and flight number and returns info on the flight. 50 | Do NOT use this tool with a flight id. Do NOT guess an airline code or flight number. 51 | A airline code is a code for an airline service consisting of two-character 52 | airline designator and followed by flight number, which is 1 to 4 digit number. 53 | For example, if given CY 0123, the airline is "CY", and flight_number is "123". 54 | Another example for this is DL 1234, the airline is "DL", and flight_number is "1234". 55 | If the tool returns more than one option choose the date closes to today. 56 | Example: 57 | {{ 58 | "airline": "CY", 59 | "flight_number": "888", 60 | }} 61 | Example: 62 | {{ 63 | "airline": "DL", 64 | "flight_number": "1234", 65 | }} 66 | parameters: 67 | - name: airline 68 | type: string 69 | description: Airline unique 2 letter identifier 70 | - name: flight_number 71 | type: string 72 | description: 1 to 4 digit number 73 | ``` 74 | 75 | ### Example with Template Parameters 76 | 77 | > **Note:** This tool allows direct modifications to the SQL statement, 78 | > including identifiers, column names, and table names. **This makes it more 79 | > vulnerable to SQL injections**. Using basic parameters only (see above) is 80 | > recommended for performance and safety reasons. For more details, please check 81 | > [templateParameters](..#template-parameters). 82 | 83 | ```yaml 84 | tools: 85 | list_table: 86 | kind: mssql-sql 87 | source: my-instance 88 | statement: | 89 | SELECT * FROM {{.tableName}}; 90 | description: | 91 | Use this tool to list all information from a specific table. 92 | Example: 93 | {{ 94 | "tableName": "flights", 95 | }} 96 | templateParameters: 97 | - name: tableName 98 | type: string 99 | description: Table to select from 100 | ``` 101 | 102 | ## Reference 103 | 104 | | **field** | **type** | **required** | **description** | 105 | |--------------------|:------------------------------------------------:|:------------:|--------------------------------------------------------------------------------------------------------------------------------------------| 106 | | kind | string | true | Must be "mssql-sql". | 107 | | source | string | true | Name of the source the T-SQL statement should execute on. | 108 | | description | string | true | Description of the tool that is passed to the LLM. | 109 | | statement | string | true | SQL statement to execute. | 110 | | parameters | [parameters](../#specifying-parameters) | false | List of [parameters](../#specifying-parameters) that will be inserted into the SQL statement. | 111 | | templateParameters | [templateParameters](..#template-parameters) | false | List of [templateParameters](..#template-parameters) that will be inserted into the SQL statement before executing prepared statement. | 112 | ``` -------------------------------------------------------------------------------- /internal/tools/looker/lookerrunlook/lookerrunlook.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 lookerrunlook 15 | 16 | import ( 17 | "context" 18 | "encoding/json" 19 | "fmt" 20 | 21 | yaml "github.com/goccy/go-yaml" 22 | "github.com/googleapis/genai-toolbox/internal/sources" 23 | lookersrc "github.com/googleapis/genai-toolbox/internal/sources/looker" 24 | "github.com/googleapis/genai-toolbox/internal/tools" 25 | "github.com/googleapis/genai-toolbox/internal/tools/looker/lookercommon" 26 | "github.com/googleapis/genai-toolbox/internal/util" 27 | 28 | "github.com/looker-open-source/sdk-codegen/go/rtl" 29 | v4 "github.com/looker-open-source/sdk-codegen/go/sdk/v4" 30 | ) 31 | 32 | const kind string = "looker-run-look" 33 | 34 | func init() { 35 | if !tools.Register(kind, newConfig) { 36 | panic(fmt.Sprintf("tool kind %q already registered", kind)) 37 | } 38 | } 39 | 40 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, 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 | Source string `yaml:"source" validate:"required"` 52 | Description string `yaml:"description" validate:"required"` 53 | AuthRequired []string `yaml:"authRequired"` 54 | } 55 | 56 | // validate interface 57 | var _ tools.ToolConfig = Config{} 58 | 59 | func (cfg Config) ToolConfigKind() string { 60 | return kind 61 | } 62 | 63 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { 64 | // verify source exists 65 | rawS, ok := srcs[cfg.Source] 66 | if !ok { 67 | return nil, fmt.Errorf("no source named %q configured", cfg.Source) 68 | } 69 | 70 | // verify the source is compatible 71 | s, ok := rawS.(*lookersrc.Source) 72 | if !ok { 73 | return nil, fmt.Errorf("invalid source for %q tool: source kind must be `looker`", kind) 74 | } 75 | 76 | lookidParameter := tools.NewStringParameter("look_id", "The id of the look to run.") 77 | limitParameter := tools.NewIntParameterWithDefault("limit", 500, "The row limit. Default 500") 78 | 79 | parameters := tools.Parameters{ 80 | lookidParameter, 81 | limitParameter, 82 | } 83 | 84 | mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters) 85 | 86 | // finish tool setup 87 | return Tool{ 88 | Name: cfg.Name, 89 | Kind: kind, 90 | Parameters: parameters, 91 | AuthRequired: cfg.AuthRequired, 92 | UseClientOAuth: s.UseClientOAuth, 93 | Client: s.Client, 94 | ApiSettings: s.ApiSettings, 95 | manifest: tools.Manifest{ 96 | Description: cfg.Description, 97 | Parameters: parameters.Manifest(), 98 | AuthRequired: cfg.AuthRequired, 99 | }, 100 | mcpManifest: mcpManifest, 101 | }, nil 102 | } 103 | 104 | // validate interface 105 | var _ tools.Tool = Tool{} 106 | 107 | type Tool struct { 108 | Name string `yaml:"name"` 109 | Kind string `yaml:"kind"` 110 | UseClientOAuth bool 111 | Client *v4.LookerSDK 112 | ApiSettings *rtl.ApiSettings 113 | AuthRequired []string `yaml:"authRequired"` 114 | Parameters tools.Parameters `yaml:"parameters"` 115 | manifest tools.Manifest 116 | mcpManifest tools.McpManifest 117 | } 118 | 119 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { 120 | logger, err := util.LoggerFromContext(ctx) 121 | if err != nil { 122 | return nil, fmt.Errorf("unable to get logger from ctx: %s", err) 123 | } 124 | logger.DebugContext(ctx, "params = ", params) 125 | paramsMap := params.AsMap() 126 | 127 | look_id := paramsMap["look_id"].(string) 128 | limit := int64(paramsMap["limit"].(int)) 129 | 130 | sdk, err := lookercommon.GetLookerSDK(t.UseClientOAuth, t.ApiSettings, t.Client, accessToken) 131 | if err != nil { 132 | return nil, fmt.Errorf("error getting sdk: %w", err) 133 | } 134 | req := v4.RequestRunLook{ 135 | LookId: look_id, 136 | ResultFormat: "json", 137 | Limit: &limit, 138 | } 139 | resp, err := sdk.RunLook(req, t.ApiSettings) 140 | if err != nil { 141 | return nil, fmt.Errorf("error making run_look request: %s", err) 142 | } 143 | logger.DebugContext(ctx, "resp = ", resp) 144 | 145 | var data []any 146 | e := json.Unmarshal([]byte(resp), &data) 147 | if e != nil { 148 | return nil, fmt.Errorf("error Unmarshaling run_look response: %s", e) 149 | } 150 | 151 | logger.DebugContext(ctx, "data = ", data) 152 | 153 | return data, nil 154 | } 155 | 156 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { 157 | return tools.ParseParams(t.Parameters, data, claims) 158 | } 159 | 160 | func (t Tool) Manifest() tools.Manifest { 161 | return t.manifest 162 | } 163 | 164 | func (t Tool) McpManifest() tools.McpManifest { 165 | return t.mcpManifest 166 | } 167 | 168 | func (t Tool) Authorized(verifiedAuthServices []string) bool { 169 | return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices) 170 | } 171 | 172 | func (t Tool) RequiresClientAuthorization() bool { 173 | return t.UseClientOAuth 174 | } 175 | ```