mirror of
https://github.com/googleapis/genai-toolbox.git
synced 2026-05-02 03:00:36 -04:00
feat!: validate tool naming (#2305)
Check for naming validation for Tool. This validation follows the MCP
SEP986 [naming
guidance](1b1eb60ec4/docs/specification/draft/server/tools.mdx (tool-names)).
Name will be checked before MCP initialization (where specs version is
confirmed). Hence, we will be implementing this across all versions and
endpoints.
This will be a breaking change for user that currently uses other
special character as name (other than `_`, `-`, `.`)
This commit is contained in:
@@ -16,6 +16,7 @@ package server
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
yaml "github.com/goccy/go-yaml"
|
yaml "github.com/goccy/go-yaml"
|
||||||
@@ -272,6 +273,10 @@ func (c *ToolConfigs) UnmarshalYAML(ctx context.Context, unmarshal func(interfac
|
|||||||
}
|
}
|
||||||
|
|
||||||
for name, u := range raw {
|
for name, u := range raw {
|
||||||
|
err := NameValidation(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
var v map[string]any
|
var v map[string]any
|
||||||
if err := u.Unmarshal(&v); err != nil {
|
if err := u.Unmarshal(&v); err != nil {
|
||||||
return fmt.Errorf("unable to unmarshal %q: %w", name, err)
|
return fmt.Errorf("unable to unmarshal %q: %w", name, err)
|
||||||
@@ -432,3 +437,23 @@ func (c *PromptsetConfigs) UnmarshalYAML(ctx context.Context, unmarshal func(int
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tools naming validation is added in the MCP v2025-11-25, but we'll be
|
||||||
|
// implementing it across Toolbox
|
||||||
|
// Tool names SHOULD be between 1 and 128 characters in length (inclusive).
|
||||||
|
// Tool names SHOULD be considered case-sensitive.
|
||||||
|
// The following SHOULD be the only allowed characters: uppercase and lowercase ASCII letters (A-Z, a-z), digits (0-9), underscore (_), hyphen (-), and dot (.)
|
||||||
|
// Tool names SHOULD NOT contain spaces, commas, or other special characters.
|
||||||
|
// Tool names SHOULD be unique within a server.
|
||||||
|
func NameValidation(name string) error {
|
||||||
|
strLen := len(name)
|
||||||
|
if strLen < 1 || strLen > 128 {
|
||||||
|
return fmt.Errorf("resource name SHOULD be between 1 and 128 characters in length (inclusive)")
|
||||||
|
}
|
||||||
|
validChars := regexp.MustCompile("^[a-zA-Z0-9_.-]+$")
|
||||||
|
isValid := validChars.MatchString(name)
|
||||||
|
if !isValid {
|
||||||
|
return fmt.Errorf("invalid character for resource name; only uppercase and lowercase ASCII letters (A-Z, a-z), digits (0-9), underscore (_), hyphen (-), and dot (.) is allowed")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -200,3 +200,62 @@ func TestUpdateServer(t *testing.T) {
|
|||||||
t.Errorf("error updating server, promptset (-want +got):\n%s", diff)
|
t.Errorf("error updating server, promptset (-want +got):\n%s", diff)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNameValidation(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
resourceName string
|
||||||
|
errStr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "names with 0 length",
|
||||||
|
resourceName: "",
|
||||||
|
errStr: "resource name SHOULD be between 1 and 128 characters in length (inclusive)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "names with allowed length",
|
||||||
|
resourceName: "foo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "names with 128 length",
|
||||||
|
resourceName: strings.Repeat("a", 128),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "names with more than 128 length",
|
||||||
|
resourceName: strings.Repeat("a", 129),
|
||||||
|
errStr: "resource name SHOULD be between 1 and 128 characters in length (inclusive)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "names with space",
|
||||||
|
resourceName: "foo bar",
|
||||||
|
errStr: "invalid character for resource name; only uppercase and lowercase ASCII letters (A-Z, a-z), digits (0-9), underscore (_), hyphen (-), and dot (.) is allowed",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "names with commas",
|
||||||
|
resourceName: "foo,bar",
|
||||||
|
errStr: "invalid character for resource name; only uppercase and lowercase ASCII letters (A-Z, a-z), digits (0-9), underscore (_), hyphen (-), and dot (.) is allowed",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "names with other special character",
|
||||||
|
resourceName: "foo!",
|
||||||
|
errStr: "invalid character for resource name; only uppercase and lowercase ASCII letters (A-Z, a-z), digits (0-9), underscore (_), hyphen (-), and dot (.) is allowed",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "names with allowed special character",
|
||||||
|
resourceName: "foo_.-bar6",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
err := server.NameValidation(tc.resourceName)
|
||||||
|
if err != nil {
|
||||||
|
if tc.errStr != err.Error() {
|
||||||
|
t.Fatalf("unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == nil && tc.errStr != "" {
|
||||||
|
t.Fatalf("expect error: %s", tc.errStr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user