Skip to content

Commit f3406ca

Browse files
committed
Basic support for roots
1 parent 645f2e9 commit f3406ca

File tree

2 files changed

+112
-0
lines changed

2 files changed

+112
-0
lines changed

client/src/App.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ import {
1010
ListPromptsResultSchema,
1111
ListResourcesResultSchema,
1212
ListResourceTemplatesResultSchema,
13+
ListRootsRequestSchema,
1314
ListToolsResultSchema,
1415
ProgressNotificationSchema,
1516
ReadResourceResultSchema,
1617
Resource,
1718
ResourceTemplate,
19+
Root,
1820
ServerNotification,
1921
Tool,
2022
} from "@modelcontextprotocol/sdk/types.js";
@@ -39,6 +41,7 @@ import {
3941
Play,
4042
Send,
4143
Terminal,
44+
FolderTree,
4245
} from "lucide-react";
4346

4447
import { AnyZodObject } from "zod";
@@ -49,6 +52,7 @@ import PingTab from "./components/PingTab";
4952
import PromptsTab, { Prompt } from "./components/PromptsTab";
5053
import RequestsTab from "./components/RequestsTabs";
5154
import ResourcesTab from "./components/ResourcesTab";
55+
import RootsTab from "./components/RootsTab";
5256
import SamplingTab, { PendingRequest } from "./components/SamplingTab";
5357
import Sidebar from "./components/Sidebar";
5458
import ToolsTab from "./components/ToolsTab";
@@ -86,6 +90,7 @@ const App = () => {
8690
>([]);
8791
const [mcpClient, setMcpClient] = useState<Client | null>(null);
8892
const [notifications, setNotifications] = useState<ServerNotification[]>([]);
93+
const [roots, setRoots] = useState<Root[]>([]);
8994

9095
const [pendingSampleRequests, setPendingSampleRequests] = useState<
9196
Array<
@@ -254,6 +259,16 @@ const App = () => {
254259
setToolResult(JSON.stringify(response.toolResult, null, 2));
255260
};
256261

262+
const handleRootsChange = async () => {
263+
if (mcpClient) {
264+
try {
265+
await mcpClient.sendRootsListChanged();
266+
} catch (e) {
267+
console.error("Failed to send roots list changed notification:", e);
268+
}
269+
}
270+
};
271+
257272
const connectMcpServer = async () => {
258273
try {
259274
const client = new Client({
@@ -293,6 +308,10 @@ const App = () => {
293308
});
294309
});
295310

311+
client.setRequestHandler(ListRootsRequestSchema, async () => {
312+
return { roots };
313+
});
314+
296315
setMcpClient(client);
297316
setConnectionStatus("connected");
298317
} catch (e) {
@@ -387,6 +406,10 @@ const App = () => {
387406
</span>
388407
)}
389408
</TabsTrigger>
409+
<TabsTrigger value="roots">
410+
<FolderTree className="w-4 h-4 mr-2" />
411+
Roots
412+
</TabsTrigger>
390413
</TabsList>
391414

392415
<div className="w-full">
@@ -443,6 +466,11 @@ const App = () => {
443466
onApprove={handleApproveSampling}
444467
onReject={handleRejectSampling}
445468
/>
469+
<RootsTab
470+
roots={roots}
471+
setRoots={setRoots}
472+
onRootsChange={handleRootsChange}
473+
/>
446474
</div>
447475
</Tabs>
448476
) : (

client/src/components/RootsTab.tsx

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { Alert, AlertDescription } from "@/components/ui/alert";
2+
import { Button } from "@/components/ui/button";
3+
import { Input } from "@/components/ui/input";
4+
import { TabsContent } from "@/components/ui/tabs";
5+
import { Root } from "@modelcontextprotocol/sdk/types.js";
6+
import { Plus, Minus, Save } from "lucide-react";
7+
import { useCallback } from "react";
8+
9+
const RootsTab = ({
10+
roots,
11+
setRoots,
12+
onRootsChange,
13+
}: {
14+
roots: Root[];
15+
setRoots: React.Dispatch<React.SetStateAction<Root[]>>;
16+
onRootsChange: () => void;
17+
}) => {
18+
const addRoot = useCallback(() => {
19+
setRoots((currentRoots) => [...currentRoots, { uri: "file://", name: "" }]);
20+
}, [setRoots]);
21+
22+
const removeRoot = useCallback(
23+
(index: number) => {
24+
setRoots((currentRoots) => currentRoots.filter((_, i) => i !== index));
25+
},
26+
[setRoots],
27+
);
28+
29+
const updateRoot = useCallback(
30+
(index: number, field: keyof Root, value: string) => {
31+
setRoots((currentRoots) =>
32+
currentRoots.map((root, i) =>
33+
i === index ? { ...root, [field]: value } : root,
34+
),
35+
);
36+
},
37+
[setRoots],
38+
);
39+
40+
const handleSave = useCallback(() => {
41+
onRootsChange();
42+
}, [onRootsChange]);
43+
44+
return (
45+
<TabsContent value="roots" className="space-y-4">
46+
<Alert>
47+
<AlertDescription>
48+
Configure the root directories that the server can access
49+
</AlertDescription>
50+
</Alert>
51+
52+
{roots.map((root, index) => (
53+
<div key={index} className="flex gap-2 items-center">
54+
<Input
55+
placeholder="file:// URI"
56+
value={root.uri}
57+
onChange={(e) => updateRoot(index, "uri", e.target.value)}
58+
className="flex-1"
59+
/>
60+
<Button
61+
variant="destructive"
62+
size="sm"
63+
onClick={() => removeRoot(index)}
64+
>
65+
<Minus className="h-4 w-4" />
66+
</Button>
67+
</div>
68+
))}
69+
70+
<div className="flex gap-2">
71+
<Button variant="outline" onClick={addRoot}>
72+
<Plus className="h-4 w-4 mr-2" />
73+
Add Root
74+
</Button>
75+
<Button onClick={handleSave}>
76+
<Save className="h-4 w-4 mr-2" />
77+
Save Changes
78+
</Button>
79+
</div>
80+
</TabsContent>
81+
);
82+
};
83+
84+
export default RootsTab;

0 commit comments

Comments
 (0)