5.6 KiB
Support for MCP client features
Roots
MCP allows clients to specify a set of filesystem "roots". The SDK supports this as follows:
Client-side: The SDK client always has the roots.listChanged capability.
To add roots to a client, use the
Client.AddRoots
and
Client.RemoveRoots
methods. If any servers are already connected to the
client, a call to AddRoot or RemoveRoots will result in a
notifications/roots/list_changed notification to each connected server.
Server-side: To query roots from the server, use the
ServerSession.ListRoots
method. To receive notifications about root changes, set
ServerOptions.RootsListChangedHandler.
func Example_roots() {
ctx := context.Background()
// Create a client with a single root.
c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, nil)
c.AddRoots(&mcp.Root{URI: "file://a"})
// Now create a server with a handler to receive notifications about roots.
rootsChanged := make(chan struct{})
handleRootsChanged := func(ctx context.Context, req *mcp.RootsListChangedRequest) {
rootList, err := req.Session.ListRoots(ctx, nil)
if err != nil {
log.Fatal(err)
}
var roots []string
for _, root := range rootList.Roots {
roots = append(roots, root.URI)
}
fmt.Println(roots)
close(rootsChanged)
}
s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, &mcp.ServerOptions{
RootsListChangedHandler: handleRootsChanged,
})
// Connect the server and client...
t1, t2 := mcp.NewInMemoryTransports()
if _, err := s.Connect(ctx, t1, nil); err != nil {
log.Fatal(err)
}
clientSession, err := c.Connect(ctx, t2, nil)
if err != nil {
log.Fatal(err)
}
defer clientSession.Close()
// ...and add a root. The server is notified about the change.
c.AddRoots(&mcp.Root{URI: "file://b"})
<-rootsChanged
// Output: [file://a file://b]
}
Sampling
Sampling is a way for servers to leverage the client's AI capabilities. It is implemented in the SDK as follows:
Client-side: To add the sampling capability to a client, set
ClientOptions.CreateMessageHandler.
This function is invoked whenever the server requests sampling.
Server-side: To use sampling from the server, call
ServerSession.CreateMessage.
func Example_sampling() {
ctx := context.Background()
// Create a client with a sampling handler.
c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, &mcp.ClientOptions{
CreateMessageHandler: func(_ context.Context, req *mcp.CreateMessageRequest) (*mcp.CreateMessageResult, error) {
return &mcp.CreateMessageResult{
Content: &mcp.TextContent{
Text: "would have created a message",
},
}, nil
},
})
// Connect the server and client...
ct, st := mcp.NewInMemoryTransports()
s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil)
session, err := s.Connect(ctx, st, nil)
if err != nil {
log.Fatal(err)
}
defer session.Close()
if _, err := c.Connect(ctx, ct, nil); err != nil {
log.Fatal(err)
}
msg, err := session.CreateMessage(ctx, &mcp.CreateMessageParams{})
if err != nil {
log.Fatal(err)
}
fmt.Println(msg.Content.(*mcp.TextContent).Text)
// Output: would have created a message
}
Elicitation
Elicitation allows servers to request user inputs. It is implemented in the SDK as follows:
Client-side: To add the elicitation capability to a client, set
ClientOptions.ElicitationHandler.
The elicitation handler must return a result that matches the requested schema;
otherwise, elicitation returns an error.
Server-side: To use elicitation from the server, call
ServerSession.Elicit.
func Example_elicitation() {
ctx := context.Background()
ct, st := mcp.NewInMemoryTransports()
s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil)
ss, err := s.Connect(ctx, st, nil)
if err != nil {
log.Fatal(err)
}
defer ss.Close()
c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, &mcp.ClientOptions{
ElicitationHandler: func(context.Context, *mcp.ElicitRequest) (*mcp.ElicitResult, error) {
return &mcp.ElicitResult{Action: "accept", Content: map[string]any{"test": "value"}}, nil
},
})
if _, err := c.Connect(ctx, ct, nil); err != nil {
log.Fatal(err)
}
res, err := ss.Elicit(ctx, &mcp.ElicitParams{
Message: "This should fail",
RequestedSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"test": {Type: "string"},
},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(res.Content["test"])
// Output: value
}