mcp: fix conformance tests (#132)
- Use new AddXXX API. - Update goldens to reflect new capability logic. Fixes #131.
This commit is contained in:
parent
bf7e518215
commit
de4b788e59
7 changed files with 70 additions and 75 deletions
|
|
@ -23,6 +23,7 @@ import (
|
|||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/modelcontextprotocol/go-sdk/internal/jsonrpc2"
|
||||
"github.com/modelcontextprotocol/go-sdk/jsonrpc"
|
||||
"golang.org/x/tools/txtar"
|
||||
)
|
||||
|
||||
|
|
@ -46,12 +47,12 @@ var update = flag.Bool("update", false, "if set, update conformance test data")
|
|||
// with -update to have the test runner update the expected output, which may
|
||||
// be client or server depending on the perspective of the test.
|
||||
type conformanceTest struct {
|
||||
name string // test name
|
||||
path string // path to test file
|
||||
archive *txtar.Archive // raw archive, for updating
|
||||
tools, prompts, resources []string // named features to include
|
||||
client []JSONRPCMessage // client messages
|
||||
server []JSONRPCMessage // server messages
|
||||
name string // test name
|
||||
path string // path to test file
|
||||
archive *txtar.Archive // raw archive, for updating
|
||||
tools, prompts, resources []string // named features to include
|
||||
client []jsonrpc.Message // client messages
|
||||
server []jsonrpc.Message // server messages
|
||||
}
|
||||
|
||||
// TODO(rfindley): add client conformance tests.
|
||||
|
|
@ -100,10 +101,36 @@ func TestServerConformance(t *testing.T) {
|
|||
func runServerTest(t *testing.T, test *conformanceTest) {
|
||||
ctx := t.Context()
|
||||
// Construct the server based on features listed in the test.
|
||||
s := NewServer("testServer", "v1.0.0", nil)
|
||||
add(tools, s.AddTools, test.tools...)
|
||||
add(prompts, s.AddPrompts, test.prompts...)
|
||||
add(resources, s.AddResources, test.resources...)
|
||||
s := NewServer(&Implementation{Name: "testServer", Version: "v1.0.0"}, nil)
|
||||
for _, tn := range test.tools {
|
||||
switch tn {
|
||||
case "greet":
|
||||
AddTool(s, &Tool{
|
||||
Name: "greet",
|
||||
Description: "say hi",
|
||||
}, sayHi)
|
||||
default:
|
||||
t.Fatalf("unknown tool %q", tn)
|
||||
}
|
||||
}
|
||||
for _, pn := range test.prompts {
|
||||
switch pn {
|
||||
case "code_review":
|
||||
s.AddPrompt(codeReviewPrompt, codReviewPromptHandler)
|
||||
default:
|
||||
t.Fatalf("unknown prompt %q", pn)
|
||||
}
|
||||
}
|
||||
for _, rn := range test.resources {
|
||||
switch rn {
|
||||
case "info.txt":
|
||||
s.AddResource(resource1, readHandler)
|
||||
case "info":
|
||||
s.AddResource(resource3, handleEmbeddedResource)
|
||||
default:
|
||||
t.Fatalf("unknown resource %q", rn)
|
||||
}
|
||||
}
|
||||
|
||||
// Connect the server, and connect the client stream,
|
||||
// but don't connect an actual client.
|
||||
|
|
@ -117,24 +144,24 @@ func runServerTest(t *testing.T, test *conformanceTest) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
writeMsg := func(msg JSONRPCMessage) {
|
||||
writeMsg := func(msg jsonrpc.Message) {
|
||||
if err := cStream.Write(ctx, msg); err != nil {
|
||||
t.Fatalf("Write failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
serverMessages []JSONRPCMessage
|
||||
outRequests []*JSONRPCRequest
|
||||
outResponses []*JSONRPCResponse
|
||||
serverMessages []jsonrpc.Message
|
||||
outRequests []*jsonrpc.Request
|
||||
outResponses []*jsonrpc.Response
|
||||
)
|
||||
|
||||
// Separate client requests and responses; we use them differently.
|
||||
for _, msg := range test.client {
|
||||
switch msg := msg.(type) {
|
||||
case *JSONRPCRequest:
|
||||
case *jsonrpc.Request:
|
||||
outRequests = append(outRequests, msg)
|
||||
case *JSONRPCResponse:
|
||||
case *jsonrpc.Response:
|
||||
outResponses = append(outResponses, msg)
|
||||
default:
|
||||
t.Fatalf("bad message type %T", msg)
|
||||
|
|
@ -143,7 +170,7 @@ func runServerTest(t *testing.T, test *conformanceTest) {
|
|||
|
||||
// nextResponse handles incoming requests and notifications, and returns the
|
||||
// next incoming response.
|
||||
nextResponse := func() (*JSONRPCResponse, error, bool) {
|
||||
nextResponse := func() (*jsonrpc.Response, error, bool) {
|
||||
for {
|
||||
msg, err := cStream.Read(ctx)
|
||||
if err != nil {
|
||||
|
|
@ -156,7 +183,7 @@ func runServerTest(t *testing.T, test *conformanceTest) {
|
|||
return nil, err, false
|
||||
}
|
||||
serverMessages = append(serverMessages, msg)
|
||||
if req, ok := msg.(*JSONRPCRequest); ok && req.ID.IsValid() {
|
||||
if req, ok := msg.(*jsonrpc.Request); ok && req.ID.IsValid() {
|
||||
// Pair up the next outgoing response with this request.
|
||||
// We assume requests arrive in the same order every time.
|
||||
if len(outResponses) == 0 {
|
||||
|
|
@ -167,7 +194,7 @@ func runServerTest(t *testing.T, test *conformanceTest) {
|
|||
outResponses = outResponses[1:]
|
||||
continue
|
||||
}
|
||||
return msg.(*JSONRPCResponse), nil, true
|
||||
return msg.(*jsonrpc.Response), nil, true
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -191,7 +218,7 @@ func runServerTest(t *testing.T, test *conformanceTest) {
|
|||
// There might be more notifications or requests, but there shouldn't be more
|
||||
// responses.
|
||||
// Run this in a goroutine so the current thread can wait for it.
|
||||
var extra *JSONRPCResponse
|
||||
var extra *jsonrpc.Response
|
||||
go func() {
|
||||
extra, err, _ = nextResponse()
|
||||
}()
|
||||
|
|
@ -240,8 +267,8 @@ func runServerTest(t *testing.T, test *conformanceTest) {
|
|||
t.Fatalf("os.WriteFile(%q) failed: %v", test.path, err)
|
||||
}
|
||||
} else {
|
||||
// JSONRPCMessages are not comparable, so we instead compare lines of JSON.
|
||||
transform := cmpopts.AcyclicTransformer("toJSON", func(msg JSONRPCMessage) []string {
|
||||
// jsonrpc.Messages are not comparable, so we instead compare lines of JSON.
|
||||
transform := cmpopts.AcyclicTransformer("toJSON", func(msg jsonrpc.Message) []string {
|
||||
encoded, err := jsonrpc2.EncodeIndent(msg, "", "\t")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -271,9 +298,9 @@ func loadConformanceTest(dir, path string) (*conformanceTest, error) {
|
|||
}
|
||||
|
||||
// decodeMessages loads JSON-RPC messages from the archive file.
|
||||
decodeMessages := func(data []byte) ([]JSONRPCMessage, error) {
|
||||
decodeMessages := func(data []byte) ([]jsonrpc.Message, error) {
|
||||
dec := json.NewDecoder(bytes.NewReader(data))
|
||||
var res []JSONRPCMessage
|
||||
var res []jsonrpc.Message
|
||||
for dec.More() {
|
||||
var raw json.RawMessage
|
||||
if err := dec.Decode(&raw); err != nil {
|
||||
|
|
|
|||
|
|
@ -39,6 +39,21 @@ func sayHi(ctx context.Context, ss *ServerSession, params *CallToolParamsFor[hiP
|
|||
return &CallToolResultFor[any]{Content: []Content{&TextContent{Text: "hi " + params.Arguments.Name}}}, nil
|
||||
}
|
||||
|
||||
var codeReviewPrompt = &Prompt{
|
||||
Name: "code_review",
|
||||
Description: "do a code review",
|
||||
Arguments: []*PromptArgument{{Name: "Code", Required: true}},
|
||||
}
|
||||
|
||||
func codReviewPromptHandler(_ context.Context, _ *ServerSession, params *GetPromptParams) (*GetPromptResult, error) {
|
||||
return &GetPromptResult{
|
||||
Description: "Code review prompt",
|
||||
Messages: []*PromptMessage{
|
||||
{Role: "user", Content: &TextContent{Text: "Please review the following code: " + params.Arguments["Code"]}},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func TestEndToEnd(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
var ct, st Transport = NewInMemoryTransports()
|
||||
|
|
@ -73,18 +88,7 @@ func TestEndToEnd(t *testing.T) {
|
|||
func(context.Context, *ServerSession, *CallToolParamsFor[map[string]any]) (*CallToolResult, error) {
|
||||
return nil, errTestFailure
|
||||
})
|
||||
s.AddPrompt(&Prompt{
|
||||
Name: "code_review",
|
||||
Description: "do a code review",
|
||||
Arguments: []*PromptArgument{{Name: "Code", Required: true}},
|
||||
}, func(_ context.Context, _ *ServerSession, params *GetPromptParams) (*GetPromptResult, error) {
|
||||
return &GetPromptResult{
|
||||
Description: "Code review prompt",
|
||||
Messages: []*PromptMessage{
|
||||
{Role: "user", Content: &TextContent{Text: "Please review the following code: " + params.Arguments["Code"]}},
|
||||
},
|
||||
}, nil
|
||||
})
|
||||
s.AddPrompt(codeReviewPrompt, codReviewPromptHandler)
|
||||
s.AddPrompt(&Prompt{Name: "fail"}, func(_ context.Context, _ *ServerSession, _ *GetPromptParams) (*GetPromptResult, error) {
|
||||
return nil, errTestFailure
|
||||
})
|
||||
|
|
|
|||
|
|
@ -29,12 +29,6 @@ code_review
|
|||
"logging": {},
|
||||
"prompts": {
|
||||
"listChanged": true
|
||||
},
|
||||
"resources": {
|
||||
"listChanged": true
|
||||
},
|
||||
"tools": {
|
||||
"listChanged": true
|
||||
}
|
||||
},
|
||||
"protocolVersion": "2024-11-05",
|
||||
|
|
|
|||
|
|
@ -47,14 +47,8 @@ info.txt
|
|||
"capabilities": {
|
||||
"completions": {},
|
||||
"logging": {},
|
||||
"prompts": {
|
||||
"listChanged": true
|
||||
},
|
||||
"resources": {
|
||||
"listChanged": true
|
||||
},
|
||||
"tools": {
|
||||
"listChanged": true
|
||||
}
|
||||
},
|
||||
"protocolVersion": "2024-11-05",
|
||||
|
|
|
|||
6
mcp/testdata/conformance/server/tools.txtar
vendored
6
mcp/testdata/conformance/server/tools.txtar
vendored
|
|
@ -30,12 +30,6 @@ greet
|
|||
"capabilities": {
|
||||
"completions": {},
|
||||
"logging": {},
|
||||
"prompts": {
|
||||
"listChanged": true
|
||||
},
|
||||
"resources": {
|
||||
"listChanged": true
|
||||
},
|
||||
"tools": {
|
||||
"listChanged": true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,18 +19,9 @@ response with its latest supported version.
|
|||
"result": {
|
||||
"capabilities": {
|
||||
"completions": {},
|
||||
"logging": {},
|
||||
"prompts": {
|
||||
"listChanged": true
|
||||
},
|
||||
"resources": {
|
||||
"listChanged": true
|
||||
},
|
||||
"tools": {
|
||||
"listChanged": true
|
||||
}
|
||||
"logging": {}
|
||||
},
|
||||
"protocolVersion": "2025-03-26",
|
||||
"protocolVersion": "2025-06-18",
|
||||
"serverInfo": {
|
||||
"name": "testServer",
|
||||
"version": "v1.0.0"
|
||||
|
|
|
|||
|
|
@ -19,16 +19,7 @@ support.
|
|||
"result": {
|
||||
"capabilities": {
|
||||
"completions": {},
|
||||
"logging": {},
|
||||
"prompts": {
|
||||
"listChanged": true
|
||||
},
|
||||
"resources": {
|
||||
"listChanged": true
|
||||
},
|
||||
"tools": {
|
||||
"listChanged": true
|
||||
}
|
||||
"logging": {}
|
||||
},
|
||||
"protocolVersion": "2024-11-05",
|
||||
"serverInfo": {
|
||||
|
|
|
|||
Loading…
Reference in a new issue