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:
1 parent
1042f2136b
commit
f19bf10ef3
35 files changed
+4743
-429
No files matched your search
+214
-20
@@ -24,9 +24,18 @@ export class MCPRepository {
|
||||
*/
|
||||
static saveInstallation(mcpId: string, config: any, tools: any[]): void {
|
||||
try {
|
||||
const [owner, repo] = mcpId.split('/');
|
||||
if (!owner || !repo) {
|
||||
throw new Error(`Invalid MCP ID format: ${mcpId}`);
|
||||
// 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(`
|
||||
@@ -58,7 +67,7 @@ export class MCPRepository {
|
||||
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 {
|
||||
@@ -75,9 +84,11 @@ export class MCPRepository {
|
||||
*/
|
||||
static listInstalled(): string[] {
|
||||
try {
|
||||
const stmt = db.prepare("SELECT id FROM mcp_installations ORDER BY installed_at DESC");
|
||||
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);
|
||||
return results.map((row) => row.id);
|
||||
} catch (error) {
|
||||
console.error("Error listing installed MCPs:", error);
|
||||
return [];
|
||||
@@ -89,9 +100,11 @@ export class MCPRepository {
|
||||
*/
|
||||
static getLocalConfig(mcpId: string): any {
|
||||
try {
|
||||
const stmt = db.prepare("SELECT config FROM mcp_installations WHERE id = ?");
|
||||
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);
|
||||
}
|
||||
@@ -107,9 +120,11 @@ export class MCPRepository {
|
||||
*/
|
||||
static getLocalTools(mcpId: string): any[] {
|
||||
try {
|
||||
const stmt = db.prepare("SELECT tools FROM mcp_installations WHERE id = ?");
|
||||
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);
|
||||
}
|
||||
@@ -130,12 +145,12 @@ export class MCPRepository {
|
||||
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)
|
||||
tools: JSON.parse(result.tools),
|
||||
};
|
||||
}
|
||||
return null;
|
||||
@@ -155,11 +170,11 @@ export class MCPRepository {
|
||||
FROM mcp_installations ORDER BY installed_at DESC
|
||||
`);
|
||||
const results = stmt.all() as any[];
|
||||
|
||||
return results.map(row => ({
|
||||
|
||||
return results.map((row) => ({
|
||||
...row,
|
||||
config: JSON.parse(row.config),
|
||||
tools: JSON.parse(row.tools)
|
||||
tools: JSON.parse(row.tools),
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error("Error listing installed MCPs with details:", error);
|
||||
@@ -172,19 +187,48 @@ export class MCPRepository {
|
||||
*/
|
||||
static updateTimestamp(mcpId: string): void {
|
||||
try {
|
||||
const stmt = db.prepare("UPDATE mcp_installations SET updated_at = CURRENT_TIMESTAMP WHERE id = ?");
|
||||
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 };
|
||||
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);
|
||||
@@ -197,7 +241,9 @@ export class MCPRepository {
|
||||
*/
|
||||
static getInstallationStats(): any {
|
||||
try {
|
||||
const totalStmt = db.prepare("SELECT COUNT(*) as total FROM mcp_installations");
|
||||
const totalStmt = db.prepare(
|
||||
"SELECT COUNT(*) as total FROM mcp_installations"
|
||||
);
|
||||
const totalResult = totalStmt.get() as { total: number };
|
||||
|
||||
const recentStmt = db.prepare(`
|
||||
@@ -211,16 +257,164 @@ export class MCPRepository {
|
||||
SELECT COUNT(DISTINCT owner) as unique_owners
|
||||
FROM mcp_installations
|
||||
`);
|
||||
const categoriesResult = categoriesStmt.get() as { unique_owners: number };
|
||||
const categoriesResult = categoriesStmt.get() as {
|
||||
unique_owners: number;
|
||||
};
|
||||
|
||||
return {
|
||||
total: totalResult.total,
|
||||
recent: recentResult.recent,
|
||||
uniqueOwners: categoriesResult.unique_owners
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user