The syncRoots call should be idempotent, requesting roots if they haven't been yet for the session, but always retuning the cached roots otherwise. That could be deferred but setting the handler for roots_list changed note should not.
* In server/roots.ts
- only set the notification handler and call for initial roots list if the roots aren't already cached for this client.
* In server/index.ts
- in the oninitialized handler
- get the sessionId from the transport
- set a 350ms timeout to call syncRoots with the server and sessionId
- this delay cause it to run after the `notifications/initialized` handler finishes, otherwise, the request gets lost.
* All other changes attributable to prettier
92 lines
3.4 KiB
TypeScript
92 lines
3.4 KiB
TypeScript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
import { syncRoots } from "../server/roots.js";
|
|
|
|
// Tool configuration
|
|
const name = "get-roots-list";
|
|
const config = {
|
|
title: "Get Roots List Tool",
|
|
description:
|
|
"Lists the current MCP roots provided by the client. Demonstrates the roots protocol capability even though this server doesn't access files.",
|
|
inputSchema: {},
|
|
};
|
|
|
|
/**
|
|
* Registers the 'get-roots-list' tool.
|
|
*
|
|
* If the client does not support the roots capability, the tool is not registered.
|
|
*
|
|
* The registered tool interacts with the MCP roots capability, which enables the server to access
|
|
* information about the client's workspace directories or file system roots.
|
|
*
|
|
* When supported, the server automatically retrieves and formats the current list of roots from the
|
|
* client upon connection and whenever the client sends a `roots/list_changed` notification.
|
|
*
|
|
* Therefore, this tool displays the roots that the server currently knows about for the connected
|
|
* client. If for some reason the server never got the initial roots list, the tool will request the
|
|
* list from the client again.
|
|
*
|
|
* @param {McpServer} server - The McpServer instance where the tool will be registered.
|
|
*/
|
|
export const registerGetRootsListTool = (server: McpServer) => {
|
|
// Does client support roots?
|
|
const clientCapabilities = server.server.getClientCapabilities() || {};
|
|
const clientSupportsRoots: boolean = clientCapabilities.roots !== undefined;
|
|
|
|
// If so, register tool
|
|
if (clientSupportsRoots) {
|
|
server.registerTool(
|
|
name,
|
|
config,
|
|
async (args, extra): Promise<CallToolResult> => {
|
|
// Get the current rootsFetch the current roots list from the client if need be
|
|
const currentRoots = await syncRoots(server, extra.sessionId);
|
|
|
|
// Respond if client supports roots but doesn't have any configured
|
|
if (
|
|
clientSupportsRoots &&
|
|
(!currentRoots || currentRoots.length === 0)
|
|
) {
|
|
return {
|
|
content: [
|
|
{
|
|
type: "text",
|
|
text:
|
|
"The client supports roots but no roots are currently configured.\n\n" +
|
|
"This could mean:\n" +
|
|
"1. The client hasn't provided any roots yet\n" +
|
|
"2. The client provided an empty roots list\n" +
|
|
"3. The roots configuration is still being loaded",
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
// Create formatted response if there is a list of roots
|
|
const rootsList = currentRoots
|
|
? currentRoots
|
|
.map((root, index) => {
|
|
return `${index + 1}. ${root.name || "Unnamed Root"}\n URI: ${
|
|
root.uri
|
|
}`;
|
|
})
|
|
.join("\n\n")
|
|
: "No roots found";
|
|
|
|
return {
|
|
content: [
|
|
{
|
|
type: "text",
|
|
text:
|
|
`Current MCP Roots (${
|
|
currentRoots!.length
|
|
} total):\n\n${rootsList}\n\n` +
|
|
"Note: This server demonstrates the roots protocol capability but doesn't actually access files. " +
|
|
"The roots are provided by the MCP client and can be used by servers that need file system access.",
|
|
},
|
|
],
|
|
};
|
|
}
|
|
);
|
|
}
|
|
};
|