Skip to content

Commit 251da13

Browse files
authored
feature: add support ResourceTemplates to mcptest package (#449)
* feat: implement AddResourceTemplate and add resource template test * fix: use local ServerResourceTemplate type in mcptest * fix: replace NewURITemplate with UnmarshalText for URI template creation * fix: correct URITemplate unmarshaling and err variable declaration in tests * fix: improve resource template argument extraction in test handler * test: add assertions to verify URI template argument population * simplify TestServerWithResourceTemplate logic and increase test coverage * changes after code review #449 (comment)
1 parent 1eddde7 commit 251da13

File tree

2 files changed

+103
-3
lines changed

2 files changed

+103
-3
lines changed

mcptest/mcptest.go

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@ import (
2020
type Server struct {
2121
name string
2222

23-
tools []server.ServerTool
24-
prompts []server.ServerPrompt
25-
resources []server.ServerResource
23+
tools []server.ServerTool
24+
prompts []server.ServerPrompt
25+
resources []server.ServerResource
26+
resourceTemplates []ServerResourceTemplate
2627

2728
cancel func()
2829

@@ -106,6 +107,25 @@ func (s *Server) AddResources(resources ...server.ServerResource) {
106107
s.resources = append(s.resources, resources...)
107108
}
108109

110+
// ServerResourceTemplate combines a ResourceTemplate with its handler function.
111+
type ServerResourceTemplate struct {
112+
Template mcp.ResourceTemplate
113+
Handler server.ResourceTemplateHandlerFunc
114+
}
115+
116+
// AddResourceTemplate adds a resource template to an unstarted server.
117+
func (s *Server) AddResourceTemplate(template mcp.ResourceTemplate, handler server.ResourceTemplateHandlerFunc) {
118+
s.resourceTemplates = append(s.resourceTemplates, ServerResourceTemplate{
119+
Template: template,
120+
Handler: handler,
121+
})
122+
}
123+
124+
// AddResourceTemplates adds multiple resource templates to an unstarted server.
125+
func (s *Server) AddResourceTemplates(templates ...ServerResourceTemplate) {
126+
s.resourceTemplates = append(s.resourceTemplates, templates...)
127+
}
128+
109129
// Start starts the server in a goroutine. Make sure to defer Close() after Start().
110130
// When using NewServer(), the returned server is already started.
111131
func (s *Server) Start(ctx context.Context) error {
@@ -122,6 +142,10 @@ func (s *Server) Start(ctx context.Context) error {
122142
mcpServer.AddTools(s.tools...)
123143
mcpServer.AddPrompts(s.prompts...)
124144
mcpServer.AddResources(s.resources...)
145+
146+
for _, template := range s.resourceTemplates {
147+
mcpServer.AddResourceTemplate(template.Template, template.Handler)
148+
}
125149

126150
logger := log.New(&s.logBuffer, "", 0)
127151

mcptest/mcptest_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,3 +187,79 @@ func TestServerWithResource(t *testing.T) {
187187
t.Errorf("Got %q, want %q", textContent.Text, want)
188188
}
189189
}
190+
191+
func TestServerWithResourceTemplate(t *testing.T) {
192+
ctx := context.Background()
193+
194+
srv := mcptest.NewUnstartedServer(t)
195+
defer srv.Close()
196+
197+
template := mcp.NewResourceTemplate(
198+
"file://users/{userId}/documents/{docId}",
199+
"User Document",
200+
mcp.WithTemplateDescription("A user's document"),
201+
mcp.WithTemplateMIMEType("text/plain"),
202+
)
203+
204+
handler := func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
205+
if request.Params.Arguments == nil {
206+
return nil, fmt.Errorf("expected arguments to be populated from URI template")
207+
}
208+
209+
userIds, ok := request.Params.Arguments["userId"].([]string)
210+
if !ok {
211+
return nil, fmt.Errorf("expected userId argument to be populated from URI template")
212+
}
213+
if len(userIds) != 1 {
214+
return nil, fmt.Errorf("expected userId to have one value, but got %d", len(userIds))
215+
}
216+
if userIds[0] != "john" {
217+
return nil, fmt.Errorf("expected userId argument to be 'john', got %s", userIds[0])
218+
}
219+
220+
docIds, ok := request.Params.Arguments["docId"].([]string)
221+
if !ok {
222+
return nil, fmt.Errorf("expected docId argument to be populated from URI template")
223+
}
224+
if len(docIds) != 1 {
225+
return nil, fmt.Errorf("expected docId to have one value, but got %d", len(docIds))
226+
}
227+
if docIds[0] != "readme.txt" {
228+
return nil, fmt.Errorf("expected docId argument to be 'readme.txt', got %v", docIds)
229+
}
230+
231+
return []mcp.ResourceContents{
232+
mcp.TextResourceContents{
233+
URI: request.Params.URI,
234+
MIMEType: "text/plain",
235+
Text: fmt.Sprintf("Document %s for user %s", docIds[0], userIds[0]),
236+
},
237+
}, nil
238+
}
239+
240+
srv.AddResourceTemplate(template, handler)
241+
242+
err := srv.Start(ctx)
243+
if err != nil {
244+
t.Fatal(err)
245+
}
246+
247+
// Test reading a resource that matches the template
248+
var readReq mcp.ReadResourceRequest
249+
readReq.Params.URI = "file://users/john/documents/readme.txt"
250+
readResult, err := srv.Client().ReadResource(ctx, readReq)
251+
if err != nil {
252+
t.Fatal("ReadResource:", err)
253+
}
254+
if len(readResult.Contents) != 1 {
255+
t.Fatalf("Expected 1 content, got %d", len(readResult.Contents))
256+
}
257+
textContent, ok := readResult.Contents[0].(mcp.TextResourceContents)
258+
if !ok {
259+
t.Fatalf("Expected TextResourceContents, got %T", readResult.Contents[0])
260+
}
261+
want := "Document readme.txt for user john"
262+
if textContent.Text != want {
263+
t.Errorf("Got %q, want %q", textContent.Text, want)
264+
}
265+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy