Skip to content

Commit 7d37706

Browse files
authored
Merge branch 'main' into add-onyx-server
2 parents e8c2357 + d47104a commit 7d37706

File tree

2 files changed

+175
-12
lines changed

2 files changed

+175
-12
lines changed

src/everything/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,13 @@ This MCP server attempts to exercise all the features of the MCP protocol. It is
8989
- `structuredContent` field conformant to the output schema
9090
- A backward compatible Text Content field, a SHOULD advisory in the specification
9191

92+
11. `listRoots`
93+
- Lists the current MCP roots provided by the client
94+
- Demonstrates the roots protocol capability even though this server doesn't access files
95+
- No inputs required
96+
- Returns: List of current roots with their URIs and names, or a message if no roots are set
97+
- Shows how servers can interact with the MCP roots protocol
98+
9299
### Resources
93100

94101
The server provides 100 test resources in two formats:
@@ -129,6 +136,18 @@ Resource features:
129136
- Returns: Multi-turn conversation with an embedded resource reference
130137
- Shows how to include resources directly in prompt messages
131138

139+
### Roots
140+
141+
The server demonstrates the MCP roots protocol capability:
142+
143+
- Declares `roots: { listChanged: true }` capability to indicate support for roots
144+
- Handles `roots/list_changed` notifications from clients
145+
- Requests initial roots during server initialization
146+
- Provides a `listRoots` tool to display current roots
147+
- Logs roots-related events for demonstration purposes
148+
149+
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.
150+
132151
### Logging
133152

134153
The server sends random-leveled log messages every 15 seconds, e.g.:

src/everything/everything.ts

Lines changed: 156 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
22
import {
33
CallToolRequestSchema,
4+
ClientCapabilities,
45
CompleteRequestSchema,
56
CreateMessageRequest,
67
CreateMessageResultSchema,
@@ -12,11 +13,13 @@ import {
1213
LoggingLevel,
1314
ReadResourceRequestSchema,
1415
Resource,
16+
RootsListChangedNotificationSchema,
1517
SetLevelRequestSchema,
1618
SubscribeRequestSchema,
1719
Tool,
1820
ToolSchema,
1921
UnsubscribeRequestSchema,
22+
type Root,
2023
} from "@modelcontextprotocol/sdk/types.js";
2124
import { z } from "zod";
2225
import { zodToJsonSchema } from "zod-to-json-schema";
@@ -96,6 +99,8 @@ const GetResourceLinksSchema = z.object({
9699
.describe("Number of resource links to return (1-10)"),
97100
});
98101

102+
const ListRootsSchema = z.object({});
103+
99104
const StructuredContentSchema = {
100105
input: z.object({
101106
location: z
@@ -129,7 +134,8 @@ enum ToolName {
129134
GET_RESOURCE_REFERENCE = "getResourceReference",
130135
ELICITATION = "startElicitation",
131136
GET_RESOURCE_LINKS = "getResourceLinks",
132-
STRUCTURED_CONTENT = "structuredContent"
137+
STRUCTURED_CONTENT = "structuredContent",
138+
LIST_ROOTS = "listRoots"
133139
}
134140

135141
enum PromptName {
@@ -171,6 +177,12 @@ export const createServer = () => {
171177

172178
let logLevel: LoggingLevel = "debug";
173179
let logsUpdateInterval: NodeJS.Timeout | undefined;
180+
// Store client capabilities
181+
let clientCapabilities: ClientCapabilities | undefined;
182+
183+
// Roots state management
184+
let currentRoots: Root[] = [];
185+
let clientSupportsRoots = false;
174186
const messages = [
175187
{ level: "debug", data: "Debug-level message" },
176188
{ level: "info", data: "Info-level message" },
@@ -530,7 +542,13 @@ export const createServer = () => {
530542
outputSchema: zodToJsonSchema(StructuredContentSchema.output) as ToolOutput,
531543
},
532544
];
533-
545+
if (clientCapabilities!.roots) tools.push ({
546+
name: ToolName.LIST_ROOTS,
547+
description:
548+
"Lists the current MCP roots provided by the client. Demonstrates the roots protocol capability even though this server doesn't access files.",
549+
inputSchema: zodToJsonSchema(ListRootsSchema) as ToolInput,
550+
});
551+
534552
return { tools };
535553
});
536554

@@ -728,10 +746,10 @@ export const createServer = () => {
728746
properties: {
729747
color: { type: 'string', description: 'Favorite color' },
730748
number: { type: 'integer', description: 'Favorite number', minimum: 1, maximum: 100 },
731-
pets: {
732-
type: 'string',
733-
enum: ['cats', 'dogs', 'birds', 'fish', 'reptiles'],
734-
description: 'Favorite pets'
749+
pets: {
750+
type: 'string',
751+
enum: ['cats', 'dogs', 'birds', 'fish', 'reptiles'],
752+
description: 'Favorite pets'
735753
},
736754
}
737755
}
@@ -791,11 +809,10 @@ export const createServer = () => {
791809
type: "resource_link",
792810
uri: resource.uri,
793811
name: resource.name,
794-
description: `Resource ${i + 1}: ${
795-
resource.mimeType === "text/plain"
796-
? "plaintext resource"
797-
: "binary blob resource"
798-
}`,
812+
description: `Resource ${i + 1}: ${resource.mimeType === "text/plain"
813+
? "plaintext resource"
814+
: "binary blob resource"
815+
}`,
799816
mimeType: resource.mimeType,
800817
});
801818
}
@@ -819,11 +836,57 @@ export const createServer = () => {
819836
}
820837

821838
return {
822-
content: [ backwardCompatiblecontent ],
839+
content: [backwardCompatiblecontent],
823840
structuredContent: weather
824841
};
825842
}
826843

844+
if (name === ToolName.LIST_ROOTS) {
845+
ListRootsSchema.parse(args);
846+
847+
if (!clientSupportsRoots) {
848+
return {
849+
content: [
850+
{
851+
type: "text",
852+
text: "The MCP client does not support the roots protocol.\n\n" +
853+
"This means the server cannot access information about the client's workspace directories or file system roots."
854+
}
855+
]
856+
};
857+
}
858+
859+
if (currentRoots.length === 0) {
860+
return {
861+
content: [
862+
{
863+
type: "text",
864+
text: "The client supports roots but no roots are currently configured.\n\n" +
865+
"This could mean:\n" +
866+
"1. The client hasn't provided any roots yet\n" +
867+
"2. The client provided an empty roots list\n" +
868+
"3. The roots configuration is still being loaded"
869+
}
870+
]
871+
};
872+
}
873+
874+
const rootsList = currentRoots.map((root, index) => {
875+
return `${index + 1}. ${root.name || 'Unnamed Root'}\n URI: ${root.uri}`;
876+
}).join('\n\n');
877+
878+
return {
879+
content: [
880+
{
881+
type: "text",
882+
text: `Current MCP Roots (${currentRoots.length} total):\n\n${rootsList}\n\n` +
883+
"Note: This server demonstrates the roots protocol capability but doesn't actually access files. " +
884+
"The roots are provided by the MCP client and can be used by servers that need file system access."
885+
}
886+
]
887+
};
888+
}
889+
827890
throw new Error(`Unknown tool: ${name}`);
828891
});
829892

@@ -873,6 +936,87 @@ export const createServer = () => {
873936
return {};
874937
});
875938

939+
// Roots protocol handlers
940+
server.setNotificationHandler(RootsListChangedNotificationSchema, async () => {
941+
try {
942+
// Request the updated roots list from the client
943+
const response = await server.listRoots();
944+
if (response && 'roots' in response) {
945+
currentRoots = response.roots;
946+
947+
// Log the roots update for demonstration
948+
await server.notification({
949+
method: "notifications/message",
950+
params: {
951+
level: "info",
952+
logger: "everything-server",
953+
data: `Roots updated: ${currentRoots.length} root(s) received from client`,
954+
},
955+
});
956+
}
957+
} catch (error) {
958+
await server.notification({
959+
method: "notifications/message",
960+
params: {
961+
level: "error",
962+
logger: "everything-server",
963+
data: `Failed to request roots from client: ${error instanceof Error ? error.message : String(error)}`,
964+
},
965+
});
966+
}
967+
});
968+
969+
// Handle post-initialization setup for roots
970+
server.oninitialized = async () => {
971+
clientCapabilities = server.getClientCapabilities();
972+
973+
if (clientCapabilities?.roots) {
974+
clientSupportsRoots = true;
975+
try {
976+
const response = await server.listRoots();
977+
if (response && 'roots' in response) {
978+
currentRoots = response.roots;
979+
980+
await server.notification({
981+
method: "notifications/message",
982+
params: {
983+
level: "info",
984+
logger: "everything-server",
985+
data: `Initial roots received: ${currentRoots.length} root(s) from client`,
986+
},
987+
});
988+
} else {
989+
await server.notification({
990+
method: "notifications/message",
991+
params: {
992+
level: "warning",
993+
logger: "everything-server",
994+
data: "Client returned no roots set",
995+
},
996+
});
997+
}
998+
} catch (error) {
999+
await server.notification({
1000+
method: "notifications/message",
1001+
params: {
1002+
level: "error",
1003+
logger: "everything-server",
1004+
data: `Failed to request initial roots from client: ${error instanceof Error ? error.message : String(error)}`,
1005+
},
1006+
});
1007+
}
1008+
} else {
1009+
await server.notification({
1010+
method: "notifications/message",
1011+
params: {
1012+
level: "info",
1013+
logger: "everything-server",
1014+
data: "Client does not support MCP roots protocol",
1015+
},
1016+
});
1017+
}
1018+
};
1019+
8761020
const cleanup = async () => {
8771021
if (subsUpdateInterval) clearInterval(subsUpdateInterval);
8781022
if (logsUpdateInterval) clearInterval(logsUpdateInterval);

0 commit comments

Comments
 (0)