UNPKG

@vessl-ai/mcpctl-control-plane

Version:

Vessl MCP Control Plane service for managing distributed jobs.

203 lines 7.96 kB
var ServerService_1; import { __decorate, __metadata } from "tslib"; import { Injectable, Logger, } from '@nestjs/common'; import { TransportType } from '@vessl-ai/mcpctl-shared/types/common'; import { ServerInstanceStatus, } from '@vessl-ai/mcpctl-shared/types/domain/server'; import { generateServerInstanceId, generateServerRunSpecId, } from '@vessl-ai/mcpctl-shared/util'; import { spawn } from 'child_process'; import { AppCacheService } from '../cache/appcache.service'; import { ServerCacheKeys } from '../types/cache'; import { findFreePort } from '../util/network'; let ServerService = ServerService_1 = class ServerService { appCacheService; logger = new Logger(ServerService_1.name); constructor(appCacheService) { this.appCacheService = appCacheService; } instances = {}; async onModuleInit() { this.logger.log('Initializing server service...'); const instances = await this.appCacheService.get(ServerCacheKeys.INSTANCE); if (instances) { this.instances = instances.reduce((acc, instance) => { acc[instance.id] = instance; return acc; }, {}); } } async onModuleDestroy() { this.logger.log('Disposing server service...'); await this.dispose(); } async start(runSpec) { runSpec.id = generateServerRunSpecId(); let instance; switch (runSpec.transport.type) { case TransportType.Stdio: instance = await this.startStdioServer(runSpec); break; case TransportType.Sse: instance = await this.startSseServer(runSpec); break; case TransportType.StreamableHttp: instance = await this.startStreamableHttpServer(runSpec); break; } this.instances[instance.id] = instance; await this.upsertRunSpecList(runSpec); await this.upsertInstanceList(instance); this.logger.debug(`Started server ${instance.id} with spec ${JSON.stringify(runSpec)}`); return this.sanitizeResponseInstance(instance); } async startStdioServer(runSpec) { const serverInstance = { id: generateServerInstanceId(), name: runSpec.name, runSpec, status: ServerInstanceStatus.Running, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), host: 'localhost', port: await findFreePort(), transport: runSpec.transport, }; const child = spawn('npx', [ 'supergateway', 'start', '--transport', runSpec.transport.type, '--port', serverInstance.port.toString(), '--baseUrl', `http://${serverInstance.host}:${serverInstance.port}`, '--ssePath', '/sse', '--messagePath', '/message', ]); serverInstance.processId = child.pid; serverInstance.processHandle = child; serverInstance.transport = { type: TransportType.Sse, }; serverInstance.connectionUrl = `http://${serverInstance.host}:${serverInstance.port}/sse`; return serverInstance; } async startSseServer(runSpec) { throw new Error('Not implemented'); } async startStreamableHttpServer(runSpec) { throw new Error('Not implemented'); } async upsertRunSpecList(runSpec) { const runSpecList = await this.appCacheService.get(ServerCacheKeys.RUN_SPEC); if (!runSpecList) { await this.appCacheService.set(ServerCacheKeys.RUN_SPEC, [runSpec]); } else { runSpecList.push(runSpec); await this.appCacheService.set(ServerCacheKeys.RUN_SPEC, runSpecList); } } async upsertInstanceList(instance) { const instanceList = await this.appCacheService.get(ServerCacheKeys.INSTANCE); if (!instanceList) { await this.appCacheService.set(ServerCacheKeys.INSTANCE, [instance]); } else { instanceList.push(instance); await this.appCacheService.set(ServerCacheKeys.INSTANCE, instanceList); } } async deleteRunSpecList(runSpec) { const runSpecList = await this.appCacheService.get(ServerCacheKeys.RUN_SPEC); if (runSpecList) { runSpecList.splice(runSpecList.indexOf(runSpec), 1); await this.appCacheService.set(ServerCacheKeys.RUN_SPEC, runSpecList); } } async deleteInstanceList(instance) { const instanceList = await this.appCacheService.get(ServerCacheKeys.INSTANCE); if (instanceList) { instanceList.splice(instanceList.indexOf(instance), 1); await this.appCacheService.set(ServerCacheKeys.INSTANCE, instanceList); } } async stopInstance(name) { const instance = await this.getInstanceByName(name); if (!instance) { throw new Error(`Instance ${name} not found`); } const instanceProcessHandle = this.instances[instance.id].processHandle; if (instanceProcessHandle) { instanceProcessHandle.kill(); } else { if (instance.processId) { try { process.kill(instance.processId); } catch (error) { this.logger.error(`Failed to kill process ${instance.processId}`); } } } instance.status = ServerInstanceStatus.Stopped; await this.upsertInstanceList(instance); return this.sanitizeResponseInstance(instance); } async restartInstance(name) { const instance = await this.stopInstance(name); return this.start(instance.runSpec); } async listInstances() { const instances = await this.appCacheService.get(ServerCacheKeys.INSTANCE); if (!instances) { return []; } this.logger.debug(`Listing servers: ${JSON.stringify(instances)}`); return this.sanitizeResponseInstanceList(instances); } async listRunSpecs() { const runSpecs = await this.appCacheService.get(ServerCacheKeys.RUN_SPEC); this.logger.debug(`Listing servers: ${JSON.stringify(runSpecs)}`); return runSpecs ?? []; } async getInstanceByName(name) { const instances = await this.appCacheService.get(ServerCacheKeys.INSTANCE); const result = instances?.find((instance) => instance.name === name); return result ? this.sanitizeResponseInstance(result) : undefined; } async getRunSpecByName(name) { const runSpecs = await this.appCacheService.get(ServerCacheKeys.RUN_SPEC); return runSpecs?.find((runSpec) => runSpec.name === name); } async dispose() { this.logger.log('Stopping all instances...'); const instances = await this.listInstances(); for (const instance of instances) { await this.stopInstance(instance.name); } this.logger.log('All instances stopped'); this.logger.log('Backing up cache...'); const cache = await this.appCacheService.get(ServerCacheKeys.RUN_SPEC); this.logger.log('Cache backed up'); this.logger.log('Clearing cache...'); await this.appCacheService.clear(); } sanitizeResponseInstance(instance) { return { ...instance, processHandle: undefined, }; } sanitizeResponseInstanceList(instances) { return instances.map((instance) => this.sanitizeResponseInstance(instance)); } }; ServerService = ServerService_1 = __decorate([ Injectable(), __metadata("design:paramtypes", [AppCacheService]) ], ServerService); export { ServerService }; //# sourceMappingURL=server.service.js.map