Skip to content

Commit dd79ca3

Browse files
mcp: include resultType on new-protocol responses (#1060)
## Summary - Add `resultType: "complete"` to non-MRTR server result responses for 2026-07-28 requests. - Cover `server/discover`, list methods, and completion responses, and update expected conformance/example output. ## Why Fixes #1032. The 2026-07-28 schema requires `resultType` on result objects, but only the MRTR-capable results were setting it. ## Validation - `go test ./mcp -run 'Test(ServerSessionHandle|CompleteResult|ClientConnectDiscover|InMemory_E2E_Discover)' -count=1` — passed\n- `go test ./mcp -count=1` — passed\n- `go test ./...` — passed\n- `go vet ./...` — passed --------- Co-authored-by: ychampion <ychampion@users.noreply.github.com> Co-authored-by: Guglielmo Colombo <guglielmoc@google.com>
1 parent 40f0343 commit dd79ca3

6 files changed

Lines changed: 178 additions & 3 deletions

File tree

mcp/protocol.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,24 @@ const (
2828
resultTypeInputRequired resultType = "input_required"
2929
)
3030

31+
type completeResultWithType struct {
32+
ResultType resultType `json:"resultType,omitempty"`
33+
}
34+
35+
func (r *completeResultWithType) setResultType(rt resultType) { r.ResultType = rt }
36+
func (*completeResultWithType) isCompleteResult() {}
37+
38+
type completeResultResponse interface {
39+
setResultType(resultType)
40+
isCompleteResult()
41+
}
42+
43+
func setCompleteResultType(res Result) {
44+
if r, ok := res.(completeResultResponse); ok {
45+
r.setResultType(resultTypeComplete)
46+
}
47+
}
48+
3149
// InputRequest is a type for parameters that a server can include in the response
3250
// to request input from client (SEP-2322). Implementations are [*ElicitParams],
3351
// [*CreateMessageParams], and [*ListRootsParams].
@@ -640,6 +658,7 @@ type CompletionResultDetails struct {
640658

641659
// The server's response to a completion/complete request
642660
type CompleteResult struct {
661+
completeResultWithType
643662
// This property is reserved by the protocol to allow clients and servers to
644663
// attach additional metadata to their responses.
645664
Meta `json:"_meta,omitempty"`
@@ -1117,6 +1136,7 @@ func (x *DiscoverParams) GetProgressToken() any { return getProgressToken(x) }
11171136
func (x *DiscoverParams) SetProgressToken(t any) { setProgressToken(x, t) }
11181137

11191138
type DiscoverResult struct {
1139+
completeResultWithType
11201140
Meta `json:"_meta,omitempty"`
11211141
Cacheable
11221142
// The versions of the Model Context Protocol that the server supports.
@@ -1180,6 +1200,7 @@ func (c *Cacheable) setDefaultCacheableValues() {
11801200

11811201
// The server's response to a prompts/list request from the client.
11821202
type ListPromptsResult struct {
1203+
completeResultWithType
11831204
// This property is reserved by the protocol to allow clients and servers to
11841205
// attach additional metadata to their responses.
11851206
Meta `json:"_meta,omitempty"`
@@ -1210,6 +1231,7 @@ func (x *ListResourceTemplatesParams) cursorPtr() *string { return &x.Cursor
12101231

12111232
// The server's response to a resources/templates/list request from the client.
12121233
type ListResourceTemplatesResult struct {
1234+
completeResultWithType
12131235
// This property is reserved by the protocol to allow clients and servers to
12141236
// attach additional metadata to their responses.
12151237
Meta `json:"_meta,omitempty"`
@@ -1240,6 +1262,7 @@ func (x *ListResourcesParams) cursorPtr() *string { return &x.Cursor }
12401262

12411263
// The server's response to a resources/list request from the client.
12421264
type ListResourcesResult struct {
1265+
completeResultWithType
12431266
// This property is reserved by the protocol to allow clients and servers to
12441267
// attach additional metadata to their responses.
12451268
Meta `json:"_meta,omitempty"`
@@ -1306,6 +1329,7 @@ func (x *ListToolsParams) cursorPtr() *string { return &x.Cursor }
13061329

13071330
// The server's response to a tools/list request from the client.
13081331
type ListToolsResult struct {
1332+
completeResultWithType
13091333
// This property is reserved by the protocol to allow clients and servers to
13101334
// attach additional metadata to their responses.
13111335
Meta `json:"_meta,omitempty"`

mcp/protocol_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"testing"
1111

1212
"github.com/google/go-cmp/cmp"
13+
"github.com/google/go-cmp/cmp/cmpopts"
1314
)
1415

1516
func TestParamsMeta(t *testing.T) {
@@ -491,7 +492,7 @@ func TestCompleteResult(t *testing.T) {
491492
if err := json.Unmarshal([]byte(test.in), &got); err != nil {
492493
t.Fatalf("json.Unmarshal(CompleteResult) failed: %v", err)
493494
}
494-
if diff := cmp.Diff(test.want, got); diff != "" {
495+
if diff := cmp.Diff(test.want, got, cmpopts.IgnoreUnexported(CompleteResult{})); diff != "" {
495496
t.Errorf("CompleteResult unmarshal mismatch (-want +got):\n%s", diff)
496497
}
497498
})

mcp/server.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1893,7 +1893,14 @@ func (ss *ServerSession) handle(ctx context.Context, req *jsonrpc.Request) (any,
18931893
if validatedMeta.usesNewProtocol {
18941894
ss.setLevel(ctx, &SetLoggingLevelParams{Level: validatedMeta.logLevel})
18951895
}
1896-
return handleReceive(ctx, ss, req)
1896+
res, err := handleReceive(ctx, ss, req)
1897+
if err != nil {
1898+
return nil, err
1899+
}
1900+
if validatedMeta.usesNewProtocol {
1901+
setCompleteResultType(res)
1902+
}
1903+
return res, nil
18971904
}
18981905

18991906
// InitializeParams returns the InitializeParams provided during the client's

mcp/server_test.go

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1399,6 +1399,148 @@ func TestServerSessionHandle_RejectsInitializeOnNewProtocol(t *testing.T) {
13991399
})
14001400
}
14011401

1402+
func TestServerSessionHandle_SetsResultTypeOnNewProtocol(t *testing.T) {
1403+
server := NewServer(testImpl, &ServerOptions{
1404+
CompletionHandler: func(context.Context, *CompleteRequest) (*CompleteResult, error) {
1405+
return &CompleteResult{
1406+
Completion: CompletionResultDetails{
1407+
Values: []string{"go"},
1408+
},
1409+
}, nil
1410+
},
1411+
})
1412+
AddTool(server, &Tool{Name: "tool"}, func(context.Context, *CallToolRequest, struct{}) (*CallToolResult, any, error) {
1413+
return &CallToolResult{
1414+
InputRequests: InputRequestMap{"confirm": &ElicitParams{Message: "Continue?"}},
1415+
}, nil, nil
1416+
})
1417+
server.AddPrompt(&Prompt{Name: "prompt"}, func(context.Context, *GetPromptRequest) (*GetPromptResult, error) {
1418+
return &GetPromptResult{
1419+
InputRequests: InputRequestMap{"confirm": &ElicitParams{Message: "Continue?"}},
1420+
}, nil
1421+
})
1422+
server.AddResource(&Resource{URI: "test://resource", Name: "resource"}, func(context.Context, *ReadResourceRequest) (*ReadResourceResult, error) {
1423+
return &ReadResourceResult{
1424+
InputRequests: InputRequestMap{"confirm": &ElicitParams{Message: "Continue?"}},
1425+
}, nil
1426+
})
1427+
newProtocolParams := func(fields map[string]any) map[string]any {
1428+
params := map[string]any{
1429+
"_meta": map[string]any{
1430+
MetaKeyProtocolVersion: protocolVersion20260728,
1431+
MetaKeyClientInfo: map[string]any{"name": "c", "version": "1"},
1432+
MetaKeyClientCapabilities: map[string]any{},
1433+
},
1434+
}
1435+
for k, v := range fields {
1436+
params[k] = v
1437+
}
1438+
return params
1439+
}
1440+
1441+
tests := []struct {
1442+
name string
1443+
method string
1444+
params map[string]any
1445+
want resultType
1446+
}{
1447+
{
1448+
name: "discover",
1449+
method: methodDiscover,
1450+
params: newProtocolParams(nil),
1451+
want: resultTypeComplete,
1452+
},
1453+
{
1454+
name: "tools list",
1455+
method: methodListTools,
1456+
params: newProtocolParams(nil),
1457+
want: resultTypeComplete,
1458+
},
1459+
{
1460+
name: "prompts list",
1461+
method: methodListPrompts,
1462+
params: newProtocolParams(nil),
1463+
want: resultTypeComplete,
1464+
},
1465+
{
1466+
name: "resources list",
1467+
method: methodListResources,
1468+
params: newProtocolParams(nil),
1469+
want: resultTypeComplete,
1470+
},
1471+
{
1472+
name: "resource templates list",
1473+
method: methodListResourceTemplates,
1474+
params: newProtocolParams(nil),
1475+
want: resultTypeComplete,
1476+
},
1477+
{
1478+
name: "complete",
1479+
method: methodComplete,
1480+
params: newProtocolParams(map[string]any{
1481+
"argument": map[string]any{
1482+
"name": "language",
1483+
"value": "g",
1484+
},
1485+
"ref": map[string]any{
1486+
"type": "ref/prompt",
1487+
"name": "code_review",
1488+
},
1489+
}),
1490+
want: resultTypeComplete,
1491+
},
1492+
{
1493+
name: "tool input required",
1494+
method: methodCallTool,
1495+
params: newProtocolParams(map[string]any{"name": "tool", "arguments": map[string]any{}}),
1496+
want: resultTypeInputRequired,
1497+
},
1498+
{
1499+
name: "prompt input required",
1500+
method: methodGetPrompt,
1501+
params: newProtocolParams(map[string]any{"name": "prompt"}),
1502+
want: resultTypeInputRequired,
1503+
},
1504+
{
1505+
name: "resource input required",
1506+
method: methodReadResource,
1507+
params: newProtocolParams(map[string]any{"uri": "test://resource"}),
1508+
want: resultTypeInputRequired,
1509+
},
1510+
}
1511+
1512+
for _, tc := range tests {
1513+
t.Run(tc.name, func(t *testing.T) {
1514+
ss := &ServerSession{server: server}
1515+
id, err := jsonrpc.MakeID("test")
1516+
if err != nil {
1517+
t.Fatal(err)
1518+
}
1519+
result, err := ss.handle(context.Background(), &jsonrpc.Request{
1520+
ID: id,
1521+
Method: tc.method,
1522+
Params: mustMarshal(tc.params),
1523+
})
1524+
if err != nil {
1525+
t.Fatal(err)
1526+
}
1527+
data, err := json.Marshal(result)
1528+
if err != nil {
1529+
t.Fatal(err)
1530+
}
1531+
var got struct {
1532+
ResultType string `json:"resultType"`
1533+
}
1534+
if err := json.Unmarshal(data, &got); err != nil {
1535+
t.Fatal(err)
1536+
}
1537+
if got.ResultType != string(tc.want) {
1538+
t.Fatalf("resultType = %q, want %q; response = %s", got.ResultType, tc.want, data)
1539+
}
1540+
})
1541+
}
1542+
}
1543+
14021544
// TestServerSessionHandle_RejectsRemovedMethodsOnNewProtocol verifies that
14031545
// the methods removed by SEP-2575 (`initialize`, `notifications/initialized`,
14041546
// `ping`) all return Method not found when the request opts into the new

mcp/testdata/conformance/server/discover.txtar

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ supports, its capabilities, and its identity.
2121
"jsonrpc": "2.0",
2222
"id": 1,
2323
"result": {
24+
"resultType": "complete",
2425
"ttlMs": 0,
2526
"cacheScope": "public",
2627
"supportedVersions": [

mcp/transport_example_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func ExampleLoggingTransport() {
4242
}
4343

4444
// Output:
45-
// read: {"jsonrpc":"2.0","id":1,"result":{"ttlMs":0,"cacheScope":"public","supportedVersions":["2026-07-28","2025-11-25","2025-06-18","2025-03-26","2024-11-05"],"capabilities":{"logging":{}},"serverInfo":{"name":"server","version":"v0.0.1"}}}
45+
// read: {"jsonrpc":"2.0","id":1,"result":{"resultType":"complete","ttlMs":0,"cacheScope":"public","supportedVersions":["2026-07-28","2025-11-25","2025-06-18","2025-03-26","2024-11-05"],"capabilities":{"logging":{}},"serverInfo":{"name":"server","version":"v0.0.1"}}}
4646
// write: {"jsonrpc":"2.0","id":1,"method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/clientCapabilities":{"roots":{"listChanged":true}},"io.modelcontextprotocol/clientInfo":{"name":"client","version":"v0.0.1"},"io.modelcontextprotocol/protocolVersion":"2026-07-28"}}}
4747
}
4848

0 commit comments

Comments
 (0)