1717import { MouseEvent , useCallback , useEffect , useMemo , useState } from 'react' ;
1818
1919import { configApiRef , fetchApiRef , useApi } from '@backstage/core-plugin-api' ;
20+ import { usePermission } from '@backstage/plugin-permission-react' ;
2021
2122import { makeStyles } from '@material-ui/core' ;
2223import ModeEditOutlineOutlinedIcon from '@mui/icons-material/ModeEditOutlineOutlined' ;
@@ -33,6 +34,8 @@ import {
3334} from '@patternfly/react-icons' ;
3435import { Table , Tbody , Td , Th , Thead , Tr } from '@patternfly/react-table' ;
3536
37+ import { lightspeedMcpManagePermission } from '@red-hat-developer-hub/backstage-plugin-lightspeed-common' ;
38+
3639type ServerStatus = 'tokenRequired' | 'disabled' | 'ok' | 'failed' | 'unknown' ;
3740
3841type McpServer = {
@@ -209,8 +212,8 @@ const getStatusIcon = (status: ServerStatus, className: string) => {
209212} ;
210213
211214const getDisplayStatus = ( server : McpServer ) : ServerStatus => {
212- if ( ! server . enabled ) return 'disabled' ;
213215 if ( ! server . hasToken ) return 'tokenRequired' ;
216+ if ( ! server . enabled ) return 'disabled' ;
214217 if ( server . status === 'error' ) return 'failed' ;
215218 if ( server . status === 'connected' ) return 'ok' ;
216219 return 'unknown' ;
@@ -248,6 +251,10 @@ export const McpServersSettings = ({ onClose }: McpServersSettingsProps) => {
248251 const classes = useStyles ( ) ;
249252 const configApi = useApi ( configApiRef ) ;
250253 const fetchApi = useApi ( fetchApiRef ) ;
254+ const mcpManagePermission = usePermission ( {
255+ permission : lightspeedMcpManagePermission ,
256+ } ) ;
257+ const canManageMcp = mcpManagePermission . allowed ;
251258 const [ servers , setServers ] = useState < McpServer [ ] > ( [ ] ) ;
252259 const [ sortAsc , setSortAsc ] = useState ( true ) ;
253260 const [ isLoading , setIsLoading ] = useState ( true ) ;
@@ -328,30 +335,32 @@ export const McpServersSettings = ({ onClose }: McpServersSettingsProps) => {
328335 const uiServers = ( data . servers ?? [ ] ) . map ( server => toUiServer ( server ) ) ;
329336 setServers ( uiServers ) ;
330337
331- const serversToValidate = uiServers . filter ( server => server . hasToken ) ;
332- void Promise . allSettled (
333- serversToValidate . map ( async server => {
334- try {
335- await validateServer ( server . name ) ;
336- } catch ( validationError ) {
337- setError (
338- prev =>
339- prev ??
340- ( validationError instanceof Error
341- ? validationError . message
342- : `Failed to validate ${ server . name } ` ) ,
343- ) ;
344- }
345- } ) ,
346- ) ;
338+ if ( canManageMcp ) {
339+ const serversToValidate = uiServers . filter ( server => server . hasToken ) ;
340+ void Promise . allSettled (
341+ serversToValidate . map ( async server => {
342+ try {
343+ await validateServer ( server . name ) ;
344+ } catch ( validationError ) {
345+ setError (
346+ prev =>
347+ prev ??
348+ ( validationError instanceof Error
349+ ? validationError . message
350+ : `Failed to validate ${ server . name } ` ) ,
351+ ) ;
352+ }
353+ } ) ,
354+ ) ;
355+ }
347356 } catch ( e ) {
348357 setError (
349358 e instanceof Error ? e . message : 'Failed to load MCP server settings' ,
350359 ) ;
351360 } finally {
352361 setIsLoading ( false ) ;
353362 }
354- } , [ fetchJson , getBaseUrl , validateServer ] ) ;
363+ } , [ canManageMcp , fetchJson , getBaseUrl , validateServer ] ) ;
355364
356365 useEffect ( ( ) => {
357366 loadServers ( ) ;
@@ -362,6 +371,9 @@ export const McpServersSettings = ({ onClose }: McpServersSettingsProps) => {
362371 serverName : string ,
363372 body : { enabled ?: boolean ; token ?: string | null } ,
364373 ) => {
374+ if ( ! canManageMcp ) {
375+ return ;
376+ }
365377 setError ( null ) ;
366378 setIsSaving ( prev => ( { ...prev , [ serverName ] : true } ) ) ;
367379 try {
@@ -395,7 +407,7 @@ export const McpServersSettings = ({ onClose }: McpServersSettingsProps) => {
395407 setIsSaving ( prev => ( { ...prev , [ serverName ] : false } ) ) ;
396408 }
397409 } ,
398- [ fetchJson , getBaseUrl , loadServers ] ,
410+ [ canManageMcp , fetchJson , getBaseUrl , loadServers ] ,
399411 ) ;
400412
401413 const selectedCount = useMemo (
@@ -449,6 +461,14 @@ export const McpServersSettings = ({ onClose }: McpServersSettingsProps) => {
449461 className = { classes . alert }
450462 />
451463 ) }
464+ { ! mcpManagePermission . loading && ! canManageMcp && (
465+ < Alert
466+ variant = "info"
467+ isInline
468+ title = "You have read-only access to MCP servers."
469+ className = { classes . alert }
470+ />
471+ ) }
452472
453473 < Table
454474 variant = "compact"
@@ -481,6 +501,11 @@ export const McpServersSettings = ({ onClose }: McpServersSettingsProps) => {
481501 < Td colSpan = { 4 } > Loading MCP servers...</ Td >
482502 </ Tr >
483503 ) }
504+ { ! isLoading && sortedServers . length === 0 && (
505+ < Tr >
506+ < Td colSpan = { 4 } > No MCP servers available.</ Td >
507+ </ Tr >
508+ ) }
484509 { sortedServers . map ( server => {
485510 const displayStatus = getDisplayStatus ( server ) ;
486511 const displayDetail = getDisplayDetail ( server , displayStatus ) ;
@@ -508,7 +533,9 @@ export const McpServersSettings = ({ onClose }: McpServersSettingsProps) => {
508533 id = { `mcp-switch-${ server . id } ` }
509534 aria-label = { `Toggle ${ server . name } ` }
510535 isChecked = { isChecked }
511- isDisabled = { isUnavailable || isRowSaving }
536+ isDisabled = {
537+ isUnavailable || isRowSaving || ! canManageMcp
538+ }
512539 onChange = { ( _event , checked ) => {
513540 patchServer ( server . name , { enabled : checked } ) ;
514541 } }
@@ -557,6 +584,7 @@ export const McpServersSettings = ({ onClose }: McpServersSettingsProps) => {
557584 icon = { < ModeEditOutlineOutlinedIcon fontSize = "small" /> }
558585 variant = "plain"
559586 className = { classes . actionButton }
587+ isDisabled = { ! canManageMcp }
560588 onClick = { onEditClick }
561589 />
562590 </ Td >
0 commit comments