UI - v1.74.3-stable QA fixes (#12559)

* fix chat ui

* fixes for types

* ui - fix back button on MCP

* mcp - show existing groups when creating new mcps

* fix design

* fix color scheme

* fix linting

* fixes for mapped tests
This commit is contained in:
Ishaan Jaff
2025-07-12 15:31:39 -07:00
committed by GitHub
parent 7f5033d8a6
commit 00ab400500
10 changed files with 131 additions and 44 deletions
@@ -177,8 +177,8 @@ class MCPServerManager:
mcp_server_cost_info=_mcp_info.get("mcp_server_cost_info", None),
),
# Stdio-specific fields
command=mcp_server.command,
args=mcp_server.args,
command=getattr(mcp_server, 'command', None),
args=getattr(mcp_server, 'args', None) or [],
env=env_dict,
)
self.registry[mcp_server.server_id] = new_server
+6 -6
View File
@@ -852,8 +852,8 @@ class NewMCPServerRequest(LiteLLMPydanticObjectBase):
mcp_access_groups: List[str] = Field(default_factory=list)
# Stdio-specific fields
command: Optional[str] = None
args: Optional[List[str]] = None
env: Optional[Dict[str, str]] = None
args: List[str] = Field(default_factory=list)
env: Dict[str, str] = Field(default_factory=dict)
@model_validator(mode="before")
@classmethod
@@ -884,8 +884,8 @@ class UpdateMCPServerRequest(LiteLLMPydanticObjectBase):
mcp_access_groups: List[str] = Field(default_factory=list)
# Stdio-specific fields
command: Optional[str] = None
args: Optional[List[str]] = None
env: Optional[Dict[str, str]] = None
args: List[str] = Field(default_factory=list)
env: Dict[str, str] = Field(default_factory=dict)
@model_validator(mode="before")
@classmethod
@@ -923,8 +923,8 @@ class LiteLLM_MCPServerTable(LiteLLMPydanticObjectBase):
mcp_info: Optional[MCPInfo] = None
# Stdio-specific fields
command: Optional[str] = None
args: Optional[List[str]] = None
env: Optional[Dict[str, str]] = None
args: List[str] = Field(default_factory=list)
env: Dict[str, str] = Field(default_factory=dict)
class NewUserRequestTeam(LiteLLMPydanticObjectBase):
@@ -247,9 +247,9 @@ if MCP_AVAILABLE:
updated_at=datetime.now(),
mcp_info=_server_config.mcp_info,
# Stdio-specific fields
command=_server_config.command,
args=_server_config.args,
env=_server_config.env,
command=getattr(_server_config, 'command', None),
args=getattr(_server_config, 'args', None) or [],
env=getattr(_server_config, 'env', None) or {},
)
)
@@ -271,9 +271,9 @@ if MCP_AVAILABLE:
mcp_info=server.mcp_info,
teams=cast(List[Dict[str, str | None]], server_to_teams_map.get(server.server_id, [])),
# Stdio-specific fields
command=server.command,
args=server.args,
env=server.env,
command=getattr(server, 'command', None),
args=getattr(server, 'args', None) or [],
env=getattr(server, 'env', None) or {},
)
for server in LIST_MCP_SERVERS
]
@@ -617,9 +617,9 @@ const ChatUI: React.FC<ChatUIProps> = ({
<Card className="w-full rounded-xl shadow-md overflow-hidden">
<div className="flex h-[80vh] w-full gap-4">
{/* Left Sidebar with Controls */}
<div className="w-1/4 p-4 bg-gray-50">
<div className="w-1/4 p-4 bg-gray-50 overflow-y-auto">
<Title className="text-xl font-semibold mb-6 mt-2">Configurations</Title>
<div className="space-y-6">
<div className="space-y-4">
<div>
<Text className="font-medium block mb-2 text-gray-700 flex items-center">
<KeyOutlined className="mr-2" /> API Key Source
@@ -799,7 +799,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
/>
</div>
<div className="space-y-2 mt-6">
<div className="space-y-2 mt-4">
<TremorButton
onClick={clearChatHistory}
className="w-full bg-gray-100 hover:bg-gray-200 text-gray-700 border-gray-300"
@@ -51,12 +51,14 @@ const MCPServerSelector: React.FC<MCPServerSelectorProps> = ({
...accessGroups.map(group => ({
label: group,
value: group,
isAccessGroup: true
isAccessGroup: true,
searchText: `${group} Access Group`
})),
...mcpServers.map(server => ({
label: `${server.alias || server.server_id} (${server.server_id})`,
value: server.server_id,
isAccessGroup: false
isAccessGroup: false,
searchText: `${server.alias || server.server_id} ${server.server_id} MCP Server`
}))
];
@@ -82,29 +84,39 @@ const MCPServerSelector: React.FC<MCPServerSelectorProps> = ({
value={selectedValues}
loading={loading}
className={className}
optionFilterProp="label"
showSearch
style={{ width: '100%' }}
disabled={disabled}
filterOption={(input, option) => {
const searchText = options.find(opt => opt.value === option?.value)?.searchText || '';
return searchText.toLowerCase().includes(input.toLowerCase());
}}
>
{options.map(opt => (
<Select.Option
key={opt.value}
value={opt.value}
label={opt.label}
>
{opt.isAccessGroup && (
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<span style={{
display: 'inline-block',
width: 10,
height: 10,
width: 8,
height: 8,
borderRadius: '50%',
background: '#1890ff',
marginRight: 8,
verticalAlign: 'middle',
background: opt.isAccessGroup ? '#52c41a' : '#1890ff',
flexShrink: 0,
}} />
)}
{opt.label}
{opt.isAccessGroup && <span style={{ color: '#1890ff', marginLeft: 8 }}>(Access Group)</span>}
<span style={{ flex: 1 }}>{opt.label}</span>
<span style={{
color: opt.isAccessGroup ? '#52c41a' : '#1890ff',
fontSize: '12px',
fontWeight: 500,
opacity: 0.8
}}>
{opt.isAccessGroup ? 'Access Group' : 'MCP Server'}
</span>
</div>
</Select.Option>
))}
</Select>
@@ -18,6 +18,7 @@ interface CreateMCPServerProps {
onCreateSuccess: (newMcpServer: MCPServer) => void
isModalVisible: boolean
setModalVisible: (visible: boolean) => void
availableAccessGroups: string[]
}
const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
@@ -26,14 +27,15 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
onCreateSuccess,
isModalVisible,
setModalVisible,
availableAccessGroups,
}) => {
const [form] = Form.useForm()
const [isLoading, setIsLoading] = useState(false)
const [costConfig, setCostConfig] = useState<MCPServerCostInfo>({})
const [mcpAccessGroups, setMcpAccessGroups] = useState<string[]>([])
const [formValues, setFormValues] = useState<Record<string, any>>({})
const [tools, setTools] = useState<any[]>([])
const [transportType, setTransportType] = useState<string>("sse")
const [searchValue, setSearchValue] = useState<string>("")
const handleCreate = async (formValues: Record<string, any>) => {
setIsLoading(true)
@@ -132,6 +134,35 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
}
}
// Generate options with existing groups and potential new group
const getAccessGroupOptions = () => {
const existingOptions = availableAccessGroups.map((group: string) => ({
value: group,
label: (
<div className="flex items-center gap-2">
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
<span className="font-medium">{group}</span>
</div>
),
}))
// If search value doesn't match any existing group and is not empty, add "create new group" option
if (searchValue && !availableAccessGroups.some(group => group.toLowerCase().includes(searchValue.toLowerCase()))) {
existingOptions.push({
value: searchValue,
label: (
<div className="flex items-center gap-2">
<div className="w-2 h-2 bg-blue-500 rounded-full"></div>
<span className="font-medium">{searchValue}</span>
<span className="text-gray-400 text-xs ml-1">create new group</span>
</div>
),
})
}
return existingOptions
}
// rendering
if (!isAdminRole(userRole)) {
return null
@@ -304,12 +335,13 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
mode="tags"
showSearch
placeholder="Select existing groups or type to create new ones"
optionFilterProp="children"
optionFilterProp="value"
filterOption={(input, option) =>
(option?.value ?? '').toLowerCase().includes(input.toLowerCase())
}
onSearch={(value) => setSearchValue(value)}
tokenSeparators={[","]}
options={mcpAccessGroups.map((group) => ({
value: group,
label: group,
}))}
options={getAccessGroupOptions()}
maxTagCount="responsive"
allowClear
/>
@@ -11,13 +11,15 @@ interface MCPServerEditProps {
accessToken: string | null;
onCancel: () => void;
onSuccess: (server: MCPServer) => void;
availableAccessGroups: string[];
}
const MCPServerEdit: React.FC<MCPServerEditProps> = ({ mcpServer, accessToken, onCancel, onSuccess }) => {
const MCPServerEdit: React.FC<MCPServerEditProps> = ({ mcpServer, accessToken, onCancel, onSuccess, availableAccessGroups }) => {
const [form] = Form.useForm();
const [costConfig, setCostConfig] = useState<MCPServerCostInfo>({});
const [tools, setTools] = useState<any[]>([]);
const [isLoadingTools, setIsLoadingTools] = useState(false);
const [searchValue, setSearchValue] = useState<string>("");
// Initialize cost config from existing server data
useEffect(() => {
@@ -75,6 +77,35 @@ const MCPServerEdit: React.FC<MCPServerEditProps> = ({ mcpServer, accessToken, o
}
};
// Generate options with existing groups and potential new group
const getAccessGroupOptions = () => {
const existingOptions = availableAccessGroups.map((group: string) => ({
value: group,
label: (
<div className="flex items-center gap-2">
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
<span className="font-medium">{group}</span>
</div>
),
}))
// If search value doesn't match any existing group and is not empty, add "create new group" option
if (searchValue && !availableAccessGroups.some(group => group.toLowerCase().includes(searchValue.toLowerCase()))) {
existingOptions.push({
value: searchValue,
label: (
<div className="flex items-center gap-2">
<div className="w-2 h-2 bg-blue-500 rounded-full"></div>
<span className="font-medium">{searchValue}</span>
<span className="text-gray-400 text-xs ml-1">create new group</span>
</div>
),
})
}
return existingOptions
}
const handleSave = async (values: Record<string, any>) => {
if (!accessToken) return;
try {
@@ -160,8 +191,15 @@ const MCPServerEdit: React.FC<MCPServerEditProps> = ({ mcpServer, accessToken, o
<Select
mode="tags"
style={{ width: '100%' }}
showSearch
placeholder="Add or select access groups"
tokenSeparators={[',']}
optionFilterProp="value"
filterOption={(input, option) =>
(option?.value ?? '').toLowerCase().includes(input.toLowerCase())
}
onSearch={(value) => setSearchValue(value)}
options={getAccessGroupOptions()}
// Ensure value is always an array of strings
getPopupContainer={trigger => trigger.parentNode}
/>
@@ -17,6 +17,7 @@ interface MCPServerViewProps {
accessToken: string | null
userRole: string | null
userID: string | null
availableAccessGroups: string[]
}
export const MCPServerView: React.FC<MCPServerViewProps> = ({
@@ -27,6 +28,7 @@ export const MCPServerView: React.FC<MCPServerViewProps> = ({
accessToken,
userRole,
userID,
availableAccessGroups,
}) => {
const [editing, setEditing] = useState(isEditing)
const [showFullUrl, setShowFullUrl] = useState(false)
@@ -47,7 +49,7 @@ export const MCPServerView: React.FC<MCPServerViewProps> = ({
<div className="p-4 max-w-full">
<div className="flex justify-between items-center mb-6">
<div>
<Button icon={ArrowLeftIcon} variant="light" className="mb-4">
<Button icon={ArrowLeftIcon} variant="light" className="mb-4" onClick={onBack}>
Back to All Servers
</Button>
<Title>{mcpServer.alias}</Title>
@@ -131,6 +133,7 @@ export const MCPServerView: React.FC<MCPServerViewProps> = ({
accessToken={accessToken}
onCancel={() => setEditing(false)}
onSuccess={handleSuccess}
availableAccessGroups={availableAccessGroups}
/>
) : (
<div className="space-y-4">
@@ -80,7 +80,7 @@ const MCPServers: React.FC<MCPServerProps> = ({ accessToken, userRole, userID })
// Get unique MCP access groups from all servers
const uniqueMcpAccessGroups = React.useMemo(() => {
if (!mcpServers) return []
return Array.from(new Set(mcpServers.flatMap((server) => server.mcp_access_groups)))
return Array.from(new Set(mcpServers.flatMap((server) => server.mcp_access_groups).filter((group): group is string => group != null)))
}, [mcpServers])
// Handle team filter change
@@ -198,6 +198,7 @@ const MCPServers: React.FC<MCPServerProps> = ({ accessToken, userRole, userID })
accessToken={accessToken}
userID={userID}
userRole={userRole}
availableAccessGroups={uniqueMcpAccessGroups}
/>
) : (
<div className="w-full h-full">
@@ -281,6 +282,7 @@ const MCPServers: React.FC<MCPServerProps> = ({ accessToken, userRole, userID })
onCreateSuccess={handleCreateSuccess}
isModalVisible={isModalVisible}
setModalVisible={setModalVisible}
availableAccessGroups={uniqueMcpAccessGroups}
/>
<Title>MCP Servers</Title>
<Text className="text-tremor-content mt-2">Configure and manage your MCP servers</Text>
@@ -80,9 +80,9 @@ export function MCPServerPermissions({
return (
<div className="space-y-3">
<div className="flex items-center gap-2">
<ServerIcon className="h-4 w-4 text-green-600" />
<ServerIcon className="h-4 w-4 text-gray-600" />
<Text className="font-semibold text-gray-900">MCP Servers</Text>
<Badge color="green" size="xs">
<Badge color="gray" size="xs">
{totalCount}
</Badge>
</div>
@@ -92,7 +92,7 @@ export function MCPServerPermissions({
item.type === 'server' ? (
<Tooltip key={index} title={`Full ID: ${item.value}`} placement="top">
<div
className="inline-flex items-center px-3 py-1.5 rounded-lg bg-green-50 border border-green-200 text-green-800 text-sm font-medium cursor-help"
className="inline-flex items-center px-3 py-1.5 rounded-lg bg-blue-50 border border-blue-200 text-blue-800 text-sm font-medium cursor-help"
>
{getMCPServerDisplayName(item.value)}
</div>
@@ -100,10 +100,10 @@ export function MCPServerPermissions({
) : (
<div
key={index}
className="inline-flex items-center px-3 py-1.5 rounded-lg bg-blue-50 border border-blue-200 text-blue-800 text-sm font-medium"
className="inline-flex items-center px-3 py-1.5 rounded-lg bg-green-50 border border-green-200 text-green-800 text-sm font-medium"
>
<span className="inline-block w-2 h-2 bg-blue-500 rounded-full mr-2"></span>
{getAccessGroupDisplayName(item.value)} <span className="ml-1 text-xs text-blue-500">(Access Group)</span>
<span className="inline-block w-2 h-2 bg-green-500 rounded-full mr-2"></span>
{getAccessGroupDisplayName(item.value)} <span className="ml-1 text-xs text-green-500">(Access Group)</span>
</div>
)
))}