diff --git a/app/api/mcp/route.ts b/app/api/mcp/route.ts index f6722260..5932c163 100644 --- a/app/api/mcp/route.ts +++ b/app/api/mcp/route.ts @@ -369,13 +369,21 @@ export async function POST(request: Request) { case 'fs.grep': return await toolFsGrep(principal, params); + // The Gemini tool-name "dev_server_list" maps to dotted action + // "dev.server.list" via executeMcpTool's underscore→dot replace. + // We accept BOTH the original underscore form and the converted + // dotted form so external MCP clients aren't surprised either way. case 'dev_server.start': + case 'dev.server.start': return await toolDevServerStart(principal, params); case 'dev_server.stop': + case 'dev.server.stop': return await toolDevServerStop(principal, params); case 'dev_server.list': + case 'dev.server.list': return await toolDevServerList(principal, params); case 'dev_server.logs': + case 'dev.server.logs': return await toolDevServerLogs(principal, params); case 'ship': return await toolShip(principal, params); diff --git a/lib/dev-container.ts b/lib/dev-container.ts index 2d4a8818..e9ff8e9d 100644 --- a/lib/dev-container.ts +++ b/lib/dev-container.ts @@ -275,8 +275,16 @@ export async function resumeDevContainer(projectId: string): Promise { } async function touchActivity(projectId: string): Promise { + // Also flips state 'provisioning' → 'running' on first successful exec. + // We can't rely on Coolify's deploy webhook alone (it fires before the + // container's actually accepting docker exec), so the first exec that + // returns is our authoritative liveness signal. await query( - `UPDATE fs_project_dev_containers SET last_active_at = now() WHERE project_id = $1`, + `UPDATE fs_project_dev_containers + SET last_active_at = now(), + state = CASE WHEN state IN ('provisioning','suspended') THEN 'running' ELSE state END, + suspended_at = NULL + WHERE project_id = $1`, [projectId], ); }