feat: add instance lifecycle management with restart, inspect, and CLI commands
Adds restart/inspect methods to InstanceService, state validation for stop, REST endpoints for restart and inspect, and full CLI command suite for instance list/start/stop/restart/remove/logs/inspect. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,16 @@
|
||||
import type { McpInstance } from '@prisma/client';
|
||||
import type { IMcpInstanceRepository, IMcpServerRepository } from '../repositories/interfaces.js';
|
||||
import type { McpOrchestrator, ContainerSpec } from './orchestrator.js';
|
||||
import type { McpOrchestrator, ContainerSpec, ContainerInfo } from './orchestrator.js';
|
||||
import { NotFoundError } from './mcp-server.service.js';
|
||||
|
||||
export class InvalidStateError extends Error {
|
||||
readonly statusCode = 409;
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = 'InvalidStateError';
|
||||
}
|
||||
}
|
||||
|
||||
export class InstanceService {
|
||||
constructor(
|
||||
private instanceRepo: IMcpInstanceRepository,
|
||||
@@ -71,6 +79,9 @@ export class InstanceService {
|
||||
|
||||
async stop(id: string): Promise<McpInstance> {
|
||||
const instance = await this.getById(id);
|
||||
if (instance.status === 'STOPPED') {
|
||||
throw new InvalidStateError(`Instance '${id}' is already stopped`);
|
||||
}
|
||||
if (!instance.containerId) {
|
||||
return this.instanceRepo.updateStatus(id, 'STOPPED');
|
||||
}
|
||||
@@ -87,6 +98,37 @@ export class InstanceService {
|
||||
}
|
||||
}
|
||||
|
||||
async restart(id: string): Promise<McpInstance> {
|
||||
const instance = await this.getById(id);
|
||||
|
||||
// Stop if running
|
||||
if (instance.containerId && (instance.status === 'RUNNING' || instance.status === 'STARTING')) {
|
||||
try {
|
||||
await this.orchestrator.stopContainer(instance.containerId);
|
||||
} catch {
|
||||
// Container may already be stopped
|
||||
}
|
||||
try {
|
||||
await this.orchestrator.removeContainer(instance.containerId, true);
|
||||
} catch {
|
||||
// Container may already be gone
|
||||
}
|
||||
}
|
||||
|
||||
await this.instanceRepo.delete(id);
|
||||
|
||||
// Start a fresh instance for the same server
|
||||
return this.start(instance.serverId);
|
||||
}
|
||||
|
||||
async inspect(id: string): Promise<ContainerInfo> {
|
||||
const instance = await this.getById(id);
|
||||
if (!instance.containerId) {
|
||||
throw new InvalidStateError(`Instance '${id}' has no container`);
|
||||
}
|
||||
return this.orchestrator.inspectContainer(instance.containerId);
|
||||
}
|
||||
|
||||
async remove(id: string): Promise<void> {
|
||||
const instance = await this.getById(id);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user