feat(mcp): apps.logs — compose-aware runtime logs

Adds apps.logs MCP tool + session REST endpoint for tailing runtime
container logs. Unblocks cold-start debugging for agent-deployed
compose apps (Twenty, Cal.com, Plane, etc.) where Coolify's own
/applications/{uuid}/logs endpoint returns empty.

Architecture:
  - dockerfile / nixpacks / static apps → Coolify's REST logs API
  - dockercompose apps                  → SSH into Coolify host,
                                          `docker logs` per service

New SSH path uses a dedicated `vibn-logs` user (docker group, no
sudo, no pty, no port-forwarding, single ed25519 key). Private key
lives in COOLIFY_SSH_PRIVATE_KEY_B64 on the vibn-frontend Coolify
app; authorized_key is installed by scripts/setup-vibn-logs-user.sh
on the Coolify host.

Tool shape:
  params:   { uuid, service?, lines? (default 200, max 5000) }
  returns:  { uuid, buildPack, source: 'coolify_api'|'ssh_docker'|'empty',
              services: { [name]: { container, lines, bytes, logs, status? } },
              warnings: string[], truncated: boolean }

Made-with: Cursor
This commit is contained in:
2026-04-23 13:21:52 -07:00
parent 9959eaeeaa
commit d86f2bea03
7 changed files with 541 additions and 0 deletions

View File

@@ -26,6 +26,7 @@ import {
getWorkspaceGcsHmacCredentials,
} from '@/lib/workspace-gcs';
import { VIBN_GCS_LOCATION } from '@/lib/gcp/storage';
import { getApplicationRuntimeLogs } from '@/lib/coolify-logs';
import {
deployApplication,
getApplicationInProject,
@@ -97,6 +98,7 @@ export async function GET() {
'apps.deployments',
'apps.domains.list',
'apps.domains.set',
'apps.logs',
'apps.envs.list',
'apps.envs.upsert',
'apps.envs.delete',
@@ -189,6 +191,8 @@ export async function POST(request: Request) {
return await toolAppsDomainsList(principal, params);
case 'apps.domains.set':
return await toolAppsDomainsSet(principal, params);
case 'apps.logs':
return await toolAppsLogs(principal, params);
case 'databases.list':
return await toolDatabasesList(principal);
@@ -400,6 +404,34 @@ async function toolAppsDeployments(principal: Principal, params: Record<string,
return NextResponse.json({ result: deployments });
}
/**
* Runtime logs for a Coolify app. Compose-aware:
* - Dockerfile/nixpacks apps → Coolify's `/applications/{uuid}/logs`
* - Compose apps → SSH into Coolify host, `docker logs` per service
*
* Params:
* uuid app uuid (required)
* service compose service filter (optional)
* lines tail lines per container, default 200, max 5000
*/
async function toolAppsLogs(principal: Principal, params: Record<string, any>) {
const projectUuid = requireCoolifyProject(principal);
if (projectUuid instanceof NextResponse) return projectUuid;
const appUuid = String(params.uuid ?? params.appUuid ?? '').trim();
if (!appUuid) {
return NextResponse.json({ error: 'Param "uuid" is required' }, { status: 400 });
}
await getApplicationInProject(appUuid, projectUuid);
const linesRaw = Number(params.lines ?? 200);
const lines = Number.isFinite(linesRaw) ? linesRaw : 200;
const serviceRaw = params.service;
const service = typeof serviceRaw === 'string' && serviceRaw.trim() ? serviceRaw.trim() : undefined;
const result = await getApplicationRuntimeLogs(appUuid, { lines, service });
return NextResponse.json({ result });
}
async function toolAppsEnvsList(principal: Principal, params: Record<string, any>) {
const projectUuid = requireCoolifyProject(principal);
if (projectUuid instanceof NextResponse) return projectUuid;