mirror of
https://github.com/modelcontextprotocol/servers.git
synced 2026-02-19 11:54:58 -05:00
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.
This commit is contained in:
@@ -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.:
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user