diff --git a/app/api/mcp/route.ts b/app/api/mcp/route.ts index e8b51f5..b66d793 100644 --- a/app/api/mcp/route.ts +++ b/app/api/mcp/route.ts @@ -20,6 +20,12 @@ import { NextResponse } from 'next/server'; import { requireWorkspacePrincipal } from '@/lib/auth/workspace-auth'; import { getWorkspaceBotCredentials, ensureWorkspaceProvisioned } from '@/lib/workspaces'; +import { + ensureWorkspaceGcsProvisioned, + getWorkspaceGcsState, + getWorkspaceGcsHmacCredentials, +} from '@/lib/workspace-gcs'; +import { VIBN_GCS_LOCATION } from '@/lib/gcp/storage'; import { deployApplication, getApplicationInProject, @@ -107,6 +113,9 @@ export async function GET() { 'domains.get', 'domains.register', 'domains.attach', + 'storage.describe', + 'storage.provision', + 'storage.inject_env', ], }, }, @@ -210,6 +219,13 @@ export async function POST(request: Request) { case 'domains.attach': return await toolDomainsAttach(principal, params); + case 'storage.describe': + return await toolStorageDescribe(principal); + case 'storage.provision': + return await toolStorageProvision(principal); + case 'storage.inject_env': + return await toolStorageInjectEnv(principal, params); + default: return NextResponse.json( { error: `Unknown tool "${action}"` }, @@ -1139,3 +1155,133 @@ async function toolDomainsAttach(principal: Principal, params: Record) { + 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 prefix = String(params.prefix ?? 'STORAGE_'); + if (!/^[A-Z][A-Z0-9_]*$/.test(prefix)) { + return NextResponse.json( + { error: 'Param "prefix" must be uppercase ASCII (letters, digits, underscores)' }, + { status: 400 }, + ); + } + + const ws = await getWorkspaceGcsState(principal.workspace.id); + if (!ws) return NextResponse.json({ error: 'Workspace not found' }, { status: 404 }); + if (ws.gcp_provision_status !== 'ready' || !ws.gcs_default_bucket_name) { + return NextResponse.json( + { + error: `Workspace storage not ready (status=${ws.gcp_provision_status}). Call storage.provision first.`, + }, + { status: 409 }, + ); + } + const creds = getWorkspaceGcsHmacCredentials(ws); + if (!creds) { + return NextResponse.json( + { error: 'Storage HMAC secret unavailable (pre-rotation key, or decrypt failed). Rotate and retry.' }, + { status: 409 }, + ); + } + + const entries: Array<{ key: string; value: string; shownOnce?: boolean }> = [ + { key: `${prefix}ENDPOINT`, value: 'https://storage.googleapis.com' }, + { key: `${prefix}REGION`, value: VIBN_GCS_LOCATION }, + { key: `${prefix}BUCKET`, value: ws.gcs_default_bucket_name }, + { key: `${prefix}ACCESS_KEY_ID`, value: creds.accessId }, + { key: `${prefix}SECRET_ACCESS_KEY`, value: creds.secret, shownOnce: true }, + { key: `${prefix}FORCE_PATH_STYLE`, value: 'true' }, + ]; + + const written: string[] = []; + const failed: Array<{ key: string; error: string }> = []; + for (const e of entries) { + try { + await upsertApplicationEnv(appUuid, { + key: e.key, + value: e.value, + is_shown_once: e.shownOnce ?? false, + }); + written.push(e.key); + } catch (err) { + failed.push({ key: e.key, error: err instanceof Error ? err.message : String(err) }); + } + } + return NextResponse.json({ + result: { + uuid: appUuid, + prefix, + written, + failed: failed.length ? failed : undefined, + bucket: ws.gcs_default_bucket_name, + }, + }); +}