From 902b16134fe14da8ab1dd3782042a1d411a4d26c Mon Sep 17 00:00:00 2001
From: Sheen Capadngan
Date: Wed, 17 Dec 2025 15:59:06 +0800
Subject: [PATCH] misc: added permission guards in the UI
---
.../ProjectPermissionContext/index.tsx | 1 +
frontend/src/context/index.tsx | 1 +
.../MCPEndpointDetailPage.tsx | 39 +++++++++++---
.../MCPEndpointConnectedServersSection.tsx | 27 +++++++---
.../components/MCPEndpointDetailsSection.tsx | 24 ++++++---
.../MCPEndpointToolSelectionSection.tsx | 18 ++++++-
.../MCPEndpointsTab/MCPEndpointRow.tsx | 52 +++++++++++++------
.../MCPEndpointsTab/MCPEndpointsTab.tsx | 24 ++++++---
.../components/MCPServersTab/MCPServerRow.tsx | 52 +++++++++++++------
.../MCPServersTab/MCPServersTab.tsx | 24 ++++++---
.../MCPServerDetailPage.tsx | 35 ++++++++++---
.../components/MCPServerDetailsSection.tsx | 23 +++++---
12 files changed, 234 insertions(+), 86 deletions(-)
diff --git a/frontend/src/context/ProjectPermissionContext/index.tsx b/frontend/src/context/ProjectPermissionContext/index.tsx
index 9eb548c071..08d3600f6b 100644
--- a/frontend/src/context/ProjectPermissionContext/index.tsx
+++ b/frontend/src/context/ProjectPermissionContext/index.tsx
@@ -16,5 +16,6 @@ export {
ProjectPermissionPkiSyncActions,
ProjectPermissionPkiTemplateActions,
ProjectPermissionSshHostActions,
+ ProjectPermissionMcpEndpointActions,
ProjectPermissionSub
} from "./types";
diff --git a/frontend/src/context/index.tsx b/frontend/src/context/index.tsx
index 81feaf8e58..17c846ab97 100644
--- a/frontend/src/context/index.tsx
+++ b/frontend/src/context/index.tsx
@@ -27,6 +27,7 @@ export {
ProjectPermissionPkiSyncActions,
ProjectPermissionPkiTemplateActions,
ProjectPermissionSshHostActions,
+ ProjectPermissionMcpEndpointActions,
ProjectPermissionSub,
useProjectPermission
} from "./ProjectPermissionContext";
diff --git a/frontend/src/pages/ai/MCPEndpointDetailPage/MCPEndpointDetailPage.tsx b/frontend/src/pages/ai/MCPEndpointDetailPage/MCPEndpointDetailPage.tsx
index 3988ac7250..af5244d915 100644
--- a/frontend/src/pages/ai/MCPEndpointDetailPage/MCPEndpointDetailPage.tsx
+++ b/frontend/src/pages/ai/MCPEndpointDetailPage/MCPEndpointDetailPage.tsx
@@ -10,6 +10,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNavigate, useParams } from "@tanstack/react-router";
import { createNotification } from "@app/components/notifications";
+import { ProjectPermissionCan } from "@app/components/permissions";
import {
Button,
ContentLoader,
@@ -20,6 +21,7 @@ import {
DropdownMenuTrigger,
EmptyState
} from "@app/components/v2";
+import { ProjectPermissionMcpEndpointActions, ProjectPermissionSub } from "@app/context";
import { useDeleteAiMcpEndpoint, useGetAiMcpEndpointById } from "@app/hooks/api";
import { EditMCPEndpointModal } from "../MCPPage/components/MCPEndpointsTab/EditMCPEndpointModal";
@@ -98,7 +100,7 @@ const PageContent = () => {
-
- setIsEditModalOpen(true)}>
- Edit Endpoint
-
- setIsDeleteModalOpen(true)} className="text-red-500">
- Delete Endpoint
-
+
+
+ {(isAllowed) => (
+ setIsEditModalOpen(true)}
+ isDisabled={!isAllowed}
+ >
+ Edit Endpoint
+
+ )}
+
+
+ {(isAllowed) => (
+ setIsDeleteModalOpen(true)}
+ className="text-red-500"
+ isDisabled={!isAllowed}
+ >
+ Delete Endpoint
+
+ )}
+
diff --git a/frontend/src/pages/ai/MCPEndpointDetailPage/components/MCPEndpointConnectedServersSection.tsx b/frontend/src/pages/ai/MCPEndpointDetailPage/components/MCPEndpointConnectedServersSection.tsx
index cb2470baae..d3367bc8c4 100644
--- a/frontend/src/pages/ai/MCPEndpointDetailPage/components/MCPEndpointConnectedServersSection.tsx
+++ b/frontend/src/pages/ai/MCPEndpointDetailPage/components/MCPEndpointConnectedServersSection.tsx
@@ -3,7 +3,9 @@ import { faCheck, faPencil, faServer, faTimes } from "@fortawesome/free-solid-sv
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { createNotification } from "@app/components/notifications";
+import { ProjectPermissionCan } from "@app/components/permissions";
import { Checkbox, IconButton, Spinner } from "@app/components/v2";
+import { ProjectPermissionMcpEndpointActions, ProjectPermissionSub } from "@app/context";
import { useListAiMcpServers, useUpdateAiMcpEndpoint } from "@app/hooks/api";
type Props = {
@@ -96,14 +98,25 @@ export const MCPEndpointConnectedServersSection = ({ endpointId, projectId, serv
) : (
-
-
-
+ {(isAllowed) => (
+
+
+
+ )}
+
)}
diff --git a/frontend/src/pages/ai/MCPEndpointDetailPage/components/MCPEndpointDetailsSection.tsx b/frontend/src/pages/ai/MCPEndpointDetailPage/components/MCPEndpointDetailsSection.tsx
index 8f9f6ebfed..e66fd033ed 100644
--- a/frontend/src/pages/ai/MCPEndpointDetailPage/components/MCPEndpointDetailsSection.tsx
+++ b/frontend/src/pages/ai/MCPEndpointDetailPage/components/MCPEndpointDetailsSection.tsx
@@ -4,6 +4,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { format } from "date-fns";
import { createNotification } from "@app/components/notifications";
+import { ProjectPermissionCan } from "@app/components/permissions";
import {
GenericFieldLabel,
IconButton,
@@ -13,6 +14,7 @@ import {
Switch,
Tooltip
} from "@app/components/v2";
+import { ProjectPermissionMcpEndpointActions, ProjectPermissionSub } from "@app/context";
import { TAiMcpEndpointWithServerIds, useUpdateAiMcpEndpoint } from "@app/hooks/api";
type Props = {
@@ -122,14 +124,22 @@ export const MCPEndpointDetailsSection = ({ endpoint, onEdit }: Props) => {
Details
-
-
-
+ {(isAllowed) => (
+
+
+
+ )}
+
{endpoint.name}
diff --git a/frontend/src/pages/ai/MCPEndpointDetailPage/components/MCPEndpointToolSelectionSection.tsx b/frontend/src/pages/ai/MCPEndpointDetailPage/components/MCPEndpointToolSelectionSection.tsx
index 6344688f04..b52bfc2a8b 100644
--- a/frontend/src/pages/ai/MCPEndpointDetailPage/components/MCPEndpointToolSelectionSection.tsx
+++ b/frontend/src/pages/ai/MCPEndpointDetailPage/components/MCPEndpointToolSelectionSection.tsx
@@ -10,6 +10,11 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { createNotification } from "@app/components/notifications";
import { Input, Switch, Tooltip } from "@app/components/v2";
+import {
+ ProjectPermissionMcpEndpointActions,
+ ProjectPermissionSub,
+ useProjectPermission
+} from "@app/context";
import {
TAiMcpEndpointToolConfig,
useDisableEndpointTool,
@@ -33,6 +38,7 @@ type ServerToolsSectionProps = {
toolConfigs: TAiMcpEndpointToolConfig[];
onToolToggle: (serverToolId: string, isEnabled: boolean) => void;
isUpdating: boolean;
+ canEdit: boolean;
};
const ServerToolsSection = ({
@@ -42,7 +48,8 @@ const ServerToolsSection = ({
searchQuery,
toolConfigs,
onToolToggle,
- isUpdating
+ isUpdating,
+ canEdit
}: ServerToolsSectionProps) => {
const [isExpanded, setIsExpanded] = useState(false);
@@ -126,7 +133,7 @@ const ServerToolsSection = ({
id={`tool-${tool.id}`}
isChecked={isToolEnabled(tool.id)}
onCheckedChange={(checked) => onToolToggle(tool.id, checked)}
- isDisabled={isUpdating}
+ isDisabled={isUpdating || !canEdit}
/>
))}
@@ -146,6 +153,12 @@ const ServerToolsSection = ({
export const MCPEndpointToolSelectionSection = ({ endpointId, projectId, serverIds }: Props) => {
const [searchQuery, setSearchQuery] = useState("");
+ const { permission } = useProjectPermission();
+ const canEdit = permission.can(
+ ProjectPermissionMcpEndpointActions.Edit,
+ ProjectPermissionSub.McpEndpoints
+ );
+
const { data: serversData } = useListAiMcpServers({ projectId });
const { data: toolConfigs = [] } = useListEndpointTools({ endpointId });
const enableTool = useEnableEndpointTool();
@@ -212,6 +225,7 @@ export const MCPEndpointToolSelectionSection = ({ endpointId, projectId, serverI
toolConfigs={toolConfigs}
onToolToggle={handleToolToggle}
isUpdating={enableTool.isPending || disableTool.isPending}
+ canEdit={canEdit}
/>
))
)}
diff --git a/frontend/src/pages/ai/MCPPage/components/MCPEndpointsTab/MCPEndpointRow.tsx b/frontend/src/pages/ai/MCPPage/components/MCPEndpointsTab/MCPEndpointRow.tsx
index ef7ca54534..f40e774514 100644
--- a/frontend/src/pages/ai/MCPPage/components/MCPEndpointsTab/MCPEndpointRow.tsx
+++ b/frontend/src/pages/ai/MCPPage/components/MCPEndpointsTab/MCPEndpointRow.tsx
@@ -4,6 +4,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNavigate, useParams } from "@tanstack/react-router";
import { createNotification } from "@app/components/notifications";
+import { ProjectPermissionCan } from "@app/components/permissions";
import {
DropdownMenu,
DropdownMenuContent,
@@ -13,6 +14,7 @@ import {
Tooltip,
Tr
} from "@app/components/v2";
+import { ProjectPermissionMcpEndpointActions, ProjectPermissionSub } from "@app/context";
import { useToggle } from "@app/hooks";
import { TAiMcpEndpoint } from "@app/hooks/api";
@@ -113,25 +115,41 @@ export const MCPEndpointRow = ({ endpoint, onEditEndpoint, onDeleteEndpoint }: P
>
Copy Endpoint ID
-
{
- e.stopPropagation();
- onEditEndpoint(endpoint);
- }}
- icon={}
+
- Edit Endpoint
-
-
{
- e.stopPropagation();
- onDeleteEndpoint(endpoint);
- }}
- icon={}
- className="text-red-500 hover:text-red-400"
+ {(isAllowed) => (
+ {
+ e.stopPropagation();
+ onEditEndpoint(endpoint);
+ }}
+ icon={}
+ isDisabled={!isAllowed}
+ >
+ Edit Endpoint
+
+ )}
+
+
- Delete Endpoint
-
+ {(isAllowed) => (
+
{
+ e.stopPropagation();
+ onDeleteEndpoint(endpoint);
+ }}
+ icon={}
+ className="text-red-500 hover:text-red-400"
+ isDisabled={!isAllowed}
+ >
+ Delete Endpoint
+
+ )}
+
diff --git a/frontend/src/pages/ai/MCPPage/components/MCPEndpointsTab/MCPEndpointsTab.tsx b/frontend/src/pages/ai/MCPPage/components/MCPEndpointsTab/MCPEndpointsTab.tsx
index 8bd86fb0bb..abcfeeb7a6 100644
--- a/frontend/src/pages/ai/MCPPage/components/MCPEndpointsTab/MCPEndpointsTab.tsx
+++ b/frontend/src/pages/ai/MCPPage/components/MCPEndpointsTab/MCPEndpointsTab.tsx
@@ -3,7 +3,9 @@ import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { createNotification } from "@app/components/notifications";
+import { ProjectPermissionCan } from "@app/components/permissions";
import { Button, DeleteActionModal } from "@app/components/v2";
+import { ProjectPermissionMcpEndpointActions, ProjectPermissionSub } from "@app/context";
import { TAiMcpEndpoint, useDeleteAiMcpEndpoint } from "@app/hooks/api";
import { AddMCPEndpointModal } from "./AddMCPEndpointModal";
@@ -63,14 +65,22 @@ export const MCPEndpointsTab = () => {
-
}
- onClick={handleCreateEndpoint}
+
- Create Endpoint
-
+ {(isAllowed) => (
+ }
+ onClick={handleCreateEndpoint}
+ isDisabled={!isAllowed}
+ >
+ Create Endpoint
+
+ )}
+
>
Copy Server ID
- {
- e.stopPropagation();
- onEditServer(server);
- }}
- icon={}
+
- Edit Server
-
- {
- e.stopPropagation();
- onDeleteServer(server);
- }}
- icon={}
- className="text-red-500 hover:text-red-400"
+ {(isAllowed) => (
+ {
+ e.stopPropagation();
+ onEditServer(server);
+ }}
+ icon={}
+ isDisabled={!isAllowed}
+ >
+ Edit Server
+
+ )}
+
+
- Delete Server
-
+ {(isAllowed) => (
+ {
+ e.stopPropagation();
+ onDeleteServer(server);
+ }}
+ icon={}
+ className="text-red-500 hover:text-red-400"
+ isDisabled={!isAllowed}
+ >
+ Delete Server
+
+ )}
+
diff --git a/frontend/src/pages/ai/MCPPage/components/MCPServersTab/MCPServersTab.tsx b/frontend/src/pages/ai/MCPPage/components/MCPServersTab/MCPServersTab.tsx
index 35c50d987a..3692b17a61 100644
--- a/frontend/src/pages/ai/MCPPage/components/MCPServersTab/MCPServersTab.tsx
+++ b/frontend/src/pages/ai/MCPPage/components/MCPServersTab/MCPServersTab.tsx
@@ -3,7 +3,9 @@ import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { createNotification } from "@app/components/notifications";
+import { ProjectPermissionCan } from "@app/components/permissions";
import { Button, DeleteActionModal } from "@app/components/v2";
+import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
import { TAiMcpServer, useDeleteAiMcpServer } from "@app/hooks/api";
import { AddMCPServerModal } from "./AddMCPServerModal";
@@ -63,14 +65,22 @@ export const MCPServersTab = () => {
- }
- onClick={handleAddServer}
+
- Add MCP Server
-
+ {(isAllowed) => (
+ }
+ onClick={handleAddServer}
+ isDisabled={!isAllowed}
+ >
+ Add MCP Server
+
+ )}
+
diff --git a/frontend/src/pages/ai/MCPServerDetailPage/MCPServerDetailPage.tsx b/frontend/src/pages/ai/MCPServerDetailPage/MCPServerDetailPage.tsx
index 1055a3af73..fb5f6fefd5 100644
--- a/frontend/src/pages/ai/MCPServerDetailPage/MCPServerDetailPage.tsx
+++ b/frontend/src/pages/ai/MCPServerDetailPage/MCPServerDetailPage.tsx
@@ -5,6 +5,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNavigate, useParams } from "@tanstack/react-router";
import { createNotification } from "@app/components/notifications";
+import { ProjectPermissionCan } from "@app/components/permissions";
import {
Button,
ContentLoader,
@@ -15,6 +16,7 @@ import {
DropdownMenuTrigger,
EmptyState
} from "@app/components/v2";
+import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
import { useDeleteAiMcpServer, useGetAiMcpServerById } from "@app/hooks/api";
import { EditMCPServerModal } from "../MCPPage/components/MCPServersTab/EditMCPServerModal";
@@ -118,12 +120,33 @@ const PageContent = () => {
- setIsEditModalOpen(true)}>
- Edit Server
-
- setIsDeleteModalOpen(true)} className="text-red-500">
- Delete Server
-
+
+ {(isAllowed) => (
+ setIsEditModalOpen(true)}
+ isDisabled={!isAllowed}
+ >
+ Edit Server
+
+ )}
+
+
+ {(isAllowed) => (
+ setIsDeleteModalOpen(true)}
+ className="text-red-500"
+ isDisabled={!isAllowed}
+ >
+ Delete Server
+
+ )}
+
diff --git a/frontend/src/pages/ai/MCPServerDetailPage/components/MCPServerDetailsSection.tsx b/frontend/src/pages/ai/MCPServerDetailPage/components/MCPServerDetailsSection.tsx
index 4c2e8aefa3..89c588713a 100644
--- a/frontend/src/pages/ai/MCPServerDetailPage/components/MCPServerDetailsSection.tsx
+++ b/frontend/src/pages/ai/MCPServerDetailPage/components/MCPServerDetailsSection.tsx
@@ -2,7 +2,9 @@ import { faEdit } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { format } from "date-fns";
+import { ProjectPermissionCan } from "@app/components/permissions";
import { GenericFieldLabel, IconButton } from "@app/components/v2";
+import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
import { AiMcpServerStatus, TAiMcpServer } from "@app/hooks/api";
type Props = {
@@ -33,14 +35,19 @@ export const MCPServerDetailsSection = ({ server, onEdit }: Props) => {
Details
-
-
-
+
+ {(isAllowed) => (
+
+
+
+ )}
+
{server.name}