mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-21 00:21:49 +00:00
feat(mcp): show toolsets section in MCPServerPermissions read view
This commit is contained in:
parent
1ef54d25c1
commit
a722d95493
1 changed files with 98 additions and 30 deletions
|
|
@ -2,25 +2,28 @@ import React, { useState, useEffect } from "react";
|
|||
import { Text, Badge } from "@tremor/react";
|
||||
import { ServerIcon, ChevronDownIcon, ChevronRightIcon } from "@heroicons/react/outline";
|
||||
import { Tooltip } from "antd";
|
||||
import { fetchMCPServers } from "../networking";
|
||||
import { MCPServer } from "../mcp_tools/types";
|
||||
import { fetchMCPServers, fetchMCPToolsets } from "../networking";
|
||||
import { MCPServer, MCPToolset } from "../mcp_tools/types";
|
||||
|
||||
interface MCPServerPermissionsProps {
|
||||
mcpServers: string[];
|
||||
mcpAccessGroups?: string[];
|
||||
mcpToolPermissions?: Record<string, string[]>;
|
||||
mcpToolsets?: string[];
|
||||
accessToken?: string | null;
|
||||
}
|
||||
|
||||
export function MCPServerPermissions({
|
||||
mcpServers,
|
||||
mcpAccessGroups = [],
|
||||
export function MCPServerPermissions({
|
||||
mcpServers,
|
||||
mcpAccessGroups = [],
|
||||
mcpToolPermissions = {},
|
||||
accessToken
|
||||
mcpToolsets = [],
|
||||
accessToken
|
||||
}: MCPServerPermissionsProps) {
|
||||
const [mcpServerDetails, setMCPServerDetails] = useState<MCPServer[]>([]);
|
||||
const [accessGroupNames, setAccessGroupNames] = useState<string[]>([]);
|
||||
const [toolsetDetails, setToolsetDetails] = useState<MCPToolset[]>([]);
|
||||
const [expandedServers, setExpandedServers] = useState<Set<string>>(new Set());
|
||||
const [expandedToolsets, setExpandedToolsets] = useState<Set<string>>(new Set());
|
||||
|
||||
const toggleServerExpansion = (serverId: string) => {
|
||||
setExpandedServers((prev) => {
|
||||
|
|
@ -34,6 +37,18 @@ export function MCPServerPermissions({
|
|||
});
|
||||
};
|
||||
|
||||
const toggleToolsetExpansion = (toolsetId: string) => {
|
||||
setExpandedToolsets((prev) => {
|
||||
const newSet = new Set(prev);
|
||||
if (newSet.has(toolsetId)) {
|
||||
newSet.delete(toolsetId);
|
||||
} else {
|
||||
newSet.add(toolsetId);
|
||||
}
|
||||
return newSet;
|
||||
});
|
||||
};
|
||||
|
||||
// Fetch MCP server details when component mounts
|
||||
useEffect(() => {
|
||||
const fetchMCPServerDetails = async () => {
|
||||
|
|
@ -53,20 +68,23 @@ export function MCPServerPermissions({
|
|||
fetchMCPServerDetails();
|
||||
}, [accessToken, mcpServers.length]);
|
||||
|
||||
// Fetch MCP access group names
|
||||
// Fetch toolset details
|
||||
useEffect(() => {
|
||||
const fetchGroups = async () => {
|
||||
if (accessToken && mcpAccessGroups.length > 0) {
|
||||
const fetchToolsets = async () => {
|
||||
if (accessToken && mcpToolsets.length > 0) {
|
||||
try {
|
||||
const groups = await import("../networking").then((m) => m.fetchMCPAccessGroups(accessToken));
|
||||
setAccessGroupNames(Array.isArray(groups) ? groups : groups.data || []);
|
||||
const all = await fetchMCPToolsets(accessToken);
|
||||
const filtered = Array.isArray(all)
|
||||
? all.filter((t: MCPToolset) => mcpToolsets.includes(t.toolset_id))
|
||||
: [];
|
||||
setToolsetDetails(filtered);
|
||||
} catch (error) {
|
||||
console.error("Error fetching MCP access groups:", error);
|
||||
console.error("Error fetching toolsets:", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
fetchGroups();
|
||||
}, [accessToken, mcpAccessGroups.length]);
|
||||
fetchToolsets();
|
||||
}, [accessToken, mcpToolsets.length]);
|
||||
|
||||
// Function to get display name for MCP server
|
||||
const getMCPServerDisplayName = (serverId: string) => {
|
||||
|
|
@ -78,17 +96,12 @@ export function MCPServerPermissions({
|
|||
return serverId;
|
||||
};
|
||||
|
||||
// Function to get display name for access group
|
||||
const getAccessGroupDisplayName = (group: string) => {
|
||||
return group;
|
||||
};
|
||||
|
||||
// Merge servers and access groups into one list
|
||||
const mergedItems = [
|
||||
...mcpServers.map((server) => ({ type: "server", value: server })),
|
||||
...mcpAccessGroups.map((group) => ({ type: "accessGroup", value: group })),
|
||||
];
|
||||
const totalCount = mergedItems.length;
|
||||
const totalCount = mergedItems.length + mcpToolsets.length;
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
|
|
@ -99,21 +112,21 @@ export function MCPServerPermissions({
|
|||
{totalCount}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
|
||||
{totalCount > 0 ? (
|
||||
<div className="max-h-[400px] overflow-y-auto space-y-2 pr-1">
|
||||
{mergedItems.map((item, index) => {
|
||||
const toolsForServer = item.type === "server" ? mcpToolPermissions[item.value] : undefined;
|
||||
const hasToolRestrictions = toolsForServer && toolsForServer.length > 0;
|
||||
const isExpanded = expandedServers.has(item.value);
|
||||
|
||||
|
||||
return (
|
||||
<div key={index} className="space-y-2">
|
||||
<div
|
||||
<div
|
||||
onClick={() => hasToolRestrictions && toggleServerExpansion(item.value)}
|
||||
className={`flex items-center gap-3 py-2 px-3 rounded-lg border border-gray-200 transition-all ${
|
||||
hasToolRestrictions
|
||||
? 'cursor-pointer hover:bg-gray-50 hover:border-gray-300'
|
||||
hasToolRestrictions
|
||||
? 'cursor-pointer hover:bg-gray-50 hover:border-gray-300'
|
||||
: 'bg-white'
|
||||
}`}
|
||||
>
|
||||
|
|
@ -128,14 +141,14 @@ export function MCPServerPermissions({
|
|||
) : (
|
||||
<div className="inline-flex items-center gap-2 min-w-0">
|
||||
<span className="inline-block w-1.5 h-1.5 bg-green-500 rounded-full flex-shrink-0"></span>
|
||||
<span className="text-sm font-medium text-gray-900 truncate">{getAccessGroupDisplayName(item.value)}</span>
|
||||
<span className="text-sm font-medium text-gray-900 truncate">{item.value}</span>
|
||||
<span className="ml-1 px-1.5 py-0.5 text-[9px] font-semibold text-green-600 bg-green-50 border border-green-200 rounded uppercase tracking-wide flex-shrink-0">
|
||||
Group
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
{hasToolRestrictions && (
|
||||
<div className="flex items-center gap-1 flex-shrink-0 whitespace-nowrap">
|
||||
<span className="text-xs font-medium text-gray-600">{toolsForServer.length}</span>
|
||||
|
|
@ -148,7 +161,7 @@ export function MCPServerPermissions({
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
{/* Show tool permissions if expanded */}
|
||||
{hasToolRestrictions && isExpanded && (
|
||||
<div className="ml-4 pl-4 border-l-2 border-blue-200 pb-1">
|
||||
|
|
@ -167,11 +180,66 @@ export function MCPServerPermissions({
|
|||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* Toolsets section */}
|
||||
{mcpToolsets.length > 0 && mcpToolsets.map((toolsetId, index) => {
|
||||
const detail = toolsetDetails.find((t) => t.toolset_id === toolsetId);
|
||||
const isExpanded = expandedToolsets.has(toolsetId);
|
||||
const toolCount = detail?.tools.length ?? 0;
|
||||
|
||||
return (
|
||||
<div key={`toolset-${index}`} className="space-y-2">
|
||||
<div
|
||||
onClick={() => toolCount > 0 && toggleToolsetExpansion(toolsetId)}
|
||||
className={`flex items-center gap-3 py-2 px-3 rounded-lg border border-purple-200 transition-all ${
|
||||
toolCount > 0 ? 'cursor-pointer hover:bg-purple-50 hover:border-purple-300' : 'bg-white'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-2 flex-1 min-w-0">
|
||||
<span className="inline-block w-1.5 h-1.5 bg-purple-500 rounded-full flex-shrink-0"></span>
|
||||
<span className="text-sm font-medium text-gray-900 truncate">
|
||||
{detail?.toolset_name ?? toolsetId}
|
||||
</span>
|
||||
<span className="ml-1 px-1.5 py-0.5 text-[9px] font-semibold text-purple-600 bg-purple-50 border border-purple-200 rounded uppercase tracking-wide flex-shrink-0">
|
||||
Toolset
|
||||
</span>
|
||||
</div>
|
||||
{toolCount > 0 && (
|
||||
<div className="flex items-center gap-1 flex-shrink-0 whitespace-nowrap">
|
||||
<span className="text-xs font-medium text-gray-600">{toolCount}</span>
|
||||
<span className="text-xs text-gray-500">{toolCount === 1 ? "tool" : "tools"}</span>
|
||||
{isExpanded ? (
|
||||
<ChevronDownIcon className="h-3.5 w-3.5 text-gray-400 ml-0.5" />
|
||||
) : (
|
||||
<ChevronRightIcon className="h-3.5 w-3.5 text-gray-400 ml-0.5" />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{toolCount > 0 && isExpanded && detail && (
|
||||
<div className="ml-4 pl-4 border-l-2 border-purple-200 pb-1">
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{detail.tools.map((tool, toolIndex) => (
|
||||
<span
|
||||
key={toolIndex}
|
||||
className="inline-flex items-center px-2.5 py-1 rounded-lg bg-purple-50 border border-purple-200 text-purple-800 text-xs font-medium"
|
||||
>
|
||||
<span className="text-purple-400 mr-1 text-[10px]">{tool.server_id.slice(0, 6)}…</span>
|
||||
{tool.tool_name}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-2 px-3 py-2 rounded-lg bg-gray-50 border border-gray-200">
|
||||
<ServerIcon className="h-4 w-4 text-gray-400" />
|
||||
<Text className="text-gray-500 text-sm">No MCP servers or access groups configured</Text>
|
||||
<Text className="text-gray-500 text-sm">No MCP servers, access groups, or toolsets configured</Text>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue