Files
servers/src/everything/server/roots.ts
cliffhall f8933ec3e6 Sync roots at startup. This was deferred to list_roots tool is used, but I'm putting it back.
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
2025-12-17 17:46:24 -05:00

97 lines
3.3 KiB
TypeScript

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import {
Root,
RootsListChangedNotificationSchema,
} from "@modelcontextprotocol/sdk/types.js";
// Track roots by session id
export const roots: Map<string | undefined, Root[]> = new Map<
string | undefined,
Root[]
>();
/**
* Get the latest the client roots list for the session.
*
* - Request and cache the roots list for the session if it has not been fetched before.
* - Return the cached roots list for the session if it exists.
*
* When requesting the roots list for a session, it also sets up a `roots/list_changed`
* notification handler. This ensures that updates are automatically fetched and handled
* in real-time.
*
* Therefore, calls to this function should only request roots from the client once per
* session, but the cache will always be up to date after that first call.
*
* @param {McpServer} server - An instance of the MCP server used to communicate with the client.
* @param {string} [sessionId] - An optional session id used to associate the roots list with a specific client session.
*
* @throws {Error} In case of a failure to request the roots from the client, an error log message is sent.
*/
export const syncRoots = async (server: McpServer, sessionId?: string) => {
const clientCapabilities = server.server.getClientCapabilities() || {};
const clientSupportsRoots: boolean = clientCapabilities.roots !== undefined;
// Fetch the roots list for this client
if (clientSupportsRoots) {
// Function to request the updated roots list from the client
const requestRoots = async () => {
try {
// Request the updated roots list from the client
const response = await server.server.listRoots();
if (response && "roots" in response) {
// Store the roots list for this client
roots.set(sessionId, response.roots);
// Notify the client of roots received
await server.sendLoggingMessage(
{
level: "info",
logger: "everything-server",
data: `Roots updated: ${response.roots.length} root(s) received from client`,
},
sessionId
);
} else {
await server.sendLoggingMessage(
{
level: "info",
logger: "everything-server",
data: "Client returned no roots set",
},
sessionId
);
}
} catch (error) {
await server.sendLoggingMessage(
{
level: "error",
logger: "everything-server",
data: `Failed to request roots from client: ${
error instanceof Error ? error.message : String(error)
}`,
},
sessionId
);
}
};
// If the roots have not been synced for this client,
// set notification handler and request initial roots
if (!roots.has(sessionId)) {
// Set the list changed notification handler
server.server.setNotificationHandler(
RootsListChangedNotificationSchema,
requestRoots
);
// Request the initial roots list immediately
await requestRoots();
console.log(roots.get(sessionId));
}
// Return the roots list for this client
return roots.get(sessionId);
}
};