Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions mcp/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,24 @@ const (
resultTypeInputRequired resultType = "input_required"
)

type completeResultWithType struct {
ResultType resultType `json:"resultType,omitempty"`
}

func (r *completeResultWithType) setResultType(rt resultType) { r.ResultType = rt }
func (*completeResultWithType) isCompleteResult() {}

type completeResultResponse interface {
setResultType(resultType)
isCompleteResult()
}

func setCompleteResultType(res Result) {
if r, ok := res.(completeResultResponse); ok {
r.setResultType(resultTypeComplete)
}
}

// InputRequest is a type for parameters that a server can include in the response
// to request input from client (SEP-2322). Implementations are [*ElicitParams],
// [*CreateMessageParams], and [*ListRootsParams].
Expand Down Expand Up @@ -637,6 +655,7 @@ type CompletionResultDetails struct {

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

type DiscoverResult struct {
completeResultWithType
Meta `json:"_meta,omitempty"`
Cacheable
// The versions of the Model Context Protocol that the server supports.
Expand Down Expand Up @@ -1177,6 +1197,7 @@ func (c *Cacheable) setDefaultCacheableValues() {

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

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

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

// The server's response to a tools/list request from the client.
type ListToolsResult struct {
completeResultWithType
// This property is reserved by the protocol to allow clients and servers to
// attach additional metadata to their responses.
Meta `json:"_meta,omitempty"`
Expand Down
3 changes: 2 additions & 1 deletion mcp/protocol_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"testing"

"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
)

func TestParamsMeta(t *testing.T) {
Expand Down Expand Up @@ -491,7 +492,7 @@ func TestCompleteResult(t *testing.T) {
if err := json.Unmarshal([]byte(test.in), &got); err != nil {
t.Fatalf("json.Unmarshal(CompleteResult) failed: %v", err)
}
if diff := cmp.Diff(test.want, got); diff != "" {
if diff := cmp.Diff(test.want, got, cmpopts.IgnoreUnexported(CompleteResult{})); diff != "" {
t.Errorf("CompleteResult unmarshal mismatch (-want +got):\n%s", diff)
}
})
Expand Down
9 changes: 8 additions & 1 deletion mcp/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -1901,7 +1901,14 @@ func (ss *ServerSession) handle(ctx context.Context, req *jsonrpc.Request) (any,
if validatedMeta.usesNewProtocol {
ss.setLevel(ctx, &SetLoggingLevelParams{Level: validatedMeta.logLevel})
}
return handleReceive(ctx, ss, req)
res, err := handleReceive(ctx, ss, req)
if err != nil {
return nil, err
}
if validatedMeta.usesNewProtocol {
setCompleteResultType(res)
}
Comment thread
ychampion marked this conversation as resolved.
return res, nil
}

// InitializeParams returns the InitializeParams provided during the client's
Expand Down
142 changes: 142 additions & 0 deletions mcp/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1399,6 +1399,148 @@ func TestServerSessionHandle_RejectsInitializeOnNewProtocol(t *testing.T) {
})
}

func TestServerSessionHandle_SetsResultTypeOnNewProtocol(t *testing.T) {
server := NewServer(testImpl, &ServerOptions{
CompletionHandler: func(context.Context, *CompleteRequest) (*CompleteResult, error) {
return &CompleteResult{
Completion: CompletionResultDetails{
Values: []string{"go"},
},
}, nil
},
})
AddTool(server, &Tool{Name: "tool"}, func(context.Context, *CallToolRequest, struct{}) (*CallToolResult, any, error) {
return &CallToolResult{
InputRequests: InputRequestMap{"confirm": &ElicitParams{Message: "Continue?"}},
}, nil, nil
})
server.AddPrompt(&Prompt{Name: "prompt"}, func(context.Context, *GetPromptRequest) (*GetPromptResult, error) {
return &GetPromptResult{
InputRequests: InputRequestMap{"confirm": &ElicitParams{Message: "Continue?"}},
}, nil
})
server.AddResource(&Resource{URI: "test://resource", Name: "resource"}, func(context.Context, *ReadResourceRequest) (*ReadResourceResult, error) {
return &ReadResourceResult{
InputRequests: InputRequestMap{"confirm": &ElicitParams{Message: "Continue?"}},
}, nil
})
newProtocolParams := func(fields map[string]any) map[string]any {
params := map[string]any{
"_meta": map[string]any{
MetaKeyProtocolVersion: protocolVersion20260728,
MetaKeyClientInfo: map[string]any{"name": "c", "version": "1"},
MetaKeyClientCapabilities: map[string]any{},
},
}
for k, v := range fields {
params[k] = v
}
return params
}

tests := []struct {
name string
method string
params map[string]any
want resultType
}{
{
name: "discover",
method: methodDiscover,
params: newProtocolParams(nil),
want: resultTypeComplete,
},
{
name: "tools list",
method: methodListTools,
params: newProtocolParams(nil),
want: resultTypeComplete,
},
{
name: "prompts list",
method: methodListPrompts,
params: newProtocolParams(nil),
want: resultTypeComplete,
},
{
name: "resources list",
method: methodListResources,
params: newProtocolParams(nil),
want: resultTypeComplete,
},
{
name: "resource templates list",
method: methodListResourceTemplates,
params: newProtocolParams(nil),
want: resultTypeComplete,
},
{
name: "complete",
method: methodComplete,
params: newProtocolParams(map[string]any{
"argument": map[string]any{
"name": "language",
"value": "g",
},
"ref": map[string]any{
"type": "ref/prompt",
"name": "code_review",
},
}),
want: resultTypeComplete,
},
{
name: "tool input required",
method: methodCallTool,
params: newProtocolParams(map[string]any{"name": "tool", "arguments": map[string]any{}}),
want: resultTypeInputRequired,
},
{
name: "prompt input required",
method: methodGetPrompt,
params: newProtocolParams(map[string]any{"name": "prompt"}),
want: resultTypeInputRequired,
},
{
name: "resource input required",
method: methodReadResource,
params: newProtocolParams(map[string]any{"uri": "test://resource"}),
want: resultTypeInputRequired,
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
ss := &ServerSession{server: server}
id, err := jsonrpc.MakeID("test")
if err != nil {
t.Fatal(err)
}
result, err := ss.handle(context.Background(), &jsonrpc.Request{
ID: id,
Method: tc.method,
Params: mustMarshal(tc.params),
})
if err != nil {
t.Fatal(err)
}
data, err := json.Marshal(result)
if err != nil {
t.Fatal(err)
}
var got struct {
ResultType string `json:"resultType"`
}
if err := json.Unmarshal(data, &got); err != nil {
t.Fatal(err)
}
if got.ResultType != string(tc.want) {
t.Fatalf("resultType = %q, want %q; response = %s", got.ResultType, tc.want, data)
}
})
}
}

// TestServerSessionHandle_RejectsRemovedMethodsOnNewProtocol verifies that
// the methods removed by SEP-2575 (`initialize`, `notifications/initialized`,
// `ping`) all return Method not found when the request opts into the new
Expand Down
1 change: 1 addition & 0 deletions mcp/testdata/conformance/server/discover.txtar
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ supports, its capabilities, and its identity.
"jsonrpc": "2.0",
"id": 1,
"result": {
"resultType": "complete",
"ttlMs": 0,
"cacheScope": "public",
"supportedVersions": [
Expand Down
2 changes: 1 addition & 1 deletion mcp/transport_example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func ExampleLoggingTransport() {
}

// Output:
// 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"}}}
// 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"}}}
// 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"}}}
}

Expand Down
Loading