From cf9f66c14ebf4e8b99b9647ea11965051dc2b895 Mon Sep 17 00:00:00 2001 From: Kumbham Ajay Goud Date: Mon, 18 Aug 2025 15:31:23 +0530 Subject: [PATCH 1/7] feat: add MCP roots protocol support to everything server - Add roots capability declaration with listChanged: true - Implement roots/list_changed notification handler - Add initialization handler to request initial roots from client - Add new listRoots tool to demonstrate roots functionality - Add comprehensive logging for roots protocol events - Update README.md with roots documentation Resolves #2552 The everything server now demonstrates all MCP features including the roots protocol. This provides a complete reference implementation for client developers to test their roots protocol implementation against, even though this server doesn't access files directly. --- src/everything/README.md | 19 +++++ src/everything/everything.ts | 145 ++++++++++++++++++++++++++++++++--- 2 files changed, 155 insertions(+), 9 deletions(-) diff --git a/src/everything/README.md b/src/everything/README.md index 7c98b8588b..466e9cc23b 100644 --- a/src/everything/README.md +++ b/src/everything/README.md @@ -89,6 +89,13 @@ This MCP server attempts to exercise all the features of the MCP protocol. It is - `structuredContent` field conformant to the output schema - A backward compatible Text Content field, a SHOULD advisory in the specification +11. `listRoots` + - Lists the current MCP roots provided by the client + - Demonstrates the roots protocol capability even though this server doesn't access files + - No inputs required + - Returns: List of current roots with their URIs and names, or a message if no roots are set + - Shows how servers can interact with the MCP roots protocol + ### Resources The server provides 100 test resources in two formats: @@ -129,6 +136,18 @@ Resource features: - Returns: Multi-turn conversation with an embedded resource reference - Shows how to include resources directly in prompt messages +### Roots + +The server demonstrates the MCP roots protocol capability: + +- Declares `roots: { listChanged: true }` capability to indicate support for roots +- Handles `roots/list_changed` notifications from clients +- Requests initial roots during server initialization +- Provides a `listRoots` tool to display current roots +- Logs roots-related events for demonstration purposes + +Note: This server doesn't actually access files, but demonstrates how servers can interact with the roots protocol for clients that need to understand which directories are available for file operations. + ### Logging The server sends random-leveled log messages every 15 seconds, e.g.: diff --git a/src/everything/everything.ts b/src/everything/everything.ts index 19dd646cad..97d22bdc2f 100644 --- a/src/everything/everything.ts +++ b/src/everything/everything.ts @@ -12,11 +12,13 @@ import { LoggingLevel, ReadResourceRequestSchema, Resource, + RootsListChangedNotificationSchema, SetLevelRequestSchema, SubscribeRequestSchema, Tool, ToolSchema, UnsubscribeRequestSchema, + type Root, } from "@modelcontextprotocol/sdk/types.js"; import { z } from "zod"; import { zodToJsonSchema } from "zod-to-json-schema"; @@ -96,6 +98,8 @@ const GetResourceLinksSchema = z.object({ .describe("Number of resource links to return (1-10)"), }); +const ListRootsSchema = z.object({}); + const StructuredContentSchema = { input: z.object({ location: z @@ -129,7 +133,8 @@ enum ToolName { GET_RESOURCE_REFERENCE = "getResourceReference", ELICITATION = "startElicitation", GET_RESOURCE_LINKS = "getResourceLinks", - STRUCTURED_CONTENT = "structuredContent" + STRUCTURED_CONTENT = "structuredContent", + LIST_ROOTS = "listRoots" } enum PromptName { @@ -160,6 +165,7 @@ export const createServer = () => { logging: {}, completions: {}, elicitation: {}, + roots: { listChanged: true }, }, instructions } @@ -171,6 +177,9 @@ export const createServer = () => { let logLevel: LoggingLevel = "debug"; let logsUpdateInterval: NodeJS.Timeout | undefined; + + // Roots state management + let currentRoots: Root[] = []; const messages = [ { level: "debug", data: "Debug-level message" }, { level: "info", data: "Info-level message" }, @@ -529,6 +538,12 @@ export const createServer = () => { inputSchema: zodToJsonSchema(StructuredContentSchema.input) as ToolInput, outputSchema: zodToJsonSchema(StructuredContentSchema.output) as ToolOutput, }, + { + name: ToolName.LIST_ROOTS, + description: + "Lists the current MCP roots provided by the client. Demonstrates the roots protocol capability even though this server doesn't access files.", + inputSchema: zodToJsonSchema(ListRootsSchema) as ToolInput, + }, ]; return { tools }; @@ -728,10 +743,10 @@ export const createServer = () => { properties: { color: { type: 'string', description: 'Favorite color' }, number: { type: 'integer', description: 'Favorite number', minimum: 1, maximum: 100 }, - pets: { - type: 'string', - enum: ['cats', 'dogs', 'birds', 'fish', 'reptiles'], - description: 'Favorite pets' + pets: { + type: 'string', + enum: ['cats', 'dogs', 'birds', 'fish', 'reptiles'], + description: 'Favorite pets' }, } } @@ -791,11 +806,10 @@ export const createServer = () => { type: "resource_link", uri: resource.uri, name: resource.name, - description: `Resource ${i + 1}: ${ - resource.mimeType === "text/plain" + description: `Resource ${i + 1}: ${resource.mimeType === "text/plain" ? "plaintext resource" : "binary blob resource" - }`, + }`, mimeType: resource.mimeType, }); } @@ -819,11 +833,44 @@ export const createServer = () => { } return { - content: [ backwardCompatiblecontent ], + content: [backwardCompatiblecontent], structuredContent: weather }; } + if (name === ToolName.LIST_ROOTS) { + ListRootsSchema.parse(args); + + if (currentRoots.length === 0) { + return { + content: [ + { + type: "text", + text: "No roots are currently set. This could mean:\n" + + "1. The client doesn't support the MCP roots protocol\n" + + "2. The client hasn't provided any roots yet\n" + + "3. The client provided empty roots" + } + ] + }; + } + + const rootsList = currentRoots.map((root, index) => { + return `${index + 1}. ${root.name || 'Unnamed Root'}\n URI: ${root.uri}`; + }).join('\n\n'); + + 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." + } + ] + }; + } + throw new Error(`Unknown tool: ${name}`); }); @@ -873,6 +920,86 @@ export const createServer = () => { return {}; }); + // Roots protocol handlers + server.setNotificationHandler(RootsListChangedNotificationSchema, async () => { + try { + // Request the updated roots list from the client + const response = await server.listRoots(); + if (response && 'roots' in response) { + currentRoots = response.roots; + + // Log the roots update for demonstration + await server.notification({ + method: "notifications/message", + params: { + level: "info", + logger: "everything-server", + data: `Roots updated: ${currentRoots.length} root(s) received from client`, + }, + }); + } + } catch (error) { + await server.notification({ + method: "notifications/message", + params: { + level: "error", + logger: "everything-server", + data: `Failed to request roots from client: ${error instanceof Error ? error.message : String(error)}`, + }, + }); + } + }); + + // Handle post-initialization setup for roots + server.oninitialized = async () => { + const clientCapabilities = server.getClientCapabilities(); + + if (clientCapabilities?.roots) { + try { + const response = await server.listRoots(); + if (response && 'roots' in response) { + currentRoots = response.roots; + + await server.notification({ + method: "notifications/message", + params: { + level: "info", + logger: "everything-server", + data: `Initial roots received: ${currentRoots.length} root(s) from client`, + }, + }); + } else { + await server.notification({ + method: "notifications/message", + params: { + level: "warning", + logger: "everything-server", + data: "Client returned no roots set", + }, + }); + } + } catch (error) { + await server.notification({ + method: "notifications/message", + params: { + level: "error", + logger: "everything-server", + data: `Failed to request initial roots from client: ${error instanceof Error ? error.message : String(error)}`, + }, + }); + } + } else { + await server.notification({ + method: "notifications/message", + params: { + level: "info", + logger: "everything-server", + data: "Client does not support MCP roots protocol", + }, + }); + } + }; + const cleanup = async () => { if (subsUpdateInterval) clearInterval(subsUpdateInterval); if (logsUpdateInterval) clearInterval(logsUpdateInterval); From 39c1ca8df07fc53ab8bfbf2802f381af7b4bec4e Mon Sep 17 00:00:00 2001 From: AjayKumbham Date: Wed, 27 Aug 2025 20:24:23 +0530 Subject: [PATCH 2/7] feat: improve roots messaging to distinguish client support vs configuration - Add clientSupportsRoots tracking variable - Set clientSupportsRoots during initialization based on client capabilities - Update listRoots tool to provide clearer messaging: - Specific message when client doesn't support roots protocol - Different message when client supports roots but none are configured - Improves user experience by clearly explaining the different scenarios Addresses feedback from @olaservo in PR review --- src/everything/everything.ts | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/everything/everything.ts b/src/everything/everything.ts index 97d22bdc2f..f6df26511e 100644 --- a/src/everything/everything.ts +++ b/src/everything/everything.ts @@ -180,6 +180,7 @@ export const createServer = () => { // Roots state management let currentRoots: Root[] = []; + let clientSupportsRoots = false; const messages = [ { level: "debug", data: "Debug-level message" }, { level: "info", data: "Info-level message" }, @@ -807,8 +808,8 @@ export const createServer = () => { uri: resource.uri, name: resource.name, description: `Resource ${i + 1}: ${resource.mimeType === "text/plain" - ? "plaintext resource" - : "binary blob resource" + ? "plaintext resource" + : "binary blob resource" }`, mimeType: resource.mimeType, }); @@ -841,15 +842,28 @@ export const createServer = () => { if (name === ToolName.LIST_ROOTS) { ListRootsSchema.parse(args); + if (!clientSupportsRoots) { + return { + content: [ + { + type: "text", + text: "The MCP client does not support the roots protocol.\n\n" + + "This means the server cannot access information about the client's workspace directories or file system roots." + } + ] + }; + } + if (currentRoots.length === 0) { return { content: [ { type: "text", - text: "No roots are currently set. This could mean:\n" + - "1. The client doesn't support the MCP roots protocol\n" + - "2. The client hasn't provided any roots yet\n" + - "3. The client provided empty roots" + 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" } ] }; @@ -955,6 +969,7 @@ export const createServer = () => { const clientCapabilities = server.getClientCapabilities(); if (clientCapabilities?.roots) { + clientSupportsRoots = true; try { const response = await server.listRoots(); if (response && 'roots' in response) { From f51757eedbb1f6193b1e1b74cfec10c5e62015af Mon Sep 17 00:00:00 2001 From: AjayKumbham Date: Thu, 28 Aug 2025 16:21:48 +0530 Subject: [PATCH 3/7] fix: remove roots from server capabilities - it's a client capability --- src/everything/everything.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/everything/everything.ts b/src/everything/everything.ts index f6df26511e..6935681588 100644 --- a/src/everything/everything.ts +++ b/src/everything/everything.ts @@ -165,7 +165,6 @@ export const createServer = () => { logging: {}, completions: {}, elicitation: {}, - roots: { listChanged: true }, }, instructions } From d279ace6b445514cbe4a9ae483db1c61cb10fbe6 Mon Sep 17 00:00:00 2001 From: Kumbham Ajay Goud Date: Thu, 28 Aug 2025 22:16:33 +0530 Subject: [PATCH 4/7] Update src/everything/everything.ts Co-authored-by: Cliff Hall --- src/everything/everything.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/everything/everything.ts b/src/everything/everything.ts index 6935681588..7f342003f4 100644 --- a/src/everything/everything.ts +++ b/src/everything/everything.ts @@ -176,6 +176,8 @@ export const createServer = () => { let logLevel: LoggingLevel = "debug"; let logsUpdateInterval: NodeJS.Timeout | undefined; + // Store client capabilities + let clientCapabilities: ClientCapabilities | undefined; // Roots state management let currentRoots: Root[] = []; From 2b8ba4ac9fd949607c3d76db7c0cf14e6228aa6e Mon Sep 17 00:00:00 2001 From: Kumbham Ajay Goud Date: Thu, 28 Aug 2025 22:16:52 +0530 Subject: [PATCH 5/7] Update src/everything/everything.ts Co-authored-by: Cliff Hall --- src/everything/everything.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/everything/everything.ts b/src/everything/everything.ts index 7f342003f4..340def8077 100644 --- a/src/everything/everything.ts +++ b/src/everything/everything.ts @@ -540,14 +540,14 @@ export const createServer = () => { inputSchema: zodToJsonSchema(StructuredContentSchema.input) as ToolInput, outputSchema: zodToJsonSchema(StructuredContentSchema.output) as ToolOutput, }, - { + ]; + if (clientCapabilities!.roots) tools.push ({ name: ToolName.LIST_ROOTS, description: - "Lists the current MCP roots provided by the client. Demonstrates the roots protocol capability even though this server doesn't access files.", + "Lists the current MCP roots provided by the client. Demonstrates the roots protocol capability even though this server doesn't access files.", inputSchema: zodToJsonSchema(ListRootsSchema) as ToolInput, - }, - ]; - + }); + return { tools }; }); From d32184151961c2dc4cfa054947bb1f0eea75099a Mon Sep 17 00:00:00 2001 From: Kumbham Ajay Goud Date: Thu, 28 Aug 2025 22:17:07 +0530 Subject: [PATCH 6/7] Update src/everything/everything.ts Co-authored-by: Cliff Hall --- src/everything/everything.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/everything/everything.ts b/src/everything/everything.ts index 340def8077..512cb8dfb0 100644 --- a/src/everything/everything.ts +++ b/src/everything/everything.ts @@ -967,7 +967,7 @@ export const createServer = () => { // Handle post-initialization setup for roots server.oninitialized = async () => { - const clientCapabilities = server.getClientCapabilities(); + clientCapabilities = server.getClientCapabilities(); if (clientCapabilities?.roots) { clientSupportsRoots = true; From c45c0e26a045cd69872db5603e07f4fa265fc813 Mon Sep 17 00:00:00 2001 From: AjayKumbham Date: Thu, 28 Aug 2025 22:25:46 +0530 Subject: [PATCH 7/7] fix: add missing ClientCapabilities import --- src/everything/everything.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/everything/everything.ts b/src/everything/everything.ts index 512cb8dfb0..3360846380 100644 --- a/src/everything/everything.ts +++ b/src/everything/everything.ts @@ -1,6 +1,7 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { CallToolRequestSchema, + ClientCapabilities, CompleteRequestSchema, CreateMessageRequest, CreateMessageResultSchema,