From 19d34aacecc2f89155dc233177290017b236ccbf Mon Sep 17 00:00:00 2001 From: Luan Vu Date: Sat, 21 Mar 2026 07:35:24 +0700 Subject: [PATCH] fix(mcp): add empty properties to object schemas for OpenAI compatibility (#321) MCP tools that accept no parameters return {type: "object"} without a properties field, which is valid per JSON Schema spec. However, OpenAI API strictly requires "properties" on object schemas and rejects requests with HTTP 400: "object schema missing properties". Add default empty properties map when type is "object" and the MCP server provides none. Co-authored-by: Luvu182 <208665161+Luvu182@users.noreply.github.com> --- internal/mcp/bridge_tool.go | 3 +++ internal/mcp/bridge_tool_test.go | 13 +++++++++++++ 2 files changed, 16 insertions(+) diff --git a/internal/mcp/bridge_tool.go b/internal/mcp/bridge_tool.go index d7ac8582..bb5e5472 100644 --- a/internal/mcp/bridge_tool.go +++ b/internal/mcp/bridge_tool.go @@ -125,6 +125,9 @@ func inputSchemaToMap(schema mcpgo.ToolInputSchema) map[string]any { } if len(schema.Properties) > 0 { m["properties"] = schema.Properties + } else if m["type"] == "object" { + // OpenAI requires "properties" even when empty for object schemas. + m["properties"] = map[string]any{} } if len(schema.Required) > 0 { m["required"] = schema.Required diff --git a/internal/mcp/bridge_tool_test.go b/internal/mcp/bridge_tool_test.go index 01e19d6d..783048e9 100644 --- a/internal/mcp/bridge_tool_test.go +++ b/internal/mcp/bridge_tool_test.go @@ -47,6 +47,19 @@ func TestInputSchemaToMap_EmptyType(t *testing.T) { } } +func TestInputSchemaToMap_ObjectNoProperties(t *testing.T) { + schema := mcpgo.ToolInputSchema{Type: "object"} + m := inputSchemaToMap(schema) + + props, ok := m["properties"].(map[string]any) + if !ok || props == nil { + t.Fatal("expected empty properties map for object schema, got nil — OpenAI rejects object schemas without properties") + } + if len(props) != 0 { + t.Errorf("expected empty properties, got %v", props) + } +} + func TestExtractTextContent(t *testing.T) { result := &mcpgo.CallToolResult{ Content: []mcpgo.Content{