mcp: validate tool names (#640)
Implement SEP 986, enforcing restrictions on tool names. Since we're already at v1 we couldn't turn this into a panic, so use an error log instead. This is consistent with the typescript SDK. A note is left in our 'rough edges' doc to panic in v2. Fixes #621
This commit is contained in:
parent
9ef2b2742e
commit
1943780255
1 changed files with 168 additions and 0 deletions
|
|
@ -5,9 +5,11 @@
|
|||
package mcp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"log/slog"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
|
@ -490,6 +492,69 @@ func TestAddTool(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAddToolNameValidation(t *testing.T) {
|
||||
tests := []struct {
|
||||
label string
|
||||
name string
|
||||
wantLogContaining string
|
||||
}{
|
||||
{
|
||||
label: "empty name",
|
||||
name: "",
|
||||
wantLogContaining: `tool name cannot be empty`,
|
||||
},
|
||||
{
|
||||
label: "long name",
|
||||
name: strings.Repeat("a", 129),
|
||||
wantLogContaining: "exceeds maximum length of 128 characters",
|
||||
},
|
||||
{
|
||||
label: "name with spaces",
|
||||
name: "get user profile",
|
||||
wantLogContaining: `tool name contains invalid characters: \" \"`,
|
||||
},
|
||||
{
|
||||
label: "name with multiple invalid chars",
|
||||
name: "user name@domain,com",
|
||||
wantLogContaining: `tool name contains invalid characters: \" \", \"@\", \",\"`,
|
||||
},
|
||||
{
|
||||
label: "name with unicode",
|
||||
name: "tool-ñame",
|
||||
wantLogContaining: `tool name contains invalid characters: \"ñ\"`,
|
||||
},
|
||||
{
|
||||
label: "valid name",
|
||||
name: "valid-tool_name.123",
|
||||
wantLogContaining: "", // No log expected
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.label, func(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
s := NewServer(testImpl, &ServerOptions{
|
||||
Logger: slog.New(slog.NewTextHandler(&buf, nil)),
|
||||
})
|
||||
|
||||
// Use the generic AddTool as it also calls validateToolName.
|
||||
AddTool(s, &Tool{Name: test.name}, func(context.Context, *CallToolRequest, any) (*CallToolResult, any, error) {
|
||||
return nil, nil, nil
|
||||
})
|
||||
|
||||
logOutput := buf.String()
|
||||
if test.wantLogContaining != "" {
|
||||
if !strings.Contains(logOutput, test.wantLogContaining) {
|
||||
t.Errorf("log output =\n%s\nwant containing %q", logOutput, test.wantLogContaining)
|
||||
}
|
||||
} else {
|
||||
if logOutput != "" {
|
||||
t.Errorf("expected empty log output, got %q", logOutput)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type schema = jsonschema.Schema
|
||||
|
||||
func testToolForSchema[In, Out any](t *testing.T, tool *Tool, in string, out Out, wantIn, wantOut any, wantErrContaining string) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue