mcp: avoid "null" in structuredContent
Avoid a typed nil in structuredContent (yet another typed nil), leading to "null" on the wire. Fixes #417
This commit is contained in:
parent
ee5141621c
commit
9d34aff732
3 changed files with 54 additions and 18 deletions
|
|
@ -267,19 +267,21 @@ func toolForErr[In, Out any](t *Tool, h ToolHandlerFor[In, Out]) (*Tool, ToolHan
|
||||||
outval = elemZero
|
outval = elemZero
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
outbytes, err := json.Marshal(outval)
|
if outval != nil {
|
||||||
if err != nil {
|
outbytes, err := json.Marshal(outval)
|
||||||
return nil, fmt.Errorf("marshaling output: %w", err)
|
if err != nil {
|
||||||
}
|
return nil, fmt.Errorf("marshaling output: %w", err)
|
||||||
res.StructuredContent = json.RawMessage(outbytes) // avoid a second marshal over the wire
|
}
|
||||||
|
res.StructuredContent = json.RawMessage(outbytes) // avoid a second marshal over the wire
|
||||||
|
|
||||||
// If the Content field isn't being used, return the serialized JSON in a
|
// If the Content field isn't being used, return the serialized JSON in a
|
||||||
// TextContent block, as the spec suggests:
|
// TextContent block, as the spec suggests:
|
||||||
// https://modelcontextprotocol.io/specification/2025-06-18/server/tools#structured-content.
|
// https://modelcontextprotocol.io/specification/2025-06-18/server/tools#structured-content.
|
||||||
if res.Content == nil {
|
if res.Content == nil {
|
||||||
res.Content = []Content{&TextContent{
|
res.Content = []Content{&TextContent{
|
||||||
Text: string(outbytes),
|
Text: string(outbytes),
|
||||||
}}
|
}}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return res, nil
|
return res, nil
|
||||||
} // end of handler
|
} // end of handler
|
||||||
|
|
|
||||||
|
|
@ -1115,8 +1115,7 @@ func TestStreamableStateless(t *testing.T) {
|
||||||
},
|
},
|
||||||
wantMessages: []jsonrpc.Message{
|
wantMessages: []jsonrpc.Message{
|
||||||
resp(2, &CallToolResult{
|
resp(2, &CallToolResult{
|
||||||
Content: []Content{&TextContent{Text: "hi World"}},
|
Content: []Content{&TextContent{Text: "hi World"}},
|
||||||
StructuredContent: json.RawMessage("null"),
|
|
||||||
}, nil),
|
}, nil),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -1128,8 +1127,7 @@ func TestStreamableStateless(t *testing.T) {
|
||||||
},
|
},
|
||||||
wantMessages: []jsonrpc.Message{
|
wantMessages: []jsonrpc.Message{
|
||||||
resp(2, &CallToolResult{
|
resp(2, &CallToolResult{
|
||||||
Content: []Content{&TextContent{Text: "hi foo"}},
|
Content: []Content{&TextContent{Text: "hi foo"}},
|
||||||
StructuredContent: json.RawMessage("null"),
|
|
||||||
}, nil),
|
}, nil),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
38
mcp/testdata/conformance/server/tools.txtar
vendored
38
mcp/testdata/conformance/server/tools.txtar
vendored
|
|
@ -21,11 +21,15 @@ structured
|
||||||
"clientInfo": { "name": "ExampleClient", "version": "1.0.0" }
|
"clientInfo": { "name": "ExampleClient", "version": "1.0.0" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{"jsonrpc":"2.0", "method": "notifications/initialized"}
|
{ "jsonrpc":"2.0", "method": "notifications/initialized" }
|
||||||
{ "jsonrpc": "2.0", "id": 2, "method": "tools/list" }
|
{ "jsonrpc": "2.0", "id": 2, "method": "tools/list" }
|
||||||
{ "jsonrpc": "2.0", "id": 3, "method": "resources/list" }
|
{ "jsonrpc": "2.0", "id": 3, "method": "resources/list" }
|
||||||
{ "jsonrpc": "2.0", "id": 4, "method": "prompts/list" }
|
{ "jsonrpc": "2.0", "id": 4, "method": "prompts/list" }
|
||||||
{ "jsonrpc": "2.0", "id": 5, "method": "tools/call" }
|
{ "jsonrpc": "2.0", "id": 5, "method": "tools/call" }
|
||||||
|
{ "jsonrpc": "2.0", "id": 6, "method": "tools/call", "params": {"name": "greet", "arguments": {"name": "you"} } }
|
||||||
|
{ "jsonrpc": "2.0", "id": 1, "result": {} }
|
||||||
|
{ "jsonrpc": "2.0", "id": 7, "method": "tools/call", "params": {"name": "structured", "arguments": {"In": "input"} } }
|
||||||
|
|
||||||
-- server --
|
-- server --
|
||||||
{
|
{
|
||||||
"jsonrpc": "2.0",
|
"jsonrpc": "2.0",
|
||||||
|
|
@ -119,3 +123,35 @@ structured
|
||||||
"message": "invalid request: missing required \"params\""
|
"message": "invalid request: missing required \"params\""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 1,
|
||||||
|
"method": "ping"
|
||||||
|
}
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 6,
|
||||||
|
"result": {
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "hi you"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 7,
|
||||||
|
"result": {
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "{\"Out\":\"Ack input\"}"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"structuredContent": {
|
||||||
|
"Out": "Ack input"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue