Files
goclaw/internal/mcp/bridge_tool_arg_normalization.go
T

124 lines
3.8 KiB
Go

package mcp
import (
"encoding/json"
"fmt"
"strings"
)
func (t *BridgeTool) normalizeArgsForSchema(args map[string]any) (map[string]any, []string, error) {
if len(args) == 0 {
return args, nil, nil
}
normalized, coercedPaths, err := normalizeValueForSchema(args, t.inputSchema, "$")
if err != nil {
return nil, nil, err
}
normalizedArgs, ok := normalized.(map[string]any)
if !ok {
return nil, nil, fmt.Errorf("arguments must be object, got %s", valueKind(normalized))
}
return normalizedArgs, coercedPaths, nil
}
func normalizeValueForSchema(value any, schema map[string]any, path string) (any, []string, error) {
if schema == nil {
return value, nil, nil
}
types := schemaTypeSet(schema)
expectsContainer := (types["object"] || types["array"]) && !types["string"]
if s, ok := value.(string); ok && expectsContainer {
return normalizeStringContainer(s, schema, types, path)
}
if obj, ok := value.(map[string]any); ok {
return normalizeObjectValue(obj, schema, path)
}
if arr, ok := value.([]any); ok {
return normalizeArrayValue(arr, schema, path)
}
return value, nil, nil
}
func normalizeStringContainer(raw string, schema map[string]any, types map[string]bool, path string) (any, []string, error) {
trimmed := strings.TrimSpace(raw)
if trimmed == "" {
return nil, nil, fmt.Errorf("%s must be %s, got empty string", path, expectedContainerLabel(types))
}
var decoded any
if err := json.Unmarshal([]byte(trimmed), &decoded); err != nil {
return nil, nil, fmt.Errorf("%s must be %s, got JSON string that cannot be parsed: pass a native value instead of a stringified JSON value", path, expectedContainerLabel(types))
}
switch v := decoded.(type) {
case map[string]any:
if !types["object"] {
return nil, nil, fmt.Errorf("%s must be %s, got JSON string containing object", path, expectedContainerLabel(types))
}
normalized, nestedPaths, err := normalizeObjectValue(v, schema, path)
if err != nil {
return nil, nil, err
}
return normalized, prependPath(path, nestedPaths), nil
case []any:
if !types["array"] {
return nil, nil, fmt.Errorf("%s must be %s, got JSON string containing array", path, expectedContainerLabel(types))
}
normalized, nestedPaths, err := normalizeArrayValue(v, schema, path)
if err != nil {
return nil, nil, err
}
return normalized, prependPath(path, nestedPaths), nil
default:
return nil, nil, fmt.Errorf("%s must be %s, got JSON string containing %s", path, expectedContainerLabel(types), valueKind(decoded))
}
}
func normalizeObjectValue(obj map[string]any, schema map[string]any, path string) (map[string]any, []string, error) {
props, _ := schema["properties"].(map[string]any)
if len(props) == 0 {
return obj, nil, nil
}
normalized := make(map[string]any, len(obj))
var coercedPaths []string
for key, value := range obj {
propSchema, _ := props[key].(map[string]any)
if propSchema == nil {
normalized[key] = value
continue
}
nextPath := joinSchemaPath(path, key)
nextValue, nestedPaths, err := normalizeValueForSchema(value, propSchema, nextPath)
if err != nil {
return nil, nil, err
}
normalized[key] = nextValue
coercedPaths = append(coercedPaths, nestedPaths...)
}
return normalized, coercedPaths, nil
}
func normalizeArrayValue(arr []any, schema map[string]any, path string) ([]any, []string, error) {
itemSchema, _ := schema["items"].(map[string]any)
if itemSchema == nil {
return arr, nil, nil
}
normalized := make([]any, len(arr))
var coercedPaths []string
for i, value := range arr {
nextPath := fmt.Sprintf("%s[%d]", path, i)
nextValue, nestedPaths, err := normalizeValueForSchema(value, itemSchema, nextPath)
if err != nil {
return nil, nil, err
}
normalized[i] = nextValue
coercedPaths = append(coercedPaths, nestedPaths...)
}
return normalized, coercedPaths, nil
}