This is page 20 of 47. 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-create-project-file.md │ │ │ ├── looker-delete-project-file.md │ │ │ ├── looker-dev-mode.md │ │ │ ├── looker-get-dashboards.md │ │ │ ├── looker-get-dimensions.md │ │ │ ├── looker-get-explores.md │ │ │ ├── looker-get-filters.md │ │ │ ├── looker-get-looks.md │ │ │ ├── looker-get-measures.md │ │ │ ├── looker-get-models.md │ │ │ ├── looker-get-parameters.md │ │ │ ├── looker-get-project-file.md │ │ │ ├── looker-get-project-files.md │ │ │ ├── looker-get-projects.md │ │ │ ├── looker-health-analyze.md │ │ │ ├── looker-health-pulse.md │ │ │ ├── looker-health-vacuum.md │ │ │ ├── looker-make-dashboard.md │ │ │ ├── looker-make-look.md │ │ │ ├── looker-query-sql.md │ │ │ ├── looker-query-url.md │ │ │ ├── looker-query.md │ │ │ ├── looker-run-look.md │ │ │ └── looker-update-project-file.md │ │ ├── mongodb │ │ │ ├── _index.md │ │ │ ├── mongodb-aggregate.md │ │ │ ├── mongodb-delete-many.md │ │ │ ├── mongodb-delete-one.md │ │ │ ├── mongodb-find-one.md │ │ │ ├── mongodb-find.md │ │ │ ├── mongodb-insert-many.md │ │ │ ├── mongodb-insert-one.md │ │ │ ├── mongodb-update-many.md │ │ │ └── mongodb-update-one.md │ │ ├── mssql │ │ │ ├── _index.md │ │ │ ├── mssql-execute-sql.md │ │ │ ├── mssql-list-tables.md │ │ │ └── mssql-sql.md │ │ ├── mysql │ │ │ ├── _index.md │ │ │ ├── mysql-execute-sql.md │ │ │ ├── mysql-list-active-queries.md │ │ │ ├── mysql-list-table-fragmentation.md │ │ │ ├── mysql-list-tables-missing-unique-indexes.md │ │ │ ├── mysql-list-tables.md │ │ │ └── mysql-sql.md │ │ ├── neo4j │ │ │ ├── _index.md │ │ │ ├── neo4j-cypher.md │ │ │ ├── neo4j-execute-cypher.md │ │ │ └── neo4j-schema.md │ │ ├── oceanbase │ │ │ ├── _index.md │ │ │ ├── oceanbase-execute-sql.md │ │ │ └── oceanbase-sql.md │ │ ├── oracle │ │ │ ├── _index.md │ │ │ ├── oracle-execute-sql.md │ │ │ └── oracle-sql.md │ │ ├── postgres │ │ │ ├── _index.md │ │ │ ├── postgres-execute-sql.md │ │ │ ├── postgres-list-active-queries.md │ │ │ ├── postgres-list-available-extensions.md │ │ │ ├── postgres-list-installed-extensions.md │ │ │ ├── postgres-list-tables.md │ │ │ └── postgres-sql.md │ │ ├── redis │ │ │ ├── _index.md │ │ │ └── redis.md │ │ ├── spanner │ │ │ ├── _index.md │ │ │ ├── spanner-execute-sql.md │ │ │ ├── spanner-list-tables.md │ │ │ └── spanner-sql.md │ │ ├── sqlite │ │ │ ├── _index.md │ │ │ ├── sqlite-execute-sql.md │ │ │ └── sqlite-sql.md │ │ ├── tidb │ │ │ ├── _index.md │ │ │ ├── tidb-execute-sql.md │ │ │ └── tidb-sql.md │ │ ├── trino │ │ │ ├── _index.md │ │ │ ├── trino-execute-sql.md │ │ │ └── trino-sql.md │ │ ├── utility │ │ │ ├── _index.md │ │ │ └── wait.md │ │ ├── valkey │ │ │ ├── _index.md │ │ │ └── valkey.md │ │ └── yuagbytedb │ │ ├── _index.md │ │ └── yugabytedb-sql.md │ ├── samples │ │ ├── _index.md │ │ ├── alloydb │ │ │ ├── _index.md │ │ │ ├── ai-nl │ │ │ │ ├── alloydb_ai_nl.ipynb │ │ │ │ └── index.md │ │ │ └── mcp_quickstart.md │ │ ├── bigquery │ │ │ ├── _index.md │ │ │ ├── colab_quickstart_bigquery.ipynb │ │ │ ├── local_quickstart.md │ │ │ └── mcp_quickstart │ │ │ ├── _index.md │ │ │ ├── inspector_tools.png │ │ │ └── inspector.png │ │ └── looker │ │ ├── _index.md │ │ ├── looker_gemini_oauth │ │ │ ├── _index.md │ │ │ ├── authenticated.png │ │ │ ├── authorize.png │ │ │ └── registration.png │ │ ├── looker_gemini.md │ │ └── looker_mcp_inspector │ │ ├── _index.md │ │ ├── inspector_tools.png │ │ └── inspector.png │ └── sdks │ ├── _index.md │ ├── go-sdk.md │ ├── js-sdk.md │ └── python-sdk.md ├── gemini-extension.json ├── go.mod ├── go.sum ├── internal │ ├── auth │ │ ├── auth.go │ │ └── google │ │ └── google.go │ ├── log │ │ ├── handler.go │ │ ├── log_test.go │ │ ├── log.go │ │ └── logger.go │ ├── prebuiltconfigs │ │ ├── prebuiltconfigs_test.go │ │ ├── prebuiltconfigs.go │ │ └── tools │ │ ├── alloydb-postgres-admin.yaml │ │ ├── alloydb-postgres-observability.yaml │ │ ├── alloydb-postgres.yaml │ │ ├── bigquery.yaml │ │ ├── clickhouse.yaml │ │ ├── cloud-sql-mssql-admin.yaml │ │ ├── cloud-sql-mssql-observability.yaml │ │ ├── cloud-sql-mssql.yaml │ │ ├── cloud-sql-mysql-admin.yaml │ │ ├── cloud-sql-mysql-observability.yaml │ │ ├── cloud-sql-mysql.yaml │ │ ├── cloud-sql-postgres-admin.yaml │ │ ├── cloud-sql-postgres-observability.yaml │ │ ├── cloud-sql-postgres.yaml │ │ ├── dataplex.yaml │ │ ├── firestore.yaml │ │ ├── looker-conversational-analytics.yaml │ │ ├── looker.yaml │ │ ├── mssql.yaml │ │ ├── mysql.yaml │ │ ├── neo4j.yaml │ │ ├── oceanbase.yaml │ │ ├── postgres.yaml │ │ ├── spanner-postgres.yaml │ │ ├── spanner.yaml │ │ └── sqlite.yaml │ ├── server │ │ ├── api_test.go │ │ ├── api.go │ │ ├── common_test.go │ │ ├── config.go │ │ ├── mcp │ │ │ ├── jsonrpc │ │ │ │ ├── jsonrpc_test.go │ │ │ │ └── jsonrpc.go │ │ │ ├── mcp.go │ │ │ ├── util │ │ │ │ └── lifecycle.go │ │ │ ├── v20241105 │ │ │ │ ├── method.go │ │ │ │ └── types.go │ │ │ ├── v20250326 │ │ │ │ ├── method.go │ │ │ │ └── types.go │ │ │ └── v20250618 │ │ │ ├── method.go │ │ │ └── types.go │ │ ├── mcp_test.go │ │ ├── mcp.go │ │ ├── server_test.go │ │ ├── server.go │ │ ├── static │ │ │ ├── assets │ │ │ │ └── mcptoolboxlogo.png │ │ │ ├── css │ │ │ │ └── style.css │ │ │ ├── index.html │ │ │ ├── js │ │ │ │ ├── auth.js │ │ │ │ ├── loadTools.js │ │ │ │ ├── mainContent.js │ │ │ │ ├── navbar.js │ │ │ │ ├── runTool.js │ │ │ │ ├── toolDisplay.js │ │ │ │ ├── tools.js │ │ │ │ └── toolsets.js │ │ │ ├── tools.html │ │ │ └── toolsets.html │ │ ├── web_test.go │ │ └── web.go │ ├── sources │ │ ├── alloydbadmin │ │ │ ├── alloydbadmin_test.go │ │ │ └── alloydbadmin.go │ │ ├── alloydbpg │ │ │ ├── alloydb_pg_test.go │ │ │ └── alloydb_pg.go │ │ ├── bigquery │ │ │ ├── bigquery_test.go │ │ │ └── bigquery.go │ │ ├── bigtable │ │ │ ├── bigtable_test.go │ │ │ └── bigtable.go │ │ ├── cassandra │ │ │ ├── cassandra_test.go │ │ │ └── cassandra.go │ │ ├── clickhouse │ │ │ ├── clickhouse_test.go │ │ │ └── clickhouse.go │ │ ├── cloudmonitoring │ │ │ ├── cloud_monitoring_test.go │ │ │ └── cloud_monitoring.go │ │ ├── cloudsqladmin │ │ │ ├── cloud_sql_admin_test.go │ │ │ └── cloud_sql_admin.go │ │ ├── cloudsqlmssql │ │ │ ├── cloud_sql_mssql_test.go │ │ │ └── cloud_sql_mssql.go │ │ ├── cloudsqlmysql │ │ │ ├── cloud_sql_mysql_test.go │ │ │ └── cloud_sql_mysql.go │ │ ├── cloudsqlpg │ │ │ ├── cloud_sql_pg_test.go │ │ │ └── cloud_sql_pg.go │ │ ├── couchbase │ │ │ ├── couchbase_test.go │ │ │ └── couchbase.go │ │ ├── dataplex │ │ │ ├── dataplex_test.go │ │ │ └── dataplex.go │ │ ├── dgraph │ │ │ ├── dgraph_test.go │ │ │ └── dgraph.go │ │ ├── dialect.go │ │ ├── firebird │ │ │ ├── firebird_test.go │ │ │ └── firebird.go │ │ ├── firestore │ │ │ ├── firestore_test.go │ │ │ └── firestore.go │ │ ├── http │ │ │ ├── http_test.go │ │ │ └── http.go │ │ ├── ip_type.go │ │ ├── looker │ │ │ ├── looker_test.go │ │ │ └── looker.go │ │ ├── mongodb │ │ │ ├── mongodb_test.go │ │ │ └── mongodb.go │ │ ├── mssql │ │ │ ├── mssql_test.go │ │ │ └── mssql.go │ │ ├── mysql │ │ │ ├── mysql_test.go │ │ │ └── mysql.go │ │ ├── neo4j │ │ │ ├── neo4j_test.go │ │ │ └── neo4j.go │ │ ├── oceanbase │ │ │ ├── oceanbase_test.go │ │ │ └── oceanbase.go │ │ ├── oracle │ │ │ └── oracle.go │ │ ├── postgres │ │ │ ├── postgres_test.go │ │ │ └── postgres.go │ │ ├── redis │ │ │ ├── redis_test.go │ │ │ └── redis.go │ │ ├── sources.go │ │ ├── spanner │ │ │ ├── spanner_test.go │ │ │ └── spanner.go │ │ ├── sqlite │ │ │ ├── sqlite_test.go │ │ │ └── sqlite.go │ │ ├── tidb │ │ │ ├── tidb_test.go │ │ │ └── tidb.go │ │ ├── trino │ │ │ ├── trino_test.go │ │ │ └── trino.go │ │ ├── util.go │ │ ├── valkey │ │ │ ├── valkey_test.go │ │ │ └── valkey.go │ │ └── yugabytedb │ │ ├── yugabytedb_test.go │ │ └── yugabytedb.go │ ├── telemetry │ │ ├── instrumentation.go │ │ └── telemetry.go │ ├── testutils │ │ └── testutils.go │ ├── tools │ │ ├── alloydb │ │ │ ├── alloydbcreatecluster │ │ │ │ ├── alloydbcreatecluster_test.go │ │ │ │ └── alloydbcreatecluster.go │ │ │ ├── alloydbcreateinstance │ │ │ │ ├── alloydbcreateinstance_test.go │ │ │ │ └── alloydbcreateinstance.go │ │ │ ├── alloydbcreateuser │ │ │ │ ├── alloydbcreateuser_test.go │ │ │ │ └── alloydbcreateuser.go │ │ │ ├── alloydbgetcluster │ │ │ │ ├── alloydbgetcluster_test.go │ │ │ │ └── alloydbgetcluster.go │ │ │ ├── alloydbgetinstance │ │ │ │ ├── alloydbgetinstance_test.go │ │ │ │ └── alloydbgetinstance.go │ │ │ ├── alloydbgetuser │ │ │ │ ├── alloydbgetuser_test.go │ │ │ │ └── alloydbgetuser.go │ │ │ ├── alloydblistclusters │ │ │ │ ├── alloydblistclusters_test.go │ │ │ │ └── alloydblistclusters.go │ │ │ ├── alloydblistinstances │ │ │ │ ├── alloydblistinstances_test.go │ │ │ │ └── alloydblistinstances.go │ │ │ ├── alloydblistusers │ │ │ │ ├── alloydblistusers_test.go │ │ │ │ └── alloydblistusers.go │ │ │ └── alloydbwaitforoperation │ │ │ ├── alloydbwaitforoperation_test.go │ │ │ └── alloydbwaitforoperation.go │ │ ├── alloydbainl │ │ │ ├── alloydbainl_test.go │ │ │ └── alloydbainl.go │ │ ├── bigquery │ │ │ ├── bigqueryanalyzecontribution │ │ │ │ ├── bigqueryanalyzecontribution_test.go │ │ │ │ └── bigqueryanalyzecontribution.go │ │ │ ├── bigquerycommon │ │ │ │ ├── table_name_parser_test.go │ │ │ │ ├── table_name_parser.go │ │ │ │ └── util.go │ │ │ ├── bigqueryconversationalanalytics │ │ │ │ ├── bigqueryconversationalanalytics_test.go │ │ │ │ └── bigqueryconversationalanalytics.go │ │ │ ├── bigqueryexecutesql │ │ │ │ ├── bigqueryexecutesql_test.go │ │ │ │ └── bigqueryexecutesql.go │ │ │ ├── bigqueryforecast │ │ │ │ ├── bigqueryforecast_test.go │ │ │ │ └── bigqueryforecast.go │ │ │ ├── bigquerygetdatasetinfo │ │ │ │ ├── bigquerygetdatasetinfo_test.go │ │ │ │ └── bigquerygetdatasetinfo.go │ │ │ ├── bigquerygettableinfo │ │ │ │ ├── bigquerygettableinfo_test.go │ │ │ │ └── bigquerygettableinfo.go │ │ │ ├── bigquerylistdatasetids │ │ │ │ ├── bigquerylistdatasetids_test.go │ │ │ │ └── bigquerylistdatasetids.go │ │ │ ├── bigquerylisttableids │ │ │ │ ├── bigquerylisttableids_test.go │ │ │ │ └── bigquerylisttableids.go │ │ │ ├── bigquerysearchcatalog │ │ │ │ ├── bigquerysearchcatalog_test.go │ │ │ │ └── bigquerysearchcatalog.go │ │ │ └── bigquerysql │ │ │ ├── bigquerysql_test.go │ │ │ └── bigquerysql.go │ │ ├── bigtable │ │ │ ├── bigtable_test.go │ │ │ └── bigtable.go │ │ ├── cassandra │ │ │ └── cassandracql │ │ │ ├── cassandracql_test.go │ │ │ └── cassandracql.go │ │ ├── clickhouse │ │ │ ├── clickhouseexecutesql │ │ │ │ ├── clickhouseexecutesql_test.go │ │ │ │ └── clickhouseexecutesql.go │ │ │ ├── clickhouselistdatabases │ │ │ │ ├── clickhouselistdatabases_test.go │ │ │ │ └── clickhouselistdatabases.go │ │ │ ├── clickhouselisttables │ │ │ │ ├── clickhouselisttables_test.go │ │ │ │ └── clickhouselisttables.go │ │ │ └── clickhousesql │ │ │ ├── clickhousesql_test.go │ │ │ └── clickhousesql.go │ │ ├── cloudmonitoring │ │ │ ├── cloudmonitoring_test.go │ │ │ └── cloudmonitoring.go │ │ ├── cloudsql │ │ │ ├── cloudsqlcreatedatabase │ │ │ │ ├── cloudsqlcreatedatabase_test.go │ │ │ │ └── cloudsqlcreatedatabase.go │ │ │ ├── cloudsqlcreateusers │ │ │ │ ├── cloudsqlcreateusers_test.go │ │ │ │ └── cloudsqlcreateusers.go │ │ │ ├── cloudsqlgetinstances │ │ │ │ ├── cloudsqlgetinstances_test.go │ │ │ │ └── cloudsqlgetinstances.go │ │ │ ├── cloudsqllistdatabases │ │ │ │ ├── cloudsqllistdatabases_test.go │ │ │ │ └── cloudsqllistdatabases.go │ │ │ ├── cloudsqllistinstances │ │ │ │ ├── cloudsqllistinstances_test.go │ │ │ │ └── cloudsqllistinstances.go │ │ │ └── cloudsqlwaitforoperation │ │ │ ├── cloudsqlwaitforoperation_test.go │ │ │ └── cloudsqlwaitforoperation.go │ │ ├── cloudsqlmssql │ │ │ └── cloudsqlmssqlcreateinstance │ │ │ ├── cloudsqlmssqlcreateinstance_test.go │ │ │ └── cloudsqlmssqlcreateinstance.go │ │ ├── cloudsqlmysql │ │ │ └── cloudsqlmysqlcreateinstance │ │ │ ├── cloudsqlmysqlcreateinstance_test.go │ │ │ └── cloudsqlmysqlcreateinstance.go │ │ ├── cloudsqlpg │ │ │ └── cloudsqlpgcreateinstances │ │ │ ├── cloudsqlpgcreateinstances_test.go │ │ │ └── cloudsqlpgcreateinstances.go │ │ ├── common_test.go │ │ ├── common.go │ │ ├── couchbase │ │ │ ├── couchbase_test.go │ │ │ └── couchbase.go │ │ ├── dataform │ │ │ └── dataformcompilelocal │ │ │ ├── dataformcompilelocal_test.go │ │ │ └── dataformcompilelocal.go │ │ ├── dataplex │ │ │ ├── dataplexlookupentry │ │ │ │ ├── dataplexlookupentry_test.go │ │ │ │ └── dataplexlookupentry.go │ │ │ ├── dataplexsearchaspecttypes │ │ │ │ ├── dataplexsearchaspecttypes_test.go │ │ │ │ └── dataplexsearchaspecttypes.go │ │ │ └── dataplexsearchentries │ │ │ ├── dataplexsearchentries_test.go │ │ │ └── dataplexsearchentries.go │ │ ├── dgraph │ │ │ ├── dgraph_test.go │ │ │ └── dgraph.go │ │ ├── firebird │ │ │ ├── firebirdexecutesql │ │ │ │ ├── firebirdexecutesql_test.go │ │ │ │ └── firebirdexecutesql.go │ │ │ └── firebirdsql │ │ │ ├── firebirdsql_test.go │ │ │ └── firebirdsql.go │ │ ├── firestore │ │ │ ├── firestoreadddocuments │ │ │ │ ├── firestoreadddocuments_test.go │ │ │ │ └── firestoreadddocuments.go │ │ │ ├── firestoredeletedocuments │ │ │ │ ├── firestoredeletedocuments_test.go │ │ │ │ └── firestoredeletedocuments.go │ │ │ ├── firestoregetdocuments │ │ │ │ ├── firestoregetdocuments_test.go │ │ │ │ └── firestoregetdocuments.go │ │ │ ├── firestoregetrules │ │ │ │ ├── firestoregetrules_test.go │ │ │ │ └── firestoregetrules.go │ │ │ ├── firestorelistcollections │ │ │ │ ├── firestorelistcollections_test.go │ │ │ │ └── firestorelistcollections.go │ │ │ ├── firestorequery │ │ │ │ ├── firestorequery_test.go │ │ │ │ └── firestorequery.go │ │ │ ├── firestorequerycollection │ │ │ │ ├── firestorequerycollection_test.go │ │ │ │ └── firestorequerycollection.go │ │ │ ├── firestoreupdatedocument │ │ │ │ ├── firestoreupdatedocument_test.go │ │ │ │ └── firestoreupdatedocument.go │ │ │ ├── firestorevalidaterules │ │ │ │ ├── firestorevalidaterules_test.go │ │ │ │ └── firestorevalidaterules.go │ │ │ └── util │ │ │ ├── converter_test.go │ │ │ ├── converter.go │ │ │ ├── validator_test.go │ │ │ └── validator.go │ │ ├── http │ │ │ ├── http_test.go │ │ │ └── http.go │ │ ├── http_method.go │ │ ├── looker │ │ │ ├── lookeradddashboardelement │ │ │ │ ├── lookeradddashboardelement_test.go │ │ │ │ └── lookeradddashboardelement.go │ │ │ ├── lookercommon │ │ │ │ ├── lookercommon_test.go │ │ │ │ └── lookercommon.go │ │ │ ├── lookerconversationalanalytics │ │ │ │ ├── lookerconversationalanalytics_test.go │ │ │ │ └── lookerconversationalanalytics.go │ │ │ ├── lookercreateprojectfile │ │ │ │ ├── lookercreateprojectfile_test.go │ │ │ │ └── lookercreateprojectfile.go │ │ │ ├── lookerdeleteprojectfile │ │ │ │ ├── lookerdeleteprojectfile_test.go │ │ │ │ └── lookerdeleteprojectfile.go │ │ │ ├── lookerdevmode │ │ │ │ ├── lookerdevmode_test.go │ │ │ │ └── lookerdevmode.go │ │ │ ├── lookergetdashboards │ │ │ │ ├── lookergetdashboards_test.go │ │ │ │ └── lookergetdashboards.go │ │ │ ├── lookergetdimensions │ │ │ │ ├── lookergetdimensions_test.go │ │ │ │ └── lookergetdimensions.go │ │ │ ├── lookergetexplores │ │ │ │ ├── lookergetexplores_test.go │ │ │ │ └── lookergetexplores.go │ │ │ ├── lookergetfilters │ │ │ │ ├── lookergetfilters_test.go │ │ │ │ └── lookergetfilters.go │ │ │ ├── lookergetlooks │ │ │ │ ├── lookergetlooks_test.go │ │ │ │ └── lookergetlooks.go │ │ │ ├── lookergetmeasures │ │ │ │ ├── lookergetmeasures_test.go │ │ │ │ └── lookergetmeasures.go │ │ │ ├── lookergetmodels │ │ │ │ ├── lookergetmodels_test.go │ │ │ │ └── lookergetmodels.go │ │ │ ├── lookergetparameters │ │ │ │ ├── lookergetparameters_test.go │ │ │ │ └── lookergetparameters.go │ │ │ ├── lookergetprojectfile │ │ │ │ ├── lookergetprojectfile_test.go │ │ │ │ └── lookergetprojectfile.go │ │ │ ├── lookergetprojectfiles │ │ │ │ ├── lookergetprojectfiles_test.go │ │ │ │ └── lookergetprojectfiles.go │ │ │ ├── lookergetprojects │ │ │ │ ├── lookergetprojects_test.go │ │ │ │ └── lookergetprojects.go │ │ │ ├── lookerhealthanalyze │ │ │ │ ├── lookerhealthanalyze_test.go │ │ │ │ └── lookerhealthanalyze.go │ │ │ ├── lookerhealthpulse │ │ │ │ ├── lookerhealthpulse_test.go │ │ │ │ └── lookerhealthpulse.go │ │ │ ├── lookerhealthvacuum │ │ │ │ ├── lookerhealthvacuum_test.go │ │ │ │ └── lookerhealthvacuum.go │ │ │ ├── lookermakedashboard │ │ │ │ ├── lookermakedashboard_test.go │ │ │ │ └── lookermakedashboard.go │ │ │ ├── lookermakelook │ │ │ │ ├── lookermakelook_test.go │ │ │ │ └── lookermakelook.go │ │ │ ├── lookerquery │ │ │ │ ├── lookerquery_test.go │ │ │ │ └── lookerquery.go │ │ │ ├── lookerquerysql │ │ │ │ ├── lookerquerysql_test.go │ │ │ │ └── lookerquerysql.go │ │ │ ├── lookerqueryurl │ │ │ │ ├── lookerqueryurl_test.go │ │ │ │ └── lookerqueryurl.go │ │ │ ├── lookerrunlook │ │ │ │ ├── lookerrunlook_test.go │ │ │ │ └── lookerrunlook.go │ │ │ └── lookerupdateprojectfile │ │ │ ├── lookerupdateprojectfile_test.go │ │ │ └── lookerupdateprojectfile.go │ │ ├── mongodb │ │ │ ├── mongodbaggregate │ │ │ │ ├── mongodbaggregate_test.go │ │ │ │ └── mongodbaggregate.go │ │ │ ├── mongodbdeletemany │ │ │ │ ├── mongodbdeletemany_test.go │ │ │ │ └── mongodbdeletemany.go │ │ │ ├── mongodbdeleteone │ │ │ │ ├── mongodbdeleteone_test.go │ │ │ │ └── mongodbdeleteone.go │ │ │ ├── mongodbfind │ │ │ │ ├── mongodbfind_test.go │ │ │ │ └── mongodbfind.go │ │ │ ├── mongodbfindone │ │ │ │ ├── mongodbfindone_test.go │ │ │ │ └── mongodbfindone.go │ │ │ ├── mongodbinsertmany │ │ │ │ ├── mongodbinsertmany_test.go │ │ │ │ └── mongodbinsertmany.go │ │ │ ├── mongodbinsertone │ │ │ │ ├── mongodbinsertone_test.go │ │ │ │ └── mongodbinsertone.go │ │ │ ├── mongodbupdatemany │ │ │ │ ├── mongodbupdatemany_test.go │ │ │ │ └── mongodbupdatemany.go │ │ │ └── mongodbupdateone │ │ │ ├── mongodbupdateone_test.go │ │ │ └── mongodbupdateone.go │ │ ├── mssql │ │ │ ├── mssqlexecutesql │ │ │ │ ├── mssqlexecutesql_test.go │ │ │ │ └── mssqlexecutesql.go │ │ │ ├── mssqllisttables │ │ │ │ ├── mssqllisttables_test.go │ │ │ │ └── mssqllisttables.go │ │ │ └── mssqlsql │ │ │ ├── mssqlsql_test.go │ │ │ └── mssqlsql.go │ │ ├── mysql │ │ │ ├── mysqlcommon │ │ │ │ └── mysqlcommon.go │ │ │ ├── mysqlexecutesql │ │ │ │ ├── mysqlexecutesql_test.go │ │ │ │ └── mysqlexecutesql.go │ │ │ ├── mysqllistactivequeries │ │ │ │ ├── mysqllistactivequeries_test.go │ │ │ │ └── mysqllistactivequeries.go │ │ │ ├── mysqllisttablefragmentation │ │ │ │ ├── mysqllisttablefragmentation_test.go │ │ │ │ └── mysqllisttablefragmentation.go │ │ │ ├── mysqllisttables │ │ │ │ ├── mysqllisttables_test.go │ │ │ │ └── mysqllisttables.go │ │ │ ├── mysqllisttablesmissinguniqueindexes │ │ │ │ ├── mysqllisttablesmissinguniqueindexes_test.go │ │ │ │ └── mysqllisttablesmissinguniqueindexes.go │ │ │ └── mysqlsql │ │ │ ├── mysqlsql_test.go │ │ │ └── mysqlsql.go │ │ ├── neo4j │ │ │ ├── neo4jcypher │ │ │ │ ├── neo4jcypher_test.go │ │ │ │ └── neo4jcypher.go │ │ │ ├── neo4jexecutecypher │ │ │ │ ├── classifier │ │ │ │ │ ├── classifier_test.go │ │ │ │ │ └── classifier.go │ │ │ │ ├── neo4jexecutecypher_test.go │ │ │ │ └── neo4jexecutecypher.go │ │ │ └── neo4jschema │ │ │ ├── cache │ │ │ │ ├── cache_test.go │ │ │ │ └── cache.go │ │ │ ├── helpers │ │ │ │ ├── helpers_test.go │ │ │ │ └── helpers.go │ │ │ ├── neo4jschema_test.go │ │ │ ├── neo4jschema.go │ │ │ └── types │ │ │ └── types.go │ │ ├── oceanbase │ │ │ ├── oceanbaseexecutesql │ │ │ │ ├── oceanbaseexecutesql_test.go │ │ │ │ └── oceanbaseexecutesql.go │ │ │ └── oceanbasesql │ │ │ ├── oceanbasesql_test.go │ │ │ └── oceanbasesql.go │ │ ├── oracle │ │ │ ├── oracleexecutesql │ │ │ │ └── oracleexecutesql.go │ │ │ └── oraclesql │ │ │ └── oraclesql.go │ │ ├── parameters_test.go │ │ ├── parameters.go │ │ ├── postgres │ │ │ ├── postgresexecutesql │ │ │ │ ├── postgresexecutesql_test.go │ │ │ │ └── postgresexecutesql.go │ │ │ ├── postgreslistactivequeries │ │ │ │ ├── postgreslistactivequeries_test.go │ │ │ │ └── postgreslistactivequeries.go │ │ │ ├── postgreslistavailableextensions │ │ │ │ ├── postgreslistavailableextensions_test.go │ │ │ │ └── postgreslistavailableextensions.go │ │ │ ├── postgreslistinstalledextensions │ │ │ │ ├── postgreslistinstalledextensions_test.go │ │ │ │ └── postgreslistinstalledextensions.go │ │ │ ├── postgreslisttables │ │ │ │ ├── postgreslisttables_test.go │ │ │ │ └── postgreslisttables.go │ │ │ └── postgressql │ │ │ ├── postgressql_test.go │ │ │ └── postgressql.go │ │ ├── redis │ │ │ ├── redis_test.go │ │ │ └── redis.go │ │ ├── spanner │ │ │ ├── spannerexecutesql │ │ │ │ ├── spannerexecutesql_test.go │ │ │ │ └── spannerexecutesql.go │ │ │ ├── spannerlisttables │ │ │ │ ├── spannerlisttables_test.go │ │ │ │ └── spannerlisttables.go │ │ │ └── spannersql │ │ │ ├── spanner_test.go │ │ │ └── spannersql.go │ │ ├── sqlite │ │ │ ├── sqliteexecutesql │ │ │ │ ├── sqliteexecutesql_test.go │ │ │ │ └── sqliteexecutesql.go │ │ │ └── sqlitesql │ │ │ ├── sqlitesql_test.go │ │ │ └── sqlitesql.go │ │ ├── tidb │ │ │ ├── tidbexecutesql │ │ │ │ ├── tidbexecutesql_test.go │ │ │ │ └── tidbexecutesql.go │ │ │ └── tidbsql │ │ │ ├── tidbsql_test.go │ │ │ └── tidbsql.go │ │ ├── tools_test.go │ │ ├── tools.go │ │ ├── toolsets.go │ │ ├── trino │ │ │ ├── trinoexecutesql │ │ │ │ ├── trinoexecutesql_test.go │ │ │ │ └── trinoexecutesql.go │ │ │ └── trinosql │ │ │ ├── trinosql_test.go │ │ │ └── trinosql.go │ │ ├── utility │ │ │ └── wait │ │ │ ├── wait_test.go │ │ │ └── wait.go │ │ ├── valkey │ │ │ ├── valkey_test.go │ │ │ └── valkey.go │ │ └── yugabytedbsql │ │ ├── yugabytedbsql_test.go │ │ └── yugabytedbsql.go │ └── util │ └── util.go ├── LICENSE ├── logo.png ├── main.go ├── MCP-TOOLBOX-EXTENSION.md ├── README.md └── tests ├── alloydb │ ├── alloydb_integration_test.go │ └── alloydb_wait_for_operation_test.go ├── alloydbainl │ └── alloydb_ai_nl_integration_test.go ├── alloydbpg │ └── alloydb_pg_integration_test.go ├── auth.go ├── bigquery │ └── bigquery_integration_test.go ├── bigtable │ └── bigtable_integration_test.go ├── cassandra │ └── cassandra_integration_test.go ├── clickhouse │ └── clickhouse_integration_test.go ├── cloudmonitoring │ └── cloud_monitoring_integration_test.go ├── cloudsql │ ├── cloud_sql_create_database_test.go │ ├── cloud_sql_create_users_test.go │ ├── cloud_sql_get_instances_test.go │ ├── cloud_sql_list_databases_test.go │ ├── cloudsql_list_instances_test.go │ └── cloudsql_wait_for_operation_test.go ├── cloudsqlmssql │ ├── cloud_sql_mssql_create_instance_integration_test.go │ └── cloud_sql_mssql_integration_test.go ├── cloudsqlmysql │ ├── cloud_sql_mysql_create_instance_integration_test.go │ └── cloud_sql_mysql_integration_test.go ├── cloudsqlpg │ ├── cloud_sql_pg_create_instances_test.go │ └── cloud_sql_pg_integration_test.go ├── common.go ├── couchbase │ └── couchbase_integration_test.go ├── dataform │ └── dataform_integration_test.go ├── dataplex │ └── dataplex_integration_test.go ├── dgraph │ └── dgraph_integration_test.go ├── firebird │ └── firebird_integration_test.go ├── firestore │ └── firestore_integration_test.go ├── http │ └── http_integration_test.go ├── looker │ └── looker_integration_test.go ├── mongodb │ └── mongodb_integration_test.go ├── mssql │ └── mssql_integration_test.go ├── mysql │ └── mysql_integration_test.go ├── neo4j │ └── neo4j_integration_test.go ├── oceanbase │ └── oceanbase_integration_test.go ├── option.go ├── oracle │ └── oracle_integration_test.go ├── postgres │ └── postgres_integration_test.go ├── redis │ └── redis_test.go ├── server.go ├── source.go ├── spanner │ └── spanner_integration_test.go ├── sqlite │ └── sqlite_integration_test.go ├── tidb │ └── tidb_integration_test.go ├── tool.go ├── trino │ └── trino_integration_test.go ├── utility │ └── wait_integration_test.go ├── valkey │ └── valkey_test.go └── yugabytedb └── yugabytedb_integration_test.go ``` # Files -------------------------------------------------------------------------------- /internal/tools/mssql/mssqlsql/mssqlsql.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 mssqlsql 16 | 17 | import ( 18 | "context" 19 | "database/sql" 20 | "fmt" 21 | "strings" 22 | 23 | yaml "github.com/goccy/go-yaml" 24 | "github.com/googleapis/genai-toolbox/internal/sources" 25 | "github.com/googleapis/genai-toolbox/internal/sources/cloudsqlmssql" 26 | "github.com/googleapis/genai-toolbox/internal/sources/mssql" 27 | "github.com/googleapis/genai-toolbox/internal/tools" 28 | ) 29 | 30 | const kind string = "mssql-sql" 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 compatibleSource interface { 47 | MSSQLDB() *sql.DB 48 | } 49 | 50 | // validate compatible sources are still compatible 51 | var _ compatibleSource = &cloudsqlmssql.Source{} 52 | var _ compatibleSource = &mssql.Source{} 53 | 54 | var compatibleSources = [...]string{cloudsqlmssql.SourceKind, mssql.SourceKind} 55 | 56 | type Config struct { 57 | Name string `yaml:"name" validate:"required"` 58 | Kind string `yaml:"kind" validate:"required"` 59 | Source string `yaml:"source" validate:"required"` 60 | Description string `yaml:"description" validate:"required"` 61 | Statement string `yaml:"statement" validate:"required"` 62 | AuthRequired []string `yaml:"authRequired"` 63 | Parameters tools.Parameters `yaml:"parameters"` 64 | TemplateParameters tools.Parameters `yaml:"templateParameters"` 65 | } 66 | 67 | // validate interface 68 | var _ tools.ToolConfig = Config{} 69 | 70 | func (cfg Config) ToolConfigKind() string { 71 | return kind 72 | } 73 | 74 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { 75 | // verify source exists 76 | rawS, ok := srcs[cfg.Source] 77 | if !ok { 78 | return nil, fmt.Errorf("no source named %q configured", cfg.Source) 79 | } 80 | 81 | // verify the source is compatible 82 | s, ok := rawS.(compatibleSource) 83 | if !ok { 84 | return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources) 85 | } 86 | 87 | allParameters, paramManifest, err := tools.ProcessParameters(cfg.TemplateParameters, cfg.Parameters) 88 | if err != nil { 89 | return nil, err 90 | } 91 | 92 | mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, allParameters) 93 | 94 | // finish tool setup 95 | t := Tool{ 96 | Name: cfg.Name, 97 | Kind: kind, 98 | Parameters: cfg.Parameters, 99 | TemplateParameters: cfg.TemplateParameters, 100 | AllParams: allParameters, 101 | Statement: cfg.Statement, 102 | AuthRequired: cfg.AuthRequired, 103 | Db: s.MSSQLDB(), 104 | manifest: tools.Manifest{Description: cfg.Description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired}, 105 | mcpManifest: mcpManifest, 106 | } 107 | return t, nil 108 | } 109 | 110 | // validate interface 111 | var _ tools.Tool = Tool{} 112 | 113 | type Tool struct { 114 | Name string `yaml:"name"` 115 | Kind string `yaml:"kind"` 116 | AuthRequired []string `yaml:"authRequired"` 117 | Parameters tools.Parameters `yaml:"parameters"` 118 | TemplateParameters tools.Parameters `yaml:"templateParameters"` 119 | AllParams tools.Parameters `yaml:"allParams"` 120 | 121 | Db *sql.DB 122 | Statement string 123 | manifest tools.Manifest 124 | mcpManifest tools.McpManifest 125 | } 126 | 127 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { 128 | paramsMap := params.AsMap() 129 | newStatement, err := tools.ResolveTemplateParams(t.TemplateParameters, t.Statement, paramsMap) 130 | if err != nil { 131 | return nil, fmt.Errorf("unable to extract template params %w", err) 132 | } 133 | 134 | newParams, err := tools.GetParams(t.Parameters, paramsMap) 135 | if err != nil { 136 | return nil, fmt.Errorf("unable to extract standard params %w", err) 137 | } 138 | 139 | namedArgs := make([]any, 0, len(newParams)) 140 | // To support both named args (e.g @id) and positional args (e.g @p1), check 141 | // if arg name is contained in the statement. 142 | for _, p := range t.Parameters { 143 | name := p.GetName() 144 | value := paramsMap[name] 145 | if strings.Contains(newStatement, "@"+name) { 146 | namedArgs = append(namedArgs, sql.Named(name, value)) 147 | } else { 148 | namedArgs = append(namedArgs, value) 149 | } 150 | } 151 | 152 | rows, err := t.Db.QueryContext(ctx, newStatement, namedArgs...) 153 | if err != nil { 154 | return nil, fmt.Errorf("unable to execute query: %w", err) 155 | } 156 | 157 | cols, err := rows.Columns() 158 | if err != nil { 159 | return nil, fmt.Errorf("unable to fetch column types: %w", err) 160 | } 161 | 162 | // create an array of values for each column, which can be re-used to scan each row 163 | rawValues := make([]any, len(cols)) 164 | values := make([]any, len(cols)) 165 | for i := range rawValues { 166 | values[i] = &rawValues[i] 167 | } 168 | 169 | var out []any 170 | for rows.Next() { 171 | err = rows.Scan(values...) 172 | if err != nil { 173 | return nil, fmt.Errorf("unable to parse row: %w", err) 174 | } 175 | vMap := make(map[string]any) 176 | for i, name := range cols { 177 | vMap[name] = rawValues[i] 178 | } 179 | out = append(out, vMap) 180 | } 181 | err = rows.Close() 182 | if err != nil { 183 | return nil, fmt.Errorf("unable to close rows: %w", err) 184 | } 185 | 186 | // Check if error occurred during iteration 187 | if err := rows.Err(); err != nil { 188 | return nil, err 189 | } 190 | 191 | return out, nil 192 | } 193 | 194 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { 195 | return tools.ParseParams(t.AllParams, data, claims) 196 | } 197 | 198 | func (t Tool) Manifest() tools.Manifest { 199 | return t.manifest 200 | } 201 | 202 | func (t Tool) McpManifest() tools.McpManifest { 203 | return t.mcpManifest 204 | } 205 | 206 | func (t Tool) Authorized(verifiedAuthServices []string) bool { 207 | return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices) 208 | } 209 | 210 | func (t Tool) RequiresClientAuthorization() bool { 211 | return false 212 | } 213 | ``` -------------------------------------------------------------------------------- /internal/tools/firebird/firebirdsql/firebirdsql.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 firebirdsql 16 | 17 | import ( 18 | "context" 19 | "database/sql" 20 | "fmt" 21 | "strings" 22 | 23 | "github.com/goccy/go-yaml" 24 | "github.com/googleapis/genai-toolbox/internal/sources" 25 | "github.com/googleapis/genai-toolbox/internal/sources/firebird" 26 | "github.com/googleapis/genai-toolbox/internal/tools" 27 | ) 28 | 29 | const kind string = "firebird-sql" 30 | 31 | func init() { 32 | if !tools.Register(kind, newConfig) { 33 | panic(fmt.Sprintf("tool kind %q already registered", kind)) 34 | } 35 | } 36 | 37 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) { 38 | actual := Config{Name: name} 39 | if err := decoder.DecodeContext(ctx, &actual); err != nil { 40 | return nil, err 41 | } 42 | return actual, nil 43 | } 44 | 45 | type compatibleSource interface { 46 | FirebirdDB() *sql.DB 47 | } 48 | 49 | // validate compatible sources are still compatible 50 | var _ compatibleSource = &firebird.Source{} 51 | 52 | var compatibleSources = [...]string{firebird.SourceKind} 53 | 54 | type Config struct { 55 | Name string `yaml:"name" validate:"required"` 56 | Kind string `yaml:"kind" validate:"required"` 57 | Source string `yaml:"source" validate:"required"` 58 | Description string `yaml:"description" validate:"required"` 59 | Statement string `yaml:"statement" validate:"required"` 60 | AuthRequired []string `yaml:"authRequired"` 61 | Parameters tools.Parameters `yaml:"parameters"` 62 | TemplateParameters tools.Parameters `yaml:"templateParameters"` 63 | } 64 | 65 | // validate interface 66 | var _ tools.ToolConfig = Config{} 67 | 68 | func (cfg Config) ToolConfigKind() string { 69 | return kind 70 | } 71 | 72 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { 73 | // verify source exists 74 | rawS, ok := srcs[cfg.Source] 75 | if !ok { 76 | return nil, fmt.Errorf("no source named %q configured", cfg.Source) 77 | } 78 | 79 | // verify the source is compatible 80 | s, ok := rawS.(compatibleSource) 81 | if !ok { 82 | return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources) 83 | } 84 | 85 | allParameters, paramManifest, err := tools.ProcessParameters(cfg.TemplateParameters, cfg.Parameters) 86 | if err != nil { 87 | return nil, err 88 | } 89 | 90 | mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, allParameters) 91 | 92 | // finish tool setup 93 | t := &Tool{ 94 | Name: cfg.Name, 95 | Kind: kind, 96 | Parameters: cfg.Parameters, 97 | TemplateParameters: cfg.TemplateParameters, 98 | AllParams: allParameters, 99 | Statement: cfg.Statement, 100 | AuthRequired: cfg.AuthRequired, 101 | Db: s.FirebirdDB(), 102 | manifest: tools.Manifest{Description: cfg.Description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired}, 103 | mcpManifest: mcpManifest, 104 | } 105 | return t, nil 106 | } 107 | 108 | // validate interface 109 | var _ tools.Tool = &Tool{} 110 | 111 | type Tool struct { 112 | Name string `yaml:"name"` 113 | Kind string `yaml:"kind"` 114 | AuthRequired []string `yaml:"authRequired"` 115 | Parameters tools.Parameters `yaml:"parameters"` 116 | TemplateParameters tools.Parameters `yaml:"templateParameters"` 117 | AllParams tools.Parameters `yaml:"allParams"` 118 | 119 | Db *sql.DB 120 | Statement string 121 | manifest tools.Manifest 122 | mcpManifest tools.McpManifest 123 | } 124 | 125 | func (t *Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { 126 | paramsMap := params.AsMap() 127 | statement, err := tools.ResolveTemplateParams(t.TemplateParameters, t.Statement, paramsMap) 128 | if err != nil { 129 | return nil, fmt.Errorf("unable to extract template params: %w", err) 130 | } 131 | 132 | newParams, err := tools.GetParams(t.Parameters, paramsMap) 133 | if err != nil { 134 | return nil, fmt.Errorf("unable to extract standard params: %w", err) 135 | } 136 | 137 | namedArgs := make([]any, 0, len(newParams)) 138 | // To support both named args (e.g :id) and positional args (e.g ?), check 139 | // if arg name is contained in the statement. 140 | for _, p := range t.Parameters { 141 | name := p.GetName() 142 | value := paramsMap[name] 143 | if strings.Contains(statement, ":"+name) { 144 | namedArgs = append(namedArgs, sql.Named(name, value)) 145 | } else { 146 | namedArgs = append(namedArgs, value) 147 | } 148 | } 149 | 150 | rows, err := t.Db.QueryContext(ctx, statement, namedArgs...) 151 | if err != nil { 152 | return nil, fmt.Errorf("unable to execute query: %w", err) 153 | } 154 | defer rows.Close() 155 | 156 | cols, err := rows.Columns() 157 | if err != nil { 158 | return nil, fmt.Errorf("unable to get columns: %w", err) 159 | } 160 | 161 | values := make([]any, len(cols)) 162 | scanArgs := make([]any, len(values)) 163 | for i := range values { 164 | scanArgs[i] = &values[i] 165 | } 166 | 167 | var out []any 168 | for rows.Next() { 169 | 170 | err = rows.Scan(scanArgs...) 171 | if err != nil { 172 | return nil, fmt.Errorf("unable to parse row: %w", err) 173 | } 174 | 175 | vMap := make(map[string]any) 176 | for i, col := range cols { 177 | if b, ok := values[i].([]byte); ok { 178 | vMap[col] = string(b) 179 | } else { 180 | vMap[col] = values[i] 181 | } 182 | } 183 | out = append(out, vMap) 184 | } 185 | 186 | if err := rows.Err(); err != nil { 187 | return nil, fmt.Errorf("error iterating rows: %w", err) 188 | } 189 | 190 | // In most cases, DML/DDL statements like INSERT, UPDATE, CREATE, etc. might return no rows 191 | // However, it is also possible that this was a query that was expected to return rows 192 | // but returned none, a case that we cannot distinguish here. 193 | return out, nil 194 | } 195 | 196 | func (t *Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { 197 | return tools.ParseParams(t.AllParams, data, claims) 198 | } 199 | 200 | func (t *Tool) Manifest() tools.Manifest { 201 | return t.manifest 202 | } 203 | 204 | func (t *Tool) McpManifest() tools.McpManifest { 205 | return t.mcpManifest 206 | } 207 | 208 | func (t *Tool) Authorized(verifiedAuthServices []string) bool { 209 | return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices) 210 | } 211 | 212 | func (t Tool) RequiresClientAuthorization() bool { 213 | return false 214 | } 215 | ``` -------------------------------------------------------------------------------- /internal/server/static/js/runTool.js: -------------------------------------------------------------------------------- ```javascript 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 | import { isParamIncluded } from "./toolDisplay.js"; 16 | 17 | /** 18 | * Runs a specific tool using the /api/tools/toolName/invoke endpoint 19 | * @param {string} toolId The unique identifier for the tool. 20 | * @param {!HTMLFormElement} form The form element containing parameter inputs. 21 | * @param {!HTMLTextAreaElement} responseArea The textarea to display results or errors. 22 | * @param {!Array<!Object>} parameters An array of parameter definition objects 23 | * @param {!HTMLInputElement} prettifyCheckbox The checkbox to control JSON formatting. 24 | * @param {function(?Object): void} updateLastResults Callback to store the last results. 25 | */ 26 | export async function handleRunTool(toolId, form, responseArea, parameters, prettifyCheckbox, updateLastResults, headers) { 27 | const formData = new FormData(form); 28 | const typedParams = {}; 29 | responseArea.value = 'Running tool...'; 30 | updateLastResults(null); 31 | 32 | for (const param of parameters) { 33 | const NAME = param.name; 34 | const VALUE_TYPE = param.valueType; 35 | const RAW_VALUE = formData.get(NAME); 36 | const INCLUDE_CHECKED = isParamIncluded(toolId, NAME) 37 | 38 | try { 39 | if (!INCLUDE_CHECKED) { 40 | console.debug(`Param ${NAME} was intentionally skipped.`) 41 | // if param was purposely unchecked, don't include it in body 42 | continue; 43 | } 44 | 45 | if (VALUE_TYPE === 'boolean') { 46 | typedParams[NAME] = RAW_VALUE !== null; 47 | console.debug(`Parameter ${NAME} (boolean) set to: ${typedParams[NAME]}`); 48 | continue; 49 | } 50 | 51 | // process remaining types 52 | if (VALUE_TYPE && VALUE_TYPE.startsWith('array<')) { 53 | typedParams[NAME] = parseArrayParameter(RAW_VALUE, VALUE_TYPE, NAME); 54 | } else { 55 | switch (VALUE_TYPE) { 56 | case 'number': 57 | if (RAW_VALUE === "") { 58 | console.debug(`Param ${NAME} was empty, setting to empty string.`) 59 | typedParams[NAME] = ""; 60 | } else { 61 | const num = Number(RAW_VALUE); 62 | if (isNaN(num)) { 63 | throw new Error(`Invalid number input for ${NAME}: ${RAW_VALUE}`); 64 | } 65 | typedParams[NAME] = num; 66 | } 67 | break; 68 | case 'string': 69 | default: 70 | typedParams[NAME] = RAW_VALUE; 71 | break; 72 | } 73 | } 74 | } catch (error) { 75 | console.error('Error processing parameter:', NAME, error); 76 | responseArea.value = `Error for ${NAME}: ${error.message}`; 77 | return; 78 | } 79 | } 80 | 81 | console.debug('Running tool:', toolId, 'with typed params:', typedParams); 82 | try { 83 | const response = await fetch(`/api/tool/${toolId}/invoke`, { 84 | method: 'POST', 85 | headers: headers, 86 | body: JSON.stringify(typedParams) 87 | }); 88 | if (!response.ok) { 89 | const errorBody = await response.text(); 90 | throw new Error(`HTTP error ${response.status}: ${errorBody}`); 91 | } 92 | const results = await response.json(); 93 | updateLastResults(results); 94 | displayResults(results, responseArea, prettifyCheckbox.checked); 95 | } catch (error) { 96 | console.error('Error running tool:', error); 97 | responseArea.value = `Error: ${error.message}`; 98 | updateLastResults(null); 99 | } 100 | } 101 | 102 | /** 103 | * Parses and validates a single array parameter from a raw string value. 104 | * @param {string} rawValue The raw string value from FormData. 105 | * @param {string} valueType The full array type string (e.g., "array<number>"). 106 | * @param {string} paramName The name of the parameter for error messaging. 107 | * @return {!Array<*>} The parsed array. 108 | * @throws {Error} If parsing or type validation fails. 109 | */ 110 | function parseArrayParameter(rawValue, valueType, paramName) { 111 | const ELEMENT_TYPE = valueType.substring(6, valueType.length - 1); 112 | let parsedArray; 113 | try { 114 | parsedArray = JSON.parse(rawValue); 115 | } catch (e) { 116 | throw new Error(`Invalid JSON format for ${paramName}. Expected an array. ${e.message}`); 117 | } 118 | 119 | if (!Array.isArray(parsedArray)) { 120 | throw new Error(`Input for ${paramName} must be a JSON array (e.g., ["a", "b"]).`); 121 | } 122 | 123 | return parsedArray.map((item, index) => { 124 | switch (ELEMENT_TYPE) { 125 | case 'number': 126 | const NUM = Number(item); 127 | if (isNaN(NUM)) { 128 | throw new Error(`Invalid number "${item}" found in array for ${paramName} at index ${index}.`); 129 | } 130 | return NUM; 131 | case 'boolean': 132 | return item === true || String(item).toLowerCase() === 'true'; 133 | case 'string': 134 | default: 135 | return item; 136 | } 137 | }); 138 | } 139 | 140 | /** 141 | * Displays the results from the tool run in the response area. 142 | */ 143 | export function displayResults(results, responseArea, prettify) { 144 | if (results === null || results === undefined) { 145 | return; 146 | } 147 | try { 148 | const resultJson = JSON.parse(results.result); 149 | if (prettify) { 150 | responseArea.value = JSON.stringify(resultJson, null, 2); 151 | } else { 152 | responseArea.value = JSON.stringify(resultJson); 153 | } 154 | } catch (error) { 155 | console.error("Error parsing or stringifying results:", error); 156 | if (typeof results.result === 'string') { 157 | responseArea.value = results.result; 158 | } else { 159 | responseArea.value = "Error displaying results. Invalid format."; 160 | } 161 | } 162 | } 163 | ``` -------------------------------------------------------------------------------- /internal/tools/alloydb/alloydbcreatecluster/alloydbcreatecluster.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 alloydbcreatecluster 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 | "google.golang.org/api/alloydb/v1" 26 | ) 27 | 28 | const kind string = "alloydb-create-cluster" 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 | // Configuration for the create-cluster 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("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 `alloydb-admin`", kind) 71 | } 72 | 73 | allParameters := tools.Parameters{ 74 | tools.NewStringParameter("project", "The GCP project ID."), 75 | tools.NewStringParameterWithDefault("location", "us-central1", "The location to create the cluster in. The default value is us-central1. If quota is exhausted then use other regions."), 76 | tools.NewStringParameter("cluster", "A unique ID for the AlloyDB cluster."), 77 | tools.NewStringParameter("password", "A secure password for the initial user."), 78 | tools.NewStringParameterWithDefault("network", "default", "The name of the VPC network to connect the cluster to (e.g., 'default')."), 79 | tools.NewStringParameterWithDefault("user", "postgres", "The name for the initial superuser. Defaults to 'postgres' if not provided."), 80 | } 81 | paramManifest := allParameters.Manifest() 82 | 83 | description := cfg.Description 84 | if description == "" { 85 | description = "Creates a new AlloyDB cluster. This is a long-running operation, but the API call returns quickly. This will return operation id to be used by get operations tool. Take all parameters from user in one go." 86 | } 87 | mcpManifest := tools.GetMcpManifest(cfg.Name, description, cfg.AuthRequired, allParameters) 88 | 89 | return Tool{ 90 | Name: cfg.Name, 91 | Kind: kind, 92 | Source: s, 93 | AllParams: allParameters, 94 | manifest: tools.Manifest{Description: description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired}, 95 | mcpManifest: mcpManifest, 96 | }, nil 97 | } 98 | 99 | // Tool represents the create-cluster tool. 100 | type Tool struct { 101 | Name string `yaml:"name"` 102 | Kind string `yaml:"kind"` 103 | Description string `yaml:"description"` 104 | 105 | Source *alloydbadmin.Source 106 | AllParams tools.Parameters `yaml:"allParams"` 107 | 108 | manifest tools.Manifest 109 | mcpManifest tools.McpManifest 110 | } 111 | 112 | // Invoke executes the tool's logic. 113 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { 114 | paramsMap := params.AsMap() 115 | project, ok := paramsMap["project"].(string) 116 | if !ok || project == "" { 117 | return nil, fmt.Errorf("invalid or missing 'project' parameter; expected a non-empty string") 118 | } 119 | 120 | location, ok := paramsMap["location"].(string) 121 | if !ok { 122 | return nil, fmt.Errorf("invalid 'location' parameter; expected a string") 123 | } 124 | 125 | clusterID, ok := paramsMap["cluster"].(string) 126 | if !ok || clusterID == "" { 127 | return nil, fmt.Errorf("invalid or missing 'cluster' parameter; expected a non-empty string") 128 | } 129 | 130 | password, ok := paramsMap["password"].(string) 131 | if !ok || password == "" { 132 | return nil, fmt.Errorf("invalid or missing 'password' parameter; expected a non-empty string") 133 | } 134 | 135 | network, ok := paramsMap["network"].(string) 136 | if !ok { 137 | return nil, fmt.Errorf("invalid 'network' parameter; expected a string") 138 | } 139 | 140 | user, ok := paramsMap["user"].(string) 141 | if !ok { 142 | return nil, fmt.Errorf("invalid 'user' parameter; expected a string") 143 | } 144 | 145 | service, err := t.Source.GetService(ctx, string(accessToken)) 146 | if err != nil { 147 | return nil, err 148 | } 149 | 150 | urlString := fmt.Sprintf("projects/%s/locations/%s", project, location) 151 | 152 | // Build the request body using the type-safe Cluster struct. 153 | clusterBody := &alloydb.Cluster{ 154 | NetworkConfig: &alloydb.NetworkConfig{ 155 | Network: fmt.Sprintf("projects/%s/global/networks/%s", project, network), 156 | }, 157 | InitialUser: &alloydb.UserPassword{ 158 | User: user, 159 | Password: password, 160 | }, 161 | } 162 | 163 | // The Create API returns a long-running operation. 164 | resp, err := service.Projects.Locations.Clusters.Create(urlString, clusterBody).ClusterId(clusterID).Do() 165 | if err != nil { 166 | return nil, fmt.Errorf("error creating AlloyDB cluster: %w", err) 167 | } 168 | 169 | return resp, nil 170 | } 171 | 172 | // ParseParams parses the parameters for the tool. 173 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { 174 | return tools.ParseParams(t.AllParams, data, claims) 175 | } 176 | 177 | // Manifest returns the tool's manifest. 178 | func (t Tool) Manifest() tools.Manifest { 179 | return t.manifest 180 | } 181 | 182 | // McpManifest returns the tool's MCP manifest. 183 | func (t Tool) McpManifest() tools.McpManifest { 184 | return t.mcpManifest 185 | } 186 | 187 | // Authorized checks if the tool is authorized. 188 | func (t Tool) Authorized(verifiedAuthServices []string) bool { 189 | return true 190 | } 191 | 192 | func (t Tool) RequiresClientAuthorization() bool { 193 | return t.Source.UseClientAuthorization() 194 | } 195 | ``` -------------------------------------------------------------------------------- /internal/tools/mongodb/mongodbupdatemany/mongodbupdatemany.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 mongodbupdatemany 15 | 16 | import ( 17 | "context" 18 | "fmt" 19 | "slices" 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-update-many" 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 | AuthRequired []string `yaml:"authRequired" validate:"required"` 51 | Description string `yaml:"description" validate:"required"` 52 | Database string `yaml:"database" validate:"required"` 53 | Collection string `yaml:"collection" validate:"required"` 54 | FilterPayload string `yaml:"filterPayload" validate:"required"` 55 | FilterParams tools.Parameters `yaml:"filterParams" validate:"required"` 56 | UpdatePayload string `yaml:"updatePayload" validate:"required"` 57 | UpdateParams tools.Parameters `yaml:"updateParams" validate:"required"` 58 | Canonical bool `yaml:"canonical" validate:"required"` 59 | Upsert bool `yaml:"upsert"` 60 | } 61 | 62 | // validate interface 63 | var _ tools.ToolConfig = Config{} 64 | 65 | func (cfg Config) ToolConfigKind() string { 66 | return kind 67 | } 68 | 69 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { 70 | // verify source exists 71 | rawS, ok := srcs[cfg.Source] 72 | if !ok { 73 | return nil, fmt.Errorf("no source named %q configured", cfg.Source) 74 | } 75 | 76 | // verify the source is compatible 77 | s, ok := rawS.(*mongosrc.Source) 78 | if !ok { 79 | return nil, fmt.Errorf("invalid source for %q tool: source kind must be `mongodb`", kind) 80 | } 81 | 82 | // Create a slice for all parameters 83 | allParameters := slices.Concat(cfg.FilterParams, cfg.UpdateParams) 84 | 85 | // Verify no duplicate parameter names 86 | err := tools.CheckDuplicateParameters(allParameters) 87 | if err != nil { 88 | return nil, err 89 | } 90 | 91 | // Create Toolbox manifest 92 | paramManifest := allParameters.Manifest() 93 | 94 | if paramManifest == nil { 95 | paramManifest = make([]tools.ParameterManifest, 0) 96 | } 97 | 98 | // Create MCP manifest 99 | mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, allParameters) 100 | 101 | // finish tool setup 102 | return Tool{ 103 | Name: cfg.Name, 104 | Kind: kind, 105 | AuthRequired: cfg.AuthRequired, 106 | Collection: cfg.Collection, 107 | Canonical: cfg.Canonical, 108 | Upsert: cfg.Upsert, 109 | FilterPayload: cfg.FilterPayload, 110 | FilterParams: cfg.FilterParams, 111 | UpdatePayload: cfg.UpdatePayload, 112 | UpdateParams: cfg.UpdateParams, 113 | AllParams: allParameters, 114 | database: s.Client.Database(cfg.Database), 115 | manifest: tools.Manifest{Description: cfg.Description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired}, 116 | mcpManifest: mcpManifest, 117 | }, nil 118 | } 119 | 120 | // validate interface 121 | var _ tools.Tool = Tool{} 122 | 123 | type Tool struct { 124 | Name string `yaml:"name"` 125 | Kind string `yaml:"kind"` 126 | AuthRequired []string `yaml:"authRequired"` 127 | Description string `yaml:"description"` 128 | Collection string `yaml:"collection"` 129 | FilterPayload string `yaml:"filterPayload" validate:"required"` 130 | FilterParams tools.Parameters `yaml:"filterParams" validate:"required"` 131 | UpdatePayload string `yaml:"updatePayload" validate:"required"` 132 | UpdateParams tools.Parameters `yaml:"updateParams" validate:"required"` 133 | AllParams tools.Parameters `yaml:"allParams"` 134 | Canonical bool `yaml:"canonical" validation:"required"` 135 | Upsert bool `yaml:"upsert"` 136 | 137 | database *mongo.Database 138 | manifest tools.Manifest 139 | mcpManifest tools.McpManifest 140 | } 141 | 142 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { 143 | paramsMap := params.AsMap() 144 | 145 | filterString, err := tools.PopulateTemplateWithJSON("MongoDBUpdateManyFilter", t.FilterPayload, paramsMap) 146 | if err != nil { 147 | return nil, fmt.Errorf("error populating filter: %s", err) 148 | } 149 | 150 | var filter = bson.D{} 151 | err = bson.UnmarshalExtJSON([]byte(filterString), t.Canonical, &filter) 152 | if err != nil { 153 | return nil, fmt.Errorf("unable to unmarshal filter string: %w", err) 154 | } 155 | 156 | updateString, err := tools.PopulateTemplateWithJSON("MongoDBUpdateMany", t.UpdatePayload, paramsMap) 157 | if err != nil { 158 | return nil, fmt.Errorf("unable to get update: %w", err) 159 | } 160 | 161 | var update = bson.D{} 162 | err = bson.UnmarshalExtJSON([]byte(updateString), false, &update) 163 | if err != nil { 164 | return nil, fmt.Errorf("unable to unmarshal update string: %w", err) 165 | } 166 | 167 | res, err := t.database.Collection(t.Collection).UpdateMany(ctx, filter, update, options.Update().SetUpsert(t.Upsert)) 168 | if err != nil { 169 | return nil, fmt.Errorf("error updating collection: %w", err) 170 | } 171 | 172 | return []any{res.ModifiedCount, res.UpsertedCount, res.MatchedCount}, nil 173 | } 174 | 175 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { 176 | return tools.ParseParams(t.AllParams, data, claims) 177 | } 178 | 179 | func (t Tool) Manifest() tools.Manifest { 180 | return t.manifest 181 | } 182 | 183 | func (t Tool) McpManifest() tools.McpManifest { 184 | return t.mcpManifest 185 | } 186 | 187 | func (t Tool) Authorized(verifiedAuthServices []string) bool { 188 | return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices) 189 | } 190 | 191 | func (t Tool) RequiresClientAuthorization() bool { 192 | return false 193 | } 194 | ``` -------------------------------------------------------------------------------- /internal/server/mcp/v20250618/types.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 v20250618 16 | 17 | import ( 18 | "github.com/googleapis/genai-toolbox/internal/server/mcp/jsonrpc" 19 | "github.com/googleapis/genai-toolbox/internal/tools" 20 | ) 21 | 22 | // SERVER_NAME is the server name used in Implementation. 23 | const SERVER_NAME = "Toolbox" 24 | 25 | // PROTOCOL_VERSION is the version of the MCP protocol in this package. 26 | const PROTOCOL_VERSION = "2025-06-18" 27 | 28 | // methods that are supported. 29 | const ( 30 | PING = "ping" 31 | TOOLS_LIST = "tools/list" 32 | TOOLS_CALL = "tools/call" 33 | ) 34 | 35 | /* Empty result */ 36 | 37 | // EmptyResult represents a response that indicates success but carries no data. 38 | type EmptyResult jsonrpc.Result 39 | 40 | /* Pagination */ 41 | 42 | // Cursor is an opaque token used to represent a cursor for pagination. 43 | type Cursor string 44 | 45 | type PaginatedRequest struct { 46 | jsonrpc.Request 47 | Params struct { 48 | // An opaque token representing the current pagination position. 49 | // If provided, the server should return results starting after this cursor. 50 | Cursor Cursor `json:"cursor,omitempty"` 51 | } `json:"params,omitempty"` 52 | } 53 | 54 | type PaginatedResult struct { 55 | jsonrpc.Result 56 | // An opaque token representing the pagination position after the last returned result. 57 | // If present, there may be more results available. 58 | NextCursor Cursor `json:"nextCursor,omitempty"` 59 | } 60 | 61 | /* Tools */ 62 | 63 | // Sent from the client to request a list of tools the server has. 64 | type ListToolsRequest struct { 65 | PaginatedRequest 66 | } 67 | 68 | // The server's response to a tools/list request from the client. 69 | type ListToolsResult struct { 70 | PaginatedResult 71 | Tools []tools.McpManifest `json:"tools"` 72 | } 73 | 74 | // Used by the client to invoke a tool provided by the server. 75 | type CallToolRequest struct { 76 | jsonrpc.Request 77 | Params struct { 78 | Name string `json:"name"` 79 | Arguments map[string]any `json:"arguments,omitempty"` 80 | } `json:"params,omitempty"` 81 | } 82 | 83 | // The sender or recipient of messages and data in a conversation. 84 | type Role string 85 | 86 | const ( 87 | RoleUser Role = "user" 88 | RoleAssistant Role = "assistant" 89 | ) 90 | 91 | // Base for objects that include optional annotations for the client. 92 | // The client can use annotations to inform how objects are used or displayed 93 | type Annotated struct { 94 | Annotations *struct { 95 | // Describes who the intended customer of this object or data is. 96 | // It can include multiple entries to indicate content useful for multiple 97 | // audiences (e.g., `["user", "assistant"]`). 98 | Audience []Role `json:"audience,omitempty"` 99 | // Describes how important this data is for operating the server. 100 | // 101 | // A value of 1 means "most important," and indicates that the data is 102 | // effectively required, while 0 means "least important," and indicates that 103 | // the data is entirely optional. 104 | // 105 | // @TJS-type number 106 | // @minimum 0 107 | // @maximum 1 108 | Priority float64 `json:"priority,omitempty"` 109 | } `json:"annotations,omitempty"` 110 | } 111 | 112 | // TextContent represents text provided to or from an LLM. 113 | type TextContent struct { 114 | Annotated 115 | Type string `json:"type"` 116 | // The text content of the message. 117 | Text string `json:"text"` 118 | } 119 | 120 | // The server's response to a tool call. 121 | // 122 | // Any errors that originate from the tool SHOULD be reported inside the result 123 | // object, with `isError` set to true, _not_ as an MCP protocol-level error 124 | // response. Otherwise, the LLM would not be able to see that an error occurred 125 | // and self-correct. 126 | // 127 | // However, any errors in _finding_ the tool, an error indicating that the 128 | // server does not support tool calls, or any other exceptional conditions, 129 | // should be reported as an MCP error response. 130 | type CallToolResult struct { 131 | jsonrpc.Result 132 | // Could be either a TextContent, ImageContent, or EmbeddedResources 133 | // For Toolbox, we will only be sending TextContent 134 | Content []TextContent `json:"content"` 135 | // Whether the tool call ended in an error. 136 | // If not set, this is assumed to be false (the call was successful). 137 | // 138 | // Any errors that originate from the tool SHOULD be reported inside the result 139 | // object, with `isError` set to true, _not_ as an MCP protocol-level error 140 | // response. Otherwise, the LLM would not be able to see that an error occurred 141 | // and self-correct. 142 | // 143 | // However, any errors in _finding_ the tool, an error indicating that the 144 | // server does not support tool calls, or any other exceptional conditions, 145 | // should be reported as an MCP error response. 146 | IsError bool `json:"isError,omitempty"` 147 | // An optional JSON object that represents the structured result of the tool call. 148 | StructuredContent map[string]any `json:"structuredContent,omitempty"` 149 | } 150 | 151 | // Additional properties describing a Tool to clients. 152 | // 153 | // NOTE: all properties in ToolAnnotations are **hints**. 154 | // They are not guaranteed to provide a faithful description of 155 | // tool behavior (including descriptive properties like `title`). 156 | // 157 | // Clients should never make tool use decisions based on ToolAnnotations 158 | // received from untrusted servers. 159 | type ToolAnnotations struct { 160 | // A human-readable title for the tool. 161 | Title string `json:"title,omitempty"` 162 | // If true, the tool does not modify its environment. 163 | // Default: false 164 | ReadOnlyHint bool `json:"readOnlyHint,omitempty"` 165 | // If true, the tool may perform destructive updates to its environment. 166 | // If false, the tool performs only additive updates. 167 | // (This property is meaningful only when `readOnlyHint == false`) 168 | // Default: true 169 | DestructiveHint bool `json:"destructiveHint,omitempty"` 170 | // If true, calling the tool repeatedly with the same arguments 171 | // will have no additional effect on the its environment. 172 | // (This property is meaningful only when `readOnlyHint == false`) 173 | // Default: false 174 | IdempotentHint bool `json:"idempotentHint,omitempty"` 175 | // If true, this tool may interact with an "open world" of external 176 | // entities. If false, the tool's domain of interaction is closed. 177 | // For example, the world of a web search tool is open, whereas that 178 | // of a memory tool is not. 179 | // Default: true 180 | OpenWorldHint bool `json:"openWorldHint,omitempty"` 181 | } 182 | ``` -------------------------------------------------------------------------------- /docs/en/resources/tools/spanner/spanner-list-tables.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: "spanner-list-tables" 3 | type: docs 4 | weight: 3 5 | description: > 6 | A "spanner-list-tables" tool retrieves schema information about tables in a 7 | Google Cloud Spanner database. 8 | --- 9 | 10 | ## About 11 | 12 | A `spanner-list-tables` tool retrieves comprehensive schema information about 13 | tables in a Cloud Spanner database. It automatically adapts to the database 14 | dialect (GoogleSQL or PostgreSQL) and returns detailed metadata including 15 | columns, constraints, and indexes. It's compatible with: 16 | 17 | - [spanner](../../sources/spanner.md) 18 | 19 | This tool is read-only and executes pre-defined SQL queries against the 20 | `INFORMATION_SCHEMA` tables to gather metadata. The tool automatically detects 21 | the database dialect from the source configuration and uses the appropriate SQL 22 | syntax. 23 | 24 | ## Features 25 | 26 | - **Automatic Dialect Detection**: Adapts queries based on whether the database 27 | uses GoogleSQL or PostgreSQL dialect 28 | - **Comprehensive Schema Information**: Returns columns, data types, constraints, 29 | indexes, and table relationships 30 | - **Flexible Filtering**: Can list all tables or filter by specific table names 31 | - **Output Format Options**: Choose between simple (table names only) or detailed 32 | (full schema information) output 33 | 34 | ## Example 35 | 36 | ### Basic Usage - List All Tables 37 | 38 | ```yaml 39 | sources: 40 | my-spanner-db: 41 | kind: spanner 42 | project: ${SPANNER_PROJECT} 43 | instance: ${SPANNER_INSTANCE} 44 | database: ${SPANNER_DATABASE} 45 | dialect: googlesql # or postgresql 46 | 47 | tools: 48 | list_all_tables: 49 | kind: spanner-list-tables 50 | source: my-spanner-db 51 | description: Lists all tables with their complete schema information 52 | ``` 53 | 54 | ### List Specific Tables 55 | 56 | ```yaml 57 | tools: 58 | list_specific_tables: 59 | kind: spanner-list-tables 60 | source: my-spanner-db 61 | description: | 62 | Lists schema information for specific tables. 63 | Example usage: 64 | { 65 | "table_names": "users,orders,products", 66 | "output_format": "detailed" 67 | } 68 | ``` 69 | 70 | ## Parameters 71 | 72 | The tool accepts two optional parameters: 73 | 74 | | **parameter** | **type** | **default** | **description** | 75 | |---------------|:--------:|:-----------:|------------------------------------------------------------------------------------------------------| 76 | | table_names | string | "" | Comma-separated list of table names to filter. If empty, lists all tables in user-accessible schemas | 77 | | output_format | string | "detailed" | Output format: "simple" returns only table names, "detailed" returns full schema information | 78 | 79 | ## Output Format 80 | 81 | ### Simple Format 82 | 83 | When `output_format` is set to "simple", the tool returns a minimal JSON structure: 84 | 85 | ```json 86 | [ 87 | { 88 | "schema_name": "public", 89 | "object_name": "users", 90 | "object_details": "{\"name\":\"users\"}" 91 | }, 92 | { 93 | "schema_name": "public", 94 | "object_name": "orders", 95 | "object_details": "{\"name\":\"orders\"}" 96 | } 97 | ] 98 | ``` 99 | 100 | ### Detailed Format 101 | 102 | When `output_format` is set to "detailed" (default), the tool returns 103 | comprehensive schema information: 104 | 105 | ```json 106 | [ 107 | { 108 | "schema_name": "public", 109 | "object_name": "users", 110 | "object_details": "{ 111 | \"schema_name\": \"public\", 112 | \"object_name\": \"users\", 113 | \"object_type\": \"BASE TABLE\", 114 | \"columns\": [ 115 | { 116 | \"column_name\": \"id\", 117 | \"data_type\": \"INT64\", 118 | \"ordinal_position\": 1, 119 | \"is_not_nullable\": true, 120 | \"column_default\": null 121 | }, 122 | { 123 | \"column_name\": \"email\", 124 | \"data_type\": \"STRING(255)\", 125 | \"ordinal_position\": 2, 126 | \"is_not_nullable\": true, 127 | \"column_default\": null 128 | } 129 | ], 130 | \"constraints\": [ 131 | { 132 | \"constraint_name\": \"PK_users\", 133 | \"constraint_type\": \"PRIMARY KEY\", 134 | \"constraint_definition\": \"PRIMARY KEY (id)\", 135 | \"constraint_columns\": [\"id\"], 136 | \"foreign_key_referenced_table\": null, 137 | \"foreign_key_referenced_columns\": [] 138 | } 139 | ], 140 | \"indexes\": [ 141 | { 142 | \"index_name\": \"idx_users_email\", 143 | \"index_type\": \"INDEX\", 144 | \"is_unique\": true, 145 | \"is_null_filtered\": false, 146 | \"interleaved_in_table\": null, 147 | \"index_key_columns\": [ 148 | {\"column_name\": \"email\", \"ordering\": \"ASC\"} 149 | ], 150 | \"storing_columns\": [] 151 | } 152 | ] 153 | }" 154 | } 155 | ] 156 | ``` 157 | 158 | ## Use Cases 159 | 160 | 1. **Database Documentation**: Generate comprehensive documentation of your 161 | database schema 162 | 2. **Schema Validation**: Verify that expected tables and columns exist 163 | 3. **Migration Planning**: Understand the current schema before making changes 164 | 4. **Development Tools**: Build tools that need to understand database structure 165 | 5. **Audit and Compliance**: Track schema changes and ensure compliance with 166 | data governance policies 167 | 168 | ## Example with Agent Integration 169 | 170 | ```yaml 171 | sources: 172 | spanner-db: 173 | kind: spanner 174 | project: my-project 175 | instance: my-instance 176 | database: my-database 177 | dialect: googlesql 178 | 179 | tools: 180 | schema_inspector: 181 | kind: spanner-list-tables 182 | source: spanner-db 183 | description: | 184 | Use this tool to inspect database schema information. 185 | You can: 186 | - List all tables by leaving table_names empty 187 | - Get specific table schemas by providing comma-separated table names 188 | - Choose between simple (names only) or detailed (full schema) output 189 | 190 | Examples: 191 | 1. List all tables with details: {"output_format": "detailed"} 192 | 2. Get specific tables: {"table_names": "users,orders", "output_format": "detailed"} 193 | 3. Just get table names: {"output_format": "simple"} 194 | ``` 195 | 196 | ## Reference 197 | 198 | | **field** | **type** | **required** | **description** | 199 | |--------------|:--------:|:------------:|----------------------------------------------------| 200 | | kind | string | true | Must be "spanner-list-tables" | 201 | | source | string | true | Name of the Spanner source to query | 202 | | description | string | false | Description of the tool that is passed to the LLM | 203 | | authRequired | string[] | false | List of auth services required to invoke this tool | 204 | 205 | ## Notes 206 | 207 | - This tool is read-only and does not modify any data 208 | - The tool automatically handles both GoogleSQL and PostgreSQL dialects 209 | - Large databases with many tables may take longer to query 210 | ``` -------------------------------------------------------------------------------- /docs/en/resources/sources/cloud-sql-pg.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: "Cloud SQL for PostgreSQL" 3 | linkTitle: "Cloud SQL (Postgres)" 4 | type: docs 5 | weight: 1 6 | description: > 7 | Cloud SQL for PostgreSQL is a fully-managed database service for Postgres. 8 | 9 | --- 10 | 11 | ## About 12 | 13 | [Cloud SQL for PostgreSQL][csql-pg-docs] is a fully-managed database service 14 | that helps you set up, maintain, manage, and administer your PostgreSQL 15 | relational databases on Google Cloud Platform. 16 | 17 | If you are new to Cloud SQL for PostgreSQL, you can try [creating and connecting 18 | to a database by following these instructions][csql-pg-quickstart]. 19 | 20 | [csql-pg-docs]: https://cloud.google.com/sql/docs/postgres 21 | [csql-pg-quickstart]: 22 | https://cloud.google.com/sql/docs/postgres/connect-instance-local-computer 23 | 24 | ## Available Tools 25 | 26 | - [`postgres-sql`](../tools/postgres/postgres-sql.md) 27 | Execute SQL queries as prepared statements in PostgreSQL. 28 | 29 | - [`postgres-execute-sql`](../tools/postgres/postgres-execute-sql.md) 30 | Run parameterized SQL statements in PostgreSQL. 31 | 32 | - [`postgres-list-tables`](../tools/postgres/postgres-list-tables.md) 33 | List tables in a PostgreSQL database. 34 | 35 | - [`postgres-list-active-queries`](../tools/postgres/postgres-list-active-queries.md) 36 | List active queries in a PostgreSQL database. 37 | 38 | - [`postgres-list-available-extensions`](../tools/postgres/postgres-list-available-extensions.md) 39 | List available extensions for installation in a PostgreSQL database. 40 | 41 | - [`postgres-list-installed-extensions`](../tools/postgres/postgres-list-installed-extensions.md) 42 | List installed extensions in a PostgreSQL database. 43 | 44 | ### Pre-built Configurations 45 | 46 | - [Cloud SQL for Postgres using 47 | MCP](https://googleapis.github.io/genai-toolbox/how-to/connect-ide/cloud_sql_pg_mcp/) 48 | Connect your IDE to Cloud SQL for Postgres using Toolbox. 49 | 50 | 51 | ## Requirements 52 | 53 | ### IAM Permissions 54 | 55 | By default, this source uses the [Cloud SQL Go Connector][csql-go-conn] to 56 | authorize and establish mTLS connections to your Cloud SQL instance. The Go 57 | connector uses your [Application Default Credentials (ADC)][adc] to authorize 58 | your connection to Cloud SQL. 59 | 60 | In addition to [setting the ADC for your server][set-adc], you need to ensure 61 | the IAM identity has been given the following IAM roles (or corresponding 62 | permissions): 63 | 64 | - `roles/cloudsql.client` 65 | 66 | {{< notice tip >}} 67 | If you are connecting from Compute Engine, make sure your VM 68 | also has the [proper 69 | scope](https://cloud.google.com/compute/docs/access/service-accounts#accesscopesiam) 70 | to connect using the Cloud SQL Admin API. 71 | {{< /notice >}} 72 | 73 | [csql-go-conn]: <https://github.com/GoogleCloudPlatform/cloud-sql-go-connector> 74 | [adc]: <https://cloud.google.com/docs/authentication#adc> 75 | [set-adc]: <https://cloud.google.com/docs/authentication/provide-credentials-adc> 76 | 77 | ### Networking 78 | 79 | Cloud SQL supports connecting over both from external networks via the internet 80 | ([public IP][public-ip]), and internal networks ([private IP][private-ip]). 81 | For more information on choosing between the two options, see the Cloud SQL page 82 | [Connection overview][conn-overview]. 83 | 84 | You can configure the `ipType` parameter in your source configuration to 85 | `public` or `private` to match your cluster's configuration. Regardless of which 86 | you choose, all connections use IAM-based authorization and are encrypted with 87 | mTLS. 88 | 89 | [private-ip]: https://cloud.google.com/sql/docs/postgres/configure-private-ip 90 | [public-ip]: https://cloud.google.com/sql/docs/postgres/configure-ip 91 | [conn-overview]: https://cloud.google.com/sql/docs/postgres/connect-overview 92 | 93 | ### Authentication 94 | 95 | This source supports both password-based authentication and IAM 96 | authentication (using your [Application Default Credentials][adc]). 97 | 98 | #### Standard Authentication 99 | 100 | To connect using user/password, [create 101 | a PostgreSQL user][cloudsql-users] and input your credentials in the `user` and 102 | `password` fields. 103 | 104 | ```yaml 105 | user: ${USER_NAME} 106 | password: ${PASSWORD} 107 | ``` 108 | 109 | #### IAM Authentication 110 | 111 | To connect using IAM authentication: 112 | 113 | 1. Prepare your database instance and user following this [guide][iam-guide]. 114 | 2. You could choose one of the two ways to log in: 115 | - Specify your IAM email as the `user`. 116 | - Leave your `user` field blank. Toolbox will fetch the [ADC][adc] 117 | automatically and log in using the email associated with it. 118 | 119 | 3. Leave the `password` field blank. 120 | 121 | [iam-guide]: https://cloud.google.com/sql/docs/postgres/iam-logins 122 | [cloudsql-users]: https://cloud.google.com/sql/docs/postgres/create-manage-users 123 | 124 | ## Example 125 | 126 | ```yaml 127 | sources: 128 | my-cloud-sql-pg-source: 129 | kind: cloud-sql-postgres 130 | project: my-project-id 131 | region: us-central1 132 | instance: my-instance 133 | database: my_db 134 | user: ${USER_NAME} 135 | password: ${PASSWORD} 136 | # ipType: "private" 137 | ``` 138 | 139 | {{< notice tip >}} 140 | Use environment variable replacement with the format ${ENV_NAME} 141 | instead of hardcoding your secrets into the configuration file. 142 | {{< /notice >}} 143 | 144 | ## Reference 145 | 146 | | **field** | **type** | **required** | **description** | 147 | |-----------|:--------:|:------------:|--------------------------------------------------------------------------------------------------------------------------| 148 | | kind | string | true | Must be "cloud-sql-postgres". | 149 | | project | string | true | Id of the GCP project that the cluster was created in (e.g. "my-project-id"). | 150 | | region | string | true | Name of the GCP region that the cluster was created in (e.g. "us-central1"). | 151 | | instance | string | true | Name of the Cloud SQL instance within the cluster (e.g. "my-instance"). | 152 | | database | string | true | Name of the Postgres database to connect to (e.g. "my_db"). | 153 | | user | string | false | Name of the Postgres user to connect as (e.g. "my-pg-user"). Defaults to IAM auth using [ADC][adc] email if unspecified. | 154 | | password | string | false | Password of the Postgres user (e.g. "my-password"). Defaults to attempting IAM authentication if unspecified. | 155 | | ipType | string | false | IP Type of the Cloud SQL instance; must be one of `public`, `private`, or `psc`. Default: `public`. | 156 | ``` -------------------------------------------------------------------------------- /internal/tools/oracle/oracleexecutesql/oracleexecutesql.go: -------------------------------------------------------------------------------- ```go 1 | // Copyright © 2025, Oracle and/or its affiliates. 2 | 3 | package oracleexecutesql 4 | 5 | import ( 6 | "context" 7 | "database/sql" 8 | "encoding/json" 9 | "fmt" 10 | "strings" 11 | 12 | yaml "github.com/goccy/go-yaml" 13 | "github.com/googleapis/genai-toolbox/internal/sources" 14 | "github.com/googleapis/genai-toolbox/internal/sources/oracle" 15 | "github.com/googleapis/genai-toolbox/internal/tools" 16 | "github.com/googleapis/genai-toolbox/internal/util" 17 | ) 18 | 19 | const kind string = "oracle-execute-sql" 20 | 21 | func init() { 22 | if !tools.Register(kind, newConfig) { 23 | panic(fmt.Sprintf("tool kind %q already registered", kind)) 24 | } 25 | } 26 | 27 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) { 28 | actual := Config{Name: name} 29 | if err := decoder.DecodeContext(ctx, &actual); err != nil { 30 | return nil, err 31 | } 32 | return actual, nil 33 | } 34 | 35 | type compatibleSource interface { 36 | OracleDB() *sql.DB 37 | } 38 | 39 | // validate compatible sources are still compatible 40 | var _ compatibleSource = &oracle.Source{} 41 | 42 | var compatibleSources = [...]string{oracle.SourceKind} 43 | 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" validate:"required"` 49 | AuthRequired []string `yaml:"authRequired"` 50 | } 51 | 52 | // validate interface 53 | var _ tools.ToolConfig = Config{} 54 | 55 | func (cfg Config) ToolConfigKind() string { 56 | return kind 57 | } 58 | 59 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { 60 | // verify source exists 61 | rawS, ok := srcs[cfg.Source] 62 | if !ok { 63 | return nil, fmt.Errorf("no source named %q configured", cfg.Source) 64 | } 65 | 66 | // verify the source is compatible 67 | s, ok := rawS.(compatibleSource) 68 | if !ok { 69 | return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources) 70 | } 71 | 72 | sqlParameter := tools.NewStringParameter("sql", "The SQL to execute.") 73 | parameters := tools.Parameters{sqlParameter} 74 | 75 | mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters) 76 | 77 | // finish tool setup 78 | t := Tool{ 79 | Name: cfg.Name, 80 | Kind: kind, 81 | Parameters: parameters, 82 | AuthRequired: cfg.AuthRequired, 83 | Pool: s.OracleDB(), 84 | manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired}, 85 | mcpManifest: mcpManifest, 86 | } 87 | return t, nil 88 | } 89 | 90 | // validate interface 91 | var _ tools.Tool = Tool{} 92 | 93 | type Tool struct { 94 | Name string `yaml:"name"` 95 | Kind string `yaml:"kind"` 96 | AuthRequired []string `yaml:"authRequired"` 97 | Parameters tools.Parameters `yaml:"parameters"` 98 | 99 | Pool *sql.DB 100 | manifest tools.Manifest 101 | mcpManifest tools.McpManifest 102 | } 103 | 104 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { 105 | paramsMap := params.AsMap() 106 | sqlParam, ok := paramsMap["sql"].(string) 107 | if !ok { 108 | return nil, fmt.Errorf("unable to get cast %s", paramsMap["sql"]) 109 | } 110 | 111 | // Log the query executed for debugging. 112 | logger, err := util.LoggerFromContext(ctx) 113 | if err != nil { 114 | return nil, fmt.Errorf("error getting logger: %s", err) 115 | } 116 | logger.DebugContext(ctx, "executing `%s` tool query: %s", kind, sqlParam) 117 | 118 | results, err := t.Pool.QueryContext(ctx, sqlParam) 119 | if err != nil { 120 | return nil, fmt.Errorf("unable to execute query: %w", err) 121 | } 122 | defer results.Close() 123 | 124 | // If Columns() errors, it might be a DDL/DML without an OUTPUT clause. 125 | // We proceed, and results.Err() will catch actual query execution errors. 126 | // 'out' will remain nil if cols is empty or err is not nil here. 127 | cols, _ := results.Columns() 128 | 129 | // Get Column types 130 | colTypes, err := results.ColumnTypes() 131 | if err != nil { 132 | if err := results.Err(); err != nil { 133 | return nil, fmt.Errorf("query execution error: %w", err) 134 | } 135 | return []any{}, nil 136 | } 137 | 138 | var out []any 139 | for results.Next() { 140 | // Create slice to hold values 141 | values := make([]any, len(cols)) 142 | for i, colType := range colTypes { 143 | // Based on the database type, we prepare a pointer to a Go type. 144 | switch strings.ToUpper(colType.DatabaseTypeName()) { 145 | case "NUMBER", "FLOAT", "BINARY_FLOAT", "BINARY_DOUBLE": 146 | if _, scale, ok := colType.DecimalSize(); ok && scale == 0 { 147 | // Scale is 0, treat as an integer. 148 | values[i] = new(sql.NullInt64) 149 | } else { 150 | // Scale is non-zero or unknown, treat as a float. 151 | values[i] = new(sql.NullFloat64) 152 | } 153 | case "DATE", "TIMESTAMP", "TIMESTAMP WITH TIME ZONE", "TIMESTAMP WITH LOCAL TIME ZONE": 154 | values[i] = new(sql.NullTime) 155 | case "JSON": 156 | values[i] = new(sql.RawBytes) 157 | default: 158 | values[i] = new(sql.NullString) 159 | } 160 | } 161 | 162 | if err := results.Scan(values...); err != nil { 163 | return nil, fmt.Errorf("unable to scan row: %w", err) 164 | } 165 | 166 | vMap := make(map[string]any) 167 | for i, col := range cols { 168 | receiver := values[i] 169 | 170 | // Dereference the pointer and check for validity (not NULL). 171 | switch v := receiver.(type) { 172 | case *sql.NullInt64: 173 | if v.Valid { 174 | vMap[col] = v.Int64 175 | } else { 176 | vMap[col] = nil 177 | } 178 | case *sql.NullFloat64: 179 | if v.Valid { 180 | vMap[col] = v.Float64 181 | } else { 182 | vMap[col] = nil 183 | } 184 | case *sql.NullString: 185 | if v.Valid { 186 | vMap[col] = v.String 187 | } else { 188 | vMap[col] = nil 189 | } 190 | case *sql.NullTime: 191 | if v.Valid { 192 | vMap[col] = v.Time 193 | } else { 194 | vMap[col] = nil 195 | } 196 | case *sql.RawBytes: 197 | if *v != nil { 198 | var unmarshaledData any 199 | if err := json.Unmarshal(*v, &unmarshaledData); err != nil { 200 | return nil, fmt.Errorf("unable to unmarshal json data for column %s", col) 201 | } 202 | vMap[col] = unmarshaledData 203 | } else { 204 | vMap[col] = nil 205 | } 206 | default: 207 | return nil, fmt.Errorf("unexpected receiver type: %T", v) 208 | } 209 | } 210 | out = append(out, vMap) 211 | } 212 | 213 | if err := results.Err(); err != nil { 214 | return nil, fmt.Errorf("errors encountered during query execution or row processing: %w", err) 215 | } 216 | 217 | return out, nil 218 | } 219 | 220 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { 221 | return tools.ParseParams(t.Parameters, data, claims) 222 | } 223 | 224 | func (t Tool) Manifest() tools.Manifest { 225 | return t.manifest 226 | } 227 | 228 | func (t Tool) McpManifest() tools.McpManifest { 229 | return t.mcpManifest 230 | } 231 | 232 | func (t Tool) Authorized(verifiedAuthServices []string) bool { 233 | return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices) 234 | } 235 | 236 | func (t Tool) RequiresClientAuthorization() bool { 237 | return false 238 | } 239 | ``` -------------------------------------------------------------------------------- /internal/tools/dataplex/dataplexsearchaspecttypes/dataplexsearchaspecttypes.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 dataplexsearchaspecttypes 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | 21 | dataplexapi "cloud.google.com/go/dataplex/apiv1" 22 | dataplexpb "cloud.google.com/go/dataplex/apiv1/dataplexpb" 23 | "github.com/cenkalti/backoff/v5" 24 | "github.com/goccy/go-yaml" 25 | "github.com/googleapis/genai-toolbox/internal/sources" 26 | dataplexds "github.com/googleapis/genai-toolbox/internal/sources/dataplex" 27 | "github.com/googleapis/genai-toolbox/internal/tools" 28 | ) 29 | 30 | const kind string = "dataplex-search-aspect-types" 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 compatibleSource interface { 47 | CatalogClient() *dataplexapi.CatalogClient 48 | ProjectID() string 49 | } 50 | 51 | // validate compatible sources are still compatible 52 | var _ compatibleSource = &dataplexds.Source{} 53 | 54 | var compatibleSources = [...]string{dataplexds.SourceKind} 55 | 56 | type Config struct { 57 | Name string `yaml:"name" validate:"required"` 58 | Kind string `yaml:"kind" validate:"required"` 59 | Source string `yaml:"source" validate:"required"` 60 | Description string `yaml:"description"` 61 | AuthRequired []string `yaml:"authRequired"` 62 | } 63 | 64 | // validate interface 65 | var _ tools.ToolConfig = Config{} 66 | 67 | func (cfg Config) ToolConfigKind() string { 68 | return kind 69 | } 70 | 71 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { 72 | // Initialize the search configuration with the provided sources 73 | rawS, ok := srcs[cfg.Source] 74 | if !ok { 75 | return nil, fmt.Errorf("no source named %q configured", cfg.Source) 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 | query := tools.NewStringParameter("query", "The query against which aspect type should be matched.") 84 | pageSize := tools.NewIntParameterWithDefault("pageSize", 5, "Number of returned aspect types in the search page.") 85 | orderBy := tools.NewStringParameterWithDefault("orderBy", "relevance", "Specifies the ordering of results. Supported values are: relevance, last_modified_timestamp, last_modified_timestamp asc") 86 | parameters := tools.Parameters{query, pageSize, orderBy} 87 | 88 | mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters) 89 | 90 | t := Tool{ 91 | Name: cfg.Name, 92 | Kind: kind, 93 | Parameters: parameters, 94 | AuthRequired: cfg.AuthRequired, 95 | CatalogClient: s.CatalogClient(), 96 | ProjectID: s.ProjectID(), 97 | manifest: tools.Manifest{ 98 | Description: cfg.Description, 99 | Parameters: parameters.Manifest(), 100 | AuthRequired: cfg.AuthRequired, 101 | }, 102 | mcpManifest: mcpManifest, 103 | } 104 | return t, nil 105 | } 106 | 107 | type Tool struct { 108 | Name string 109 | Kind string 110 | Parameters tools.Parameters 111 | AuthRequired []string 112 | CatalogClient *dataplexapi.CatalogClient 113 | ProjectID string 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 | // Invoke the tool with the provided parameters 120 | paramsMap := params.AsMap() 121 | query, _ := paramsMap["query"].(string) 122 | pageSize := int32(paramsMap["pageSize"].(int)) 123 | orderBy, _ := paramsMap["orderBy"].(string) 124 | 125 | // Create SearchEntriesRequest with the provided parameters 126 | req := &dataplexpb.SearchEntriesRequest{ 127 | Query: query + " type=projects/dataplex-types/locations/global/entryTypes/aspecttype", 128 | Name: fmt.Sprintf("projects/%s/locations/global", t.ProjectID), 129 | PageSize: pageSize, 130 | OrderBy: orderBy, 131 | SemanticSearch: true, 132 | } 133 | 134 | // Perform the search using the CatalogClient - this will return an iterator 135 | it := t.CatalogClient.SearchEntries(ctx, req) 136 | if it == nil { 137 | return nil, fmt.Errorf("failed to create search entries iterator for project %q", t.ProjectID) 138 | } 139 | 140 | // Create an instance of exponential backoff with default values for retrying GetAspectType calls 141 | // InitialInterval, RandomizationFactor, Multiplier, MaxInterval = 500 ms, 0.5, 1.5, 60 s 142 | getAspectBackOff := backoff.NewExponentialBackOff() 143 | 144 | // Iterate through the search results and call GetAspectType for each result using the resource name 145 | var results []*dataplexpb.AspectType 146 | for { 147 | entry, err := it.Next() 148 | if err != nil { 149 | break 150 | } 151 | resourceName := entry.DataplexEntry.GetEntrySource().Resource 152 | getAspectTypeReq := &dataplexpb.GetAspectTypeRequest{ 153 | Name: resourceName, 154 | } 155 | 156 | operation := func() (*dataplexpb.AspectType, error) { 157 | aspectType, err := t.CatalogClient.GetAspectType(ctx, getAspectTypeReq) 158 | if err != nil { 159 | return nil, fmt.Errorf("failed to get aspect type for entry %q: %w", resourceName, err) 160 | } 161 | return aspectType, nil 162 | } 163 | 164 | // Retry the GetAspectType operation with exponential backoff 165 | aspectType, err := backoff.Retry(ctx, operation, backoff.WithBackOff(getAspectBackOff)) 166 | if err != nil { 167 | return nil, fmt.Errorf("failed to get aspect type after retries for entry %q: %w", resourceName, err) 168 | } 169 | 170 | results = append(results, aspectType) 171 | } 172 | return results, nil 173 | } 174 | 175 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { 176 | // Parse parameters from the provided data 177 | return tools.ParseParams(t.Parameters, data, claims) 178 | } 179 | 180 | func (t Tool) Manifest() tools.Manifest { 181 | // Returns the tool manifest 182 | return t.manifest 183 | } 184 | 185 | func (t Tool) McpManifest() tools.McpManifest { 186 | // Returns the tool MCP manifest 187 | return t.mcpManifest 188 | } 189 | 190 | func (t Tool) Authorized(verifiedAuthServices []string) bool { 191 | return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices) 192 | } 193 | 194 | func (t Tool) RequiresClientAuthorization() bool { 195 | return false 196 | } 197 | ``` -------------------------------------------------------------------------------- /internal/tools/tidb/tidbsql/tidbsql.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 tidbsql 16 | 17 | import ( 18 | "context" 19 | "database/sql" 20 | "encoding/json" 21 | "fmt" 22 | 23 | yaml "github.com/goccy/go-yaml" 24 | "github.com/googleapis/genai-toolbox/internal/sources" 25 | "github.com/googleapis/genai-toolbox/internal/sources/tidb" 26 | "github.com/googleapis/genai-toolbox/internal/tools" 27 | ) 28 | 29 | const kind string = "tidb-sql" 30 | 31 | func init() { 32 | if !tools.Register(kind, newConfig) { 33 | panic(fmt.Sprintf("tool kind %q already registered", kind)) 34 | } 35 | } 36 | 37 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) { 38 | actual := Config{Name: name} 39 | if err := decoder.DecodeContext(ctx, &actual); err != nil { 40 | return nil, err 41 | } 42 | return actual, nil 43 | } 44 | 45 | type compatibleSource interface { 46 | TiDBPool() *sql.DB 47 | } 48 | 49 | // validate compatible sources are still compatible 50 | var _ compatibleSource = &tidb.Source{} 51 | 52 | var compatibleSources = [...]string{tidb.SourceKind} 53 | 54 | type Config struct { 55 | Name string `yaml:"name" validate:"required"` 56 | Kind string `yaml:"kind" validate:"required"` 57 | Source string `yaml:"source" validate:"required"` 58 | Description string `yaml:"description" validate:"required"` 59 | Statement string `yaml:"statement" validate:"required"` 60 | AuthRequired []string `yaml:"authRequired"` 61 | Parameters tools.Parameters `yaml:"parameters"` 62 | TemplateParameters tools.Parameters `yaml:"templateParameters"` 63 | } 64 | 65 | // validate interface 66 | var _ tools.ToolConfig = Config{} 67 | 68 | func (cfg Config) ToolConfigKind() string { 69 | return kind 70 | } 71 | 72 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { 73 | // verify source exists 74 | rawS, ok := srcs[cfg.Source] 75 | if !ok { 76 | return nil, fmt.Errorf("no source named %q configured", cfg.Source) 77 | } 78 | 79 | // verify the source is compatible 80 | s, ok := rawS.(compatibleSource) 81 | if !ok { 82 | return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources) 83 | } 84 | 85 | allParameters, paramManifest, err := tools.ProcessParameters(cfg.TemplateParameters, cfg.Parameters) 86 | if err != nil { 87 | return nil, err 88 | } 89 | 90 | mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, allParameters) 91 | 92 | // finish tool setup 93 | t := Tool{ 94 | Name: cfg.Name, 95 | Kind: kind, 96 | Parameters: cfg.Parameters, 97 | TemplateParameters: cfg.TemplateParameters, 98 | AllParams: allParameters, 99 | Statement: cfg.Statement, 100 | AuthRequired: cfg.AuthRequired, 101 | Pool: s.TiDBPool(), 102 | manifest: tools.Manifest{Description: cfg.Description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired}, 103 | mcpManifest: mcpManifest, 104 | } 105 | return t, nil 106 | } 107 | 108 | // validate interface 109 | var _ tools.Tool = Tool{} 110 | 111 | type Tool struct { 112 | Name string `yaml:"name"` 113 | Kind string `yaml:"kind"` 114 | AuthRequired []string `yaml:"authRequired"` 115 | Parameters tools.Parameters `yaml:"parameters"` 116 | TemplateParameters tools.Parameters `yaml:"templateParameters"` 117 | AllParams tools.Parameters `yaml:"allParams"` 118 | 119 | Pool *sql.DB 120 | Statement string 121 | manifest tools.Manifest 122 | mcpManifest tools.McpManifest 123 | } 124 | 125 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { 126 | paramsMap := params.AsMap() 127 | newStatement, err := tools.ResolveTemplateParams(t.TemplateParameters, t.Statement, paramsMap) 128 | if err != nil { 129 | return nil, fmt.Errorf("unable to extract template params %w", err) 130 | } 131 | 132 | newParams, err := tools.GetParams(t.Parameters, paramsMap) 133 | if err != nil { 134 | return nil, fmt.Errorf("unable to extract standard params %w", err) 135 | } 136 | 137 | sliceParams := newParams.AsSlice() 138 | results, err := t.Pool.QueryContext(ctx, newStatement, sliceParams...) 139 | if err != nil { 140 | return nil, fmt.Errorf("unable to execute query: %w", err) 141 | } 142 | 143 | cols, err := results.Columns() 144 | if err != nil { 145 | return nil, fmt.Errorf("unable to retrieve rows column name: %w", err) 146 | } 147 | 148 | // create an array of values for each column, which can be re-used to scan each row 149 | rawValues := make([]any, len(cols)) 150 | values := make([]any, len(cols)) 151 | for i := range rawValues { 152 | values[i] = &rawValues[i] 153 | } 154 | defer results.Close() 155 | 156 | colTypes, err := results.ColumnTypes() 157 | if err != nil { 158 | return nil, fmt.Errorf("unable to get column types: %w", err) 159 | } 160 | 161 | var out []any 162 | for results.Next() { 163 | err := results.Scan(values...) 164 | if err != nil { 165 | return nil, fmt.Errorf("unable to parse row: %w", err) 166 | } 167 | vMap := make(map[string]any) 168 | for i, name := range cols { 169 | val := rawValues[i] 170 | if val == nil { 171 | vMap[name] = nil 172 | continue 173 | } 174 | 175 | // mysql driver return []uint8 type for "TEXT", "VARCHAR", and "NVARCHAR" 176 | // we'll need to cast it back to string 177 | switch colTypes[i].DatabaseTypeName() { 178 | case "JSON": 179 | // unmarshal JSON data before storing to prevent double marshaling 180 | var unmarshaledData any 181 | err := json.Unmarshal(val.([]byte), &unmarshaledData) 182 | if err != nil { 183 | return nil, fmt.Errorf("unable to unmarshal json data %s", val) 184 | } 185 | vMap[name] = unmarshaledData 186 | case "TEXT", "VARCHAR", "NVARCHAR": 187 | vMap[name] = string(val.([]byte)) 188 | default: 189 | vMap[name] = val 190 | } 191 | } 192 | out = append(out, vMap) 193 | } 194 | 195 | if err := results.Err(); err != nil { 196 | return nil, fmt.Errorf("errors encountered during row iteration: %w", err) 197 | } 198 | 199 | return out, nil 200 | } 201 | 202 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { 203 | return tools.ParseParams(t.AllParams, data, claims) 204 | } 205 | 206 | func (t Tool) Manifest() tools.Manifest { 207 | return t.manifest 208 | } 209 | 210 | func (t Tool) McpManifest() tools.McpManifest { 211 | return t.mcpManifest 212 | } 213 | 214 | func (t Tool) Authorized(verifiedAuthServices []string) bool { 215 | return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices) 216 | } 217 | 218 | func (t Tool) RequiresClientAuthorization() bool { 219 | return false 220 | } 221 | ``` -------------------------------------------------------------------------------- /tests/cloudsql/cloud_sql_get_instances_test.go: -------------------------------------------------------------------------------- ```go 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cloudsql 16 | 17 | import ( 18 | "bytes" 19 | "context" 20 | "encoding/json" 21 | "fmt" 22 | "io" 23 | "net/http" 24 | "net/http/httptest" 25 | "net/url" 26 | "reflect" 27 | "regexp" 28 | "strings" 29 | "sync" 30 | "testing" 31 | "time" 32 | 33 | "github.com/googleapis/genai-toolbox/internal/testutils" 34 | "github.com/googleapis/genai-toolbox/tests" 35 | ) 36 | 37 | var ( 38 | getInstancesToolKind = "cloud-sql-get-instance" 39 | ) 40 | 41 | type getInstancesTransport struct { 42 | transport http.RoundTripper 43 | url *url.URL 44 | } 45 | 46 | func (t *getInstancesTransport) RoundTrip(req *http.Request) (*http.Response, error) { 47 | if strings.HasPrefix(req.URL.String(), "https://sqladmin.googleapis.com") { 48 | req.URL.Scheme = t.url.Scheme 49 | req.URL.Host = t.url.Host 50 | } 51 | return t.transport.RoundTrip(req) 52 | } 53 | 54 | type instance struct { 55 | Name string `json:"name"` 56 | Kind string `json:"kind"` 57 | } 58 | 59 | type handler struct { 60 | mu sync.Mutex 61 | instances map[string]*instance 62 | t *testing.T 63 | } 64 | 65 | func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 66 | h.mu.Lock() 67 | defer h.mu.Unlock() 68 | 69 | if !strings.Contains(r.UserAgent(), "genai-toolbox/") { 70 | h.t.Errorf("User-Agent header not found") 71 | } 72 | 73 | if !strings.HasPrefix(r.URL.Path, "/v1/projects/") { 74 | http.Error(w, "unexpected path", http.StatusBadRequest) 75 | return 76 | } 77 | 78 | // The format is /v1/projects/{project}/instances/{instance_name} 79 | // We only care about the instance_name for the test 80 | parts := regexp.MustCompile("/").Split(r.URL.Path, -1) 81 | instanceName := parts[len(parts)-1] 82 | 83 | inst, ok := h.instances[instanceName] 84 | if !ok { 85 | http.NotFound(w, r) 86 | return 87 | } 88 | 89 | w.Header().Set("Content-Type", "application/json") 90 | if err := json.NewEncoder(w).Encode(inst); err != nil { 91 | http.Error(w, err.Error(), http.StatusInternalServerError) 92 | } 93 | } 94 | 95 | func TestGetInstancesToolEndpoints(t *testing.T) { 96 | h := &handler{ 97 | instances: map[string]*instance{ 98 | "instance-1": {Name: "instance-1", Kind: "sql#instance"}, 99 | }, 100 | t: t, 101 | } 102 | server := httptest.NewServer(h) 103 | defer server.Close() 104 | 105 | serverURL, err := url.Parse(server.URL) 106 | if err != nil { 107 | t.Fatalf("failed to parse server URL: %v", err) 108 | } 109 | 110 | originalTransport := http.DefaultClient.Transport 111 | if originalTransport == nil { 112 | originalTransport = http.DefaultTransport 113 | } 114 | http.DefaultClient.Transport = &getInstancesTransport{ 115 | transport: originalTransport, 116 | url: serverURL, 117 | } 118 | t.Cleanup(func() { 119 | http.DefaultClient.Transport = originalTransport 120 | }) 121 | 122 | ctx, cancel := context.WithTimeout(context.Background(), time.Minute) 123 | defer cancel() 124 | 125 | var args []string 126 | 127 | toolsFile := getToolsConfig() 128 | cmd, cleanup, err := tests.StartCmd(ctx, toolsFile, args...) 129 | if err != nil { 130 | t.Fatalf("command initialization returned an error: %s", err) 131 | } 132 | defer cleanup() 133 | 134 | waitCtx, cancel := context.WithTimeout(ctx, 10*time.Second) 135 | defer cancel() 136 | out, err := testutils.WaitForString(waitCtx, regexp.MustCompile(`Server ready to serve`), cmd.Out) 137 | if err != nil { 138 | t.Logf("toolbox command logs: \n%s", out) 139 | t.Fatalf("toolbox didn't start successfully: %s", err) 140 | } 141 | 142 | tcs := []struct { 143 | name string 144 | toolName string 145 | body string 146 | want string 147 | expectError bool 148 | wantSubstring bool 149 | }{ 150 | { 151 | name: "successful get instance", 152 | toolName: "get-instance-1", 153 | body: `{"projectId": "p1", "instanceId": "instance-1"}`, 154 | want: `{"name":"instance-1","kind":"sql#instance"}`, 155 | }, 156 | { 157 | name: "failed get instance", 158 | toolName: "get-instance-2", 159 | body: `{"projectId": "p1", "instanceId": "instance-2"}`, 160 | expectError: true, 161 | }, 162 | } 163 | 164 | for _, tc := range tcs { 165 | t.Run(tc.name, func(t *testing.T) { 166 | api := fmt.Sprintf("http://127.0.0.1:5000/api/tool/%s/invoke", tc.toolName) 167 | req, err := http.NewRequest(http.MethodPost, api, bytes.NewBufferString(tc.body)) 168 | if err != nil { 169 | t.Fatalf("unable to create request: %s", err) 170 | } 171 | req.Header.Add("Content-type", "application/json") 172 | resp, err := http.DefaultClient.Do(req) 173 | if err != nil { 174 | t.Fatalf("unable to send request: %s", err) 175 | } 176 | defer resp.Body.Close() 177 | 178 | if tc.expectError { 179 | if resp.StatusCode == http.StatusOK { 180 | t.Fatal("expected error but got status 200") 181 | } 182 | return 183 | } 184 | 185 | if resp.StatusCode != http.StatusOK { 186 | bodyBytes, _ := io.ReadAll(resp.Body) 187 | t.Fatalf("response status code is not 200, got %d: %s", resp.StatusCode, string(bodyBytes)) 188 | } 189 | 190 | var result struct { 191 | Result any `json:"result"` 192 | } 193 | if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { 194 | t.Fatalf("failed to decode response: %v", err) 195 | } 196 | 197 | var gotBytes []byte 198 | if s, ok := result.Result.(string); ok { 199 | gotBytes = []byte(s) 200 | } else { 201 | var err error 202 | gotBytes, err = json.Marshal(result.Result) 203 | if err != nil { 204 | t.Fatalf("failed to marshal result: %v", err) 205 | } 206 | } 207 | 208 | if tc.wantSubstring { 209 | if !bytes.Contains(gotBytes, []byte(tc.want)) { 210 | t.Fatalf("unexpected result: got %q, want substring %q", string(gotBytes), tc.want) 211 | } 212 | return 213 | } 214 | 215 | var got, want map[string]any 216 | if err := json.Unmarshal(gotBytes, &got); err != nil { 217 | t.Fatalf("failed to unmarshal result: %v", err) 218 | } 219 | if err := json.Unmarshal([]byte(tc.want), &want); err != nil { 220 | t.Fatalf("failed to unmarshal want: %v", err) 221 | } 222 | 223 | if !reflect.DeepEqual(got, want) { 224 | t.Fatalf("unexpected result: got %+v, want %+v", got, want) 225 | } 226 | }) 227 | } 228 | } 229 | 230 | func getToolsConfig() map[string]any { 231 | return map[string]any{ 232 | "sources": map[string]any{ 233 | "my-cloud-sql-source": map[string]any{ 234 | "kind": "cloud-sql-admin", 235 | }, 236 | "my-invalid-cloud-sql-source": map[string]any{ 237 | "kind": "cloud-sql-admin", 238 | "useClientOAuth": true, 239 | }, 240 | }, 241 | "tools": map[string]any{ 242 | "get-instance-1": map[string]any{ 243 | "kind": getInstancesToolKind, 244 | "description": "get instance 1", 245 | "source": "my-cloud-sql-source", 246 | }, 247 | "get-instance-2": map[string]any{ 248 | "kind": getInstancesToolKind, 249 | "description": "get instance 2", 250 | "source": "my-invalid-cloud-sql-source", 251 | }, 252 | }, 253 | } 254 | } 255 | ``` -------------------------------------------------------------------------------- /internal/tools/oceanbase/oceanbasesql/oceanbasesql.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 oceanbasesql 16 | 17 | import ( 18 | "context" 19 | "database/sql" 20 | "fmt" 21 | 22 | yaml "github.com/goccy/go-yaml" 23 | "github.com/googleapis/genai-toolbox/internal/sources" 24 | "github.com/googleapis/genai-toolbox/internal/sources/oceanbase" 25 | "github.com/googleapis/genai-toolbox/internal/tools" 26 | "github.com/googleapis/genai-toolbox/internal/tools/mysql/mysqlcommon" 27 | ) 28 | 29 | const kind string = "oceanbase-sql" 30 | 31 | func init() { 32 | if !tools.Register(kind, newConfig) { 33 | panic(fmt.Sprintf("tool kind %q already registered", kind)) 34 | } 35 | } 36 | 37 | type compatibleSource interface { 38 | OceanBasePool() *sql.DB 39 | } 40 | 41 | // validate compatible sources are still compatible 42 | var _ compatibleSource = &oceanbase.Source{} 43 | 44 | var compatibleSources = [...]string{oceanbase.SourceKind} 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 | Statement string `yaml:"statement" validate:"required"` 52 | AuthRequired []string `yaml:"authRequired"` 53 | Parameters tools.Parameters `yaml:"parameters"` 54 | TemplateParameters tools.Parameters `yaml:"templateParameters"` 55 | } 56 | 57 | // validate interface 58 | var _ tools.ToolConfig = Config{} 59 | 60 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) { 61 | actual := Config{Name: name} 62 | if err := decoder.DecodeContext(ctx, &actual); err != nil { 63 | return nil, err 64 | } 65 | return actual, nil 66 | } 67 | 68 | func (cfg Config) ToolConfigKind() string { 69 | return kind 70 | } 71 | 72 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { 73 | // verify source exists 74 | rawS, ok := srcs[cfg.Source] 75 | if !ok { 76 | return nil, fmt.Errorf("no source named %q configured", cfg.Source) 77 | } 78 | 79 | // verify the source is compatible 80 | s, ok := rawS.(compatibleSource) 81 | if !ok { 82 | return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources) 83 | } 84 | 85 | allParameters, paramManifest, err := tools.ProcessParameters(cfg.TemplateParameters, cfg.Parameters) 86 | if err != nil { 87 | return nil, fmt.Errorf("unable to process parameters: %w", err) 88 | } 89 | 90 | mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, allParameters) 91 | 92 | // finish tool setup 93 | t := Tool{ 94 | Name: cfg.Name, 95 | Kind: kind, 96 | Parameters: cfg.Parameters, 97 | TemplateParameters: cfg.TemplateParameters, 98 | AllParams: allParameters, 99 | Statement: cfg.Statement, 100 | AuthRequired: cfg.AuthRequired, 101 | Pool: s.OceanBasePool(), 102 | manifest: tools.Manifest{Description: cfg.Description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired}, 103 | mcpManifest: mcpManifest, 104 | } 105 | return t, nil 106 | } 107 | 108 | // validate interface 109 | var _ tools.Tool = Tool{} 110 | 111 | type Tool struct { 112 | Name string `yaml:"name"` 113 | Kind string `yaml:"kind"` 114 | AuthRequired []string `yaml:"authRequired"` 115 | Parameters tools.Parameters `yaml:"parameters"` 116 | TemplateParameters tools.Parameters `yaml:"templateParameters"` 117 | AllParams tools.Parameters `yaml:"allParams"` 118 | 119 | Pool *sql.DB 120 | Statement string 121 | manifest tools.Manifest 122 | mcpManifest tools.McpManifest 123 | } 124 | 125 | // Invoke executes the SQL statement with the provided parameters. 126 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { 127 | paramsMap := params.AsMap() 128 | newStatement, err := tools.ResolveTemplateParams(t.TemplateParameters, t.Statement, paramsMap) 129 | if err != nil { 130 | return nil, fmt.Errorf("unable to extract template params %w", err) 131 | } 132 | 133 | newParams, err := tools.GetParams(t.Parameters, paramsMap) 134 | if err != nil { 135 | return nil, fmt.Errorf("unable to extract standard params %w", err) 136 | } 137 | 138 | sliceParams := newParams.AsSlice() 139 | results, err := t.Pool.QueryContext(ctx, newStatement, sliceParams...) 140 | if err != nil { 141 | return nil, fmt.Errorf("unable to execute query: %w", err) 142 | } 143 | 144 | cols, err := results.Columns() 145 | if err != nil { 146 | return nil, fmt.Errorf("unable to retrieve rows column name: %w", err) 147 | } 148 | 149 | // create an array of values for each column, which can be re-used to scan each row 150 | rawValues := make([]any, len(cols)) 151 | values := make([]any, len(cols)) 152 | for i := range rawValues { 153 | values[i] = &rawValues[i] 154 | } 155 | defer results.Close() 156 | 157 | colTypes, err := results.ColumnTypes() 158 | if err != nil { 159 | return nil, fmt.Errorf("unable to get column types: %w", err) 160 | } 161 | 162 | var out []any 163 | for results.Next() { 164 | err := results.Scan(values...) 165 | if err != nil { 166 | return nil, fmt.Errorf("unable to parse row: %w", err) 167 | } 168 | vMap := make(map[string]any) 169 | for i, name := range cols { 170 | val := rawValues[i] 171 | if val == nil { 172 | vMap[name] = nil 173 | continue 174 | } 175 | 176 | // oceanbase uses mysql driver 177 | vMap[name], err = mysqlcommon.ConvertToType(colTypes[i], val) 178 | if err != nil { 179 | return nil, fmt.Errorf("errors encountered when converting values: %w", err) 180 | } 181 | } 182 | out = append(out, vMap) 183 | } 184 | 185 | if err := results.Err(); err != nil { 186 | return nil, fmt.Errorf("errors encountered during row iteration: %w", err) 187 | } 188 | 189 | return out, nil 190 | } 191 | 192 | // ParseParams parses the input parameters for the tool. 193 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { 194 | return tools.ParseParams(t.AllParams, data, claims) 195 | } 196 | 197 | // Manifest returns the tool manifest. 198 | func (t Tool) Manifest() tools.Manifest { 199 | return t.manifest 200 | } 201 | 202 | // McpManifest returns the MCP manifest for the tool. 203 | func (t Tool) McpManifest() tools.McpManifest { 204 | return t.mcpManifest 205 | } 206 | 207 | // Authorized checks if the tool is authorized for the given auth services. 208 | func (t Tool) Authorized(verifiedAuthServices []string) bool { 209 | return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices) 210 | } 211 | 212 | func (t Tool) RequiresClientAuthorization() bool { 213 | return false 214 | } 215 | ``` -------------------------------------------------------------------------------- /internal/tools/bigquery/bigquerylisttableids/bigquerylisttableids.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 bigquerylisttableids 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | 21 | bigqueryapi "cloud.google.com/go/bigquery" 22 | yaml "github.com/goccy/go-yaml" 23 | "github.com/googleapis/genai-toolbox/internal/sources" 24 | bigqueryds "github.com/googleapis/genai-toolbox/internal/sources/bigquery" 25 | "github.com/googleapis/genai-toolbox/internal/tools" 26 | bqutil "github.com/googleapis/genai-toolbox/internal/tools/bigquery/bigquerycommon" 27 | "google.golang.org/api/iterator" 28 | ) 29 | 30 | const kind string = "bigquery-list-table-ids" 31 | const projectKey string = "project" 32 | const datasetKey string = "dataset" 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 compatibleSource interface { 49 | BigQueryClient() *bigqueryapi.Client 50 | BigQueryClientCreator() bigqueryds.BigqueryClientCreator 51 | BigQueryProject() string 52 | UseClientAuthorization() bool 53 | IsDatasetAllowed(projectID, datasetID string) bool 54 | BigQueryAllowedDatasets() []string 55 | } 56 | 57 | // validate compatible sources are still compatible 58 | var _ compatibleSource = &bigqueryds.Source{} 59 | 60 | var compatibleSources = [...]string{bigqueryds.SourceKind} 61 | 62 | type Config struct { 63 | Name string `yaml:"name" validate:"required"` 64 | Kind string `yaml:"kind" validate:"required"` 65 | Source string `yaml:"source" validate:"required"` 66 | Description string `yaml:"description" validate:"required"` 67 | AuthRequired []string `yaml:"authRequired"` 68 | } 69 | 70 | // validate interface 71 | var _ tools.ToolConfig = Config{} 72 | 73 | func (cfg Config) ToolConfigKind() string { 74 | return kind 75 | } 76 | 77 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { 78 | // verify source exists 79 | rawS, ok := srcs[cfg.Source] 80 | if !ok { 81 | return nil, fmt.Errorf("no source named %q configured", cfg.Source) 82 | } 83 | 84 | // verify the source is compatible 85 | s, ok := rawS.(compatibleSource) 86 | if !ok { 87 | return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources) 88 | } 89 | 90 | defaultProjectID := s.BigQueryProject() 91 | projectDescription := "The Google Cloud project ID containing the dataset." 92 | datasetDescription := "The dataset to list table ids." 93 | var datasetParameter tools.Parameter 94 | var projectParameter tools.Parameter 95 | 96 | projectParameter, datasetParameter = bqutil.InitializeDatasetParameters( 97 | s.BigQueryAllowedDatasets(), 98 | defaultProjectID, 99 | projectKey, datasetKey, 100 | projectDescription, datasetDescription, 101 | ) 102 | 103 | parameters := tools.Parameters{projectParameter, datasetParameter} 104 | 105 | mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters) 106 | 107 | // finish tool setup 108 | t := Tool{ 109 | Name: cfg.Name, 110 | Kind: kind, 111 | Parameters: parameters, 112 | AuthRequired: cfg.AuthRequired, 113 | UseClientOAuth: s.UseClientAuthorization(), 114 | ClientCreator: s.BigQueryClientCreator(), 115 | Client: s.BigQueryClient(), 116 | IsDatasetAllowed: s.IsDatasetAllowed, 117 | manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired}, 118 | mcpManifest: mcpManifest, 119 | } 120 | return t, nil 121 | } 122 | 123 | // validate interface 124 | var _ tools.Tool = Tool{} 125 | 126 | type Tool struct { 127 | Name string `yaml:"name"` 128 | Kind string `yaml:"kind"` 129 | AuthRequired []string `yaml:"authRequired"` 130 | UseClientOAuth bool `yaml:"useClientOAuth"` 131 | Parameters tools.Parameters `yaml:"parameters"` 132 | 133 | Client *bigqueryapi.Client 134 | ClientCreator bigqueryds.BigqueryClientCreator 135 | IsDatasetAllowed func(projectID, datasetID string) bool 136 | Statement string 137 | manifest tools.Manifest 138 | mcpManifest tools.McpManifest 139 | } 140 | 141 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { 142 | mapParams := params.AsMap() 143 | projectId, ok := mapParams[projectKey].(string) 144 | if !ok { 145 | return nil, fmt.Errorf("invalid or missing '%s' parameter; expected a string", projectKey) 146 | } 147 | 148 | datasetId, ok := mapParams[datasetKey].(string) 149 | if !ok { 150 | return nil, fmt.Errorf("invalid or missing '%s' parameter; expected a string", datasetKey) 151 | } 152 | 153 | if !t.IsDatasetAllowed(projectId, datasetId) { 154 | return nil, fmt.Errorf("access denied to dataset '%s' because it is not in the configured list of allowed datasets for project '%s'", datasetId, projectId) 155 | } 156 | 157 | bqClient := t.Client 158 | // Initialize new client if using user OAuth token 159 | if t.UseClientOAuth { 160 | tokenStr, err := accessToken.ParseBearerToken() 161 | if err != nil { 162 | return nil, fmt.Errorf("error parsing access token: %w", err) 163 | } 164 | bqClient, _, err = t.ClientCreator(tokenStr, false) 165 | if err != nil { 166 | return nil, fmt.Errorf("error creating client from OAuth access token: %w", err) 167 | } 168 | } 169 | 170 | dsHandle := bqClient.DatasetInProject(projectId, datasetId) 171 | 172 | var tableIds []any 173 | tableIterator := dsHandle.Tables(ctx) 174 | for { 175 | table, err := tableIterator.Next() 176 | if err == iterator.Done { 177 | break 178 | } 179 | if err != nil { 180 | return nil, fmt.Errorf("failed to iterate through tables in dataset %s.%s: %w", projectId, datasetId, err) 181 | } 182 | 183 | // Remove leading and trailing quotes 184 | id := table.TableID 185 | if len(id) >= 2 && id[0] == '"' && id[len(id)-1] == '"' { 186 | id = id[1 : len(id)-1] 187 | } 188 | tableIds = append(tableIds, id) 189 | } 190 | 191 | return tableIds, nil 192 | } 193 | 194 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { 195 | return tools.ParseParams(t.Parameters, data, claims) 196 | } 197 | 198 | func (t Tool) Manifest() tools.Manifest { 199 | return t.manifest 200 | } 201 | 202 | func (t Tool) McpManifest() tools.McpManifest { 203 | return t.mcpManifest 204 | } 205 | 206 | func (t Tool) Authorized(verifiedAuthServices []string) bool { 207 | return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices) 208 | } 209 | 210 | func (t Tool) RequiresClientAuthorization() bool { 211 | return t.UseClientOAuth 212 | } 213 | ``` -------------------------------------------------------------------------------- /internal/tools/bigquery/bigquerygettableinfo/bigquerygettableinfo.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 bigquerygettableinfo 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | 21 | bigqueryapi "cloud.google.com/go/bigquery" 22 | yaml "github.com/goccy/go-yaml" 23 | "github.com/googleapis/genai-toolbox/internal/sources" 24 | bigqueryds "github.com/googleapis/genai-toolbox/internal/sources/bigquery" 25 | "github.com/googleapis/genai-toolbox/internal/tools" 26 | bqutil "github.com/googleapis/genai-toolbox/internal/tools/bigquery/bigquerycommon" 27 | ) 28 | 29 | const kind string = "bigquery-get-table-info" 30 | const projectKey string = "project" 31 | const datasetKey string = "dataset" 32 | const tableKey string = "table" 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 compatibleSource interface { 49 | BigQueryProject() string 50 | BigQueryClient() *bigqueryapi.Client 51 | BigQueryClientCreator() bigqueryds.BigqueryClientCreator 52 | UseClientAuthorization() bool 53 | IsDatasetAllowed(projectID, datasetID string) bool 54 | BigQueryAllowedDatasets() []string 55 | } 56 | 57 | // validate compatible sources are still compatible 58 | var _ compatibleSource = &bigqueryds.Source{} 59 | 60 | var compatibleSources = [...]string{bigqueryds.SourceKind} 61 | 62 | type Config struct { 63 | Name string `yaml:"name" validate:"required"` 64 | Kind string `yaml:"kind" validate:"required"` 65 | Source string `yaml:"source" validate:"required"` 66 | Description string `yaml:"description" validate:"required"` 67 | AuthRequired []string `yaml:"authRequired"` 68 | } 69 | 70 | // validate interface 71 | var _ tools.ToolConfig = Config{} 72 | 73 | func (cfg Config) ToolConfigKind() string { 74 | return kind 75 | } 76 | 77 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { 78 | // verify source exists 79 | rawS, ok := srcs[cfg.Source] 80 | if !ok { 81 | return nil, fmt.Errorf("no source named %q configured", cfg.Source) 82 | } 83 | 84 | // verify the source is compatible 85 | s, ok := rawS.(compatibleSource) 86 | if !ok { 87 | return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources) 88 | } 89 | 90 | defaultProjectID := s.BigQueryProject() 91 | projectDescription := "The Google Cloud project ID containing the dataset and table." 92 | datasetDescription := "The table's parent dataset." 93 | var datasetParameter tools.Parameter 94 | var projectParameter tools.Parameter 95 | 96 | projectParameter, datasetParameter = bqutil.InitializeDatasetParameters( 97 | s.BigQueryAllowedDatasets(), 98 | defaultProjectID, 99 | projectKey, datasetKey, 100 | projectDescription, datasetDescription, 101 | ) 102 | 103 | tableParameter := tools.NewStringParameter(tableKey, "The table to get metadata information.") 104 | parameters := tools.Parameters{projectParameter, datasetParameter, tableParameter} 105 | 106 | mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters) 107 | 108 | // finish tool setup 109 | t := Tool{ 110 | Name: cfg.Name, 111 | Kind: kind, 112 | Parameters: parameters, 113 | AuthRequired: cfg.AuthRequired, 114 | UseClientOAuth: s.UseClientAuthorization(), 115 | ClientCreator: s.BigQueryClientCreator(), 116 | Client: s.BigQueryClient(), 117 | IsDatasetAllowed: s.IsDatasetAllowed, 118 | manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired}, 119 | mcpManifest: mcpManifest, 120 | } 121 | return t, nil 122 | } 123 | 124 | // validate interface 125 | var _ tools.Tool = Tool{} 126 | 127 | type Tool struct { 128 | Name string `yaml:"name"` 129 | Kind string `yaml:"kind"` 130 | AuthRequired []string `yaml:"authRequired"` 131 | UseClientOAuth bool `yaml:"useClientOAuth"` 132 | Parameters tools.Parameters `yaml:"parameters"` 133 | 134 | Client *bigqueryapi.Client 135 | ClientCreator bigqueryds.BigqueryClientCreator 136 | Statement string 137 | IsDatasetAllowed func(projectID, datasetID string) bool 138 | manifest tools.Manifest 139 | mcpManifest tools.McpManifest 140 | } 141 | 142 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { 143 | mapParams := params.AsMap() 144 | projectId, ok := mapParams[projectKey].(string) 145 | if !ok { 146 | return nil, fmt.Errorf("invalid or missing '%s' parameter; expected a string", projectKey) 147 | } 148 | 149 | datasetId, ok := mapParams[datasetKey].(string) 150 | if !ok { 151 | return nil, fmt.Errorf("invalid or missing '%s' parameter; expected a string", datasetKey) 152 | } 153 | 154 | tableId, ok := mapParams[tableKey].(string) 155 | if !ok { 156 | return nil, fmt.Errorf("invalid or missing '%s' parameter; expected a string", tableKey) 157 | } 158 | 159 | if !t.IsDatasetAllowed(projectId, datasetId) { 160 | return nil, fmt.Errorf("access denied to dataset '%s' because it is not in the configured list of allowed datasets for project '%s'", datasetId, projectId) 161 | } 162 | 163 | bqClient := t.Client 164 | 165 | var err error 166 | // Initialize new client if using user OAuth token 167 | if t.UseClientOAuth { 168 | tokenStr, err := accessToken.ParseBearerToken() 169 | if err != nil { 170 | return nil, fmt.Errorf("error parsing access token: %w", err) 171 | } 172 | bqClient, _, err = t.ClientCreator(tokenStr, false) 173 | if err != nil { 174 | return nil, fmt.Errorf("error creating client from OAuth access token: %w", err) 175 | } 176 | } 177 | 178 | dsHandle := bqClient.DatasetInProject(projectId, datasetId) 179 | tableHandle := dsHandle.Table(tableId) 180 | 181 | metadata, err := tableHandle.Metadata(ctx) 182 | if err != nil { 183 | return nil, fmt.Errorf("failed to get metadata for table %s.%s.%s: %w", projectId, datasetId, tableId, err) 184 | } 185 | 186 | return metadata, nil 187 | } 188 | 189 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { 190 | return tools.ParseParams(t.Parameters, data, claims) 191 | } 192 | 193 | func (t Tool) Manifest() tools.Manifest { 194 | return t.manifest 195 | } 196 | 197 | func (t Tool) McpManifest() tools.McpManifest { 198 | return t.mcpManifest 199 | } 200 | 201 | func (t Tool) Authorized(verifiedAuthServices []string) bool { 202 | return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices) 203 | } 204 | 205 | func (t Tool) RequiresClientAuthorization() bool { 206 | return t.UseClientOAuth 207 | } 208 | ``` -------------------------------------------------------------------------------- /internal/tools/alloydbainl/alloydbainl.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 alloydbainl 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "strings" 21 | 22 | yaml "github.com/goccy/go-yaml" 23 | "github.com/googleapis/genai-toolbox/internal/sources" 24 | "github.com/googleapis/genai-toolbox/internal/sources/alloydbpg" 25 | "github.com/googleapis/genai-toolbox/internal/tools" 26 | "github.com/jackc/pgx/v5/pgxpool" 27 | ) 28 | 29 | const kind string = "alloydb-ai-nl" 30 | 31 | func init() { 32 | if !tools.Register(kind, newConfig) { 33 | panic(fmt.Sprintf("tool kind %q already registered", kind)) 34 | } 35 | } 36 | 37 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) { 38 | actual := Config{Name: name} 39 | if err := decoder.DecodeContext(ctx, &actual); err != nil { 40 | return nil, err 41 | } 42 | return actual, nil 43 | } 44 | 45 | type compatibleSource interface { 46 | PostgresPool() *pgxpool.Pool 47 | } 48 | 49 | // validate compatible sources are still compatible 50 | var _ compatibleSource = &alloydbpg.Source{} 51 | 52 | var compatibleSources = [...]string{alloydbpg.SourceKind} 53 | 54 | type Config struct { 55 | Name string `yaml:"name" validate:"required"` 56 | Kind string `yaml:"kind" validate:"required"` 57 | Source string `yaml:"source" validate:"required"` 58 | Description string `yaml:"description" validate:"required"` 59 | NLConfig string `yaml:"nlConfig" validate:"required"` 60 | AuthRequired []string `yaml:"authRequired"` 61 | NLConfigParameters tools.Parameters `yaml:"nlConfigParameters"` 62 | } 63 | 64 | // validate interface 65 | var _ tools.ToolConfig = Config{} 66 | 67 | func (cfg Config) ToolConfigKind() string { 68 | return kind 69 | } 70 | 71 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { 72 | // verify source exists 73 | rawS, ok := srcs[cfg.Source] 74 | if !ok { 75 | return nil, fmt.Errorf("no source named %q configured", cfg.Source) 76 | } 77 | 78 | // verify the source is compatible 79 | s, ok := rawS.(compatibleSource) 80 | if !ok { 81 | return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources) 82 | } 83 | 84 | numParams := len(cfg.NLConfigParameters) 85 | quotedNameParts := make([]string, 0, numParams) 86 | placeholderParts := make([]string, 0, numParams) 87 | 88 | for i, paramDef := range cfg.NLConfigParameters { 89 | name := paramDef.GetName() 90 | escapedName := strings.ReplaceAll(name, "'", "''") // Escape for SQL literal 91 | quotedNameParts = append(quotedNameParts, fmt.Sprintf("'%s'", escapedName)) 92 | placeholderParts = append(placeholderParts, fmt.Sprintf("$%d", i+3)) // $1, $2 reserved 93 | } 94 | 95 | var paramNamesSQL string 96 | var paramValuesSQL string 97 | 98 | if numParams > 0 { 99 | paramNamesSQL = fmt.Sprintf("ARRAY[%s]", strings.Join(quotedNameParts, ", ")) 100 | paramValuesSQL = fmt.Sprintf("ARRAY[%s]", strings.Join(placeholderParts, ", ")) 101 | } else { 102 | paramNamesSQL = "ARRAY[]::TEXT[]" 103 | paramValuesSQL = "ARRAY[]::TEXT[]" 104 | } 105 | 106 | // execute_nl_query is the AlloyDB AI function that executes the natural language query 107 | // The first parameter is the natural language query, which is passed as $1 108 | // The second parameter is the NLConfig, which is passed as a $2 109 | // The following params are the list of PSV values passed to the NLConfig 110 | // Example SQL statement being executed: 111 | // SELECT alloydb_ai_nl.execute_nl_query('How many tickets do I have?', 'cymbal_air_nl_config', param_names => ARRAY ['user_email'], param_values => ARRAY ['[email protected]']); 112 | stmtFormat := "SELECT alloydb_ai_nl.execute_nl_query($1, $2, param_names => %s, param_values => %s);" 113 | stmt := fmt.Sprintf(stmtFormat, paramNamesSQL, paramValuesSQL) 114 | 115 | newQuestionParam := tools.NewStringParameter( 116 | "question", // name 117 | "The natural language question to ask.", // description 118 | ) 119 | 120 | cfg.NLConfigParameters = append([]tools.Parameter{newQuestionParam}, cfg.NLConfigParameters...) 121 | 122 | mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, cfg.NLConfigParameters) 123 | 124 | t := Tool{ 125 | Name: cfg.Name, 126 | Kind: kind, 127 | Parameters: cfg.NLConfigParameters, 128 | Statement: stmt, 129 | NLConfig: cfg.NLConfig, 130 | AuthRequired: cfg.AuthRequired, 131 | Pool: s.PostgresPool(), 132 | manifest: tools.Manifest{Description: cfg.Description, Parameters: cfg.NLConfigParameters.Manifest(), AuthRequired: cfg.AuthRequired}, 133 | mcpManifest: mcpManifest, 134 | } 135 | 136 | return t, nil 137 | } 138 | 139 | // validate interface 140 | var _ tools.Tool = Tool{} 141 | 142 | type Tool struct { 143 | Name string `yaml:"name"` 144 | Kind string `yaml:"kind"` 145 | AuthRequired []string `yaml:"authRequired"` 146 | Parameters tools.Parameters `yaml:"parameters"` 147 | 148 | Pool *pgxpool.Pool 149 | Statement string 150 | NLConfig string 151 | manifest tools.Manifest 152 | mcpManifest tools.McpManifest 153 | } 154 | 155 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { 156 | sliceParams := params.AsSlice() 157 | allParamValues := make([]any, len(sliceParams)+1) 158 | allParamValues[0] = fmt.Sprintf("%s", sliceParams[0]) // nl_question 159 | allParamValues[1] = t.NLConfig // nl_config 160 | for i, param := range sliceParams[1:] { 161 | allParamValues[i+2] = fmt.Sprintf("%s", param) 162 | } 163 | 164 | results, err := t.Pool.Query(ctx, t.Statement, allParamValues...) 165 | if err != nil { 166 | return nil, fmt.Errorf("unable to execute query: %w. Query: %v , Values: %v", err, t.Statement, allParamValues) 167 | } 168 | 169 | fields := results.FieldDescriptions() 170 | 171 | var out []any 172 | for results.Next() { 173 | v, err := results.Values() 174 | if err != nil { 175 | return nil, fmt.Errorf("unable to parse row: %w", err) 176 | } 177 | vMap := make(map[string]any) 178 | for i, f := range fields { 179 | vMap[f.Name] = v[i] 180 | } 181 | out = append(out, vMap) 182 | } 183 | 184 | return out, nil 185 | } 186 | 187 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { 188 | return tools.ParseParams(t.Parameters, data, claims) 189 | } 190 | 191 | func (t Tool) Manifest() tools.Manifest { 192 | return t.manifest 193 | } 194 | 195 | func (t Tool) McpManifest() tools.McpManifest { 196 | return t.mcpManifest 197 | } 198 | 199 | func (t Tool) Authorized(verifiedAuthServices []string) bool { 200 | return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices) 201 | } 202 | 203 | func (t Tool) RequiresClientAuthorization() bool { 204 | return false 205 | } 206 | ```