Skip to content

Support for MCP client features

  1. Roots
  2. Sampling
  3. Elicitation
  4. Capabilities
    1. Capability inference
    2. Explicit capabilities

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()
    serverSession, err := s.Connect(ctx, t1, nil)
    if err != nil {
        log.Fatal(err)
    }
    defer serverSession.Close()

    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. If your handler supports URL mode elicitation, you must declare that capability explicitly (see Capabilities)

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
}

Capabilities

Client capabilities are advertised to servers during the initialization handshake. By default, the SDK advertises the logging capability. Additional capabilities are automatically added when server features are added (e.g. via AddTool), or when handlers are set in the ServerOptions struct (e.g., setting CompletionHandler adds the completions capability), or may be configured explicitly.

Capability inference

When handlers are set on ClientOptions (e.g., CreateMessageHandler or ElicitationHandler), the corresponding capability is automatically added if not already present, with a default configuration.

For elicitation, if the handler is set but no Capabilities.Elicitation is specified, the client defaults to form elicitation. To enable URL elicitation or both modes, configure Capabilities.Elicitation explicitly.

See the ClientCapabilities documentation for further details on inference.

Explicit capabilities

To explicitly declare capabilities, or to override the default inferred capability, set ClientOptions.Capabilities. This sets the initial client capabilities, before any capabilities are added based on configured handlers. If a capability is already present in Capabilities, adding a handler will not change its configuration.

This allows you to:

  • Disable default capabilities: Pass an empty &ClientCapabilities{} to disable all defaults, including roots.
  • Disable listChanged notifications: Set ListChanged: false on a capability to prevent the client from sending list-changed notifications when roots are added or removed.
  • Configure elicitation modes: Specify which elicitation modes (form, URL) the client supports.
// Configure elicitation modes and disable roots.
client := mcp.NewClient(impl, &mcp.ClientOptions{
    Capabilities: &mcp.ClientCapabilities{
        Elicitation: &mcp.ElicitationCapabilities{
            Form: &mcp.FormElicitationCapabilities{},
            URL:  &mcp.URLElicitationCapabilities{},
        },
    },
    ElicitationHandler: handler,
})