@@ -26,10 +26,12 @@ import {
2626 CardActions ,
2727 Button ,
2828 CircularProgress ,
29+ Alert as MuiAlert ,
2930} from '@mui/material' ;
3031import EditIcon from '@mui/icons-material/Edit' ;
3132import DeleteIcon from '@mui/icons-material/Delete' ;
3233import CodeIcon from '@mui/icons-material/Code' ;
34+ import SyncIcon from '@mui/icons-material/Sync' ;
3335import Drawer from '@mui/material/Drawer' ;
3436import Tooltip from '@mui/material/Tooltip' ;
3537import Box from '@mui/material/Box' ;
@@ -113,10 +115,13 @@ const EndpointCard = ({
113115 const [ open , setOpen ] = useState ( false ) ;
114116 const [ editedDefinition , setEditedDefinition ] = useState ( null ) ;
115117 const [ isSaving , setIsSaving ] = useState ( false ) ;
118+ const [ isSyncing , setIsSyncing ] = useState ( false ) ;
119+ const [ resyncAlert , setResyncAlert ] = useState ( false ) ;
116120
117121 const toggleDefinitionViewDrawer = ( state ) => ( ) => {
118122 if ( state ) {
119123 setEditedDefinition ( null ) ;
124+ setResyncAlert ( false ) ;
120125 }
121126 setOpen ( state ) ;
122127 }
@@ -151,9 +156,11 @@ const EndpointCard = ({
151156 . then ( ( ) => {
152157 Alert . success ( intl . formatMessage ( {
153158 id : 'MCPServers.Details.Endpoints.EndpointCard.definition.save.success' ,
154- defaultMessage : 'Backend API definition updated successfully' ,
159+ defaultMessage : 'Backend API definition updated successfully.'
160+ + ' Visit the Tools page to review and update the tools accordingly.' ,
155161 } ) ) ;
156162 setEditedDefinition ( null ) ;
163+ setResyncAlert ( false ) ;
157164 if ( onDefinitionUpdate ) {
158165 onDefinitionUpdate ( ) ;
159166 }
@@ -200,6 +207,42 @@ const EndpointCard = ({
200207 return JSON . stringify ( apiDef , null , 2 ) ;
201208 }
202209
210+ const handleResync = ( ) => {
211+ const url = getEndpointUrl ( ) ;
212+ if ( ! url || url === 'No URL configured' ) {
213+ Alert . error ( intl . formatMessage ( {
214+ id : 'MCPServers.Details.Endpoints.EndpointCard.resync.nourl' ,
215+ defaultMessage : 'No endpoint URL configured.' ,
216+ } ) ) ;
217+ return ;
218+ }
219+ setIsSyncing ( true ) ;
220+ MCPServer . validateThirdPartyMCPServerUrl ( url , null , apiObject . id , endpointType )
221+ . then ( ( response ) => {
222+ const { body } = response ;
223+ const freshContent = body . content ;
224+ if ( ! freshContent ) {
225+ throw new Error ( 'Empty response from backend' ) ;
226+ }
227+ let prettyContent = freshContent ;
228+ try {
229+ prettyContent = JSON . stringify ( JSON . parse ( freshContent ) , null , 2 ) ;
230+ } catch ( _ ) { /* leave as-is if not valid JSON */ }
231+
232+ setEditedDefinition ( prettyContent ) ;
233+ setResyncAlert ( true ) ;
234+ } )
235+ . catch ( ( error ) => {
236+ // eslint-disable-next-line no-console
237+ console . error ( error ) ;
238+ Alert . error ( intl . formatMessage ( {
239+ id : 'MCPServers.Details.Endpoints.EndpointCard.resync.error' ,
240+ defaultMessage : 'Could not re-sync backend definition.' ,
241+ } ) ) ;
242+ } )
243+ . finally ( ( ) => setIsSyncing ( false ) ) ;
244+ } ;
245+
203246 /**
204247 * Check if delete button should be disabled based on endpoint configuration
205248 * @returns {boolean } True if delete should be disabled (only one endpoint section exists)
@@ -256,6 +299,7 @@ const EndpointCard = ({
256299 < Tooltip title = 'View Backend Definition' >
257300 < IconButton
258301 size = 'small'
302+ data-testid = 'endpoint-definition-view-btn'
259303 onClick = { toggleDefinitionViewDrawer ( true ) }
260304 >
261305 < CodeIcon fontSize = 'small' />
@@ -296,32 +340,76 @@ const EndpointCard = ({
296340 defaultMessage = 'Backend API Definition'
297341 />
298342 </ Typography >
299- < Button
300- variant = 'contained'
301- color = 'primary'
302- size = 'small'
303- onClick = { handleDefinitionSave }
304- disabled = {
305- editedDefinition === null
306- || isSaving
307- || isRestricted ( [
308- 'apim:mcp_server_create' ,
309- 'apim:mcp_server_manage' ,
310- 'apim:mcp_server_publish' ,
311- 'apim:mcp_server_import_export' ,
312- ] , apiObject )
313- || apiObject . isRevision
314- }
315- >
316- { isSaving ? (
317- < CircularProgress size = { 16 } sx = { { mr : 1 } } />
318- ) : null }
319- < FormattedMessage
320- id = 'MCPServers.Details.Endpoints.EndpointCard.definition.update'
321- defaultMessage = 'Update'
322- />
323- </ Button >
343+ < Box sx = { { display : 'flex' , gap : 1 } } >
344+ { apiObject . subtypeConfiguration ?. subtype === 'SERVER_PROXY' && (
345+ < Button
346+ variant = 'outlined'
347+ size = 'small'
348+ data-testid = 'endpoint-definition-resync-btn'
349+ startIcon = { isSyncing
350+ ? < CircularProgress size = { 16 } />
351+ : < SyncIcon fontSize = 'small' /> }
352+ onClick = { handleResync }
353+ disabled = {
354+ isSyncing
355+ || isRestricted ( [
356+ 'apim:mcp_server_create' ,
357+ 'apim:mcp_server_manage' ,
358+ 'apim:mcp_server_publish' ,
359+ 'apim:mcp_server_import_export' ,
360+ ] , apiObject )
361+ || apiObject . isRevision
362+ }
363+ >
364+ < FormattedMessage
365+ id = 'MCPServers.Details.Endpoints.EndpointCard.resync.label'
366+ defaultMessage = 'Re-sync'
367+ />
368+ </ Button >
369+ ) }
370+ < Button
371+ variant = 'contained'
372+ color = 'primary'
373+ size = 'small'
374+ data-testid = 'endpoint-definition-update-btn'
375+ onClick = { handleDefinitionSave }
376+ disabled = {
377+ editedDefinition === null
378+ || isSaving
379+ || isRestricted ( [
380+ 'apim:mcp_server_create' ,
381+ 'apim:mcp_server_manage' ,
382+ 'apim:mcp_server_publish' ,
383+ 'apim:mcp_server_import_export' ,
384+ ] , apiObject )
385+ || apiObject . isRevision
386+ }
387+ >
388+ { isSaving ? (
389+ < CircularProgress size = { 16 } sx = { { mr : 1 } } />
390+ ) : null }
391+ < FormattedMessage
392+ id = 'MCPServers.Details.Endpoints.EndpointCard.definition.update'
393+ defaultMessage = 'Update'
394+ />
395+ </ Button >
396+ </ Box >
324397 </ Box >
398+ { resyncAlert && (
399+ < Box px = { 2 } pb = { 1 } sx = { { flexShrink : 0 } } >
400+ < MuiAlert
401+ severity = 'info'
402+ variant = 'outlined'
403+ sx = { { py : 0.5 } }
404+ >
405+ < FormattedMessage
406+ id = 'MCPServers.Details.Endpoints.EndpointCard.resync.pending'
407+ defaultMessage = { 'Definition fetched from backend'
408+ + ' — review the changes and click Update to save.' }
409+ />
410+ </ MuiAlert >
411+ </ Box >
412+ ) }
325413 < Box
326414 sx = { {
327415 flex : 1 ,
@@ -335,7 +423,7 @@ const EndpointCard = ({
335423 language = 'json'
336424 width = '100%'
337425 height = '100%'
338- value = { getApiDefinition ( ) }
426+ value = { editedDefinition !== null ? editedDefinition : getApiDefinition ( ) }
339427 options = { editorOptions }
340428 theme = 'vs-dark'
341429 onChange = { ( value ) => setEditedDefinition ( value ) }
0 commit comments