Files
OllaHub/database/repositories/mcp.ts
T
Evandro Fonseca Junior f19bf10ef3 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.
2025-10-28 21:59:00 -03:00

421 lines
11 KiB
TypeScript

import Database from "better-sqlite3";
import path from "path";
const dbPath = path.join(process.cwd(), "database", "ollahub.db");
const db = new Database(dbPath);
export class MCPRepository {
/**
* Verificar se um MCP está instalado localmente
*/
static isInstalled(mcpId: string): boolean {
try {
const stmt = db.prepare("SELECT id FROM mcp_installations WHERE id = ?");
const result = stmt.get(mcpId);
return !!result;
} catch (error) {
console.error(`Error checking if MCP ${mcpId} is installed:`, error);
return false;
}
}
/**
* Salvar instalação de MCP no banco local
*/
static saveInstallation(mcpId: string, config: any, tools: any[]): void {
try {
// Tentar extrair owner/repo do ID
let owner: string;
let repo: string;
if (mcpId.includes("/")) {
const parts = mcpId.split("/");
owner = parts[0];
repo = parts.slice(1).join("/"); // Suportar múltiplas barras
} else {
// Se não tem /, usar mcpId como repo e extrair owner do config se disponível
owner = config.owner || config.author || "unknown";
repo = mcpId;
}
const stmt = db.prepare(`
INSERT OR REPLACE INTO mcp_installations
(id, owner, repo, name, config, tools, installed_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
`);
stmt.run(
mcpId,
owner,
repo,
config.name || repo,
JSON.stringify(config),
JSON.stringify(tools)
);
console.log(`MCP ${mcpId} saved to local database`);
} catch (error) {
console.error(`Error saving MCP ${mcpId} installation:`, error);
throw error;
}
}
/**
* Remover instalação de MCP do banco local
*/
static removeInstallation(mcpId: string): void {
try {
const stmt = db.prepare("DELETE FROM mcp_installations WHERE id = ?");
const result = stmt.run(mcpId);
if (result.changes > 0) {
console.log(`MCP ${mcpId} removed from local database`);
} else {
console.log(`MCP ${mcpId} was not found in local database`);
}
} catch (error) {
console.error(`Error removing MCP ${mcpId} installation:`, error);
throw error;
}
}
/**
* Listar todos os MCPs instalados localmente
*/
static listInstalled(): string[] {
try {
const stmt = db.prepare(
"SELECT id FROM mcp_installations ORDER BY installed_at DESC"
);
const results = stmt.all() as { id: string }[];
return results.map((row) => row.id);
} catch (error) {
console.error("Error listing installed MCPs:", error);
return [];
}
}
/**
* Obter configuração local de um MCP
*/
static getLocalConfig(mcpId: string): any {
try {
const stmt = db.prepare(
"SELECT config FROM mcp_installations WHERE id = ?"
);
const result = stmt.get(mcpId) as { config: string } | undefined;
if (result) {
return JSON.parse(result.config);
}
return null;
} catch (error) {
console.error(`Error getting local config for MCP ${mcpId}:`, error);
return null;
}
}
/**
* Obter ferramentas locais de um MCP
*/
static getLocalTools(mcpId: string): any[] {
try {
const stmt = db.prepare(
"SELECT tools FROM mcp_installations WHERE id = ?"
);
const result = stmt.get(mcpId) as { tools: string } | undefined;
if (result) {
return JSON.parse(result.tools);
}
return [];
} catch (error) {
console.error(`Error getting local tools for MCP ${mcpId}:`, error);
return [];
}
}
/**
* Obter informações completas de um MCP instalado
*/
static getInstalledMCP(mcpId: string): any {
try {
const stmt = db.prepare(`
SELECT id, owner, repo, name, config, tools, installed_at, updated_at
FROM mcp_installations WHERE id = ?
`);
const result = stmt.get(mcpId) as any;
if (result) {
return {
...result,
config: JSON.parse(result.config),
tools: JSON.parse(result.tools),
};
}
return null;
} catch (error) {
console.error(`Error getting installed MCP ${mcpId}:`, error);
return null;
}
}
/**
* Listar todos os MCPs instalados com informações completas
*/
static listInstalledWithDetails(): any[] {
try {
const stmt = db.prepare(`
SELECT id, owner, repo, name, config, tools, installed_at, updated_at
FROM mcp_installations ORDER BY installed_at DESC
`);
const results = stmt.all() as any[];
return results.map((row) => ({
...row,
config: JSON.parse(row.config),
tools: JSON.parse(row.tools),
}));
} catch (error) {
console.error("Error listing installed MCPs with details:", error);
return [];
}
}
/**
* Atualizar timestamp de um MCP instalado
*/
static updateTimestamp(mcpId: string): void {
try {
const stmt = db.prepare(
"UPDATE mcp_installations SET updated_at = CURRENT_TIMESTAMP WHERE id = ?"
);
stmt.run(mcpId);
} catch (error) {
console.error(`Error updating timestamp for MCP ${mcpId}:`, error);
}
}
/**
* Atualizar configuração de um MCP instalado
*/
static updateConfig(mcpId: string, newConfig: any): void {
try {
const stmt = db.prepare(`
UPDATE mcp_installations
SET config = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?
`);
const result = stmt.run(JSON.stringify(newConfig), mcpId);
if (result.changes > 0) {
console.log(`MCP ${mcpId} configuration updated successfully`);
} else {
console.log(`MCP ${mcpId} was not found for configuration update`);
throw new Error(`MCP ${mcpId} not found`);
}
} catch (error) {
console.error(`Error updating configuration for MCP ${mcpId}:`, error);
throw error;
}
}
/**
* Verificar integridade do banco de dados
*/
static checkDatabaseIntegrity(): boolean {
try {
const result = db.prepare("PRAGMA integrity_check").get() as {
integrity_check: string;
};
return result.integrity_check === "ok";
} catch (error) {
console.error("Error checking database integrity:", error);
return false;
}
}
/**
* Obter estatísticas das instalações
*/
static getInstallationStats(): any {
try {
const totalStmt = db.prepare(
"SELECT COUNT(*) as total FROM mcp_installations"
);
const totalResult = totalStmt.get() as { total: number };
const recentStmt = db.prepare(`
SELECT COUNT(*) as recent
FROM mcp_installations
WHERE installed_at > datetime('now', '-7 days')
`);
const recentResult = recentStmt.get() as { recent: number };
const categoriesStmt = db.prepare(`
SELECT COUNT(DISTINCT owner) as unique_owners
FROM mcp_installations
`);
const categoriesResult = categoriesStmt.get() as {
unique_owners: number;
};
return {
total: totalResult.total,
recent: recentResult.recent,
uniqueOwners: categoriesResult.unique_owners,
};
} catch (error) {
console.error("Error getting installation stats:", error);
return { total: 0, recent: 0, uniqueOwners: 0 };
}
}
/**
* Atualizar status de instalação de um MCP
*/
static updateInstallationStatus(
mcpId: string,
status: string,
message?: string
): void {
try {
const stmt = db.prepare(`
UPDATE mcp_installations
SET status = ?, status_message = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?
`);
stmt.run(status, message || null, mcpId);
console.log(`MCP ${mcpId} status updated to: ${status}`);
} catch (error) {
console.error(`Error updating status for MCP ${mcpId}:`, error);
throw error;
}
}
/**
* Salvar informações de ambiente de um MCP instalado
*/
static saveEnvironment(mcpId: string, environment: any): void {
try {
const stmt = db.prepare(`
UPDATE mcp_installations
SET environment_path = ?,
executable_command = ?,
updated_at = CURRENT_TIMESTAMP
WHERE id = ?
`);
const executableCommand = `${environment.executable} ${(
environment.args || []
).join(" ")}`.trim();
stmt.run(environment.path, executableCommand, mcpId);
console.log(`MCP ${mcpId} environment saved`);
} catch (error) {
console.error(`Error saving environment for MCP ${mcpId}:`, error);
throw error;
}
}
/**
* Obter ambiente de um MCP instalado
*/
static getEnvironment(mcpId: string): any | null {
try {
const stmt = db.prepare(`
SELECT environment_path, executable_command
FROM mcp_installations
WHERE id = ?
`);
const result = stmt.get(mcpId) as any;
if (!result || !result.environment_path) {
return null;
}
// Reconstruir objeto de ambiente
const parts = result.executable_command.split(" ");
return {
path: result.environment_path,
executable: parts[0],
args: parts.slice(1),
};
} catch (error) {
console.error(`Error getting environment for MCP ${mcpId}:`, error);
return null;
}
}
/**
* Adicionar logs de instalação
*/
static appendLogs(mcpId: string, logs: string[]): void {
try {
const stmt = db.prepare(`
SELECT install_logs FROM mcp_installations WHERE id = ?
`);
const result = stmt.get(mcpId) as
| { install_logs: string | null }
| undefined;
const existingLogs = result?.install_logs
? JSON.parse(result.install_logs)
: [];
const updatedLogs = [...existingLogs, ...logs];
// Limitar a 1000 linhas de log
const trimmedLogs = updatedLogs.slice(-1000);
const updateStmt = db.prepare(`
UPDATE mcp_installations
SET install_logs = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?
`);
updateStmt.run(JSON.stringify(trimmedLogs), mcpId);
} catch (error) {
console.error(`Error appending logs for MCP ${mcpId}:`, error);
}
}
/**
* Salvar resultado de validação
*/
static saveValidationResult(mcpId: string, validationResult: any): void {
try {
const stmt = db.prepare(`
UPDATE mcp_installations
SET validation_result = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?
`);
stmt.run(JSON.stringify(validationResult), mcpId);
console.log(`MCP ${mcpId} validation result saved`);
} catch (error) {
console.error(`Error saving validation result for MCP ${mcpId}:`, error);
}
}
/**
* Salvar ferramentas validadas
*/
static saveTools(mcpId: string, tools: any[]): void {
try {
const stmt = db.prepare(`
UPDATE mcp_installations
SET tools = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?
`);
stmt.run(JSON.stringify(tools), mcpId);
console.log(`MCP ${mcpId} tools saved: ${tools.length} tools`);
} catch (error) {
console.error(`Error saving tools for MCP ${mcpId}:`, error);
}
}
}