Skip to content

Commit 5b28181

Browse files
authored
fix: generate correct JSON schemas for empty structs (#396)
1 parent 2612eb3 commit 5b28181

File tree

2 files changed

+82
-12
lines changed

2 files changed

+82
-12
lines changed

tools.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -216,16 +216,24 @@ func ConvertTool[T any, R any](name, description string, toolHandler ToolHandler
216216
for pair := jsonSchema.Properties.Oldest(); pair != nil; pair = pair.Next() {
217217
properties[pair.Key] = pair.Value
218218
}
219-
inputSchema := mcp.ToolInputSchema{
219+
// Use RawInputSchema with ToolArgumentsSchema to work around a Go limitation where type aliases
220+
// don't inherit custom MarshalJSON methods. This ensures empty properties are included in the schema.
221+
argumentsSchema := mcp.ToolArgumentsSchema{
220222
Type: jsonSchema.Type,
221223
Properties: properties,
222224
Required: jsonSchema.Required,
223225
}
224226

227+
// Marshal the schema to preserve empty properties
228+
schemaBytes, err := json.Marshal(argumentsSchema)
229+
if err != nil {
230+
return zero, nil, fmt.Errorf("failed to marshal input schema: %w", err)
231+
}
232+
225233
t := mcp.Tool{
226-
Name: name,
227-
Description: description,
228-
InputSchema: inputSchema,
234+
Name: name,
235+
Description: description,
236+
RawInputSchema: schemaBytes,
229237
}
230238
for _, option := range options {
231239
option(&t)

tools_test.go

Lines changed: 70 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package mcpgrafana
55

66
import (
77
"context"
8+
"encoding/json"
89
"errors"
910
"testing"
1011

@@ -98,11 +99,23 @@ func TestConvertTool(t *testing.T) {
9899
assert.Equal(t, "test_tool", tool.Name)
99100
assert.Equal(t, "A test tool", tool.Description)
100101

101-
// Check schema properties
102-
assert.Equal(t, "object", tool.InputSchema.Type)
103-
assert.Contains(t, tool.InputSchema.Properties, "name")
104-
assert.Contains(t, tool.InputSchema.Properties, "value")
105-
assert.Contains(t, tool.InputSchema.Properties, "optional")
102+
// Check schema properties by marshaling the tool
103+
toolJSON, err := json.Marshal(tool)
104+
require.NoError(t, err)
105+
106+
var toolData map[string]any
107+
err = json.Unmarshal(toolJSON, &toolData)
108+
require.NoError(t, err)
109+
110+
inputSchema, ok := toolData["inputSchema"].(map[string]any)
111+
require.True(t, ok, "inputSchema should be a map")
112+
assert.Equal(t, "object", inputSchema["type"])
113+
114+
properties, ok := inputSchema["properties"].(map[string]any)
115+
require.True(t, ok, "properties should be a map")
116+
assert.Contains(t, properties, "name")
117+
assert.Contains(t, properties, "value")
118+
assert.Contains(t, properties, "optional")
106119

107120
// Test handler execution
108121
ctx := context.Background()
@@ -158,9 +171,21 @@ func TestConvertTool(t *testing.T) {
158171
assert.Equal(t, "empty", tool.Name)
159172
assert.Equal(t, "description", tool.Description)
160173

161-
// Check schema properties
162-
assert.Equal(t, "object", tool.InputSchema.Type)
163-
assert.Len(t, tool.InputSchema.Properties, 0)
174+
// Check schema properties by marshaling the tool
175+
toolJSON, err := json.Marshal(tool)
176+
require.NoError(t, err)
177+
178+
var toolData map[string]any
179+
err = json.Unmarshal(toolJSON, &toolData)
180+
require.NoError(t, err)
181+
182+
inputSchema, ok := toolData["inputSchema"].(map[string]any)
183+
require.True(t, ok, "inputSchema should be a map")
184+
assert.Equal(t, "object", inputSchema["type"])
185+
186+
properties, ok := inputSchema["properties"].(map[string]any)
187+
require.True(t, ok, "properties should be a map")
188+
assert.Len(t, properties, 0)
164189

165190
// Test handler execution
166191
ctx := context.Background()
@@ -524,3 +549,40 @@ func TestCreateJSONSchemaFromHandler(t *testing.T) {
524549
assert.Equal(t, "boolean", optionalProperty.Type)
525550
assert.Equal(t, "An optional parameter", optionalProperty.Description)
526551
}
552+
553+
func TestEmptyStructJSONSchema(t *testing.T) {
554+
// Test that empty structs generate correct JSON schema with empty properties object
555+
tool, _, err := ConvertTool("empty_tool", "An empty tool", emptyToolHandler)
556+
require.NoError(t, err)
557+
558+
// Marshal the entire Tool to JSON
559+
jsonBytes, err := json.Marshal(tool)
560+
require.NoError(t, err)
561+
t.Logf("Marshaled Tool JSON: %s", string(jsonBytes))
562+
563+
// Unmarshal to verify structure
564+
var unmarshaled map[string]any
565+
err = json.Unmarshal(jsonBytes, &unmarshaled)
566+
require.NoError(t, err)
567+
568+
// Verify that inputSchema exists
569+
inputSchema, exists := unmarshaled["inputSchema"]
570+
assert.True(t, exists, "inputSchema field should exist in tool JSON")
571+
assert.NotNil(t, inputSchema, "inputSchema should not be nil")
572+
573+
// Verify inputSchema structure
574+
inputSchemaMap, ok := inputSchema.(map[string]any)
575+
assert.True(t, ok, "inputSchema should be a map")
576+
577+
// Verify type is object
578+
assert.Equal(t, "object", inputSchemaMap["type"], "inputSchema type should be object")
579+
580+
// Verify that properties key exists and is an empty object
581+
properties, exists := inputSchemaMap["properties"]
582+
assert.True(t, exists, "properties field should exist in inputSchema")
583+
assert.NotNil(t, properties, "properties should not be nil")
584+
585+
propertiesMap, ok := properties.(map[string]any)
586+
assert.True(t, ok, "properties should be a map")
587+
assert.Len(t, propertiesMap, 0, "properties should be an empty map")
588+
}

0 commit comments

Comments
 (0)