mirror of
https://github.com/googleapis/genai-toolbox.git
synced 2026-01-12 17:09:48 -05:00
Compare commits
2 Commits
ci-py-test
...
enum-regex
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9af7970322 | ||
|
|
d96fceacd2 |
21
README.md
21
README.md
@@ -160,17 +160,6 @@ go install github.com/googleapis/genai-toolbox@v0.15.0
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Gemini CLI Extensions</summary>
|
||||
|
||||
To install Gemini CLI Extensions for MCP Toolbox, run the following command:
|
||||
|
||||
```sh
|
||||
gemini extensions install https://github.com/gemini-cli-extensions/mcp-toolbox
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Running the server
|
||||
|
||||
[Configure](#configuration) a `tools.yaml` to define your tools, and then
|
||||
@@ -244,16 +233,6 @@ toolbox --tools-file "tools.yaml"
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
|
||||
<summary>Gemini CLI</summary>
|
||||
|
||||
Interact with your custom tools using natural language. Check
|
||||
[gemini-cli-extensions/mcp-toolbox](https://github.com/gemini-cli-extensions/mcp-toolbox)
|
||||
for more information.
|
||||
|
||||
</details>
|
||||
|
||||
You can use `toolbox help` for a full list of flags! To stop the server, send a
|
||||
terminate signal (`ctrl+c` on most platforms).
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ api_key = os.environ.get("GOOGLE_API_KEY") or "your-api-key" # Set your API key
|
||||
os.environ["GOOGLE_API_KEY"] = api_key
|
||||
|
||||
async def main():
|
||||
with ToolboxSyncClient("http://localhost:5000") as toolbox_client:
|
||||
with ToolboxSyncClient("http://127.0.0.1:5000") as toolbox_client:
|
||||
|
||||
prompt = """
|
||||
You're a helpful hotel assistant. You handle hotel searching, booking and
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
---
|
||||
title: Connect via Gemini CLI Extensions
|
||||
type: docs
|
||||
weight: 2
|
||||
description: "Connect to Toolbox via Gemini CLI Extensions."
|
||||
---
|
||||
|
||||
## Gemini CLI Extensions
|
||||
|
||||
[Gemini CLI][gemini-cli] is an open-source AI agent designed to assist with development workflows by assisting with coding, debugging, data exploration, and content creation. Its mission is to provide an agentic interface for interacting with database and analytics services and popular open-source databases.
|
||||
|
||||
### How extensions work
|
||||
Gemini CLI is highly extensible, allowing for the addition of new tools and capabilities through extensions. You can load the extensions from a GitHub URL, a local directory, or a configurable registry. They provide new tools, slash commands, and prompts to assist with your workflow.
|
||||
|
||||
Use the Gemini CLI Extensions to load prebuilt or custom tools to interact with your databases.
|
||||
|
||||
[gemini-cli]: https://google-gemini.github.io/gemini-cli/
|
||||
|
||||
Below are a list of Gemini CLI Extensions powered by MCP Toolbox:
|
||||
|
||||
* [alloydb](https://github.com/gemini-cli-extensions/alloydb)
|
||||
* [alloydb-observability](https://github.com/gemini-cli-extensions/alloydb-observability)
|
||||
* [bigquery-conversational-analytics](https://github.com/gemini-cli-extensions/bigquery-conversational-analytics)
|
||||
* [bigquery-data-analytics](https://github.com/gemini-cli-extensions/bigquery-data-analytics)
|
||||
* [cloud-sql-mysql](https://github.com/gemini-cli-extensions/cloud-sql-mysql)
|
||||
* [cloud-sql-mysql-observability](https://github.com/gemini-cli-extensions/cloud-sql-mysql-observability)
|
||||
* [cloud-sql-postgresql](https://github.com/gemini-cli-extensions/cloud-sql-postgresql)
|
||||
* [cloud-sql-postgresql-observability](https://github.com/gemini-cli-extensions/cloud-sql-postgresql-observability)
|
||||
* [cloud-sql-sqlserver](https://github.com/gemini-cli-extensions/cloud-sql-sqlserver)
|
||||
* [cloud-sql-sqlserver-observability](https://github.com/gemini-cli-extensions/cloud-sql-sqlserver-observability)
|
||||
* [dataplex](https://github.com/gemini-cli-extensions/dataplex)
|
||||
* [firestore-native](https://github.com/gemini-cli-extensions/firestore-native)
|
||||
* [looker](https://github.com/gemini-cli-extensions/looker)
|
||||
* [mcp-toolbox](https://github.com/gemini-cli-extensions/mcp-toolbox)
|
||||
* [mysql](https://github.com/gemini-cli-extensions/mysql)
|
||||
* [postgres](https://github.com/gemini-cli-extensions/postgres)
|
||||
* [spanner](https://github.com/gemini-cli-extensions/spanner)
|
||||
* [sql-server](https://github.com/gemini-cli-extensions/sql-server)
|
||||
@@ -153,6 +153,32 @@ will be thrown in case of value type mismatch.
|
||||
valueType: integer # This enforces the value type for all entries.
|
||||
```
|
||||
|
||||
### Enum Parameters
|
||||
|
||||
The `enum` type allow users to specify a set of allowed values with that
|
||||
parameter. When toolbox parse the input of parameters, it will check against the
|
||||
allowed values.
|
||||
|
||||
```yaml
|
||||
parameter:
|
||||
- name: airline
|
||||
type: enum
|
||||
description: name of airline.
|
||||
enumType: string
|
||||
allowedValues:
|
||||
- cymbalair
|
||||
- delta
|
||||
```
|
||||
|
||||
Other than the regular fields required with the `enumType` specified, below are
|
||||
the additional fields that are needed when using `enum` type.
|
||||
|
||||
| **field** | **type** | **required** | **description** |
|
||||
|---------------|:--------:|:------------:|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| enumType | string | true | This indicates the type of the value. Must be one of the supported parameter type (e.g. `string`/ `integer` / `float` / `boolean` / `array` / `map`). |
|
||||
| escape | bool | false | Indicates if the value will be escaped if used with `templateParameters`. Escaping will add double quotes (or backticks/square brackets depending on the source) depending on the database. This is defaulted to `false`. |
|
||||
| allowedValues | []string | true | Input value will be checked against this field. |
|
||||
|
||||
### Authenticated Parameters
|
||||
|
||||
Authenticated parameters are automatically populated with user
|
||||
|
||||
@@ -19,10 +19,12 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
yaml "github.com/goccy/go-yaml"
|
||||
"github.com/googleapis/genai-toolbox/internal/util"
|
||||
)
|
||||
|
||||
@@ -33,6 +35,7 @@ const (
|
||||
typeBool = "boolean"
|
||||
typeArray = "array"
|
||||
typeMap = "map"
|
||||
typeEnum = "enum"
|
||||
)
|
||||
|
||||
// ParamValues is an ordered list of ParamValue
|
||||
@@ -179,6 +182,11 @@ func GetParams(params Parameters, paramValuesMap map[string]any) (ParamValues, e
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing parameter %s", k)
|
||||
}
|
||||
if p.GetType() == typeEnum {
|
||||
if p.(*EnumParameter).GetEscape() {
|
||||
v = fmt.Sprintf(`"%s"`, v)
|
||||
}
|
||||
}
|
||||
resultParamValues = append(resultParamValues, ParamValue{Name: k, Value: v})
|
||||
}
|
||||
return resultParamValues, nil
|
||||
@@ -233,6 +241,7 @@ type Parameter interface {
|
||||
// but this is done to differentiate it from the fields in CommonParameter.
|
||||
GetName() string
|
||||
GetType() string
|
||||
GetDesc() string
|
||||
GetDefault() any
|
||||
GetRequired() bool
|
||||
GetAuthServices() []ParamAuthService
|
||||
@@ -278,7 +287,7 @@ func parseParamFromDelayedUnmarshaler(ctx context.Context, u *util.DelayedUnmars
|
||||
|
||||
t, ok := p["type"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("parameter is missing 'type' field: %w", err)
|
||||
return nil, fmt.Errorf("parameter is missing 'type' field")
|
||||
}
|
||||
|
||||
dec, err := util.NewStrictDecoder(p)
|
||||
@@ -356,6 +365,17 @@ func parseParamFromDelayedUnmarshaler(ctx context.Context, u *util.DelayedUnmars
|
||||
a.AuthSources = nil
|
||||
}
|
||||
return a, nil
|
||||
case typeEnum:
|
||||
a := &EnumParameter{}
|
||||
if err := dec.DecodeContext(ctx, a); err != nil {
|
||||
return nil, fmt.Errorf("unable to parse as %q: %w", t, err)
|
||||
}
|
||||
if a.AuthSources != nil {
|
||||
logger.WarnContext(ctx, "`authSources` is deprecated, use `authServices` for parameters instead")
|
||||
a.AuthServices = append(a.AuthServices, a.AuthSources...)
|
||||
a.AuthSources = nil
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
return nil, fmt.Errorf("%q is not valid type for a parameter", t)
|
||||
}
|
||||
@@ -427,6 +447,11 @@ func (p *CommonParameter) GetType() string {
|
||||
return p.Type
|
||||
}
|
||||
|
||||
// GetDesc returns the description specified for the Parameter.
|
||||
func (p *CommonParameter) GetDesc() string {
|
||||
return p.Desc
|
||||
}
|
||||
|
||||
// GetRequired returns the type specified for the Parameter.
|
||||
func (p *CommonParameter) GetRequired() bool {
|
||||
// parameters are defaulted to required
|
||||
@@ -1230,3 +1255,155 @@ func (p *MapParameter) McpManifest() ParameterMcpManifest {
|
||||
AdditionalProperties: additionalProperties,
|
||||
}
|
||||
}
|
||||
|
||||
// NewEnumParameter is a convenience function for initializing a EnumParameter.
|
||||
func NewEnumParameter(param Parameter, escape bool, allowedValues []any) *EnumParameter {
|
||||
d := param.GetDefault()
|
||||
r := param.GetRequired()
|
||||
return &EnumParameter{
|
||||
CommonParameter: CommonParameter{
|
||||
Name: param.GetName(),
|
||||
Type: typeEnum,
|
||||
Desc: param.GetDesc(),
|
||||
Required: &r,
|
||||
AuthServices: param.GetAuthServices(),
|
||||
},
|
||||
EnumType: param.GetType(),
|
||||
Escape: escape,
|
||||
AllowedValues: allowedValues,
|
||||
EnumItem: param,
|
||||
Default: &d,
|
||||
}
|
||||
}
|
||||
|
||||
// EnumParameter is a parameter that allow users to specify
|
||||
// allowedValues to provide a fixed set of values. This will
|
||||
// make parameter, especially templateParameter more secure and safe.
|
||||
type EnumParameter struct {
|
||||
CommonParameter `yaml:",inline"`
|
||||
Default *any `yaml:"default"`
|
||||
EnumType string `yaml:"enumType"`
|
||||
Escape bool `yaml:"escape"`
|
||||
AllowedValues []any `yaml:"allowedValues"`
|
||||
EnumItem Parameter
|
||||
}
|
||||
|
||||
// Ensure EnumParameter implements the Parameter interface.
|
||||
var _ Parameter = &EnumParameter{}
|
||||
|
||||
// UnmarshalYAML handles parsing the EnumParameter from YAML input.
|
||||
func (p *EnumParameter) UnmarshalYAML(ctx context.Context, unmarshal func(interface{}) error) error {
|
||||
var rawItem map[string]any
|
||||
if err := unmarshal(&rawItem); err != nil {
|
||||
return fmt.Errorf("error parsing enum parameter: %w", err)
|
||||
}
|
||||
|
||||
// extract enum parameter known fields
|
||||
enumType, ok := rawItem["enumType"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("error parsing 'enumType' field")
|
||||
}
|
||||
escape := false
|
||||
if v, ok := rawItem["escape"]; ok {
|
||||
if escape, ok = v.(bool); !ok {
|
||||
return fmt.Errorf("error parsing 'escape' field")
|
||||
}
|
||||
}
|
||||
allowedValues, ok := rawItem["allowedValues"].([]any)
|
||||
if !ok {
|
||||
return fmt.Errorf("error parsing 'allowedValues' field")
|
||||
}
|
||||
rawItem["type"] = enumType
|
||||
|
||||
// remove the extracted field from the map
|
||||
delete(rawItem, "enumType")
|
||||
delete(rawItem, "escape")
|
||||
delete(rawItem, "allowedValues")
|
||||
|
||||
// create a util.DelayedUnmarshaler from the remaining fields
|
||||
m, err := yaml.Marshal(rawItem)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error marshaling remaining fields from enum parameter")
|
||||
}
|
||||
var delayedUnmarshaler util.DelayedUnmarshaler
|
||||
if err = yaml.UnmarshalContext(ctx, m, &delayedUnmarshaler); err != nil {
|
||||
return fmt.Errorf("error unmarhaling into DelayedUnmarshaler")
|
||||
}
|
||||
parameter, err := parseParamFromDelayedUnmarshaler(ctx, &delayedUnmarshaler)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d := parameter.GetDefault()
|
||||
r := parameter.GetRequired()
|
||||
|
||||
p.Default = &d
|
||||
p.CommonParameter = CommonParameter{
|
||||
Name: parameter.GetName(),
|
||||
Type: "enum",
|
||||
Desc: parameter.GetDesc(),
|
||||
Required: &r,
|
||||
AuthServices: parameter.GetAuthServices(),
|
||||
}
|
||||
p.EnumType = enumType
|
||||
p.Escape = escape
|
||||
p.AllowedValues = allowedValues
|
||||
p.EnumItem = parameter
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parse validates and parses an incoming value for enum parameter.
|
||||
func (p *EnumParameter) Parse(v any) (any, error) {
|
||||
input := fmt.Sprintf("%v", v)
|
||||
var exists bool
|
||||
for _, av := range p.AllowedValues {
|
||||
target := fmt.Sprintf("%v", av)
|
||||
if MatchStringOrRegex(input, target) {
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("unable to parse enum parameter: input is not part of allowed values")
|
||||
}
|
||||
|
||||
return p.EnumItem.Parse(v)
|
||||
}
|
||||
|
||||
// MatchStringOrRegex checks if the input matches the target
|
||||
func MatchStringOrRegex(input, target string) bool {
|
||||
re, err := regexp.Compile(target)
|
||||
if err != nil {
|
||||
return strings.Contains(input, target)
|
||||
}
|
||||
return re.MatchString(input)
|
||||
}
|
||||
|
||||
func (p *EnumParameter) GetAuthServices() []ParamAuthService {
|
||||
return p.AuthServices
|
||||
}
|
||||
|
||||
func (p *EnumParameter) GetDefault() any {
|
||||
if p.Default == nil {
|
||||
return nil
|
||||
}
|
||||
return *p.Default
|
||||
}
|
||||
|
||||
func (p *EnumParameter) GetEnumType() string {
|
||||
return p.EnumType
|
||||
}
|
||||
|
||||
func (p *EnumParameter) GetEscape() bool {
|
||||
return p.Escape
|
||||
}
|
||||
|
||||
// Manifest returns the manifest for the EnumParameter.
|
||||
func (p *EnumParameter) Manifest() ParameterManifest {
|
||||
return p.EnumItem.Manifest()
|
||||
}
|
||||
|
||||
// McpManifest returns the MCP manifest for EnumParameter.
|
||||
func (p *EnumParameter) McpManifest() ParameterMcpManifest {
|
||||
return p.EnumItem.McpManifest()
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ package tools_test
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -351,6 +352,37 @@ func TestParametersMarshal(t *testing.T) {
|
||||
tools.NewMapParameter("my_generic_map", "this param is a generic map", ""),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "enum string",
|
||||
in: []map[string]any{
|
||||
{
|
||||
"name": "enum_string",
|
||||
"type": "enum",
|
||||
"enumType": "string",
|
||||
"description": "enum string parameter",
|
||||
"allowedValues": []any{"foo", "bar"},
|
||||
},
|
||||
},
|
||||
want: tools.Parameters{
|
||||
tools.NewEnumParameter(tools.NewStringParameter("enum_string", "enum string parameter"), false, []any{"foo", "bar"}),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "enum string with escape",
|
||||
in: []map[string]any{
|
||||
{
|
||||
"name": "enum_string",
|
||||
"type": "enum",
|
||||
"enumType": "string",
|
||||
"description": "enum string parameter",
|
||||
"allowedValues": []any{"foo", "bar"},
|
||||
"escape": true,
|
||||
},
|
||||
},
|
||||
want: tools.Parameters{
|
||||
tools.NewEnumParameter(tools.NewStringParameter("enum_string", "enum string parameter"), true, []any{"foo", "bar"}),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
@@ -669,6 +701,31 @@ func TestAuthParametersMarshal(t *testing.T) {
|
||||
tools.NewMapParameterWithAuth("my_map", "this param is a map of strings", "string", authServices),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "enum",
|
||||
in: []map[string]any{
|
||||
{
|
||||
"name": "enum_string",
|
||||
"type": "enum",
|
||||
"description": "enum of strings",
|
||||
"enumType": "string",
|
||||
"allowedValues": []any{"foo", "bar"},
|
||||
"authServices": []map[string]string{
|
||||
{
|
||||
"name": "my-google-auth-service",
|
||||
"field": "user_id",
|
||||
},
|
||||
{
|
||||
"name": "other-auth-service",
|
||||
"field": "user_id",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: tools.Parameters{
|
||||
tools.NewEnumParameter(tools.NewStringParameterWithAuth("enum_string", "enum of strings", authServices), false, []any{"foo", "bar"}),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
@@ -784,6 +841,35 @@ func TestParametersParse(t *testing.T) {
|
||||
"my_bool": 1.5,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "enum",
|
||||
params: tools.Parameters{
|
||||
tools.NewEnumParameter(tools.NewStringParameter("enum_string", "enum of strings"), false, []any{"foo", "bar"}),
|
||||
},
|
||||
in: map[string]any{
|
||||
"enum_string": "foo",
|
||||
},
|
||||
want: tools.ParamValues{tools.ParamValue{Name: "enum_string", Value: "foo"}},
|
||||
},
|
||||
{
|
||||
name: "enum not allowed",
|
||||
params: tools.Parameters{
|
||||
tools.NewEnumParameter(tools.NewStringParameter("enum_string", "enum of strings"), false, []any{"foo", "bar"}),
|
||||
},
|
||||
in: map[string]any{
|
||||
"enum_string": "invalid",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "enum with integer",
|
||||
params: tools.Parameters{
|
||||
tools.NewEnumParameter(tools.NewIntParameter("enum_int", "enum of int"), false, []any{"^[1-5]$"}),
|
||||
},
|
||||
in: map[string]any{
|
||||
"enum_int": 4,
|
||||
},
|
||||
want: tools.ParamValues{tools.ParamValue{Name: "enum_int", Value: 4}},
|
||||
},
|
||||
{
|
||||
name: "string default",
|
||||
params: tools.Parameters{
|
||||
@@ -824,6 +910,14 @@ func TestParametersParse(t *testing.T) {
|
||||
in: map[string]any{},
|
||||
want: tools.ParamValues{tools.ParamValue{Name: "my_bool", Value: true}},
|
||||
},
|
||||
{
|
||||
name: "enum default",
|
||||
params: tools.Parameters{
|
||||
tools.NewEnumParameter(tools.NewStringParameterWithDefault("enum_string", "foo", "enum of strings"), false, []any{"foo", "bar"}),
|
||||
},
|
||||
in: map[string]any{},
|
||||
want: tools.ParamValues{tools.ParamValue{Name: "enum_string", Value: "foo"}},
|
||||
},
|
||||
{
|
||||
name: "string not required",
|
||||
params: tools.Parameters{
|
||||
@@ -856,6 +950,14 @@ func TestParametersParse(t *testing.T) {
|
||||
in: map[string]any{},
|
||||
want: tools.ParamValues{tools.ParamValue{Name: "my_bool", Value: nil}},
|
||||
},
|
||||
{
|
||||
name: "enum not required",
|
||||
params: tools.Parameters{
|
||||
tools.NewEnumParameter(tools.NewStringParameterWithRequired("enum_string", "enum of strings", false), true, []any{"foo", "bar"}),
|
||||
},
|
||||
in: map[string]any{},
|
||||
want: tools.ParamValues{tools.ParamValue{Name: "enum_string", Value: nil}},
|
||||
},
|
||||
{
|
||||
name: "map",
|
||||
params: tools.Parameters{
|
||||
@@ -1197,6 +1299,17 @@ func TestParamManifest(t *testing.T) {
|
||||
Items: &tools.ParameterManifest{Name: "foo-string", Type: "string", Required: true, Description: "bar", AuthServices: []string{}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "enum with string",
|
||||
in: tools.NewEnumParameter(tools.NewStringParameter("foo-enum", "enum of strings"), false, []any{"foo", "bar"}),
|
||||
want: tools.ParameterManifest{
|
||||
Name: "foo-enum",
|
||||
Type: "string",
|
||||
Required: true,
|
||||
Description: "enum of strings",
|
||||
AuthServices: []string{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "string default",
|
||||
in: tools.NewStringParameterWithDefault("foo-string", "foo", "bar"),
|
||||
@@ -1229,6 +1342,17 @@ func TestParamManifest(t *testing.T) {
|
||||
Items: &tools.ParameterManifest{Name: "foo-string", Type: "string", Required: false, Description: "bar", AuthServices: []string{}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "enum with string default",
|
||||
in: tools.NewEnumParameter(tools.NewStringParameterWithDefault("foo-enum", "foo", "enum of strings"), false, []any{"foo", "bar"}),
|
||||
want: tools.ParameterManifest{
|
||||
Name: "foo-enum",
|
||||
Type: "string",
|
||||
Required: false,
|
||||
Description: "enum of strings",
|
||||
AuthServices: []string{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "string not required",
|
||||
in: tools.NewStringParameterWithRequired("foo-string", "bar", false),
|
||||
@@ -1261,6 +1385,17 @@ func TestParamManifest(t *testing.T) {
|
||||
Items: &tools.ParameterManifest{Name: "foo-string", Type: "string", Required: false, Description: "bar", AuthServices: []string{}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "enum with string not required",
|
||||
in: tools.NewEnumParameter(tools.NewStringParameterWithRequired("foo-enum", "enum of strings", false), false, []any{"foo", "bar"}),
|
||||
want: tools.ParameterManifest{
|
||||
Name: "foo-enum",
|
||||
Type: "string",
|
||||
Required: false,
|
||||
Description: "enum of strings",
|
||||
AuthServices: []string{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "map with string values",
|
||||
in: tools.NewMapParameter("foo-map", "bar", "string"),
|
||||
@@ -1343,7 +1478,6 @@ func TestParamMcpManifest(t *testing.T) {
|
||||
Items: &tools.ParameterMcpManifest{Type: "string", Description: "bar"},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "map with string values",
|
||||
in: tools.NewMapParameter("foo-map", "bar", "string"),
|
||||
@@ -1362,6 +1496,11 @@ func TestParamMcpManifest(t *testing.T) {
|
||||
AdditionalProperties: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "enum param",
|
||||
in: tools.NewEnumParameter(tools.NewStringParameter("foo-enum", "enum of strings"), false, []any{"foo", "bar"}),
|
||||
want: tools.ParameterMcpManifest{Type: "string", Description: "enum of strings"},
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
@@ -1389,6 +1528,7 @@ func TestMcpManifest(t *testing.T) {
|
||||
tools.NewArrayParameter("foo-array2", "bar", tools.NewStringParameter("foo-string", "bar")),
|
||||
tools.NewMapParameter("foo-map-int", "a map of ints", "integer"),
|
||||
tools.NewMapParameter("foo-map-any", "a map of any", ""),
|
||||
tools.NewEnumParameter(tools.NewStringParameter("foo-enum-string", "enum of strings"), false, []any{"foo", "bar"}),
|
||||
},
|
||||
want: tools.McpToolsSchema{
|
||||
Type: "object",
|
||||
@@ -1412,8 +1552,12 @@ func TestMcpManifest(t *testing.T) {
|
||||
Description: "a map of any",
|
||||
AdditionalProperties: true,
|
||||
},
|
||||
"foo-enum-string": {
|
||||
Type: "string",
|
||||
Description: "enum of strings",
|
||||
},
|
||||
},
|
||||
Required: []string{"foo-string2", "foo-int2", "foo-float", "foo-array2", "foo-map-int", "foo-map-any"},
|
||||
Required: []string{"foo-string2", "foo-int2", "foo-float", "foo-array2", "foo-map-int", "foo-map-any", "foo-enum-string"},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1455,7 +1599,7 @@ func TestFailParametersUnmarshal(t *testing.T) {
|
||||
"description": "this is a param for string",
|
||||
},
|
||||
},
|
||||
err: "parameter is missing 'type' field: %!w(<nil>)",
|
||||
err: "parameter is missing 'type' field",
|
||||
},
|
||||
{
|
||||
name: "common parameter missing description",
|
||||
@@ -1663,6 +1807,18 @@ func TestGetParams(t *testing.T) {
|
||||
in: map[string]any{},
|
||||
want: tools.ParamValues{},
|
||||
},
|
||||
{
|
||||
name: "enum with escape",
|
||||
params: tools.Parameters{
|
||||
tools.NewEnumParameter(tools.NewStringParameter("my_string_enum", "string of enums"), true, []any{"foo", "bar"}),
|
||||
},
|
||||
in: map[string]any{
|
||||
"my_string_enum": "foo",
|
||||
},
|
||||
want: tools.ParamValues{
|
||||
tools.ParamValue{Name: "my_string_enum", Value: `"foo"`},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
@@ -1881,3 +2037,75 @@ func TestCheckParamRequired(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchStringOrRegex(t *testing.T) {
|
||||
tcs := []struct {
|
||||
name string
|
||||
input string
|
||||
target string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "exact string",
|
||||
input: "foo",
|
||||
target: "foo",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "exact integer",
|
||||
input: fmt.Sprintf("%v", 5),
|
||||
target: fmt.Sprintf("%v", 5),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "wrong integer",
|
||||
input: fmt.Sprintf("%v", 4),
|
||||
target: fmt.Sprintf("%v", 5),
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "exact boolean",
|
||||
input: fmt.Sprintf("%v", true),
|
||||
target: fmt.Sprintf("%v", true),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "target contains input",
|
||||
input: "foo",
|
||||
target: "foo bar",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "regex any string",
|
||||
input: "foo",
|
||||
target: ".*",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "regex",
|
||||
input: "foo6",
|
||||
target: `foo\d+`,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "regex of numbers",
|
||||
input: "4",
|
||||
target: "^[1-5]$",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "regex of numbers invalid",
|
||||
input: "7",
|
||||
target: "^[1-5]$",
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := tools.MatchStringOrRegex(tc.input, tc.target)
|
||||
if got != tc.want {
|
||||
t.Fatalf("got %v, want %v", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user