Merge branch 'main' of https://github.com/googleapis/genai-toolbox into js-quickstart-doc

This commit is contained in:
Anmol Shukla
2025-07-22 10:13:53 +05:30
29 changed files with 340 additions and 255 deletions

View File

@@ -1,5 +1,4 @@
assign_issues:
- kurtisvg
- Yuan325
- duwenxin99
- akitsch
@@ -11,7 +10,6 @@ assign_issues_by:
- shobsi
- jiaxunwu
assign_prs:
- kurtisvg
- Yuan325
- duwenxin99
- akitsch

View File

@@ -63,7 +63,7 @@ import (
_ "github.com/googleapis/genai-toolbox/internal/tools/mssql/mssqlsql"
_ "github.com/googleapis/genai-toolbox/internal/tools/mysql/mysqlexecutesql"
_ "github.com/googleapis/genai-toolbox/internal/tools/mysql/mysqlsql"
_ "github.com/googleapis/genai-toolbox/internal/tools/neo4j"
_ "github.com/googleapis/genai-toolbox/internal/tools/neo4j/neo4jcypher"
_ "github.com/googleapis/genai-toolbox/internal/tools/postgres/postgresexecutesql"
_ "github.com/googleapis/genai-toolbox/internal/tools/postgres/postgressql"
_ "github.com/googleapis/genai-toolbox/internal/tools/redis"

View File

@@ -81,8 +81,9 @@ the parameter.
|-------------|:---------------:|:------------:|-----------------------------------------------------------------------------|
| name | string | true | Name of the parameter. |
| type | string | true | Must be one of "string", "integer", "float", "boolean" "array" |
| default | parameter type | false | Default value of the parameter. If provided, the parameter is not required. |
| description | string | true | Natural language description of the parameter to describe it to the agent. |
| default | parameter type | false | Default value of the parameter. If provided, `required` will be `false`. |
| required | bool | false | Indicate if the parameter is required. Default to `true`. |
### Array Parameters
@@ -107,12 +108,13 @@ in the list using the items field:
|-------------|:----------------:|:------------:|-----------------------------------------------------------------------------|
| name | string | true | Name of the parameter. |
| type | string | true | Must be "array" |
| default | parameter type | false | Default value of the parameter. If provided, the parameter is not required. |
| description | string | true | Natural language description of the parameter to describe it to the agent. |
| default | parameter type | false | Default value of the parameter. If provided, `required` will be `false`. |
| required | bool | false | Indicate if the parameter is required. Default to `true`. |
| items | parameter object | true | Specify a Parameter object for the type of the values in the array. |
{{< notice note >}}
Items in array should not have a default value. If provided, it will be ignored.
Items in array should not have a `default` or `required` value. If provided, it will be ignored.
{{< /notice >}}
### Map Parameters

4
go.mod
View File

@@ -13,7 +13,7 @@ require (
cloud.google.com/go/spanner v1.83.0
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.29.0
github.com/couchbase/gocb/v2 v2.10.0
github.com/couchbase/gocb/v2 v2.10.1
github.com/couchbase/tools-common/http v1.0.9
github.com/fsnotify/fsnotify v1.9.0
github.com/go-chi/chi/v5 v5.2.2
@@ -66,7 +66,7 @@ require (
github.com/cenkalti/backoff/v5 v5.0.2 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f // indirect
github.com/couchbase/gocbcore/v10 v10.7.0 // indirect
github.com/couchbase/gocbcore/v10 v10.7.1 // indirect
github.com/couchbase/gocbcoreps v0.1.3 // indirect
github.com/couchbase/goprotostellar v1.0.2 // indirect
github.com/couchbase/tools-common/errors v1.0.0 // indirect

8
go.sum
View File

@@ -712,10 +712,10 @@ github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWH
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f h1:C5bqEmzEPLsHm9Mv73lSE9e9bKV23aB1vxOsmZrkl3k=
github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
github.com/couchbase/gocb/v2 v2.10.0 h1:NNxZ4okToU1Ylqp6F8tE41CEJQPhb2WjufryAkeubOk=
github.com/couchbase/gocb/v2 v2.10.0/go.mod h1:OSbMfQkP7ltbKiDZhsT2mGDhkQNmvGXxptKcxAUJQ2Y=
github.com/couchbase/gocbcore/v10 v10.7.0 h1:lAEi0PNeEGKOu8pWrPUdtLOT2oGr1J/UTdGHVPC3r/0=
github.com/couchbase/gocbcore/v10 v10.7.0/go.mod h1:Q8JWVenMCEOuRgrDQKApHbzzPif38HzefGgRVe9apAI=
github.com/couchbase/gocb/v2 v2.10.1 h1:5r1jngGxw3dTZdtq6Xmjq3pdU6hOwRvynvbVIp58T64=
github.com/couchbase/gocb/v2 v2.10.1/go.mod h1:GGEJuYjrfnPHCQLcxTcIco+Puy63PS2p8QQd8FRw66I=
github.com/couchbase/gocbcore/v10 v10.7.1 h1:6jsNDtqyfoQ8Xg6kv99rzccc3CrHbp7FjeY+ahWXTF4=
github.com/couchbase/gocbcore/v10 v10.7.1/go.mod h1:Q8JWVenMCEOuRgrDQKApHbzzPif38HzefGgRVe9apAI=
github.com/couchbase/gocbcoreps v0.1.3 h1:fILaKGCjxFIeCgAUG8FGmRDSpdrRggohOMKEgO9CUpg=
github.com/couchbase/gocbcoreps v0.1.3/go.mod h1:hBFpDNPnRno6HH5cRXExhqXYRmTsFJlFHQx7vztcXPk=
github.com/couchbase/goprotostellar v1.0.2 h1:yoPbAL9sCtcyZ5e/DcU5PRMOEFaJrF9awXYu3VPfGls=

View File

@@ -125,7 +125,7 @@ func (t Tool) Invoke(ctx context.Context, params tools.ParamValues) (any, error)
return nil, fmt.Errorf("unable to get cast %s", sliceParams[0])
}
dryRunJob, err := dryRunQuery(ctx, t.RestService, t.Client.Project(), sql)
dryRunJob, err := dryRunQuery(ctx, t.RestService, t.Client.Project(), t.Client.Location, sql)
if err != nil {
return nil, fmt.Errorf("query validation failed during dry run: %w", err)
}
@@ -200,9 +200,13 @@ func (t Tool) Authorized(verifiedAuthServices []string) bool {
}
// dryRunQuery performs a dry run of the SQL query to validate it and get metadata.
func dryRunQuery(ctx context.Context, restService *bigqueryrestapi.Service, projectID string, sql string) (*bigqueryrestapi.Job, error) {
func dryRunQuery(ctx context.Context, restService *bigqueryrestapi.Service, projectID string, location string, sql string) (*bigqueryrestapi.Job, error) {
useLegacySql := false
jobToInsert := &bigqueryrestapi.Job{
JobReference: &bigqueryrestapi.JobReference{
ProjectId: projectID,
Location: location,
},
Configuration: &bigqueryrestapi.JobConfiguration{
DryRun: true,
Query: &bigqueryrestapi.JobConfigurationQuery{

View File

@@ -29,10 +29,10 @@ import (
// Constants for tool configuration
const (
kind = "firestore-query-collection"
defaultLimit = 100
defaultAnalyze = false
maxFilterLength = 100 // Maximum filters to prevent abuse
kind = "firestore-query-collection"
defaultLimit = 100
defaultAnalyze = false
maxFilterLength = 100 // Maximum filters to prevent abuse
)
// Parameter keys
@@ -46,16 +46,16 @@ const (
// Firestore operators
var validOperators = map[string]bool{
"<": true,
"<=": true,
">": true,
">=": true,
"==": true,
"!=": true,
"<": true,
"<=": true,
">": true,
">=": true,
"==": true,
"!=": true,
"array-contains": true,
"array-contains-any": true,
"in": true,
"not-in": true,
"in": true,
"not-in": true,
}
// Error messages
@@ -128,7 +128,7 @@ func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error)
// Create parameters
parameters := createParameters()
mcpManifest := tools.McpManifest{
Name: cfg.Name,
Description: cfg.Description,
@@ -151,44 +151,44 @@ func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error)
// createParameters creates the parameter definitions for the tool
func createParameters() tools.Parameters {
collectionPathParameter := tools.NewStringParameter(
collectionPathKey,
collectionPathKey,
"The path to the Firestore collection to query",
)
filtersDescription := `Array of filter objects to apply to the query. Each filter is a JSON string with:
- field: The field name to filter on
- op: The operator to use ("<", "<=", ">", ">=", "==", "!=", "array-contains", "array-contains-any", "in", "not-in")
- value: The value to compare against (can be string, number, boolean, or array)
Example: {"field": "age", "op": ">", "value": 18}`
filtersParameter := tools.NewArrayParameter(
filtersKey,
filtersKey,
filtersDescription,
tools.NewStringParameter("item", "JSON string representation of a filter object"),
)
orderByParameter := tools.NewStringParameter(
orderByKey,
orderByKey,
"JSON string specifying the field and direction to order by (e.g., {\"field\": \"name\", \"direction\": \"ASCENDING\"}). Leave empty if not specified",
)
limitParameter := tools.NewIntParameterWithDefault(
limitKey,
defaultLimit,
limitKey,
defaultLimit,
"The maximum number of documents to return",
)
analyzeQueryParameter := tools.NewBooleanParameterWithDefault(
analyzeQueryKey,
defaultAnalyze,
analyzeQueryKey,
defaultAnalyze,
"If true, returns query explain metrics including execution statistics",
)
return tools.Parameters{
collectionPathParameter,
filtersParameter,
orderByParameter,
limitParameter,
collectionPathParameter,
filtersParameter,
orderByParameter,
limitParameter,
analyzeQueryParameter,
}
}
@@ -220,7 +220,7 @@ func (f *FilterConfig) Validate() error {
if f.Field == "" {
return fmt.Errorf("filter field cannot be empty")
}
if !validOperators[f.Op] {
ops := make([]string, 0, len(validOperators))
for op := range validOperators {
@@ -228,11 +228,11 @@ func (f *FilterConfig) Validate() error {
}
return fmt.Errorf(errInvalidOperator, f.Op, ops)
}
if f.Value == nil {
return fmt.Errorf(errMissingFilterValue, f.Field)
}
return nil
}
@@ -296,7 +296,7 @@ type queryParameters struct {
// parseQueryParameters extracts and validates parameters from the input
func (t Tool) parseQueryParameters(params tools.ParamValues) (*queryParameters, error) {
mapParams := params.AsMap()
// Get collection path
collectionPath, ok := mapParams[collectionPathKey].(string)
if !ok || collectionPath == "" {
@@ -480,31 +480,31 @@ func (t Tool) getExplainMetrics(docIterator *firestoreapi.DocumentIterator) (map
}
metricsData := make(map[string]any)
// Add plan summary if available
if explainMetrics.PlanSummary != nil {
planSummary := make(map[string]any)
planSummary["indexesUsed"] = explainMetrics.PlanSummary.IndexesUsed
metricsData["planSummary"] = planSummary
}
// Add execution stats if available
if explainMetrics.ExecutionStats != nil {
executionStats := make(map[string]any)
executionStats["resultsReturned"] = explainMetrics.ExecutionStats.ResultsReturned
executionStats["readOperations"] = explainMetrics.ExecutionStats.ReadOperations
if explainMetrics.ExecutionStats.ExecutionDuration != nil {
executionStats["executionDuration"] = explainMetrics.ExecutionStats.ExecutionDuration.String()
}
if explainMetrics.ExecutionStats.DebugStats != nil {
executionStats["debugStats"] = *explainMetrics.ExecutionStats.DebugStats
}
metricsData["executionStats"] = executionStats
}
return metricsData, nil
}

View File

@@ -151,10 +151,10 @@ type SourcePosition struct {
// ValidationResult represents the result of rules validation
type ValidationResult struct {
Valid bool `json:"valid"`
IssueCount int `json:"issueCount"`
FormattedIssues string `json:"formattedIssues,omitempty"`
RawIssues []Issue `json:"rawIssues,omitempty"`
Valid bool `json:"valid"`
IssueCount int `json:"issueCount"`
FormattedIssues string `json:"formattedIssues,omitempty"`
RawIssues []Issue `json:"rawIssues,omitempty"`
}
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues) (any, error) {
@@ -162,7 +162,7 @@ func (t Tool) Invoke(ctx context.Context, params tools.ParamValues) (any, error)
// Get source parameter
source, ok := mapParams[sourceKey].(string)
if !ok || source == ""{
if !ok || source == "" {
return nil, fmt.Errorf("invalid or missing '%s' parameter", sourceKey)
}
@@ -191,15 +191,15 @@ func (t Tool) Invoke(ctx context.Context, params tools.ParamValues) (any, error)
// Process the response
result := t.processValidationResponse(response, source)
return result, nil
}
func (t Tool) processValidationResponse(response *firebaserules.TestRulesetResponse, source string) ValidationResult {
if len(response.Issues) == 0 {
return ValidationResult{
Valid: true,
IssueCount: 0,
Valid: true,
IssueCount: 0,
FormattedIssues: "✓ No errors detected. Rules are valid.",
}
}

View File

@@ -12,13 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package neo4j
package neo4jcypher
import (
"context"
"fmt"
yaml "github.com/goccy/go-yaml"
"github.com/goccy/go-yaml"
neo4jsc "github.com/googleapis/genai-toolbox/internal/sources/neo4j"
"github.com/neo4j/neo4j-go-driver/v5/neo4j"

View File

@@ -12,17 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package neo4j_test
package neo4jcypher
import (
"testing"
yaml "github.com/goccy/go-yaml"
"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"
"github.com/googleapis/genai-toolbox/internal/tools"
"github.com/googleapis/genai-toolbox/internal/tools/neo4j"
)
func TestParseFromYamlNeo4j(t *testing.T) {
@@ -54,7 +53,7 @@ func TestParseFromYamlNeo4j(t *testing.T) {
description: country parameter description
`,
want: server.ToolConfigs{
"example_tool": neo4j.Config{
"example_tool": Config{
Name: "example_tool",
Kind: "neo4j-cypher",
Source: "my-neo4j-instance",
@@ -74,7 +73,7 @@ func TestParseFromYamlNeo4j(t *testing.T) {
Tools server.ToolConfigs `yaml:"tools"`
}{}
// Parse contents
err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
err = yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
if err != nil {
t.Fatalf("unable to unmarshal: %s", err)
}

View File

@@ -135,7 +135,7 @@ func TestAlloyDBPgToolEndpoints(t *testing.T) {
tableNameTemplateParam := "template_param_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
// set up data for param tool
createParamTableStmt, insertParamTableStmt, paramToolStmt, paramToolStmt2, arrayToolStmt, paramTestParams := tests.GetPostgresSQLParamToolInfo(tableNameParam)
createParamTableStmt, insertParamTableStmt, paramToolStmt, idParamToolStmt, nameParamToolStmt, arrayToolStmt, paramTestParams := tests.GetPostgresSQLParamToolInfo(tableNameParam)
teardownTable1 := tests.SetupPostgresSQLTable(t, ctx, pool, createParamTableStmt, insertParamTableStmt, tableNameParam, paramTestParams)
defer teardownTable1(t)
@@ -145,7 +145,7 @@ func TestAlloyDBPgToolEndpoints(t *testing.T) {
defer teardownTable2(t)
// Write config into a file and pass it to command
toolsFile := tests.GetToolsConfig(sourceConfig, AlloyDBPostgresToolKind, paramToolStmt, paramToolStmt2, arrayToolStmt, authToolStmt)
toolsFile := tests.GetToolsConfig(sourceConfig, AlloyDBPostgresToolKind, paramToolStmt, idParamToolStmt, nameParamToolStmt, arrayToolStmt, authToolStmt)
toolsFile = tests.AddPgExecuteSqlConfig(t, toolsFile)
tmplSelectCombined, tmplSelectFilterCombined := tests.GetPostgresSQLTmplToolStatement()
toolsFile = tests.AddTemplateParamConfig(t, toolsFile, AlloyDBPostgresToolKind, tmplSelectCombined, tmplSelectFilterCombined, "")
@@ -167,8 +167,8 @@ func TestAlloyDBPgToolEndpoints(t *testing.T) {
tests.RunToolGetTest(t)
select1Want, failInvocationWant, createTableStatement := tests.GetPostgresWants()
invokeParamWant, invokeParamWantNull, mcpInvokeParamWant := tests.GetNonSpannerInvokeParamWant()
tests.RunToolInvokeTest(t, select1Want, invokeParamWant, invokeParamWantNull, true)
invokeParamWant, invokeIdNullWant, nullWant, mcpInvokeParamWant := tests.GetNonSpannerInvokeParamWant()
tests.RunToolInvokeTest(t, select1Want, invokeParamWant, invokeIdNullWant, nullWant, true, true)
tests.RunExecuteSqlToolInvokeTest(t, createTableStatement, select1Want)
tests.RunMCPToolCallMethod(t, mcpInvokeParamWant, failInvocationWant)
tests.RunToolInvokeWithTemplateParameters(t, tableNameTemplateParam, tests.NewTemplateParameterTestConfig())

View File

@@ -102,7 +102,7 @@ func TestBigQueryToolEndpoints(t *testing.T) {
)
// set up data for param tool
createParamTableStmt, insertParamTableStmt, paramToolStmt, paramToolStmt2, arrayToolStmt, paramTestParams := getBigQueryParamToolInfo(tableNameParam)
createParamTableStmt, insertParamTableStmt, paramToolStmt, idParamToolStmt, nameParamToolStmt, arrayToolStmt, paramTestParams := getBigQueryParamToolInfo(tableNameParam)
teardownTable1 := setupBigQueryTable(t, ctx, client, createParamTableStmt, insertParamTableStmt, datasetName, tableNameParam, paramTestParams)
defer teardownTable1(t)
@@ -112,7 +112,7 @@ func TestBigQueryToolEndpoints(t *testing.T) {
defer teardownTable2(t)
// Write config into a file and pass it to command
toolsFile := tests.GetToolsConfig(sourceConfig, BigqueryToolKind, paramToolStmt, paramToolStmt2, arrayToolStmt, authToolStmt)
toolsFile := tests.GetToolsConfig(sourceConfig, BigqueryToolKind, paramToolStmt, idParamToolStmt, nameParamToolStmt, arrayToolStmt, authToolStmt)
toolsFile = addBigQueryPrebuiltToolsConfig(t, toolsFile)
tmplSelectCombined, tmplSelectFilterCombined := getBigQueryTmplToolStatement()
toolsFile = tests.AddTemplateParamConfig(t, toolsFile, BigqueryToolKind, tmplSelectCombined, tmplSelectFilterCombined, "")
@@ -138,8 +138,8 @@ func TestBigQueryToolEndpoints(t *testing.T) {
failInvocationWant := `{"jsonrpc":"2.0","id":"invoke-fail-tool","result":{"content":[{"type":"text","text":"unable to execute query: googleapi: Error 400: Syntax error: Unexpected identifier \"SELEC\" at [1:1]`
datasetInfoWant := "\"Location\":\"US\",\"DefaultTableExpiration\":0,\"Labels\":null,\"Access\":"
tableInfoWant := "{\"Name\":\"\",\"Location\":\"US\",\"Description\":\"\",\"Schema\":[{\"Name\":\"id\""
invokeParamWant, invokeParamWantNull, mcpInvokeParamWant := tests.GetNonSpannerInvokeParamWant()
tests.RunToolInvokeTest(t, select1Want, invokeParamWant, invokeParamWantNull, true)
invokeParamWant, invokeIdNullWant, nullWant, mcpInvokeParamWant := tests.GetNonSpannerInvokeParamWant()
tests.RunToolInvokeTest(t, select1Want, invokeParamWant, invokeIdNullWant, nullWant, false, true)
tests.RunMCPToolCallMethod(t, mcpInvokeParamWant, failInvocationWant)
templateParamTestConfig := tests.NewTemplateParameterTestConfig(
tests.WithCreateColArray(`["id INT64", "name STRING", "age INT64"]`),
@@ -153,14 +153,15 @@ func TestBigQueryToolEndpoints(t *testing.T) {
runBigQueryGetTableInfoToolInvokeTest(t, datasetName, tableName, tableInfoWant)
}
// getBigQueryParamToolInfo returns statements and param for my-param-tool for bigquery kind
func getBigQueryParamToolInfo(tableName string) (string, string, string, string, string, []bigqueryapi.QueryParameter) {
// getBigQueryParamToolInfo returns statements and param for my-tool for bigquery kind
func getBigQueryParamToolInfo(tableName string) (string, string, string, string, string, string, []bigqueryapi.QueryParameter) {
createStatement := fmt.Sprintf(`
CREATE TABLE IF NOT EXISTS %s (id INT64, name STRING);`, tableName)
insertStatement := fmt.Sprintf(`
INSERT INTO %s (id, name) VALUES (?, ?), (?, ?), (?, ?), (?, NULL);`, tableName)
toolStatement := fmt.Sprintf(`SELECT * FROM %s WHERE id = ? OR name = ? ORDER BY id;`, tableName)
toolStatement2 := fmt.Sprintf(`SELECT * FROM %s WHERE id = ? ORDER BY id;`, tableName)
idToolStatement := fmt.Sprintf(`SELECT * FROM %s WHERE id = ? ORDER BY id;`, tableName)
nameToolStatement := fmt.Sprintf(`SELECT * FROM %s WHERE name = ? ORDER BY id;`, tableName)
arrayToolStatememt := fmt.Sprintf(`SELECT * FROM %s WHERE id IN UNNEST(@idArray) AND name IN UNNEST(@nameArray) ORDER BY id;`, tableName)
params := []bigqueryapi.QueryParameter{
{Value: int64(1)}, {Value: "Alice"},
@@ -168,7 +169,7 @@ func getBigQueryParamToolInfo(tableName string) (string, string, string, string,
{Value: int64(3)}, {Value: "Sid"},
{Value: int64(4)},
}
return createStatement, insertStatement, toolStatement, toolStatement2, arrayToolStatememt, params
return createStatement, insertStatement, toolStatement, idToolStatement, nameToolStatement, arrayToolStatememt, params
}
// getBigQueryAuthToolInfo returns statements and param of my-auth-tool for bigquery kind

View File

@@ -79,7 +79,8 @@ func TestBigtableToolEndpoints(t *testing.T) {
// Do not change the shape of statement without checking tests/common_test.go.
// The structure and value of seed data has to match https://github.com/googleapis/genai-toolbox/blob/4dba0df12dc438eca3cb476ef52aa17cdf232c12/tests/common_test.go#L200-L251
paramTestStatement := fmt.Sprintf("SELECT TO_INT64(cf['id']) as id, CAST(cf['name'] AS string) as name, FROM %s WHERE TO_INT64(cf['id']) = @id OR CAST(cf['name'] AS string) = @name;", tableName)
paramTestStatement2 := fmt.Sprintf("SELECT TO_INT64(cf['id']) as id, CAST(cf['name'] AS string) as name, FROM %s WHERE TO_INT64(cf['id']) = @id;", tableName)
idParamTestStatement := fmt.Sprintf("SELECT TO_INT64(cf['id']) as id, CAST(cf['name'] AS string) as name, FROM %s WHERE TO_INT64(cf['id']) = @id;", tableName)
nameParamTestStatement := fmt.Sprintf("SELECT TO_INT64(cf['id']) as id, CAST(cf['name'] AS string) as name, FROM %s WHERE CAST(cf['name'] AS string) = @name;", tableName)
arrayTestStatement := fmt.Sprintf(
"SELECT TO_INT64(cf['id']) AS id, CAST(cf['name'] AS string) AS name FROM %s WHERE TO_INT64(cf['id']) IN UNNEST(@idArray) AND CAST(cf['name'] AS string) IN UNNEST(@nameArray);",
tableName,
@@ -98,7 +99,7 @@ func TestBigtableToolEndpoints(t *testing.T) {
defer teardownTableTmpl(t)
// Write config into a file and pass it to command
toolsFile := tests.GetToolsConfig(sourceConfig, BigtableToolKind, paramTestStatement, paramTestStatement2, arrayTestStatement, authToolStatement)
toolsFile := tests.GetToolsConfig(sourceConfig, BigtableToolKind, paramTestStatement, idParamTestStatement, nameParamTestStatement, arrayTestStatement, authToolStatement)
toolsFile = addTemplateParamConfig(t, toolsFile)
cmd, cleanup, err := tests.StartCmd(ctx, toolsFile, args...)
@@ -120,9 +121,9 @@ func TestBigtableToolEndpoints(t *testing.T) {
// Actual test parameters are set in https://github.com/googleapis/genai-toolbox/blob/52b09a67cb40ac0c5f461598b4673136699a3089/tests/tool_test.go#L250
select1Want := "[{\"$col1\":1}]"
failInvocationWant := `{"jsonrpc":"2.0","id":"invoke-fail-tool","result":{"content":[{"type":"text","text":"unable to prepare statement: rpc error: code = InvalidArgument desc = Syntax error: Unexpected identifier \"SELEC\" [at 1:1]"}],"isError":true}}`
invokeParamWant, _, mcpInvokeParamWant := tests.GetNonSpannerInvokeParamWant()
invokeParamWantNull := `[{"id":4,"name":""}]`
tests.RunToolInvokeTest(t, select1Want, invokeParamWant, invokeParamWantNull, true)
invokeParamWant, _, nullWant, mcpInvokeParamWant := tests.GetNonSpannerInvokeParamWant()
invokeIdNullWant := `[{"id":4,"name":""}]`
tests.RunToolInvokeTest(t, select1Want, invokeParamWant, invokeIdNullWant, nullWant, true, true)
tests.RunMCPToolCallMethod(t, mcpInvokeParamWant, failInvocationWant)
templateParamTestConfig := tests.NewTemplateParameterTestConfig(

View File

@@ -129,7 +129,7 @@ func TestCloudSQLMSSQLToolEndpoints(t *testing.T) {
tableNameTemplateParam := "template_param_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
// set up data for param tool
createParamTableStmt, insertParamTableStmt, paramToolStmt, paramToolStmt2, arrayToolStmt, paramTestParams := tests.GetMSSQLParamToolInfo(tableNameParam)
createParamTableStmt, insertParamTableStmt, paramToolStmt, idParamToolStmt, nameParamToolStmt, arrayToolStmt, paramTestParams := tests.GetMSSQLParamToolInfo(tableNameParam)
teardownTable1 := tests.SetupMsSQLTable(t, ctx, db, createParamTableStmt, insertParamTableStmt, tableNameParam, paramTestParams)
defer teardownTable1(t)
@@ -139,7 +139,7 @@ func TestCloudSQLMSSQLToolEndpoints(t *testing.T) {
defer teardownTable2(t)
// Write config into a file and pass it to command
toolsFile := tests.GetToolsConfig(sourceConfig, CloudSQLMSSQLToolKind, paramToolStmt, paramToolStmt2, arrayToolStmt, authToolStmt)
toolsFile := tests.GetToolsConfig(sourceConfig, CloudSQLMSSQLToolKind, paramToolStmt, idParamToolStmt, nameParamToolStmt, arrayToolStmt, authToolStmt)
toolsFile = tests.AddMSSQLExecuteSqlConfig(t, toolsFile)
tmplSelectCombined, tmplSelectFilterCombined := tests.GetMSSQLTmplToolStatement()
toolsFile = tests.AddTemplateParamConfig(t, toolsFile, CloudSQLMSSQLToolKind, tmplSelectCombined, tmplSelectFilterCombined, "")
@@ -161,8 +161,8 @@ func TestCloudSQLMSSQLToolEndpoints(t *testing.T) {
tests.RunToolGetTest(t)
select1Want, failInvocationWant, createTableStatement := tests.GetMSSQLWants()
invokeParamWant, invokeParamWantNull, mcpInvokeParamWant := tests.GetNonSpannerInvokeParamWant()
tests.RunToolInvokeTest(t, select1Want, invokeParamWant, invokeParamWantNull, false)
invokeParamWant, invokeIdNullWant, nullWant, mcpInvokeParamWant := tests.GetNonSpannerInvokeParamWant()
tests.RunToolInvokeTest(t, select1Want, invokeParamWant, invokeIdNullWant, nullWant, true, false)
tests.RunExecuteSqlToolInvokeTest(t, createTableStatement, select1Want)
tests.RunMCPToolCallMethod(t, mcpInvokeParamWant, failInvocationWant)
tests.RunToolInvokeWithTemplateParameters(t, tableNameTemplateParam, tests.NewTemplateParameterTestConfig())

View File

@@ -116,7 +116,7 @@ func TestCloudSQLMySQLToolEndpoints(t *testing.T) {
tableNameTemplateParam := "template_param_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
// set up data for param tool
createParamTableStmt, insertParamTableStmt, paramToolStmt, paramToolStmt2, arrayToolStmt, paramTestParams := tests.GetMySQLParamToolInfo(tableNameParam)
createParamTableStmt, insertParamTableStmt, paramToolStmt, idParamToolStmt, nameParamToolStmt, arrayToolStmt, paramTestParams := tests.GetMySQLParamToolInfo(tableNameParam)
teardownTable1 := tests.SetupMySQLTable(t, ctx, pool, createParamTableStmt, insertParamTableStmt, tableNameParam, paramTestParams)
defer teardownTable1(t)
@@ -126,7 +126,7 @@ func TestCloudSQLMySQLToolEndpoints(t *testing.T) {
defer teardownTable2(t)
// Write config into a file and pass it to command
toolsFile := tests.GetToolsConfig(sourceConfig, CloudSQLMySQLToolKind, paramToolStmt, paramToolStmt2, arrayToolStmt, authToolStmt)
toolsFile := tests.GetToolsConfig(sourceConfig, CloudSQLMySQLToolKind, paramToolStmt, idParamToolStmt, nameParamToolStmt, arrayToolStmt, authToolStmt)
toolsFile = tests.AddMySqlExecuteSqlConfig(t, toolsFile)
tmplSelectCombined, tmplSelectFilterCombined := tests.GetMySQLTmplToolStatement()
toolsFile = tests.AddTemplateParamConfig(t, toolsFile, CloudSQLMySQLToolKind, tmplSelectCombined, tmplSelectFilterCombined, "")
@@ -148,8 +148,8 @@ func TestCloudSQLMySQLToolEndpoints(t *testing.T) {
tests.RunToolGetTest(t)
select1Want, failInvocationWant, createTableStatement := tests.GetMySQLWants()
invokeParamWant, invokeParamWantNull, mcpInvokeParamWant := tests.GetNonSpannerInvokeParamWant()
tests.RunToolInvokeTest(t, select1Want, invokeParamWant, invokeParamWantNull, false)
invokeParamWant, invokeIdNullWant, nullWant, mcpInvokeParamWant := tests.GetNonSpannerInvokeParamWant()
tests.RunToolInvokeTest(t, select1Want, invokeParamWant, invokeIdNullWant, nullWant, true, false)
tests.RunExecuteSqlToolInvokeTest(t, createTableStatement, select1Want)
tests.RunMCPToolCallMethod(t, mcpInvokeParamWant, failInvocationWant)
tests.RunToolInvokeWithTemplateParameters(t, tableNameTemplateParam, tests.NewTemplateParameterTestConfig())

View File

@@ -120,7 +120,7 @@ func TestCloudSQLPgSimpleToolEndpoints(t *testing.T) {
tableNameTemplateParam := "template_param_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
// set up data for param tool
createParamTableStmt, insertParamTableStmt, paramToolStmt, paramToolStmt2, arrayToolStmt, paramTestParams := tests.GetPostgresSQLParamToolInfo(tableNameParam)
createParamTableStmt, insertParamTableStmt, paramToolStmt, idParamToolStmt, nameParamToolStmt, arrayToolStmt, paramTestParams := tests.GetPostgresSQLParamToolInfo(tableNameParam)
teardownTable1 := tests.SetupPostgresSQLTable(t, ctx, pool, createParamTableStmt, insertParamTableStmt, tableNameParam, paramTestParams)
defer teardownTable1(t)
@@ -130,7 +130,7 @@ func TestCloudSQLPgSimpleToolEndpoints(t *testing.T) {
defer teardownTable2(t)
// Write config into a file and pass it to command
toolsFile := tests.GetToolsConfig(sourceConfig, CloudSQLPostgresToolKind, paramToolStmt, paramToolStmt2, arrayToolStmt, authToolStmt)
toolsFile := tests.GetToolsConfig(sourceConfig, CloudSQLPostgresToolKind, paramToolStmt, idParamToolStmt, nameParamToolStmt, arrayToolStmt, authToolStmt)
toolsFile = tests.AddPgExecuteSqlConfig(t, toolsFile)
tmplSelectCombined, tmplSelectFilterCombined := tests.GetPostgresSQLTmplToolStatement()
toolsFile = tests.AddTemplateParamConfig(t, toolsFile, CloudSQLPostgresToolKind, tmplSelectCombined, tmplSelectFilterCombined, "")
@@ -152,8 +152,8 @@ func TestCloudSQLPgSimpleToolEndpoints(t *testing.T) {
tests.RunToolGetTest(t)
select1Want, failInvocationWant, createTableStatement := tests.GetPostgresWants()
invokeParamWant, invokeParamWantNull, mcpInvokeParamWant := tests.GetNonSpannerInvokeParamWant()
tests.RunToolInvokeTest(t, select1Want, invokeParamWant, invokeParamWantNull, true)
invokeParamWant, invokeIdNullWant, nullWant, mcpInvokeParamWant := tests.GetNonSpannerInvokeParamWant()
tests.RunToolInvokeTest(t, select1Want, invokeParamWant, invokeIdNullWant, nullWant, true, true)
tests.RunExecuteSqlToolInvokeTest(t, createTableStatement, select1Want)
tests.RunMCPToolCallMethod(t, mcpInvokeParamWant, failInvocationWant)
tests.RunToolInvokeWithTemplateParameters(t, tableNameTemplateParam, tests.NewTemplateParameterTestConfig())

View File

@@ -28,7 +28,7 @@ import (
)
// GetToolsConfig returns a mock tools config file
func GetToolsConfig(sourceConfig map[string]any, toolKind, paramToolStatement, paramToolStatement2, arrayToolStatement, authToolStatement string) map[string]any {
func GetToolsConfig(sourceConfig map[string]any, toolKind, paramToolStatement, idParamToolStmt, nameParamToolStmt, arrayToolStatement, authToolStatement string) map[string]any {
// Write config into a file and pass it to command
toolsFile := map[string]any{
"sources": map[string]any{
@@ -47,7 +47,7 @@ func GetToolsConfig(sourceConfig map[string]any, toolKind, paramToolStatement, p
"description": "Simple tool to test end to end functionality.",
"statement": "SELECT 1;",
},
"my-param-tool": map[string]any{
"my-tool": map[string]any{
"kind": toolKind,
"source": "my-instance",
"description": "Tool to test invocation with params.",
@@ -65,11 +65,11 @@ func GetToolsConfig(sourceConfig map[string]any, toolKind, paramToolStatement, p
},
},
},
"my-param-tool2": map[string]any{
"my-tool-by-id": map[string]any{
"kind": toolKind,
"source": "my-instance",
"description": "Tool to test invocation with params.",
"statement": paramToolStatement2,
"statement": idParamToolStmt,
"parameters": []any{
map[string]any{
"name": "id",
@@ -78,6 +78,20 @@ func GetToolsConfig(sourceConfig map[string]any, toolKind, paramToolStatement, p
},
},
},
"my-tool-by-name": map[string]any{
"kind": toolKind,
"source": "my-instance",
"description": "Tool to test invocation with params.",
"statement": nameParamToolStmt,
"parameters": []any{
map[string]any{
"name": "name",
"type": "string",
"description": "user name",
"required": false,
},
},
},
"my-array-tool": map[string]any{
"kind": toolKind,
"source": "my-instance",
@@ -301,15 +315,16 @@ func AddMSSQLExecuteSqlConfig(t *testing.T, config map[string]any) map[string]an
return config
}
// GetPostgresSQLParamToolInfo returns statements and param for my-param-tool postgres-sql kind
func GetPostgresSQLParamToolInfo(tableName string) (string, string, string, string, string, []any) {
// GetPostgresSQLParamToolInfo returns statements and param for my-tool postgres-sql kind
func GetPostgresSQLParamToolInfo(tableName string) (string, string, string, string, string, string, []any) {
createStatement := fmt.Sprintf("CREATE TABLE %s (id SERIAL PRIMARY KEY, name TEXT);", tableName)
insertStatement := fmt.Sprintf("INSERT INTO %s (name) VALUES ($1), ($2), ($3), ($4);", tableName)
toolStatement := fmt.Sprintf("SELECT * FROM %s WHERE id = $1 OR name = $2;", tableName)
toolStatement2 := fmt.Sprintf("SELECT * FROM %s WHERE id = $1;", tableName)
idParamStatement := fmt.Sprintf("SELECT * FROM %s WHERE id = $1;", tableName)
nameParamStatement := fmt.Sprintf("SELECT * FROM %s WHERE name = $1;", tableName)
arrayToolStatement := fmt.Sprintf("SELECT * FROM %s WHERE id = ANY($1) AND name = ANY($2);", tableName)
params := []any{"Alice", "Jane", "Sid", nil}
return createStatement, insertStatement, toolStatement, toolStatement2, arrayToolStatement, params
return createStatement, insertStatement, toolStatement, idParamStatement, nameParamStatement, arrayToolStatement, params
}
// GetPostgresSQLAuthToolInfo returns statements and param of my-auth-tool for postgres-sql kind
@@ -328,15 +343,16 @@ func GetPostgresSQLTmplToolStatement() (string, string) {
return tmplSelectCombined, tmplSelectFilterCombined
}
// GetMSSQLParamToolInfo returns statements and param for my-param-tool mssql-sql kind
func GetMSSQLParamToolInfo(tableName string) (string, string, string, string, string, []any) {
// GetMSSQLParamToolInfo returns statements and param for my-tool mssql-sql kind
func GetMSSQLParamToolInfo(tableName string) (string, string, string, string, string, string, []any) {
createStatement := fmt.Sprintf("CREATE TABLE %s (id INT IDENTITY(1,1) PRIMARY KEY, name VARCHAR(255));", tableName)
insertStatement := fmt.Sprintf("INSERT INTO %s (name) VALUES (@alice), (@jane), (@sid), (@nil);", tableName)
toolStatement := fmt.Sprintf("SELECT * FROM %s WHERE id = @id OR name = @p2;", tableName)
toolStatement2 := fmt.Sprintf("SELECT * FROM %s WHERE id = @id;", tableName)
idParamStatement := fmt.Sprintf("SELECT * FROM %s WHERE id = @id;", tableName)
nameParamStatement := fmt.Sprintf("SELECT * FROM %s WHERE name = @name;", tableName)
arrayToolStatement := fmt.Sprintf("SELECT * FROM %s WHERE id = ANY(@idArray) OR name = ANY(@p2);", tableName)
params := []any{sql.Named("alice", "Alice"), sql.Named("jane", "Jane"), sql.Named("sid", "Sid"), sql.Named("nil", nil)}
return createStatement, insertStatement, toolStatement, toolStatement2, arrayToolStatement, params
return createStatement, insertStatement, toolStatement, idParamStatement, nameParamStatement, arrayToolStatement, params
}
// GetMSSQLAuthToolInfo returns statements and param of my-auth-tool for mssql-sql kind
@@ -355,15 +371,16 @@ func GetMSSQLTmplToolStatement() (string, string) {
return tmplSelectCombined, tmplSelectFilterCombined
}
// GetMySQLParamToolInfo returns statements and param for my-param-tool mysql-sql kind
func GetMySQLParamToolInfo(tableName string) (string, string, string, string, string, []any) {
// GetMySQLParamToolInfo returns statements and param for my-tool mysql-sql kind
func GetMySQLParamToolInfo(tableName string) (string, string, string, string, string, string, []any) {
createStatement := fmt.Sprintf("CREATE TABLE %s (id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255));", tableName)
insertStatement := fmt.Sprintf("INSERT INTO %s (name) VALUES (?), (?), (?), (?);", tableName)
toolStatement := fmt.Sprintf("SELECT * FROM %s WHERE id = ? OR name = ?;", tableName)
toolStatement2 := fmt.Sprintf("SELECT * FROM %s WHERE id = ?;", tableName)
idParamStatement := fmt.Sprintf("SELECT * FROM %s WHERE id = ?;", tableName)
nameParamStatement := fmt.Sprintf("SELECT * FROM %s WHERE name = ?;", tableName)
arrayToolStatement := fmt.Sprintf("SELECT * FROM %s WHERE id = ANY(?) AND name = ANY(?);", tableName)
params := []any{"Alice", "Jane", "Sid", nil}
return createStatement, insertStatement, toolStatement, toolStatement2, arrayToolStatement, params
return createStatement, insertStatement, toolStatement, idParamStatement, nameParamStatement, arrayToolStatement, params
}
// GetMySQLAuthToolInfo returns statements and param of my-auth-tool for mysql-sql kind
@@ -382,11 +399,12 @@ func GetMySQLTmplToolStatement() (string, string) {
return tmplSelectCombined, tmplSelectFilterCombined
}
func GetNonSpannerInvokeParamWant() (string, string, string) {
func GetNonSpannerInvokeParamWant() (string, string, string, string) {
invokeParamWant := "[{\"id\":1,\"name\":\"Alice\"},{\"id\":3,\"name\":\"Sid\"}]"
invokeParamWantNull := "[{\"id\":4,\"name\":null}]"
mcpInvokeParamWant := `{"jsonrpc":"2.0","id":"my-param-tool","result":{"content":[{"type":"text","text":"{\"id\":1,\"name\":\"Alice\"}"},{"type":"text","text":"{\"id\":3,\"name\":\"Sid\"}"}]}}`
return invokeParamWant, invokeParamWantNull, mcpInvokeParamWant
invokeIdNullWant := "[{\"id\":4,\"name\":null}]"
nullWant := "null"
mcpInvokeParamWant := `{"jsonrpc":"2.0","id":"my-tool","result":{"content":[{"type":"text","text":"{\"id\":1,\"name\":\"Alice\"}"},{"type":"text","text":"{\"id\":3,\"name\":\"Sid\"}"}]}}`
return invokeParamWant, invokeIdNullWant, nullWant, mcpInvokeParamWant
}
// GetPostgresWants return the expected wants for postgres
@@ -501,13 +519,14 @@ func SetupMySQLTable(t *testing.T, ctx context.Context, pool *sql.DB, createStat
}
// GetRedisWants return the expected wants for redis
func GetRedisValkeyWants() (string, string, string, string, string) {
func GetRedisValkeyWants() (string, string, string, string, string, string) {
select1Want := "[\"PONG\"]"
failInvocationWant := `unknown command 'SELEC 1;', with args beginning with: \""}]}}`
invokeParamWant := "[{\"id\":\"1\",\"name\":\"Alice\"},{\"id\":\"3\",\"name\":\"Sid\"}]"
invokeParamWantNull := `[{"id":"4","name":""}]`
mcpInvokeParamWant := `{"jsonrpc":"2.0","id":"my-param-tool","result":{"content":[{"type":"text","text":"{\"id\":\"1\",\"name\":\"Alice\"}"},{"type":"text","text":"{\"id\":\"3\",\"name\":\"Sid\"}"}]}}`
return select1Want, failInvocationWant, invokeParamWant, invokeParamWantNull, mcpInvokeParamWant
invokeIdNullWant := `[{"id":"4","name":""}]`
nullWant := `["null"]`
mcpInvokeParamWant := `{"jsonrpc":"2.0","id":"my-tool","result":{"content":[{"type":"text","text":"{\"id\":\"1\",\"name\":\"Alice\"}"},{"type":"text","text":"{\"id\":\"3\",\"name\":\"Sid\"}"}]}}`
return select1Want, failInvocationWant, invokeParamWant, invokeIdNullWant, nullWant, mcpInvokeParamWant
}
func GetRedisValkeyToolsConfig(sourceConfig map[string]any, toolKind string) map[string]any {
@@ -528,7 +547,7 @@ func GetRedisValkeyToolsConfig(sourceConfig map[string]any, toolKind string) map
"description": "Simple tool to test end to end functionality.",
"commands": [][]string{{"PING"}},
},
"my-param-tool": map[string]any{
"my-tool": map[string]any{
"kind": toolKind,
"source": "my-instance",
"description": "Tool to test invocation with params.",
@@ -546,7 +565,7 @@ func GetRedisValkeyToolsConfig(sourceConfig map[string]any, toolKind string) map
},
},
},
"my-param-tool2": map[string]any{
"my-tool-by-id": map[string]any{
"kind": toolKind,
"source": "my-instance",
"description": "Tool to test invocation with params.",
@@ -559,6 +578,20 @@ func GetRedisValkeyToolsConfig(sourceConfig map[string]any, toolKind string) map
},
},
},
"my-tool-by-name": map[string]any{
"kind": toolKind,
"source": "my-instance",
"description": "Tool to test invocation with params.",
"commands": [][]string{{"GET", "null"}},
"parameters": []any{
map[string]any{
"name": "name",
"type": "string",
"description": "user name",
"required": false,
},
},
},
"my-array-tool": map[string]any{
"kind": toolKind,
"source": "my-instance",

View File

@@ -103,7 +103,7 @@ func TestCouchbaseToolEndpoints(t *testing.T) {
collectionNameTemplateParam := "template_param_" + strings.ReplaceAll(uuid.New().String(), "-", "")
// Set up data for param tool
paramToolStatement, paramToolStmt2, arrayToolStatement, paramTestParams := getCouchbaseParamToolInfo(collectionNameParam)
paramToolStatement, idParamToolStmt, nameParamToolStmt, arrayToolStatement, paramTestParams := getCouchbaseParamToolInfo(collectionNameParam)
teardownCollection1 := setupCouchbaseCollection(t, ctx, cluster, couchbaseBucket, couchbaseScope, collectionNameParam, paramTestParams)
defer teardownCollection1(t)
@@ -118,7 +118,7 @@ func TestCouchbaseToolEndpoints(t *testing.T) {
defer teardownCollection3(t)
// Write config into a file and pass it to command
toolsFile := tests.GetToolsConfig(sourceConfig, couchbaseToolKind, paramToolStatement, paramToolStmt2, arrayToolStatement, authToolStatement)
toolsFile := tests.GetToolsConfig(sourceConfig, couchbaseToolKind, paramToolStatement, idParamToolStmt, nameParamToolStmt, arrayToolStatement, authToolStatement)
toolsFile = tests.AddTemplateParamConfig(t, toolsFile, couchbaseToolKind, tmplSelectCombined, tmplSelectFilterCombined, tmplSelectAll)
cmd, cleanup, err := tests.StartCmd(ctx, toolsFile, args...)
@@ -140,8 +140,8 @@ func TestCouchbaseToolEndpoints(t *testing.T) {
select1Want := "[{\"$1\":1}]"
failMcpInvocationWant := "{\"jsonrpc\":\"2.0\",\"id\":\"invoke-fail-tool\",\"result\":{\"content\":[{\"type\":\"text\",\"text\":\"unable to execute query: parsing failure | {\\\"statement\\\":\\\"SELEC 1;\\\""
invokeParamWant, invokeParamWantNull, mcpInvokeParamWant := tests.GetNonSpannerInvokeParamWant()
tests.RunToolInvokeTest(t, select1Want, invokeParamWant, invokeParamWantNull, true)
invokeParamWant, invokeIdNullWant, nullWant, mcpInvokeParamWant := tests.GetNonSpannerInvokeParamWant()
tests.RunToolInvokeTest(t, select1Want, invokeParamWant, invokeIdNullWant, nullWant, true, true)
tests.RunMCPToolCallMethod(t, mcpInvokeParamWant, failMcpInvocationWant)
templateParamTestConfig := tests.NewTemplateParameterTestConfig(
@@ -230,27 +230,27 @@ func setupCouchbaseCollection(t *testing.T, ctx context.Context, cluster *gocb.C
}
}
// getCouchbaseParamToolInfo returns statements and params for my-param-tool couchbase-sql kind
func getCouchbaseParamToolInfo(collectionName string) (string, string, string, []map[string]any) {
// getCouchbaseParamToolInfo returns statements and params for my-tool couchbase-sql kind
func getCouchbaseParamToolInfo(collectionName string) (string, string, string, string, []map[string]any) {
// N1QL uses positional or named parameters with $ prefix
toolStatement := fmt.Sprintf("SELECT TONUMBER(meta().id) as id, "+
"%s.* FROM %s WHERE meta().id = TOSTRING($id) OR name = $name order by meta().id",
collectionName, collectionName)
toolStatement2 := fmt.Sprintf("SELECT TONUMBER(meta().id) as id, "+
idToolStatement := fmt.Sprintf("SELECT TONUMBER(meta().id) as id, "+
"%s.* FROM %s WHERE meta().id = TOSTRING($id) order by meta().id",
collectionName, collectionName)
nameToolStatement := fmt.Sprintf("SELECT TONUMBER(meta().id) as id, "+
"%s.* FROM %s WHERE name = $name order by meta().id",
collectionName, collectionName)
arrayToolStatemnt := fmt.Sprintf("SELECT TONUMBER(meta().id) as id, "+
"%s.* FROM %s WHERE TONUMBER(meta().id) IN $idArray AND name IN $nameArray order by meta().id", collectionName, collectionName)
params := []map[string]any{
{"name": "Alice"},
{"name": "Jane"},
{"name": "Sid"},
{"name": nil},
}
return toolStatement, toolStatement2, arrayToolStatemnt, params
return toolStatement, idToolStatement, nameToolStatement, arrayToolStatemnt, params
}
// getCouchbaseAuthToolInfo returns statements and param of my-auth-tool for couchbase-sql kind

View File

@@ -62,7 +62,7 @@ func getFirestoreVars(t *testing.T) map[string]any {
// initFirestoreConnection creates a Firestore client for testing
func initFirestoreConnection(project, database string) (*firestoreapi.Client, error) {
ctx := context.Background()
if database == "" {
database = "(default)"
}
@@ -93,20 +93,20 @@ func TestFirestoreToolEndpoints(t *testing.T) {
testDocID1 := fmt.Sprintf("doc_%s", strings.ReplaceAll(uuid.New().String(), "-", ""))
testDocID2 := fmt.Sprintf("doc_%s", strings.ReplaceAll(uuid.New().String(), "-", ""))
testDocID3 := fmt.Sprintf("doc_%s", strings.ReplaceAll(uuid.New().String(), "-", ""))
// Document paths for testing
docPath1 := fmt.Sprintf("%s/%s", testCollectionName, testDocID1)
docPath2 := fmt.Sprintf("%s/%s", testCollectionName, testDocID2)
docPath3 := fmt.Sprintf("%s/%s", testCollectionName, testDocID3)
// Set up test data
teardown := setupFirestoreTestData(t, ctx, client, testCollectionName, testSubCollectionName,
teardown := setupFirestoreTestData(t, ctx, client, testCollectionName, testSubCollectionName,
testDocID1, testDocID2, testDocID3)
defer teardown(t)
// Write config into a file and pass it to command
toolsFile := getFirestoreToolsConfig(sourceConfig)
cmd, cleanup, err := tests.StartCmd(ctx, toolsFile, args...)
if err != nil {
t.Fatalf("command initialization returned an error: %s", err)
@@ -123,7 +123,7 @@ func TestFirestoreToolEndpoints(t *testing.T) {
// Run Firestore-specific tool get test
runFirestoreToolGetTest(t)
// Run Firestore-specific MCP test
runFirestoreMCPToolCallMethod(t, docPath1, docPath2)
@@ -170,7 +170,7 @@ func runFirestoreToolGetTest(t *testing.T) {
},
},
}
for _, tc := range tcs {
t.Run(tc.name, func(t *testing.T) {
resp, err := http.Get(tc.api)
@@ -192,7 +192,7 @@ func runFirestoreToolGetTest(t *testing.T) {
if !ok {
t.Fatalf("unable to find tools in response body")
}
// Compare as JSON strings to handle any ordering differences
gotJSON, _ := json.Marshal(got)
wantJSON, _ := json.Marshal(tc.want)
@@ -212,31 +212,31 @@ func runFirestoreValidateRulesTest(t *testing.T) {
isErr bool
}{
{
name: "validate valid rules",
api: "http://127.0.0.1:5000/api/tool/firestore-validate-rules/invoke",
name: "validate valid rules",
api: "http://127.0.0.1:5000/api/tool/firestore-validate-rules/invoke",
requestBody: bytes.NewBuffer([]byte(`{
"source": "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /{document=**} {\n allow read, write: if true;\n }\n }\n}"
}`)),
wantRegex: `"valid":true.*"issueCount":0`,
isErr: false,
wantRegex: `"valid":true.*"issueCount":0`,
isErr: false,
},
{
name: "validate rules with syntax error",
api: "http://127.0.0.1:5000/api/tool/firestore-validate-rules/invoke",
name: "validate rules with syntax error",
api: "http://127.0.0.1:5000/api/tool/firestore-validate-rules/invoke",
requestBody: bytes.NewBuffer([]byte(`{
"source": "rules_version = '2';\nservice cloud.firestore {\n match /databases/{database}/documents {\n match /{document=**} {\n allow read, write: if true;;\n }\n }\n}"
}`)),
wantRegex: `"valid":false.*"issueCount":[1-9]`,
isErr: false,
wantRegex: `"valid":false.*"issueCount":[1-9]`,
isErr: false,
},
{
name: "validate rules with missing version",
api: "http://127.0.0.1:5000/api/tool/firestore-validate-rules/invoke",
name: "validate rules with missing version",
api: "http://127.0.0.1:5000/api/tool/firestore-validate-rules/invoke",
requestBody: bytes.NewBuffer([]byte(`{
"source": "service cloud.firestore {\n match /databases/{database}/documents {\n match /{document=**} {\n allow read, write: if true;\n }\n }\n}"
}`)),
wantRegex: `"valid":false.*"issueCount":[1-9]`,
isErr: false,
wantRegex: `"valid":false.*"issueCount":[1-9]`,
isErr: false,
},
{
name: "validate empty rules",
@@ -259,7 +259,7 @@ func runFirestoreValidateRulesTest(t *testing.T) {
t.Fatalf("unable to create request: %s", err)
}
req.Header.Add("Content-type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("unable to send request: %s", err)
@@ -322,7 +322,7 @@ func runFirestoreGetRulesTest(t *testing.T) {
t.Fatalf("unable to create request: %s", err)
}
req.Header.Add("Content-type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("unable to send request: %s", err)
@@ -400,7 +400,7 @@ func runFirestoreMCPToolCallMethod(t *testing.T, docPath1, docPath2 string) {
},
},
wantContains: `\"name\":\"Alice\"`,
wantError: false,
wantError: false,
},
{
name: "MCP Invoke invalid tool",
@@ -418,7 +418,7 @@ func runFirestoreMCPToolCallMethod(t *testing.T, docPath1, docPath2 string) {
},
},
wantContains: `tool with name \"foo\" does not exist`,
wantError: true,
wantError: true,
},
{
name: "MCP Invoke my-param-tool without parameters",
@@ -436,7 +436,7 @@ func runFirestoreMCPToolCallMethod(t *testing.T, docPath1, docPath2 string) {
},
},
wantContains: `parameter \"documentPaths\" is required`,
wantError: true,
wantError: true,
},
{
name: "MCP Invoke my-auth-required-tool",
@@ -454,7 +454,7 @@ func runFirestoreMCPToolCallMethod(t *testing.T, docPath1, docPath2 string) {
},
},
wantContains: `tool with name \"my-auth-required-tool\" does not exist`,
wantError: true,
wantError: true,
},
{
name: "MCP Invoke my-fail-tool",
@@ -467,17 +467,17 @@ func runFirestoreMCPToolCallMethod(t *testing.T, docPath1, docPath2 string) {
Method: "tools/call",
},
Params: map[string]any{
"name": "my-fail-tool",
"name": "my-fail-tool",
"arguments": map[string]any{
"documentPaths": []string{"non-existent/path"},
},
},
},
wantContains: `\"exists\":false`,
wantError: false,
wantError: false,
},
}
for _, tc := range invokeTcs {
t.Run(tc.name, func(t *testing.T) {
reqMarshal, err := json.Marshal(tc.requestBody)
@@ -598,12 +598,12 @@ func setupFirestoreTestData(t *testing.T, ctx context.Context, client *firestore
if err != nil {
t.Fatalf("Failed to create test document 1: %v", err)
}
_, err = client.Collection(collectionName).Doc(docID2).Set(ctx, testData2)
if err != nil {
t.Fatalf("Failed to create test document 2: %v", err)
}
_, err = client.Collection(collectionName).Doc(docID3).Set(ctx, testData3)
if err != nil {
t.Fatalf("Failed to create test document 3: %v", err)
@@ -611,7 +611,7 @@ func setupFirestoreTestData(t *testing.T, ctx context.Context, client *firestore
// Create a subcollection document
subDocData := map[string]interface{}{
"type": "subcollection_doc",
"type": "subcollection_doc",
"value": "test",
}
_, err = client.Collection(collectionName).Doc(docID1).Collection(subCollectionName).Doc("subdoc1").Set(ctx, subDocData)
@@ -696,7 +696,7 @@ func runFirestoreGetDocumentsTest(t *testing.T, docPath1, docPath2 string) {
t.Fatalf("unable to create request: %s", err)
}
req.Header.Add("Content-type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("unable to send request: %s", err)
@@ -773,7 +773,7 @@ func runFirestoreListCollectionsTest(t *testing.T, collectionName, subCollection
t.Fatalf("unable to create request: %s", err)
}
req.Header.Add("Content-type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("unable to send request: %s", err)
@@ -849,7 +849,7 @@ func runFirestoreDeleteDocumentsTest(t *testing.T, docPath string) {
t.Fatalf("unable to create request: %s", err)
}
req.Header.Add("Content-type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("unable to send request: %s", err)
@@ -891,32 +891,32 @@ func runFirestoreQueryCollectionTest(t *testing.T, collectionName string) {
isErr bool
}{
{
name: "query collection with filter",
api: "http://127.0.0.1:5000/api/tool/firestore-query-coll/invoke",
name: "query collection with filter",
api: "http://127.0.0.1:5000/api/tool/firestore-query-coll/invoke",
requestBody: bytes.NewBuffer([]byte(fmt.Sprintf(`{
"collectionPath": "%s",
"filters": ["{\"field\": \"age\", \"op\": \">\", \"value\": 25}"],
"orderBy": "",
"limit": 10
}`, collectionName))),
wantRegex: `"name":"Alice"`,
isErr: false,
wantRegex: `"name":"Alice"`,
isErr: false,
},
{
name: "query collection with orderBy",
api: "http://127.0.0.1:5000/api/tool/firestore-query-coll/invoke",
name: "query collection with orderBy",
api: "http://127.0.0.1:5000/api/tool/firestore-query-coll/invoke",
requestBody: bytes.NewBuffer([]byte(fmt.Sprintf(`{
"collectionPath": "%s",
"filters": [],
"orderBy": "{\"field\": \"age\", \"direction\": \"DESCENDING\"}",
"limit": 2
}`, collectionName))),
wantRegex: `"age":30.*"age":25`, // Should be ordered by age descending (Charlie=35, Alice=30, Bob=25)
isErr: false,
wantRegex: `"age":30.*"age":25`, // Should be ordered by age descending (Charlie=35, Alice=30, Bob=25)
isErr: false,
},
{
name: "query collection with multiple filters",
api: "http://127.0.0.1:5000/api/tool/firestore-query-coll/invoke",
name: "query collection with multiple filters",
api: "http://127.0.0.1:5000/api/tool/firestore-query-coll/invoke",
requestBody: bytes.NewBuffer([]byte(fmt.Sprintf(`{
"collectionPath": "%s",
"filters": [
@@ -926,32 +926,32 @@ func runFirestoreQueryCollectionTest(t *testing.T, collectionName string) {
"orderBy": "",
"limit": 10
}`, collectionName))),
wantRegex: `"name":"Bob".*"name":"Alice"`, // Results may be ordered by document ID
isErr: false,
wantRegex: `"name":"Bob".*"name":"Alice"`, // Results may be ordered by document ID
isErr: false,
},
{
name: "query with limit",
api: "http://127.0.0.1:5000/api/tool/firestore-query-coll/invoke",
name: "query with limit",
api: "http://127.0.0.1:5000/api/tool/firestore-query-coll/invoke",
requestBody: bytes.NewBuffer([]byte(fmt.Sprintf(`{
"collectionPath": "%s",
"filters": [],
"orderBy": "",
"limit": 1
}`, collectionName))),
wantRegex: `^\[{.*}\]$`, // Should return exactly one document
isErr: false,
wantRegex: `^\[{.*}\]$`, // Should return exactly one document
isErr: false,
},
{
name: "query non-existent collection",
api: "http://127.0.0.1:5000/api/tool/firestore-query-coll/invoke",
name: "query non-existent collection",
api: "http://127.0.0.1:5000/api/tool/firestore-query-coll/invoke",
requestBody: bytes.NewBuffer([]byte(`{
"collectionPath": "non-existent-collection",
"filters": [],
"orderBy": "",
"limit": 10
}`)),
wantRegex: `^\[\]$`, // Empty array
isErr: false,
wantRegex: `^\[\]$`, // Empty array
isErr: false,
},
{
name: "missing collectionPath parameter",
@@ -960,18 +960,18 @@ func runFirestoreQueryCollectionTest(t *testing.T, collectionName string) {
isErr: true,
},
{
name: "invalid filter operator",
api: "http://127.0.0.1:5000/api/tool/firestore-query-coll/invoke",
name: "invalid filter operator",
api: "http://127.0.0.1:5000/api/tool/firestore-query-coll/invoke",
requestBody: bytes.NewBuffer([]byte(fmt.Sprintf(`{
"collectionPath": "%s",
"filters": ["{\"field\": \"age\", \"op\": \"INVALID\", \"value\": 25}"],
"orderBy": ""
}`, collectionName))),
isErr: true,
isErr: true,
},
{
name: "query with analyzeQuery",
api: "http://127.0.0.1:5000/api/tool/firestore-query-coll/invoke",
name: "query with analyzeQuery",
api: "http://127.0.0.1:5000/api/tool/firestore-query-coll/invoke",
requestBody: bytes.NewBuffer([]byte(fmt.Sprintf(`{
"collectionPath": "%s",
"filters": [],
@@ -979,8 +979,8 @@ func runFirestoreQueryCollectionTest(t *testing.T, collectionName string) {
"analyzeQuery": true,
"limit": 1
}`, collectionName))),
wantRegex: `"documents":\[.*\]`,
isErr: false,
wantRegex: `"documents":\[.*\]`,
isErr: false,
},
}
@@ -991,7 +991,7 @@ func runFirestoreQueryCollectionTest(t *testing.T, collectionName string) {
t.Fatalf("unable to create request: %s", err)
}
req.Header.Add("Content-type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("unable to send request: %s", err)

View File

@@ -60,8 +60,10 @@ func multiTool(w http.ResponseWriter, r *http.Request) {
handleTool0(w, r)
case "tool1":
handleTool1(w, r)
case "tool1a":
handleTool1a(w, r)
case "tool1id":
handleTool1Id(w, r)
case "tool1name":
handleTool1Name(w, r)
case "tool2":
handleTool2(w, r)
case "tool3":
@@ -131,7 +133,7 @@ func handleTool1(w http.ResponseWriter, r *http.Request) {
}
// handler function for the test server
func handleTool1a(w http.ResponseWriter, r *http.Request) {
func handleTool1Id(w http.ResponseWriter, r *http.Request) {
// expect GET method
if r.Method != http.MethodGet {
errorMessage := fmt.Sprintf("expected GET method but got: %s", string(r.Method))
@@ -151,6 +153,27 @@ func handleTool1a(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
// handler function for the test server
func handleTool1Name(w http.ResponseWriter, r *http.Request) {
// expect GET method
if r.Method != http.MethodGet {
errorMessage := fmt.Sprintf("expected GET method but got: %s", string(r.Method))
http.Error(w, errorMessage, http.StatusBadRequest)
return
}
name := r.URL.Query().Get("name")
if name == "" {
response := "null"
_, err := w.Write([]byte(response))
if err != nil {
http.Error(w, "Failed to write response", http.StatusInternalServerError)
}
return
}
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
// handler function for the test server
func handleTool2(w http.ResponseWriter, r *http.Request) {
// expect GET method
@@ -279,9 +302,10 @@ func TestHttpToolEndpoints(t *testing.T) {
}
select1Want := `"hello world"`
invokeParamWant, invokeParamWantNull, _ := tests.GetNonSpannerInvokeParamWant()
invokeParamWant, invokeIdNullWant, _, _ := tests.GetNonSpannerInvokeParamWant()
nullWant := "null"
tests.RunToolGetTest(t)
tests.RunToolInvokeTest(t, select1Want, invokeParamWant, invokeParamWantNull, false)
tests.RunToolInvokeTest(t, select1Want, invokeParamWant, invokeIdNullWant, nullWant, true, false)
runAdvancedHTTPInvokeTest(t)
}
@@ -385,7 +409,7 @@ func getHTTPToolsConfig(sourceConfig map[string]any, toolKind string) map[string
"requestBody": "{}",
"description": "Simple tool to test end to end functionality.",
},
"my-param-tool": map[string]any{
"my-tool": map[string]any{
"kind": toolKind,
"source": "my-instance",
"method": "GET",
@@ -401,16 +425,26 @@ func getHTTPToolsConfig(sourceConfig map[string]any, toolKind string) map[string
"bodyParams": []tools.Parameter{tools.NewStringParameter("name", "user name")},
"headers": map[string]string{"Content-Type": "application/json"},
},
"my-param-tool2": map[string]any{
"my-tool-by-id": map[string]any{
"kind": toolKind,
"source": "my-instance",
"method": "GET",
"path": "/tool1a",
"path": "/tool1id",
"description": "some description",
"queryParams": []tools.Parameter{
tools.NewIntParameter("id", "user ID")},
"headers": map[string]string{"Content-Type": "application/json"},
},
"my-tool-by-name": map[string]any{
"kind": toolKind,
"source": "my-instance",
"method": "GET",
"path": "/tool1name",
"description": "some description",
"queryParams": []tools.Parameter{
tools.NewStringParameterWithRequired("name", "user name", false)},
"headers": map[string]string{"Content-Type": "application/json"},
},
"my-auth-tool": map[string]any{
"kind": toolKind,
"source": "my-instance",

View File

@@ -102,7 +102,7 @@ func TestMSSQLToolEndpoints(t *testing.T) {
tableNameTemplateParam := "template_param_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
// set up data for param tool
createParamTableStmt, insertParamTableStmt, paramToolStmt, paramToolStmt2, arrayToolStmt, paramTestParams := tests.GetMSSQLParamToolInfo(tableNameParam)
createParamTableStmt, insertParamTableStmt, paramToolStmt, idParamToolStmt, nameParamToolStmt, arrayToolStmt, paramTestParams := tests.GetMSSQLParamToolInfo(tableNameParam)
teardownTable1 := tests.SetupMsSQLTable(t, ctx, pool, createParamTableStmt, insertParamTableStmt, tableNameParam, paramTestParams)
defer teardownTable1(t)
@@ -112,7 +112,7 @@ func TestMSSQLToolEndpoints(t *testing.T) {
defer teardownTable2(t)
// Write config into a file and pass it to command
toolsFile := tests.GetToolsConfig(sourceConfig, MSSQLToolKind, paramToolStmt, paramToolStmt2, arrayToolStmt, authToolStmt)
toolsFile := tests.GetToolsConfig(sourceConfig, MSSQLToolKind, paramToolStmt, idParamToolStmt, nameParamToolStmt, arrayToolStmt, authToolStmt)
toolsFile = tests.AddMSSQLExecuteSqlConfig(t, toolsFile)
tmplSelectCombined, tmplSelectFilterCombined := tests.GetMSSQLTmplToolStatement()
toolsFile = tests.AddTemplateParamConfig(t, toolsFile, MSSQLToolKind, tmplSelectCombined, tmplSelectFilterCombined, "")
@@ -134,8 +134,8 @@ func TestMSSQLToolEndpoints(t *testing.T) {
tests.RunToolGetTest(t)
select1Want, failInvocationWant, createTableStatement := tests.GetMSSQLWants()
invokeParamWant, invokeParamWantNull, mcpInvokeParamWant := tests.GetNonSpannerInvokeParamWant()
tests.RunToolInvokeTest(t, select1Want, invokeParamWant, invokeParamWantNull, false)
invokeParamWant, invokeIdNullWant, nullWant, mcpInvokeParamWant := tests.GetNonSpannerInvokeParamWant()
tests.RunToolInvokeTest(t, select1Want, invokeParamWant, invokeIdNullWant, nullWant, true, false)
tests.RunExecuteSqlToolInvokeTest(t, createTableStatement, select1Want)
tests.RunMCPToolCallMethod(t, mcpInvokeParamWant, failInvocationWant)
tests.RunToolInvokeWithTemplateParameters(t, tableNameTemplateParam, tests.NewTemplateParameterTestConfig())

View File

@@ -93,7 +93,7 @@ func TestMySQLToolEndpoints(t *testing.T) {
tableNameTemplateParam := "template_param_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
// set up data for param tool
createParamTableStmt, insertParamTableStmt, paramToolStmt, paramToolStmt2, arrayToolStmt, paramTestParams := tests.GetMySQLParamToolInfo(tableNameParam)
createParamTableStmt, insertParamTableStmt, paramToolStmt, idParamToolStmt, nameParamToolStmt, arrayToolStmt, paramTestParams := tests.GetMySQLParamToolInfo(tableNameParam)
teardownTable1 := tests.SetupMySQLTable(t, ctx, pool, createParamTableStmt, insertParamTableStmt, tableNameParam, paramTestParams)
defer teardownTable1(t)
@@ -103,7 +103,7 @@ func TestMySQLToolEndpoints(t *testing.T) {
defer teardownTable2(t)
// Write config into a file and pass it to command
toolsFile := tests.GetToolsConfig(sourceConfig, MySQLToolKind, paramToolStmt, paramToolStmt2, arrayToolStmt, authToolStmt)
toolsFile := tests.GetToolsConfig(sourceConfig, MySQLToolKind, paramToolStmt, idParamToolStmt, nameParamToolStmt, arrayToolStmt, authToolStmt)
toolsFile = tests.AddMySqlExecuteSqlConfig(t, toolsFile)
tmplSelectCombined, tmplSelectFilterCombined := tests.GetMySQLTmplToolStatement()
toolsFile = tests.AddTemplateParamConfig(t, toolsFile, MySQLToolKind, tmplSelectCombined, tmplSelectFilterCombined, "")
@@ -125,8 +125,8 @@ func TestMySQLToolEndpoints(t *testing.T) {
tests.RunToolGetTest(t)
select1Want, failInvocationWant, createTableStatement := tests.GetMySQLWants()
invokeParamWant, invokeParamWantNull, mcpInvokeParamWant := tests.GetNonSpannerInvokeParamWant()
tests.RunToolInvokeTest(t, select1Want, invokeParamWant, invokeParamWantNull, false)
invokeParamWant, invokeIdNullWant, nullWant, mcpInvokeParamWant := tests.GetNonSpannerInvokeParamWant()
tests.RunToolInvokeTest(t, select1Want, invokeParamWant, invokeIdNullWant, nullWant, true, false)
tests.RunExecuteSqlToolInvokeTest(t, createTableStatement, select1Want)
tests.RunMCPToolCallMethod(t, mcpInvokeParamWant, failInvocationWant)
tests.RunToolInvokeWithTemplateParameters(t, tableNameTemplateParam, tests.NewTemplateParameterTestConfig())

View File

@@ -101,7 +101,7 @@ func TestNeo4jToolEndpoints(t *testing.T) {
want map[string]any
}{
{
name: "get my-simple-tool",
name: "get my-simple-cypher-tool",
api: "http://127.0.0.1:5000/api/tool/my-simple-cypher-tool/",
want: map[string]any{
"my-simple-cypher-tool": map[string]any{

View File

@@ -99,7 +99,7 @@ func TestPostgres(t *testing.T) {
tableNameTemplateParam := "template_param_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
// set up data for param tool
createParamTableStmt, insertParamTableStmt, paramToolStmt, paramToolStmt2, arrayToolStmt, paramTestParams := tests.GetPostgresSQLParamToolInfo(tableNameParam)
createParamTableStmt, insertParamTableStmt, paramToolStmt, idParamToolStmt, nameParamToolStmt, arrayToolStmt, paramTestParams := tests.GetPostgresSQLParamToolInfo(tableNameParam)
teardownTable1 := tests.SetupPostgresSQLTable(t, ctx, pool, createParamTableStmt, insertParamTableStmt, tableNameParam, paramTestParams)
defer teardownTable1(t)
@@ -109,7 +109,7 @@ func TestPostgres(t *testing.T) {
defer teardownTable2(t)
// Write config into a file and pass it to command
toolsFile := tests.GetToolsConfig(sourceConfig, PostgresToolKind, paramToolStmt, paramToolStmt2, arrayToolStmt, authToolStmt)
toolsFile := tests.GetToolsConfig(sourceConfig, PostgresToolKind, paramToolStmt, idParamToolStmt, nameParamToolStmt, arrayToolStmt, authToolStmt)
toolsFile = tests.AddPgExecuteSqlConfig(t, toolsFile)
tmplSelectCombined, tmplSelectFilterCombined := tests.GetPostgresSQLTmplToolStatement()
toolsFile = tests.AddTemplateParamConfig(t, toolsFile, PostgresToolKind, tmplSelectCombined, tmplSelectFilterCombined, "")
@@ -131,8 +131,8 @@ func TestPostgres(t *testing.T) {
tests.RunToolGetTest(t)
select1Want, failInvocationWant, createTableStatement := tests.GetPostgresWants()
invokeParamWant, invokeParamWantNull, mcpInvokeParamWant := tests.GetNonSpannerInvokeParamWant()
tests.RunToolInvokeTest(t, select1Want, invokeParamWant, invokeParamWantNull, true)
invokeParamWant, invokeIdNullWant, nullWant, mcpInvokeParamWant := tests.GetNonSpannerInvokeParamWant()
tests.RunToolInvokeTest(t, select1Want, invokeParamWant, invokeIdNullWant, nullWant, true, true)
tests.RunExecuteSqlToolInvokeTest(t, createTableStatement, select1Want)
tests.RunMCPToolCallMethod(t, mcpInvokeParamWant, failInvocationWant)
tests.RunToolInvokeWithTemplateParameters(t, tableNameTemplateParam, tests.NewTemplateParameterTestConfig())

View File

@@ -99,18 +99,19 @@ func TestRedisToolEndpoints(t *testing.T) {
tests.RunToolGetTest(t)
select1Want, failInvocationWant, invokeParamWant, invokeParamWantNull, mcpInvokeParamWant := tests.GetRedisValkeyWants()
tests.RunToolInvokeTest(t, select1Want, invokeParamWant, invokeParamWantNull, true)
select1Want, failInvocationWant, invokeParamWant, invokeIdNullWant, nullWant, mcpInvokeParamWant := tests.GetRedisValkeyWants()
tests.RunToolInvokeTest(t, select1Want, invokeParamWant, invokeIdNullWant, nullWant, true, true)
tests.RunMCPToolCallMethod(t, mcpInvokeParamWant, failInvocationWant)
}
func setupRedisDB(t *testing.T, ctx context.Context, client *redis.Client) func(*testing.T) {
keys := []string{"row1", "row2", "row3", "row4"}
keys := []string{"row1", "row2", "row3", "row4", "null"}
commands := [][]any{
{"HSET", keys[0], "id", 1, "name", "Alice"},
{"HSET", keys[1], "id", 2, "name", "Jane"},
{"HSET", keys[2], "id", 3, "name", "Sid"},
{"HSET", keys[3], "id", 4, "name", nil},
{"SET", keys[4], "null"},
{"HSET", tests.ServiceAccountEmail, "name", "Alice"},
}
for _, c := range commands {

View File

@@ -108,7 +108,7 @@ func TestSpannerToolEndpoints(t *testing.T) {
tableNameTemplateParam := "template_param_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
// set up data for param tool
createParamTableStmt, insertParamTableStmt, paramToolStmt, paramToolStmt2, arrayToolStmt, paramTestParams := getSpannerParamToolInfo(tableNameParam)
createParamTableStmt, insertParamTableStmt, paramToolStmt, idParamToolStmt, nameParamToolStmt, arrayToolStmt, paramTestParams := getSpannerParamToolInfo(tableNameParam)
dbString := fmt.Sprintf(
"projects/%s/instances/%s/databases/%s",
SpannerProject,
@@ -129,7 +129,7 @@ func TestSpannerToolEndpoints(t *testing.T) {
defer teardownTableTmpl(t)
// Write config into a file and pass it to command
toolsFile := tests.GetToolsConfig(sourceConfig, SpannerToolKind, paramToolStmt, paramToolStmt2, arrayToolStmt, authToolStmt)
toolsFile := tests.GetToolsConfig(sourceConfig, SpannerToolKind, paramToolStmt, idParamToolStmt, nameParamToolStmt, arrayToolStmt, authToolStmt)
toolsFile = addSpannerExecuteSqlConfig(t, toolsFile)
toolsFile = addSpannerReadOnlyConfig(t, toolsFile)
toolsFile = addTemplateParamConfig(t, toolsFile)
@@ -153,11 +153,12 @@ func TestSpannerToolEndpoints(t *testing.T) {
select1Want := "[{\"\":\"1\"}]"
accessSchemaWant := "[{\"schema_name\":\"INFORMATION_SCHEMA\"}]"
invokeParamWant := "[{\"id\":\"1\",\"name\":\"Alice\"},{\"id\":\"3\",\"name\":\"Sid\"}]"
invokeParamWantNull := `[{"id":"4","name":null}]`
mcpInvokeParamWant := `{"jsonrpc":"2.0","id":"my-param-tool","result":{"content":[{"type":"text","text":"{\"id\":\"1\",\"name\":\"Alice\"}"},{"type":"text","text":"{\"id\":\"3\",\"name\":\"Sid\"}"}]}}`
invokeIdNullWant := `[{"id":"4","name":null}]`
mcpInvokeParamWant := `{"jsonrpc":"2.0","id":"my-tool","result":{"content":[{"type":"text","text":"{\"id\":\"1\",\"name\":\"Alice\"}"},{"type":"text","text":"{\"id\":\"3\",\"name\":\"Sid\"}"}]}}`
nullWant := "null"
failInvocationWant := `"jsonrpc":"2.0","id":"invoke-fail-tool","result":{"content":[{"type":"text","text":"unable to execute client: unable to parse row: spanner: code = \"InvalidArgument\", desc = \"Syntax error: Unexpected identifier \\\\\\\"SELEC\\\\\\\" [at 1:1]\\\\nSELEC 1;\\\\n^\"`
tests.RunToolInvokeTest(t, select1Want, invokeParamWant, invokeParamWantNull, true)
tests.RunToolInvokeTest(t, select1Want, invokeParamWant, invokeIdNullWant, nullWant, true, true)
tests.RunMCPToolCallMethod(t, mcpInvokeParamWant, failInvocationWant)
runSpannerSchemaToolInvokeTest(t, accessSchemaWant)
runSpannerExecuteSqlToolInvokeTest(t, select1Want, invokeParamWant, tableNameParam, tableNameAuth)
@@ -170,15 +171,16 @@ func TestSpannerToolEndpoints(t *testing.T) {
tests.RunToolInvokeWithTemplateParameters(t, tableNameTemplateParam, templateParamTestConfig)
}
// getSpannerToolInfo returns statements and param for my-param-tool for spanner-sql kind
func getSpannerParamToolInfo(tableName string) (string, string, string, string, string, map[string]any) {
// getSpannerToolInfo returns statements and param for my-tool for spanner-sql kind
func getSpannerParamToolInfo(tableName string) (string, string, string, string, string, string, map[string]any) {
createStatement := fmt.Sprintf("CREATE TABLE %s (id INT64, name STRING(MAX)) PRIMARY KEY (id)", tableName)
insertStatement := fmt.Sprintf("INSERT INTO %s (id, name) VALUES (1, @name1), (2, @name2), (3, @name3), (4, @name4)", tableName)
toolStatement := fmt.Sprintf("SELECT * FROM %s WHERE id = @id OR name = @name", tableName)
toolStatement2 := fmt.Sprintf("SELECT * FROM %s WHERE id = @id", tableName)
idToolStatement := fmt.Sprintf("SELECT * FROM %s WHERE id = @id", tableName)
nameToolStatement := fmt.Sprintf("SELECT * FROM %s WHERE name = @name", tableName)
arrayToolStatement := fmt.Sprintf("SELECT * FROM %s WHERE id IN UNNEST(@idArray) AND name IN UNNEST(@nameArray)", tableName)
params := map[string]any{"name1": "Alice", "name2": "Jane", "name3": "Sid", "name4": nil}
return createStatement, insertStatement, toolStatement, toolStatement2, arrayToolStatement, params
return createStatement, insertStatement, toolStatement, idToolStatement, nameToolStatement, arrayToolStatement, params
}
// getSpannerAuthToolInfo returns statements and param of my-auth-tool for spanner-sql kind

View File

@@ -81,14 +81,15 @@ func setupSQLiteTestDB(t *testing.T, ctx context.Context, db *sql.DB, createStat
}
}
func getSQLiteParamToolInfo(tableName string) (string, string, string, string, string, []any) {
func getSQLiteParamToolInfo(tableName string) (string, string, string, string, string, string, []any) {
createStatement := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s (id INTEGER PRIMARY KEY, name TEXT);", tableName)
insertStatement := fmt.Sprintf("INSERT INTO %s (name) VALUES (?), (?), (?), (?);", tableName)
toolStatement := fmt.Sprintf("SELECT * FROM %s WHERE id = ? OR name = ?;", tableName)
toolStatement2 := fmt.Sprintf("SELECT * FROM %s WHERE id = ?;", tableName)
idToolStatement := fmt.Sprintf("SELECT * FROM %s WHERE id = ?;", tableName)
nameToolStatement := fmt.Sprintf("SELECT * FROM %s WHERE name = ?;", tableName)
arrayToolStatement := fmt.Sprintf("SELECT * FROM %s WHERE id = ANY({{.idArray}}) AND name = ANY({{.nameArray}});", tableName)
params := []any{"Alice", "Jane", "Sid", nil}
return createStatement, insertStatement, toolStatement, toolStatement2, arrayToolStatement, params
return createStatement, insertStatement, toolStatement, idToolStatement, nameToolStatement, arrayToolStatement, params
}
func getSQLiteAuthToolInfo(tableName string) (string, string, string, []any) {
@@ -126,7 +127,7 @@ func TestSQLiteToolEndpoint(t *testing.T) {
tableNameTemplateParam := "template_param_table_" + strings.ReplaceAll(uuid.New().String(), "-", "")
// set up data for param tool
createParamTableStmt, insertParamTableStmt, paramToolStmt, paramToolStmt2, arrayToolStmt, paramTestParams := getSQLiteParamToolInfo(tableNameParam)
createParamTableStmt, insertParamTableStmt, paramToolStmt, idParamToolStmt, nameParamToolStmt, arrayToolStmt, paramTestParams := getSQLiteParamToolInfo(tableNameParam)
setupSQLiteTestDB(t, ctx, db, createParamTableStmt, insertParamTableStmt, tableNameParam, paramTestParams)
// set up data for auth tool
@@ -134,7 +135,7 @@ func TestSQLiteToolEndpoint(t *testing.T) {
setupSQLiteTestDB(t, ctx, db, createAuthTableStmt, insertAuthTableStmt, tableNameAuth, authTestParams)
// Write config into a file and pass it to command
toolsFile := tests.GetToolsConfig(sourceConfig, SQLiteToolKind, paramToolStmt, paramToolStmt2, arrayToolStmt, authToolStmt)
toolsFile := tests.GetToolsConfig(sourceConfig, SQLiteToolKind, paramToolStmt, idParamToolStmt, nameParamToolStmt, arrayToolStmt, authToolStmt)
tmplSelectCombined, tmplSelectFilterCombined := getSQLiteTmplToolStatement()
toolsFile = tests.AddTemplateParamConfig(t, toolsFile, SQLiteToolKind, tmplSelectCombined, tmplSelectFilterCombined, "")
@@ -156,8 +157,8 @@ func TestSQLiteToolEndpoint(t *testing.T) {
select1Want := "[{\"1\":1}]"
failInvocationWant := `{"jsonrpc":"2.0","id":"invoke-fail-tool","result":{"content":[{"type":"text","text":"unable to execute query: SQL logic error: near \"SELEC\": syntax error (1)"}],"isError":true}}`
invokeParamWant, invokeParamWantNull, mcpInvokeParamWant := tests.GetNonSpannerInvokeParamWant()
tests.RunToolInvokeTest(t, select1Want, invokeParamWant, invokeParamWantNull, false)
invokeParamWant, invokeIdNullWant, nullWant, mcpInvokeParamWant := tests.GetNonSpannerInvokeParamWant()
tests.RunToolInvokeTest(t, select1Want, invokeParamWant, invokeIdNullWant, nullWant, true, false)
tests.RunMCPToolCallMethod(t, mcpInvokeParamWant, failInvocationWant)
tests.RunToolInvokeWithTemplateParameters(t, tableNameTemplateParam, tests.NewTemplateParameterTestConfig())
}

View File

@@ -76,7 +76,7 @@ func RunToolGetTest(t *testing.T) {
}
// RunToolInvoke runs the tool invoke endpoint
func RunToolInvokeTest(t *testing.T, select1Want, invokeParamWant, invokeParamWantNull string, supportsArray bool) {
func RunToolInvokeTest(t *testing.T, select1Want, invokeParamWant, invokeIdNullWant, nullString string, supportNullParam, supportsArray bool) {
// Get ID token
idToken, err := GetGoogleIdToken(ClientId)
if err != nil {
@@ -101,31 +101,39 @@ func RunToolInvokeTest(t *testing.T, select1Want, invokeParamWant, invokeParamWa
isErr: false,
},
{
name: "invoke my-param-tool",
api: "http://127.0.0.1:5000/api/tool/my-param-tool/invoke",
name: "invoke my-tool",
api: "http://127.0.0.1:5000/api/tool/my-tool/invoke",
requestHeader: map[string]string{},
requestBody: bytes.NewBuffer([]byte(`{"id": 3, "name": "Alice"}`)),
want: invokeParamWant,
isErr: false,
},
{
name: "invoke my-param-tool2 with nil response",
api: "http://127.0.0.1:5000/api/tool/my-param-tool2/invoke",
name: "invoke my-tool-by-id with nil response",
api: "http://127.0.0.1:5000/api/tool/my-tool-by-id/invoke",
requestHeader: map[string]string{},
requestBody: bytes.NewBuffer([]byte(`{"id": 4}`)),
want: invokeParamWantNull,
want: invokeIdNullWant,
isErr: false,
},
{
name: "Invoke my-param-tool without parameters",
api: "http://127.0.0.1:5000/api/tool/my-param-tool/invoke",
name: "invoke my-tool-by-name with nil response",
api: "http://127.0.0.1:5000/api/tool/my-tool-by-name/invoke",
requestHeader: map[string]string{},
requestBody: bytes.NewBuffer([]byte(`{}`)),
want: nullString,
isErr: !supportNullParam,
},
{
name: "Invoke my-tool without parameters",
api: "http://127.0.0.1:5000/api/tool/my-tool/invoke",
requestHeader: map[string]string{},
requestBody: bytes.NewBuffer([]byte(`{}`)),
isErr: true,
},
{
name: "Invoke my-param-tool with insufficient parameters",
api: "http://127.0.0.1:5000/api/tool/my-param-tool/invoke",
name: "Invoke my-tool with insufficient parameters",
api: "http://127.0.0.1:5000/api/tool/my-tool/invoke",
requestHeader: map[string]string{},
requestBody: bytes.NewBuffer([]byte(`{"id": 1}`)),
isErr: true,
@@ -629,17 +637,17 @@ func RunMCPToolCallMethod(t *testing.T, invokeParamWant, failInvocationWant stri
want string
}{
{
name: "MCP Invoke my-param-tool",
name: "MCP Invoke my-tool",
api: "http://127.0.0.1:5000/mcp",
requestHeader: map[string]string{},
requestBody: jsonrpc.JSONRPCRequest{
Jsonrpc: "2.0",
Id: "my-param-tool",
Id: "my-tool",
Request: jsonrpc.Request{
Method: "tools/call",
},
Params: map[string]any{
"name": "my-param-tool",
"name": "my-tool",
"arguments": map[string]any{
"id": int(3),
"name": "Alice",
@@ -666,7 +674,7 @@ func RunMCPToolCallMethod(t *testing.T, invokeParamWant, failInvocationWant stri
want: `{"jsonrpc":"2.0","id":"invalid-tool","error":{"code":-32602,"message":"invalid tool name: tool with name \"foo\" does not exist"}}`,
},
{
name: "MCP Invoke my-param-tool without parameters",
name: "MCP Invoke my-tool without parameters",
api: "http://127.0.0.1:5000/mcp",
requestHeader: map[string]string{},
requestBody: jsonrpc.JSONRPCRequest{
@@ -676,14 +684,14 @@ func RunMCPToolCallMethod(t *testing.T, invokeParamWant, failInvocationWant stri
Method: "tools/call",
},
Params: map[string]any{
"name": "my-param-tool",
"name": "my-tool",
"arguments": map[string]any{},
},
},
want: `{"jsonrpc":"2.0","id":"invoke-without-parameter","error":{"code":-32602,"message":"provided parameters were invalid: parameter \"id\" is required"}}`,
},
{
name: "MCP Invoke my-param-tool with insufficient parameters",
name: "MCP Invoke my-tool with insufficient parameters",
api: "http://127.0.0.1:5000/mcp",
requestHeader: map[string]string{},
requestBody: jsonrpc.JSONRPCRequest{
@@ -693,7 +701,7 @@ func RunMCPToolCallMethod(t *testing.T, invokeParamWant, failInvocationWant stri
Method: "tools/call",
},
Params: map[string]any{
"name": "my-param-tool",
"name": "my-tool",
"arguments": map[string]any{"id": 1},
},
},

View File

@@ -102,18 +102,19 @@ func TestValkeyToolEndpoints(t *testing.T) {
tests.RunToolGetTest(t)
select1Want, failInvocationWant, invokeParamWant, invokeParamWantNull, mcpInvokeParamWant := tests.GetRedisValkeyWants()
tests.RunToolInvokeTest(t, select1Want, invokeParamWant, invokeParamWantNull, true)
select1Want, failInvocationWant, invokeParamWant, invokeIdNullWant, nullWant, mcpInvokeParamWant := tests.GetRedisValkeyWants()
tests.RunToolInvokeTest(t, select1Want, invokeParamWant, invokeIdNullWant, nullWant, true, true)
tests.RunMCPToolCallMethod(t, mcpInvokeParamWant, failInvocationWant)
}
func setupValkeyDB(t *testing.T, ctx context.Context, client valkey.Client) func(*testing.T) {
keys := []string{"row1", "row2", "row3", "row4"}
keys := []string{"row1", "row2", "row3", "row4", "null"}
commands := [][]string{
{"HSET", keys[0], "name", "Alice", "id", "1"},
{"HSET", keys[1], "name", "Jane", "id", "2"},
{"HSET", keys[2], "name", "Sid", "id", "3"},
{"HSET", keys[3], "name", "", "id", "4"},
{"SET", keys[4], "null"},
{"HSET", tests.ServiceAccountEmail, "name", "Alice"},
}
builtCmds := make(valkey.Commands, len(commands))