Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/everything/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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.:
Expand Down
168 changes: 156 additions & 12 deletions src/everything/everything.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import {
CallToolRequestSchema,
ClientCapabilities,
CompleteRequestSchema,
CreateMessageRequest,
CreateMessageResultSchema,
Expand All @@ -12,11 +13,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";
Expand Down Expand Up @@ -96,6 +99,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
Expand Down Expand Up @@ -129,7 +134,8 @@ enum ToolName {
GET_RESOURCE_REFERENCE = "getResourceReference",
ELICITATION = "startElicitation",
GET_RESOURCE_LINKS = "getResourceLinks",
STRUCTURED_CONTENT = "structuredContent"
STRUCTURED_CONTENT = "structuredContent",
LIST_ROOTS = "listRoots"
}

enum PromptName {
Expand Down Expand Up @@ -171,6 +177,12 @@ 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[] = [];
let clientSupportsRoots = false;
const messages = [
{ level: "debug", data: "Debug-level message" },
{ level: "info", data: "Info-level message" },
Expand Down Expand Up @@ -530,7 +542,13 @@ export const createServer = () => {
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.",
inputSchema: zodToJsonSchema(ListRootsSchema) as ToolInput,
});

return { tools };
});

Expand Down Expand Up @@ -728,10 +746,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'
},
}
}
Expand Down Expand Up @@ -791,11 +809,10 @@ export const createServer = () => {
type: "resource_link",
uri: resource.uri,
name: resource.name,
description: `Resource ${i + 1}: ${
resource.mimeType === "text/plain"
? "plaintext resource"
: "binary blob resource"
}`,
description: `Resource ${i + 1}: ${resource.mimeType === "text/plain"
? "plaintext resource"
: "binary blob resource"
}`,
mimeType: resource.mimeType,
});
}
Expand All @@ -819,11 +836,57 @@ export const createServer = () => {
}

return {
content: [ backwardCompatiblecontent ],
content: [backwardCompatiblecontent],
structuredContent: weather
};
}

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: "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"
}
]
};
}

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}`);
});

Expand Down Expand Up @@ -873,6 +936,87 @@ 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 () => {
clientCapabilities = server.getClientCapabilities();

if (clientCapabilities?.roots) {
clientSupportsRoots = true;
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);
Expand Down