Skip to content

Commit 8ac3b57

Browse files
committed
mcp: avoid "null" in structuredContent
Avoid a typed nil in structuredContent (yet another typed nil), leading to "null" on the wire. Fixes #417
1 parent ee51416 commit 8ac3b57

File tree

2 files changed

+52
-14
lines changed

2 files changed

+52
-14
lines changed

mcp/server.go

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -267,19 +267,21 @@ func toolForErr[In, Out any](t *Tool, h ToolHandlerFor[In, Out]) (*Tool, ToolHan
267267
outval = elemZero
268268
}
269269
}
270-
outbytes, err := json.Marshal(outval)
271-
if err != nil {
272-
return nil, fmt.Errorf("marshaling output: %w", err)
273-
}
274-
res.StructuredContent = json.RawMessage(outbytes) // avoid a second marshal over the wire
275-
276-
// If the Content field isn't being used, return the serialized JSON in a
277-
// TextContent block, as the spec suggests:
278-
// https://modelcontextprotocol.io/specification/2025-06-18/server/tools#structured-content.
279-
if res.Content == nil {
280-
res.Content = []Content{&TextContent{
281-
Text: string(outbytes),
282-
}}
270+
if outval != nil {
271+
outbytes, err := json.Marshal(outval)
272+
if err != nil {
273+
return nil, fmt.Errorf("marshaling output: %w", err)
274+
}
275+
res.StructuredContent = json.RawMessage(outbytes) // avoid a second marshal over the wire
276+
277+
// If the Content field isn't being used, return the serialized JSON in a
278+
// TextContent block, as the spec suggests:
279+
// https://modelcontextprotocol.io/specification/2025-06-18/server/tools#structured-content.
280+
if res.Content == nil {
281+
res.Content = []Content{&TextContent{
282+
Text: string(outbytes),
283+
}}
284+
}
283285
}
284286
return res, nil
285287
} // end of handler

mcp/testdata/conformance/server/tools.txtar

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,15 @@ structured
2121
"clientInfo": { "name": "ExampleClient", "version": "1.0.0" }
2222
}
2323
}
24-
{"jsonrpc":"2.0", "method": "notifications/initialized"}
24+
{ "jsonrpc":"2.0", "method": "notifications/initialized" }
2525
{ "jsonrpc": "2.0", "id": 2, "method": "tools/list" }
2626
{ "jsonrpc": "2.0", "id": 3, "method": "resources/list" }
2727
{ "jsonrpc": "2.0", "id": 4, "method": "prompts/list" }
2828
{ "jsonrpc": "2.0", "id": 5, "method": "tools/call" }
29+
{ "jsonrpc": "2.0", "id": 6, "method": "tools/call", "params": {"name": "greet", "arguments": {"name": "you"} } }
30+
{ "jsonrpc": "2.0", "id": 1, "result": {} }
31+
{ "jsonrpc": "2.0", "id": 7, "method": "tools/call", "params": {"name": "structured", "arguments": {"In": "input"} } }
32+
2933
-- server --
3034
{
3135
"jsonrpc": "2.0",
@@ -119,3 +123,35 @@ structured
119123
"message": "invalid request: missing required \"params\""
120124
}
121125
}
126+
{
127+
"jsonrpc": "2.0",
128+
"id": 1,
129+
"method": "ping"
130+
}
131+
{
132+
"jsonrpc": "2.0",
133+
"id": 6,
134+
"result": {
135+
"content": [
136+
{
137+
"type": "text",
138+
"text": "hi you"
139+
}
140+
]
141+
}
142+
}
143+
{
144+
"jsonrpc": "2.0",
145+
"id": 7,
146+
"result": {
147+
"content": [
148+
{
149+
"type": "text",
150+
"text": "{\"Out\":\"Ack input\"}"
151+
}
152+
],
153+
"structuredContent": {
154+
"Out": "Ack input"
155+
}
156+
}
157+
}

0 commit comments

Comments
 (0)