Basic support for roots
This commit is contained in:
@@ -10,11 +10,13 @@ import {
|
|||||||
ListPromptsResultSchema,
|
ListPromptsResultSchema,
|
||||||
ListResourcesResultSchema,
|
ListResourcesResultSchema,
|
||||||
ListResourceTemplatesResultSchema,
|
ListResourceTemplatesResultSchema,
|
||||||
|
ListRootsRequestSchema,
|
||||||
ListToolsResultSchema,
|
ListToolsResultSchema,
|
||||||
ProgressNotificationSchema,
|
ProgressNotificationSchema,
|
||||||
ReadResourceResultSchema,
|
ReadResourceResultSchema,
|
||||||
Resource,
|
Resource,
|
||||||
ResourceTemplate,
|
ResourceTemplate,
|
||||||
|
Root,
|
||||||
ServerNotification,
|
ServerNotification,
|
||||||
Tool,
|
Tool,
|
||||||
} from "@modelcontextprotocol/sdk/types.js";
|
} from "@modelcontextprotocol/sdk/types.js";
|
||||||
@@ -39,6 +41,7 @@ import {
|
|||||||
Play,
|
Play,
|
||||||
Send,
|
Send,
|
||||||
Terminal,
|
Terminal,
|
||||||
|
FolderTree,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
||||||
import { AnyZodObject } from "zod";
|
import { AnyZodObject } from "zod";
|
||||||
@@ -49,6 +52,7 @@ import PingTab from "./components/PingTab";
|
|||||||
import PromptsTab, { Prompt } from "./components/PromptsTab";
|
import PromptsTab, { Prompt } from "./components/PromptsTab";
|
||||||
import RequestsTab from "./components/RequestsTabs";
|
import RequestsTab from "./components/RequestsTabs";
|
||||||
import ResourcesTab from "./components/ResourcesTab";
|
import ResourcesTab from "./components/ResourcesTab";
|
||||||
|
import RootsTab from "./components/RootsTab";
|
||||||
import SamplingTab, { PendingRequest } from "./components/SamplingTab";
|
import SamplingTab, { PendingRequest } from "./components/SamplingTab";
|
||||||
import Sidebar from "./components/Sidebar";
|
import Sidebar from "./components/Sidebar";
|
||||||
import ToolsTab from "./components/ToolsTab";
|
import ToolsTab from "./components/ToolsTab";
|
||||||
@@ -86,6 +90,7 @@ const App = () => {
|
|||||||
>([]);
|
>([]);
|
||||||
const [mcpClient, setMcpClient] = useState<Client | null>(null);
|
const [mcpClient, setMcpClient] = useState<Client | null>(null);
|
||||||
const [notifications, setNotifications] = useState<ServerNotification[]>([]);
|
const [notifications, setNotifications] = useState<ServerNotification[]>([]);
|
||||||
|
const [roots, setRoots] = useState<Root[]>([]);
|
||||||
|
|
||||||
const [pendingSampleRequests, setPendingSampleRequests] = useState<
|
const [pendingSampleRequests, setPendingSampleRequests] = useState<
|
||||||
Array<
|
Array<
|
||||||
@@ -254,6 +259,16 @@ const App = () => {
|
|||||||
setToolResult(JSON.stringify(response.toolResult, null, 2));
|
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 () => {
|
const connectMcpServer = async () => {
|
||||||
try {
|
try {
|
||||||
const client = new Client({
|
const client = new Client({
|
||||||
@@ -293,6 +308,10 @@ const App = () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
client.setRequestHandler(ListRootsRequestSchema, async () => {
|
||||||
|
return { roots };
|
||||||
|
});
|
||||||
|
|
||||||
setMcpClient(client);
|
setMcpClient(client);
|
||||||
setConnectionStatus("connected");
|
setConnectionStatus("connected");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -387,6 +406,10 @@ const App = () => {
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="roots">
|
||||||
|
<FolderTree className="w-4 h-4 mr-2" />
|
||||||
|
Roots
|
||||||
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
@@ -443,6 +466,11 @@ const App = () => {
|
|||||||
onApprove={handleApproveSampling}
|
onApprove={handleApproveSampling}
|
||||||
onReject={handleRejectSampling}
|
onReject={handleRejectSampling}
|
||||||
/>
|
/>
|
||||||
|
<RootsTab
|
||||||
|
roots={roots}
|
||||||
|
setRoots={setRoots}
|
||||||
|
onRootsChange={handleRootsChange}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Tabs>
|
</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