From fd4250d0db1d7ac276db3e292198ec89dc890fa9 Mon Sep 17 00:00:00 2001 From: rahulpinto19 Date: Thu, 8 Jan 2026 04:43:41 +0000 Subject: [PATCH 01/23] cleanup updated --- tests/common.go | 77 +++++++++++++++++++++++++++---------------------- 1 file changed, 42 insertions(+), 35 deletions(-) diff --git a/tests/common.go b/tests/common.go index a567b5eae8e..dcf3d2f62c7 100644 --- a/tests/common.go +++ b/tests/common.go @@ -213,7 +213,6 @@ func AddPostgresPrebuiltConfig(t *testing.T, config map[string]any) map[string]a PostgresListPGSettingsToolKind = "postgres-list-pg-settings" PostgresListDatabaseStatsToolKind = "postgres-list-database-stats" PostgresListRolesToolKind = "postgres-list-roles" - PostgresListStoredProcedureToolKind = "postgres-list-stored-procedure" ) tools, ok := config["tools"].(map[string]any) @@ -311,11 +310,6 @@ func AddPostgresPrebuiltConfig(t *testing.T, config map[string]any) map[string]a "kind": PostgresListRolesToolKind, "source": "my-instance", } - - tools["list_stored_procedure"] = map[string]any{ - "kind": PostgresListStoredProcedureToolKind, - "source": "my-instance", - } config["tools"] = tools return config } @@ -943,35 +937,48 @@ func TestCloudSQLMySQL_IPTypeParsingFromYAML(t *testing.T) { // Finds and drops all tables in a postgres database. func CleanupPostgresTables(t *testing.T, ctx context.Context, pool *pgxpool.Pool) { - query := ` - SELECT table_name FROM information_schema.tables - WHERE table_schema = 'public' AND table_type = 'BASE TABLE';` - - rows, err := pool.Query(ctx, query) - if err != nil { - t.Fatalf("Failed to query for all tables in 'public' schema: %v", err) - } - defer rows.Close() - - var tablesToDrop []string - for rows.Next() { - var tableName string - if err := rows.Scan(&tableName); err != nil { - t.Errorf("Failed to scan table name: %v", err) - continue - } - tablesToDrop = append(tablesToDrop, fmt.Sprintf("public.%q", tableName)) - } - - if len(tablesToDrop) == 0 { - return - } - - dropQuery := fmt.Sprintf("DROP TABLE IF EXISTS %s CASCADE;", strings.Join(tablesToDrop, ", ")) - - if _, err := pool.Exec(ctx, dropQuery); err != nil { - t.Fatalf("Failed to drop all tables in 'public' schema: %v", err) - } + // query := ` + // SELECT table_name FROM information_schema.tables + // WHERE table_schema = 'public' AND table_type = 'BASE TABLE';` + + // rows, err := pool.Query(ctx, query) + // if err != nil { + // t.Fatalf("Failed to query for all tables in 'public' schema: %v", err) + // } + // defer rows.Close() + + // var tablesToDrop []string + // for rows.Next() { + // var tableName string + // if err := rows.Scan(&tableName); err != nil { + // t.Errorf("Failed to scan table name: %v", err) + // continue + // } + // tablesToDrop = append(tablesToDrop, fmt.Sprintf("public.%q", tableName)) + // } + + // if len(tablesToDrop) == 0 { + // return + // } + + // dropQuery := fmt.Sprintf("DROP TABLE IF EXISTS %s CASCADE;", strings.Join(tablesToDrop, ", ")) + + // if _, err := pool.Exec(ctx, dropQuery); err != nil { + // t.Fatalf("Failed to drop all tables in 'public' schema: %v", err) + // } + + + // 1. Drop the entire public schema (this kills tables, views, types, etc.) + dropSchema := "DROP SCHEMA public CASCADE;" + // 2. Recreate the empty public schema + createSchema := "CREATE SCHEMA public;" + // 3. Grant permissions back (Postgres default) + grantPublic := "GRANT ALL ON SCHEMA public TO public;" + + _, err := pool.Exec(ctx, dropSchema + createSchema + grantPublic) + if err != nil { + t.Fatalf("Failed to nuclear-wipe the public schema: %v", err) + } } // Finds and drops all tables in a mysql database. From edf8377c57f0c1f3cb2c5fba449b34f09ea2f73c Mon Sep 17 00:00:00 2001 From: rahulpinto19 Date: Thu, 8 Jan 2026 06:33:14 +0000 Subject: [PATCH 02/23] test --- tests/common.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/common.go b/tests/common.go index dcf3d2f62c7..fc7fa90b5ab 100644 --- a/tests/common.go +++ b/tests/common.go @@ -967,7 +967,7 @@ func CleanupPostgresTables(t *testing.T, ctx context.Context, pool *pgxpool.Pool // t.Fatalf("Failed to drop all tables in 'public' schema: %v", err) // } - + fmt.println("drop schema started --------------") // 1. Drop the entire public schema (this kills tables, views, types, etc.) dropSchema := "DROP SCHEMA public CASCADE;" // 2. Recreate the empty public schema From 1a39de7617ea6afc7fe30ad10939f54a87b62276 Mon Sep 17 00:00:00 2001 From: rahulpinto19 Date: Thu, 8 Jan 2026 07:33:28 +0000 Subject: [PATCH 03/23] test1 --- tests/common.go | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/common.go b/tests/common.go index fc7fa90b5ab..16852b05ab3 100644 --- a/tests/common.go +++ b/tests/common.go @@ -967,6 +967,7 @@ func CleanupPostgresTables(t *testing.T, ctx context.Context, pool *pgxpool.Pool // t.Fatalf("Failed to drop all tables in 'public' schema: %v", err) // } + fmt.println("drop schema started --------------") // 1. Drop the entire public schema (this kills tables, views, types, etc.) dropSchema := "DROP SCHEMA public CASCADE;" From c3c2cec9ab46ee8ed387cc6d0431a11ca34d0b1b Mon Sep 17 00:00:00 2001 From: rahulpinto19 Date: Thu, 8 Jan 2026 07:38:53 +0000 Subject: [PATCH 04/23] test --- tests/common.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/common.go b/tests/common.go index 16852b05ab3..7f6a3007f6b 100644 --- a/tests/common.go +++ b/tests/common.go @@ -213,6 +213,7 @@ func AddPostgresPrebuiltConfig(t *testing.T, config map[string]any) map[string]a PostgresListPGSettingsToolKind = "postgres-list-pg-settings" PostgresListDatabaseStatsToolKind = "postgres-list-database-stats" PostgresListRolesToolKind = "postgres-list-roles" + PostgresListStoredProcedureToolKind = "postgres-list-stored-procedure" ) tools, ok := config["tools"].(map[string]any) @@ -310,6 +311,11 @@ func AddPostgresPrebuiltConfig(t *testing.T, config map[string]any) map[string]a "kind": PostgresListRolesToolKind, "source": "my-instance", } + + tools["list_stored_procedure"] = map[string]any{ + "kind": PostgresListStoredProcedureToolKind, + "source": "my-instance", + } config["tools"] = tools return config } @@ -966,8 +972,6 @@ func CleanupPostgresTables(t *testing.T, ctx context.Context, pool *pgxpool.Pool // if _, err := pool.Exec(ctx, dropQuery); err != nil { // t.Fatalf("Failed to drop all tables in 'public' schema: %v", err) // } - - fmt.println("drop schema started --------------") // 1. Drop the entire public schema (this kills tables, views, types, etc.) dropSchema := "DROP SCHEMA public CASCADE;" From 96904e28e99e3e6d4259bd67492fe16a074268bb Mon Sep 17 00:00:00 2001 From: rahulpinto19 Date: Thu, 8 Jan 2026 08:56:39 +0000 Subject: [PATCH 05/23] test --- tests/common.go | 89 +++++++++++++++++++++++-------------------------- 1 file changed, 42 insertions(+), 47 deletions(-) diff --git a/tests/common.go b/tests/common.go index 7f6a3007f6b..c27131ab267 100644 --- a/tests/common.go +++ b/tests/common.go @@ -213,7 +213,6 @@ func AddPostgresPrebuiltConfig(t *testing.T, config map[string]any) map[string]a PostgresListPGSettingsToolKind = "postgres-list-pg-settings" PostgresListDatabaseStatsToolKind = "postgres-list-database-stats" PostgresListRolesToolKind = "postgres-list-roles" - PostgresListStoredProcedureToolKind = "postgres-list-stored-procedure" ) tools, ok := config["tools"].(map[string]any) @@ -311,11 +310,6 @@ func AddPostgresPrebuiltConfig(t *testing.T, config map[string]any) map[string]a "kind": PostgresListRolesToolKind, "source": "my-instance", } - - tools["list_stored_procedure"] = map[string]any{ - "kind": PostgresListStoredProcedureToolKind, - "source": "my-instance", - } config["tools"] = tools return config } @@ -943,47 +937,48 @@ func TestCloudSQLMySQL_IPTypeParsingFromYAML(t *testing.T) { // Finds and drops all tables in a postgres database. func CleanupPostgresTables(t *testing.T, ctx context.Context, pool *pgxpool.Pool) { - // query := ` - // SELECT table_name FROM information_schema.tables - // WHERE table_schema = 'public' AND table_type = 'BASE TABLE';` - - // rows, err := pool.Query(ctx, query) - // if err != nil { - // t.Fatalf("Failed to query for all tables in 'public' schema: %v", err) - // } - // defer rows.Close() - - // var tablesToDrop []string - // for rows.Next() { - // var tableName string - // if err := rows.Scan(&tableName); err != nil { - // t.Errorf("Failed to scan table name: %v", err) - // continue - // } - // tablesToDrop = append(tablesToDrop, fmt.Sprintf("public.%q", tableName)) - // } - - // if len(tablesToDrop) == 0 { - // return - // } - - // dropQuery := fmt.Sprintf("DROP TABLE IF EXISTS %s CASCADE;", strings.Join(tablesToDrop, ", ")) - - // if _, err := pool.Exec(ctx, dropQuery); err != nil { - // t.Fatalf("Failed to drop all tables in 'public' schema: %v", err) - // } - fmt.println("drop schema started --------------") - // 1. Drop the entire public schema (this kills tables, views, types, etc.) - dropSchema := "DROP SCHEMA public CASCADE;" - // 2. Recreate the empty public schema - createSchema := "CREATE SCHEMA public;" - // 3. Grant permissions back (Postgres default) - grantPublic := "GRANT ALL ON SCHEMA public TO public;" - - _, err := pool.Exec(ctx, dropSchema + createSchema + grantPublic) - if err != nil { - t.Fatalf("Failed to nuclear-wipe the public schema: %v", err) - } + query := ` + SELECT table_name FROM information_schema.tables + WHERE table_schema = 'public' AND table_type = 'BASE TABLE';` + + rows, err := pool.Query(ctx, query) + if err != nil { + t.Fatalf("Failed to query for all tables in 'public' schema: %v", err) + } + defer rows.Close() + + var tablesToDrop []string + for rows.Next() { + var tableName string + if err := rows.Scan(&tableName); err != nil { + t.Errorf("Failed to scan table name: %v", err) + continue + } + tablesToDrop = append(tablesToDrop, fmt.Sprintf("public.%q", tableName)) + } + + if len(tablesToDrop) == 0 { + return + } + + dropQuery := fmt.Sprintf("DROP TABLE IF EXISTS %s CASCADE;", strings.Join(tablesToDrop, ", ")) + + if _, err := pool.Exec(ctx, dropQuery); err != nil { + t.Fatalf("Failed to drop all tables in 'public' schema: %v", err) + } + + fmt.println("drop schema") + // // 1. Drop the entire public schema (this kills tables, views, types, etc.) + // dropSchema := "DROP SCHEMA public CASCADE;" + // // 2. Recreate the empty public schema + // createSchema := "CREATE SCHEMA public;" + // // 3. Grant permissions back (Postgres default) + // grantPublic := "GRANT ALL ON SCHEMA public TO public;" + + // _, err := pool.Exec(ctx, dropSchema + createSchema + grantPublic) + // if err != nil { + // t.Fatalf("Failed to nuclear-wipe the public schema: %v", err) + // } } // Finds and drops all tables in a mysql database. From 1bfe394222d76643e08fca3c199a994537935085 Mon Sep 17 00:00:00 2001 From: rahulpinto19 Date: Thu, 8 Jan 2026 09:57:32 +0000 Subject: [PATCH 06/23] test --- tests/common.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/common.go b/tests/common.go index c27131ab267..aff8c7272a4 100644 --- a/tests/common.go +++ b/tests/common.go @@ -937,6 +937,8 @@ func TestCloudSQLMySQL_IPTypeParsingFromYAML(t *testing.T) { // Finds and drops all tables in a postgres database. func CleanupPostgresTables(t *testing.T, ctx context.Context, pool *pgxpool.Pool) { + // fmt.println("") + t.Logf("in cleanupPostgrestTables"); query := ` SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type = 'BASE TABLE';` @@ -967,7 +969,6 @@ func CleanupPostgresTables(t *testing.T, ctx context.Context, pool *pgxpool.Pool t.Fatalf("Failed to drop all tables in 'public' schema: %v", err) } - fmt.println("drop schema") // // 1. Drop the entire public schema (this kills tables, views, types, etc.) // dropSchema := "DROP SCHEMA public CASCADE;" // // 2. Recreate the empty public schema From 3932efbd4bc97b547c3d185494c37c891fb080fa Mon Sep 17 00:00:00 2001 From: Twisha Bansal <58483338+twishabansal@users.noreply.github.com> Date: Thu, 8 Jan 2026 16:19:55 +0530 Subject: [PATCH 07/23] docs: link medium blogs to toolbox docsite (#2269) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description Adds a section in the navbar that links to the toolbox medium blog: 87F2yTQdcbpMHs3 ## PR Checklist > Thank you for opening a Pull Request! Before submitting your PR, there are a > few things you can do to make sure it goes smoothly: - [ ] Make sure you reviewed [CONTRIBUTING.md](https://github.com/googleapis/genai-toolbox/blob/main/CONTRIBUTING.md) - [ ] Make sure to open an issue as a [bug/issue](https://github.com/googleapis/genai-toolbox/issues/new/choose) before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea - [ ] Ensure the tests and linter pass - [ ] Code coverage does not decrease (if any source code was changed) - [ ] Appropriate docs were updated (if necessary) - [ ] Make sure to add `!` if this involve a breaking change 🛠️ Fixes # --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- docs/en/blogs/_index.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 docs/en/blogs/_index.md diff --git a/docs/en/blogs/_index.md b/docs/en/blogs/_index.md new file mode 100644 index 00000000000..f7273356dc5 --- /dev/null +++ b/docs/en/blogs/_index.md @@ -0,0 +1,18 @@ +--- +title: "Featured Articles" +weight: 3 +description: Toolbox Medium Blogs +manualLink: "https://medium.com/@mcp_toolbox" +manualLinkTarget: _blank +--- + + + + Redirecting to Featured Articles + + + + +

If you are not automatically redirected, please follow this link to our articles.

+ + From 2bcfca0981b60ac41889e7b6a26e2d6e3905d7b4 Mon Sep 17 00:00:00 2001 From: Wenxin Du <117315983+duwenxin99@users.noreply.github.com> Date: Thu, 8 Jan 2026 13:58:39 -0500 Subject: [PATCH 08/23] fix(server): Add `embeddingModel` config initialization (#2281) Embedding Models were only loaded in hot reload because it was not initialized properly. --- cmd/root.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/root.go b/cmd/root.go index 5b1635cc99c..1ba6c6e0a45 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -947,6 +947,7 @@ func run(cmd *Command) error { cmd.cfg.SourceConfigs = finalToolsFile.Sources cmd.cfg.AuthServiceConfigs = finalToolsFile.AuthServices + cmd.cfg.EmbeddingModelConfigs = finalToolsFile.EmbeddingModels cmd.cfg.ToolConfigs = finalToolsFile.Tools cmd.cfg.ToolsetConfigs = finalToolsFile.Toolsets cmd.cfg.PromptConfigs = finalToolsFile.Prompts From 7d5af5f001da214fc8bdd628f3f4b5075ebe0a6d Mon Sep 17 00:00:00 2001 From: Yuan Teoh <45984206+Yuan325@users.noreply.github.com> Date: Thu, 8 Jan 2026 11:42:54 -0800 Subject: [PATCH 09/23] feat: add allowed-hosts flag (#2254) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description Previously added `allowed-origins` (for CORs) is not sufficient for preventing DNS rebinding attacks. We'll have to check host headers. To test, run Toolbox with the following: ``` go run . --allowed-hosts=127.0.0.1:5000 ``` Test with the following: ``` // curl successfully curl -H "Host: 127.0.0.1:5000" http://127.0.0.1:5000 // will show Invalid Host Header error curl -H "Host: attacker:5000" http://127.0.0.1:5000 ``` ## PR Checklist > Thank you for opening a Pull Request! Before submitting your PR, there are a > few things you can do to make sure it goes smoothly: - [ ] Make sure you reviewed [CONTRIBUTING.md](https://github.com/googleapis/genai-toolbox/blob/main/CONTRIBUTING.md) - [ ] Make sure to open an issue as a [bug/issue](https://github.com/googleapis/genai-toolbox/issues/new/choose) before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea - [ ] Ensure the tests and linter pass - [ ] Code coverage does not decrease (if any source code was changed) - [ ] Appropriate docs were updated (if necessary) - [ ] Make sure to add `!` if this involve a breaking change 🛠️ Fixes # --- cmd/root.go | 2 ++ cmd/root_test.go | 10 ++++++++++ docs/en/how-to/deploy_docker.md | 7 ++++++- docs/en/how-to/deploy_gke.md | 6 +++++- docs/en/how-to/deploy_toolbox.md | 12 ++++++++---- docs/en/reference/cli.md | 33 ++++++++++++++++---------------- internal/server/config.go | 2 ++ internal/server/server.go | 26 ++++++++++++++++++++++++- internal/server/server_test.go | 7 ++++--- 9 files changed, 79 insertions(+), 26 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 1ba6c6e0a45..a9b41989ac4 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -381,7 +381,9 @@ func NewCommand(opts ...Option) *Command { flags.BoolVar(&cmd.cfg.Stdio, "stdio", false, "Listens via MCP STDIO instead of acting as a remote HTTP server.") flags.BoolVar(&cmd.cfg.DisableReload, "disable-reload", false, "Disables dynamic reloading of tools file.") flags.BoolVar(&cmd.cfg.UI, "ui", false, "Launches the Toolbox UI web server.") + // TODO: Insecure by default. Might consider updating this for v1.0.0 flags.StringSliceVar(&cmd.cfg.AllowedOrigins, "allowed-origins", []string{"*"}, "Specifies a list of origins permitted to access this server. Defaults to '*'.") + flags.StringSliceVar(&cmd.cfg.AllowedHosts, "allowed-hosts", []string{"*"}, "Specifies a list of hosts permitted to access this server. Defaults to '*'.") // wrap RunE command so that we have access to original Command object cmd.RunE = func(*cobra.Command, []string) error { return run(cmd) } diff --git a/cmd/root_test.go b/cmd/root_test.go index 22840efbc52..590eb4bd28f 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -67,6 +67,9 @@ func withDefaults(c server.ServerConfig) server.ServerConfig { if c.AllowedOrigins == nil { c.AllowedOrigins = []string{"*"} } + if c.AllowedHosts == nil { + c.AllowedHosts = []string{"*"} + } return c } @@ -220,6 +223,13 @@ func TestServerConfigFlags(t *testing.T) { AllowedOrigins: []string{"http://foo.com", "http://bar.com"}, }), }, + { + desc: "allowed hosts", + args: []string{"--allowed-hosts", "http://foo.com,http://bar.com"}, + want: withDefaults(server.ServerConfig{ + AllowedHosts: []string{"http://foo.com", "http://bar.com"}, + }), + }, } for _, tc := range tcs { t.Run(tc.desc, func(t *testing.T) { diff --git a/docs/en/how-to/deploy_docker.md b/docs/en/how-to/deploy_docker.md index ff12367572e..41378e3e751 100644 --- a/docs/en/how-to/deploy_docker.md +++ b/docs/en/how-to/deploy_docker.md @@ -68,7 +68,12 @@ networks: ``` {{< notice tip >}} -To prevent DNS rebinding attack, use the `--allowed-origins` flag to specify a +To prevent DNS rebinding attack, use the `--allowed-hosts` flag to specify a +list of hosts for validation. E.g. `command: [ "toolbox", +"--tools-file", "/config/tools.yaml", "--address", "0.0.0.0", +"--allowed-hosts", "localhost:5000"]` + +To implement CORs, use the `--allowed-origins` flag to specify a list of origins permitted to access the server. E.g. `command: [ "toolbox", "--tools-file", "/config/tools.yaml", "--address", "0.0.0.0", "--allowed-origins", "https://foo.bar"]` diff --git a/docs/en/how-to/deploy_gke.md b/docs/en/how-to/deploy_gke.md index 4c18e9bfb14..058976ee0a9 100644 --- a/docs/en/how-to/deploy_gke.md +++ b/docs/en/how-to/deploy_gke.md @@ -188,9 +188,13 @@ description: > path: tools.yaml ``` - {{< notice tip >}} + {{< notice tip >}} To prevent DNS rebinding attack, use the `--allowed-origins` flag to specify a list of origins permitted to access the server. E.g. `args: ["--address", +"0.0.0.0", "--allowed-hosts", "foo.bar:5000"]` + +To implement CORs, use the `--allowed-origins` flag to specify a +list of origins permitted to access the server. E.g. `args: ["--address", "0.0.0.0", "--allowed-origins", "https://foo.bar"]` {{< /notice >}} diff --git a/docs/en/how-to/deploy_toolbox.md b/docs/en/how-to/deploy_toolbox.md index 455f6bd3ff0..67003108771 100644 --- a/docs/en/how-to/deploy_toolbox.md +++ b/docs/en/how-to/deploy_toolbox.md @@ -142,14 +142,18 @@ deployment will time out. ### Update deployed server to be secure -To prevent DNS rebinding attack, use the `--allowed-origins` flag to specify a -list of origins permitted to access the server. In order to do that, you will +To prevent DNS rebinding attack, use the `--allowed-hosts` flag to specify a +list of hosts. In order to do that, you will have to re-deploy the cloud run service with the new flag. +To implement CORs checks, use the `--allowed-origins` flag to specify a list of +origins permitted to access the server. + 1. Set an environment variable to the cloud run url: ```bash export URL= + export HOST= ``` 2. Redeploy Toolbox: @@ -160,7 +164,7 @@ have to re-deploy the cloud run service with the new flag. --service-account toolbox-identity \ --region us-central1 \ --set-secrets "/app/tools.yaml=tools:latest" \ - --args="--tools-file=/app/tools.yaml","--address=0.0.0.0","--port=8080","--allowed-origins=$URL" + --args="--tools-file=/app/tools.yaml","--address=0.0.0.0","--port=8080","--allowed-origins=$URL","--allowed-hosts=$HOST" # --allow-unauthenticated # https://cloud.google.com/run/docs/authenticating/public#gcloud ``` @@ -172,7 +176,7 @@ have to re-deploy the cloud run service with the new flag. --service-account toolbox-identity \ --region us-central1 \ --set-secrets "/app/tools.yaml=tools:latest" \ - --args="--tools-file=/app/tools.yaml","--address=0.0.0.0","--port=8080","--allowed-origins=$URL" \ + --args="--tools-file=/app/tools.yaml","--address=0.0.0.0","--port=8080","--allowed-origins=$URL","--allowed-hosts=$HOST" \ # TODO(dev): update the following to match your VPC if necessary --network default \ --subnet default diff --git a/docs/en/reference/cli.md b/docs/en/reference/cli.md index 1c9829995e8..583f5a40873 100644 --- a/docs/en/reference/cli.md +++ b/docs/en/reference/cli.md @@ -8,25 +8,26 @@ description: > ## Reference -| Flag (Short) | Flag (Long) | Description | Default | -|--------------|----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------| -| `-a` | `--address` | Address of the interface the server will listen on. | `127.0.0.1` | -| | `--disable-reload` | Disables dynamic reloading of tools file. | | -| `-h` | `--help` | help for toolbox | | -| | `--log-level` | Specify the minimum level logged. Allowed: 'DEBUG', 'INFO', 'WARN', 'ERROR'. | `info` | -| | `--logging-format` | Specify logging format to use. Allowed: 'standard' or 'JSON'. | `standard` | -| `-p` | `--port` | Port the server will listen on. | `5000` | -| | `--prebuilt` | Use a prebuilt tool configuration by source type. See [Prebuilt Tools Reference](prebuilt-tools.md) for allowed values. | | -| | `--stdio` | Listens via MCP STDIO instead of acting as a remote HTTP server. | | -| | `--telemetry-gcp` | Enable exporting directly to Google Cloud Monitoring. | | -| | `--telemetry-otlp` | Enable exporting using OpenTelemetry Protocol (OTLP) to the specified endpoint (e.g. 'http://127.0.0.1:4318') | | -| | `--telemetry-service-name` | Sets the value of the service.name resource attribute for telemetry data. | `toolbox` | +| Flag (Short) | Flag (Long) | Description | Default | +|--------------|----------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------| +| `-a` | `--address` | Address of the interface the server will listen on. | `127.0.0.1` | +| | `--disable-reload` | Disables dynamic reloading of tools file. | | +| `-h` | `--help` | help for toolbox | | +| | `--log-level` | Specify the minimum level logged. Allowed: 'DEBUG', 'INFO', 'WARN', 'ERROR'. | `info` | +| | `--logging-format` | Specify logging format to use. Allowed: 'standard' or 'JSON'. | `standard` | +| `-p` | `--port` | Port the server will listen on. | `5000` | +| | `--prebuilt` | Use a prebuilt tool configuration by source type. See [Prebuilt Tools Reference](prebuilt-tools.md) for allowed values. | | +| | `--stdio` | Listens via MCP STDIO instead of acting as a remote HTTP server. | | +| | `--telemetry-gcp` | Enable exporting directly to Google Cloud Monitoring. | | +| | `--telemetry-otlp` | Enable exporting using OpenTelemetry Protocol (OTLP) to the specified endpoint (e.g. 'http://127.0.0.1:4318') | | +| | `--telemetry-service-name` | Sets the value of the service.name resource attribute for telemetry data. | `toolbox` | | | `--tools-file` | File path specifying the tool configuration. Cannot be used with --tools-files or --tools-folder. | | | | `--tools-files` | Multiple file paths specifying tool configurations. Files will be merged. Cannot be used with --tools-file or --tools-folder. | | | | `--tools-folder` | Directory path containing YAML tool configuration files. All .yaml and .yml files in the directory will be loaded and merged. Cannot be used with --tools-file or --tools-files. | | -| | `--ui` | Launches the Toolbox UI web server. | | -| | `--allowed-origins` | Specifies a list of origins permitted to access this server. | `*` | -| `-v` | `--version` | version for toolbox | | +| | `--ui` | Launches the Toolbox UI web server. | | +| | `--allowed-origins` | Specifies a list of origins permitted to access this server for CORs access. | `*` | +| | `--allowed-hosts` | Specifies a list of hosts permitted to access this server to prevent DNS rebinding attacks. | `*` | +| `-v` | `--version` | version for toolbox | | ## Examples diff --git a/internal/server/config.go b/internal/server/config.go index 8beb32c839f..9d8561e5e67 100644 --- a/internal/server/config.go +++ b/internal/server/config.go @@ -68,6 +68,8 @@ type ServerConfig struct { UI bool // Specifies a list of origins permitted to access this server. AllowedOrigins []string + // Specifies a list of hosts permitted to access this server + AllowedHosts []string } type logFormat string diff --git a/internal/server/server.go b/internal/server/server.go index 10d146143ce..1db4fcf1dab 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -300,6 +300,21 @@ func InitializeConfigs(ctx context.Context, cfg ServerConfig) ( return sourcesMap, authServicesMap, embeddingModelsMap, toolsMap, toolsetsMap, promptsMap, promptsetsMap, nil } +func hostCheck(allowedHosts map[string]struct{}) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, hasWildcard := allowedHosts["*"] + _, hostIsAllowed := allowedHosts[r.Host] + if !hasWildcard && !hostIsAllowed { + // Return 400 Bad Request or 403 Forbidden to block the attack + http.Error(w, "Invalid Host header", http.StatusBadRequest) + return + } + next.ServeHTTP(w, r) + }) + } +} + // NewServer returns a Server object based on provided Config. func NewServer(ctx context.Context, cfg ServerConfig) (*Server, error) { instrumentation, err := util.InstrumentationFromContext(ctx) @@ -374,7 +389,7 @@ func NewServer(ctx context.Context, cfg ServerConfig) (*Server, error) { // cors if slices.Contains(cfg.AllowedOrigins, "*") { - s.logger.WarnContext(ctx, "wildcard (`*`) allows all origin to access the resource and is not secure. Use it with cautious for public, non-sensitive data, or during local development. Recommended to use `--allowed-origins` flag to prevent DNS rebinding attacks") + s.logger.WarnContext(ctx, "wildcard (`*`) allows all origin to access the resource and is not secure. Use it with cautious for public, non-sensitive data, or during local development. Recommended to use `--allowed-origins` flag") } corsOpts := cors.Options{ AllowedOrigins: cfg.AllowedOrigins, @@ -385,6 +400,15 @@ func NewServer(ctx context.Context, cfg ServerConfig) (*Server, error) { MaxAge: 300, // cache preflight results for 5 minutes } r.Use(cors.Handler(corsOpts)) + // validate hosts for DNS rebinding attacks + if slices.Contains(cfg.AllowedHosts, "*") { + s.logger.WarnContext(ctx, "wildcard (`*`) allows all hosts to access the resource and is not secure. Use it with cautious for public, non-sensitive data, or during local development. Recommended to use `--allowed-hosts` flag to prevent DNS rebinding attacks") + } + allowedHostsMap := make(map[string]struct{}, len(cfg.AllowedHosts)) + for _, h := range cfg.AllowedHosts { + allowedHostsMap[h] = struct{}{} + } + r.Use(hostCheck(allowedHostsMap)) // control plane apiR, err := apiRouter(s) diff --git a/internal/server/server_test.go b/internal/server/server_test.go index dc221e9a5e3..b87401202e7 100644 --- a/internal/server/server_test.go +++ b/internal/server/server_test.go @@ -43,9 +43,10 @@ func TestServe(t *testing.T) { addr, port := "127.0.0.1", 5000 cfg := server.ServerConfig{ - Version: "0.0.0", - Address: addr, - Port: port, + Version: "0.0.0", + Address: addr, + Port: port, + AllowedHosts: []string{"*"}, } otelShutdown, err := telemetry.SetupOTel(ctx, "0.0.0", "", false, "toolbox") From ca4a12b5f3dfd3a0ee81b6f777422ad289fb15f7 Mon Sep 17 00:00:00 2001 From: Yuan Teoh <45984206+Yuan325@users.noreply.github.com> Date: Thu, 8 Jan 2026 12:53:45 -0800 Subject: [PATCH 10/23] feat: add default value to manifest (#2264) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description Add default value to manifest (for both native endpoint and mcp endpoint). ## PR Checklist > Thank you for opening a Pull Request! Before submitting your PR, there are a > few things you can do to make sure it goes smoothly: - [x] Make sure you reviewed [CONTRIBUTING.md](https://github.com/googleapis/genai-toolbox/blob/main/CONTRIBUTING.md) - [x] Make sure to open an issue as a [bug/issue](https://github.com/googleapis/genai-toolbox/issues/new/choose) before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea - [x] Ensure the tests and linter pass - [x] Code coverage does not decrease (if any source code was changed) - [x] Appropriate docs were updated (if necessary) - [x] Make sure to add `!` if this involve a breaking change 🛠️ Fixes #1602 --- internal/util/parameters/parameters.go | 19 +++++++- internal/util/parameters/parameters_test.go | 11 ++--- tests/alloydb/alloydb_integration_test.go | 2 +- tests/looker/looker_integration_test.go | 50 +++++++++++++++++++++ tests/neo4j/neo4j_integration_test.go | 1 + 5 files changed, 75 insertions(+), 8 deletions(-) diff --git a/internal/util/parameters/parameters.go b/internal/util/parameters/parameters.go index 9fcd1550adb..b11a4c1b05a 100644 --- a/internal/util/parameters/parameters.go +++ b/internal/util/parameters/parameters.go @@ -478,9 +478,13 @@ func (ps Parameters) McpManifest() (McpToolsSchema, map[string][]string) { for _, p := range ps { name := p.GetName() paramManifest, authParamList := p.McpManifest() + defaultV := p.GetDefault() + if defaultV != nil { + paramManifest.Default = defaultV + } properties[name] = paramManifest // parameters that doesn't have a default value are added to the required field - if CheckParamRequired(p.GetRequired(), p.GetDefault()) { + if CheckParamRequired(p.GetRequired(), defaultV) { required = append(required, name) } if len(authParamList) > 0 { @@ -502,6 +506,7 @@ type ParameterManifest struct { Description string `json:"description"` AuthServices []string `json:"authSources"` Items *ParameterManifest `json:"items,omitempty"` + Default any `json:"default,omitempty"` AdditionalProperties any `json:"additionalProperties,omitempty"` EmbeddedBy string `json:"embeddedBy,omitempty"` } @@ -511,6 +516,7 @@ type ParameterMcpManifest struct { Type string `json:"type"` Description string `json:"description"` Items *ParameterMcpManifest `json:"items,omitempty"` + Default any `json:"default,omitempty"` AdditionalProperties any `json:"additionalProperties,omitempty"` } @@ -788,6 +794,7 @@ func (p *StringParameter) Manifest() ParameterManifest { Required: r, Description: p.Desc, AuthServices: authServiceNames, + Default: p.GetDefault(), } } @@ -946,6 +953,7 @@ func (p *IntParameter) Manifest() ParameterManifest { Required: r, Description: p.Desc, AuthServices: authServiceNames, + Default: p.GetDefault(), } } @@ -1102,6 +1110,7 @@ func (p *FloatParameter) Manifest() ParameterManifest { Required: r, Description: p.Desc, AuthServices: authServiceNames, + Default: p.GetDefault(), } } @@ -1235,6 +1244,7 @@ func (p *BooleanParameter) Manifest() ParameterManifest { Required: r, Description: p.Desc, AuthServices: authServiceNames, + Default: p.GetDefault(), } } @@ -1430,6 +1440,7 @@ func (p *ArrayParameter) Manifest() ParameterManifest { Description: p.Desc, AuthServices: authServiceNames, Items: &items, + Default: p.GetDefault(), } } @@ -1675,7 +1686,10 @@ func (p *MapParameter) Manifest() ParameterManifest { // If no valueType is given, allow any properties. additionalProperties = true } - + var defaultV any + if p.Default != nil { + defaultV = *p.Default + } return ParameterManifest{ Name: p.Name, Type: "object", @@ -1683,6 +1697,7 @@ func (p *MapParameter) Manifest() ParameterManifest { Description: p.Desc, AuthServices: authServiceNames, AdditionalProperties: additionalProperties, + Default: defaultV, } } diff --git a/internal/util/parameters/parameters_test.go b/internal/util/parameters/parameters_test.go index 7ee24237892..624a05ab03e 100644 --- a/internal/util/parameters/parameters_test.go +++ b/internal/util/parameters/parameters_test.go @@ -1625,22 +1625,22 @@ func TestParamManifest(t *testing.T) { { name: "string default", in: parameters.NewStringParameterWithDefault("foo-string", "foo", "bar"), - want: parameters.ParameterManifest{Name: "foo-string", Type: "string", Required: false, Description: "bar", AuthServices: []string{}}, + want: parameters.ParameterManifest{Name: "foo-string", Type: "string", Required: false, Description: "bar", Default: "foo", AuthServices: []string{}}, }, { name: "int default", in: parameters.NewIntParameterWithDefault("foo-int", 1, "bar"), - want: parameters.ParameterManifest{Name: "foo-int", Type: "integer", Required: false, Description: "bar", AuthServices: []string{}}, + want: parameters.ParameterManifest{Name: "foo-int", Type: "integer", Required: false, Description: "bar", Default: 1, AuthServices: []string{}}, }, { name: "float default", in: parameters.NewFloatParameterWithDefault("foo-float", 1.1, "bar"), - want: parameters.ParameterManifest{Name: "foo-float", Type: "float", Required: false, Description: "bar", AuthServices: []string{}}, + want: parameters.ParameterManifest{Name: "foo-float", Type: "float", Required: false, Description: "bar", Default: 1.1, AuthServices: []string{}}, }, { name: "boolean default", in: parameters.NewBooleanParameterWithDefault("foo-bool", true, "bar"), - want: parameters.ParameterManifest{Name: "foo-bool", Type: "boolean", Required: false, Description: "bar", AuthServices: []string{}}, + want: parameters.ParameterManifest{Name: "foo-bool", Type: "boolean", Required: false, Description: "bar", Default: true, AuthServices: []string{}}, }, { name: "array default", @@ -1650,6 +1650,7 @@ func TestParamManifest(t *testing.T) { Type: "array", Required: false, Description: "bar", + Default: []any{"foo", "bar"}, AuthServices: []string{}, Items: ¶meters.ParameterManifest{Name: "foo-string", Type: "string", Required: false, Description: "bar", AuthServices: []string{}}, }, @@ -1841,7 +1842,7 @@ func TestMcpManifest(t *testing.T) { wantSchema: parameters.McpToolsSchema{ Type: "object", Properties: map[string]parameters.ParameterMcpManifest{ - "foo-string": {Type: "string", Description: "bar"}, + "foo-string": {Type: "string", Description: "bar", Default: "foo"}, "foo-string2": {Type: "string", Description: "bar"}, "foo-string3-auth": {Type: "string", Description: "bar"}, "foo-int2": {Type: "integer", Description: "bar"}, diff --git a/tests/alloydb/alloydb_integration_test.go b/tests/alloydb/alloydb_integration_test.go index 5028f892e48..0c2c3bb2e21 100644 --- a/tests/alloydb/alloydb_integration_test.go +++ b/tests/alloydb/alloydb_integration_test.go @@ -194,7 +194,7 @@ func runAlloyDBToolGetTest(t *testing.T) { "description": "Simple tool to test end to end functionality.", "parameters": []any{ map[string]any{"name": "project", "type": "string", "description": "The GCP project ID to list clusters for.", "required": true, "authSources": []any{}}, - map[string]any{"name": "location", "type": "string", "description": "Optional: The location to list clusters in (e.g., 'us-central1'). Use '-' to list clusters across all locations.(Default: '-')", "required": false, "authSources": []any{}}, + map[string]any{"name": "location", "type": "string", "description": "Optional: The location to list clusters in (e.g., 'us-central1'). Use '-' to list clusters across all locations.(Default: '-')", "required": false, "default": "-", "authSources": []any{}}, }, "authRequired": []any{}, }, diff --git a/tests/looker/looker_integration_test.go b/tests/looker/looker_integration_test.go index d179cf0483c..c360455907d 100644 --- a/tests/looker/looker_integration_test.go +++ b/tests/looker/looker_integration_test.go @@ -431,6 +431,7 @@ func TestLooker(t *testing.T) { "description": "The filters for the query", "name": "filters", "required": false, + "default": map[string]any{}, "type": "object", }, map[string]any{ @@ -446,6 +447,7 @@ func TestLooker(t *testing.T) { "name": "pivots", "required": false, "type": "array", + "default": []any{}, }, map[string]any{ "authSources": []any{}, @@ -460,6 +462,7 @@ func TestLooker(t *testing.T) { "name": "sorts", "required": false, "type": "array", + "default": []any{}, }, map[string]any{ "authSources": []any{}, @@ -467,6 +470,7 @@ func TestLooker(t *testing.T) { "name": "limit", "required": false, "type": "integer", + "default": float64(500), }, map[string]any{ "authSources": []any{}, @@ -519,6 +523,7 @@ func TestLooker(t *testing.T) { "description": "The filters for the query", "name": "filters", "required": false, + "default": map[string]any{}, "type": "object", }, map[string]any{ @@ -534,6 +539,7 @@ func TestLooker(t *testing.T) { "name": "pivots", "required": false, "type": "array", + "default": []any{}, }, map[string]any{ "authSources": []any{}, @@ -548,6 +554,7 @@ func TestLooker(t *testing.T) { "name": "sorts", "required": false, "type": "array", + "default": []any{}, }, map[string]any{ "authSources": []any{}, @@ -555,6 +562,7 @@ func TestLooker(t *testing.T) { "name": "limit", "required": false, "type": "integer", + "default": float64(500), }, map[string]any{ "authSources": []any{}, @@ -607,6 +615,7 @@ func TestLooker(t *testing.T) { "description": "The filters for the query", "name": "filters", "required": false, + "default": map[string]any{}, "type": "object", }, map[string]any{ @@ -622,6 +631,7 @@ func TestLooker(t *testing.T) { "name": "pivots", "required": false, "type": "array", + "default": []any{}, }, map[string]any{ "authSources": []any{}, @@ -636,6 +646,7 @@ func TestLooker(t *testing.T) { "name": "sorts", "required": false, "type": "array", + "default": []any{}, }, map[string]any{ "authSources": []any{}, @@ -643,6 +654,7 @@ func TestLooker(t *testing.T) { "name": "limit", "required": false, "type": "integer", + "default": float64(500), }, map[string]any{ "authSources": []any{}, @@ -658,6 +670,7 @@ func TestLooker(t *testing.T) { "name": "vis_config", "required": false, "type": "object", + "default": map[string]any{}, }, }, }, @@ -675,6 +688,7 @@ func TestLooker(t *testing.T) { "name": "title", "required": false, "type": "string", + "default": "", }, map[string]any{ "authSources": []any{}, @@ -682,6 +696,7 @@ func TestLooker(t *testing.T) { "name": "desc", "required": false, "type": "string", + "default": "", }, map[string]any{ "authSources": []any{}, @@ -689,6 +704,7 @@ func TestLooker(t *testing.T) { "name": "limit", "required": false, "type": "integer", + "default": float64(100), }, map[string]any{ "authSources": []any{}, @@ -696,6 +712,7 @@ func TestLooker(t *testing.T) { "name": "offset", "required": false, "type": "integer", + "default": float64(0), }, }, }, @@ -741,6 +758,7 @@ func TestLooker(t *testing.T) { "description": "The filters for the query", "name": "filters", "required": false, + "default": map[string]any{}, "type": "object", }, map[string]any{ @@ -756,6 +774,7 @@ func TestLooker(t *testing.T) { "name": "pivots", "required": false, "type": "array", + "default": []any{}, }, map[string]any{ "authSources": []any{}, @@ -770,6 +789,7 @@ func TestLooker(t *testing.T) { "name": "sorts", "required": false, "type": "array", + "default": []any{}, }, map[string]any{ "authSources": []any{}, @@ -777,6 +797,7 @@ func TestLooker(t *testing.T) { "name": "limit", "required": false, "type": "integer", + "default": float64(500), }, map[string]any{ "authSources": []any{}, @@ -797,6 +818,7 @@ func TestLooker(t *testing.T) { "description": "The description of the Look", "name": "description", "required": false, + "default": "", "type": "string", }, map[string]any{ @@ -804,6 +826,7 @@ func TestLooker(t *testing.T) { "description": "The folder id where the Look will be created. Leave blank to use the user's personal folder", "name": "folder", "required": false, + "default": "", "type": "string", }, map[string]any{ @@ -813,6 +836,7 @@ func TestLooker(t *testing.T) { "name": "vis_config", "required": false, "type": "object", + "default": map[string]any{}, }, }, }, @@ -830,6 +854,7 @@ func TestLooker(t *testing.T) { "name": "title", "required": false, "type": "string", + "default": "", }, map[string]any{ "authSources": []any{}, @@ -837,6 +862,7 @@ func TestLooker(t *testing.T) { "name": "desc", "required": false, "type": "string", + "default": "", }, map[string]any{ "authSources": []any{}, @@ -844,6 +870,7 @@ func TestLooker(t *testing.T) { "name": "limit", "required": false, "type": "integer", + "default": float64(100), }, map[string]any{ "authSources": []any{}, @@ -851,6 +878,7 @@ func TestLooker(t *testing.T) { "name": "offset", "required": false, "type": "integer", + "default": float64(0), }, }, }, @@ -875,6 +903,7 @@ func TestLooker(t *testing.T) { "name": "description", "required": false, "type": "string", + "default": "", }, map[string]any{ "authSources": []any{}, @@ -882,6 +911,7 @@ func TestLooker(t *testing.T) { "name": "folder", "required": false, "type": "string", + "default": "", }, }, }, @@ -920,6 +950,7 @@ func TestLooker(t *testing.T) { "name": "filter_type", "required": false, "type": "string", + "default": "field_filter", }, map[string]any{ "authSources": []any{}, @@ -955,6 +986,7 @@ func TestLooker(t *testing.T) { "name": "allow_multiple_values", "required": false, "type": "boolean", + "default": true, }, map[string]any{ "authSources": []any{}, @@ -962,6 +994,7 @@ func TestLooker(t *testing.T) { "name": "required", "required": false, "type": "boolean", + "default": false, }, }, }, @@ -1007,6 +1040,7 @@ func TestLooker(t *testing.T) { "description": "The filters for the query", "name": "filters", "required": false, + "default": map[string]any{}, "type": "object", }, map[string]any{ @@ -1022,6 +1056,7 @@ func TestLooker(t *testing.T) { "name": "pivots", "required": false, "type": "array", + "default": []any{}, }, map[string]any{ "authSources": []any{}, @@ -1036,6 +1071,7 @@ func TestLooker(t *testing.T) { "name": "sorts", "required": false, "type": "array", + "default": []any{}, }, map[string]any{ "authSources": []any{}, @@ -1043,6 +1079,7 @@ func TestLooker(t *testing.T) { "name": "limit", "required": false, "type": "integer", + "default": float64(500), }, map[string]any{ "authSources": []any{}, @@ -1064,6 +1101,7 @@ func TestLooker(t *testing.T) { "name": "title", "required": false, "type": "string", + "default": "", }, map[string]any{ "additionalProperties": true, @@ -1072,6 +1110,7 @@ func TestLooker(t *testing.T) { "name": "vis_config", "required": false, "type": "object", + "default": map[string]any{}, }, map[string]any{ "authSources": []any{}, @@ -1083,6 +1122,7 @@ func TestLooker(t *testing.T) { "name": "dashboard_filter", "required": false, "type": "object", + "default": map[string]any{}, }, "name": "dashboard_filters", "required": false, @@ -1181,6 +1221,7 @@ func TestLooker(t *testing.T) { "name": "timeframe", "required": false, "type": "integer", + "default": float64(90), }, map[string]any{ "authSources": []any{}, @@ -1188,6 +1229,7 @@ func TestLooker(t *testing.T) { "name": "min_queries", "required": false, "type": "integer", + "default": float64(0), }, }, }, @@ -1212,6 +1254,7 @@ func TestLooker(t *testing.T) { "name": "project", "required": false, "type": "string", + "default": "", }, map[string]any{ "authSources": []any{}, @@ -1219,6 +1262,7 @@ func TestLooker(t *testing.T) { "name": "model", "required": false, "type": "string", + "default": "", }, map[string]any{ "authSources": []any{}, @@ -1226,6 +1270,7 @@ func TestLooker(t *testing.T) { "name": "explore", "required": false, "type": "string", + "default": "", }, map[string]any{ "authSources": []any{}, @@ -1233,6 +1278,7 @@ func TestLooker(t *testing.T) { "name": "timeframe", "required": false, "type": "integer", + "default": float64(90), }, map[string]any{ "authSources": []any{}, @@ -1240,6 +1286,7 @@ func TestLooker(t *testing.T) { "name": "min_queries", "required": false, "type": "integer", + "default": float64(1), }, }, }, @@ -1257,6 +1304,7 @@ func TestLooker(t *testing.T) { "name": "devMode", "required": false, "type": "boolean", + "default": true, }, }, }, @@ -1410,6 +1458,7 @@ func TestLooker(t *testing.T) { "name": "type", "required": false, "type": "string", + "default": "", }, map[string]any{ "authSources": []any{}, @@ -1417,6 +1466,7 @@ func TestLooker(t *testing.T) { "name": "id", "required": false, "type": "string", + "default": "", }, }, }, diff --git a/tests/neo4j/neo4j_integration_test.go b/tests/neo4j/neo4j_integration_test.go index 816edba6e9e..5f2f357b4fd 100644 --- a/tests/neo4j/neo4j_integration_test.go +++ b/tests/neo4j/neo4j_integration_test.go @@ -165,6 +165,7 @@ func TestNeo4jToolEndpoints(t *testing.T) { "type": "boolean", "required": false, "description": "If set to true, the query will be validated and information about the execution will be returned without running the query. Defaults to false.", + "default": false, "authSources": []any{}, }, }, From 0b7ffa43645a65080a9dc8b6cb54329dc34f2430 Mon Sep 17 00:00:00 2001 From: Yuan Teoh <45984206+Yuan325@users.noreply.github.com> Date: Thu, 8 Jan 2026 13:30:35 -0800 Subject: [PATCH 11/23] chore: update mcp registry schema version (#2266) --- server.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.json b/server.json index fe2dfd9a822..d4be47cd8a5 100644 --- a/server.json +++ b/server.json @@ -1,5 +1,5 @@ { - "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json", + "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", "name": "io.github.googleapis/genai-toolbox", "description": "MCP Toolbox for Databases enables your agent to connect to your database.", "title": "MCP Toolbox", From 0cfac2984e614100b7fcd0a2dc3dcf7bc3fddcb3 Mon Sep 17 00:00:00 2001 From: Yuan Teoh <45984206+Yuan325@users.noreply.github.com> Date: Thu, 8 Jan 2026 13:52:05 -0800 Subject: [PATCH 12/23] fix(tools/alloydbainl): only add psv when NL Config Param is defined (#2265) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description PSV should only be required when when it is needed. Currently, we require psv even whenever user uses AlloyDB AI NL tool. This is due to the statement that we use to execute nl query. This PR modified the statement query to only utilize `param_names` and `param_values` when needed. Manually tested with a db that does not have psv installed. 🛠️ Fixes #1970 --- internal/tools/alloydbainl/alloydbainl.go | 29 ++++++++++------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/internal/tools/alloydbainl/alloydbainl.go b/internal/tools/alloydbainl/alloydbainl.go index 9a9ca5ebcba..117abd2ad5d 100644 --- a/internal/tools/alloydbainl/alloydbainl.go +++ b/internal/tools/alloydbainl/alloydbainl.go @@ -77,26 +77,21 @@ func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) placeholderParts = append(placeholderParts, fmt.Sprintf("$%d", i+3)) // $1, $2 reserved } - var paramNamesSQL string - var paramValuesSQL string - + var stmt string if numParams > 0 { - paramNamesSQL = fmt.Sprintf("ARRAY[%s]", strings.Join(quotedNameParts, ", ")) - paramValuesSQL = fmt.Sprintf("ARRAY[%s]", strings.Join(placeholderParts, ", ")) + paramNamesSQL := fmt.Sprintf("ARRAY[%s]", strings.Join(quotedNameParts, ", ")) + paramValuesSQL := fmt.Sprintf("ARRAY[%s]", strings.Join(placeholderParts, ", ")) + // execute_nl_query is the AlloyDB AI function that executes the natural language query + // The first parameter is the natural language query, which is passed as $1 + // The second parameter is the NLConfig, which is passed as a $2 + // The following params are the list of PSV values passed to the NLConfig + // Example SQL statement being executed: + // SELECT alloydb_ai_nl.execute_nl_query(nl_question => 'How many tickets do I have?', nl_config_id => 'cymbal_air_nl_config', param_names => ARRAY ['user_email'], param_values => ARRAY ['hailongli@google.com']); + stmtFormat := "SELECT alloydb_ai_nl.execute_nl_query(nl_question => $1, nl_config_id => $2, param_names => %s, param_values => %s);" + stmt = fmt.Sprintf(stmtFormat, paramNamesSQL, paramValuesSQL) } else { - paramNamesSQL = "ARRAY[]::TEXT[]" - paramValuesSQL = "ARRAY[]::TEXT[]" + stmt = "SELECT alloydb_ai_nl.execute_nl_query(nl_question => $1, nl_config_id => $2);" } - - // execute_nl_query is the AlloyDB AI function that executes the natural language query - // The first parameter is the natural language query, which is passed as $1 - // The second parameter is the NLConfig, which is passed as a $2 - // The following params are the list of PSV values passed to the NLConfig - // Example SQL statement being executed: - // SELECT alloydb_ai_nl.execute_nl_query(nl_question => 'How many tickets do I have?', nl_config_id => 'cymbal_air_nl_config', param_names => ARRAY ['user_email'], param_values => ARRAY ['hailongli@google.com']); - stmtFormat := "SELECT alloydb_ai_nl.execute_nl_query(nl_question => $1, nl_config_id => $2, param_names => %s, param_values => %s);" - stmt := fmt.Sprintf(stmtFormat, paramNamesSQL, paramValuesSQL) - newQuestionParam := parameters.NewStringParameter( "question", // name "The natural language question to ask.", // description From edaa1c23cfedcb5c2ea9faaf79dd29485b8d76d8 Mon Sep 17 00:00:00 2001 From: Yuan Teoh <45984206+Yuan325@users.noreply.github.com> Date: Thu, 8 Jan 2026 14:18:59 -0800 Subject: [PATCH 13/23] chore: update hugo for release (#2282) Update hugo version for release v0.25.0 --- .hugo/hugo.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.hugo/hugo.toml b/.hugo/hugo.toml index 27c2945a6ef..ed34b581caa 100644 --- a/.hugo/hugo.toml +++ b/.hugo/hugo.toml @@ -51,6 +51,10 @@ ignoreFiles = ["quickstart/shared", "quickstart/python", "quickstart/js", "quick # Add a new version block here before every release # The order of versions in this file is mirrored into the dropdown +[[params.versions]] + version = "v0.25.0" + url = "https://googleapis.github.io/genai-toolbox/v0.25.0/" + [[params.versions]] version = "v0.24.0" url = "https://googleapis.github.io/genai-toolbox/v0.24.0/" From be7560f606d16e79847258bdf31be920edb23e3d Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Thu, 8 Jan 2026 14:56:45 -0800 Subject: [PATCH 14/23] chore(main): release 0.25.0 (#2218) :robot: I have created a release *beep* *boop* --- ## [0.25.0](https://github.com/googleapis/genai-toolbox/compare/v0.24.0...v0.25.0) (2026-01-08) ### Features * Add `embeddingModel` support ([#2121](https://github.com/googleapis/genai-toolbox/issues/2121)) ([9c62f31](https://github.com/googleapis/genai-toolbox/commit/9c62f313ff5edf0a3b5b8a3e996eba078fba4095)) * Add `allowed-hosts` flag ([#2254](https://github.com/googleapis/genai-toolbox/issues/2254)) ([17b41f6](https://github.com/googleapis/genai-toolbox/commit/17b41f64531b8fe417c28ada45d1992ba430dc1b)) * Add parameter default value to manifest ([#2264](https://github.com/googleapis/genai-toolbox/issues/2264)) ([9d1feca](https://github.com/googleapis/genai-toolbox/commit/9d1feca10810fa42cb4c94a409252f1bd373ee36)) * **snowflake:** Add Snowflake Source and Tools ([#858](https://github.com/googleapis/genai-toolbox/issues/858)) ([b706b5b](https://github.com/googleapis/genai-toolbox/commit/b706b5bc685aeda277f277868bae77d38d5fd7b6)) * **prebuilt/cloud-sql-mysql:** Update CSQL MySQL prebuilt tools to use IAM ([#2202](https://github.com/googleapis/genai-toolbox/issues/2202)) ([731a32e](https://github.com/googleapis/genai-toolbox/commit/731a32e5360b4d6862d81fcb27d7127c655679a8)) * **sources/bigquery:** Make credentials scope configurable ([#2210](https://github.com/googleapis/genai-toolbox/issues/2210)) ([a450600](https://github.com/googleapis/genai-toolbox/commit/a4506009b93771b77fb05ae97044f914967e67ed)) * **sources/trino:** Add ssl verification options and fix docs example ([#2155](https://github.com/googleapis/genai-toolbox/issues/2155)) ([4a4cf1e](https://github.com/googleapis/genai-toolbox/commit/4a4cf1e712b671853678dba99c4dc49dd4fc16a2)) * **tools/looker:** Add ability to set destination folder with `make_look` and `make_dashboard`. ([#2245](https://github.com/googleapis/genai-toolbox/issues/2245)) ([eb79339](https://github.com/googleapis/genai-toolbox/commit/eb793398cd1cc4006d9808ccda5dc7aea5e92bd5)) * **tools/postgressql:** Add tool to list store procedure ([#2156](https://github.com/googleapis/genai-toolbox/issues/2156)) ([cf0fc51](https://github.com/googleapis/genai-toolbox/commit/cf0fc515b57d9b84770076f3c0c5597c4597ef62)) * **tools/postgressql:** Add Parameter `embeddedBy` config support ([#2151](https://github.com/googleapis/genai-toolbox/issues/2151)) ([17b70cc](https://github.com/googleapis/genai-toolbox/commit/17b70ccaa754d15bcc33a1a3ecb7e652520fa600)) ### Bug Fixes * **server:** Add `embeddingModel` config initialization ([#2281](https://github.com/googleapis/genai-toolbox/issues/2281)) ([a779975](https://github.com/googleapis/genai-toolbox/commit/a7799757c9345f99b6d2717841fbf792d364e1a2)) * **sources/cloudgda:** Add import for cloudgda source ([#2217](https://github.com/googleapis/genai-toolbox/issues/2217)) ([7daa411](https://github.com/googleapis/genai-toolbox/commit/7daa4111f4ebfb0a35319fd67a8f7b9f0f99efcf)) * **tools/alloydb-wait-for-operation:** Fix connection message generation ([#2228](https://github.com/googleapis/genai-toolbox/issues/2228)) ([7053fbb](https://github.com/googleapis/genai-toolbox/commit/7053fbb1953653143d39a8510916ea97a91022a6)) * **tools/alloydbainl:** Only add psv when NL Config Param is defined ([#2265](https://github.com/googleapis/genai-toolbox/issues/2265)) ([ef8f3b0](https://github.com/googleapis/genai-toolbox/commit/ef8f3b02f2f38ce94a6ba9acf35d08b9469bef4e)) * **tools/looker:** Looker client OAuth nil pointer error ([#2231](https://github.com/googleapis/genai-toolbox/issues/2231)) ([268700b](https://github.com/googleapis/genai-toolbox/commit/268700bdbf8281de0318d60ca613ed3672990b20)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --------- Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Co-authored-by: Yuan Teoh <45984206+Yuan325@users.noreply.github.com> --- CHANGELOG.md | 25 +++++++++++++++++++ README.md | 14 +++++------ cmd/version.txt | 2 +- .../en/getting-started/colab_quickstart.ipynb | 2 +- .../en/getting-started/introduction/_index.md | 14 +++++------ .../getting-started/mcp_quickstart/_index.md | 2 +- .../quickstart/shared/configure_toolbox.md | 2 +- docs/en/how-to/connect-ide/looker_mcp.md | 8 +++--- docs/en/how-to/connect-ide/mssql_mcp.md | 8 +++--- docs/en/how-to/connect-ide/mysql_mcp.md | 8 +++--- docs/en/how-to/connect-ide/neo4j_mcp.md | 8 +++--- docs/en/how-to/connect-ide/postgres_mcp.md | 8 +++--- docs/en/how-to/connect-ide/sqlite_mcp.md | 8 +++--- .../samples/alloydb/ai-nl/alloydb_ai_nl.ipynb | 2 +- docs/en/samples/alloydb/mcp_quickstart.md | 2 +- .../bigquery/colab_quickstart_bigquery.ipynb | 2 +- docs/en/samples/bigquery/local_quickstart.md | 2 +- .../samples/bigquery/mcp_quickstart/_index.md | 2 +- docs/en/samples/looker/looker_gemini.md | 2 +- .../looker/looker_gemini_oauth/_index.md | 2 +- .../looker/looker_mcp_inspector/_index.md | 2 +- gemini-extension.json | 2 +- server.json | 4 +-- 23 files changed, 78 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4fccb78d7c..06feb2a627d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,30 @@ # Changelog +## [0.25.0](https://github.com/googleapis/genai-toolbox/compare/v0.24.0...v0.25.0) (2026-01-08) + + +### Features + +* Add `embeddingModel` support ([#2121](https://github.com/googleapis/genai-toolbox/issues/2121)) ([9c62f31](https://github.com/googleapis/genai-toolbox/commit/9c62f313ff5edf0a3b5b8a3e996eba078fba4095)) +* Add `allowed-hosts` flag ([#2254](https://github.com/googleapis/genai-toolbox/issues/2254)) ([17b41f6](https://github.com/googleapis/genai-toolbox/commit/17b41f64531b8fe417c28ada45d1992ba430dc1b)) +* Add parameter default value to manifest ([#2264](https://github.com/googleapis/genai-toolbox/issues/2264)) ([9d1feca](https://github.com/googleapis/genai-toolbox/commit/9d1feca10810fa42cb4c94a409252f1bd373ee36)) +* **snowflake:** Add Snowflake Source and Tools ([#858](https://github.com/googleapis/genai-toolbox/issues/858)) ([b706b5b](https://github.com/googleapis/genai-toolbox/commit/b706b5bc685aeda277f277868bae77d38d5fd7b6)) +* **prebuilt/cloud-sql-mysql:** Update CSQL MySQL prebuilt tools to use IAM ([#2202](https://github.com/googleapis/genai-toolbox/issues/2202)) ([731a32e](https://github.com/googleapis/genai-toolbox/commit/731a32e5360b4d6862d81fcb27d7127c655679a8)) +* **sources/bigquery:** Make credentials scope configurable ([#2210](https://github.com/googleapis/genai-toolbox/issues/2210)) ([a450600](https://github.com/googleapis/genai-toolbox/commit/a4506009b93771b77fb05ae97044f914967e67ed)) +* **sources/trino:** Add ssl verification options and fix docs example ([#2155](https://github.com/googleapis/genai-toolbox/issues/2155)) ([4a4cf1e](https://github.com/googleapis/genai-toolbox/commit/4a4cf1e712b671853678dba99c4dc49dd4fc16a2)) +* **tools/looker:** Add ability to set destination folder with `make_look` and `make_dashboard`. ([#2245](https://github.com/googleapis/genai-toolbox/issues/2245)) ([eb79339](https://github.com/googleapis/genai-toolbox/commit/eb793398cd1cc4006d9808ccda5dc7aea5e92bd5)) +* **tools/postgressql:** Add tool to list store procedure ([#2156](https://github.com/googleapis/genai-toolbox/issues/2156)) ([cf0fc51](https://github.com/googleapis/genai-toolbox/commit/cf0fc515b57d9b84770076f3c0c5597c4597ef62)) +* **tools/postgressql:** Add Parameter `embeddedBy` config support ([#2151](https://github.com/googleapis/genai-toolbox/issues/2151)) ([17b70cc](https://github.com/googleapis/genai-toolbox/commit/17b70ccaa754d15bcc33a1a3ecb7e652520fa600)) + + +### Bug Fixes + +* **server:** Add `embeddingModel` config initialization ([#2281](https://github.com/googleapis/genai-toolbox/issues/2281)) ([a779975](https://github.com/googleapis/genai-toolbox/commit/a7799757c9345f99b6d2717841fbf792d364e1a2)) +* **sources/cloudgda:** Add import for cloudgda source ([#2217](https://github.com/googleapis/genai-toolbox/issues/2217)) ([7daa411](https://github.com/googleapis/genai-toolbox/commit/7daa4111f4ebfb0a35319fd67a8f7b9f0f99efcf)) +* **tools/alloydb-wait-for-operation:** Fix connection message generation ([#2228](https://github.com/googleapis/genai-toolbox/issues/2228)) ([7053fbb](https://github.com/googleapis/genai-toolbox/commit/7053fbb1953653143d39a8510916ea97a91022a6)) +* **tools/alloydbainl:** Only add psv when NL Config Param is defined ([#2265](https://github.com/googleapis/genai-toolbox/issues/2265)) ([ef8f3b0](https://github.com/googleapis/genai-toolbox/commit/ef8f3b02f2f38ce94a6ba9acf35d08b9469bef4e)) +* **tools/looker:** Looker client OAuth nil pointer error ([#2231](https://github.com/googleapis/genai-toolbox/issues/2231)) ([268700b](https://github.com/googleapis/genai-toolbox/commit/268700bdbf8281de0318d60ca613ed3672990b20)) + ## [0.24.0](https://github.com/googleapis/genai-toolbox/compare/v0.23.0...v0.24.0) (2025-12-19) diff --git a/README.md b/README.md index 13d08558d7a..0037577579c 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ To install Toolbox as a binary: > > ```sh > # see releases page for other versions -> export VERSION=0.24.0 +> export VERSION=0.25.0 > curl -L -o toolbox https://storage.googleapis.com/genai-toolbox/v$VERSION/linux/amd64/toolbox > chmod +x toolbox > ``` @@ -153,7 +153,7 @@ To install Toolbox as a binary: > > ```sh > # see releases page for other versions -> export VERSION=0.24.0 +> export VERSION=0.25.0 > curl -L -o toolbox https://storage.googleapis.com/genai-toolbox/v$VERSION/darwin/arm64/toolbox > chmod +x toolbox > ``` @@ -166,7 +166,7 @@ To install Toolbox as a binary: > > ```sh > # see releases page for other versions -> export VERSION=0.24.0 +> export VERSION=0.25.0 > curl -L -o toolbox https://storage.googleapis.com/genai-toolbox/v$VERSION/darwin/amd64/toolbox > chmod +x toolbox > ``` @@ -179,7 +179,7 @@ To install Toolbox as a binary: > > ```cmd > :: see releases page for other versions -> set VERSION=0.24.0 +> set VERSION=0.25.0 > curl -o toolbox.exe "https://storage.googleapis.com/genai-toolbox/v%VERSION%/windows/amd64/toolbox.exe" > ``` > @@ -191,7 +191,7 @@ To install Toolbox as a binary: > > ```powershell > # see releases page for other versions -> $VERSION = "0.24.0" +> $VERSION = "0.25.0" > curl.exe -o toolbox.exe "https://storage.googleapis.com/genai-toolbox/v$VERSION/windows/amd64/toolbox.exe" > ``` > @@ -204,7 +204,7 @@ You can also install Toolbox as a container: ```sh # see releases page for other versions -export VERSION=0.24.0 +export VERSION=0.25.0 docker pull us-central1-docker.pkg.dev/database-toolbox/toolbox/toolbox:$VERSION ``` @@ -228,7 +228,7 @@ To install from source, ensure you have the latest version of [Go installed](https://go.dev/doc/install), and then run the following command: ```sh -go install github.com/googleapis/genai-toolbox@v0.24.0 +go install github.com/googleapis/genai-toolbox@v0.25.0 ``` diff --git a/cmd/version.txt b/cmd/version.txt index 2094a100ca8..d21d277be51 100644 --- a/cmd/version.txt +++ b/cmd/version.txt @@ -1 +1 @@ -0.24.0 +0.25.0 diff --git a/docs/en/getting-started/colab_quickstart.ipynb b/docs/en/getting-started/colab_quickstart.ipynb index a2e2f989e0b..828a2197be9 100644 --- a/docs/en/getting-started/colab_quickstart.ipynb +++ b/docs/en/getting-started/colab_quickstart.ipynb @@ -234,7 +234,7 @@ }, "outputs": [], "source": [ - "version = \"0.24.0\" # x-release-please-version\n", + "version = \"0.25.0\" # x-release-please-version\n", "! curl -O https://storage.googleapis.com/genai-toolbox/v{version}/linux/amd64/toolbox\n", "\n", "# Make the binary executable\n", diff --git a/docs/en/getting-started/introduction/_index.md b/docs/en/getting-started/introduction/_index.md index f5f7d768365..5a42ca83a7a 100644 --- a/docs/en/getting-started/introduction/_index.md +++ b/docs/en/getting-started/introduction/_index.md @@ -103,7 +103,7 @@ To install Toolbox as a binary on Linux (AMD64): ```sh # see releases page for other versions -export VERSION=0.24.0 +export VERSION=0.25.0 curl -L -o toolbox https://storage.googleapis.com/genai-toolbox/v$VERSION/linux/amd64/toolbox chmod +x toolbox ``` @@ -114,7 +114,7 @@ To install Toolbox as a binary on macOS (Apple Silicon): ```sh # see releases page for other versions -export VERSION=0.24.0 +export VERSION=0.25.0 curl -L -o toolbox https://storage.googleapis.com/genai-toolbox/v$VERSION/darwin/arm64/toolbox chmod +x toolbox ``` @@ -125,7 +125,7 @@ To install Toolbox as a binary on macOS (Intel): ```sh # see releases page for other versions -export VERSION=0.24.0 +export VERSION=0.25.0 curl -L -o toolbox https://storage.googleapis.com/genai-toolbox/v$VERSION/darwin/amd64/toolbox chmod +x toolbox ``` @@ -136,7 +136,7 @@ To install Toolbox as a binary on Windows (Command Prompt): ```cmd :: see releases page for other versions -set VERSION=0.24.0 +set VERSION=0.25.0 curl -o toolbox.exe "https://storage.googleapis.com/genai-toolbox/v%VERSION%/windows/amd64/toolbox.exe" ``` @@ -146,7 +146,7 @@ To install Toolbox as a binary on Windows (PowerShell): ```powershell # see releases page for other versions -$VERSION = "0.24.0" +$VERSION = "0.25.0" curl.exe -o toolbox.exe "https://storage.googleapis.com/genai-toolbox/v$VERSION/windows/amd64/toolbox.exe" ``` @@ -158,7 +158,7 @@ You can also install Toolbox as a container: ```sh # see releases page for other versions -export VERSION=0.24.0 +export VERSION=0.25.0 docker pull us-central1-docker.pkg.dev/database-toolbox/toolbox/toolbox:$VERSION ``` @@ -177,7 +177,7 @@ To install from source, ensure you have the latest version of [Go installed](https://go.dev/doc/install), and then run the following command: ```sh -go install github.com/googleapis/genai-toolbox@v0.24.0 +go install github.com/googleapis/genai-toolbox@v0.25.0 ``` {{% /tab %}} diff --git a/docs/en/getting-started/mcp_quickstart/_index.md b/docs/en/getting-started/mcp_quickstart/_index.md index f07528d2bf6..f2b10c7bd0f 100644 --- a/docs/en/getting-started/mcp_quickstart/_index.md +++ b/docs/en/getting-started/mcp_quickstart/_index.md @@ -105,7 +105,7 @@ In this section, we will download Toolbox, configure our tools in a ```bash export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64 - curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/$OS/toolbox + curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/$OS/toolbox ``` diff --git a/docs/en/getting-started/quickstart/shared/configure_toolbox.md b/docs/en/getting-started/quickstart/shared/configure_toolbox.md index dda247e2ef8..edfbb7f3ad1 100644 --- a/docs/en/getting-started/quickstart/shared/configure_toolbox.md +++ b/docs/en/getting-started/quickstart/shared/configure_toolbox.md @@ -13,7 +13,7 @@ In this section, we will download Toolbox, configure our tools in a ```bash export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64 - curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/$OS/toolbox + curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/$OS/toolbox ``` diff --git a/docs/en/how-to/connect-ide/looker_mcp.md b/docs/en/how-to/connect-ide/looker_mcp.md index 82b8575d1c2..226ad0b78b8 100644 --- a/docs/en/how-to/connect-ide/looker_mcp.md +++ b/docs/en/how-to/connect-ide/looker_mcp.md @@ -100,19 +100,19 @@ After you install Looker in the MCP Store, resources and tools from the server a {{< tabpane persist=header >}} {{< tab header="linux/amd64" lang="bash" >}} -curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/linux/amd64/toolbox +curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/linux/amd64/toolbox {{< /tab >}} {{< tab header="darwin/arm64" lang="bash" >}} -curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/darwin/arm64/toolbox +curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/darwin/arm64/toolbox {{< /tab >}} {{< tab header="darwin/amd64" lang="bash" >}} -curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/darwin/amd64/toolbox +curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/darwin/amd64/toolbox {{< /tab >}} {{< tab header="windows/amd64" lang="bash" >}} -curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/windows/amd64/toolbox.exe +curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/windows/amd64/toolbox.exe {{< /tab >}} {{< /tabpane >}} diff --git a/docs/en/how-to/connect-ide/mssql_mcp.md b/docs/en/how-to/connect-ide/mssql_mcp.md index defb5f0e185..85203567e1f 100644 --- a/docs/en/how-to/connect-ide/mssql_mcp.md +++ b/docs/en/how-to/connect-ide/mssql_mcp.md @@ -45,19 +45,19 @@ instance: {{< tabpane persist=header >}} {{< tab header="linux/amd64" lang="bash" >}} -curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/linux/amd64/toolbox +curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/linux/amd64/toolbox {{< /tab >}} {{< tab header="darwin/arm64" lang="bash" >}} -curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/darwin/arm64/toolbox +curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/darwin/arm64/toolbox {{< /tab >}} {{< tab header="darwin/amd64" lang="bash" >}} -curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/darwin/amd64/toolbox +curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/darwin/amd64/toolbox {{< /tab >}} {{< tab header="windows/amd64" lang="bash" >}} -curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/windows/amd64/toolbox.exe +curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/windows/amd64/toolbox.exe {{< /tab >}} {{< /tabpane >}} diff --git a/docs/en/how-to/connect-ide/mysql_mcp.md b/docs/en/how-to/connect-ide/mysql_mcp.md index 0d8d5a1ba52..e365c57040a 100644 --- a/docs/en/how-to/connect-ide/mysql_mcp.md +++ b/docs/en/how-to/connect-ide/mysql_mcp.md @@ -43,19 +43,19 @@ expose your developer assistant tools to a MySQL instance: {{< tabpane persist=header >}} {{< tab header="linux/amd64" lang="bash" >}} -curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/linux/amd64/toolbox +curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/linux/amd64/toolbox {{< /tab >}} {{< tab header="darwin/arm64" lang="bash" >}} -curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/darwin/arm64/toolbox +curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/darwin/arm64/toolbox {{< /tab >}} {{< tab header="darwin/amd64" lang="bash" >}} -curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/darwin/amd64/toolbox +curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/darwin/amd64/toolbox {{< /tab >}} {{< tab header="windows/amd64" lang="bash" >}} -curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/windows/amd64/toolbox.exe +curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/windows/amd64/toolbox.exe {{< /tab >}} {{< /tabpane >}} diff --git a/docs/en/how-to/connect-ide/neo4j_mcp.md b/docs/en/how-to/connect-ide/neo4j_mcp.md index 56795aef0fd..158974506f5 100644 --- a/docs/en/how-to/connect-ide/neo4j_mcp.md +++ b/docs/en/how-to/connect-ide/neo4j_mcp.md @@ -44,19 +44,19 @@ expose your developer assistant tools to a Neo4j instance: {{< tabpane persist=header >}} {{< tab header="linux/amd64" lang="bash" >}} -curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/linux/amd64/toolbox +curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/linux/amd64/toolbox {{< /tab >}} {{< tab header="darwin/arm64" lang="bash" >}} -curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/darwin/arm64/toolbox +curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/darwin/arm64/toolbox {{< /tab >}} {{< tab header="darwin/amd64" lang="bash" >}} -curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/darwin/amd64/toolbox +curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/darwin/amd64/toolbox {{< /tab >}} {{< tab header="windows/amd64" lang="bash" >}} -curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/windows/amd64/toolbox.exe +curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/windows/amd64/toolbox.exe {{< /tab >}} {{< /tabpane >}} diff --git a/docs/en/how-to/connect-ide/postgres_mcp.md b/docs/en/how-to/connect-ide/postgres_mcp.md index 6ec92b948ec..8339b65a8af 100644 --- a/docs/en/how-to/connect-ide/postgres_mcp.md +++ b/docs/en/how-to/connect-ide/postgres_mcp.md @@ -56,19 +56,19 @@ Omni](https://cloud.google.com/alloydb/omni/current/docs/overview). {{< tabpane persist=header >}} {{< tab header="linux/amd64" lang="bash" >}} -curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/linux/amd64/toolbox +curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/linux/amd64/toolbox {{< /tab >}} {{< tab header="darwin/arm64" lang="bash" >}} -curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/darwin/arm64/toolbox +curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/darwin/arm64/toolbox {{< /tab >}} {{< tab header="darwin/amd64" lang="bash" >}} -curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/darwin/amd64/toolbox +curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/darwin/amd64/toolbox {{< /tab >}} {{< tab header="windows/amd64" lang="bash" >}} -curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/windows/amd64/toolbox.exe +curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/windows/amd64/toolbox.exe {{< /tab >}} {{< /tabpane >}} diff --git a/docs/en/how-to/connect-ide/sqlite_mcp.md b/docs/en/how-to/connect-ide/sqlite_mcp.md index 1493a71885f..c36f2902b20 100644 --- a/docs/en/how-to/connect-ide/sqlite_mcp.md +++ b/docs/en/how-to/connect-ide/sqlite_mcp.md @@ -43,19 +43,19 @@ to expose your developer assistant tools to a SQLite instance: {{< tabpane persist=header >}} {{< tab header="linux/amd64" lang="bash" >}} -curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/linux/amd64/toolbox +curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/linux/amd64/toolbox {{< /tab >}} {{< tab header="darwin/arm64" lang="bash" >}} -curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/darwin/arm64/toolbox +curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/darwin/arm64/toolbox {{< /tab >}} {{< tab header="darwin/amd64" lang="bash" >}} -curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/darwin/amd64/toolbox +curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/darwin/amd64/toolbox {{< /tab >}} {{< tab header="windows/amd64" lang="bash" >}} -curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/windows/amd64/toolbox.exe +curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/windows/amd64/toolbox.exe {{< /tab >}} {{< /tabpane >}} diff --git a/docs/en/samples/alloydb/ai-nl/alloydb_ai_nl.ipynb b/docs/en/samples/alloydb/ai-nl/alloydb_ai_nl.ipynb index fc8e5300b14..4897937b189 100644 --- a/docs/en/samples/alloydb/ai-nl/alloydb_ai_nl.ipynb +++ b/docs/en/samples/alloydb/ai-nl/alloydb_ai_nl.ipynb @@ -771,7 +771,7 @@ }, "outputs": [], "source": [ - "version = \"0.24.0\" # x-release-please-version\n", + "version = \"0.25.0\" # x-release-please-version\n", "! curl -L -o /content/toolbox https://storage.googleapis.com/genai-toolbox/v{version}/linux/amd64/toolbox\n", "\n", "# Make the binary executable\n", diff --git a/docs/en/samples/alloydb/mcp_quickstart.md b/docs/en/samples/alloydb/mcp_quickstart.md index c047416428d..ab15c867a67 100644 --- a/docs/en/samples/alloydb/mcp_quickstart.md +++ b/docs/en/samples/alloydb/mcp_quickstart.md @@ -123,7 +123,7 @@ In this section, we will download and install the Toolbox binary. ```bash export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64 - export VERSION="0.24.0" + export VERSION="0.25.0" curl -O https://storage.googleapis.com/genai-toolbox/v$VERSION/$OS/toolbox ``` diff --git a/docs/en/samples/bigquery/colab_quickstart_bigquery.ipynb b/docs/en/samples/bigquery/colab_quickstart_bigquery.ipynb index eb551ca0152..9a2424c77c4 100644 --- a/docs/en/samples/bigquery/colab_quickstart_bigquery.ipynb +++ b/docs/en/samples/bigquery/colab_quickstart_bigquery.ipynb @@ -220,7 +220,7 @@ }, "outputs": [], "source": [ - "version = \"0.24.0\" # x-release-please-version\n", + "version = \"0.25.0\" # x-release-please-version\n", "! curl -O https://storage.googleapis.com/genai-toolbox/v{version}/linux/amd64/toolbox\n", "\n", "# Make the binary executable\n", diff --git a/docs/en/samples/bigquery/local_quickstart.md b/docs/en/samples/bigquery/local_quickstart.md index badda3f75ea..01cc5d1937c 100644 --- a/docs/en/samples/bigquery/local_quickstart.md +++ b/docs/en/samples/bigquery/local_quickstart.md @@ -179,7 +179,7 @@ to use BigQuery, and then run the Toolbox server. ```bash export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64 - curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/$OS/toolbox + curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/$OS/toolbox ``` diff --git a/docs/en/samples/bigquery/mcp_quickstart/_index.md b/docs/en/samples/bigquery/mcp_quickstart/_index.md index 6f0b44d18be..ef116a2e7df 100644 --- a/docs/en/samples/bigquery/mcp_quickstart/_index.md +++ b/docs/en/samples/bigquery/mcp_quickstart/_index.md @@ -98,7 +98,7 @@ In this section, we will download Toolbox, configure our tools in a ```bash export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64 - curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/$OS/toolbox + curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/$OS/toolbox ``` diff --git a/docs/en/samples/looker/looker_gemini.md b/docs/en/samples/looker/looker_gemini.md index 0fc81afc32e..1d209857f9a 100644 --- a/docs/en/samples/looker/looker_gemini.md +++ b/docs/en/samples/looker/looker_gemini.md @@ -34,7 +34,7 @@ In this section, we will download Toolbox and run the Toolbox server. ```bash export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64 - curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/$OS/toolbox + curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/$OS/toolbox ``` diff --git a/docs/en/samples/looker/looker_gemini_oauth/_index.md b/docs/en/samples/looker/looker_gemini_oauth/_index.md index 6eb730ceee9..03f016a7363 100644 --- a/docs/en/samples/looker/looker_gemini_oauth/_index.md +++ b/docs/en/samples/looker/looker_gemini_oauth/_index.md @@ -48,7 +48,7 @@ In this section, we will download Toolbox and run the Toolbox server. ```bash export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64 - curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/$OS/toolbox + curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/$OS/toolbox ``` diff --git a/docs/en/samples/looker/looker_mcp_inspector/_index.md b/docs/en/samples/looker/looker_mcp_inspector/_index.md index ef3a51c4e96..3aa9c24a86e 100644 --- a/docs/en/samples/looker/looker_mcp_inspector/_index.md +++ b/docs/en/samples/looker/looker_mcp_inspector/_index.md @@ -34,7 +34,7 @@ In this section, we will download Toolbox and run the Toolbox server. ```bash export OS="linux/amd64" # one of linux/amd64, darwin/arm64, darwin/amd64, or windows/amd64 - curl -O https://storage.googleapis.com/genai-toolbox/v0.24.0/$OS/toolbox + curl -O https://storage.googleapis.com/genai-toolbox/v0.25.0/$OS/toolbox ``` diff --git a/gemini-extension.json b/gemini-extension.json index b068279cd67..d01fdb85695 100644 --- a/gemini-extension.json +++ b/gemini-extension.json @@ -1,6 +1,6 @@ { "name": "mcp-toolbox-for-databases", - "version": "0.24.0", + "version": "0.25.0", "description": "MCP Toolbox for Databases is an open-source MCP server for more than 30 different datasources.", "contextFileName": "MCP-TOOLBOX-EXTENSION.md" } \ No newline at end of file diff --git a/server.json b/server.json index d4be47cd8a5..b72200be3a3 100644 --- a/server.json +++ b/server.json @@ -14,11 +14,11 @@ "url": "https://github.com/googleapis/genai-toolbox", "source": "github" }, - "version": "0.24.0", + "version": "0.25.0", "packages": [ { "registryType": "oci", - "identifier": "us-central1-docker.pkg.dev/database-toolbox/toolbox/toolbox:0.24.0", + "identifier": "us-central1-docker.pkg.dev/database-toolbox/toolbox/toolbox:0.25.0", "transport": { "type": "streamable-http", "url": "http://{host}:{port}/mcp" From 87ff9150d27fc2ff6c1ee0535f8aba0638ddb1dc Mon Sep 17 00:00:00 2001 From: rahulpinto19 Date: Fri, 9 Jan 2026 06:43:49 +0000 Subject: [PATCH 15/23] printign in common.go --- tests/common.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/common.go b/tests/common.go index aff8c7272a4..448b4907d77 100644 --- a/tests/common.go +++ b/tests/common.go @@ -960,6 +960,7 @@ func CleanupPostgresTables(t *testing.T, ctx context.Context, pool *pgxpool.Pool } if len(tablesToDrop) == 0 { + t.Logf("No tables to drop in 'public' schema") return } @@ -968,6 +969,9 @@ func CleanupPostgresTables(t *testing.T, ctx context.Context, pool *pgxpool.Pool if _, err := pool.Exec(ctx, dropQuery); err != nil { t.Fatalf("Failed to drop all tables in 'public' schema: %v", err) } + else { { + t.Logf("Dropped tables in 'public' schema: %s", strings.Join(tablesToDrop, ", ")) + } // // 1. Drop the entire public schema (this kills tables, views, types, etc.) // dropSchema := "DROP SCHEMA public CASCADE;" From cc3c52731d104b4cabf1ea4a1979d394aa0f50ba Mon Sep 17 00:00:00 2001 From: rahulpinto19 Date: Fri, 9 Jan 2026 06:53:10 +0000 Subject: [PATCH 16/23] test --- tests/common.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/common.go b/tests/common.go index 448b4907d77..5d23abe23f4 100644 --- a/tests/common.go +++ b/tests/common.go @@ -969,7 +969,7 @@ func CleanupPostgresTables(t *testing.T, ctx context.Context, pool *pgxpool.Pool if _, err := pool.Exec(ctx, dropQuery); err != nil { t.Fatalf("Failed to drop all tables in 'public' schema: %v", err) } - else { { + else { t.Logf("Dropped tables in 'public' schema: %s", strings.Join(tablesToDrop, ", ")) } From 6ff567df47476211aa6239eb088a9c0e92a1a7c0 Mon Sep 17 00:00:00 2001 From: rahulpinto19 Date: Fri, 9 Jan 2026 07:03:44 +0000 Subject: [PATCH 17/23] test --- tests/common.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/common.go b/tests/common.go index 5d23abe23f4..180cf97d7cc 100644 --- a/tests/common.go +++ b/tests/common.go @@ -969,9 +969,9 @@ func CleanupPostgresTables(t *testing.T, ctx context.Context, pool *pgxpool.Pool if _, err := pool.Exec(ctx, dropQuery); err != nil { t.Fatalf("Failed to drop all tables in 'public' schema: %v", err) } - else { - t.Logf("Dropped tables in 'public' schema: %s", strings.Join(tablesToDrop, ", ")) - } + + t.Logf("Dropped tables in 'public' schema: %s", strings.Join(tablesToDrop, ", ")) + // // 1. Drop the entire public schema (this kills tables, views, types, etc.) // dropSchema := "DROP SCHEMA public CASCADE;" From 65b21960fa6c9f79db446388f6a69345fe4b36d0 Mon Sep 17 00:00:00 2001 From: rahulpinto19 Date: Fri, 9 Jan 2026 08:42:14 +0000 Subject: [PATCH 18/23] test --- tests/common.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/common.go b/tests/common.go index 180cf97d7cc..978be5ac867 100644 --- a/tests/common.go +++ b/tests/common.go @@ -213,12 +213,16 @@ func AddPostgresPrebuiltConfig(t *testing.T, config map[string]any) map[string]a PostgresListPGSettingsToolKind = "postgres-list-pg-settings" PostgresListDatabaseStatsToolKind = "postgres-list-database-stats" PostgresListRolesToolKind = "postgres-list-roles" + PostgresListStoredProcedureToolKind = "postgres-list-stored-procedure" + + ) tools, ok := config["tools"].(map[string]any) if !ok { t.Fatalf("unable to get tools from config") } + tools["list_tables"] = map[string]any{ "kind": PostgresListTablesToolKind, "source": "my-instance", @@ -310,6 +314,11 @@ func AddPostgresPrebuiltConfig(t *testing.T, config map[string]any) map[string]a "kind": PostgresListRolesToolKind, "source": "my-instance", } + + tools["list_stored_procedure"] = map[string]any{ + "kind": PostgresListStoredProcedureToolKind, + "source": "my-instance", + } config["tools"] = tools return config } From eedd0ab67f3d561ab68f993485f84b8de967d193 Mon Sep 17 00:00:00 2001 From: Yuan Teoh <45984206+Yuan325@users.noreply.github.com> Date: Fri, 9 Jan 2026 11:21:41 -0800 Subject: [PATCH 19/23] docs: add issue and pr triaging and SLO (#2257) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description update docs to reflect triaging workflow and SLO ## PR Checklist > Thank you for opening a Pull Request! Before submitting your PR, there are a > few things you can do to make sure it goes smoothly: - [x] Make sure you reviewed [CONTRIBUTING.md](https://github.com/googleapis/genai-toolbox/blob/main/CONTRIBUTING.md) - [x] Make sure to open an issue as a [bug/issue](https://github.com/googleapis/genai-toolbox/issues/new/choose) before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea - [x] Ensure the tests and linter pass - [x] Code coverage does not decrease (if any source code was changed) - [x] Appropriate docs were updated (if necessary) - [x] Make sure to add `!` if this involve a breaking change 🛠️ Fixes # --- DEVELOPER.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/DEVELOPER.md b/DEVELOPER.md index bd8c49913e9..ce9f8270701 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -379,6 +379,23 @@ to approve PRs for main. TeamSync is used to create this team from the MDB Group `toolbox-contributors`. Googlers who are developing for MCP-Toolbox but aren't part of the core team should join this group. +### Issue/PR Triage and SLO +After an issue is created, maintainers will assign the following labels: +* `Priority` (defaulted to P0) +* `Type` (if applicable) +* `Product` (if applicable) + +All incoming issues and PRs will follow the following SLO: +| Type | Priority | Objective | +|-----------------|----------|------------------------------------------------------------------------| +| Feature Request | P0 | Must respond within **5 days** | +| Process | P0 | Must respond within **5 days** | +| Bugs | P0 | Must respond within **5 days**, and resolve/closure within **14 days** | +| Bugs | P1 | Must respond within **7 days**, and resolve/closure within **90 days** | +| Bugs | P2 | Must respond within **30 days** + +_Types that are not listed in the table do not adhere to any SLO._ + ### Releasing Toolbox has two types of releases: versioned and continuous. It uses Google From 49d255254c1cebdf10d7bbb83f81e5ada6171cd2 Mon Sep 17 00:00:00 2001 From: Shobhit Singh Date: Fri, 9 Jan 2026 12:43:46 -0800 Subject: [PATCH 20/23] feat(bigquery): make maximum rows returned from queries configurable (#2262) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change allows the agent developer to control the maxium number of rows returned from tools running BigQuery SQL query. Using this feature the agent developer could limit how large output is presented to LLM in an agentic user journey. ## Description > Should include a concise description of the changes (bug or feature), it's > impact, along with a summary of the solution ## PR Checklist > Thank you for opening a Pull Request! Before submitting your PR, there are a > few things you can do to make sure it goes smoothly: - [x] Make sure you reviewed [CONTRIBUTING.md](https://github.com/googleapis/genai-toolbox/blob/main/CONTRIBUTING.md) - [ ] Make sure to open an issue https://github.com/googleapis/genai-toolbox/issues/2261 before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea - [x] Ensure the tests and linter pass - [x] Code coverage does not decrease (if any source code was changed) - [x] Appropriate docs were updated (if necessary) - [ ] Make sure to add `!` if this involve a breaking change 🛠️ Fixes #2261 2261 --- docs/en/resources/sources/bigquery.md | 3 + internal/prebuiltconfigs/tools/bigquery.yaml | 1 + internal/sources/bigquery/bigquery.go | 9 ++- internal/sources/bigquery/bigquery_test.go | 76 ++++++++++++++++++++ 4 files changed, 87 insertions(+), 2 deletions(-) diff --git a/docs/en/resources/sources/bigquery.md b/docs/en/resources/sources/bigquery.md index 42f6a985b37..8cb05ccbb80 100644 --- a/docs/en/resources/sources/bigquery.md +++ b/docs/en/resources/sources/bigquery.md @@ -134,6 +134,7 @@ sources: # scopes: # Optional: List of OAuth scopes to request. # - "https://www.googleapis.com/auth/bigquery" # - "https://www.googleapis.com/auth/drive.readonly" + # maxQueryResultRows: 50 # Optional: Limits the number of rows returned by queries. Defaults to 50. ``` Initialize a BigQuery source that uses the client's access token: @@ -153,6 +154,7 @@ sources: # scopes: # Optional: List of OAuth scopes to request. # - "https://www.googleapis.com/auth/bigquery" # - "https://www.googleapis.com/auth/drive.readonly" + # maxQueryResultRows: 50 # Optional: Limits the number of rows returned by queries. Defaults to 50. ``` ## Reference @@ -167,3 +169,4 @@ sources: | useClientOAuth | bool | false | If true, forwards the client's OAuth access token from the "Authorization" header to downstream queries. **Note:** This cannot be used with `writeMode: protected`. | | scopes | []string | false | A list of OAuth 2.0 scopes to use for the credentials. If not provided, default scopes are used. | | impersonateServiceAccount | string | false | Service account email to impersonate when making BigQuery and Dataplex API calls. The authenticated principal must have the `roles/iam.serviceAccountTokenCreator` role on the target service account. [Learn More](https://cloud.google.com/iam/docs/service-account-impersonation) | +| maxQueryResultRows | int | false | The maximum number of rows to return from a query. Defaults to 50. | diff --git a/internal/prebuiltconfigs/tools/bigquery.yaml b/internal/prebuiltconfigs/tools/bigquery.yaml index 8fdff895910..f1e67b516e5 100644 --- a/internal/prebuiltconfigs/tools/bigquery.yaml +++ b/internal/prebuiltconfigs/tools/bigquery.yaml @@ -19,6 +19,7 @@ sources: location: ${BIGQUERY_LOCATION:} useClientOAuth: ${BIGQUERY_USE_CLIENT_OAUTH:false} scopes: ${BIGQUERY_SCOPES:} + maxQueryResultRows: ${BIGQUERY_MAX_QUERY_RESULT_ROWS:50} tools: analyze_contribution: diff --git a/internal/sources/bigquery/bigquery.go b/internal/sources/bigquery/bigquery.go index 8f3c3f7d2e7..b24378c858e 100644 --- a/internal/sources/bigquery/bigquery.go +++ b/internal/sources/bigquery/bigquery.go @@ -89,6 +89,7 @@ type Config struct { UseClientOAuth bool `yaml:"useClientOAuth"` ImpersonateServiceAccount string `yaml:"impersonateServiceAccount"` Scopes StringOrStringSlice `yaml:"scopes"` + MaxQueryResultRows int `yaml:"maxQueryResultRows"` } // StringOrStringSlice is a custom type that can unmarshal both a single string @@ -127,6 +128,10 @@ func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.So r.WriteMode = WriteModeAllowed } + if r.MaxQueryResultRows == 0 { + r.MaxQueryResultRows = 50 + } + if r.WriteMode == WriteModeProtected && r.UseClientOAuth { // The protected mode only allows write operations to the session's temporary datasets. // when using client OAuth, a new session is created every @@ -150,7 +155,7 @@ func (r Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.So Client: client, RestService: restService, TokenSource: tokenSource, - MaxQueryResultRows: 50, + MaxQueryResultRows: r.MaxQueryResultRows, ClientCreator: clientCreator, } @@ -567,7 +572,7 @@ func (s *Source) RunSQL(ctx context.Context, bqClient *bigqueryapi.Client, state } var out []any - for { + for s.MaxQueryResultRows <= 0 || len(out) < s.MaxQueryResultRows { var val []bigqueryapi.Value err = it.Next(&val) if err == iterator.Done { diff --git a/internal/sources/bigquery/bigquery_test.go b/internal/sources/bigquery/bigquery_test.go index 81afcf27119..a23f1b47bd6 100644 --- a/internal/sources/bigquery/bigquery_test.go +++ b/internal/sources/bigquery/bigquery_test.go @@ -21,9 +21,12 @@ import ( yaml "github.com/goccy/go-yaml" "github.com/google/go-cmp/cmp" + "go.opentelemetry.io/otel/trace/noop" + "github.com/googleapis/genai-toolbox/internal/server" "github.com/googleapis/genai-toolbox/internal/sources/bigquery" "github.com/googleapis/genai-toolbox/internal/testutils" + "github.com/googleapis/genai-toolbox/internal/util" ) func TestParseFromYamlBigQuery(t *testing.T) { @@ -154,6 +157,26 @@ func TestParseFromYamlBigQuery(t *testing.T) { }, }, }, + { + desc: "with max query result rows example", + in: ` + sources: + my-instance: + kind: bigquery + project: my-project + location: us + maxQueryResultRows: 10 + `, + want: server.SourceConfigs{ + "my-instance": bigquery.Config{ + Name: "my-instance", + Kind: bigquery.SourceKind, + Project: "my-project", + Location: "us", + MaxQueryResultRows: 10, + }, + }, + }, } for _, tc := range tcs { t.Run(tc.desc, func(t *testing.T) { @@ -220,6 +243,59 @@ func TestFailParseFromYaml(t *testing.T) { } } +func TestInitialize_MaxQueryResultRows(t *testing.T) { + ctx, err := testutils.ContextWithNewLogger() + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + ctx = util.WithUserAgent(ctx, "test-agent") + tracer := noop.NewTracerProvider().Tracer("") + + tcs := []struct { + desc string + cfg bigquery.Config + want int + }{ + { + desc: "default value", + cfg: bigquery.Config{ + Name: "test-default", + Kind: bigquery.SourceKind, + Project: "test-project", + UseClientOAuth: true, + }, + want: 50, + }, + { + desc: "configured value", + cfg: bigquery.Config{ + Name: "test-configured", + Kind: bigquery.SourceKind, + Project: "test-project", + UseClientOAuth: true, + MaxQueryResultRows: 100, + }, + want: 100, + }, + } + + for _, tc := range tcs { + t.Run(tc.desc, func(t *testing.T) { + src, err := tc.cfg.Initialize(ctx, tracer) + if err != nil { + t.Fatalf("Initialize failed: %v", err) + } + bqSrc, ok := src.(*bigquery.Source) + if !ok { + t.Fatalf("Expected *bigquery.Source, got %T", src) + } + if bqSrc.MaxQueryResultRows != tc.want { + t.Errorf("MaxQueryResultRows = %d, want %d", bqSrc.MaxQueryResultRows, tc.want) + } + }) + } +} + func TestNormalizeValue(t *testing.T) { tests := []struct { name string From c835af431aacf99463c47e2ee1eac9e52c855672 Mon Sep 17 00:00:00 2001 From: rahulpinto19 Date: Mon, 12 Jan 2026 06:13:48 +0000 Subject: [PATCH 21/23] test --- tests/common.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/common.go b/tests/common.go index 978be5ac867..a8851d3d641 100644 --- a/tests/common.go +++ b/tests/common.go @@ -972,7 +972,8 @@ func CleanupPostgresTables(t *testing.T, ctx context.Context, pool *pgxpool.Pool t.Logf("No tables to drop in 'public' schema") return } - + t.Logf("Tables to drop in 'public' schema: %s", strings.Join(tablesToDrop, ", ")) + dropQuery := fmt.Sprintf("DROP TABLE IF EXISTS %s CASCADE;", strings.Join(tablesToDrop, ", ")) if _, err := pool.Exec(ctx, dropQuery); err != nil { From fd26fa4433ec050d133e67c84505ee795937e626 Mon Sep 17 00:00:00 2001 From: Wenxin Du <117315983+duwenxin99@users.noreply.github.com> Date: Fri, 16 Jan 2026 12:08:47 -0500 Subject: [PATCH 22/23] docs: fix redis array sample (#2301) The Redis tool code sample is missing the "items" field for the array parameter, causing confusion. fix: https://github.com/googleapis/genai-toolbox/issues/2293 --- docs/en/resources/tools/redis/redis.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/resources/tools/redis/redis.md b/docs/en/resources/tools/redis/redis.md index ed9730ef112..53dc344d6ab 100644 --- a/docs/en/resources/tools/redis/redis.md +++ b/docs/en/resources/tools/redis/redis.md @@ -30,6 +30,10 @@ following config for example: - name: userNames type: array description: The user names to be set. + items: + name: userName # the item name doesn't matter but it has to exist + type: string + description: username ``` If the input is an array of strings `["Alice", "Sid", "Bob"]`, The final command From 2bf2461e9ef55f328917af68fa90509dda36d749 Mon Sep 17 00:00:00 2001 From: Juexin Wang <51683731+wangauone@users.noreply.github.com> Date: Fri, 16 Jan 2026 10:57:06 -0800 Subject: [PATCH 23/23] docs(tools/cloudgda): update cloud gda datasource references note (#2326) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description Update the GDA source document to clarify that only `AlloyDbReference`, `SpannerReference`, and `CloudSqlReference` are supported. ## PR Checklist > Thank you for opening a Pull Request! Before submitting your PR, there are a > few things you can do to make sure it goes smoothly: - [x] Make sure you reviewed [CONTRIBUTING.md](https://github.com/googleapis/genai-toolbox/blob/main/CONTRIBUTING.md) - [x] Make sure to open an issue as a [bug/issue](https://github.com/googleapis/genai-toolbox/issues/new/choose) before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea - [x] Ensure the tests and linter pass - [x] Code coverage does not decrease (if any source code was changed) - [x] Appropriate docs were updated (if necessary) - [x] Make sure to add `!` if this involve a breaking change 🛠️ Fixes #2324 --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- docs/en/resources/tools/cloudgda/cloud-gda-query.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/en/resources/tools/cloudgda/cloud-gda-query.md b/docs/en/resources/tools/cloudgda/cloud-gda-query.md index 0ec1812d62d..39e5bf64ae0 100644 --- a/docs/en/resources/tools/cloudgda/cloud-gda-query.md +++ b/docs/en/resources/tools/cloudgda/cloud-gda-query.md @@ -12,6 +12,9 @@ aliases: The `cloud-gemini-data-analytics-query` tool allows you to send natural language questions to the Gemini Data Analytics API and receive structured responses containing SQL queries, natural language answers, and explanations. For details on defining data agent context for database data sources, see the official [documentation](https://docs.cloud.google.com/gemini/docs/conversational-analytics-api/data-agent-authored-context-databases). +> [!NOTE] +> Only `alloydb`, `spannerReference`, and `cloudSqlReference` are supported as [datasource references](https://clouddocs.devsite.corp.google.com/gemini/docs/conversational-analytics-api/reference/rest/v1beta/projects.locations.dataAgents#DatasourceReferences). + ## Example ```yaml