feat(mcp): enhance MCP installation with status tracking and custom configs

- Add /mcp-servers/ to .gitignore for MCP server installations
- Extend mcp_installations table with status, logs, and validation fields
- Refactor MCP install route to support custom configs, enable logs, and async installation
- Improve MCPSyncService with promise-based syncing to prevent concurrent operations

This update improves MCP management by adding robust installation tracking, custom configuration support, and better error handling for a more reliable user experience.
This commit is contained in:
Evandro Fonseca Junior committed 2025-10-28 21:59:00 -03:00
1 parent 1042f2136b
commit f19bf10ef3
35 files changed
+4743 -429

No files matched your search

+214
View File
@@ -0,0 +1,214 @@
/**
* Serviço de gerenciamento de ambientes isolados para servidores MCP
*/
import { mkdir, rm, access } from "fs/promises";
import path from "path";
import { MCP_CONFIG } from "@/lib/config/mcp";
import { commandExists, spawnWithProgress } from "@/lib/utils/system";
import type {
MCPEnvironment,
SystemDependencies,
} from "@/lib/types/mcp-installer";
export class MCPEnvironmentService {
/**
* Verifica dependências do sistema
*/
static async checkSystemDependencies(): Promise<SystemDependencies> {
const [node, npm, python, pip, cargo, git] = await Promise.all([
commandExists("node"),
commandExists("npm"),
commandExists("python3"),
commandExists("pip3"),
commandExists("cargo"),
commandExists("git"),
]);
return { node, npm, python, pip, cargo, git };
}
/**
* Garante que o diretório de ambiente existe
*/
static async ensureEnvironmentDirectory(
type: "npm" | "python" | "rust"
): Promise<string> {
let dir: string;
switch (type) {
case "npm":
dir = MCP_CONFIG.npmDir;
break;
case "python":
dir = MCP_CONFIG.pythonDir;
break;
case "rust":
dir = MCP_CONFIG.rustDir;
break;
}
try {
await mkdir(dir, { recursive: true });
console.log(`✓ Environment directory ensured: ${dir}`);
return dir;
} catch (error: any) {
throw new Error(
`Failed to create environment directory: ${error.message}`
);
}
}
/**
* Cria ambiente virtual Python para um MCP específico
*/
static async createPythonVenv(
mcpId: string,
onProgress?: (message: string) => void
): Promise<string> {
const venvPath = path.join(MCP_CONFIG.pythonDir, mcpId);
// Verificar se já existe
try {
await access(venvPath);
onProgress?.(`Virtual environment already exists at ${venvPath}`);
return venvPath;
} catch {
// Não existe, criar novo
}
onProgress?.("Creating Python virtual environment...");
try {
const result = await spawnWithProgress(
"python3",
["-m", "venv", venvPath],
{
onOutput: (data) => onProgress?.(data.trim()),
timeout: 60000, // 1 minuto para criar venv
}
);
if (result.exitCode !== 0) {
throw new Error(
`Failed to create venv: ${result.stderr || result.stdout}`
);
}
onProgress?.(`✓ Virtual environment created at ${venvPath}`);
return venvPath;
} catch (error: any) {
throw new Error(`Failed to create Python venv: ${error.message}`);
}
}
/**
* Obtém caminho do executável para um ambiente
*/
static getExecutablePath(
type: "npm" | "python" | "rust",
mcpId: string,
packageName?: string
): { executable: string; args: string[] } {
switch (type) {
case "npm":
// Para npm, usar npx para executar pacotes instalados localmente
return {
executable: "npx",
args: packageName ? ["--prefix", MCP_CONFIG.npmDir, packageName] : [],
};
case "python": {
const venvPath = path.join(MCP_CONFIG.pythonDir, mcpId);
const pythonBin =
process.platform === "win32"
? path.join(venvPath, "Scripts", "python.exe")
: path.join(venvPath, "bin", "python");
return {
executable: pythonBin,
args: packageName ? ["-m", packageName] : [],
};
}
case "rust": {
// Para Rust, o binário estará em target/release/
const binaryPath = path.join(
MCP_CONFIG.rustDir,
mcpId,
"target",
"release",
packageName || mcpId
);
return {
executable: binaryPath,
args: [],
};
}
}
}
/**
* Limpa ambiente de um MCP (desinstalação)
*/
static async cleanupEnvironment(environment: MCPEnvironment): Promise<void> {
try {
console.log(`Cleaning up environment at ${environment.path}`);
await rm(environment.path, { recursive: true, force: true });
console.log(`✓ Environment cleaned: ${environment.path}`);
} catch (error: any) {
console.error(`Warning: Failed to cleanup environment: ${error.message}`);
// Não lançar erro - cleanup é best-effort
}
}
/**
* Verifica se um ambiente está válido
*/
static async validateEnvironment(
environment: MCPEnvironment
): Promise<boolean> {
try {
// Verificar se o diretório existe
await access(environment.path);
// Verificar se o executável existe
await access(environment.executable);
return true;
} catch {
return false;
}
}
/**
* Obtém informações sobre o ambiente de um MCP
*/
static async getEnvironmentInfo(
mcpId: string,
type: "npm" | "python" | "rust"
): Promise<{
exists: boolean;
path: string;
size?: number;
}> {
const envPath = path.join(
type === "npm"
? MCP_CONFIG.npmDir
: type === "python"
? path.join(MCP_CONFIG.pythonDir, mcpId)
: path.join(MCP_CONFIG.rustDir, mcpId)
);
try {
await access(envPath);
// TODO: Calcular tamanho do diretório se necessário
return { exists: true, path: envPath };
} catch {
return { exists: false, path: envPath };
}
}
}