Skip to content

Commit f81f835

Browse files
author
Lasim
committed
feat(gateway): add selective restart functionality for MCP servers and enhance configuration change handling
1 parent b65fade commit f81f835

File tree

4 files changed

+580
-5
lines changed

4 files changed

+580
-5
lines changed

services/gateway/src/core/server/proxy.ts

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,19 @@ export class ProxyServer {
212212
return this.getStatus();
213213
});
214214

215+
// MCP Server Management API endpoints for selective restart
216+
this.fastify.post('/api/mcp/servers', async (request, reply) => {
217+
await this.handleAddServer(request, reply);
218+
});
219+
220+
this.fastify.delete('/api/mcp/servers/:serverName', async (request, reply) => {
221+
await this.handleRemoveServer(request, reply);
222+
});
223+
224+
this.fastify.post('/api/mcp/servers/:serverName/restart', async (request, reply) => {
225+
await this.handleRestartServer(request, reply);
226+
});
227+
215228
// Logs streaming endpoint - real-time log streaming via SSE
216229
this.fastify.get('/logs/stream', async (request, reply) => {
217230
await this.handleLogsStream(request, reply);
@@ -1130,4 +1143,181 @@ export class ProxyServer {
11301143
console.error(chalk.red('Failed to load team MCP configuration:'), error);
11311144
}
11321145
}
1146+
1147+
/**
1148+
* Handle adding a new MCP server (selective restart API)
1149+
*/
1150+
private async handleAddServer(request: FastifyRequest, reply: FastifyReply): Promise<void> {
1151+
try {
1152+
const body = request.body as any;
1153+
const config = body.config as MCPServerConfig;
1154+
1155+
if (!config || !config.installation_name) {
1156+
reply.code(400).send({
1157+
error: 'Invalid request',
1158+
message: 'Missing server configuration'
1159+
});
1160+
return;
1161+
}
1162+
1163+
console.log(chalk.blue(`[API] Adding MCP server: ${config.installation_name}`));
1164+
1165+
// Check if server already exists
1166+
const existingProcess = this.processManager.getProcessByName(config.installation_name);
1167+
if (existingProcess && existingProcess.status === 'running') {
1168+
reply.code(409).send({
1169+
error: 'Server already exists',
1170+
message: `MCP server ${config.installation_name} is already running`
1171+
});
1172+
return;
1173+
}
1174+
1175+
// Spawn the new process
1176+
const processInfo = await this.processManager.spawnProcess(config);
1177+
1178+
console.log(chalk.green(`[API] Successfully added MCP server: ${config.installation_name}`));
1179+
1180+
reply.code(201).send({
1181+
success: true,
1182+
message: `MCP server ${config.installation_name} added successfully`,
1183+
server: {
1184+
name: config.installation_name,
1185+
status: processInfo.status,
1186+
pid: processInfo.process.pid
1187+
}
1188+
});
1189+
1190+
} catch (error) {
1191+
console.error(chalk.red(`[API] Error adding server:`), error);
1192+
1193+
reply.code(500).send({
1194+
error: 'Internal server error',
1195+
message: error instanceof Error ? error.message : String(error)
1196+
});
1197+
}
1198+
}
1199+
1200+
/**
1201+
* Handle removing an MCP server (selective restart API)
1202+
*/
1203+
private async handleRemoveServer(request: FastifyRequest, reply: FastifyReply): Promise<void> {
1204+
try {
1205+
const params = request.params as any;
1206+
const serverName = params.serverName;
1207+
1208+
if (!serverName) {
1209+
reply.code(400).send({
1210+
error: 'Invalid request',
1211+
message: 'Missing server name'
1212+
});
1213+
return;
1214+
}
1215+
1216+
console.log(chalk.blue(`[API] Removing MCP server: ${serverName}`));
1217+
1218+
// Find the process
1219+
const processInfo = this.processManager.getProcessByName(serverName);
1220+
if (!processInfo) {
1221+
reply.code(404).send({
1222+
error: 'Server not found',
1223+
message: `MCP server ${serverName} not found`
1224+
});
1225+
return;
1226+
}
1227+
1228+
// Terminate the process
1229+
await this.processManager.terminateProcess(processInfo, 10000);
1230+
1231+
console.log(chalk.green(`[API] Successfully removed MCP server: ${serverName}`));
1232+
1233+
reply.code(200).send({
1234+
success: true,
1235+
message: `MCP server ${serverName} removed successfully`
1236+
});
1237+
1238+
} catch (error) {
1239+
console.error(chalk.red(`[API] Error removing server:`), error);
1240+
1241+
reply.code(500).send({
1242+
error: 'Internal server error',
1243+
message: error instanceof Error ? error.message : String(error)
1244+
});
1245+
}
1246+
}
1247+
1248+
/**
1249+
* Handle restarting an MCP server (selective restart API)
1250+
*/
1251+
private async handleRestartServer(request: FastifyRequest, reply: FastifyReply): Promise<void> {
1252+
try {
1253+
const params = request.params as any;
1254+
const body = request.body as any;
1255+
const serverName = params.serverName;
1256+
const config = body.config as MCPServerConfig;
1257+
1258+
if (!serverName) {
1259+
reply.code(400).send({
1260+
error: 'Invalid request',
1261+
message: 'Missing server name'
1262+
});
1263+
return;
1264+
}
1265+
1266+
if (!config || !config.installation_name) {
1267+
reply.code(400).send({
1268+
error: 'Invalid request',
1269+
message: 'Missing server configuration'
1270+
});
1271+
return;
1272+
}
1273+
1274+
console.log(chalk.blue(`[API] Restarting MCP server: ${serverName}`));
1275+
1276+
// Find the existing process
1277+
const existingProcess = this.processManager.getProcessByName(serverName);
1278+
if (!existingProcess) {
1279+
// Server doesn't exist, just start it
1280+
const processInfo = await this.processManager.spawnProcess(config);
1281+
1282+
console.log(chalk.green(`[API] Started MCP server: ${serverName} (was not running)`));
1283+
1284+
reply.code(200).send({
1285+
success: true,
1286+
message: `MCP server ${serverName} started successfully`,
1287+
server: {
1288+
name: config.installation_name,
1289+
status: processInfo.status,
1290+
pid: processInfo.process.pid
1291+
}
1292+
});
1293+
return;
1294+
}
1295+
1296+
// Restart the existing process
1297+
const processInfo = await this.processManager.restartServer(serverName, {
1298+
timeout: 10000,
1299+
showProgress: false
1300+
});
1301+
1302+
console.log(chalk.green(`[API] Successfully restarted MCP server: ${serverName}`));
1303+
1304+
reply.code(200).send({
1305+
success: true,
1306+
message: `MCP server ${serverName} restarted successfully`,
1307+
server: {
1308+
name: config.installation_name,
1309+
status: processInfo.status,
1310+
pid: processInfo.process.pid
1311+
}
1312+
});
1313+
1314+
} catch (error) {
1315+
console.error(chalk.red(`[API] Error restarting server:`), error);
1316+
1317+
reply.code(500).send({
1318+
error: 'Internal server error',
1319+
message: error instanceof Error ? error.message : String(error)
1320+
});
1321+
}
1322+
}
11331323
}

services/gateway/src/services/configuration-change-service.ts

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import chalk from 'chalk';
22
import inquirer from 'inquirer';
33
import { TeamMCPConfig, MCPServerConfig } from '../types/mcp';
44
import { ServerRestartService } from './server-restart-service';
5+
import { SelectiveRestartService } from './selective-restart-service';
56

67
export interface ConfigurationChangeInfo {
78
hasChanges: boolean;
@@ -17,9 +18,11 @@ export interface ConfigurationChangeInfo {
1718
*/
1819
export class ConfigurationChangeService {
1920
private restartService: ServerRestartService;
21+
private selectiveRestartService: SelectiveRestartService;
2022

2123
constructor() {
2224
this.restartService = new ServerRestartService();
25+
this.selectiveRestartService = new SelectiveRestartService();
2326
}
2427

2528
/**
@@ -125,13 +128,15 @@ export class ConfigurationChangeService {
125128

126129
/**
127130
* Handle configuration changes with interactive restart prompt
131+
* Uses selective restart when possible, falls back to full restart
128132
*/
129-
async handleConfigurationChanges(changeInfo: ConfigurationChangeInfo): Promise<void> {
133+
async handleConfigurationChanges(
134+
changeInfo: ConfigurationChangeInfo,
135+
newServerConfigs?: MCPServerConfig[]
136+
): Promise<void> {
130137
console.log(chalk.blue('\n🔄 Configuration changes detected:'));
131138
changeInfo.changes.forEach(change => console.log(` ${change}`));
132139

133-
console.log(chalk.yellow('\n⚠️ Gateway restart required for changes to take effect.'));
134-
135140
// Check if gateway is running
136141
const isRunning = this.restartService.isServerRunning();
137142

@@ -140,7 +145,45 @@ export class ConfigurationChangeService {
140145
return;
141146
}
142147

143-
// Prompt user for restart
148+
// Try selective restart first if we have server configs
149+
if (newServerConfigs && await this.selectiveRestartService.isGatewayRunning()) {
150+
console.log(chalk.blue('\nSelective restart available - only affected servers will be restarted.'));
151+
152+
const { useSelectiveRestart } = await inquirer.prompt([
153+
{
154+
type: 'confirm',
155+
name: 'useSelectiveRestart',
156+
message: 'Use selective restart (faster, no interruption to unchanged servers)?',
157+
default: true
158+
}
159+
]);
160+
161+
if (useSelectiveRestart) {
162+
try {
163+
const result = await this.selectiveRestartService.performSelectiveRestart(
164+
changeInfo,
165+
newServerConfigs,
166+
{ showProgress: true }
167+
);
168+
169+
if (result.success) {
170+
console.log(chalk.green('\n✅ Selective restart completed successfully'));
171+
console.log(chalk.gray('All changes applied without full gateway restart'));
172+
return;
173+
} else {
174+
console.log(chalk.yellow('\nSelective restart completed with errors'));
175+
console.log(chalk.gray('Falling back to full gateway restart...'));
176+
}
177+
} catch (error) {
178+
console.log(chalk.red(`❌ Selective restart failed: ${error instanceof Error ? error.message : String(error)}`));
179+
console.log(chalk.gray('Falling back to full gateway restart...'));
180+
}
181+
}
182+
}
183+
184+
// Fall back to full gateway restart
185+
console.log(chalk.yellow('\nFull gateway restart required for changes to take effect.'));
186+
144187
const { shouldRestart } = await inquirer.prompt([
145188
{
146189
type: 'confirm',

services/gateway/src/services/refresh-service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ export class RefreshService {
9898

9999
// Step 2: Handle configuration changes
100100
if (changeInfo.hasChanges) {
101-
await this.changeService.handleConfigurationChanges(changeInfo);
101+
await this.changeService.handleConfigurationChanges(changeInfo, teamMCPConfig.servers);
102102
} else {
103103
console.log(chalk.green('✅ No configuration changes detected - your MCP servers are up to date'));
104104
console.log(chalk.gray('💡 All servers are already running with the latest configuration'));

0 commit comments

Comments
 (0)