diff --git a/app/api/mcp/route.ts b/app/api/mcp/route.ts index 2b763356..ba9eaa90 100644 --- a/app/api/mcp/route.ts +++ b/app/api/mcp/route.ts @@ -382,19 +382,94 @@ async function toolProjectsGet(principal: Principal, params: Record } const r = rows[0]; const d = r.data || {}; - // Return a clean summary rather than the raw JSONB, which contains noisy - // internal scaffold fields (product/website/admin/storybook sub-app configs) - // that the AI tends to misread as live deployed services. + const projectName = d.productName || d.name || d.title || 'Untitled'; + + // Auto-enrich: if no Coolify link is stored yet, scan apps + services in + // the workspace and surface any whose name fuzzy-matches the project. Lets + // the AI tell the user "this is probably your deployment" even when the + // backend never wrote the link. + let possibleDeployments: Array<{ + uuid: string; + name: string; + status: string; + fqdn: string | null; + resourceType: 'application' | 'service'; + }> = []; + + const linkedUuid = d.coolifyAppUuid || d.coolifyServiceUuid || null; + const projectUuid = principal.workspace.coolify_project_uuid; + if (projectUuid) { + try { + const [appsRes, servicesRes, projectRes] = await Promise.allSettled([ + listApplicationsInProject(projectUuid), + listAllServices(), + getProject(projectUuid), + ]); + const envIds = new Set( + projectRes.status === 'fulfilled' + ? (projectRes.value.environments ?? []).map((e) => e.id) + : [], + ); + const apps = appsRes.status === 'fulfilled' ? appsRes.value : []; + const services = (servicesRes.status === 'fulfilled' && Array.isArray(servicesRes.value) + ? (servicesRes.value as Array>) + : [] + ).filter((s) => envIds.has(Number(s.environment_id))); + + // Build searchable tokens from the project name (lowercased words, length >= 3) + const tokens = projectName + .toLowerCase() + .replace(/[^a-z0-9 ]/g, ' ') + .split(/\s+/) + .filter((t: string) => t.length >= 3); + + const matches = (name: string) => { + const n = name.toLowerCase(); + return tokens.some((t: string) => n.includes(t)); + }; + + for (const a of apps) { + if (matches(a.name) || a.uuid === linkedUuid) { + possibleDeployments.push({ + uuid: a.uuid, + name: a.name, + status: a.status, + fqdn: a.fqdn ?? null, + resourceType: 'application', + }); + } + } + for (const s of services) { + const name = String(s.name ?? ''); + const uuid = String(s.uuid); + if (matches(name) || uuid === linkedUuid) { + const subApps = (s.applications as Array>) || []; + const publicApp = subApps.find((a) => a.fqdn); + possibleDeployments.push({ + uuid, + name, + status: String(s.status ?? 'unknown'), + fqdn: publicApp?.fqdn ? String(publicApp.fqdn) : null, + resourceType: 'service', + }); + } + } + } catch { + // Best-effort enrichment — never let it block the project read + } + } + return NextResponse.json({ result: { id: r.id, - name: d.productName || d.name || d.title || 'Untitled', + name: projectName, status: d.status || 'defining', vision: d.productVision || d.vision || null, domain: d.domain || d.customDomain || null, - coolifyAppUuid: d.coolifyAppUuid || d.coolifyServiceUuid || null, + coolifyAppUuid: linkedUuid, coolifyDomain: d.coolifyDomain || null, repositoryUrl: d.repositoryUrl || null, + possibleDeployments, createdAt: r.created_at, updatedAt: r.updated_at, },