This is page 12 of 45. Use http://codebase.md/googleapis/genai-toolbox?lines=true&page={x} to view the full context. # Directory Structure ``` ├── .ci │ ├── continuous.release.cloudbuild.yaml │ ├── generate_release_table.sh │ ├── integration.cloudbuild.yaml │ ├── quickstart_test │ │ ├── go.integration.cloudbuild.yaml │ │ ├── js.integration.cloudbuild.yaml │ │ ├── py.integration.cloudbuild.yaml │ │ ├── run_go_tests.sh │ │ ├── run_js_tests.sh │ │ ├── run_py_tests.sh │ │ └── setup_hotels_sample.sql │ ├── test_with_coverage.sh │ └── versioned.release.cloudbuild.yaml ├── .github │ ├── auto-label.yaml │ ├── blunderbuss.yml │ ├── CODEOWNERS │ ├── header-checker-lint.yml │ ├── ISSUE_TEMPLATE │ │ ├── bug_report.yml │ │ ├── config.yml │ │ ├── feature_request.yml │ │ └── question.yml │ ├── label-sync.yml │ ├── labels.yaml │ ├── PULL_REQUEST_TEMPLATE.md │ ├── release-please.yml │ ├── renovate.json5 │ ├── sync-repo-settings.yaml │ └── workflows │ ├── cloud_build_failure_reporter.yml │ ├── deploy_dev_docs.yaml │ ├── deploy_previous_version_docs.yaml │ ├── deploy_versioned_docs.yaml │ ├── docs_deploy.yaml │ ├── docs_preview_clean.yaml │ ├── docs_preview_deploy.yaml │ ├── lint.yaml │ ├── schedule_reporter.yml │ ├── sync-labels.yaml │ └── tests.yaml ├── .gitignore ├── .gitmodules ├── .golangci.yaml ├── .hugo │ ├── archetypes │ │ └── default.md │ ├── assets │ │ ├── icons │ │ │ └── logo.svg │ │ └── scss │ │ ├── _styles_project.scss │ │ └── _variables_project.scss │ ├── go.mod │ ├── go.sum │ ├── hugo.toml │ ├── layouts │ │ ├── _default │ │ │ └── home.releases.releases │ │ ├── index.llms-full.txt │ │ ├── index.llms.txt │ │ ├── partials │ │ │ ├── hooks │ │ │ │ └── head-end.html │ │ │ ├── navbar-version-selector.html │ │ │ ├── page-meta-links.html │ │ │ └── td │ │ │ └── render-heading.html │ │ ├── robot.txt │ │ └── shortcodes │ │ ├── include.html │ │ ├── ipynb.html │ │ └── regionInclude.html │ ├── package-lock.json │ ├── package.json │ └── static │ ├── favicons │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ └── favicon.ico │ └── js │ └── w3.js ├── CHANGELOG.md ├── cmd │ ├── options_test.go │ ├── options.go │ ├── root_test.go │ ├── root.go │ └── version.txt ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── DEVELOPER.md ├── Dockerfile ├── docs │ └── en │ ├── _index.md │ ├── about │ │ ├── _index.md │ │ └── faq.md │ ├── concepts │ │ ├── _index.md │ │ └── telemetry │ │ ├── index.md │ │ ├── telemetry_flow.png │ │ └── telemetry_traces.png │ ├── getting-started │ │ ├── _index.md │ │ ├── colab_quickstart.ipynb │ │ ├── configure.md │ │ ├── introduction │ │ │ ├── _index.md │ │ │ └── architecture.png │ │ ├── local_quickstart_go.md │ │ ├── local_quickstart_js.md │ │ ├── local_quickstart.md │ │ ├── mcp_quickstart │ │ │ ├── _index.md │ │ │ ├── inspector_tools.png │ │ │ └── inspector.png │ │ └── quickstart │ │ ├── go │ │ │ ├── genAI │ │ │ │ ├── go.mod │ │ │ │ ├── go.sum │ │ │ │ └── quickstart.go │ │ │ ├── genkit │ │ │ │ ├── go.mod │ │ │ │ ├── go.sum │ │ │ │ └── quickstart.go │ │ │ ├── langchain │ │ │ │ ├── go.mod │ │ │ │ ├── go.sum │ │ │ │ └── quickstart.go │ │ │ ├── openAI │ │ │ │ ├── go.mod │ │ │ │ ├── go.sum │ │ │ │ └── quickstart.go │ │ │ └── quickstart_test.go │ │ ├── golden.txt │ │ ├── js │ │ │ ├── genAI │ │ │ │ ├── package-lock.json │ │ │ │ ├── package.json │ │ │ │ └── quickstart.js │ │ │ ├── genkit │ │ │ │ ├── package-lock.json │ │ │ │ ├── package.json │ │ │ │ └── quickstart.js │ │ │ ├── langchain │ │ │ │ ├── package-lock.json │ │ │ │ ├── package.json │ │ │ │ └── quickstart.js │ │ │ ├── llamaindex │ │ │ │ ├── package-lock.json │ │ │ │ ├── package.json │ │ │ │ └── quickstart.js │ │ │ └── quickstart.test.js │ │ ├── python │ │ │ ├── __init__.py │ │ │ ├── adk │ │ │ │ ├── quickstart.py │ │ │ │ └── requirements.txt │ │ │ ├── core │ │ │ │ ├── quickstart.py │ │ │ │ └── requirements.txt │ │ │ ├── langchain │ │ │ │ ├── quickstart.py │ │ │ │ └── requirements.txt │ │ │ ├── llamaindex │ │ │ │ ├── quickstart.py │ │ │ │ └── requirements.txt │ │ │ └── quickstart_test.py │ │ └── shared │ │ ├── cloud_setup.md │ │ ├── configure_toolbox.md │ │ └── database_setup.md │ ├── how-to │ │ ├── _index.md │ │ ├── connect_via_geminicli.md │ │ ├── connect_via_mcp.md │ │ ├── connect-ide │ │ │ ├── _index.md │ │ │ ├── alloydb_pg_admin_mcp.md │ │ │ ├── alloydb_pg_mcp.md │ │ │ ├── bigquery_mcp.md │ │ │ ├── cloud_sql_mssql_admin_mcp.md │ │ │ ├── cloud_sql_mssql_mcp.md │ │ │ ├── cloud_sql_mysql_admin_mcp.md │ │ │ ├── cloud_sql_mysql_mcp.md │ │ │ ├── cloud_sql_pg_admin_mcp.md │ │ │ ├── cloud_sql_pg_mcp.md │ │ │ ├── firestore_mcp.md │ │ │ ├── looker_mcp.md │ │ │ ├── mssql_mcp.md │ │ │ ├── mysql_mcp.md │ │ │ ├── neo4j_mcp.md │ │ │ ├── postgres_mcp.md │ │ │ ├── spanner_mcp.md │ │ │ └── sqlite_mcp.md │ │ ├── deploy_docker.md │ │ ├── deploy_gke.md │ │ ├── deploy_toolbox.md │ │ ├── export_telemetry.md │ │ └── toolbox-ui │ │ ├── edit-headers.gif │ │ ├── edit-headers.png │ │ ├── index.md │ │ ├── optional-param-checked.png │ │ ├── optional-param-unchecked.png │ │ ├── run-tool.gif │ │ ├── tools.png │ │ └── toolsets.png │ ├── reference │ │ ├── _index.md │ │ ├── cli.md │ │ └── prebuilt-tools.md │ ├── resources │ │ ├── _index.md │ │ ├── authServices │ │ │ ├── _index.md │ │ │ └── google.md │ │ ├── sources │ │ │ ├── _index.md │ │ │ ├── alloydb-admin.md │ │ │ ├── alloydb-pg.md │ │ │ ├── bigquery.md │ │ │ ├── bigtable.md │ │ │ ├── cassandra.md │ │ │ ├── clickhouse.md │ │ │ ├── cloud-monitoring.md │ │ │ ├── cloud-sql-admin.md │ │ │ ├── cloud-sql-mssql.md │ │ │ ├── cloud-sql-mysql.md │ │ │ ├── cloud-sql-pg.md │ │ │ ├── couchbase.md │ │ │ ├── dataplex.md │ │ │ ├── dgraph.md │ │ │ ├── firebird.md │ │ │ ├── firestore.md │ │ │ ├── http.md │ │ │ ├── looker.md │ │ │ ├── mongodb.md │ │ │ ├── mssql.md │ │ │ ├── mysql.md │ │ │ ├── neo4j.md │ │ │ ├── oceanbase.md │ │ │ ├── oracle.md │ │ │ ├── postgres.md │ │ │ ├── redis.md │ │ │ ├── spanner.md │ │ │ ├── sqlite.md │ │ │ ├── tidb.md │ │ │ ├── trino.md │ │ │ ├── valkey.md │ │ │ └── yugabytedb.md │ │ └── tools │ │ ├── _index.md │ │ ├── alloydb │ │ │ ├── _index.md │ │ │ ├── alloydb-create-cluster.md │ │ │ ├── alloydb-create-instance.md │ │ │ ├── alloydb-create-user.md │ │ │ ├── alloydb-get-cluster.md │ │ │ ├── alloydb-get-instance.md │ │ │ ├── alloydb-get-user.md │ │ │ ├── alloydb-list-clusters.md │ │ │ ├── alloydb-list-instances.md │ │ │ ├── alloydb-list-users.md │ │ │ └── alloydb-wait-for-operation.md │ │ ├── alloydbainl │ │ │ ├── _index.md │ │ │ └── alloydb-ai-nl.md │ │ ├── bigquery │ │ │ ├── _index.md │ │ │ ├── bigquery-analyze-contribution.md │ │ │ ├── bigquery-conversational-analytics.md │ │ │ ├── bigquery-execute-sql.md │ │ │ ├── bigquery-forecast.md │ │ │ ├── bigquery-get-dataset-info.md │ │ │ ├── bigquery-get-table-info.md │ │ │ ├── bigquery-list-dataset-ids.md │ │ │ ├── bigquery-list-table-ids.md │ │ │ ├── bigquery-search-catalog.md │ │ │ └── bigquery-sql.md │ │ ├── bigtable │ │ │ ├── _index.md │ │ │ └── bigtable-sql.md │ │ ├── cassandra │ │ │ ├── _index.md │ │ │ └── cassandra-cql.md │ │ ├── clickhouse │ │ │ ├── _index.md │ │ │ ├── clickhouse-execute-sql.md │ │ │ ├── clickhouse-list-databases.md │ │ │ ├── clickhouse-list-tables.md │ │ │ └── clickhouse-sql.md │ │ ├── cloudmonitoring │ │ │ ├── _index.md │ │ │ └── cloud-monitoring-query-prometheus.md │ │ ├── cloudsql │ │ │ ├── _index.md │ │ │ ├── cloudsqlcreatedatabase.md │ │ │ ├── cloudsqlcreateusers.md │ │ │ ├── cloudsqlgetinstances.md │ │ │ ├── cloudsqllistdatabases.md │ │ │ ├── cloudsqllistinstances.md │ │ │ ├── cloudsqlmssqlcreateinstance.md │ │ │ ├── cloudsqlmysqlcreateinstance.md │ │ │ ├── cloudsqlpgcreateinstances.md │ │ │ └── cloudsqlwaitforoperation.md │ │ ├── couchbase │ │ │ ├── _index.md │ │ │ └── couchbase-sql.md │ │ ├── dataform │ │ │ ├── _index.md │ │ │ └── dataform-compile-local.md │ │ ├── dataplex │ │ │ ├── _index.md │ │ │ ├── dataplex-lookup-entry.md │ │ │ ├── dataplex-search-aspect-types.md │ │ │ └── dataplex-search-entries.md │ │ ├── dgraph │ │ │ ├── _index.md │ │ │ └── dgraph-dql.md │ │ ├── firebird │ │ │ ├── _index.md │ │ │ ├── firebird-execute-sql.md │ │ │ └── firebird-sql.md │ │ ├── firestore │ │ │ ├── _index.md │ │ │ ├── firestore-add-documents.md │ │ │ ├── firestore-delete-documents.md │ │ │ ├── firestore-get-documents.md │ │ │ ├── firestore-get-rules.md │ │ │ ├── firestore-list-collections.md │ │ │ ├── firestore-query-collection.md │ │ │ ├── firestore-query.md │ │ │ ├── firestore-update-document.md │ │ │ └── firestore-validate-rules.md │ │ ├── http │ │ │ ├── _index.md │ │ │ └── http.md │ │ ├── looker │ │ │ ├── _index.md │ │ │ ├── looker-add-dashboard-element.md │ │ │ ├── looker-conversational-analytics.md │ │ │ ├── looker-get-dashboards.md │ │ │ ├── looker-get-dimensions.md │ │ │ ├── looker-get-explores.md │ │ │ ├── looker-get-filters.md │ │ │ ├── looker-get-looks.md │ │ │ ├── looker-get-measures.md │ │ │ ├── looker-get-models.md │ │ │ ├── looker-get-parameters.md │ │ │ ├── looker-health-analyze.md │ │ │ ├── looker-health-pulse.md │ │ │ ├── looker-health-vacuum.md │ │ │ ├── looker-make-dashboard.md │ │ │ ├── looker-make-look.md │ │ │ ├── looker-query-sql.md │ │ │ ├── looker-query-url.md │ │ │ ├── looker-query.md │ │ │ └── looker-run-look.md │ │ ├── mongodb │ │ │ ├── _index.md │ │ │ ├── mongodb-aggregate.md │ │ │ ├── mongodb-delete-many.md │ │ │ ├── mongodb-delete-one.md │ │ │ ├── mongodb-find-one.md │ │ │ ├── mongodb-find.md │ │ │ ├── mongodb-insert-many.md │ │ │ ├── mongodb-insert-one.md │ │ │ ├── mongodb-update-many.md │ │ │ └── mongodb-update-one.md │ │ ├── mssql │ │ │ ├── _index.md │ │ │ ├── mssql-execute-sql.md │ │ │ ├── mssql-list-tables.md │ │ │ └── mssql-sql.md │ │ ├── mysql │ │ │ ├── _index.md │ │ │ ├── mysql-execute-sql.md │ │ │ ├── mysql-list-active-queries.md │ │ │ ├── mysql-list-table-fragmentation.md │ │ │ ├── mysql-list-tables-missing-unique-indexes.md │ │ │ ├── mysql-list-tables.md │ │ │ └── mysql-sql.md │ │ ├── neo4j │ │ │ ├── _index.md │ │ │ ├── neo4j-cypher.md │ │ │ ├── neo4j-execute-cypher.md │ │ │ └── neo4j-schema.md │ │ ├── oceanbase │ │ │ ├── _index.md │ │ │ ├── oceanbase-execute-sql.md │ │ │ └── oceanbase-sql.md │ │ ├── oracle │ │ │ ├── _index.md │ │ │ ├── oracle-execute-sql.md │ │ │ └── oracle-sql.md │ │ ├── postgres │ │ │ ├── _index.md │ │ │ ├── postgres-execute-sql.md │ │ │ ├── postgres-list-active-queries.md │ │ │ ├── postgres-list-available-extensions.md │ │ │ ├── postgres-list-installed-extensions.md │ │ │ ├── postgres-list-tables.md │ │ │ └── postgres-sql.md │ │ ├── redis │ │ │ ├── _index.md │ │ │ └── redis.md │ │ ├── spanner │ │ │ ├── _index.md │ │ │ ├── spanner-execute-sql.md │ │ │ ├── spanner-list-tables.md │ │ │ └── spanner-sql.md │ │ ├── sqlite │ │ │ ├── _index.md │ │ │ ├── sqlite-execute-sql.md │ │ │ └── sqlite-sql.md │ │ ├── tidb │ │ │ ├── _index.md │ │ │ ├── tidb-execute-sql.md │ │ │ └── tidb-sql.md │ │ ├── trino │ │ │ ├── _index.md │ │ │ ├── trino-execute-sql.md │ │ │ └── trino-sql.md │ │ ├── utility │ │ │ ├── _index.md │ │ │ └── wait.md │ │ ├── valkey │ │ │ ├── _index.md │ │ │ └── valkey.md │ │ └── yuagbytedb │ │ ├── _index.md │ │ └── yugabytedb-sql.md │ ├── samples │ │ ├── _index.md │ │ ├── alloydb │ │ │ ├── _index.md │ │ │ ├── ai-nl │ │ │ │ ├── alloydb_ai_nl.ipynb │ │ │ │ └── index.md │ │ │ └── mcp_quickstart.md │ │ ├── bigquery │ │ │ ├── _index.md │ │ │ ├── colab_quickstart_bigquery.ipynb │ │ │ ├── local_quickstart.md │ │ │ └── mcp_quickstart │ │ │ ├── _index.md │ │ │ ├── inspector_tools.png │ │ │ └── inspector.png │ │ └── looker │ │ ├── _index.md │ │ ├── looker_gemini_oauth │ │ │ ├── _index.md │ │ │ ├── authenticated.png │ │ │ ├── authorize.png │ │ │ └── registration.png │ │ ├── looker_gemini.md │ │ └── looker_mcp_inspector │ │ ├── _index.md │ │ ├── inspector_tools.png │ │ └── inspector.png │ └── sdks │ ├── _index.md │ ├── go-sdk.md │ ├── js-sdk.md │ └── python-sdk.md ├── gemini-extension.json ├── go.mod ├── go.sum ├── internal │ ├── auth │ │ ├── auth.go │ │ └── google │ │ └── google.go │ ├── log │ │ ├── handler.go │ │ ├── log_test.go │ │ ├── log.go │ │ └── logger.go │ ├── prebuiltconfigs │ │ ├── prebuiltconfigs_test.go │ │ ├── prebuiltconfigs.go │ │ └── tools │ │ ├── alloydb-postgres-admin.yaml │ │ ├── alloydb-postgres-observability.yaml │ │ ├── alloydb-postgres.yaml │ │ ├── bigquery.yaml │ │ ├── clickhouse.yaml │ │ ├── cloud-sql-mssql-admin.yaml │ │ ├── cloud-sql-mssql-observability.yaml │ │ ├── cloud-sql-mssql.yaml │ │ ├── cloud-sql-mysql-admin.yaml │ │ ├── cloud-sql-mysql-observability.yaml │ │ ├── cloud-sql-mysql.yaml │ │ ├── cloud-sql-postgres-admin.yaml │ │ ├── cloud-sql-postgres-observability.yaml │ │ ├── cloud-sql-postgres.yaml │ │ ├── dataplex.yaml │ │ ├── firestore.yaml │ │ ├── looker-conversational-analytics.yaml │ │ ├── looker.yaml │ │ ├── mssql.yaml │ │ ├── mysql.yaml │ │ ├── neo4j.yaml │ │ ├── oceanbase.yaml │ │ ├── postgres.yaml │ │ ├── spanner-postgres.yaml │ │ ├── spanner.yaml │ │ └── sqlite.yaml │ ├── server │ │ ├── api_test.go │ │ ├── api.go │ │ ├── common_test.go │ │ ├── config.go │ │ ├── mcp │ │ │ ├── jsonrpc │ │ │ │ ├── jsonrpc_test.go │ │ │ │ └── jsonrpc.go │ │ │ ├── mcp.go │ │ │ ├── util │ │ │ │ └── lifecycle.go │ │ │ ├── v20241105 │ │ │ │ ├── method.go │ │ │ │ └── types.go │ │ │ ├── v20250326 │ │ │ │ ├── method.go │ │ │ │ └── types.go │ │ │ └── v20250618 │ │ │ ├── method.go │ │ │ └── types.go │ │ ├── mcp_test.go │ │ ├── mcp.go │ │ ├── server_test.go │ │ ├── server.go │ │ ├── static │ │ │ ├── assets │ │ │ │ └── mcptoolboxlogo.png │ │ │ ├── css │ │ │ │ └── style.css │ │ │ ├── index.html │ │ │ ├── js │ │ │ │ ├── auth.js │ │ │ │ ├── loadTools.js │ │ │ │ ├── mainContent.js │ │ │ │ ├── navbar.js │ │ │ │ ├── runTool.js │ │ │ │ ├── toolDisplay.js │ │ │ │ ├── tools.js │ │ │ │ └── toolsets.js │ │ │ ├── tools.html │ │ │ └── toolsets.html │ │ ├── web_test.go │ │ └── web.go │ ├── sources │ │ ├── alloydbadmin │ │ │ ├── alloydbadmin_test.go │ │ │ └── alloydbadmin.go │ │ ├── alloydbpg │ │ │ ├── alloydb_pg_test.go │ │ │ └── alloydb_pg.go │ │ ├── bigquery │ │ │ ├── bigquery_test.go │ │ │ └── bigquery.go │ │ ├── bigtable │ │ │ ├── bigtable_test.go │ │ │ └── bigtable.go │ │ ├── cassandra │ │ │ ├── cassandra_test.go │ │ │ └── cassandra.go │ │ ├── clickhouse │ │ │ ├── clickhouse_test.go │ │ │ └── clickhouse.go │ │ ├── cloudmonitoring │ │ │ ├── cloud_monitoring_test.go │ │ │ └── cloud_monitoring.go │ │ ├── cloudsqladmin │ │ │ ├── cloud_sql_admin_test.go │ │ │ └── cloud_sql_admin.go │ │ ├── cloudsqlmssql │ │ │ ├── cloud_sql_mssql_test.go │ │ │ └── cloud_sql_mssql.go │ │ ├── cloudsqlmysql │ │ │ ├── cloud_sql_mysql_test.go │ │ │ └── cloud_sql_mysql.go │ │ ├── cloudsqlpg │ │ │ ├── cloud_sql_pg_test.go │ │ │ └── cloud_sql_pg.go │ │ ├── couchbase │ │ │ ├── couchbase_test.go │ │ │ └── couchbase.go │ │ ├── dataplex │ │ │ ├── dataplex_test.go │ │ │ └── dataplex.go │ │ ├── dgraph │ │ │ ├── dgraph_test.go │ │ │ └── dgraph.go │ │ ├── dialect.go │ │ ├── firebird │ │ │ ├── firebird_test.go │ │ │ └── firebird.go │ │ ├── firestore │ │ │ ├── firestore_test.go │ │ │ └── firestore.go │ │ ├── http │ │ │ ├── http_test.go │ │ │ └── http.go │ │ ├── ip_type.go │ │ ├── looker │ │ │ ├── looker_test.go │ │ │ └── looker.go │ │ ├── mongodb │ │ │ ├── mongodb_test.go │ │ │ └── mongodb.go │ │ ├── mssql │ │ │ ├── mssql_test.go │ │ │ └── mssql.go │ │ ├── mysql │ │ │ ├── mysql_test.go │ │ │ └── mysql.go │ │ ├── neo4j │ │ │ ├── neo4j_test.go │ │ │ └── neo4j.go │ │ ├── oceanbase │ │ │ ├── oceanbase_test.go │ │ │ └── oceanbase.go │ │ ├── oracle │ │ │ └── oracle.go │ │ ├── postgres │ │ │ ├── postgres_test.go │ │ │ └── postgres.go │ │ ├── redis │ │ │ ├── redis_test.go │ │ │ └── redis.go │ │ ├── sources.go │ │ ├── spanner │ │ │ ├── spanner_test.go │ │ │ └── spanner.go │ │ ├── sqlite │ │ │ ├── sqlite_test.go │ │ │ └── sqlite.go │ │ ├── tidb │ │ │ ├── tidb_test.go │ │ │ └── tidb.go │ │ ├── trino │ │ │ ├── trino_test.go │ │ │ └── trino.go │ │ ├── util.go │ │ ├── valkey │ │ │ ├── valkey_test.go │ │ │ └── valkey.go │ │ └── yugabytedb │ │ ├── yugabytedb_test.go │ │ └── yugabytedb.go │ ├── telemetry │ │ ├── instrumentation.go │ │ └── telemetry.go │ ├── testutils │ │ └── testutils.go │ ├── tools │ │ ├── alloydb │ │ │ ├── alloydbcreatecluster │ │ │ │ ├── alloydbcreatecluster_test.go │ │ │ │ └── alloydbcreatecluster.go │ │ │ ├── alloydbcreateinstance │ │ │ │ ├── alloydbcreateinstance_test.go │ │ │ │ └── alloydbcreateinstance.go │ │ │ ├── alloydbcreateuser │ │ │ │ ├── alloydbcreateuser_test.go │ │ │ │ └── alloydbcreateuser.go │ │ │ ├── alloydbgetcluster │ │ │ │ ├── alloydbgetcluster_test.go │ │ │ │ └── alloydbgetcluster.go │ │ │ ├── alloydbgetinstance │ │ │ │ ├── alloydbgetinstance_test.go │ │ │ │ └── alloydbgetinstance.go │ │ │ ├── alloydbgetuser │ │ │ │ ├── alloydbgetuser_test.go │ │ │ │ └── alloydbgetuser.go │ │ │ ├── alloydblistclusters │ │ │ │ ├── alloydblistclusters_test.go │ │ │ │ └── alloydblistclusters.go │ │ │ ├── alloydblistinstances │ │ │ │ ├── alloydblistinstances_test.go │ │ │ │ └── alloydblistinstances.go │ │ │ ├── alloydblistusers │ │ │ │ ├── alloydblistusers_test.go │ │ │ │ └── alloydblistusers.go │ │ │ └── alloydbwaitforoperation │ │ │ ├── alloydbwaitforoperation_test.go │ │ │ └── alloydbwaitforoperation.go │ │ ├── alloydbainl │ │ │ ├── alloydbainl_test.go │ │ │ └── alloydbainl.go │ │ ├── bigquery │ │ │ ├── bigqueryanalyzecontribution │ │ │ │ ├── bigqueryanalyzecontribution_test.go │ │ │ │ └── bigqueryanalyzecontribution.go │ │ │ ├── bigquerycommon │ │ │ │ ├── table_name_parser_test.go │ │ │ │ ├── table_name_parser.go │ │ │ │ └── util.go │ │ │ ├── bigqueryconversationalanalytics │ │ │ │ ├── bigqueryconversationalanalytics_test.go │ │ │ │ └── bigqueryconversationalanalytics.go │ │ │ ├── bigqueryexecutesql │ │ │ │ ├── bigqueryexecutesql_test.go │ │ │ │ └── bigqueryexecutesql.go │ │ │ ├── bigqueryforecast │ │ │ │ ├── bigqueryforecast_test.go │ │ │ │ └── bigqueryforecast.go │ │ │ ├── bigquerygetdatasetinfo │ │ │ │ ├── bigquerygetdatasetinfo_test.go │ │ │ │ └── bigquerygetdatasetinfo.go │ │ │ ├── bigquerygettableinfo │ │ │ │ ├── bigquerygettableinfo_test.go │ │ │ │ └── bigquerygettableinfo.go │ │ │ ├── bigquerylistdatasetids │ │ │ │ ├── bigquerylistdatasetids_test.go │ │ │ │ └── bigquerylistdatasetids.go │ │ │ ├── bigquerylisttableids │ │ │ │ ├── bigquerylisttableids_test.go │ │ │ │ └── bigquerylisttableids.go │ │ │ ├── bigquerysearchcatalog │ │ │ │ ├── bigquerysearchcatalog_test.go │ │ │ │ └── bigquerysearchcatalog.go │ │ │ └── bigquerysql │ │ │ ├── bigquerysql_test.go │ │ │ └── bigquerysql.go │ │ ├── bigtable │ │ │ ├── bigtable_test.go │ │ │ └── bigtable.go │ │ ├── cassandra │ │ │ └── cassandracql │ │ │ ├── cassandracql_test.go │ │ │ └── cassandracql.go │ │ ├── clickhouse │ │ │ ├── clickhouseexecutesql │ │ │ │ ├── clickhouseexecutesql_test.go │ │ │ │ └── clickhouseexecutesql.go │ │ │ ├── clickhouselistdatabases │ │ │ │ ├── clickhouselistdatabases_test.go │ │ │ │ └── clickhouselistdatabases.go │ │ │ ├── clickhouselisttables │ │ │ │ ├── clickhouselisttables_test.go │ │ │ │ └── clickhouselisttables.go │ │ │ └── clickhousesql │ │ │ ├── clickhousesql_test.go │ │ │ └── clickhousesql.go │ │ ├── cloudmonitoring │ │ │ ├── cloudmonitoring_test.go │ │ │ └── cloudmonitoring.go │ │ ├── cloudsql │ │ │ ├── cloudsqlcreatedatabase │ │ │ │ ├── cloudsqlcreatedatabase_test.go │ │ │ │ └── cloudsqlcreatedatabase.go │ │ │ ├── cloudsqlcreateusers │ │ │ │ ├── cloudsqlcreateusers_test.go │ │ │ │ └── cloudsqlcreateusers.go │ │ │ ├── cloudsqlgetinstances │ │ │ │ ├── cloudsqlgetinstances_test.go │ │ │ │ └── cloudsqlgetinstances.go │ │ │ ├── cloudsqllistdatabases │ │ │ │ ├── cloudsqllistdatabases_test.go │ │ │ │ └── cloudsqllistdatabases.go │ │ │ ├── cloudsqllistinstances │ │ │ │ ├── cloudsqllistinstances_test.go │ │ │ │ └── cloudsqllistinstances.go │ │ │ └── cloudsqlwaitforoperation │ │ │ ├── cloudsqlwaitforoperation_test.go │ │ │ └── cloudsqlwaitforoperation.go │ │ ├── cloudsqlmssql │ │ │ └── cloudsqlmssqlcreateinstance │ │ │ ├── cloudsqlmssqlcreateinstance_test.go │ │ │ └── cloudsqlmssqlcreateinstance.go │ │ ├── cloudsqlmysql │ │ │ └── cloudsqlmysqlcreateinstance │ │ │ ├── cloudsqlmysqlcreateinstance_test.go │ │ │ └── cloudsqlmysqlcreateinstance.go │ │ ├── cloudsqlpg │ │ │ └── cloudsqlpgcreateinstances │ │ │ ├── cloudsqlpgcreateinstances_test.go │ │ │ └── cloudsqlpgcreateinstances.go │ │ ├── common_test.go │ │ ├── common.go │ │ ├── couchbase │ │ │ ├── couchbase_test.go │ │ │ └── couchbase.go │ │ ├── dataform │ │ │ └── dataformcompilelocal │ │ │ ├── dataformcompilelocal_test.go │ │ │ └── dataformcompilelocal.go │ │ ├── dataplex │ │ │ ├── dataplexlookupentry │ │ │ │ ├── dataplexlookupentry_test.go │ │ │ │ └── dataplexlookupentry.go │ │ │ ├── dataplexsearchaspecttypes │ │ │ │ ├── dataplexsearchaspecttypes_test.go │ │ │ │ └── dataplexsearchaspecttypes.go │ │ │ └── dataplexsearchentries │ │ │ ├── dataplexsearchentries_test.go │ │ │ └── dataplexsearchentries.go │ │ ├── dgraph │ │ │ ├── dgraph_test.go │ │ │ └── dgraph.go │ │ ├── firebird │ │ │ ├── firebirdexecutesql │ │ │ │ ├── firebirdexecutesql_test.go │ │ │ │ └── firebirdexecutesql.go │ │ │ └── firebirdsql │ │ │ ├── firebirdsql_test.go │ │ │ └── firebirdsql.go │ │ ├── firestore │ │ │ ├── firestoreadddocuments │ │ │ │ ├── firestoreadddocuments_test.go │ │ │ │ └── firestoreadddocuments.go │ │ │ ├── firestoredeletedocuments │ │ │ │ ├── firestoredeletedocuments_test.go │ │ │ │ └── firestoredeletedocuments.go │ │ │ ├── firestoregetdocuments │ │ │ │ ├── firestoregetdocuments_test.go │ │ │ │ └── firestoregetdocuments.go │ │ │ ├── firestoregetrules │ │ │ │ ├── firestoregetrules_test.go │ │ │ │ └── firestoregetrules.go │ │ │ ├── firestorelistcollections │ │ │ │ ├── firestorelistcollections_test.go │ │ │ │ └── firestorelistcollections.go │ │ │ ├── firestorequery │ │ │ │ ├── firestorequery_test.go │ │ │ │ └── firestorequery.go │ │ │ ├── firestorequerycollection │ │ │ │ ├── firestorequerycollection_test.go │ │ │ │ └── firestorequerycollection.go │ │ │ ├── firestoreupdatedocument │ │ │ │ ├── firestoreupdatedocument_test.go │ │ │ │ └── firestoreupdatedocument.go │ │ │ ├── firestorevalidaterules │ │ │ │ ├── firestorevalidaterules_test.go │ │ │ │ └── firestorevalidaterules.go │ │ │ └── util │ │ │ ├── converter_test.go │ │ │ ├── converter.go │ │ │ ├── validator_test.go │ │ │ └── validator.go │ │ ├── http │ │ │ ├── http_test.go │ │ │ └── http.go │ │ ├── http_method.go │ │ ├── looker │ │ │ ├── lookeradddashboardelement │ │ │ │ ├── lookeradddashboardelement_test.go │ │ │ │ └── lookeradddashboardelement.go │ │ │ ├── lookercommon │ │ │ │ ├── lookercommon_test.go │ │ │ │ └── lookercommon.go │ │ │ ├── lookerconversationalanalytics │ │ │ │ ├── lookerconversationalanalytics_test.go │ │ │ │ └── lookerconversationalanalytics.go │ │ │ ├── lookergetdashboards │ │ │ │ ├── lookergetdashboards_test.go │ │ │ │ └── lookergetdashboards.go │ │ │ ├── lookergetdimensions │ │ │ │ ├── lookergetdimensions_test.go │ │ │ │ └── lookergetdimensions.go │ │ │ ├── lookergetexplores │ │ │ │ ├── lookergetexplores_test.go │ │ │ │ └── lookergetexplores.go │ │ │ ├── lookergetfilters │ │ │ │ ├── lookergetfilters_test.go │ │ │ │ └── lookergetfilters.go │ │ │ ├── lookergetlooks │ │ │ │ ├── lookergetlooks_test.go │ │ │ │ └── lookergetlooks.go │ │ │ ├── lookergetmeasures │ │ │ │ ├── lookergetmeasures_test.go │ │ │ │ └── lookergetmeasures.go │ │ │ ├── lookergetmodels │ │ │ │ ├── lookergetmodels_test.go │ │ │ │ └── lookergetmodels.go │ │ │ ├── lookergetparameters │ │ │ │ ├── lookergetparameters_test.go │ │ │ │ └── lookergetparameters.go │ │ │ ├── lookerhealthanalyze │ │ │ │ ├── lookerhealthanalyze_test.go │ │ │ │ └── lookerhealthanalyze.go │ │ │ ├── lookerhealthpulse │ │ │ │ ├── lookerhealthpulse_test.go │ │ │ │ └── lookerhealthpulse.go │ │ │ ├── lookerhealthvacuum │ │ │ │ ├── lookerhealthvacuum_test.go │ │ │ │ └── lookerhealthvacuum.go │ │ │ ├── lookermakedashboard │ │ │ │ ├── lookermakedashboard_test.go │ │ │ │ └── lookermakedashboard.go │ │ │ ├── lookermakelook │ │ │ │ ├── lookermakelook_test.go │ │ │ │ └── lookermakelook.go │ │ │ ├── lookerquery │ │ │ │ ├── lookerquery_test.go │ │ │ │ └── lookerquery.go │ │ │ ├── lookerquerysql │ │ │ │ ├── lookerquerysql_test.go │ │ │ │ └── lookerquerysql.go │ │ │ ├── lookerqueryurl │ │ │ │ ├── lookerqueryurl_test.go │ │ │ │ └── lookerqueryurl.go │ │ │ └── lookerrunlook │ │ │ ├── lookerrunlook_test.go │ │ │ └── lookerrunlook.go │ │ ├── mongodb │ │ │ ├── mongodbaggregate │ │ │ │ ├── mongodbaggregate_test.go │ │ │ │ └── mongodbaggregate.go │ │ │ ├── mongodbdeletemany │ │ │ │ ├── mongodbdeletemany_test.go │ │ │ │ └── mongodbdeletemany.go │ │ │ ├── mongodbdeleteone │ │ │ │ ├── mongodbdeleteone_test.go │ │ │ │ └── mongodbdeleteone.go │ │ │ ├── mongodbfind │ │ │ │ ├── mongodbfind_test.go │ │ │ │ └── mongodbfind.go │ │ │ ├── mongodbfindone │ │ │ │ ├── mongodbfindone_test.go │ │ │ │ └── mongodbfindone.go │ │ │ ├── mongodbinsertmany │ │ │ │ ├── mongodbinsertmany_test.go │ │ │ │ └── mongodbinsertmany.go │ │ │ ├── mongodbinsertone │ │ │ │ ├── mongodbinsertone_test.go │ │ │ │ └── mongodbinsertone.go │ │ │ ├── mongodbupdatemany │ │ │ │ ├── mongodbupdatemany_test.go │ │ │ │ └── mongodbupdatemany.go │ │ │ └── mongodbupdateone │ │ │ ├── mongodbupdateone_test.go │ │ │ └── mongodbupdateone.go │ │ ├── mssql │ │ │ ├── mssqlexecutesql │ │ │ │ ├── mssqlexecutesql_test.go │ │ │ │ └── mssqlexecutesql.go │ │ │ ├── mssqllisttables │ │ │ │ ├── mssqllisttables_test.go │ │ │ │ └── mssqllisttables.go │ │ │ └── mssqlsql │ │ │ ├── mssqlsql_test.go │ │ │ └── mssqlsql.go │ │ ├── mysql │ │ │ ├── mysqlcommon │ │ │ │ └── mysqlcommon.go │ │ │ ├── mysqlexecutesql │ │ │ │ ├── mysqlexecutesql_test.go │ │ │ │ └── mysqlexecutesql.go │ │ │ ├── mysqllistactivequeries │ │ │ │ ├── mysqllistactivequeries_test.go │ │ │ │ └── mysqllistactivequeries.go │ │ │ ├── mysqllisttablefragmentation │ │ │ │ ├── mysqllisttablefragmentation_test.go │ │ │ │ └── mysqllisttablefragmentation.go │ │ │ ├── mysqllisttables │ │ │ │ ├── mysqllisttables_test.go │ │ │ │ └── mysqllisttables.go │ │ │ ├── mysqllisttablesmissinguniqueindexes │ │ │ │ ├── mysqllisttablesmissinguniqueindexes_test.go │ │ │ │ └── mysqllisttablesmissinguniqueindexes.go │ │ │ └── mysqlsql │ │ │ ├── mysqlsql_test.go │ │ │ └── mysqlsql.go │ │ ├── neo4j │ │ │ ├── neo4jcypher │ │ │ │ ├── neo4jcypher_test.go │ │ │ │ └── neo4jcypher.go │ │ │ ├── neo4jexecutecypher │ │ │ │ ├── classifier │ │ │ │ │ ├── classifier_test.go │ │ │ │ │ └── classifier.go │ │ │ │ ├── neo4jexecutecypher_test.go │ │ │ │ └── neo4jexecutecypher.go │ │ │ └── neo4jschema │ │ │ ├── cache │ │ │ │ ├── cache_test.go │ │ │ │ └── cache.go │ │ │ ├── helpers │ │ │ │ ├── helpers_test.go │ │ │ │ └── helpers.go │ │ │ ├── neo4jschema_test.go │ │ │ ├── neo4jschema.go │ │ │ └── types │ │ │ └── types.go │ │ ├── oceanbase │ │ │ ├── oceanbaseexecutesql │ │ │ │ ├── oceanbaseexecutesql_test.go │ │ │ │ └── oceanbaseexecutesql.go │ │ │ └── oceanbasesql │ │ │ ├── oceanbasesql_test.go │ │ │ └── oceanbasesql.go │ │ ├── oracle │ │ │ ├── oracleexecutesql │ │ │ │ └── oracleexecutesql.go │ │ │ └── oraclesql │ │ │ └── oraclesql.go │ │ ├── parameters_test.go │ │ ├── parameters.go │ │ ├── postgres │ │ │ ├── postgresexecutesql │ │ │ │ ├── postgresexecutesql_test.go │ │ │ │ └── postgresexecutesql.go │ │ │ ├── postgreslistactivequeries │ │ │ │ ├── postgreslistactivequeries_test.go │ │ │ │ └── postgreslistactivequeries.go │ │ │ ├── postgreslistavailableextensions │ │ │ │ ├── postgreslistavailableextensions_test.go │ │ │ │ └── postgreslistavailableextensions.go │ │ │ ├── postgreslistinstalledextensions │ │ │ │ ├── postgreslistinstalledextensions_test.go │ │ │ │ └── postgreslistinstalledextensions.go │ │ │ ├── postgreslisttables │ │ │ │ ├── postgreslisttables_test.go │ │ │ │ └── postgreslisttables.go │ │ │ └── postgressql │ │ │ ├── postgressql_test.go │ │ │ └── postgressql.go │ │ ├── redis │ │ │ ├── redis_test.go │ │ │ └── redis.go │ │ ├── spanner │ │ │ ├── spannerexecutesql │ │ │ │ ├── spannerexecutesql_test.go │ │ │ │ └── spannerexecutesql.go │ │ │ ├── spannerlisttables │ │ │ │ ├── spannerlisttables_test.go │ │ │ │ └── spannerlisttables.go │ │ │ └── spannersql │ │ │ ├── spanner_test.go │ │ │ └── spannersql.go │ │ ├── sqlite │ │ │ ├── sqliteexecutesql │ │ │ │ ├── sqliteexecutesql_test.go │ │ │ │ └── sqliteexecutesql.go │ │ │ └── sqlitesql │ │ │ ├── sqlitesql_test.go │ │ │ └── sqlitesql.go │ │ ├── tidb │ │ │ ├── tidbexecutesql │ │ │ │ ├── tidbexecutesql_test.go │ │ │ │ └── tidbexecutesql.go │ │ │ └── tidbsql │ │ │ ├── tidbsql_test.go │ │ │ └── tidbsql.go │ │ ├── tools_test.go │ │ ├── tools.go │ │ ├── toolsets.go │ │ ├── trino │ │ │ ├── trinoexecutesql │ │ │ │ ├── trinoexecutesql_test.go │ │ │ │ └── trinoexecutesql.go │ │ │ └── trinosql │ │ │ ├── trinosql_test.go │ │ │ └── trinosql.go │ │ ├── utility │ │ │ └── wait │ │ │ ├── wait_test.go │ │ │ └── wait.go │ │ ├── valkey │ │ │ ├── valkey_test.go │ │ │ └── valkey.go │ │ └── yugabytedbsql │ │ ├── yugabytedbsql_test.go │ │ └── yugabytedbsql.go │ └── util │ └── util.go ├── LICENSE ├── logo.png ├── main.go ├── MCP-TOOLBOX-EXTENSION.md ├── README.md └── tests ├── alloydb │ ├── alloydb_integration_test.go │ └── alloydb_wait_for_operation_test.go ├── alloydbainl │ └── alloydb_ai_nl_integration_test.go ├── alloydbpg │ └── alloydb_pg_integration_test.go ├── auth.go ├── bigquery │ └── bigquery_integration_test.go ├── bigtable │ └── bigtable_integration_test.go ├── cassandra │ └── cassandra_integration_test.go ├── clickhouse │ └── clickhouse_integration_test.go ├── cloudmonitoring │ └── cloud_monitoring_integration_test.go ├── cloudsql │ ├── cloud_sql_create_database_test.go │ ├── cloud_sql_create_users_test.go │ ├── cloud_sql_get_instances_test.go │ ├── cloud_sql_list_databases_test.go │ ├── cloudsql_list_instances_test.go │ └── cloudsql_wait_for_operation_test.go ├── cloudsqlmssql │ ├── cloud_sql_mssql_create_instance_integration_test.go │ └── cloud_sql_mssql_integration_test.go ├── cloudsqlmysql │ ├── cloud_sql_mysql_create_instance_integration_test.go │ └── cloud_sql_mysql_integration_test.go ├── cloudsqlpg │ ├── cloud_sql_pg_create_instances_test.go │ └── cloud_sql_pg_integration_test.go ├── common.go ├── couchbase │ └── couchbase_integration_test.go ├── dataform │ └── dataform_integration_test.go ├── dataplex │ └── dataplex_integration_test.go ├── dgraph │ └── dgraph_integration_test.go ├── firebird │ └── firebird_integration_test.go ├── firestore │ └── firestore_integration_test.go ├── http │ └── http_integration_test.go ├── looker │ └── looker_integration_test.go ├── mongodb │ └── mongodb_integration_test.go ├── mssql │ └── mssql_integration_test.go ├── mysql │ └── mysql_integration_test.go ├── neo4j │ └── neo4j_integration_test.go ├── oceanbase │ └── oceanbase_integration_test.go ├── option.go ├── oracle │ └── oracle_integration_test.go ├── postgres │ └── postgres_integration_test.go ├── redis │ └── redis_test.go ├── server.go ├── source.go ├── spanner │ └── spanner_integration_test.go ├── sqlite │ └── sqlite_integration_test.go ├── tidb │ └── tidb_integration_test.go ├── tool.go ├── trino │ └── trino_integration_test.go ├── utility │ └── wait_integration_test.go ├── valkey │ └── valkey_test.go └── yugabytedb └── yugabytedb_integration_test.go ``` # Files -------------------------------------------------------------------------------- /internal/tools/neo4j/neo4jschema/types/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 types contains the shared data structures for Neo4j schema representation. 16 | package types 17 | 18 | // ValueType interface representing a Neo4j value. 19 | type ValueType interface { 20 | String() string 21 | } 22 | 23 | // SchemaInfo represents the complete database schema. 24 | type SchemaInfo struct { 25 | NodeLabels []NodeLabel `json:"nodeLabels"` 26 | Relationships []Relationship `json:"relationships"` 27 | Constraints []Constraint `json:"constraints"` 28 | Indexes []Index `json:"indexes"` 29 | DatabaseInfo DatabaseInfo `json:"databaseInfo"` 30 | Statistics Statistics `json:"statistics"` 31 | Errors []string `json:"errors,omitempty"` 32 | } 33 | 34 | // NodeLabel represents a node label with its properties. 35 | type NodeLabel struct { 36 | Name string `json:"name"` 37 | Properties []PropertyInfo `json:"properties"` 38 | Count int64 `json:"count"` 39 | } 40 | 41 | // RelConnectivityInfo holds information about a relationship's start and end nodes, 42 | // primarily used during schema extraction without APOC procedures. 43 | type RelConnectivityInfo struct { 44 | StartNode string 45 | EndNode string 46 | Count int64 47 | } 48 | 49 | // Relationship represents a relationship type with its properties. 50 | type Relationship struct { 51 | Type string `json:"type"` 52 | Properties []PropertyInfo `json:"properties"` 53 | StartNode string `json:"startNode,omitempty"` 54 | EndNode string `json:"endNode,omitempty"` 55 | Count int64 `json:"count"` 56 | } 57 | 58 | // PropertyInfo represents a property with its data types. 59 | type PropertyInfo struct { 60 | Name string `json:"name"` 61 | Types []string `json:"types"` 62 | Mandatory bool `json:"-"` 63 | Unique bool `json:"-"` 64 | Indexed bool `json:"-"` 65 | } 66 | 67 | // Constraint represents a database constraint. 68 | type Constraint struct { 69 | Name string `json:"name"` 70 | Type string `json:"type"` 71 | EntityType string `json:"entityType"` 72 | Label string `json:"label,omitempty"` 73 | Properties []string `json:"properties"` 74 | } 75 | 76 | // Index represents a database index. 77 | type Index struct { 78 | Name string `json:"name"` 79 | State string `json:"state"` 80 | Type string `json:"type"` 81 | EntityType string `json:"entityType"` 82 | Label string `json:"label,omitempty"` 83 | Properties []string `json:"properties"` 84 | } 85 | 86 | // DatabaseInfo contains general database information. 87 | type DatabaseInfo struct { 88 | Name string `json:"name"` 89 | Version string `json:"version"` 90 | Edition string `json:"edition,omitempty"` 91 | } 92 | 93 | // Statistics contains database statistics. 94 | type Statistics struct { 95 | TotalNodes int64 `json:"totalNodes"` 96 | TotalRelationships int64 `json:"totalRelationships"` 97 | TotalProperties int64 `json:"totalProperties"` 98 | NodesByLabel map[string]int64 `json:"nodesByLabel"` 99 | RelationshipsByType map[string]int64 `json:"relationshipsByType"` 100 | PropertiesByLabel map[string]int64 `json:"propertiesByLabel"` 101 | PropertiesByRelType map[string]int64 `json:"propertiesByRelType"` 102 | } 103 | 104 | // APOCSchemaResult represents the result from apoc.meta.schema(). 105 | type APOCSchemaResult struct { 106 | Value map[string]APOCEntity `json:"value"` 107 | } 108 | 109 | // APOCEntity represents a node or relationship in APOC schema. 110 | type APOCEntity struct { 111 | Type string `json:"type"` 112 | Count int64 `json:"count"` 113 | Labels []string `json:"labels,omitempty"` 114 | Properties map[string]APOCProperty `json:"properties"` 115 | Relationships map[string]APOCRelationshipInfo `json:"relationships,omitempty"` 116 | } 117 | 118 | // APOCProperty represents property info from APOC. 119 | type APOCProperty struct { 120 | Type string `json:"type"` 121 | Indexed bool `json:"indexed"` 122 | Unique bool `json:"unique"` 123 | Existence bool `json:"existence"` 124 | } 125 | 126 | // APOCRelationshipInfo represents relationship info from APOC. 127 | type APOCRelationshipInfo struct { 128 | Count int64 `json:"count"` 129 | Direction string `json:"direction"` 130 | Labels []string `json:"labels"` 131 | Properties map[string]APOCProperty `json:"properties"` 132 | } 133 | ``` -------------------------------------------------------------------------------- /docs/en/resources/tools/cassandra/cassandra-cql.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: "cassandra-cql" 3 | type: docs 4 | weight: 1 5 | description: > 6 | A "cassandra-cql" tool executes a pre-defined CQL statement against a Cassandra 7 | database. 8 | aliases: 9 | - /resources/tools/cassandra-cql 10 | --- 11 | 12 | ## About 13 | 14 | A `cassandra-cql` tool executes a pre-defined CQL statement against a Cassandra 15 | database. It's compatible with any of the following sources: 16 | 17 | - [cassandra](../sources/cassandra.md) 18 | 19 | The specified CQL statement is executed as a [prepared statement][cassandra-prepare], 20 | and expects parameters in the CQL query to be in the form of placeholders `?`. 21 | 22 | [cassandra-prepare]: https://docs.datastax.com/en/developer/go-driver/4.8/cql-prepared-statements/ 23 | 24 | ## Example 25 | 26 | > **Note:** This tool uses parameterized queries to prevent CQL injections. 27 | > Query parameters can be used as substitutes for arbitrary expressions. 28 | > Parameters cannot be used as substitutes for keyspaces, table names, column names, 29 | > or other parts of the query. 30 | 31 | ```yaml 32 | tools: 33 | search_users_by_email: 34 | kind: cassandra-cql 35 | source: my-cassandra-cluster 36 | statement: | 37 | SELECT user_id, email, first_name, last_name, created_at 38 | FROM users 39 | WHERE email = ? 40 | description: | 41 | Use this tool to retrieve specific user information by their email address. 42 | Takes an email address and returns user details including user ID, email, 43 | first name, last name, and account creation timestamp. 44 | Do NOT use this tool with a user ID or other identifiers. 45 | Example: 46 | {{ 47 | "email": "[email protected]", 48 | }} 49 | parameters: 50 | - name: email 51 | type: string 52 | description: User's email address 53 | ``` 54 | 55 | ### Example with Template Parameters 56 | 57 | > **Note:** This tool allows direct modifications to the CQL statement, 58 | > including keyspaces, table names, and column names. **This makes it more 59 | > vulnerable to CQL injections**. Using basic parameters only (see above) is 60 | > recommended for performance and safety reasons. For more details, please check 61 | > [templateParameters](../#template-parameters). 62 | 63 | ```yaml 64 | tools: 65 | list_keyspace_table: 66 | kind: cassandra-cql 67 | source: my-cassandra-cluster 68 | statement: | 69 | SELECT * FROM {{.keyspace}}.{{.tableName}}; 70 | description: | 71 | Use this tool to list all information from a specific table in a keyspace. 72 | Example: 73 | {{ 74 | "keyspace": "my_keyspace", 75 | "tableName": "users", 76 | }} 77 | templateParameters: 78 | - name: keyspace 79 | type: string 80 | description: Keyspace containing the table 81 | - name: tableName 82 | type: string 83 | description: Table to select from 84 | ``` 85 | 86 | ## Reference 87 | 88 | | **field** | **type** | **required** | **description** | 89 | |--------------------|:------------------------------------------------:|:------------:|--------------------------------------------------------------------------------------------------------------------------------------------| 90 | | kind | string | true | Must be "cassandra-cql". | 91 | | source | string | true | Name of the source the CQL should execute on. | 92 | | description | string | true | Description of the tool that is passed to the LLM. | 93 | | statement | string | true | CQL statement to execute. | 94 | | authRequired | []string | false | List of authentication requirements for the source. | 95 | | parameters | [parameters](../#specifying-parameters) | false | List of [parameters](../#specifying-parameters) that will be inserted into the CQL statement. | 96 | | templateParameters | [templateParameters](../#template-parameters) | false | List of [templateParameters](../#template-parameters) that will be inserted into the CQL statement before executing prepared statement. | 97 | ``` -------------------------------------------------------------------------------- /docs/en/samples/looker/looker_gemini_oauth/_index.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: "Gemini-CLI and OAuth" 3 | type: docs 4 | weight: 2 5 | description: > 6 | How to connect to Looker from Gemini-CLI with end-user credentials 7 | --- 8 | 9 | ## Overview 10 | 11 | Gemini-CLI can be configured to get an OAuth token from Looker, then send this 12 | token to MCP Toolbox as part of the request. MCP Toolbox can then use this token 13 | to authentincate with Looker. This means that there is no need to get a Looker 14 | Client ID and Client Secret. This also means that MCP Toolbox can be set up as a 15 | shared resource. 16 | 17 | This configuration requires Toolbox v0.14.0 or later. 18 | 19 | ## Step 1: Register the OAuth App in Looker 20 | 21 | You first need to register the OAuth application. Refer to the documentation 22 | [here](https://cloud.google.com/looker/docs/api-cors#registering_an_oauth_client_application). 23 | You may need to ask an administrator to do this for you. 24 | 25 | 1. Go to the API Explorer application, locate "Register OAuth App", and press 26 | the "Run It" button. 27 | 1. Set the `client_guid` to "gemini-cli". 28 | 1. Set the `redirect_uri` to "http://localhost:7777/oauth/callback". 29 | 1. The `display_name` and `description` can be "Gemini-CLI" or anything 30 | meaningful. 31 | 1. Set `enabled` to "true". 32 | 1. Check the box confirming that you understand this API will change data. 33 | 1. Click the "Run" button. 34 | 35 |  36 | 37 | ## Step 2: Install and configure Toolbox 38 | 39 | In this section, we will download Toolbox and run the Toolbox server. 40 | 41 | 1. Download the latest version of Toolbox as a binary: 42 | 43 | {{< notice tip >}} 44 | Select the 45 | [correct binary](https://github.com/googleapis/genai-toolbox/releases) 46 | corresponding to your OS and CPU architecture. 47 | {{< /notice >}} 48 | <!-- {x-release-please-start-version} --> 49 | ```bash 50 | export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64 51 | curl -O https://storage.googleapis.com/genai-toolbox/v0.17.0/$OS/toolbox 52 | ``` 53 | <!-- {x-release-please-end} --> 54 | 55 | 1. Make the binary executable: 56 | 57 | ```bash 58 | chmod +x toolbox 59 | ``` 60 | 61 | 1. Create a file `looker_env` with the settings for your 62 | Looker instance. 63 | 64 | ```bash 65 | export LOOKER_BASE_URL=https://looker.example.com 66 | export LOOKER_VERIFY_SSL=true 67 | export LOOKER_USE_CLIENT_OAUTH=true 68 | ``` 69 | 70 | In some instances you may need to append `:19999` to 71 | the LOOKER_BASE_URL. 72 | 73 | 1. Load the looker_env file into your environment. 74 | 75 | ```bash 76 | source looker_env 77 | ``` 78 | 79 | 1. Run the Toolbox server using the prebuilt Looker tools. 80 | 81 | ```bash 82 | ./toolbox --prebuilt looker 83 | ``` 84 | 85 | The toolbox server will begin listening on localhost port 5000. Leave it 86 | running and continue in another terminal. 87 | 88 | Later, when it is time to shut everything down, you can quit the toolbox 89 | server with Ctrl-C in this terminal window. 90 | 91 | ## Step 3: Configure Gemini-CLI 92 | 93 | 1. Edit the file `~/.gemini/settings.json`. Add the following, substituting your 94 | Looker server host name for `looker.example.com`. 95 | 96 | ```json 97 | "mcpServers": { 98 | "looker": { 99 | "httpUrl": "http://localhost:5000/mcp", 100 | "oauth": { 101 | "enabled": true, 102 | "clientId": "gemini-cli", 103 | "authorizationUrl": "https://looker.example.com/auth", 104 | "tokenUrl": "https://looker.example.com/api/token", 105 | "scopes": ["cors_api"] 106 | } 107 | } 108 | } 109 | ``` 110 | 111 | The `authorizationUrl` should point to the URL you use to access Looker via the 112 | web UI. The `tokenUrl` should point to the URL you use to access Looker via 113 | the API. In some cases you will need to use the port number `:19999` after 114 | the host name but before the `/api/token` part. 115 | 116 | 1. Start Gemini-CLI. 117 | 118 | 1. Authenticate with the command `/mcp auth looker`. Gemini-CLI will open up a 119 | browser where you will confirm that you want to access Looker with your 120 | account. 121 | 122 |  123 | 124 |  125 | 126 | 1. Use Gemini-CLI with your tools. 127 | 128 | ## Using Toolbox as a Shared Service 129 | 130 | Toolbox can be run on another server as a shared service accessed by multiple 131 | users. We strongly recommend running toolbox behind a web proxy such as `nginx` 132 | which will provide SSL encryption. Google Cloud Run is another good way to run 133 | toolbox. You will connect to a service like `https://toolbox.example.com/mcp`. 134 | The proxy server will handle the SSL encryption and certificates. Then it will 135 | foward the requests to `http://localhost:5000/mcp` running in that environment. 136 | The details of the config are beyond the scope of this document, but will be 137 | familiar to your system administrators. 138 | 139 | To use the shared service, just change the `localhost:5000` in the `httpUrl` in 140 | `~/.gemini/settings.json` to the host name and possibly the port of the shared 141 | service. 142 | ``` -------------------------------------------------------------------------------- /internal/tools/neo4j/neo4jcypher/neo4jcypher.go: -------------------------------------------------------------------------------- ```go 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package neo4jcypher 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | 21 | "github.com/goccy/go-yaml" 22 | neo4jsc "github.com/googleapis/genai-toolbox/internal/sources/neo4j" 23 | "github.com/googleapis/genai-toolbox/internal/tools/neo4j/neo4jschema/helpers" 24 | "github.com/neo4j/neo4j-go-driver/v5/neo4j" 25 | 26 | "github.com/googleapis/genai-toolbox/internal/sources" 27 | "github.com/googleapis/genai-toolbox/internal/tools" 28 | ) 29 | 30 | const kind string = "neo4j-cypher" 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 | Neo4jDriver() neo4j.DriverWithContext 48 | Neo4jDatabase() string 49 | } 50 | 51 | // validate compatible sources are still compatible 52 | var _ compatibleSource = &neo4jsc.Source{} 53 | 54 | var compatibleSources = [...]string{neo4jsc.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 | } 65 | 66 | // validate interface 67 | var _ tools.ToolConfig = Config{} 68 | 69 | func (cfg Config) ToolConfigKind() string { 70 | return kind 71 | } 72 | 73 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { 74 | // verify source exists 75 | rawS, ok := srcs[cfg.Source] 76 | if !ok { 77 | return nil, fmt.Errorf("no source named %q configured", cfg.Source) 78 | } 79 | 80 | // verify the source is compatible 81 | s, ok := rawS.(compatibleSource) 82 | if !ok { 83 | return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources) 84 | } 85 | 86 | mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, cfg.Parameters) 87 | 88 | // finish tool setup 89 | t := Tool{ 90 | Name: cfg.Name, 91 | Kind: kind, 92 | Parameters: cfg.Parameters, 93 | Statement: cfg.Statement, 94 | AuthRequired: cfg.AuthRequired, 95 | Driver: s.Neo4jDriver(), 96 | Database: s.Neo4jDatabase(), 97 | manifest: tools.Manifest{Description: cfg.Description, Parameters: cfg.Parameters.Manifest(), AuthRequired: cfg.AuthRequired}, 98 | mcpManifest: mcpManifest, 99 | } 100 | return t, nil 101 | } 102 | 103 | // validate interface 104 | var _ tools.Tool = Tool{} 105 | 106 | type Tool struct { 107 | Name string `yaml:"name"` 108 | Kind string `yaml:"kind"` 109 | Parameters tools.Parameters `yaml:"parameters"` 110 | AuthRequired []string `yaml:"authRequired"` 111 | 112 | Driver neo4j.DriverWithContext 113 | Database string 114 | Statement string 115 | manifest tools.Manifest 116 | mcpManifest tools.McpManifest 117 | } 118 | 119 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { 120 | paramsMap := params.AsMap() 121 | 122 | config := neo4j.ExecuteQueryWithDatabase(t.Database) 123 | results, err := neo4j.ExecuteQuery[*neo4j.EagerResult](ctx, t.Driver, t.Statement, paramsMap, 124 | neo4j.EagerResultTransformer, config) 125 | if err != nil { 126 | return nil, fmt.Errorf("unable to execute query: %w", err) 127 | } 128 | 129 | var out []any 130 | keys := results.Keys 131 | records := results.Records 132 | for _, record := range records { 133 | vMap := make(map[string]any) 134 | for col, value := range record.Values { 135 | vMap[keys[col]] = helpers.ConvertValue(value) 136 | } 137 | out = append(out, vMap) 138 | } 139 | 140 | return out, nil 141 | } 142 | 143 | func (t Tool) ParseParams(data map[string]any, claimsMap map[string]map[string]any) (tools.ParamValues, error) { 144 | return tools.ParseParams(t.Parameters, data, claimsMap) 145 | } 146 | 147 | func (t Tool) Manifest() tools.Manifest { 148 | return t.manifest 149 | } 150 | 151 | func (t Tool) McpManifest() tools.McpManifest { 152 | return t.mcpManifest 153 | } 154 | 155 | func (t Tool) Authorized(verifiedAuthServices []string) bool { 156 | return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices) 157 | } 158 | 159 | func (t Tool) RequiresClientAuthorization() bool { 160 | return false 161 | } 162 | ``` -------------------------------------------------------------------------------- /internal/sources/postgres/postgres_test.go: -------------------------------------------------------------------------------- ```go 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package postgres_test 16 | 17 | import ( 18 | "sort" 19 | "strings" 20 | "testing" 21 | 22 | yaml "github.com/goccy/go-yaml" 23 | "github.com/google/go-cmp/cmp" 24 | "github.com/googleapis/genai-toolbox/internal/server" 25 | "github.com/googleapis/genai-toolbox/internal/sources/postgres" 26 | "github.com/googleapis/genai-toolbox/internal/testutils" 27 | ) 28 | 29 | func TestParseFromYamlPostgres(t *testing.T) { 30 | tcs := []struct { 31 | desc string 32 | in string 33 | want server.SourceConfigs 34 | }{ 35 | { 36 | desc: "basic example", 37 | in: ` 38 | sources: 39 | my-pg-instance: 40 | kind: postgres 41 | host: my-host 42 | port: my-port 43 | database: my_db 44 | user: my_user 45 | password: my_pass 46 | `, 47 | want: server.SourceConfigs{ 48 | "my-pg-instance": postgres.Config{ 49 | Name: "my-pg-instance", 50 | Kind: postgres.SourceKind, 51 | Host: "my-host", 52 | Port: "my-port", 53 | Database: "my_db", 54 | User: "my_user", 55 | Password: "my_pass", 56 | }, 57 | }, 58 | }, 59 | { 60 | desc: "example with query params", 61 | in: ` 62 | sources: 63 | my-pg-instance: 64 | kind: postgres 65 | host: my-host 66 | port: my-port 67 | database: my_db 68 | user: my_user 69 | password: my_pass 70 | queryParams: 71 | sslmode: verify-full 72 | sslrootcert: /tmp/ca.crt 73 | `, 74 | want: server.SourceConfigs{ 75 | "my-pg-instance": postgres.Config{ 76 | Name: "my-pg-instance", 77 | Kind: postgres.SourceKind, 78 | Host: "my-host", 79 | Port: "my-port", 80 | Database: "my_db", 81 | User: "my_user", 82 | Password: "my_pass", 83 | QueryParams: map[string]string{ 84 | "sslmode": "verify-full", 85 | "sslrootcert": "/tmp/ca.crt", 86 | }, 87 | }, 88 | }, 89 | }, 90 | } 91 | for _, tc := range tcs { 92 | t.Run(tc.desc, func(t *testing.T) { 93 | got := struct { 94 | Sources server.SourceConfigs `yaml:"sources"` 95 | }{} 96 | // Parse contents 97 | err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got) 98 | if err != nil { 99 | t.Fatalf("unable to unmarshal: %s", err) 100 | } 101 | if !cmp.Equal(tc.want, got.Sources) { 102 | t.Fatalf("incorrect parse: want %v, got %v", tc.want, got.Sources) 103 | } 104 | }) 105 | } 106 | 107 | } 108 | 109 | func TestFailParseFromYaml(t *testing.T) { 110 | tcs := []struct { 111 | desc string 112 | in string 113 | err string 114 | }{ 115 | { 116 | desc: "extra field", 117 | in: ` 118 | sources: 119 | my-pg-instance: 120 | kind: postgres 121 | host: my-host 122 | port: my-port 123 | database: my_db 124 | user: my_user 125 | password: my_pass 126 | foo: bar 127 | `, 128 | err: "unable to parse source \"my-pg-instance\" as \"postgres\": [2:1] unknown field \"foo\"\n 1 | database: my_db\n> 2 | foo: bar\n ^\n 3 | host: my-host\n 4 | kind: postgres\n 5 | password: my_pass\n 6 | ", 129 | }, 130 | { 131 | desc: "missing required field", 132 | in: ` 133 | sources: 134 | my-pg-instance: 135 | kind: postgres 136 | host: my-host 137 | port: my-port 138 | database: my_db 139 | user: my_user 140 | `, 141 | err: "unable to parse source \"my-pg-instance\" as \"postgres\": Key: 'Config.Password' Error:Field validation for 'Password' failed on the 'required' tag", 142 | }, 143 | } 144 | for _, tc := range tcs { 145 | t.Run(tc.desc, func(t *testing.T) { 146 | got := struct { 147 | Sources server.SourceConfigs `yaml:"sources"` 148 | }{} 149 | // Parse contents 150 | err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got) 151 | if err == nil { 152 | t.Fatalf("expect parsing to fail") 153 | } 154 | errStr := err.Error() 155 | if errStr != tc.err { 156 | t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err) 157 | } 158 | }) 159 | } 160 | } 161 | 162 | func TestConvertParamMapToRawQuery(t *testing.T) { 163 | tcs := []struct { 164 | desc string 165 | in map[string]string 166 | want string 167 | }{ 168 | { 169 | desc: "nil param", 170 | in: nil, 171 | want: "", 172 | }, 173 | { 174 | desc: "single query param", 175 | in: map[string]string{ 176 | "foo": "bar", 177 | }, 178 | want: "foo=bar", 179 | }, 180 | { 181 | desc: "more than one query param", 182 | in: map[string]string{ 183 | "foo": "bar", 184 | "hello": "world", 185 | }, 186 | want: "foo=bar&hello=world", 187 | }, 188 | } 189 | for _, tc := range tcs { 190 | t.Run(tc.desc, func(t *testing.T) { 191 | got := postgres.ConvertParamMapToRawQuery(tc.in) 192 | if strings.Contains(got, "&") { 193 | splitGot := strings.Split(got, "&") 194 | sort.Strings(splitGot) 195 | got = strings.Join(splitGot, "&") 196 | } 197 | if got != tc.want { 198 | t.Fatalf("incorrect conversion: got %s want %s", got, tc.want) 199 | } 200 | }) 201 | } 202 | } 203 | ``` -------------------------------------------------------------------------------- /internal/tools/looker/lookerquery/lookerquery.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 lookerquery 15 | 16 | import ( 17 | "context" 18 | "encoding/json" 19 | "fmt" 20 | 21 | yaml "github.com/goccy/go-yaml" 22 | "github.com/googleapis/genai-toolbox/internal/sources" 23 | lookersrc "github.com/googleapis/genai-toolbox/internal/sources/looker" 24 | "github.com/googleapis/genai-toolbox/internal/tools" 25 | "github.com/googleapis/genai-toolbox/internal/tools/looker/lookercommon" 26 | "github.com/googleapis/genai-toolbox/internal/util" 27 | 28 | "github.com/looker-open-source/sdk-codegen/go/rtl" 29 | v4 "github.com/looker-open-source/sdk-codegen/go/sdk/v4" 30 | ) 31 | 32 | const kind string = "looker-query" 33 | 34 | func init() { 35 | if !tools.Register(kind, newConfig) { 36 | panic(fmt.Sprintf("tool kind %q already registered", kind)) 37 | } 38 | } 39 | 40 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) { 41 | actual := Config{Name: name} 42 | if err := decoder.DecodeContext(ctx, &actual); err != nil { 43 | return nil, err 44 | } 45 | return actual, nil 46 | } 47 | 48 | type Config struct { 49 | Name string `yaml:"name" validate:"required"` 50 | Kind string `yaml:"kind" validate:"required"` 51 | Source string `yaml:"source" validate:"required"` 52 | Description string `yaml:"description" validate:"required"` 53 | AuthRequired []string `yaml:"authRequired"` 54 | } 55 | 56 | // validate interface 57 | var _ tools.ToolConfig = Config{} 58 | 59 | func (cfg Config) ToolConfigKind() string { 60 | return kind 61 | } 62 | 63 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { 64 | // verify source exists 65 | rawS, ok := srcs[cfg.Source] 66 | if !ok { 67 | return nil, fmt.Errorf("no source named %q configured", cfg.Source) 68 | } 69 | 70 | // verify the source is compatible 71 | s, ok := rawS.(*lookersrc.Source) 72 | if !ok { 73 | return nil, fmt.Errorf("invalid source for %q tool: source kind must be `looker`", kind) 74 | } 75 | 76 | parameters := lookercommon.GetQueryParameters() 77 | 78 | mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters) 79 | 80 | // finish tool setup 81 | return Tool{ 82 | Name: cfg.Name, 83 | Kind: kind, 84 | Parameters: parameters, 85 | AuthRequired: cfg.AuthRequired, 86 | UseClientOAuth: s.UseClientOAuth, 87 | Client: s.Client, 88 | ApiSettings: s.ApiSettings, 89 | manifest: tools.Manifest{ 90 | Description: cfg.Description, 91 | Parameters: parameters.Manifest(), 92 | AuthRequired: cfg.AuthRequired, 93 | }, 94 | mcpManifest: mcpManifest, 95 | }, nil 96 | } 97 | 98 | // validate interface 99 | var _ tools.Tool = Tool{} 100 | 101 | type Tool struct { 102 | Name string `yaml:"name"` 103 | Kind string `yaml:"kind"` 104 | UseClientOAuth bool 105 | Client *v4.LookerSDK 106 | ApiSettings *rtl.ApiSettings 107 | AuthRequired []string `yaml:"authRequired"` 108 | Parameters tools.Parameters `yaml:"parameters"` 109 | manifest tools.Manifest 110 | mcpManifest tools.McpManifest 111 | } 112 | 113 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { 114 | logger, err := util.LoggerFromContext(ctx) 115 | if err != nil { 116 | return nil, fmt.Errorf("unable to get logger from ctx: %s", err) 117 | } 118 | wq, err := lookercommon.ProcessQueryArgs(ctx, params) 119 | if err != nil { 120 | return nil, fmt.Errorf("error building WriteQuery request: %w", err) 121 | } 122 | sdk, err := lookercommon.GetLookerSDK(t.UseClientOAuth, t.ApiSettings, t.Client, accessToken) 123 | if err != nil { 124 | return nil, fmt.Errorf("error getting sdk: %w", err) 125 | } 126 | resp, err := lookercommon.RunInlineQuery(ctx, sdk, wq, "json", t.ApiSettings) 127 | if err != nil { 128 | return nil, fmt.Errorf("error making query request: %s", err) 129 | } 130 | 131 | logger.DebugContext(ctx, "resp = ", resp) 132 | 133 | var data []any 134 | e := json.Unmarshal([]byte(resp), &data) 135 | if e != nil { 136 | return nil, fmt.Errorf("error unmarshaling query response: %s", e) 137 | } 138 | 139 | logger.DebugContext(ctx, "data = ", data) 140 | 141 | return data, nil 142 | } 143 | 144 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { 145 | return tools.ParseParams(t.Parameters, data, claims) 146 | } 147 | 148 | func (t Tool) Manifest() tools.Manifest { 149 | return t.manifest 150 | } 151 | 152 | func (t Tool) McpManifest() tools.McpManifest { 153 | return t.mcpManifest 154 | } 155 | 156 | func (t Tool) Authorized(verifiedAuthServices []string) bool { 157 | return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices) 158 | } 159 | 160 | func (t Tool) RequiresClientAuthorization() bool { 161 | return t.UseClientOAuth 162 | } 163 | ``` -------------------------------------------------------------------------------- /docs/en/resources/tools/tidb/tidb-sql.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: "tidb-sql" 3 | type: docs 4 | weight: 1 5 | description: > 6 | A "tidb-sql" tool executes a pre-defined SQL statement against a TiDB 7 | database. 8 | aliases: 9 | - /resources/tools/tidb-sql 10 | --- 11 | 12 | ## About 13 | 14 | A `tidb-sql` tool executes a pre-defined SQL statement against a TiDB 15 | database. It's compatible with the following source: 16 | 17 | - [tidb](../sources/tidb.md) 18 | 19 | The specified SQL statement is executed as a [prepared statement][tidb-prepare], 20 | and expects parameters in the SQL query to be in the form of placeholders `?`. 21 | 22 | [tidb-prepare]: https://docs.pingcap.com/tidb/stable/sql-prepared-plan-cache 23 | 24 | ## Example 25 | 26 | > **Note:** This tool uses parameterized queries to prevent SQL injections. 27 | > Query parameters can be used as substitutes for arbitrary expressions. 28 | > Parameters cannot be used as substitutes for identifiers, column names, table 29 | > names, or other parts of the query. 30 | 31 | ```yaml 32 | tools: 33 | search_flights_by_number: 34 | kind: tidb-sql 35 | source: my-tidb-instance 36 | statement: | 37 | SELECT * FROM flights 38 | WHERE airline = ? 39 | AND flight_number = ? 40 | LIMIT 10 41 | description: | 42 | Use this tool to get information for a specific flight. 43 | Takes an airline code and flight number and returns info on the flight. 44 | Do NOT use this tool with a flight id. Do NOT guess an airline code or flight number. 45 | A airline code is a code for an airline service consisting of two-character 46 | airline designator and followed by flight number, which is 1 to 4 digit number. 47 | For example, if given CY 0123, the airline is "CY", and flight_number is "123". 48 | Another example for this is DL 1234, the airline is "DL", and flight_number is "1234". 49 | If the tool returns more than one option choose the date closes to today. 50 | Example: 51 | {{ 52 | "airline": "CY", 53 | "flight_number": "888", 54 | }} 55 | Example: 56 | {{ 57 | "airline": "DL", 58 | "flight_number": "1234", 59 | }} 60 | parameters: 61 | - name: airline 62 | type: string 63 | description: Airline unique 2 letter identifier 64 | - name: flight_number 65 | type: string 66 | description: 1 to 4 digit number 67 | ``` 68 | 69 | ### Example with Template Parameters 70 | 71 | > **Note:** This tool allows direct modifications to the SQL statement, 72 | > including identifiers, column names, and table names. **This makes it more 73 | > vulnerable to SQL injections**. Using basic parameters only (see above) is 74 | > recommended for performance and safety reasons. For more details, please check 75 | > [templateParameters](_index#template-parameters). 76 | 77 | ```yaml 78 | tools: 79 | list_table: 80 | kind: tidb-sql 81 | source: my-tidb-instance 82 | statement: | 83 | SELECT * FROM {{.tableName}}; 84 | description: | 85 | Use this tool to list all information from a specific table. 86 | Example: 87 | {{ 88 | "tableName": "flights", 89 | }} 90 | templateParameters: 91 | - name: tableName 92 | type: string 93 | description: Table to select from 94 | ``` 95 | 96 | ## Reference 97 | 98 | | **field** | **type** | **required** | **description** | 99 | |--------------------|:------------------------------------------------:|:------------:|--------------------------------------------------------------------------------------------------------------------------------------------| 100 | | kind | string | true | Must be "tidb-sql". | 101 | | source | string | true | Name of the source the SQL should execute on. | 102 | | description | string | true | Description of the tool that is passed to the LLM. | 103 | | statement | string | true | SQL statement to execute on. | 104 | | parameters | [parameters](_index#specifying-parameters) | false | List of [parameters](_index#specifying-parameters) that will be inserted into the SQL statement. | 105 | | templateParameters | [templateParameters](_index#template-parameters) | false | List of [templateParameters](_index#template-parameters) that will be inserted into the SQL statement before executing prepared statement. | 106 | ``` -------------------------------------------------------------------------------- /docs/en/resources/sources/looker.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: "Looker" 3 | type: docs 4 | weight: 1 5 | description: > 6 | Looker is a business intelligence tool that also provides a semantic layer. 7 | --- 8 | 9 | ## About 10 | 11 | [Looker][looker-docs] is a web based business intelligence and data management 12 | tool that provides a semantic layer to facilitate querying. It can be deployed 13 | in the cloud, on GCP, or on premises. 14 | 15 | [looker-docs]: https://cloud.google.com/looker/docs 16 | 17 | ## Requirements 18 | 19 | ### Looker User 20 | 21 | This source only uses API authentication. You will need to 22 | [create an API user][looker-user] to login to Looker. 23 | 24 | [looker-user]: 25 | https://cloud.google.com/looker/docs/api-auth#authentication_with_an_sdk 26 | 27 | {{< notice note >}} 28 | To use the Conversational Analytics API, you will need to have the following 29 | Google Cloud Project API enabled and IAM permissions. 30 | {{< /notice >}} 31 | 32 | ### API Enablement in GCP 33 | 34 | Enable the following APIs in your Google Cloud Project: 35 | 36 | ``` 37 | gcloud services enable geminidataanalytics.googleapis.com --project=$PROJECT_ID 38 | gcloud services enable cloudaicompanion.googleapis.com --project=$PROJECT_ID 39 | ``` 40 | 41 | ### IAM Permissions in GCP 42 | 43 | In addition to [setting the ADC for your server][set-adc], you need to ensure 44 | the IAM identity has been given the following IAM roles (or corresponding 45 | permissions): 46 | 47 | - `roles/looker.instanceUser` 48 | - `roles/cloudaicompanion.user` 49 | - `roles/geminidataanalytics.dataAgentStatelessUser` 50 | 51 | To initialize the application default credential run `gcloud auth login --update-adc` 52 | in your environment before starting MCP Toolbox. 53 | 54 | [set-adc]: https://cloud.google.com/docs/authentication/provide-credentials-adc 55 | 56 | ## Example 57 | 58 | ```yaml 59 | sources: 60 | my-looker-source: 61 | kind: looker 62 | base_url: http://looker.example.com 63 | client_id: ${LOOKER_CLIENT_ID} 64 | client_secret: ${LOOKER_CLIENT_SECRET} 65 | project: ${LOOKER_PROJECT} 66 | location: ${LOOKER_LOCATION} 67 | verify_ssl: true 68 | timeout: 600s 69 | ``` 70 | 71 | The Looker base url will look like "https://looker.example.com", don't include 72 | a trailing "/". In some cases, especially if your Looker is deployed 73 | on-premises, you may need to add the API port number like 74 | "https://looker.example.com:19999". 75 | 76 | Verify ssl should almost always be "true" (all lower case) unless you are using 77 | a self-signed ssl certificate for the Looker server. Anything other than "true" 78 | will be interpreted as false. 79 | 80 | The client id and client secret are seemingly random character sequences 81 | assigned by the looker server. If you are using Looker OAuth you don't need 82 | these settings 83 | 84 | The `project` and `location` fields are utilized **only** when using the conversational analytics tool. 85 | 86 | {{< notice tip >}} 87 | Use environment variable replacement with the format ${ENV_NAME} 88 | instead of hardcoding your secrets into the configuration file. 89 | {{< /notice >}} 90 | 91 | ## Reference 92 | 93 | | **field** | **type** | **required** | **description** | 94 | | -------------------- | :------: | :----------: | ----------------------------------------------------------------------------------------- | 95 | | kind | string | true | Must be "looker". | 96 | | base_url | string | true | The URL of your Looker server with no trailing /). | 97 | | client_id | string | false | The client id assigned by Looker. | 98 | | client_secret | string | false | The client secret assigned by Looker. | 99 | | verify_ssl | string | false | Whether to check the ssl certificate of the server. | 100 | | project | string | false | The project id to use in Google Cloud. | 101 | | location | string | false | The location to use in Google Cloud. (default: us) | 102 | | timeout | string | false | Maximum time to wait for query execution (e.g. "30s", "2m"). By default, 120s is applied. | 103 | | use_client_oauth | string | false | Use OAuth tokens instead of client_id and client_secret. (default: false) | 104 | | show_hidden_models | string | false | Show or hide hidden models. (default: true) | 105 | | show_hidden_explores | string | false | Show or hide hidden explores. (default: true) | 106 | | show_hidden_fields | string | false | Show or hide hidden fields. (default: true) | 107 | ``` -------------------------------------------------------------------------------- /docs/en/getting-started/quickstart/go/langchain/quickstart.go: -------------------------------------------------------------------------------- ```go 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "log" 8 | "os" 9 | 10 | "github.com/googleapis/mcp-toolbox-sdk-go/core" 11 | "github.com/tmc/langchaingo/llms" 12 | "github.com/tmc/langchaingo/llms/googleai" 13 | ) 14 | 15 | // ConvertToLangchainTool converts a generic core.ToolboxTool into a LangChainGo llms.Tool. 16 | func ConvertToLangchainTool(toolboxTool *core.ToolboxTool) llms.Tool { 17 | 18 | // Fetch the tool's input schema 19 | inputschema, err := toolboxTool.InputSchema() 20 | if err != nil { 21 | return llms.Tool{} 22 | } 23 | 24 | var paramsSchema map[string]any 25 | _ = json.Unmarshal(inputschema, ¶msSchema) 26 | 27 | // Convert into LangChain's llms.Tool 28 | return llms.Tool{ 29 | Type: "function", 30 | Function: &llms.FunctionDefinition{ 31 | Name: toolboxTool.Name(), 32 | Description: toolboxTool.Description(), 33 | Parameters: paramsSchema, 34 | }, 35 | } 36 | } 37 | 38 | const systemPrompt = ` 39 | You're a helpful hotel assistant. You handle hotel searching, booking, and 40 | cancellations. When the user searches for a hotel, mention its name, id, 41 | location and price tier. Always mention hotel ids while performing any 42 | searches. This is very important for any operations. For any bookings or 43 | cancellations, please provide the appropriate confirmation. Be sure to 44 | update checkin or checkout dates if mentioned by the user. 45 | Don't ask for confirmations from the user. 46 | ` 47 | 48 | var queries = []string{ 49 | "Find hotels in Basel with Basel in its name.", 50 | "Can you book the hotel Hilton Basel for me?", 51 | "Oh wait, this is too expensive. Please cancel it.", 52 | "Please book the Hyatt Regency instead.", 53 | "My check in dates would be from April 10, 2024 to April 19, 2024.", 54 | } 55 | 56 | func main() { 57 | genaiKey := os.Getenv("GOOGLE_API_KEY") 58 | toolboxURL := "http://localhost:5000" 59 | ctx := context.Background() 60 | 61 | // Initialize the Google AI client (LLM). 62 | llm, err := googleai.New(ctx, googleai.WithAPIKey(genaiKey), googleai.WithDefaultModel("gemini-2.0-flash")) 63 | if err != nil { 64 | log.Fatalf("Failed to create Google AI client: %v", err) 65 | } 66 | 67 | // Initialize the MCP Toolbox client. 68 | toolboxClient, err := core.NewToolboxClient(toolboxURL) 69 | if err != nil { 70 | log.Fatalf("Failed to create Toolbox client: %v", err) 71 | } 72 | 73 | // Load the tool using the MCP Toolbox SDK. 74 | tools, err := toolboxClient.LoadToolset("my-toolset", ctx) 75 | if err != nil { 76 | log.Fatalf("Failed to load tools: %v\nMake sure your Toolbox server is running and the tool is configured.", err) 77 | } 78 | 79 | toolsMap := make(map[string]*core.ToolboxTool, len(tools)) 80 | 81 | langchainTools := make([]llms.Tool, len(tools)) 82 | // Convert the loaded ToolboxTools into the format LangChainGo requires. 83 | for i, tool := range tools { 84 | langchainTools[i] = ConvertToLangchainTool(tool) 85 | toolsMap[tool.Name()] = tool 86 | } 87 | 88 | // Start the conversation history. 89 | messageHistory := []llms.MessageContent{ 90 | llms.TextParts(llms.ChatMessageTypeSystem, systemPrompt), 91 | } 92 | 93 | for _, query := range queries { 94 | messageHistory = append(messageHistory, llms.TextParts(llms.ChatMessageTypeHuman, query)) 95 | 96 | // Make the first call to the LLM, making it aware of the tool. 97 | resp, err := llm.GenerateContent(ctx, messageHistory, llms.WithTools(langchainTools)) 98 | if err != nil { 99 | log.Fatalf("LLM call failed: %v", err) 100 | } 101 | respChoice := resp.Choices[0] 102 | 103 | assistantResponse := llms.TextParts(llms.ChatMessageTypeAI, respChoice.Content) 104 | for _, tc := range respChoice.ToolCalls { 105 | assistantResponse.Parts = append(assistantResponse.Parts, tc) 106 | } 107 | messageHistory = append(messageHistory, assistantResponse) 108 | 109 | // Process each tool call requested by the model. 110 | for _, tc := range respChoice.ToolCalls { 111 | toolName := tc.FunctionCall.Name 112 | tool := toolsMap[toolName] 113 | var args map[string]any 114 | if err := json.Unmarshal([]byte(tc.FunctionCall.Arguments), &args); err != nil { 115 | log.Fatalf("Failed to unmarshal arguments for tool '%s': %v", toolName, err) 116 | } 117 | toolResult, err := tool.Invoke(ctx, args) 118 | if err != nil { 119 | log.Fatalf("Failed to execute tool '%s': %v", toolName, err) 120 | } 121 | if toolResult == "" || toolResult == nil { 122 | toolResult = "Operation completed successfully with no specific return value." 123 | } 124 | 125 | // Create the tool call response message and add it to the history. 126 | toolResponse := llms.MessageContent{ 127 | Role: llms.ChatMessageTypeTool, 128 | Parts: []llms.ContentPart{ 129 | llms.ToolCallResponse{ 130 | Name: toolName, 131 | Content: fmt.Sprintf("%v", toolResult), 132 | }, 133 | }, 134 | } 135 | messageHistory = append(messageHistory, toolResponse) 136 | } 137 | finalResp, err := llm.GenerateContent(ctx, messageHistory) 138 | if err != nil { 139 | log.Fatalf("Final LLM call failed after tool execution: %v", err) 140 | } 141 | 142 | // Add the final textual response from the LLM to the history 143 | messageHistory = append(messageHistory, llms.TextParts(llms.ChatMessageTypeAI, finalResp.Choices[0].Content)) 144 | 145 | fmt.Println(finalResp.Choices[0].Content) 146 | 147 | } 148 | 149 | } 150 | ``` -------------------------------------------------------------------------------- /internal/tools/postgres/postgressql/postgressql_test.go: -------------------------------------------------------------------------------- ```go 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package postgressql_test 16 | 17 | import ( 18 | "testing" 19 | 20 | yaml "github.com/goccy/go-yaml" 21 | "github.com/google/go-cmp/cmp" 22 | "github.com/googleapis/genai-toolbox/internal/server" 23 | "github.com/googleapis/genai-toolbox/internal/testutils" 24 | "github.com/googleapis/genai-toolbox/internal/tools" 25 | "github.com/googleapis/genai-toolbox/internal/tools/postgres/postgressql" 26 | ) 27 | 28 | func TestParseFromYamlPostgres(t *testing.T) { 29 | ctx, err := testutils.ContextWithNewLogger() 30 | if err != nil { 31 | t.Fatalf("unexpected error: %s", err) 32 | } 33 | tcs := []struct { 34 | desc string 35 | in string 36 | want server.ToolConfigs 37 | }{ 38 | { 39 | desc: "basic example", 40 | in: ` 41 | tools: 42 | example_tool: 43 | kind: postgres-sql 44 | source: my-pg-instance 45 | description: some description 46 | statement: | 47 | SELECT * FROM SQL_STATEMENT; 48 | authRequired: 49 | - my-google-auth-service 50 | - other-auth-service 51 | parameters: 52 | - name: country 53 | type: string 54 | description: some description 55 | authServices: 56 | - name: my-google-auth-service 57 | field: user_id 58 | - name: other-auth-service 59 | field: user_id 60 | `, 61 | want: server.ToolConfigs{ 62 | "example_tool": postgressql.Config{ 63 | Name: "example_tool", 64 | Kind: "postgres-sql", 65 | Source: "my-pg-instance", 66 | Description: "some description", 67 | Statement: "SELECT * FROM SQL_STATEMENT;\n", 68 | AuthRequired: []string{"my-google-auth-service", "other-auth-service"}, 69 | Parameters: []tools.Parameter{ 70 | tools.NewStringParameterWithAuth("country", "some description", 71 | []tools.ParamAuthService{{Name: "my-google-auth-service", Field: "user_id"}, 72 | {Name: "other-auth-service", Field: "user_id"}}), 73 | }, 74 | }, 75 | }, 76 | }, 77 | } 78 | for _, tc := range tcs { 79 | t.Run(tc.desc, func(t *testing.T) { 80 | got := struct { 81 | Tools server.ToolConfigs `yaml:"tools"` 82 | }{} 83 | // Parse contents 84 | err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got) 85 | if err != nil { 86 | t.Fatalf("unable to unmarshal: %s", err) 87 | } 88 | if diff := cmp.Diff(tc.want, got.Tools); diff != "" { 89 | t.Fatalf("incorrect parse: diff %v", diff) 90 | } 91 | }) 92 | } 93 | 94 | } 95 | 96 | func TestParseFromYamlWithTemplateParamsPostgres(t *testing.T) { 97 | ctx, err := testutils.ContextWithNewLogger() 98 | if err != nil { 99 | t.Fatalf("unexpected error: %s", err) 100 | } 101 | tcs := []struct { 102 | desc string 103 | in string 104 | want server.ToolConfigs 105 | }{ 106 | { 107 | desc: "basic example", 108 | in: ` 109 | tools: 110 | example_tool: 111 | kind: postgres-sql 112 | source: my-pg-instance 113 | description: some description 114 | statement: | 115 | SELECT * FROM SQL_STATEMENT; 116 | parameters: 117 | - name: name 118 | type: string 119 | description: some description 120 | templateParameters: 121 | - name: tableName 122 | type: string 123 | description: The table to select hotels from. 124 | - name: fieldArray 125 | type: array 126 | description: The columns to return for the query. 127 | items: 128 | name: column 129 | type: string 130 | description: A column name that will be returned from the query. 131 | `, 132 | want: server.ToolConfigs{ 133 | "example_tool": postgressql.Config{ 134 | Name: "example_tool", 135 | Kind: "postgres-sql", 136 | Source: "my-pg-instance", 137 | Description: "some description", 138 | Statement: "SELECT * FROM SQL_STATEMENT;\n", 139 | AuthRequired: []string{}, 140 | Parameters: []tools.Parameter{ 141 | tools.NewStringParameter("name", "some description"), 142 | }, 143 | TemplateParameters: []tools.Parameter{ 144 | tools.NewStringParameter("tableName", "The table to select hotels from."), 145 | tools.NewArrayParameter("fieldArray", "The columns to return for the query.", tools.NewStringParameter("column", "A column name that will be returned from the query.")), 146 | }, 147 | }, 148 | }, 149 | }, 150 | } 151 | for _, tc := range tcs { 152 | t.Run(tc.desc, func(t *testing.T) { 153 | got := struct { 154 | Tools server.ToolConfigs `yaml:"tools"` 155 | }{} 156 | // Parse contents 157 | err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got) 158 | if err != nil { 159 | t.Fatalf("unable to unmarshal: %s", err) 160 | } 161 | if diff := cmp.Diff(tc.want, got.Tools); diff != "" { 162 | t.Fatalf("incorrect parse: diff %v", diff) 163 | } 164 | }) 165 | } 166 | 167 | } 168 | ``` -------------------------------------------------------------------------------- /internal/tools/firebird/firebirdsql/firebirdsql_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 firebirdsql_test 16 | 17 | import ( 18 | "testing" 19 | 20 | yaml "github.com/goccy/go-yaml" 21 | "github.com/google/go-cmp/cmp" 22 | "github.com/googleapis/genai-toolbox/internal/server" 23 | "github.com/googleapis/genai-toolbox/internal/testutils" 24 | "github.com/googleapis/genai-toolbox/internal/tools" 25 | "github.com/googleapis/genai-toolbox/internal/tools/firebird/firebirdsql" 26 | ) 27 | 28 | func TestParseFromYamlFirebird(t *testing.T) { 29 | ctx, err := testutils.ContextWithNewLogger() 30 | if err != nil { 31 | t.Fatalf("unexpected error: %s", err) 32 | } 33 | tcs := []struct { 34 | desc string 35 | in string 36 | want server.ToolConfigs 37 | }{ 38 | { 39 | desc: "basic example", 40 | in: ` 41 | tools: 42 | example_tool: 43 | kind: firebird-sql 44 | source: my-fdb-instance 45 | description: some description 46 | statement: | 47 | SELECT * FROM SQL_STATEMENT; 48 | authRequired: 49 | - my-google-auth-service 50 | - other-auth-service 51 | parameters: 52 | - name: country 53 | type: string 54 | description: some description 55 | authServices: 56 | - name: my-google-auth-service 57 | field: user_id 58 | - name: other-auth-service 59 | field: user_id 60 | `, 61 | want: server.ToolConfigs{ 62 | "example_tool": firebirdsql.Config{ 63 | Name: "example_tool", 64 | Kind: "firebird-sql", 65 | Source: "my-fdb-instance", 66 | Description: "some description", 67 | Statement: "SELECT * FROM SQL_STATEMENT;\n", 68 | AuthRequired: []string{"my-google-auth-service", "other-auth-service"}, 69 | Parameters: []tools.Parameter{ 70 | tools.NewStringParameterWithAuth("country", "some description", 71 | []tools.ParamAuthService{{Name: "my-google-auth-service", Field: "user_id"}, 72 | {Name: "other-auth-service", Field: "user_id"}}), 73 | }, 74 | }, 75 | }, 76 | }, 77 | } 78 | for _, tc := range tcs { 79 | t.Run(tc.desc, func(t *testing.T) { 80 | got := struct { 81 | Tools server.ToolConfigs `yaml:"tools"` 82 | }{} 83 | // Parse contents 84 | err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got) 85 | if err != nil { 86 | t.Fatalf("unable to unmarshal: %s", err) 87 | } 88 | if diff := cmp.Diff(tc.want, got.Tools); diff != "" { 89 | t.Fatalf("incorrect parse: diff %v", diff) 90 | } 91 | }) 92 | } 93 | 94 | } 95 | 96 | func TestParseFromYamlWithTemplateParamsFirebird(t *testing.T) { 97 | ctx, err := testutils.ContextWithNewLogger() 98 | if err != nil { 99 | t.Fatalf("unexpected error: %s", err) 100 | } 101 | tcs := []struct { 102 | desc string 103 | in string 104 | want server.ToolConfigs 105 | }{ 106 | { 107 | desc: "basic example", 108 | in: ` 109 | tools: 110 | example_tool: 111 | kind: firebird-sql 112 | source: my-fdb-instance 113 | description: some description 114 | statement: | 115 | SELECT * FROM SQL_STATEMENT; 116 | parameters: 117 | - name: name 118 | type: string 119 | description: some description 120 | templateParameters: 121 | - name: tableName 122 | type: string 123 | description: The table to select hotels from. 124 | - name: fieldArray 125 | type: array 126 | description: The columns to return for the query. 127 | items: 128 | name: column 129 | type: string 130 | description: A column name that will be returned from the query. 131 | `, 132 | want: server.ToolConfigs{ 133 | "example_tool": firebirdsql.Config{ 134 | Name: "example_tool", 135 | Kind: "firebird-sql", 136 | Source: "my-fdb-instance", 137 | Description: "some description", 138 | Statement: "SELECT * FROM SQL_STATEMENT;\n", 139 | AuthRequired: []string{}, 140 | Parameters: []tools.Parameter{ 141 | tools.NewStringParameter("name", "some description"), 142 | }, 143 | TemplateParameters: []tools.Parameter{ 144 | tools.NewStringParameter("tableName", "The table to select hotels from."), 145 | tools.NewArrayParameter("fieldArray", "The columns to return for the query.", tools.NewStringParameter("column", "A column name that will be returned from the query.")), 146 | }, 147 | }, 148 | }, 149 | }, 150 | } 151 | for _, tc := range tcs { 152 | t.Run(tc.desc, func(t *testing.T) { 153 | got := struct { 154 | Tools server.ToolConfigs `yaml:"tools"` 155 | }{} 156 | // Parse contents 157 | err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got) 158 | if err != nil { 159 | t.Fatalf("unable to unmarshal: %s", err) 160 | } 161 | if diff := cmp.Diff(tc.want, got.Tools); diff != "" { 162 | t.Fatalf("incorrect parse: diff %v", diff) 163 | } 164 | }) 165 | } 166 | 167 | } 168 | ``` -------------------------------------------------------------------------------- /internal/tools/cloudsql/cloudsqllistinstances/cloudsqllistinstances.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 cloudsqllistinstances 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | 21 | "github.com/goccy/go-yaml" 22 | "github.com/googleapis/genai-toolbox/internal/sources" 23 | cloudsqladminsrc "github.com/googleapis/genai-toolbox/internal/sources/cloudsqladmin" 24 | "github.com/googleapis/genai-toolbox/internal/tools" 25 | ) 26 | 27 | const kind string = "cloud-sql-list-instances" 28 | 29 | func init() { 30 | if !tools.Register(kind, newConfig) { 31 | panic(fmt.Sprintf("tool kind %q already registered", kind)) 32 | } 33 | } 34 | 35 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) { 36 | actual := Config{Name: name} 37 | if err := decoder.DecodeContext(ctx, &actual); err != nil { 38 | return nil, err 39 | } 40 | return actual, nil 41 | } 42 | 43 | // Config defines the configuration for the list-instance tool. 44 | type Config struct { 45 | Name string `yaml:"name" validate:"required"` 46 | Kind string `yaml:"kind" validate:"required"` 47 | Source string `yaml:"source" validate:"required"` 48 | Description string `yaml:"description"` 49 | AuthRequired []string `yaml:"authRequired"` 50 | } 51 | 52 | // validate interface 53 | var _ tools.ToolConfig = Config{} 54 | 55 | // ToolConfigKind returns the kind of the tool. 56 | func (cfg Config) ToolConfigKind() string { 57 | return kind 58 | } 59 | 60 | // Initialize initializes the tool from the configuration. 61 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { 62 | rawS, ok := srcs[cfg.Source] 63 | if !ok { 64 | return nil, fmt.Errorf("no source named %q configured", cfg.Source) 65 | } 66 | s, ok := rawS.(*cloudsqladminsrc.Source) 67 | if !ok { 68 | return nil, fmt.Errorf("invalid source for %q tool: source kind must be `cloud-sql-admin`", kind) 69 | } 70 | 71 | allParameters := tools.Parameters{ 72 | tools.NewStringParameter("project", "The project ID"), 73 | } 74 | paramManifest := allParameters.Manifest() 75 | 76 | description := cfg.Description 77 | if description == "" { 78 | description = "Lists all type of Cloud SQL instances for a project." 79 | } 80 | mcpManifest := tools.GetMcpManifest(cfg.Name, description, cfg.AuthRequired, allParameters) 81 | 82 | return Tool{ 83 | Name: cfg.Name, 84 | Kind: kind, 85 | AuthRequired: cfg.AuthRequired, 86 | source: s, 87 | AllParams: allParameters, 88 | manifest: tools.Manifest{Description: description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired}, 89 | mcpManifest: mcpManifest, 90 | }, nil 91 | } 92 | 93 | // Tool represents the list-instance tool. 94 | type Tool struct { 95 | Name string `yaml:"name"` 96 | Kind string `yaml:"kind"` 97 | Description string `yaml:"description"` 98 | AuthRequired []string `yaml:"authRequired"` 99 | 100 | AllParams tools.Parameters `yaml:"allParams"` 101 | source *cloudsqladminsrc.Source 102 | manifest tools.Manifest 103 | mcpManifest tools.McpManifest 104 | } 105 | 106 | // Invoke executes the tool's logic. 107 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { 108 | paramsMap := params.AsMap() 109 | 110 | project, ok := paramsMap["project"].(string) 111 | if !ok { 112 | return nil, fmt.Errorf("missing 'project' parameter") 113 | } 114 | 115 | service, err := t.source.GetService(ctx, string(accessToken)) 116 | if err != nil { 117 | return nil, err 118 | } 119 | 120 | resp, err := service.Instances.List(project).Do() 121 | if err != nil { 122 | return nil, fmt.Errorf("error listing instances: %w", err) 123 | } 124 | 125 | if resp.Items == nil { 126 | return []any{}, nil 127 | } 128 | 129 | type instanceInfo struct { 130 | Name string `json:"name"` 131 | InstanceType string `json:"instanceType"` 132 | } 133 | 134 | var instances []instanceInfo 135 | for _, item := range resp.Items { 136 | instances = append(instances, instanceInfo{ 137 | Name: item.Name, 138 | InstanceType: item.InstanceType, 139 | }) 140 | } 141 | 142 | return instances, nil 143 | } 144 | 145 | // ParseParams parses the parameters for the tool. 146 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { 147 | return tools.ParseParams(t.AllParams, data, claims) 148 | } 149 | 150 | // Manifest returns the tool's manifest. 151 | func (t Tool) Manifest() tools.Manifest { 152 | return t.manifest 153 | } 154 | 155 | // McpManifest returns the tool's MCP manifest. 156 | func (t Tool) McpManifest() tools.McpManifest { 157 | return t.mcpManifest 158 | } 159 | 160 | // Authorized checks if the tool is authorized. 161 | func (t Tool) Authorized(verifiedAuthServices []string) bool { 162 | return true 163 | } 164 | 165 | func (t Tool) RequiresClientAuthorization() bool { 166 | return t.source.UseClientAuthorization() 167 | } 168 | ``` -------------------------------------------------------------------------------- /docs/en/getting-started/quickstart/python/core/quickstart.py: -------------------------------------------------------------------------------- ```python 1 | import asyncio 2 | import os 3 | 4 | from google import genai 5 | from google.genai.types import ( 6 | Content, 7 | FunctionDeclaration, 8 | GenerateContentConfig, 9 | Part, 10 | Tool, 11 | ) 12 | 13 | from toolbox_core import ToolboxClient 14 | 15 | project = os.environ.get("GCP_PROJECT") or "project-id" 16 | 17 | prompt = """ 18 | You're a helpful hotel assistant. You handle hotel searching, booking and 19 | cancellations. When the user searches for a hotel, mention it's name, id, 20 | location and price tier. Always mention hotel id while performing any 21 | searches. This is very important for any operations. For any bookings or 22 | cancellations, please provide the appropriate confirmation. Be sure to 23 | update checkin or checkout dates if mentioned by the user. 24 | Don't ask for confirmations from the user. 25 | """ 26 | 27 | queries = [ 28 | "Find hotels in Basel with Basel in its name.", 29 | "Please book the hotel Hilton Basel for me.", 30 | "This is too expensive. Please cancel it.", 31 | "Please book Hyatt Regency for me", 32 | "My check in dates for my booking would be from April 10, 2024 to April 19, 2024.", 33 | ] 34 | 35 | async def main(): 36 | async with ToolboxClient("http://127.0.0.1:5000") as toolbox_client: 37 | 38 | # The toolbox_tools list contains Python callables (functions/methods) designed for LLM tool-use 39 | # integration. While this example uses Google's genai client, these callables can be adapted for 40 | # various function-calling or agent frameworks. For easier integration with supported frameworks 41 | # (https://github.com/googleapis/mcp-toolbox-python-sdk/tree/main/packages), use the 42 | # provided wrapper packages, which handle framework-specific boilerplate. 43 | toolbox_tools = await toolbox_client.load_toolset("my-toolset") 44 | genai_client = genai.Client( 45 | vertexai=True, project=project, location="us-central1" 46 | ) 47 | 48 | genai_tools = [ 49 | Tool( 50 | function_declarations=[ 51 | FunctionDeclaration.from_callable_with_api_option(callable=tool) 52 | ] 53 | ) 54 | for tool in toolbox_tools 55 | ] 56 | history = [] 57 | for query in queries: 58 | user_prompt_content = Content( 59 | role="user", 60 | parts=[Part.from_text(text=query)], 61 | ) 62 | history.append(user_prompt_content) 63 | 64 | response = genai_client.models.generate_content( 65 | model="gemini-2.0-flash-001", 66 | contents=history, 67 | config=GenerateContentConfig( 68 | system_instruction=prompt, 69 | tools=genai_tools, 70 | ), 71 | ) 72 | history.append(response.candidates[0].content) 73 | function_response_parts = [] 74 | 75 | if response.function_calls: 76 | for function_call in response.function_calls: 77 | fn_name = function_call.name 78 | # The tools are sorted alphabetically 79 | if fn_name == "search-hotels-by-name": 80 | function_result = await toolbox_tools[3](**function_call.args) 81 | elif fn_name == "search-hotels-by-location": 82 | function_result = await toolbox_tools[2](**function_call.args) 83 | elif fn_name == "book-hotel": 84 | function_result = await toolbox_tools[0](**function_call.args) 85 | elif fn_name == "update-hotel": 86 | function_result = await toolbox_tools[4](**function_call.args) 87 | elif fn_name == "cancel-hotel": 88 | function_result = await toolbox_tools[1](**function_call.args) 89 | else: 90 | raise ValueError(f"Function name {fn_name} not present.") 91 | 92 | function_response = {"result": function_result} 93 | function_response_part = Part.from_function_response( 94 | name=function_call.name, 95 | response=function_response, 96 | ) 97 | function_response_parts.append(function_response_part) 98 | 99 | if function_response_parts: 100 | tool_response_content = Content(role="tool", parts=function_response_parts) 101 | history.append(tool_response_content) 102 | 103 | response2 = genai_client.models.generate_content( 104 | model="gemini-2.0-flash-001", 105 | contents=history, 106 | config=GenerateContentConfig( 107 | tools=genai_tools, 108 | ), 109 | ) 110 | final_model_response_content = response2.candidates[0].content 111 | history.append(final_model_response_content) 112 | print(response2.text) 113 | else: 114 | print(response.text) 115 | 116 | asyncio.run(main()) ``` -------------------------------------------------------------------------------- /docs/en/resources/tools/yuagbytedb/yugabytedb-sql.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: "yugabytedb-sql" 3 | type: docs 4 | weight: 1 5 | description: > 6 | A "yugabytedb-sql" tool executes a pre-defined SQL statement against a YugabyteDB 7 | database. 8 | --- 9 | 10 | ## About 11 | 12 | A `yugabytedb-sql` tool executes a pre-defined SQL statement against a YugabyteDB 13 | database. 14 | 15 | The specified SQL statement is executed as a prepared statement, 16 | and specified parameters will inserted according to their position: e.g. `1` 17 | will be the first parameter specified, `$@` will be the second parameter, and so 18 | on. If template parameters are included, they will be resolved before execution 19 | of the prepared statement. 20 | 21 | ## Example 22 | 23 | > **Note:** This tool uses parameterized queries to prevent SQL injections. 24 | > Query parameters can be used as substitutes for arbitrary expressions. 25 | > Parameters cannot be used as substitutes for identifiers, column names, table 26 | > names, or other parts of the query. 27 | 28 | ```yaml 29 | tools: 30 | search_flights_by_number: 31 | kind: yugabytedb-sql 32 | source: my-yb-instance 33 | statement: | 34 | SELECT * FROM flights 35 | WHERE airline = $1 36 | AND flight_number = $2 37 | LIMIT 10 38 | description: | 39 | Use this tool to get information for a specific flight. 40 | Takes an airline code and flight number and returns info on the flight. 41 | Do NOT use this tool with a flight id. Do NOT guess an airline code or flight number. 42 | A airline code is a code for an airline service consisting of two-character 43 | airline designator and followed by flight number, which is 1 to 4 digit number. 44 | For example, if given CY 0123, the airline is "CY", and flight_number is "123". 45 | Another example for this is DL 1234, the airline is "DL", and flight_number is "1234". 46 | If the tool returns more than one option choose the date closes to today. 47 | Example: 48 | {{ 49 | "airline": "CY", 50 | "flight_number": "888", 51 | }} 52 | Example: 53 | {{ 54 | "airline": "DL", 55 | "flight_number": "1234", 56 | }} 57 | parameters: 58 | - name: airline 59 | type: string 60 | description: Airline unique 2 letter identifier 61 | - name: flight_number 62 | type: string 63 | description: 1 to 4 digit number 64 | ``` 65 | 66 | ### Example with Template Parameters 67 | 68 | > **Note:** This tool allows direct modifications to the SQL statement, 69 | > including identifiers, column names, and table names. **This makes it more 70 | > vulnerable to SQL injections**. Using basic parameters only (see above) is 71 | > recommended for performance and safety reasons. For more details, please check 72 | > [templateParameters](_index#template-parameters). 73 | 74 | ```yaml 75 | tools: 76 | list_table: 77 | kind: yugabytedb-sql 78 | source: my-yb-instance 79 | statement: | 80 | SELECT * FROM {{.tableName}} 81 | description: | 82 | Use this tool to list all information from a specific table. 83 | Example: 84 | {{ 85 | "tableName": "flights", 86 | }} 87 | templateParameters: 88 | - name: tableName 89 | type: string 90 | description: Table to select from 91 | ``` 92 | 93 | ## Reference 94 | 95 | | **field** | **type** | **required** | **description** | 96 | |--------------------|:------------------------------------------------:|:------------:|--------------------------------------------------------------------------------------------------------------------------------------------| 97 | | kind | string | true | Must be "yugabytedb-sql". | 98 | | source | string | true | Name of the source the SQL should execute on. | 99 | | description | string | true | Description of the tool that is passed to the LLM. | 100 | | statement | string | true | SQL statement to execute on. | 101 | | parameters | [parameters](_index#specifying-parameters) | false | List of [parameters](_index#specifying-parameters) that will be inserted into the SQL statement. | 102 | | templateParameters | [templateParameters](_index#template-parameters) | false | List of [templateParameters](_index#template-parameters) that will be inserted into the SQL statement before executing prepared statement. | 103 | ``` -------------------------------------------------------------------------------- /internal/tools/clickhouse/clickhouselisttables/clickhouselisttables.go: -------------------------------------------------------------------------------- ```go 1 | // Copyright 2025 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package clickhouse 16 | 17 | import ( 18 | "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/tools" 25 | ) 26 | 27 | type compatibleSource interface { 28 | ClickHousePool() *sql.DB 29 | } 30 | 31 | var compatibleSources = []string{"clickhouse"} 32 | 33 | const listTablesKind string = "clickhouse-list-tables" 34 | const databaseKey string = "database" 35 | 36 | func init() { 37 | if !tools.Register(listTablesKind, newListTablesConfig) { 38 | panic(fmt.Sprintf("tool kind %q already registered", listTablesKind)) 39 | } 40 | } 41 | 42 | func newListTablesConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) { 43 | actual := Config{Name: name} 44 | if err := decoder.DecodeContext(ctx, &actual); err != nil { 45 | return nil, err 46 | } 47 | return actual, nil 48 | } 49 | 50 | type Config struct { 51 | Name string `yaml:"name" validate:"required"` 52 | Kind string `yaml:"kind" validate:"required"` 53 | Source string `yaml:"source" validate:"required"` 54 | Description string `yaml:"description" validate:"required"` 55 | AuthRequired []string `yaml:"authRequired"` 56 | Parameters tools.Parameters `yaml:"parameters"` 57 | } 58 | 59 | var _ tools.ToolConfig = Config{} 60 | 61 | func (cfg Config) ToolConfigKind() string { 62 | return listTablesKind 63 | } 64 | 65 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { 66 | rawS, ok := srcs[cfg.Source] 67 | if !ok { 68 | return nil, fmt.Errorf("no source named %q configured", cfg.Source) 69 | } 70 | 71 | s, ok := rawS.(compatibleSource) 72 | if !ok { 73 | return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", listTablesKind, compatibleSources) 74 | } 75 | 76 | databaseParameter := tools.NewStringParameter(databaseKey, "The database to list tables from.") 77 | parameters := tools.Parameters{databaseParameter} 78 | 79 | allParameters, paramManifest, _ := tools.ProcessParameters(nil, parameters) 80 | mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, allParameters) 81 | 82 | t := Tool{ 83 | Name: cfg.Name, 84 | Kind: listTablesKind, 85 | Parameters: parameters, 86 | AllParams: allParameters, 87 | AuthRequired: cfg.AuthRequired, 88 | Pool: s.ClickHousePool(), 89 | manifest: tools.Manifest{Description: cfg.Description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired}, 90 | mcpManifest: mcpManifest, 91 | } 92 | return t, nil 93 | } 94 | 95 | var _ tools.Tool = Tool{} 96 | 97 | type Tool struct { 98 | Name string `yaml:"name"` 99 | Kind string `yaml:"kind"` 100 | AuthRequired []string `yaml:"authRequired"` 101 | Parameters tools.Parameters `yaml:"parameters"` 102 | AllParams tools.Parameters `yaml:"allParams"` 103 | 104 | Pool *sql.DB 105 | manifest tools.Manifest 106 | mcpManifest tools.McpManifest 107 | } 108 | 109 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, token tools.AccessToken) (any, error) { 110 | mapParams := params.AsMap() 111 | database, ok := mapParams[databaseKey].(string) 112 | if !ok { 113 | return nil, fmt.Errorf("invalid or missing '%s' parameter; expected a string", databaseKey) 114 | } 115 | 116 | // Query to list all tables in the specified database 117 | query := fmt.Sprintf("SHOW TABLES FROM %s", database) 118 | 119 | results, err := t.Pool.QueryContext(ctx, query) 120 | if err != nil { 121 | return nil, fmt.Errorf("unable to execute query: %w", err) 122 | } 123 | defer results.Close() 124 | 125 | var tables []map[string]any 126 | for results.Next() { 127 | var tableName string 128 | err := results.Scan(&tableName) 129 | if err != nil { 130 | return nil, fmt.Errorf("unable to parse row: %w", err) 131 | } 132 | tables = append(tables, map[string]any{ 133 | "name": tableName, 134 | "database": database, 135 | }) 136 | } 137 | 138 | if err := results.Err(); err != nil { 139 | return nil, fmt.Errorf("errors encountered by results.Scan: %w", err) 140 | } 141 | 142 | return tables, nil 143 | } 144 | 145 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { 146 | return tools.ParseParams(t.AllParams, data, claims) 147 | } 148 | 149 | func (t Tool) Manifest() tools.Manifest { 150 | return t.manifest 151 | } 152 | 153 | func (t Tool) McpManifest() tools.McpManifest { 154 | return t.mcpManifest 155 | } 156 | 157 | func (t Tool) Authorized(verifiedAuthServices []string) bool { 158 | return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices) 159 | } 160 | 161 | func (t Tool) RequiresClientAuthorization() bool { 162 | return false 163 | } 164 | ``` -------------------------------------------------------------------------------- /internal/server/server_test.go: -------------------------------------------------------------------------------- ```go 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package server_test 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "io" 21 | "net/http" 22 | "os" 23 | "strings" 24 | "testing" 25 | 26 | "github.com/google/go-cmp/cmp" 27 | "github.com/googleapis/genai-toolbox/internal/auth" 28 | "github.com/googleapis/genai-toolbox/internal/log" 29 | "github.com/googleapis/genai-toolbox/internal/server" 30 | "github.com/googleapis/genai-toolbox/internal/sources" 31 | "github.com/googleapis/genai-toolbox/internal/sources/alloydbpg" 32 | "github.com/googleapis/genai-toolbox/internal/telemetry" 33 | "github.com/googleapis/genai-toolbox/internal/testutils" 34 | "github.com/googleapis/genai-toolbox/internal/tools" 35 | "github.com/googleapis/genai-toolbox/internal/util" 36 | ) 37 | 38 | func TestServe(t *testing.T) { 39 | ctx, cancel := context.WithCancel(context.Background()) 40 | defer cancel() 41 | 42 | addr, port := "127.0.0.1", 5000 43 | cfg := server.ServerConfig{ 44 | Version: "0.0.0", 45 | Address: addr, 46 | Port: port, 47 | } 48 | 49 | otelShutdown, err := telemetry.SetupOTel(ctx, "0.0.0", "", false, "toolbox") 50 | if err != nil { 51 | t.Fatalf("unexpected error: %s", err) 52 | } 53 | defer func() { 54 | err := otelShutdown(ctx) 55 | if err != nil { 56 | t.Fatalf("unexpected error: %s", err) 57 | } 58 | }() 59 | 60 | testLogger, err := log.NewStdLogger(os.Stdout, os.Stderr, "info") 61 | if err != nil { 62 | t.Fatalf("unexpected error: %s", err) 63 | } 64 | ctx = util.WithLogger(ctx, testLogger) 65 | 66 | instrumentation, err := telemetry.CreateTelemetryInstrumentation(cfg.Version) 67 | if err != nil { 68 | t.Fatalf("unexpected error: %s", err) 69 | } 70 | 71 | ctx = util.WithInstrumentation(ctx, instrumentation) 72 | 73 | s, err := server.NewServer(ctx, cfg) 74 | if err != nil { 75 | t.Fatalf("unable to initialize server: %v", err) 76 | } 77 | 78 | err = s.Listen(ctx) 79 | if err != nil { 80 | t.Fatalf("unable to start server: %v", err) 81 | } 82 | 83 | // start server in background 84 | errCh := make(chan error) 85 | go func() { 86 | defer close(errCh) 87 | 88 | err = s.Serve(ctx) 89 | if err != nil { 90 | errCh <- err 91 | } 92 | }() 93 | 94 | url := fmt.Sprintf("http://%s:%d/", addr, port) 95 | resp, err := http.Get(url) 96 | if err != nil { 97 | t.Fatalf("error when sending a request: %s", err) 98 | } 99 | defer resp.Body.Close() 100 | if resp.StatusCode != 200 { 101 | t.Fatalf("response status code is not 200") 102 | } 103 | raw, err := io.ReadAll(resp.Body) 104 | if err != nil { 105 | t.Fatalf("error reading from request body: %s", err) 106 | } 107 | if got := string(raw); strings.Contains(got, "0.0.0") { 108 | t.Fatalf("version missing from output: %q", got) 109 | } 110 | } 111 | 112 | func TestUpdateServer(t *testing.T) { 113 | ctx, err := testutils.ContextWithNewLogger() 114 | if err != nil { 115 | t.Fatalf("error setting up logger: %s", err) 116 | } 117 | 118 | addr, port := "127.0.0.1", 5000 119 | cfg := server.ServerConfig{ 120 | Version: "0.0.0", 121 | Address: addr, 122 | Port: port, 123 | } 124 | 125 | instrumentation, err := telemetry.CreateTelemetryInstrumentation(cfg.Version) 126 | if err != nil { 127 | t.Fatalf("unexpected error: %s", err) 128 | } 129 | 130 | ctx = util.WithInstrumentation(ctx, instrumentation) 131 | 132 | s, err := server.NewServer(ctx, cfg) 133 | if err != nil { 134 | t.Fatalf("error setting up server: %s", err) 135 | } 136 | 137 | newSources := map[string]sources.Source{ 138 | "example-source": &alloydbpg.Source{ 139 | Name: "example-alloydb-source", 140 | Kind: "alloydb-postgres", 141 | }, 142 | } 143 | newAuth := map[string]auth.AuthService{"example-auth": nil} 144 | newTools := map[string]tools.Tool{"example-tool": nil} 145 | newToolsets := map[string]tools.Toolset{ 146 | "example-toolset": { 147 | Name: "example-toolset", Tools: []*tools.Tool{}, 148 | }, 149 | } 150 | s.ResourceMgr.SetResources(newSources, newAuth, newTools, newToolsets) 151 | if err != nil { 152 | t.Errorf("error updating server: %s", err) 153 | } 154 | 155 | gotSource, _ := s.ResourceMgr.GetSource("example-source") 156 | if diff := cmp.Diff(gotSource, newSources["example-source"]); diff != "" { 157 | t.Errorf("error updating server, sources (-want +got):\n%s", diff) 158 | } 159 | 160 | gotAuthService, _ := s.ResourceMgr.GetAuthService("example-auth") 161 | if diff := cmp.Diff(gotAuthService, newAuth["example-auth"]); diff != "" { 162 | t.Errorf("error updating server, authServices (-want +got):\n%s", diff) 163 | } 164 | 165 | gotTool, _ := s.ResourceMgr.GetTool("example-tool") 166 | if diff := cmp.Diff(gotTool, newTools["example-tool"]); diff != "" { 167 | t.Errorf("error updating server, tools (-want +got):\n%s", diff) 168 | } 169 | 170 | gotToolset, _ := s.ResourceMgr.GetToolset("example-toolset") 171 | if diff := cmp.Diff(gotToolset, newToolsets["example-toolset"]); diff != "" { 172 | t.Errorf("error updating server, toolset (-want +got):\n%s", diff) 173 | } 174 | } 175 | ``` -------------------------------------------------------------------------------- /docs/en/resources/sources/oracle.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: "Oracle" 3 | type: docs 4 | weight: 1 5 | description: > 6 | Oracle Database is a widely-used relational database management system. 7 | --- 8 | 9 | ## About 10 | 11 | [Oracle Database][oracle-docs] is a multi-model database management system produced and marketed by Oracle Corporation. It is commonly used for running online transaction processing (OLTP), data warehousing (DW), and mixed (OLTP & DW) database workloads. 12 | 13 | [oracle-docs]: https://www.oracle.com/database/ 14 | 15 | ## Available Tools 16 | 17 | - [`oracle-sql`](../tools/oracle/oracle-sql.md) 18 | Execute pre-defined prepared SQL queries in Oracle. 19 | 20 | - [`oracle-execute-sql`](../tools/oracle/oracle-execute-sql.md) 21 | Run parameterized SQL queries in Oracle. 22 | 23 | ## Requirements 24 | 25 | ### Database User 26 | 27 | This source uses standard authentication. You will need to [create an Oracle user][oracle-users] to log in to the database with the necessary permissions. 28 | 29 | [oracle-users]: 30 | https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/CREATE-USER.html 31 | 32 | ## Connection Methods 33 | 34 | You can configure the connection to your Oracle database using one of the following three methods. **You should only use one method** in your source configuration. 35 | 36 | ### Basic Connection (Host/Port/Service Name) 37 | 38 | This is the most straightforward method, where you provide the connection details as separate fields: 39 | 40 | - `host`: The IP address or hostname of the database server. 41 | - `port`: The port number the Oracle listener is running on (typically 1521). 42 | - `serviceName`: The service name for the database instance you wish to connect to. 43 | 44 | ### Connection String 45 | 46 | As an alternative, you can provide all the connection details in a single `connectionString`. This is a convenient way to consolidate the connection information. The typical format is `hostname:port/servicename`. 47 | 48 | ### TNS Alias 49 | 50 | For environments that use a `tnsnames.ora` configuration file, you can connect using a TNS (Transparent Network Substrate) alias. 51 | 52 | - `tnsAlias`: Specify the alias name defined in your `tnsnames.ora` file. 53 | - `tnsAdmin` (Optional): If your configuration file is not in a standard location, you can use this field to provide the path to the directory containing it. This setting will override the `TNS_ADMIN` environment variable. 54 | 55 | ## Example 56 | 57 | ```yaml 58 | sources: 59 | my-oracle-source: 60 | kind: oracle 61 | # --- Choose one connection method --- 62 | # 1. Host, Port, and Service Name 63 | host: 127.0.0.1 64 | port: 1521 65 | serviceName: XEPDB1 66 | 67 | # 2. Direct Connection String 68 | connectionString: "127.0.0.1:1521/XEPDB1" 69 | 70 | # 3. TNS Alias (requires tnsnames.ora) 71 | tnsAlias: "MY_DB_ALIAS" 72 | tnsAdmin: "/opt/oracle/network/admin" # Optional: overrides TNS_ADMIN env var 73 | 74 | user: ${USER_NAME} 75 | password: ${PASSWORD} 76 | 77 | ``` 78 | 79 | {{< notice tip >}} 80 | Use environment variable replacement with the format ${ENV_NAME} 81 | instead of hardcoding your secrets into the configuration file. 82 | {{< /notice >}} 83 | 84 | ## Reference 85 | 86 | | **field** | **type** | **required** | **description** | 87 | |------------------|:--------:|:------------:|-----------------------------------------------------------------------------------------------------------------------------| 88 | | kind | string | true | Must be "oracle". | 89 | | user | string | true | Name of the Oracle user to connect as (e.g. "my-oracle-user"). | 90 | | password | string | true | Password of the Oracle user (e.g. "my-password"). | 91 | | host | string | false | IP address or hostname to connect to (e.g. "127.0.0.1"). Required if not using `connectionString` or `tnsAlias`. | 92 | | port | integer | false | Port to connect to (e.g. "1521"). Required if not using `connectionString` or `tnsAlias`. | 93 | | serviceName | string | false | The Oracle service name of the database to connect to. Required if not using `connectionString` or `tnsAlias`. | 94 | | connectionString | string | false | A direct connection string (e.g. "hostname:port/servicename"). Use as an alternative to `host`, `port`, and `serviceName`. | 95 | | tnsAlias | string | false | A TNS alias from a `tnsnames.ora` file. Use as an alternative to `host`/`port` or `connectionString`. | 96 | | tnsAdmin | string | false | Path to the directory containing the `tnsnames.ora` file. This overrides the `TNS_ADMIN` environment variable if it is set. | 97 | ``` -------------------------------------------------------------------------------- /docs/en/getting-started/local_quickstart.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: "Python Quickstart (Local)" 3 | type: docs 4 | weight: 2 5 | description: > 6 | How to get started running Toolbox locally with [Python](https://github.com/googleapis/mcp-toolbox-sdk-python), PostgreSQL, and [Agent Development Kit](https://google.github.io/adk-docs/), 7 | [LangGraph](https://www.langchain.com/langgraph), [LlamaIndex](https://www.llamaindex.ai/) or [GoogleGenAI](https://pypi.org/project/google-genai/). 8 | --- 9 | 10 | [](https://colab.research.google.com/github/googleapis/genai-toolbox/blob/main/docs/en/getting-started/colab_quickstart.ipynb) 11 | 12 | ## Before you begin 13 | 14 | This guide assumes you have already done the following: 15 | 16 | 1. Installed [Python 3.9+][install-python] (including [pip][install-pip] and 17 | your preferred virtual environment tool for managing dependencies e.g. 18 | [venv][install-venv]). 19 | 1. Installed [PostgreSQL 16+ and the `psql` client][install-postgres]. 20 | 21 | [install-python]: https://wiki.python.org/moin/BeginnersGuide/Download 22 | [install-pip]: https://pip.pypa.io/en/stable/installation/ 23 | [install-venv]: 24 | https://packaging.python.org/en/latest/tutorials/installing-packages/#creating-virtual-environments 25 | [install-postgres]: https://www.postgresql.org/download/ 26 | 27 | ### Cloud Setup (Optional) 28 | {{< regionInclude "quickstart/shared/cloud_setup.md" "cloud_setup" >}} 29 | 30 | ## Step 1: Set up your database 31 | {{< regionInclude "quickstart/shared/database_setup.md" "database_setup" >}} 32 | 33 | ## Step 2: Install and configure Toolbox 34 | {{< regionInclude "quickstart/shared/configure_toolbox.md" "configure_toolbox" >}} 35 | 36 | ## Step 3: Connect your agent to Toolbox 37 | 38 | In this section, we will write and run an agent that will load the Tools 39 | from Toolbox. 40 | 41 | {{< notice tip>}} 42 | If you prefer to experiment within a Google Colab environment, you can connect 43 | to a [local 44 | runtime](https://research.google.com/colaboratory/local-runtimes.html). 45 | {{< /notice >}} 46 | 47 | 1. In a new terminal, install the SDK package. 48 | 49 | {{< tabpane persist=header >}} 50 | {{< tab header="ADK" lang="bash" >}} 51 | 52 | pip install toolbox-core 53 | {{< /tab >}} 54 | {{< tab header="Langchain" lang="bash" >}} 55 | 56 | pip install toolbox-langchain 57 | {{< /tab >}} 58 | {{< tab header="LlamaIndex" lang="bash" >}} 59 | 60 | pip install toolbox-llamaindex 61 | {{< /tab >}} 62 | {{< tab header="Core" lang="bash" >}} 63 | 64 | pip install toolbox-core 65 | {{< /tab >}} 66 | {{< /tabpane >}} 67 | 68 | 1. Install other required dependencies: 69 | 70 | {{< tabpane persist=header >}} 71 | {{< tab header="ADK" lang="bash" >}} 72 | 73 | pip install google-adk 74 | {{< /tab >}} 75 | {{< tab header="Langchain" lang="bash" >}} 76 | 77 | # TODO(developer): replace with correct package if needed 78 | 79 | pip install langgraph langchain-google-vertexai 80 | 81 | # pip install langchain-google-genai 82 | 83 | # pip install langchain-anthropic 84 | 85 | {{< /tab >}} 86 | {{< tab header="LlamaIndex" lang="bash" >}} 87 | 88 | # TODO(developer): replace with correct package if needed 89 | 90 | pip install llama-index-llms-google-genai 91 | 92 | # pip install llama-index-llms-anthropic 93 | 94 | {{< /tab >}} 95 | {{< tab header="Core" lang="bash" >}} 96 | 97 | pip install google-genai 98 | {{< /tab >}} 99 | {{< /tabpane >}} 100 | 101 | 1. Create a new file named `hotel_agent.py` and copy the following 102 | code to create an agent: 103 | {{< tabpane persist=header >}} 104 | {{< tab header="ADK" lang="python" >}} 105 | 106 | {{< include "quickstart/python/adk/quickstart.py" >}} 107 | 108 | {{< /tab >}} 109 | {{< tab header="LangChain" lang="python" >}} 110 | 111 | {{< include "quickstart/python/langchain/quickstart.py" >}} 112 | 113 | {{< /tab >}} 114 | {{< tab header="LlamaIndex" lang="python" >}} 115 | 116 | {{< include "quickstart/python/llamaindex/quickstart.py" >}} 117 | 118 | {{< /tab >}} 119 | {{< tab header="Core" lang="python" >}} 120 | 121 | {{< include "quickstart/python/core/quickstart.py" >}} 122 | 123 | {{< /tab >}} 124 | {{< /tabpane >}} 125 | 126 | {{< tabpane text=true persist=header >}} 127 | {{% tab header="ADK" lang="en" %}} 128 | To learn more about Agent Development Kit, check out the [ADK 129 | documentation.](https://google.github.io/adk-docs/) 130 | {{% /tab %}} 131 | {{% tab header="Langchain" lang="en" %}} 132 | To learn more about Agents in LangChain, check out the [LangGraph Agent 133 | documentation.](https://langchain-ai.github.io/langgraph/reference/prebuilt/#langgraph.prebuilt.chat_agent_executor.create_react_agent) 134 | {{% /tab %}} 135 | {{% tab header="LlamaIndex" lang="en" %}} 136 | To learn more about Agents in LlamaIndex, check out the [LlamaIndex 137 | AgentWorkflow 138 | documentation.](https://docs.llamaindex.ai/en/stable/examples/agent/agent_workflow_basic/) 139 | {{% /tab %}} 140 | {{% tab header="Core" lang="en" %}} 141 | To learn more about tool calling with Google GenAI, check out the 142 | [Google GenAI 143 | Documentation](https://github.com/googleapis/python-genai?tab=readme-ov-file#manually-declare-and-invoke-a-function-for-function-calling). 144 | {{% /tab %}} 145 | {{< /tabpane >}} 146 | 147 | 1. Run your agent, and observe the results: 148 | 149 | ```sh 150 | python hotel_agent.py 151 | ``` 152 | 153 | {{< notice info >}} 154 | For more information, visit the [Python SDK 155 | repo](https://github.com/googleapis/mcp-toolbox-sdk-python). 156 | {{</ notice >}} 157 | ``` -------------------------------------------------------------------------------- /docs/en/resources/tools/mysql/mysql-sql.md: -------------------------------------------------------------------------------- ```markdown 1 | --- 2 | title: "mysql-sql" 3 | type: docs 4 | weight: 1 5 | description: > 6 | A "mysql-sql" tool executes a pre-defined SQL statement against a MySQL 7 | database. 8 | aliases: 9 | - /resources/tools/mysql-sql 10 | --- 11 | 12 | ## About 13 | 14 | A `mysql-sql` tool executes a pre-defined SQL statement against a MySQL 15 | database. It's compatible with any of the following sources: 16 | 17 | - [cloud-sql-mysql](../../sources/cloud-sql-mysql.md) 18 | - [mysql](../../sources/mysql.md) 19 | 20 | The specified SQL statement is executed as a [prepared statement][mysql-prepare], 21 | and expects parameters in the SQL query to be in the form of placeholders `?`. 22 | 23 | [mysql-prepare]: https://dev.mysql.com/doc/refman/8.4/en/sql-prepared-statements.html 24 | 25 | ## Example 26 | 27 | > **Note:** This tool uses parameterized queries to prevent SQL injections. 28 | > Query parameters can be used as substitutes for arbitrary expressions. 29 | > Parameters cannot be used as substitutes for identifiers, column names, table 30 | > names, or other parts of the query. 31 | 32 | ```yaml 33 | tools: 34 | search_flights_by_number: 35 | kind: mysql-sql 36 | source: my-mysql-instance 37 | statement: | 38 | SELECT * FROM flights 39 | WHERE airline = ? 40 | AND flight_number = ? 41 | LIMIT 10 42 | description: | 43 | Use this tool to get information for a specific flight. 44 | Takes an airline code and flight number and returns info on the flight. 45 | Do NOT use this tool with a flight id. Do NOT guess an airline code or flight number. 46 | A airline code is a code for an airline service consisting of two-character 47 | airline designator and followed by flight number, which is 1 to 4 digit number. 48 | For example, if given CY 0123, the airline is "CY", and flight_number is "123". 49 | Another example for this is DL 1234, the airline is "DL", and flight_number is "1234". 50 | If the tool returns more than one option choose the date closes to today. 51 | Example: 52 | {{ 53 | "airline": "CY", 54 | "flight_number": "888", 55 | }} 56 | Example: 57 | {{ 58 | "airline": "DL", 59 | "flight_number": "1234", 60 | }} 61 | parameters: 62 | - name: airline 63 | type: string 64 | description: Airline unique 2 letter identifier 65 | - name: flight_number 66 | type: string 67 | description: 1 to 4 digit number 68 | ``` 69 | 70 | ### Example with Template Parameters 71 | 72 | > **Note:** This tool allows direct modifications to the SQL statement, 73 | > including identifiers, column names, and table names. **This makes it more 74 | > vulnerable to SQL injections**. Using basic parameters only (see above) is 75 | > recommended for performance and safety reasons. For more details, please check 76 | > [templateParameters](..#template-parameters). 77 | 78 | ```yaml 79 | tools: 80 | list_table: 81 | kind: mysql-sql 82 | source: my-mysql-instance 83 | statement: | 84 | SELECT * FROM {{.tableName}}; 85 | description: | 86 | Use this tool to list all information from a specific table. 87 | Example: 88 | {{ 89 | "tableName": "flights", 90 | }} 91 | templateParameters: 92 | - name: tableName 93 | type: string 94 | description: Table to select from 95 | ``` 96 | 97 | ## Reference 98 | 99 | | **field** | **type** | **required** | **description** | 100 | |--------------------|:------------------------------------------------:|:------------:|--------------------------------------------------------------------------------------------------------------------------------------------| 101 | | kind | string | true | Must be "mysql-sql". | 102 | | source | string | true | Name of the source the SQL should execute on. | 103 | | description | string | true | Description of the tool that is passed to the LLM. | 104 | | statement | string | true | SQL statement to execute on. | 105 | | parameters | [parameters](../#specifying-parameters) | false | List of [parameters](../#specifying-parameters) that will be inserted into the SQL statement. | 106 | | templateParameters | [templateParameters](..#template-parameters) | false | List of [templateParameters](..#template-parameters) that will be inserted into the SQL statement before executing prepared statement. | 107 | ``` -------------------------------------------------------------------------------- /internal/tools/alloydb/alloydblistclusters/alloydblistclusters.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 alloydblistclusters 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | 21 | yaml "github.com/goccy/go-yaml" 22 | "github.com/googleapis/genai-toolbox/internal/sources" 23 | alloydbadmin "github.com/googleapis/genai-toolbox/internal/sources/alloydbadmin" 24 | "github.com/googleapis/genai-toolbox/internal/tools" 25 | ) 26 | 27 | const kind string = "alloydb-list-clusters" 28 | 29 | func init() { 30 | if !tools.Register(kind, newConfig) { 31 | panic(fmt.Sprintf("tool kind %q already registered", kind)) 32 | } 33 | } 34 | 35 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) { 36 | actual := Config{Name: name} 37 | if err := decoder.DecodeContext(ctx, &actual); err != nil { 38 | return nil, err 39 | } 40 | return actual, nil 41 | } 42 | 43 | // Configuration for the list-clusters tool. 44 | type Config struct { 45 | Name string `yaml:"name" validate:"required"` 46 | Kind string `yaml:"kind" validate:"required"` 47 | Source string `yaml:"source" validate:"required"` 48 | Description string `yaml:"description"` 49 | AuthRequired []string `yaml:"authRequired"` 50 | BaseURL string `yaml:"baseURL"` 51 | } 52 | 53 | // validate interface 54 | var _ tools.ToolConfig = Config{} 55 | 56 | // ToolConfigKind returns the kind of the tool. 57 | func (cfg Config) ToolConfigKind() string { 58 | return kind 59 | } 60 | 61 | // Initialize initializes the tool from the configuration. 62 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { 63 | rawS, ok := srcs[cfg.Source] 64 | if !ok { 65 | return nil, fmt.Errorf("source %q not found", cfg.Source) 66 | } 67 | 68 | s, ok := rawS.(*alloydbadmin.Source) 69 | if !ok { 70 | return nil, fmt.Errorf("invalid source for %q tool: source kind must be `%s`", kind, alloydbadmin.SourceKind) 71 | } 72 | 73 | allParameters := tools.Parameters{ 74 | tools.NewStringParameter("project", "The GCP project ID to list clusters for."), 75 | tools.NewStringParameterWithDefault("location", "-", "Optional: The location to list clusters in (e.g., 'us-central1'). Use '-' to list clusters across all locations.(Default: '-')"), 76 | } 77 | paramManifest := allParameters.Manifest() 78 | 79 | description := cfg.Description 80 | if description == "" { 81 | description = "Lists all AlloyDB clusters in a given project and location." 82 | } 83 | mcpManifest := tools.GetMcpManifest(cfg.Name, description, cfg.AuthRequired, allParameters) 84 | 85 | return Tool{ 86 | Name: cfg.Name, 87 | Kind: kind, 88 | Source: s, 89 | AllParams: allParameters, 90 | manifest: tools.Manifest{Description: description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired}, 91 | mcpManifest: mcpManifest, 92 | }, nil 93 | } 94 | 95 | // Tool represents the list-clusters tool. 96 | type Tool struct { 97 | Name string `yaml:"name"` 98 | Kind string `yaml:"kind"` 99 | Description string `yaml:"description"` 100 | 101 | Source *alloydbadmin.Source 102 | AllParams tools.Parameters `yaml:"allParams"` 103 | 104 | manifest tools.Manifest 105 | mcpManifest tools.McpManifest 106 | } 107 | 108 | // Invoke executes the tool's logic. 109 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { 110 | paramsMap := params.AsMap() 111 | 112 | project, ok := paramsMap["project"].(string) 113 | if !ok { 114 | return nil, fmt.Errorf("invalid or missing 'project' parameter; expected a string") 115 | } 116 | location, ok := paramsMap["location"].(string) 117 | if !ok { 118 | return nil, fmt.Errorf("invalid 'location' parameter; expected a string") 119 | } 120 | 121 | service, err := t.Source.GetService(ctx, string(accessToken)) 122 | if err != nil { 123 | return nil, err 124 | } 125 | 126 | urlString := fmt.Sprintf("projects/%s/locations/%s", project, location) 127 | 128 | resp, err := service.Projects.Locations.Clusters.List(urlString).Do() 129 | if err != nil { 130 | return nil, fmt.Errorf("error listing AlloyDB clusters: %w", err) 131 | } 132 | 133 | return resp, nil 134 | } 135 | 136 | // ParseParams parses the parameters for the tool. 137 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { 138 | return tools.ParseParams(t.AllParams, data, claims) 139 | } 140 | 141 | // Manifest returns the tool's manifest. 142 | func (t Tool) Manifest() tools.Manifest { 143 | return t.manifest 144 | } 145 | 146 | // McpManifest returns the tool's MCP manifest. 147 | func (t Tool) McpManifest() tools.McpManifest { 148 | return t.mcpManifest 149 | } 150 | 151 | // Authorized checks if the tool is authorized. 152 | func (t Tool) Authorized(verifiedAuthServices []string) bool { 153 | return true 154 | } 155 | 156 | func (t Tool) RequiresClientAuthorization() bool { 157 | return t.Source.UseClientAuthorization() 158 | } 159 | ``` -------------------------------------------------------------------------------- /internal/tools/neo4j/neo4jschema/cache/cache_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 cache 16 | 17 | import ( 18 | "sync" 19 | "testing" 20 | "time" 21 | ) 22 | 23 | // TestCache_SetAndGet verifies the basic functionality of setting a value 24 | // and immediately retrieving it. 25 | func TestCache_SetAndGet(t *testing.T) { 26 | cache := NewCache() 27 | defer cache.Stop() 28 | 29 | key := "testKey" 30 | value := "testValue" 31 | 32 | cache.Set(key, value, 1*time.Minute) 33 | 34 | retrievedValue, found := cache.Get(key) 35 | if !found { 36 | t.Errorf("Expected to find key %q, but it was not found", key) 37 | } 38 | 39 | if retrievedValue != value { 40 | t.Errorf("Expected value %q, but got %q", value, retrievedValue) 41 | } 42 | } 43 | 44 | // TestCache_GetExpired tests that an item is not retrievable after it has expired. 45 | func TestCache_GetExpired(t *testing.T) { 46 | cache := NewCache() 47 | defer cache.Stop() 48 | 49 | key := "expiredKey" 50 | value := "expiredValue" 51 | 52 | // Set an item with a very short TTL. 53 | cache.Set(key, value, 1*time.Millisecond) 54 | time.Sleep(2 * time.Millisecond) // Wait for the item to expire. 55 | 56 | // Attempt to get the expired item. 57 | _, found := cache.Get(key) 58 | if found { 59 | t.Errorf("Expected key %q to be expired, but it was found", key) 60 | } 61 | } 62 | 63 | // TestCache_SetNoExpiration ensures that an item with a TTL of 0 or less 64 | // does not expire. 65 | func TestCache_SetNoExpiration(t *testing.T) { 66 | cache := NewCache() 67 | defer cache.Stop() 68 | 69 | key := "noExpireKey" 70 | value := "noExpireValue" 71 | 72 | cache.Set(key, value, 0) // Setting with 0 should mean no expiration. 73 | time.Sleep(5 * time.Millisecond) 74 | 75 | retrievedValue, found := cache.Get(key) 76 | if !found { 77 | t.Errorf("Expected to find key %q, but it was not found", key) 78 | } 79 | if retrievedValue != value { 80 | t.Errorf("Expected value %q, but got %q", value, retrievedValue) 81 | } 82 | } 83 | 84 | // TestCache_Janitor verifies that the janitor goroutine automatically removes 85 | // expired items from the cache. 86 | func TestCache_Janitor(t *testing.T) { 87 | // Initialize cache with a very short janitor interval for quick testing. 88 | cache := NewCache().WithJanitor(10 * time.Millisecond) 89 | defer cache.Stop() 90 | 91 | expiredKey := "expired" 92 | activeKey := "active" 93 | 94 | // Set one item that will expire and one that will not. 95 | cache.Set(expiredKey, "value", 1*time.Millisecond) 96 | cache.Set(activeKey, "value", 1*time.Hour) 97 | 98 | // Wait longer than the janitor interval to ensure it has a chance to run. 99 | time.Sleep(20 * time.Millisecond) 100 | 101 | // Check that the expired key has been removed. 102 | _, found := cache.Get(expiredKey) 103 | if found { 104 | t.Errorf("Expected janitor to clean up expired key %q, but it was found", expiredKey) 105 | } 106 | 107 | // Check that the active key is still present. 108 | _, found = cache.Get(activeKey) 109 | if !found { 110 | t.Errorf("Expected active key %q to be present, but it was not found", activeKey) 111 | } 112 | } 113 | 114 | // TestCache_Stop ensures that calling the Stop method does not cause a panic, 115 | // regardless of whether the janitor is running or not. It also tests idempotency. 116 | func TestCache_Stop(t *testing.T) { 117 | t.Run("Stop without janitor", func(t *testing.T) { 118 | cache := NewCache() 119 | // Test that calling Stop multiple times on a cache without a janitor is safe. 120 | cache.Stop() 121 | cache.Stop() 122 | }) 123 | 124 | t.Run("Stop with janitor", func(t *testing.T) { 125 | cache := NewCache().WithJanitor(1 * time.Minute) 126 | // Test that calling Stop multiple times on a cache with a janitor is safe. 127 | cache.Stop() 128 | cache.Stop() 129 | }) 130 | } 131 | 132 | // TestCache_Concurrent performs a stress test on the cache with concurrent 133 | // reads and writes to check for race conditions. 134 | func TestCache_Concurrent(t *testing.T) { 135 | cache := NewCache().WithJanitor(100 * time.Millisecond) 136 | defer cache.Stop() 137 | 138 | var wg sync.WaitGroup 139 | numGoroutines := 100 140 | numOperations := 1000 141 | 142 | // Start concurrent writer goroutines. 143 | for i := 0; i < numGoroutines; i++ { 144 | wg.Add(1) 145 | go func(g int) { 146 | defer wg.Done() 147 | for j := 0; j < numOperations; j++ { 148 | key := string(rune(g*numOperations + j)) 149 | value := g*numOperations + j 150 | cache.Set(key, value, 10*time.Second) 151 | } 152 | }(i) 153 | } 154 | 155 | // Start concurrent reader goroutines. 156 | for i := 0; i < numGoroutines; i++ { 157 | wg.Add(1) 158 | go func(g int) { 159 | defer wg.Done() 160 | for j := 0; j < numOperations; j++ { 161 | key := string(rune(g*numOperations + j)) 162 | cache.Get(key) // We don't check the result, just that access is safe. 163 | } 164 | }(i) 165 | } 166 | 167 | // Wait for all goroutines to complete. If a race condition exists, the Go 168 | // race detector (`go test -race`) will likely catch it. 169 | wg.Wait() 170 | } 171 | ``` -------------------------------------------------------------------------------- /internal/tools/alloydb/alloydbgetcluster/alloydbgetcluster.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 alloydbgetcluster 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | 21 | yaml "github.com/goccy/go-yaml" 22 | "github.com/googleapis/genai-toolbox/internal/sources" 23 | alloydbadmin "github.com/googleapis/genai-toolbox/internal/sources/alloydbadmin" 24 | "github.com/googleapis/genai-toolbox/internal/tools" 25 | ) 26 | 27 | const kind string = "alloydb-get-cluster" 28 | 29 | func init() { 30 | if !tools.Register(kind, newConfig) { 31 | panic(fmt.Sprintf("tool kind %q already registered", kind)) 32 | } 33 | } 34 | 35 | func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) { 36 | actual := Config{Name: name} 37 | if err := decoder.DecodeContext(ctx, &actual); err != nil { 38 | return nil, err 39 | } 40 | return actual, nil 41 | } 42 | 43 | // Configuration for the get-cluster tool. 44 | type Config struct { 45 | Name string `yaml:"name" validate:"required"` 46 | Kind string `yaml:"kind" validate:"required"` 47 | Source string `yaml:"source" validate:"required"` 48 | Description string `yaml:"description"` 49 | AuthRequired []string `yaml:"authRequired"` 50 | BaseURL string `yaml:"baseURL"` 51 | } 52 | 53 | // validate interface 54 | var _ tools.ToolConfig = Config{} 55 | 56 | // ToolConfigKind returns the kind of the tool. 57 | func (cfg Config) ToolConfigKind() string { 58 | return kind 59 | } 60 | 61 | // Initialize initializes the tool from the configuration. 62 | func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) { 63 | rawS, ok := srcs[cfg.Source] 64 | if !ok { 65 | return nil, fmt.Errorf("source %q not found", cfg.Source) 66 | } 67 | 68 | s, ok := rawS.(*alloydbadmin.Source) 69 | if !ok { 70 | return nil, fmt.Errorf("invalid source for %q tool: source kind must be `%s`", kind, alloydbadmin.SourceKind) 71 | } 72 | 73 | allParameters := tools.Parameters{ 74 | tools.NewStringParameter("project", "The GCP project ID."), 75 | tools.NewStringParameter("location", "The location of the cluster (e.g., 'us-central1')."), 76 | tools.NewStringParameter("cluster", "The ID of the cluster."), 77 | } 78 | paramManifest := allParameters.Manifest() 79 | 80 | description := cfg.Description 81 | if description == "" { 82 | description = "Retrieves details about a specific AlloyDB cluster." 83 | } 84 | mcpManifest := tools.GetMcpManifest(cfg.Name, description, cfg.AuthRequired, allParameters) 85 | 86 | return Tool{ 87 | Name: cfg.Name, 88 | Kind: kind, 89 | Source: s, 90 | AllParams: allParameters, 91 | manifest: tools.Manifest{Description: description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired}, 92 | mcpManifest: mcpManifest, 93 | }, nil 94 | } 95 | 96 | // Tool represents the get-cluster tool. 97 | type Tool struct { 98 | Name string `yaml:"name"` 99 | Kind string `yaml:"kind"` 100 | 101 | Source *alloydbadmin.Source 102 | AllParams tools.Parameters 103 | 104 | manifest tools.Manifest 105 | mcpManifest tools.McpManifest 106 | } 107 | 108 | // Invoke executes the tool's logic. 109 | func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) { 110 | paramsMap := params.AsMap() 111 | 112 | project, ok := paramsMap["project"].(string) 113 | if !ok { 114 | return nil, fmt.Errorf("invalid or missing 'project' parameter; expected a string") 115 | } 116 | location, ok := paramsMap["location"].(string) 117 | if !ok { 118 | return nil, fmt.Errorf("invalid 'location' parameter; expected a string") 119 | } 120 | cluster, ok := paramsMap["cluster"].(string) 121 | if !ok { 122 | return nil, fmt.Errorf("invalid 'cluster' parameter; expected a string") 123 | } 124 | 125 | service, err := t.Source.GetService(ctx, string(accessToken)) 126 | if err != nil { 127 | return nil, err 128 | } 129 | 130 | urlString := fmt.Sprintf("projects/%s/locations/%s/clusters/%s", project, location, cluster) 131 | 132 | resp, err := service.Projects.Locations.Clusters.Get(urlString).Do() 133 | if err != nil { 134 | return nil, fmt.Errorf("error getting AlloyDB cluster: %w", err) 135 | } 136 | 137 | return resp, nil 138 | } 139 | 140 | // ParseParams parses the parameters for the tool. 141 | func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) { 142 | return tools.ParseParams(t.AllParams, data, claims) 143 | } 144 | 145 | // Manifest returns the tool's manifest. 146 | func (t Tool) Manifest() tools.Manifest { 147 | return t.manifest 148 | } 149 | 150 | // McpManifest returns the tool's MCP manifest. 151 | func (t Tool) McpManifest() tools.McpManifest { 152 | return t.mcpManifest 153 | } 154 | 155 | // Authorized checks if the tool is authorized. 156 | func (t Tool) Authorized(verifiedAuthServices []string) bool { 157 | return true 158 | } 159 | 160 | func (t Tool) RequiresClientAuthorization() bool { 161 | return t.Source.UseClientAuthorization() 162 | } 163 | ```