Basic support for roots
This commit is contained in:
@@ -10,11 +10,13 @@ import {
|
||||
ListPromptsResultSchema,
|
||||
ListResourcesResultSchema,
|
||||
ListResourceTemplatesResultSchema,
|
||||
ListRootsRequestSchema,
|
||||
ListToolsResultSchema,
|
||||
ProgressNotificationSchema,
|
||||
ReadResourceResultSchema,
|
||||
Resource,
|
||||
ResourceTemplate,
|
||||
Root,
|
||||
ServerNotification,
|
||||
Tool,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
@@ -39,6 +41,7 @@ import {
|
||||
Play,
|
||||
Send,
|
||||
Terminal,
|
||||
FolderTree,
|
||||
} from "lucide-react";
|
||||
|
||||
import { AnyZodObject } from "zod";
|
||||
@@ -49,6 +52,7 @@ import PingTab from "./components/PingTab";
|
||||
import PromptsTab, { Prompt } from "./components/PromptsTab";
|
||||
import RequestsTab from "./components/RequestsTabs";
|
||||
import ResourcesTab from "./components/ResourcesTab";
|
||||
import RootsTab from "./components/RootsTab";
|
||||
import SamplingTab, { PendingRequest } from "./components/SamplingTab";
|
||||
import Sidebar from "./components/Sidebar";
|
||||
import ToolsTab from "./components/ToolsTab";
|
||||
@@ -86,6 +90,7 @@ const App = () => {
|
||||
>([]);
|
||||
const [mcpClient, setMcpClient] = useState<Client | null>(null);
|
||||
const [notifications, setNotifications] = useState<ServerNotification[]>([]);
|
||||
const [roots, setRoots] = useState<Root[]>([]);
|
||||
|
||||
const [pendingSampleRequests, setPendingSampleRequests] = useState<
|
||||
Array<
|
||||
@@ -254,6 +259,16 @@ const App = () => {
|
||||
setToolResult(JSON.stringify(response.toolResult, null, 2));
|
||||
};
|
||||
|
||||
const handleRootsChange = async () => {
|
||||
if (mcpClient) {
|
||||
try {
|
||||
await mcpClient.sendRootsListChanged();
|
||||
} catch (e) {
|
||||
console.error("Failed to send roots list changed notification:", e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const connectMcpServer = async () => {
|
||||
try {
|
||||
const client = new Client({
|
||||
@@ -293,6 +308,10 @@ const App = () => {
|
||||
});
|
||||
});
|
||||
|
||||
client.setRequestHandler(ListRootsRequestSchema, async () => {
|
||||
return { roots };
|
||||
});
|
||||
|
||||
setMcpClient(client);
|
||||
setConnectionStatus("connected");
|
||||
} catch (e) {
|
||||
@@ -387,6 +406,10 @@ const App = () => {
|
||||
</span>
|
||||
)}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="roots">
|
||||
<FolderTree className="w-4 h-4 mr-2" />
|
||||
Roots
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<div className="w-full">
|
||||
@@ -443,6 +466,11 @@ const App = () => {
|
||||
onApprove={handleApproveSampling}
|
||||
onReject={handleRejectSampling}
|
||||
/>
|
||||
<RootsTab
|
||||
roots={roots}
|
||||
setRoots={setRoots}
|
||||
onRootsChange={handleRootsChange}
|
||||
/>
|
||||
</div>
|
||||
</Tabs>
|
||||
) : (
|
||||
|
||||
84
client/src/components/RootsTab.tsx
Normal file
84
client/src/components/RootsTab.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { TabsContent } from "@/components/ui/tabs";
|
||||
import { Root } from "@modelcontextprotocol/sdk/types.js";
|
||||
import { Plus, Minus, Save } from "lucide-react";
|
||||
import { useCallback } from "react";
|
||||
|
||||
const RootsTab = ({
|
||||
roots,
|
||||
setRoots,
|
||||
onRootsChange,
|
||||
}: {
|
||||
roots: Root[];
|
||||
setRoots: React.Dispatch<React.SetStateAction<Root[]>>;
|
||||
onRootsChange: () => void;
|
||||
}) => {
|
||||
const addRoot = useCallback(() => {
|
||||
setRoots((currentRoots) => [...currentRoots, { uri: "file://", name: "" }]);
|
||||
}, [setRoots]);
|
||||
|
||||
const removeRoot = useCallback(
|
||||
(index: number) => {
|
||||
setRoots((currentRoots) => currentRoots.filter((_, i) => i !== index));
|
||||
},
|
||||
[setRoots],
|
||||
);
|
||||
|
||||
const updateRoot = useCallback(
|
||||
(index: number, field: keyof Root, value: string) => {
|
||||
setRoots((currentRoots) =>
|
||||
currentRoots.map((root, i) =>
|
||||
i === index ? { ...root, [field]: value } : root,
|
||||
),
|
||||
);
|
||||
},
|
||||
[setRoots],
|
||||
);
|
||||
|
||||
const handleSave = useCallback(() => {
|
||||
onRootsChange();
|
||||
}, [onRootsChange]);
|
||||
|
||||
return (
|
||||
<TabsContent value="roots" className="space-y-4">
|
||||
<Alert>
|
||||
<AlertDescription>
|
||||
Configure the root directories that the server can access
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
{roots.map((root, index) => (
|
||||
<div key={index} className="flex gap-2 items-center">
|
||||
<Input
|
||||
placeholder="file:// URI"
|
||||
value={root.uri}
|
||||
onChange={(e) => updateRoot(index, "uri", e.target.value)}
|
||||
className="flex-1"
|
||||
/>
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
onClick={() => removeRoot(index)}
|
||||
>
|
||||
<Minus className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline" onClick={addRoot}>
|
||||
<Plus className="h-4 w-4 mr-2" />
|
||||
Add Root
|
||||
</Button>
|
||||
<Button onClick={handleSave}>
|
||||
<Save className="h-4 w-4 mr-2" />
|
||||
Save Changes
|
||||
</Button>
|
||||
</div>
|
||||
</TabsContent>
|
||||
);
|
||||
};
|
||||
|
||||
export default RootsTab;
|
||||
Reference in New Issue
Block a user