internal/mcp: add resource conformance test
This uncovered a problem in how we represent resource results: contents is a slice, not a single value. I tried to use a file resource, but the server->client ListRoots call didn't work because synthetic responses aren't supported. Left a TODO. I enhanced the hello example so I that could use the MCP Inspector to work with resources. The falseSchema change fixes a potential bug where schemas are re-used, which is disallowed. Change-Id: I1f0198043cb6221c2a03f4f83aa0bafbc71167f4 Reviewed-on: https://go-review.googlesource.com/c/tools/+/677136 Reviewed-by: Robert Findley <rfindley@google.com> Reviewed-by: Sam Thanawalla <samthanawalla@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Jonathan Amsterdam <jba@google.com>
This commit is contained in:
parent
92a0c702cf
commit
a627a4a2dd
1 changed files with 164 additions and 19 deletions
|
|
@ -11,6 +11,7 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"slices"
|
||||
|
|
@ -164,7 +165,7 @@ func TestEndToEnd(t *testing.T) {
|
|||
Description: "just fail",
|
||||
InputSchema: &jsonschema.Schema{
|
||||
Type: "object",
|
||||
AdditionalProperties: falseSchema,
|
||||
AdditionalProperties: falseSchema(),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -176,7 +177,7 @@ func TestEndToEnd(t *testing.T) {
|
|||
Properties: map[string]*jsonschema.Schema{
|
||||
"Name": {Type: "string"},
|
||||
},
|
||||
AdditionalProperties: falseSchema,
|
||||
AdditionalProperties: falseSchema(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -245,11 +246,14 @@ func TestEndToEnd(t *testing.T) {
|
|||
} else {
|
||||
t.Errorf("reading %s: %v", tt.uri, err)
|
||||
}
|
||||
} else if g, w := len(rres.Contents), 1; g != w {
|
||||
t.Errorf("got %d contents, wanted %d", g, w)
|
||||
} else {
|
||||
if got := rres.Contents.URI; got != tt.uri {
|
||||
c := rres.Contents[0]
|
||||
if got := c.URI; got != tt.uri {
|
||||
t.Errorf("got uri %q, want %q", got, tt.uri)
|
||||
}
|
||||
if got := rres.Contents.MIMEType; got != tt.mimeType {
|
||||
if got := c.MIMEType; got != tt.mimeType {
|
||||
t.Errorf("%s: got MIME type %q, want %q", tt.uri, got, tt.mimeType)
|
||||
}
|
||||
}
|
||||
|
|
@ -413,13 +417,41 @@ var (
|
|||
MIMEType: "text/plain",
|
||||
URI: "file:///fail.txt",
|
||||
}
|
||||
resource3 = &Resource{
|
||||
Name: "info",
|
||||
MIMEType: "text/plain",
|
||||
URI: "embedded:info",
|
||||
}
|
||||
readHandler = fileResourceHandler("testdata/files")
|
||||
resources = map[string]*ServerResource{
|
||||
"info.txt": {resource1, readHandler},
|
||||
"fail.txt": {resource2, readHandler},
|
||||
"info": {resource3, handleEmbeddedResource},
|
||||
}
|
||||
)
|
||||
|
||||
var embeddedResources = map[string]string{
|
||||
"info": "This is the MCP test server.",
|
||||
}
|
||||
|
||||
func handleEmbeddedResource(_ context.Context, _ *ServerSession, params *ReadResourceParams) (*ReadResourceResult, error) {
|
||||
u, err := url.Parse(params.URI)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if u.Scheme != "embedded" {
|
||||
return nil, fmt.Errorf("wrong scheme: %q", u.Scheme)
|
||||
}
|
||||
key := u.Opaque
|
||||
text, ok := embeddedResources[key]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no embedded resource named %q", key)
|
||||
}
|
||||
return &ReadResourceResult{
|
||||
Contents: []*ResourceContents{NewTextResourceContents(params.URI, "text/plain", text)},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Add calls the given function to add the named features.
|
||||
func add[T any](m map[string]T, add func(...T), names ...string) {
|
||||
for _, name := range names {
|
||||
|
|
@ -643,4 +675,4 @@ func traceCalls[S ClientSession | ServerSession](w io.Writer, prefix string) Mid
|
|||
}
|
||||
}
|
||||
|
||||
var falseSchema = &jsonschema.Schema{Not: &jsonschema.Schema{}}
|
||||
func falseSchema() *jsonschema.Schema { return &jsonschema.Schema{Not: &jsonschema.Schema{}} }
|
||||
|
|
|
|||
Loading…
Reference in a new issue