Roots

Roots allow a client to declare specific URI prefixes that it has access to. Servers can then discover these roots to understand the boundaries of the client environment, which is particularly useful for tools that need to perform file system operations or access specific resources.

Roots Overview

In the Model Context Protocol, roots represent the “home” or “workspace” directories of a client. By declaring roots, the client informs the server about:

  • The absolute URIs (typically file:///) that the server is allowed to interact with.

  • The names of these workspaces to provide context to the LLM.

Client-side Setup

A client provides its list of roots using the set_roots() method. This method stores the roots internally, registers a handler for roots/list, and—when called before connect()—automatically advertises the matching client capability.

                    std::vector<Root> client_roots = {
                        Root{.uri = "file:///workspace/", .name = "Workspace"},
                        Root{.uri = "mcp://docs/", .name = "Documentation"}};

                    std::cout << "Client: Setting " << client_roots.size() << " root(s)\n";
                    for (const auto& root : client_roots) {
                        std::cout << "Client:   - " << root.uri;
                        if (root.name) {
                            std::cout << " (" << *root.name << ")";
                        }
                        std::cout << "\n";
                    }
                    client.set_roots(client_roots);

The set_roots method also takes an optional notify parameter. If set to true, the client sends notifications/roots/list_changed only when its initialization capabilities advertised roots.listChanged=true. This is a client capability; servers do not advertise roots support.

Server-side Discovery

Servers can discover client roots at any time using the request_roots() method available in the Server Context. This is a “reverse RPC” call where the server sends a request to the client.

        using namespace mcp;

Handling Roots Requests

While set_roots() provides a convenient way to manage a static or semi-static list of roots, you can also register a dynamic handler using on_roots_list(). This is useful if the roots need to be calculated on-the-fly when requested.

client.on_roots_list([](const nlohmann::json& params) -> Task<ListRootsResult> {
    ListRootsResult result;
    result.roots = {
        Root{.uri = "file:///dynamic/path", .name = "Dynamic"}
    };
    co_return result;
});

Roots Change Notifications

When the set of roots changes (e.g., the user opens a new folder in their IDE), the client should inform the server.

  1. Client: Call set_roots(new_roots, true) to update and notify.

  2. Server: If the server wants to react to these changes, it should monitor the roots/list state or re-request roots when needed.

Example Walkthrough

The complete implementation of roots discovery can be found in the feature examples. It demonstrates a server tool that, when called, asks the client for its roots and returns them as a text summary.

        nlohmann::json tool_schema = {
            {"type", "object"}, {"properties", {}}, {"required", nlohmann::json::array()}};

        server.add_tool<nlohmann::json, nlohmann::json>(
            "discover_roots", "Discovers and displays the client's declared root URIs", tool_schema,
            [](Context& ctx, const nlohmann::json&) -> Task<nlohmann::json> {
                std::cout << "Tool: Requesting roots from client\n";

                try {
                    auto roots_result = co_await ctx.request_roots();

                    std::cout << "Tool: Received " << roots_result.roots.size() << " root(s)\n";

                    std::string output = "Discovered roots:\n";
                    for (const auto& root : roots_result.roots) {
                        std::cout << "Tool:   - URI: " << root.uri;
                        if (root.name) {
                            std::cout << " (name: " << *root.name << ")";
                        }
                        std::cout << "\n";

                        output += "  - " + root.uri;
                        if (root.name) {
                            output += " (" + *root.name + ")";
                        }
                        output += "\n";
                    }

                    co_return nlohmann::json{
                        {"content",
                         nlohmann::json::array({nlohmann::json{{"type", "text"}, {"text", output}}})},
                        {"isError", false}};
                } catch (const std::exception& e) {
                    std::cout << "Tool: Error requesting roots: " << e.what() << "\n";
                    std::string error_msg = "Error: " + std::string(e.what());
                    co_return nlohmann::json{{"content", nlohmann::json::array({nlohmann::json{
                                                             {"type", "text"}, {"text", error_msg}}})},
                                             {"isError", true}};
                }
            });