Demonstrate registration of tools conditioned upon client capability support. Also, obviated need for clientConnected callback to pass sessionId because we defer initial fetching of roots happens when you run the get-roots-list tool.
* In how-it-works.md, - added a section on conditional tool registration * In server/index.ts - import registerConditionalTools - in an oninitialized handler for the server, call registerConditionalTools - removed clientConnected from ServerFactoryResponse and all mentions in docs * In tools/index.ts - export a registerConditionalTools function - refactor/move calls to registerGetRootsListTool, registerTriggerElicitationRequestTool, and registerTriggerSamplingRequestTool out of registerTools and into registerConditionalTools * In server/roots.ts - only act if client supports roots - remove setInterval from call to requestRoots. It isn't happening during the initialze handshake anymore, so it doesn't interfere with that process if called immediaately * In get-roots-list.ts, trigger-elicitation-request.ts, and trigger-sampling-request.ts, - only register tool if client supports capability * Throughout the rest of the files, removing all references to `clientConnected`
This commit is contained in:
parent
ebac6314cf
commit
1b8f376b90
2 changed files with 323 additions and 323 deletions
|
|
@ -3,16 +3,14 @@ import {
|
|||
setSubscriptionHandlers,
|
||||
stopSimulatedResourceUpdates,
|
||||
} from "../resources/subscriptions.js";
|
||||
import { registerTools } from "../tools/index.js";
|
||||
import { registerConditionalTools, registerTools } from "../tools/index.js";
|
||||
import { registerResources, readInstructions } from "../resources/index.js";
|
||||
import { registerPrompts } from "../prompts/index.js";
|
||||
import { stopSimulatedLogging } from "./logging.js";
|
||||
import { syncRoots } from "./roots.js";
|
||||
|
||||
// Server Factory response
|
||||
export type ServerFactoryResponse = {
|
||||
server: McpServer;
|
||||
clientConnected: (sessionId?: string) => void;
|
||||
cleanup: (sessionId?: string) => void;
|
||||
};
|
||||
|
||||
|
|
@ -22,13 +20,11 @@ export type ServerFactoryResponse = {
|
|||
* This function initializes a `McpServer` with specific capabilities and instructions,
|
||||
* registers tools, resources, and prompts, and configures resource subscription handlers.
|
||||
*
|
||||
* @returns {ServerFactoryResponse} An object containing the server instance, a `clientConnected`
|
||||
* callback for post-connection setup, and a `cleanup` function for handling server-side cleanup
|
||||
* when a session ends.
|
||||
* @returns {ServerFactoryResponse} An object containing the server instance, and a `cleanup`
|
||||
* function for handling server-side cleanup when a session ends.
|
||||
*
|
||||
* Properties of the returned object:
|
||||
* - `server` {Object}: The initialized server instance.
|
||||
* - `clientConnected` {Function}: A post-connect callback to enable operations that require a `sessionId`.
|
||||
* - `cleanup` {Function}: Function to perform cleanup operations for a closing session.
|
||||
*/
|
||||
export const createServer: () => ServerFactoryResponse = () => {
|
||||
|
|
@ -72,13 +68,12 @@ export const createServer: () => ServerFactoryResponse = () => {
|
|||
// Set resource subscription handlers
|
||||
setSubscriptionHandlers(server);
|
||||
|
||||
// Register conditional tools until client capabilities are known
|
||||
server.server.oninitialized = () => registerConditionalTools(server);
|
||||
|
||||
// Return the ServerFactoryResponse
|
||||
return {
|
||||
server,
|
||||
clientConnected: (sessionId?: string) => {
|
||||
// Set a roots list changed handler and fetch the initial roots list from the client
|
||||
syncRoots(server, sessionId);
|
||||
},
|
||||
cleanup: (sessionId?: string) => {
|
||||
// Stop any simulated logging or resource updates that may have been initiated.
|
||||
stopSimulatedLogging(sessionId);
|
||||
|
|
|
|||
|
|
@ -22,56 +22,65 @@ export const roots: Map<string | undefined, Root[]> = new Map<
|
|||
*
|
||||
* @throws {Error} In case of a failure to request the roots from the client, an error log message is sent.
|
||||
*/
|
||||
export const syncRoots = (server: McpServer, sessionId?: string) => {
|
||||
// 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);
|
||||
export const syncRoots = async (server: McpServer, sessionId?: string) => {
|
||||
|
||||
// Notify the client of roots received
|
||||
const clientCapabilities = server.server.getClientCapabilities() || {};
|
||||
const clientSupportsRoots: boolean = clientCapabilities.roots !== undefined;
|
||||
|
||||
// If roots have not been fetched for this client, fetch them
|
||||
if (clientSupportsRoots && !roots.has(sessionId)) {
|
||||
// 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: "info",
|
||||
level: "error",
|
||||
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",
|
||||
data: `Failed to request roots from client: ${
|
||||
error instanceof Error ? error.message : String(error)
|
||||
}`,
|
||||
},
|
||||
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
|
||||
);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Set the list changed notification handler
|
||||
server.server.setNotificationHandler(
|
||||
RootsListChangedNotificationSchema,
|
||||
requestRoots
|
||||
);
|
||||
// Set the list changed notification handler
|
||||
server.server.setNotificationHandler(
|
||||
RootsListChangedNotificationSchema,
|
||||
requestRoots
|
||||
);
|
||||
|
||||
// Request initial roots list after a brief delay
|
||||
// Allows initial POST request to complete on streamableHttp transports
|
||||
setTimeout(() => requestRoots(), 350);
|
||||
// Request initial roots list immediatelys
|
||||
await requestRoots();
|
||||
|
||||
// Return the roots list for this client
|
||||
return roots.get(sessionId);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue