Skip to content

Commit 023c005

Browse files
author
Lasim
committed
feat(backend): add MCP Registry sync progress and management endpoints
1 parent 777520c commit 023c005

File tree

4 files changed

+716
-6
lines changed

4 files changed

+716
-6
lines changed

services/backend/src/routes/admin/mcp-registry.ts

Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,4 +136,337 @@ export default async function mcpRegistryRoutes(server: FastifyInstance) {
136136
return reply.status(500).type('application/json').send(jsonString);
137137
}
138138
});
139+
140+
/**
141+
* Get detailed batch progress
142+
* Provides real-time progress, job status, and error details for a sync batch
143+
*/
144+
server.get('/admin/mcp-registry/progress/:batchId', {
145+
preValidation: requirePermission('mcp.registry.sync'),
146+
schema: {
147+
tags: ['Admin - MCP Registry'],
148+
summary: 'Get detailed sync progress for batch',
149+
description: 'Get real-time progress, job status, and error details for MCP Registry sync batch',
150+
security: [{ cookieAuth: [] }],
151+
152+
params: {
153+
type: 'object',
154+
properties: {
155+
batchId: { type: 'string', description: 'Job batch ID' }
156+
},
157+
required: ['batchId']
158+
},
159+
160+
response: {
161+
200: {
162+
type: 'object',
163+
properties: {
164+
success: { type: 'boolean' },
165+
data: {
166+
type: 'object',
167+
properties: {
168+
batch: {
169+
type: 'object',
170+
description: 'Batch information'
171+
},
172+
progress: {
173+
type: 'object',
174+
properties: {
175+
total: { type: 'number' },
176+
completed: { type: 'number' },
177+
failed: { type: 'number' },
178+
pending: { type: 'number' },
179+
processing: { type: 'number' },
180+
percentage: { type: 'number' }
181+
}
182+
},
183+
recentJobs: {
184+
type: 'array',
185+
description: 'Recent jobs in batch (last 10)'
186+
},
187+
errors: {
188+
type: 'array',
189+
description: 'Failed jobs with error details'
190+
},
191+
estimatedTimeRemaining: {
192+
type: 'number',
193+
nullable: true,
194+
description: 'Estimated milliseconds until completion'
195+
}
196+
}
197+
}
198+
}
199+
},
200+
500: {
201+
type: 'object',
202+
properties: {
203+
success: { type: 'boolean' },
204+
error: { type: 'string' }
205+
}
206+
}
207+
}
208+
}
209+
}, async (request, reply) => {
210+
const { batchId } = request.params as { batchId: string };
211+
212+
try {
213+
const db = getDb();
214+
const jobQueueService = new JobQueueService(db, request.log);
215+
216+
const batchProgress = await jobQueueService.getBatchProgress(batchId);
217+
218+
const responseData = {
219+
success: true,
220+
data: batchProgress
221+
};
222+
const jsonString = JSON.stringify(responseData);
223+
return reply.status(200).type('application/json').send(jsonString);
224+
225+
} catch (error) {
226+
request.log.error({ error, batchId }, 'Failed to get batch progress');
227+
const errorResponse = {
228+
success: false,
229+
error: error instanceof Error ? error.message : 'Failed to get batch progress'
230+
};
231+
const jsonString = JSON.stringify(errorResponse);
232+
return reply.status(500).type('application/json').send(jsonString);
233+
}
234+
});
235+
236+
/**
237+
* Cancel active sync batch
238+
* Cancels all pending jobs in a batch
239+
*/
240+
server.post('/admin/mcp-registry/cancel/:batchId', {
241+
preValidation: requirePermission('mcp.registry.sync'),
242+
schema: {
243+
tags: ['Admin - MCP Registry'],
244+
summary: 'Cancel active sync batch',
245+
description: 'Cancel an active MCP Registry sync batch and all pending jobs',
246+
security: [{ cookieAuth: [] }],
247+
248+
params: {
249+
type: 'object',
250+
properties: {
251+
batchId: { type: 'string', description: 'Job batch ID to cancel' }
252+
},
253+
required: ['batchId']
254+
},
255+
256+
response: {
257+
200: {
258+
type: 'object',
259+
properties: {
260+
success: { type: 'boolean' },
261+
message: { type: 'string' },
262+
data: {
263+
type: 'object',
264+
properties: {
265+
batchId: { type: 'string' },
266+
cancelledJobs: { type: 'number' }
267+
}
268+
}
269+
}
270+
},
271+
500: {
272+
type: 'object',
273+
properties: {
274+
success: { type: 'boolean' },
275+
error: { type: 'string' }
276+
}
277+
}
278+
}
279+
}
280+
}, async (request, reply) => {
281+
const { batchId } = request.params as { batchId: string };
282+
283+
try {
284+
const db = getDb();
285+
const jobQueueService = new JobQueueService(db, request.log);
286+
287+
const cancelledJobs = await jobQueueService.cancelBatchJobs(batchId);
288+
289+
request.log.info({
290+
batchId,
291+
cancelledJobs,
292+
userId: request.user!.id,
293+
operation: 'mcp_sync_batch_cancelled'
294+
}, 'MCP Registry sync batch cancelled');
295+
296+
const responseData = {
297+
success: true,
298+
message: `Cancelled ${cancelledJobs} pending jobs in batch`,
299+
data: { batchId, cancelledJobs }
300+
};
301+
const jsonString = JSON.stringify(responseData);
302+
return reply.status(200).type('application/json').send(jsonString);
303+
304+
} catch (error) {
305+
request.log.error({ error, batchId }, 'Failed to cancel sync batch');
306+
const errorResponse = {
307+
success: false,
308+
error: error instanceof Error ? error.message : 'Failed to cancel sync batch'
309+
};
310+
const jsonString = JSON.stringify(errorResponse);
311+
return reply.status(500).type('application/json').send(jsonString);
312+
}
313+
});
314+
315+
/**
316+
* Retry failed jobs in batch
317+
* Resets and retries all failed jobs in a batch
318+
*/
319+
server.post('/admin/mcp-registry/retry/:batchId', {
320+
preValidation: requirePermission('mcp.registry.sync'),
321+
schema: {
322+
tags: ['Admin - MCP Registry'],
323+
summary: 'Retry failed jobs in batch',
324+
description: 'Retry all failed jobs in an MCP Registry sync batch',
325+
security: [{ cookieAuth: [] }],
326+
327+
params: {
328+
type: 'object',
329+
properties: {
330+
batchId: { type: 'string', description: 'Job batch ID to retry failed jobs' }
331+
},
332+
required: ['batchId']
333+
},
334+
335+
response: {
336+
200: {
337+
type: 'object',
338+
properties: {
339+
success: { type: 'boolean' },
340+
message: { type: 'string' },
341+
data: {
342+
type: 'object',
343+
properties: {
344+
batchId: { type: 'string' },
345+
retriedJobs: { type: 'number' }
346+
}
347+
}
348+
}
349+
},
350+
500: {
351+
type: 'object',
352+
properties: {
353+
success: { type: 'boolean' },
354+
error: { type: 'string' }
355+
}
356+
}
357+
}
358+
}
359+
}, async (request, reply) => {
360+
const { batchId } = request.params as { batchId: string };
361+
362+
try {
363+
const db = getDb();
364+
const jobQueueService = new JobQueueService(db, request.log);
365+
366+
const retriedJobs = await jobQueueService.retryFailedBatchJobs(batchId);
367+
368+
request.log.info({
369+
batchId,
370+
retriedJobs,
371+
userId: request.user!.id,
372+
operation: 'mcp_sync_batch_retry'
373+
}, 'Retried failed jobs in MCP Registry sync batch');
374+
375+
const responseData = {
376+
success: true,
377+
message: `Retried ${retriedJobs} failed jobs in batch`,
378+
data: { batchId, retriedJobs }
379+
};
380+
const jsonString = JSON.stringify(responseData);
381+
return reply.status(200).type('application/json').send(jsonString);
382+
383+
} catch (error) {
384+
request.log.error({ error, batchId }, 'Failed to retry batch jobs');
385+
const errorResponse = {
386+
success: false,
387+
error: error instanceof Error ? error.message : 'Failed to retry batch jobs'
388+
};
389+
const jsonString = JSON.stringify(errorResponse);
390+
return reply.status(500).type('application/json').send(jsonString);
391+
}
392+
});
393+
394+
/**
395+
* Get recent sync batches
396+
* Returns recent MCP registry sync operations for monitoring
397+
*/
398+
server.get('/admin/mcp-registry/batches', {
399+
preValidation: requirePermission('mcp.registry.sync'),
400+
schema: {
401+
tags: ['Admin - MCP Registry'],
402+
summary: 'Get recent sync batches',
403+
description: 'Get recent MCP Registry sync operations with progress information',
404+
security: [{ cookieAuth: [] }],
405+
406+
querystring: {
407+
type: 'object',
408+
properties: {
409+
limit: {
410+
type: 'number',
411+
minimum: 1,
412+
maximum: 50,
413+
default: 10,
414+
description: 'Number of batches to return'
415+
}
416+
}
417+
},
418+
419+
response: {
420+
200: {
421+
type: 'object',
422+
properties: {
423+
success: { type: 'boolean' },
424+
data: {
425+
type: 'array',
426+
items: {
427+
type: 'object',
428+
description: 'Batch information with progress'
429+
}
430+
}
431+
}
432+
},
433+
500: {
434+
type: 'object',
435+
properties: {
436+
success: { type: 'boolean' },
437+
error: { type: 'string' }
438+
}
439+
}
440+
}
441+
}
442+
}, async (request, reply) => {
443+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
444+
const { limit } = (request.query || {}) as any;
445+
446+
try {
447+
const db = getDb();
448+
const jobQueueService = new JobQueueService(db, request.log);
449+
450+
const recentBatches = await jobQueueService.getRecentBatches(
451+
'mcp_registry_sync',
452+
limit || 10
453+
);
454+
455+
const responseData = {
456+
success: true,
457+
data: recentBatches
458+
};
459+
const jsonString = JSON.stringify(responseData);
460+
return reply.status(200).type('application/json').send(jsonString);
461+
462+
} catch (error) {
463+
request.log.error({ error }, 'Failed to get recent batches');
464+
const errorResponse = {
465+
success: false,
466+
error: error instanceof Error ? error.message : 'Failed to get recent batches'
467+
};
468+
const jsonString = JSON.stringify(errorResponse);
469+
return reply.status(500).type('application/json').send(jsonString);
470+
}
471+
});
139472
}

0 commit comments

Comments
 (0)