Skip to content

Commit 04a9db7

Browse files
Merge pull request #1377 from Thenujan-Nagaratnam/mcp-tool-fetch
Add resync functionality to MCP SERVER PROXY
2 parents a1bc11b + 075b295 commit 04a9db7

3 files changed

Lines changed: 132 additions & 39 deletions

File tree

portals/publisher/src/main/webapp/site/public/locales/en.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2315,8 +2315,12 @@
23152315
"MCPServers.Details.Endpoints.EndpointCard.definition.empty": "API definition cannot be empty",
23162316
"MCPServers.Details.Endpoints.EndpointCard.definition.invalid.json": "API definition is not valid JSON",
23172317
"MCPServers.Details.Endpoints.EndpointCard.definition.save.error": "Error updating backend API definition",
2318-
"MCPServers.Details.Endpoints.EndpointCard.definition.save.success": "Backend API definition updated successfully",
2318+
"MCPServers.Details.Endpoints.EndpointCard.definition.save.success": "Backend API definition updated successfully. Visit the Tools page to review and update the tools accordingly.",
23192319
"MCPServers.Details.Endpoints.EndpointCard.definition.update": "Update",
2320+
"MCPServers.Details.Endpoints.EndpointCard.resync.error": "Could not re-sync backend definition.",
2321+
"MCPServers.Details.Endpoints.EndpointCard.resync.label": "Re-sync",
2322+
"MCPServers.Details.Endpoints.EndpointCard.resync.nourl": "No endpoint URL configured.",
2323+
"MCPServers.Details.Endpoints.EndpointCard.resync.pending": "Definition fetched from backend — review the changes and click Update to save.",
23202324
"MCPServers.Details.Endpoints.Endpoints.delete.confirmation.cancel": "Cancel",
23212325
"MCPServers.Details.Endpoints.Endpoints.delete.confirmation.delete": "Delete",
23222326
"MCPServers.Details.Endpoints.Endpoints.delete.confirmation.message": "Are you sure you want to delete the {endpointType} endpoint? This action cannot be undone.",

portals/publisher/src/main/webapp/source/src/app/components/MCPServers/Details/Endpoints/EndpointCard.jsx

Lines changed: 115 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,12 @@ import {
2626
CardActions,
2727
Button,
2828
CircularProgress,
29+
Alert as MuiAlert,
2930
} from '@mui/material';
3031
import EditIcon from '@mui/icons-material/Edit';
3132
import DeleteIcon from '@mui/icons-material/Delete';
3233
import CodeIcon from '@mui/icons-material/Code';
34+
import SyncIcon from '@mui/icons-material/Sync';
3335
import Drawer from '@mui/material/Drawer';
3436
import Tooltip from '@mui/material/Tooltip';
3537
import 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)}

portals/publisher/src/main/webapp/source/src/app/data/MCPServer.js

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,8 @@ class MCPServer extends Resource {
302302
* @param {string} [securityInfo.value] - The security header value.
303303
* @returns {Promise} A promise that resolves to the validation result.
304304
*/
305-
static validateThirdPartyMCPServerUrl(url, securityInfo = null) {
305+
static validateThirdPartyMCPServerUrl(url, securityInfo = null,
306+
mcpServerId = null, endpointType = null) {
306307
const apiClient = new APIClientFactory()
307308
.getAPIClient(
308309
Utils.getCurrentEnvironment(),
@@ -312,16 +313,16 @@ class MCPServer extends Resource {
312313
const payload = {
313314
'Content-Type': 'multipart/form-data',
314315
}
315-
const requestBody = {
316-
requestBody: {
317-
url,
318-
securityInfo: securityInfo || {
319-
isSecure: false,
320-
header: '',
321-
value: ''
322-
}
323-
}
324-
};
316+
const body = { url };
317+
if (securityInfo) {
318+
body.securityInfo = securityInfo;
319+
} else if (mcpServerId && endpointType) {
320+
body.mcpServerId = mcpServerId;
321+
body.endpointType = endpointType;
322+
} else {
323+
body.securityInfo = { isSecure: false, header: '', value: '' };
324+
}
325+
const requestBody = { requestBody: body };
325326
return client.apis.Validation.validateThirdPartyMCPServer(
326327
payload,
327328
requestBody,

0 commit comments

Comments
 (0)