feat(mcp): show toolsets section in MCPServerPermissions read view

This commit is contained in:
Ishaan Jaffer 2026-03-21 19:01:03 -07:00
parent 1ef54d25c1
commit a722d95493

View file

@ -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>