Skip to content

Commit 777520c

Browse files
author
Lasim
committed
feat(backend): add MCP Registry sync endpoint and worker
1 parent 5d53296 commit 777520c

File tree

9 files changed

+1430
-8
lines changed

9 files changed

+1430
-8
lines changed

services/backend/api-spec.json

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24931,6 +24931,129 @@
2493124931
}
2493224932
}
2493324933
},
24934+
"/api/admin/mcp-registry/sync": {
24935+
"post": {
24936+
"summary": "Trigger MCP Registry sync via job queue",
24937+
"tags": [
24938+
"Admin - MCP Registry"
24939+
],
24940+
"description": "Trigger synchronization with official MCP Registry using background jobs for rate limiting. Progress can be monitored via GET /api/admin/jobs/batches/{batchId}",
24941+
"requestBody": {
24942+
"content": {
24943+
"application/json": {
24944+
"schema": {
24945+
"type": "object",
24946+
"properties": {
24947+
"maxServers": {
24948+
"type": "number",
24949+
"minimum": 1,
24950+
"maximum": 1000,
24951+
"description": "Maximum number of servers to sync (for testing). Omit to sync all servers."
24952+
},
24953+
"skipExisting": {
24954+
"type": "boolean",
24955+
"default": true,
24956+
"description": "Skip servers that already exist in the database"
24957+
},
24958+
"forceRefresh": {
24959+
"type": "boolean",
24960+
"default": false,
24961+
"description": "Force refresh of existing servers (overrides skipExisting)"
24962+
},
24963+
"rateLimitDelay": {
24964+
"type": "number",
24965+
"minimum": 1,
24966+
"maximum": 10,
24967+
"default": 2,
24968+
"description": "Delay between jobs in seconds (default: 2)"
24969+
}
24970+
},
24971+
"additionalProperties": false
24972+
}
24973+
}
24974+
}
24975+
},
24976+
"security": [
24977+
{
24978+
"cookieAuth": []
24979+
}
24980+
],
24981+
"responses": {
24982+
"200": {
24983+
"description": "Default Response",
24984+
"content": {
24985+
"application/json": {
24986+
"schema": {
24987+
"type": "object",
24988+
"properties": {
24989+
"success": {
24990+
"type": "boolean"
24991+
},
24992+
"data": {
24993+
"type": "object",
24994+
"properties": {
24995+
"batchId": {
24996+
"type": "string",
24997+
"description": "Job batch ID for tracking progress"
24998+
},
24999+
"totalServers": {
25000+
"type": "number",
25001+
"description": "Total servers to sync"
25002+
},
25003+
"jobsCreated": {
25004+
"type": "number",
25005+
"description": "Number of jobs created"
25006+
},
25007+
"estimatedCompletion": {
25008+
"type": "string",
25009+
"description": "Estimated completion time (ISO 8601)"
25010+
},
25011+
"rateLimitDelay": {
25012+
"type": "number",
25013+
"description": "Delay between jobs in seconds"
25014+
}
25015+
},
25016+
"required": [
25017+
"batchId",
25018+
"totalServers",
25019+
"jobsCreated",
25020+
"estimatedCompletion"
25021+
]
25022+
}
25023+
},
25024+
"required": [
25025+
"success",
25026+
"data"
25027+
]
25028+
}
25029+
}
25030+
}
25031+
},
25032+
"500": {
25033+
"description": "Default Response",
25034+
"content": {
25035+
"application/json": {
25036+
"schema": {
25037+
"type": "object",
25038+
"properties": {
25039+
"success": {
25040+
"type": "boolean"
25041+
},
25042+
"error": {
25043+
"type": "string"
25044+
}
25045+
},
25046+
"required": [
25047+
"success",
25048+
"error"
25049+
]
25050+
}
25051+
}
25052+
}
25053+
}
25054+
}
25055+
}
25056+
},
2493425057
"/api/gateway/me/mcp-configurations": {
2493525058
"get": {
2493625059
"summary": "Get merged MCP configurations for gateway",

services/backend/api-spec.yaml

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17656,6 +17656,93 @@ paths:
1765617656
- success
1765717657
- error
1765817658
description: Internal Server Error
17659+
/api/admin/mcp-registry/sync:
17660+
post:
17661+
summary: Trigger MCP Registry sync via job queue
17662+
tags:
17663+
- Admin - MCP Registry
17664+
description: Trigger synchronization with official MCP Registry using background
17665+
jobs for rate limiting. Progress can be monitored via GET
17666+
/api/admin/jobs/batches/{batchId}
17667+
requestBody:
17668+
content:
17669+
application/json:
17670+
schema:
17671+
type: object
17672+
properties:
17673+
maxServers:
17674+
type: number
17675+
minimum: 1
17676+
maximum: 1000
17677+
description: Maximum number of servers to sync (for testing). Omit to sync all
17678+
servers.
17679+
skipExisting:
17680+
type: boolean
17681+
default: true
17682+
description: Skip servers that already exist in the database
17683+
forceRefresh:
17684+
type: boolean
17685+
default: false
17686+
description: Force refresh of existing servers (overrides skipExisting)
17687+
rateLimitDelay:
17688+
type: number
17689+
minimum: 1
17690+
maximum: 10
17691+
default: 2
17692+
description: "Delay between jobs in seconds (default: 2)"
17693+
additionalProperties: false
17694+
security:
17695+
- cookieAuth: []
17696+
responses:
17697+
"200":
17698+
description: Default Response
17699+
content:
17700+
application/json:
17701+
schema:
17702+
type: object
17703+
properties:
17704+
success:
17705+
type: boolean
17706+
data:
17707+
type: object
17708+
properties:
17709+
batchId:
17710+
type: string
17711+
description: Job batch ID for tracking progress
17712+
totalServers:
17713+
type: number
17714+
description: Total servers to sync
17715+
jobsCreated:
17716+
type: number
17717+
description: Number of jobs created
17718+
estimatedCompletion:
17719+
type: string
17720+
description: Estimated completion time (ISO 8601)
17721+
rateLimitDelay:
17722+
type: number
17723+
description: Delay between jobs in seconds
17724+
required:
17725+
- batchId
17726+
- totalServers
17727+
- jobsCreated
17728+
- estimatedCompletion
17729+
required:
17730+
- success
17731+
- data
17732+
"500":
17733+
description: Default Response
17734+
content:
17735+
application/json:
17736+
schema:
17737+
type: object
17738+
properties:
17739+
success:
17740+
type: boolean
17741+
error:
17742+
type: string
17743+
required:
17744+
- success
17745+
- error
1765917746
/api/gateway/me/mcp-configurations:
1766017747
get:
1766117748
summary: Get merged MCP configurations for gateway

services/backend/src/permissions/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export const ROLE_DEFINITIONS = {
5151
'jobs.view',
5252
'jobs.monitor',
5353
'jobs.manage',
54+
'mcp.registry.sync',
5455
],
5556
global_user: [
5657
'profile.view',
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { type FastifyInstance } from 'fastify';
22
import emailRoutes from './email';
33
import jobsRoutes from './jobs';
4+
import mcpRegistryRoutes from './mcp-registry';
45

56
export default async function adminRoutes(fastify: FastifyInstance) {
67
await fastify.register(emailRoutes, { prefix: '/admin/email' });
78
await fastify.register(jobsRoutes);
9+
await fastify.register(mcpRegistryRoutes);
810
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import { type FastifyInstance } from 'fastify';
2+
import { RegistrySyncService } from '../../services/registrySyncService';
3+
import { JobQueueService } from '../../services/jobQueueService';
4+
import { getDb } from '../../db';
5+
import { requirePermission } from '../../middleware/roleMiddleware';
6+
7+
/**
8+
* Admin routes for MCP Registry synchronization
9+
*
10+
* These routes allow administrators to:
11+
* - Trigger sync from official MCP Registry
12+
* - Monitor sync progress via existing job queue APIs
13+
*/
14+
15+
export default async function mcpRegistryRoutes(server: FastifyInstance) {
16+
/**
17+
* Trigger MCP Registry sync via job queue
18+
*
19+
* This endpoint:
20+
* 1. Fetches server list from registry.modelcontextprotocol.io
21+
* 2. Creates a job batch for tracking
22+
* 3. Creates individual jobs for each server with rate-limited scheduling
23+
* 4. Returns batch ID for progress monitoring
24+
*
25+
* Progress can be monitored via: GET /api/admin/jobs/batches/{batchId}
26+
*/
27+
server.post('/admin/mcp-registry/sync', {
28+
preValidation: requirePermission('mcp.registry.sync'),
29+
schema: {
30+
tags: ['Admin - MCP Registry'],
31+
summary: 'Trigger MCP Registry sync via job queue',
32+
description: 'Trigger synchronization with official MCP Registry using background jobs for rate limiting. Progress can be monitored via GET /api/admin/jobs/batches/{batchId}',
33+
security: [{ cookieAuth: [] }],
34+
35+
body: {
36+
type: 'object',
37+
properties: {
38+
maxServers: {
39+
type: 'number',
40+
minimum: 1,
41+
maximum: 1000,
42+
description: 'Maximum number of servers to sync (for testing). Omit to sync all servers.'
43+
},
44+
skipExisting: {
45+
type: 'boolean',
46+
default: true,
47+
description: 'Skip servers that already exist in the database'
48+
},
49+
forceRefresh: {
50+
type: 'boolean',
51+
default: false,
52+
description: 'Force refresh of existing servers (overrides skipExisting)'
53+
},
54+
rateLimitDelay: {
55+
type: 'number',
56+
minimum: 1,
57+
maximum: 10,
58+
default: 2,
59+
description: 'Delay between jobs in seconds (default: 2)'
60+
}
61+
},
62+
additionalProperties: false
63+
},
64+
65+
response: {
66+
200: {
67+
type: 'object',
68+
properties: {
69+
success: { type: 'boolean' },
70+
data: {
71+
type: 'object',
72+
properties: {
73+
batchId: { type: 'string', description: 'Job batch ID for tracking progress' },
74+
totalServers: { type: 'number', description: 'Total servers to sync' },
75+
jobsCreated: { type: 'number', description: 'Number of jobs created' },
76+
estimatedCompletion: { type: 'string', description: 'Estimated completion time (ISO 8601)' },
77+
rateLimitDelay: { type: 'number', description: 'Delay between jobs in seconds' }
78+
},
79+
required: ['batchId', 'totalServers', 'jobsCreated', 'estimatedCompletion']
80+
}
81+
},
82+
required: ['success', 'data']
83+
},
84+
500: {
85+
type: 'object',
86+
properties: {
87+
success: { type: 'boolean' },
88+
error: { type: 'string' }
89+
},
90+
required: ['success', 'error']
91+
}
92+
}
93+
}
94+
}, async (request, reply) => {
95+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
96+
const config = (request.body || {}) as any;
97+
98+
request.log.info({
99+
operation: 'mcp_registry_sync_triggered',
100+
userId: request.user!.id,
101+
config,
102+
}, 'MCP Registry sync via job queue triggered');
103+
104+
try {
105+
const db = getDb();
106+
const jobQueueService = new JobQueueService(db, request.log);
107+
const syncService = new RegistrySyncService(db, request.log, jobQueueService);
108+
109+
const stats = await syncService.syncAllServersViaJobQueue({
110+
maxServers: config.maxServers || null,
111+
skipExisting: config.skipExisting !== false,
112+
forceRefresh: config.forceRefresh || false,
113+
rateLimitDelay: config.rateLimitDelay || 2,
114+
}, request.user!.id);
115+
116+
const responseData = {
117+
success: true,
118+
data: {
119+
batchId: stats.batchId,
120+
totalServers: stats.totalServers,
121+
jobsCreated: stats.jobsCreated,
122+
estimatedCompletion: stats.estimatedCompletion.toISOString(),
123+
rateLimitDelay: config.rateLimitDelay || 2,
124+
},
125+
};
126+
const jsonString = JSON.stringify(responseData);
127+
return reply.status(200).type('application/json').send(jsonString);
128+
129+
} catch (error) {
130+
request.log.error({ error }, 'MCP Registry sync via job queue failed');
131+
const errorResponse = {
132+
success: false,
133+
error: error instanceof Error ? error.message : 'Failed to start MCP Registry sync'
134+
};
135+
const jsonString = JSON.stringify(errorResponse);
136+
return reply.status(500).type('application/json').send(jsonString);
137+
}
138+
});
139+
}

0 commit comments

Comments
 (0)