Skip to content

Commit f8933ec

Browse files
committed
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
1 parent 17a2be2 commit f8933ec

File tree

5 files changed

+44
-24
lines changed

5 files changed

+44
-24
lines changed

src/everything/server/index.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { registerConditionalTools, registerTools } from "../tools/index.js";
77
import { registerResources, readInstructions } from "../resources/index.js";
88
import { registerPrompts } from "../prompts/index.js";
99
import { stopSimulatedLogging } from "./logging.js";
10+
import { syncRoots } from "./roots.js";
1011

1112
// Server Factory response
1213
export type ServerFactoryResponse = {
@@ -68,8 +69,18 @@ export const createServer: () => ServerFactoryResponse = () => {
6869
// Set resource subscription handlers
6970
setSubscriptionHandlers(server);
7071

71-
// Register conditional tools until client capabilities are known
72-
server.server.oninitialized = () => registerConditionalTools(server);
72+
// Perform post-initialization operations
73+
server.server.oninitialized = async () => {
74+
// Register conditional tools now that client capabilities are known.
75+
// This finishes before the `notifications/initialized` handler finishes.
76+
registerConditionalTools(server);
77+
78+
// Sync roots if the client supports them.
79+
// This is delayed until after the `notifications/initialized` handler finishes,
80+
// otherwise, the request gets lost.
81+
const sessionId = server.server.transport?.sessionId;
82+
setTimeout(() => syncRoots(server, sessionId), 350);
83+
};
7384

7485
// Return the ServerFactoryResponse
7586
return {

src/everything/server/roots.ts

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,24 @@ export const roots: Map<string | undefined, Root[]> = new Map<
1111
>();
1212

1313
/**
14-
* Sync the root directories from the client by requesting and updating the roots list for
15-
* the specified session.
14+
* Get the latest the client roots list for the session.
1615
*
17-
* Also sets up a notification handler to listen for changes in the roots list, ensuring that
18-
* updates are automatically fetched and handled in real-time.
16+
* - Request and cache the roots list for the session if it has not been fetched before.
17+
* - Return the cached roots list for the session if it exists.
18+
*
19+
* When requesting the roots list for a session, it also sets up a `roots/list_changed`
20+
* notification handler. This ensures that updates are automatically fetched and handled
21+
* in real-time.
22+
*
23+
* Therefore, calls to this function should only request roots from the client once per
24+
* session, but the cache will always be up to date after that first call.
1925
*
2026
* @param {McpServer} server - An instance of the MCP server used to communicate with the client.
2127
* @param {string} [sessionId] - An optional session id used to associate the roots list with a specific client session.
2228
*
2329
* @throws {Error} In case of a failure to request the roots from the client, an error log message is sent.
2430
*/
2531
export const syncRoots = async (server: McpServer, sessionId?: string) => {
26-
2732
const clientCapabilities = server.server.getClientCapabilities() || {};
2833
const clientSupportsRoots: boolean = clientCapabilities.roots !== undefined;
2934

@@ -71,14 +76,19 @@ export const syncRoots = async (server: McpServer, sessionId?: string) => {
7176
}
7277
};
7378

74-
// Set the list changed notification handler
75-
server.server.setNotificationHandler(
76-
RootsListChangedNotificationSchema,
77-
requestRoots
78-
);
79+
// If the roots have not been synced for this client,
80+
// set notification handler and request initial roots
81+
if (!roots.has(sessionId)) {
82+
// Set the list changed notification handler
83+
server.server.setNotificationHandler(
84+
RootsListChangedNotificationSchema,
85+
requestRoots
86+
);
7987

80-
// Request initial roots list immediatelys
81-
await requestRoots();
88+
// Request the initial roots list immediately
89+
await requestRoots();
90+
console.log(roots.get(sessionId));
91+
}
8292

8393
// Return the roots list for this client
8494
return roots.get(sessionId);

src/everything/tools/get-roots-list.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export const registerGetRootsListTool = (server: McpServer) => {
4040
config,
4141
async (args, extra): Promise<CallToolResult> => {
4242
// Get the current rootsFetch the current roots list from the client if need be
43-
const currentRoots = await syncRoots(server, extra.sessionId);
43+
const currentRoots = await syncRoots(server, extra.sessionId);
4444

4545
// Respond if client supports roots but doesn't have any configured
4646
if (

src/everything/tools/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ export const registerTools = (server: McpServer) => {
3939
* These must be registered conditionally, after initialization.
4040
*/
4141
export const registerConditionalTools = (server: McpServer) => {
42-
console.log("Registering conditional tools...");
4342
registerGetRootsListTool(server);
4443
registerTriggerElicitationRequestTool(server);
4544
registerTriggerSamplingRequestTool(server);

src/everything/tools/trigger-elicitation-request.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -131,9 +131,9 @@ export const registerTriggerElicitationRequestTool = (server: McpServer) => {
131131
title: "Titled Single Select Enum",
132132
description: "Choose your favorite hero",
133133
oneOf: [
134-
{const: "hero-1", title: "Superman"},
135-
{const: "hero-2", title: "Green Lantern"},
136-
{const: "hero-3", title: "Wonder Woman"},
134+
{ const: "hero-1", title: "Superman" },
135+
{ const: "hero-2", title: "Green Lantern" },
136+
{ const: "hero-3", title: "Wonder Woman" },
137137
],
138138
default: "hero-1",
139139
},
@@ -145,9 +145,9 @@ export const registerTriggerElicitationRequestTool = (server: McpServer) => {
145145
maxItems: 3,
146146
items: {
147147
anyOf: [
148-
{const: "fish-1", title: "Tuna"},
149-
{const: "fish-2", title: "Salmon"},
150-
{const: "fish-3", title: "Trout"},
148+
{ const: "fish-1", title: "Tuna" },
149+
{ const: "fish-2", title: "Salmon" },
150+
{ const: "fish-3", title: "Trout" },
151151
],
152152
},
153153
default: ["fish-1"],
@@ -166,7 +166,7 @@ export const registerTriggerElicitationRequestTool = (server: McpServer) => {
166166
},
167167
},
168168
ElicitResultSchema,
169-
{timeout: 10 * 60 * 1000 /* 10 minutes */}
169+
{ timeout: 10 * 60 * 1000 /* 10 minutes */ }
170170
);
171171

172172
// Handle different response actions
@@ -220,7 +220,7 @@ export const registerTriggerElicitationRequestTool = (server: McpServer) => {
220220
text: `\nRaw result: ${JSON.stringify(elicitationResult, null, 2)}`,
221221
});
222222

223-
return {content};
223+
return { content };
224224
}
225225
);
226226
}

0 commit comments

Comments
 (0)