diff --git a/ui/litellm-dashboard/src/components/key_edit_view.tsx b/ui/litellm-dashboard/src/components/key_edit_view.tsx
index 959ce0362f2..cd4f32567f3 100644
--- a/ui/litellm-dashboard/src/components/key_edit_view.tsx
+++ b/ui/litellm-dashboard/src/components/key_edit_view.tsx
@@ -6,6 +6,7 @@ import { fetchTeamModels } from "../components/create_key_button";
import { modelAvailableCall } from "./networking";
import NumericalInput from "./shared/numerical_input";
import VectorStoreSelector from "./vector_store_management/VectorStoreSelector";
+import MCPServerSelector from "./mcp_server_management/MCPServerSelector";
interface KeyEditViewProps {
keyData: KeyResponse;
@@ -98,7 +99,8 @@ export function KeyEditView({
budget_duration: getBudgetDuration(keyData.budget_duration),
metadata: keyData.metadata ? JSON.stringify(keyData.metadata, null, 2) : "",
guardrails: keyData.metadata?.guardrails || [],
- vector_stores: keyData.object_permission?.vector_stores || []
+ vector_stores: keyData.object_permission?.vector_stores || [],
+ mcp_servers: keyData.object_permission?.mcp_servers || []
};
return (
@@ -180,6 +182,15 @@ export function KeyEditView({
/>
+
+ form.setFieldValue('mcp_servers', values)}
+ value={form.getFieldValue('mcp_servers')}
+ accessToken={accessToken || ""}
+ placeholder="Select MCP servers"
+ />
+
+
diff --git a/ui/litellm-dashboard/src/components/key_info_view.tsx b/ui/litellm-dashboard/src/components/key_info_view.tsx
index 087d60d20ec..1326d37d708 100644
--- a/ui/litellm-dashboard/src/components/key_info_view.tsx
+++ b/ui/litellm-dashboard/src/components/key_info_view.tsx
@@ -76,6 +76,15 @@ export default function KeyInfoView({ keyId, onClose, keyData, accessToken, user
delete formValues.vector_stores;
}
+ if (formValues.mcp_servers !== undefined) {
+ formValues.object_permission = {
+ ...keyData.object_permission,
+ mcp_servers: formValues.mcp_servers || []
+ };
+ // Remove mcp_servers from the top level as it should be in object_permission
+ delete formValues.mcp_servers;
+ }
+
// Convert metadata back to an object if it exists and is a string
if (formValues.metadata && typeof formValues.metadata === "string") {
try {
diff --git a/ui/litellm-dashboard/src/components/mcp_server_management/MCPServerSelector.tsx b/ui/litellm-dashboard/src/components/mcp_server_management/MCPServerSelector.tsx
new file mode 100644
index 00000000000..a8ddda2d257
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/mcp_server_management/MCPServerSelector.tsx
@@ -0,0 +1,74 @@
+import React, { useEffect, useState } from 'react';
+import { Select } from 'antd';
+import { fetchMCPServers } from '../networking';
+import { MCPServer } from '../mcp_tools/types';
+
+
+interface MCPServerSelectorProps {
+ onChange: (selectedMCPServers: string[]) => void;
+ value?: string[];
+ className?: string;
+ accessToken: string;
+ placeholder?: string;
+ disabled?: boolean;
+}
+
+const MCPServerSelector: React.FC = ({
+ onChange,
+ value,
+ className,
+ accessToken,
+ placeholder = "Select MCP servers",
+ disabled = false
+}) => {
+ const [mcpServers, setMCPServers] = useState([]);
+ const [loading, setLoading] = useState(false);
+
+ useEffect(() => {
+ const fetchMCPServerList = async () => {
+ if (!accessToken) return;
+
+ setLoading(true);
+ try {
+ const response = await fetchMCPServers(accessToken);
+ if (response && Array.isArray(response)) {
+ // Direct array response
+ setMCPServers(response);
+ } else if (response.data && Array.isArray(response.data)) {
+ // Response with data wrapper
+ setMCPServers(response.data);
+ }
+ } catch (error) {
+ console.error("Error fetching MCP servers:", error);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchMCPServerList();
+ }, [accessToken]);
+
+ return (
+
+
+ );
+};
+
+export default MCPServerSelector;
\ No newline at end of file
diff --git a/ui/litellm-dashboard/src/components/object_permissions_view.tsx b/ui/litellm-dashboard/src/components/object_permissions_view.tsx
index e8143af5aa4..16ff13d0e63 100644
--- a/ui/litellm-dashboard/src/components/object_permissions_view.tsx
+++ b/ui/litellm-dashboard/src/components/object_permissions_view.tsx
@@ -1,12 +1,7 @@
-import React, { useState, useEffect } from "react";
-import { Card, Text, Badge } from "@tremor/react";
-import { ServerIcon, DatabaseIcon } from "@heroicons/react/outline";
-import { vectorStoreListCall } from "./networking";
-
-interface VectorStoreDetails {
- vector_store_id: string;
- vector_store_name?: string;
-}
+import React from "react";
+import { Text } from "@tremor/react";
+import VectorStorePermissions from "./permissions/VectorStorePermissions";
+import MCPServerPermissions from "./permissions/MCPServerPermissions";
interface ObjectPermission {
object_permission_id: string;
@@ -29,97 +24,17 @@ export function ObjectPermissionsView({
}: ObjectPermissionsViewProps) {
const vectorStores = objectPermission?.vector_stores || [];
const mcpServers = objectPermission?.mcp_servers || [];
- const [vectorStoreDetails, setVectorStoreDetails] = useState([]);
-
- // Fetch vector store details when component mounts
- useEffect(() => {
- const fetchVectorStores = async () => {
- if (!accessToken || vectorStores.length === 0) return;
-
- try {
- const response = await vectorStoreListCall(accessToken);
- if (response.data) {
- setVectorStoreDetails(response.data.map((store: any) => ({
- vector_store_id: store.vector_store_id,
- vector_store_name: store.vector_store_name
- })));
- }
- } catch (error) {
- console.error("Error fetching vector stores:", error);
- }
- };
-
- fetchVectorStores();
- }, [accessToken, vectorStores.length]);
-
- // Function to get display name for vector store
- const getVectorStoreDisplayName = (storeId: string) => {
- const storeDetail = vectorStoreDetails.find(store => store.vector_store_id === storeId);
- if (storeDetail) {
- return `${storeDetail.vector_store_name || storeDetail.vector_store_id} (${storeDetail.vector_store_id})`;
- }
- return storeId;
- };
const content = (
- {/* Vector Stores Section */}
-
-
-
- Vector Stores
-
- {vectorStores.length}
-
-
-
- {vectorStores.length > 0 ? (
-
- {vectorStores.map((store, index) => (
-
- {getVectorStoreDisplayName(store)}
-
- ))}
-
- ) : (
-
-
- No vector stores configured
-
- )}
-
-
- {/* MCP Servers Section */}
- {/*
-
-
- MCP Servers
-
- {mcpServers.length}
-
-
-
- {mcpServers.length > 0 ? (
-
- {mcpServers.map((server, index) => (
-
- {server}
-
- ))}
-
- ) : (
-
-
- No MCP servers configured
-
- )}
-
*/}
+
+
);
@@ -130,7 +45,7 @@ export function ObjectPermissionsView({
Object Permissions
- Access control for Vector Stores
+ Access control for Vector Stores and MCP Servers
diff --git a/ui/litellm-dashboard/src/components/permissions/MCPServerPermissions.tsx b/ui/litellm-dashboard/src/components/permissions/MCPServerPermissions.tsx
new file mode 100644
index 00000000000..2d85a9b3f11
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/permissions/MCPServerPermissions.tsx
@@ -0,0 +1,86 @@
+import React, { useState, useEffect } from "react";
+import { Text, Badge } from "@tremor/react";
+import { ServerIcon } from "@heroicons/react/outline";
+import { Tooltip } from "antd";
+import { fetchMCPServers } from "../networking";
+import { MCPServer } from '../mcp_tools/types';
+
+interface MCPServerPermissionsProps {
+ mcpServers: string[];
+ accessToken?: string | null;
+}
+
+export function MCPServerPermissions({
+ mcpServers,
+ accessToken
+}: MCPServerPermissionsProps) {
+ const [mcpServerDetails, setMCPServerDetails] = useState([]);
+
+ // Fetch MCP server details when component mounts
+ useEffect(() => {
+ const fetchMCPServerDetails = async () => {
+ if (!accessToken || mcpServers.length === 0) return;
+
+ try {
+ const response = await fetchMCPServers(accessToken);
+ if (response && Array.isArray(response)) {
+ // Direct array response
+ setMCPServerDetails(response);
+ } else if (response.data && Array.isArray(response.data)) {
+ // Response with data wrapper
+ setMCPServerDetails(response.data);
+ }
+ } catch (error) {
+ console.error("Error fetching MCP servers:", error);
+ }
+ };
+
+ fetchMCPServerDetails();
+ }, [accessToken, mcpServers.length]);
+
+ // Function to get display name for MCP server
+ const getMCPServerDisplayName = (serverId: string) => {
+ const serverDetail = mcpServerDetails.find(server => server.server_id === serverId);
+ if (serverDetail) {
+ // Truncate server ID: first 3 chars + "..." + last 4 chars
+ const truncatedId = serverId.length > 7
+ ? `${serverId.slice(0, 3)}...${serverId.slice(-4)}`
+ : serverId;
+ return `${serverDetail.alias} (${truncatedId})`;
+ }
+ return serverId;
+ };
+
+ return (
+
+
+
+ MCP Servers
+
+ {mcpServers.length}
+
+
+
+ {mcpServers.length > 0 ? (
+
+ {mcpServers.map((server, index) => (
+
+
+ {getMCPServerDisplayName(server)}
+
+
+ ))}
+
+ ) : (
+
+
+ No MCP servers configured
+
+ )}
+
+ );
+}
+
+export default MCPServerPermissions;
\ No newline at end of file
diff --git a/ui/litellm-dashboard/src/components/permissions/VectorStorePermissions.tsx b/ui/litellm-dashboard/src/components/permissions/VectorStorePermissions.tsx
new file mode 100644
index 00000000000..faa9067a68b
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/permissions/VectorStorePermissions.tsx
@@ -0,0 +1,83 @@
+import React, { useState, useEffect } from "react";
+import { Text, Badge } from "@tremor/react";
+import { DatabaseIcon } from "@heroicons/react/outline";
+import { vectorStoreListCall } from "../networking";
+
+interface VectorStoreDetails {
+ vector_store_id: string;
+ vector_store_name?: string;
+}
+
+interface VectorStorePermissionsProps {
+ vectorStores: string[];
+ accessToken?: string | null;
+}
+
+export function VectorStorePermissions({
+ vectorStores,
+ accessToken
+}: VectorStorePermissionsProps) {
+ const [vectorStoreDetails, setVectorStoreDetails] = useState([]);
+
+ // Fetch vector store details when component mounts
+ useEffect(() => {
+ const fetchVectorStores = async () => {
+ if (!accessToken || vectorStores.length === 0) return;
+
+ try {
+ const response = await vectorStoreListCall(accessToken);
+ if (response.data) {
+ setVectorStoreDetails(response.data.map((store: any) => ({
+ vector_store_id: store.vector_store_id,
+ vector_store_name: store.vector_store_name
+ })));
+ }
+ } catch (error) {
+ console.error("Error fetching vector stores:", error);
+ }
+ };
+
+ fetchVectorStores();
+ }, [accessToken, vectorStores.length]);
+
+ // Function to get display name for vector store
+ const getVectorStoreDisplayName = (storeId: string) => {
+ const storeDetail = vectorStoreDetails.find(store => store.vector_store_id === storeId);
+ if (storeDetail) {
+ return `${storeDetail.vector_store_name || storeDetail.vector_store_id} (${storeDetail.vector_store_id})`;
+ }
+ return storeId;
+ };
+
+ return (
+
+
+
+ Vector Stores
+
+ {vectorStores.length}
+
+
+
+ {vectorStores.length > 0 ? (
+
+ {vectorStores.map((store, index) => (
+
+ {getVectorStoreDisplayName(store)}
+
+ ))}
+
+ ) : (
+
+
+ No vector stores configured
+
+ )}
+
+ );
+}
+
+export default VectorStorePermissions;
\ No newline at end of file
diff --git a/ui/litellm-dashboard/src/components/team/team_info.tsx b/ui/litellm-dashboard/src/components/team/team_info.tsx
index 084f58065ee..1c6bfd46832 100644
--- a/ui/litellm-dashboard/src/components/team/team_info.tsx
+++ b/ui/litellm-dashboard/src/components/team/team_info.tsx
@@ -35,6 +35,7 @@ import { getModelDisplayName } from "../key_team_helpers/fetch_available_models_
import { isAdminRole } from "@/utils/roles";
import ObjectPermissionsView from "../object_permissions_view";
import VectorStoreSelector from "../vector_store_management/VectorStoreSelector";
+import MCPServerSelector from "../mcp_server_management/MCPServerSelector";
import PremiumVectorStoreSelector from "../common_components/PremiumVectorStoreSelector";
export interface TeamData {
@@ -235,10 +236,11 @@ const TeamInfoView: React.FC = ({
};
// Handle object_permission updates
- if (values.vector_stores !== undefined) {
+ if (values.vector_stores !== undefined || values.mcp_servers !== undefined) {
updateData.object_permission = {
...teamData?.team_info.object_permission,
- vector_stores: values.vector_stores || []
+ vector_stores: values.vector_stores || [],
+ mcp_servers: values.mcp_servers || []
};
}
@@ -385,7 +387,8 @@ const TeamInfoView: React.FC = ({
guardrails: info.metadata?.guardrails || [],
metadata: info.metadata ? JSON.stringify(info.metadata, null, 2) : "",
organization_id: info.organization_id,
- vector_stores: info.object_permission?.vector_stores || []
+ vector_stores: info.object_permission?.vector_stores || [],
+ mcp_servers: info.object_permission?.mcp_servers || []
}}
layout="vertical"
>
@@ -466,6 +469,15 @@ const TeamInfoView: React.FC = ({
placeholder="Select vector stores"
/>
+
+
+ form.setFieldValue('mcp_servers', values)}
+ value={form.getFieldValue('mcp_servers')}
+ accessToken={accessToken || ""}
+ placeholder="Select MCP servers"
+ />
+