diff --git a/internal/mcpserver/schema.go b/internal/mcpserver/schema.go index 18bfae8..edabcac 100644 --- a/internal/mcpserver/schema.go +++ b/internal/mcpserver/schema.go @@ -221,12 +221,19 @@ func formatLogDuration(d time.Duration) string { return fmt.Sprintf("%02d:%02d:%03d", minutes, seconds, milliseconds) } +func normalizeObjectSchema(schema *jsonschema.Schema) { + if schema != nil && schema.Type == "object" && schema.Properties == nil { + schema.Properties = map[string]*jsonschema.Schema{} + } +} + func setToolSchemas[In any, Out any](tool *mcp.Tool) error { if tool.InputSchema == nil { inputSchema, err := jsonschema.For[In](toolSchemaOptions) if err != nil { return fmt.Errorf("infer input schema: %w", err) } + normalizeObjectSchema(inputSchema) tool.InputSchema = inputSchema } diff --git a/internal/mcpserver/schema_test.go b/internal/mcpserver/schema_test.go index 5e7b9e3..9403f77 100644 --- a/internal/mcpserver/schema_test.go +++ b/internal/mcpserver/schema_test.go @@ -13,6 +13,24 @@ import ( "git.warky.dev/wdevs/amcs/internal/tools" ) +func TestSetToolSchemasAddsEmptyPropertiesForNoArgInput(t *testing.T) { + type noArgInput struct{} + type anyOutput struct{} + + tool := &mcp.Tool{Name: "no_args"} + if err := setToolSchemas[noArgInput, anyOutput](tool); err != nil { + t.Fatalf("set tool schemas: %v", err) + } + + schema, ok := tool.InputSchema.(*jsonschema.Schema) + if !ok { + t.Fatalf("input schema type = %T, want *jsonschema.Schema", tool.InputSchema) + } + if schema.Properties == nil { + t.Fatal("input schema missing properties: strict MCP clients require properties:{} on object schemas") + } +} + func TestSetToolSchemasUsesStringUUIDsInListOutput(t *testing.T) { tool := &mcp.Tool{Name: "list_thoughts"}