mirror of
https://github.com/googleapis/genai-toolbox.git
synced 2026-01-14 18:08:05 -05:00
Compare commits
2 Commits
host-error
...
envvariabl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2ec345e4a | ||
|
|
6f569bc950 |
@@ -64,6 +64,7 @@ import (
|
|||||||
_ "github.com/googleapis/genai-toolbox/internal/tools/spanner/spannerexecutesql"
|
_ "github.com/googleapis/genai-toolbox/internal/tools/spanner/spannerexecutesql"
|
||||||
_ "github.com/googleapis/genai-toolbox/internal/tools/spanner/spannersql"
|
_ "github.com/googleapis/genai-toolbox/internal/tools/spanner/spannersql"
|
||||||
_ "github.com/googleapis/genai-toolbox/internal/tools/sqlitesql"
|
_ "github.com/googleapis/genai-toolbox/internal/tools/sqlitesql"
|
||||||
|
_ "github.com/googleapis/genai-toolbox/internal/tools/utility/envvariable"
|
||||||
_ "github.com/googleapis/genai-toolbox/internal/tools/utility/wait"
|
_ "github.com/googleapis/genai-toolbox/internal/tools/utility/wait"
|
||||||
_ "github.com/googleapis/genai-toolbox/internal/tools/valkey"
|
_ "github.com/googleapis/genai-toolbox/internal/tools/valkey"
|
||||||
|
|
||||||
|
|||||||
22
docs/en/resources/tools/utility/update-mcp-settings.md
Normal file
22
docs/en/resources/tools/utility/update-mcp-settings.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# update-mcp-settings
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
The `update-mcp-settings` tool is a utility that updates the MCP (Model Context Protocol) settings file with the necessary environment variables for a given tool. This is particularly useful when you need to configure a tool with specific environment variables being set previously in chat for AlloyDB Control Plane.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
To use the `update-mcp-settings` tool, you need to configure it in your `toolbox.yaml` file. Here is an example configuration:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
tools:
|
||||||
|
update-mcp-settings-tool:
|
||||||
|
kind: update-mcp-settings
|
||||||
|
description: "Run this tool to update mcp json file prebuilt tool for data plane with right parameters ALLOYDB_POSTGRES_PROJECT, ALLOYDB_POSTGRES_REGION, ALLOYDB_POSTGRES_CLUSTER, ALLOYDB_POSTGRES_INSTANCE, ALLOYDB_POSTGRES_DATABASE, ALLOYDB_POSTGRES_USER, ALLOYDB_POSTGRES_PASSWORD. Identify the mcp settings json file or ask user to share it's full path. Run this tool once cluster and instance creation is done."
|
||||||
|
```
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
| **field** | **type** | **required** | **description** |
|
||||||
|
|-------------|:------------------------------------------:|:------------:|--------------------------------------------------------------------------------------------------|
|
||||||
|
| kind | string | true | Must be "update-mcp-settings". |
|
||||||
|
| description | string | true | Description of the tool that is passed to the LLM. |
|
||||||
209
internal/tools/utility/envvariable/setenvvariable.go
Normal file
209
internal/tools/utility/envvariable/setenvvariable.go
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
// Copyright 2025 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package setenvvariable
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
yaml "github.com/goccy/go-yaml"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/sources"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/tools"
|
||||||
|
)
|
||||||
|
|
||||||
|
const kind string = "update-mcp-settings"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if !tools.Register(kind, newConfig) {
|
||||||
|
panic(fmt.Sprintf("tool kind %q already registered", kind))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
|
||||||
|
actual := Config{Name: name}
|
||||||
|
if err := decoder.DecodeContext(ctx, &actual); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return actual, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Name string `yaml:"name" validate:"required"`
|
||||||
|
Kind string `yaml:"kind" validate:"required"`
|
||||||
|
Description string `yaml:"description" validate:"required"`
|
||||||
|
AuthRequired []string `yaml:"authRequired"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ tools.ToolConfig = Config{}
|
||||||
|
|
||||||
|
func (cfg Config) ToolConfigKind() string {
|
||||||
|
return kind
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
|
||||||
|
projectIDParam := tools.NewStringParameter("ALLOYDB_POSTGRES_PROJECT", "The Google Cloud project ID.")
|
||||||
|
regionParam := tools.NewStringParameter("ALLOYDB_POSTGRES_REGION", "The region for AlloyDB.")
|
||||||
|
clusterParam := tools.NewStringParameter("ALLOYDB_POSTGRES_CLUSTER", "The AlloyDB cluster name.")
|
||||||
|
instanceParam := tools.NewStringParameter("ALLOYDB_POSTGRES_INSTANCE", "The AlloyDB instance name.")
|
||||||
|
databaseParam := tools.NewStringParameter("ALLOYDB_POSTGRES_DATABASE", "The AlloyDB database name (defaults to 'postgres').")
|
||||||
|
userParam := tools.NewStringParameter("ALLOYDB_POSTGRES_USER", "The database username.")
|
||||||
|
passwordParam := tools.NewStringParameter("ALLOYDB_POSTGRES_PASSWORD", "The database password.")
|
||||||
|
mcpSettingsFile := tools.NewStringParameter("mcpSettingsFile", "The MCP Settings json file which contains information about server to run for the IDE")
|
||||||
|
|
||||||
|
parameters := tools.Parameters{
|
||||||
|
projectIDParam,
|
||||||
|
regionParam,
|
||||||
|
clusterParam,
|
||||||
|
instanceParam,
|
||||||
|
databaseParam,
|
||||||
|
userParam,
|
||||||
|
passwordParam,
|
||||||
|
mcpSettingsFile,
|
||||||
|
}
|
||||||
|
|
||||||
|
mcpManifest := tools.McpManifest{
|
||||||
|
Name: cfg.Name,
|
||||||
|
Description: cfg.Description,
|
||||||
|
InputSchema: parameters.McpManifest(),
|
||||||
|
}
|
||||||
|
|
||||||
|
t := Tool{
|
||||||
|
Name: cfg.Name,
|
||||||
|
Kind: kind,
|
||||||
|
Parameters: parameters,
|
||||||
|
manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired},
|
||||||
|
mcpManifest: mcpManifest,
|
||||||
|
}
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate interface
|
||||||
|
var _ tools.Tool = Tool{}
|
||||||
|
|
||||||
|
type Tool struct {
|
||||||
|
Name string
|
||||||
|
Kind string
|
||||||
|
Parameters tools.Parameters
|
||||||
|
manifest tools.Manifest
|
||||||
|
mcpManifest tools.McpManifest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues) ([]any, error) {
|
||||||
|
paramsMap := params.AsMap()
|
||||||
|
mcpSettingsFile, ok := paramsMap["mcpSettingsFile"]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("mcpSettingsFile not found in params")
|
||||||
|
}
|
||||||
|
|
||||||
|
mcpSettingsFileStr, ok := mcpSettingsFile.(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("mcpSettingsFile is not a string")
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := os.ReadFile(mcpSettingsFileStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read mcp settings file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var mcpSettings map[string]interface{}
|
||||||
|
if err := json.Unmarshal(data, &mcpSettings); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to unmarshal mcp settings file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mcpServers, ok := mcpSettings["mcpServers"].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
if servers, found := mcpSettings["servers"].(map[string]interface{}); found {
|
||||||
|
mcpServers = servers
|
||||||
|
} else {
|
||||||
|
mcpServers = make(map[string]interface{})
|
||||||
|
mcpSettings["mcpServers"] = mcpServers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetServer map[string]interface{}
|
||||||
|
var targetServerName string
|
||||||
|
for serverName, server := range mcpServers {
|
||||||
|
serverMap, ok := server.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
args, ok := serverMap["args"].([]interface{})
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, arg := range args {
|
||||||
|
if argStr, ok := arg.(string); ok && argStr == "alloydb-postgres" {
|
||||||
|
targetServer = serverMap
|
||||||
|
targetServerName = serverName
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if targetServer != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if targetServer == nil {
|
||||||
|
targetServerName = "alloydb"
|
||||||
|
targetServer = make(map[string]interface{})
|
||||||
|
targetServer["args"] = []interface{}{"--prebuilt", "alloydb-postgres", "--stdio"}
|
||||||
|
mcpServers[targetServerName] = targetServer
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := targetServer["command"]; !ok {
|
||||||
|
targetServer["command"] = "./PATH/TO/toolbox"
|
||||||
|
}
|
||||||
|
|
||||||
|
env, ok := targetServer["env"].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
env = make(map[string]interface{})
|
||||||
|
targetServer["env"] = env
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range paramsMap {
|
||||||
|
if key != "mcpSettingsFile" {
|
||||||
|
env[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedData, err := json.MarshalIndent(mcpSettings, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to marshal mcp settings file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.WriteFile(mcpSettingsFileStr, updatedData, 0644); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to write mcp settings file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return []any{"Successfully updated MCP settings file"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
|
||||||
|
return tools.ParseParams(t.Parameters, data, claims)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Tool) Manifest() tools.Manifest {
|
||||||
|
return t.manifest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Tool) McpManifest() tools.McpManifest {
|
||||||
|
return t.mcpManifest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Tool) Authorized(verifiedAuthServices []string) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
72
internal/tools/utility/envvariable/setenvvariable_test.go
Normal file
72
internal/tools/utility/envvariable/setenvvariable_test.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
// Copyright 2025 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package setenvvariable_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/goccy/go-yaml"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/server"
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/testutils"
|
||||||
|
setenvvariable "github.com/googleapis/genai-toolbox/internal/tools/utility/envvariable"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseFromYamlSetEnvVariable(t *testing.T) {
|
||||||
|
ctx, err := testutils.ContextWithNewLogger()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
tcs := []struct {
|
||||||
|
desc string
|
||||||
|
in string
|
||||||
|
want server.ToolConfigs
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "basic example",
|
||||||
|
in: `
|
||||||
|
tools:
|
||||||
|
example_tool:
|
||||||
|
kind: update-mcp-settings
|
||||||
|
description: some description
|
||||||
|
authRequired:
|
||||||
|
- my-google-auth-service
|
||||||
|
`,
|
||||||
|
want: server.ToolConfigs{
|
||||||
|
"example_tool": setenvvariable.Config{
|
||||||
|
Name: "example_tool",
|
||||||
|
Kind: "update-mcp-settings",
|
||||||
|
Description: "some description",
|
||||||
|
AuthRequired: []string{"my-google-auth-service"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range tcs {
|
||||||
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
got := struct {
|
||||||
|
Tools server.ToolConfigs `yaml:"tools"`
|
||||||
|
}{}
|
||||||
|
// Parse contents
|
||||||
|
err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to unmarshal: %s", err)
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
|
||||||
|
t.Fatalf("incorrect parse: diff %v", diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
225
tests/utility/update_mcp_settings_test.go
Normal file
225
tests/utility/update_mcp_settings_test.go
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
// Copyright 2025 Google LLC
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package utility_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/googleapis/genai-toolbox/internal/testutils"
|
||||||
|
_ "github.com/googleapis/genai-toolbox/internal/tools/utility/envvariable"
|
||||||
|
"github.com/googleapis/genai-toolbox/tests"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUpdateMCPSettings(t *testing.T) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
toolName := "my-update-mcp-settings"
|
||||||
|
mcpSettings := map[string]interface{}{
|
||||||
|
"mcpServers": map[string]interface{}{},
|
||||||
|
}
|
||||||
|
mcpSettingsData, err := json.Marshal(mcpSettings)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to marshal mcp settings: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
mcpSettingsFile := filepath.Join(tmpDir, "mcp.json")
|
||||||
|
if err := os.WriteFile(mcpSettingsFile, mcpSettingsData, 0644); err != nil {
|
||||||
|
t.Fatalf("failed to write mcp settings file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
toolsFile := map[string]any{
|
||||||
|
"tools": map[string]any{
|
||||||
|
toolName: map[string]any{
|
||||||
|
"kind": "update-mcp-settings",
|
||||||
|
"description": "Update MCP settings.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd, cleanup, err := tests.StartCmd(ctx, toolsFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("command initialization returned an error: %s", err)
|
||||||
|
}
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
waitCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
out, err := testutils.WaitForString(waitCtx, regexp.MustCompile(`Server ready to serve`), cmd.Out)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("toolbox command logs: \n%s", out)
|
||||||
|
t.Fatalf("toolbox didn't start successfully: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("success", func(t *testing.T) {
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"mcpSettingsFile": mcpSettingsFile,
|
||||||
|
"ALLOYDB_POSTGRES_PROJECT": "my-project",
|
||||||
|
"ALLOYDB_POSTGRES_REGION": "my-region",
|
||||||
|
"ALLOYDB_POSTGRES_CLUSTER": "my-cluster",
|
||||||
|
"ALLOYDB_POSTGRES_INSTANCE": "my-instance",
|
||||||
|
"ALLOYDB_POSTGRES_DATABASE": "my-db",
|
||||||
|
"ALLOYDB_POSTGRES_USER": "my-user",
|
||||||
|
"ALLOYDB_POSTGRES_PASSWORD": "my-password",
|
||||||
|
}
|
||||||
|
var result struct{ Result string }
|
||||||
|
if err := invoke(toolName, params, &result); err != nil {
|
||||||
|
t.Fatalf("tool invocation failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedResult := "[\"Successfully updated MCP settings file\"]"
|
||||||
|
if !reflect.DeepEqual(result.Result, expectedResult) {
|
||||||
|
t.Errorf("unexpected result: got %q, want %q", result.Result, expectedResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := os.ReadFile(mcpSettingsFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read mcp settings file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var updatedMCPSettings map[string]interface{}
|
||||||
|
if err := json.Unmarshal(data, &updatedMCPSettings); err != nil {
|
||||||
|
t.Fatalf("failed to unmarshal mcp settings file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mcpServers, ok := updatedMCPSettings["mcpServers"].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("mcpServers not found in updated settings")
|
||||||
|
}
|
||||||
|
|
||||||
|
alloydbServer, ok := mcpServers["alloydb"].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("alloydb server not found in updated settings")
|
||||||
|
}
|
||||||
|
|
||||||
|
env, ok := alloydbServer["env"].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("env not found in alloydb server settings")
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedEnv := map[string]interface{}{
|
||||||
|
"ALLOYDB_POSTGRES_PROJECT": "my-project",
|
||||||
|
"ALLOYDB_POSTGRES_REGION": "my-region",
|
||||||
|
"ALLOYDB_POSTGRES_CLUSTER": "my-cluster",
|
||||||
|
"ALLOYDB_POSTGRES_INSTANCE": "my-instance",
|
||||||
|
"ALLOYDB_POSTGRES_DATABASE": "my-db",
|
||||||
|
"ALLOYDB_POSTGRES_USER": "my-user",
|
||||||
|
"ALLOYDB_POSTGRES_PASSWORD": "my-password",
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(env, expectedEnv) {
|
||||||
|
t.Errorf("unexpected env: got %v, want %v", env, expectedEnv)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("file not found", func(t *testing.T) {
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"mcpSettingsFile": "non-existent-file.json",
|
||||||
|
"ALLOYDB_POSTGRES_PROJECT": "my-project",
|
||||||
|
"ALLOYDB_POSTGRES_REGION": "my-region",
|
||||||
|
"ALLOYDB_POSTGRES_CLUSTER": "my-cluster",
|
||||||
|
"ALLOYDB_POSTGRES_INSTANCE": "my-instance",
|
||||||
|
"ALLOYDB_POSTGRES_DATABASE": "my-db",
|
||||||
|
"ALLOYDB_POSTGRES_USER": "my-user",
|
||||||
|
"ALLOYDB_POSTGRES_PASSWORD": "my-password",
|
||||||
|
}
|
||||||
|
var result struct{ Result string }
|
||||||
|
err := invoke(toolName, params, &result)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected an error but got none")
|
||||||
|
}
|
||||||
|
expectedError := "failed to read mcp settings file"
|
||||||
|
if !strings.Contains(err.Error(), expectedError) {
|
||||||
|
t.Errorf("unexpected error: got %v, want to contain %v", err, expectedError)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid json", func(t *testing.T) {
|
||||||
|
invalidJSONFile := filepath.Join(tmpDir, "invalid.json")
|
||||||
|
if err := os.WriteFile(invalidJSONFile, []byte("{"), 0644); err != nil {
|
||||||
|
t.Fatalf("failed to write invalid json file: %v", err)
|
||||||
|
}
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"mcpSettingsFile": invalidJSONFile,
|
||||||
|
"ALLOYDB_POSTGRES_PROJECT": "my-project",
|
||||||
|
"ALLOYDB_POSTGRES_REGION": "my-region",
|
||||||
|
"ALLOYDB_POSTGRES_CLUSTER": "my-cluster",
|
||||||
|
"ALLOYDB_POSTGRES_INSTANCE": "my-instance",
|
||||||
|
"ALLOYDB_POSTGRES_DATABASE": "my-db",
|
||||||
|
"ALLOYDB_POSTGRES_USER": "my-user",
|
||||||
|
"ALLOYDB_POSTGRES_PASSWORD": "my-password",
|
||||||
|
}
|
||||||
|
var result struct{ Result string }
|
||||||
|
err := invoke(toolName, params, &result)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected an error but got none")
|
||||||
|
}
|
||||||
|
expectedError := "failed to unmarshal mcp settings file"
|
||||||
|
if !strings.Contains(err.Error(), expectedError) {
|
||||||
|
t.Errorf("unexpected error: got %v, want to contain %v", err, expectedError)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("missing mcpSettingsFile parameter", func(t *testing.T) {
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"ALLOYDB_POSTGRES_PROJECT": "my-project",
|
||||||
|
"ALLOYDB_POSTGRES_REGION": "my-region",
|
||||||
|
"ALLOYDB_POSTGRES_CLUSTER": "my-cluster",
|
||||||
|
"ALLOYDB_POSTGRES_INSTANCE": "my-instance",
|
||||||
|
"ALLOYDB_POSTGRES_DATABASE": "my-db",
|
||||||
|
"ALLOYDB_POSTGRES_USER": "my-user",
|
||||||
|
"ALLOYDB_POSTGRES_PASSWORD": "my-password",
|
||||||
|
}
|
||||||
|
var result struct{ Result string }
|
||||||
|
err := invoke(toolName, params, &result)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected an error but got none")
|
||||||
|
}
|
||||||
|
expectedError := "parameter \\\"mcpSettingsFile\\\" is required"
|
||||||
|
if !strings.Contains(err.Error(), expectedError) {
|
||||||
|
t.Errorf("unexpected error: got %v, want to contain %v", err, expectedError)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func invoke(toolName string, params map[string]interface{}, result interface{}) error {
|
||||||
|
url := fmt.Sprintf("http://127.0.0.1:5000/api/tool/%s/invoke", toolName)
|
||||||
|
body, err := json.Marshal(params)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
resp, err := http.Post(url, "application/json", bytes.NewBuffer(body))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
respBody, _ := io.ReadAll(resp.Body)
|
||||||
|
return fmt.Errorf("unexpected status code: %d, body: %s", resp.StatusCode, string(respBody))
|
||||||
|
}
|
||||||
|
return json.NewDecoder(resp.Body).Decode(result)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user