diff --git a/lib/ai/vibn-tools.ts b/lib/ai/vibn-tools.ts index 5a4d534c..a4ed0375 100644 --- a/lib/ai/vibn-tools.ts +++ b/lib/ai/vibn-tools.ts @@ -82,7 +82,7 @@ Auto-domain {name}.{workspace}.vibnai.com is assigned automatically.`, composeRaw: { type: 'STRING', description: 'Raw Docker Compose YAML for custom multi-service stacks. Only use when no template exists.' }, repo: { type: 'STRING', description: 'Gitea repo name (e.g. "my-site") for deploying the user\'s own code.' }, ports: { type: 'STRING', description: 'Port(s) the app listens on (e.g. "3000"). Required for repo/image pathways.' }, - envs: { type: 'OBJECT', description: 'Environment variables as key-value pairs to inject at creation.' }, + envsJson: { type: 'STRING', description: 'Environment variables as a JSON object string (e.g. \'{"KEY":"value"}\'). Optional.' }, instantDeploy: { type: 'BOOLEAN', description: 'Whether to deploy immediately (default true).' }, }, required: ['name'], @@ -95,9 +95,9 @@ Auto-domain {name}.{workspace}.vibnai.com is assigned automatically.`, type: 'OBJECT', properties: { uuid: { type: 'STRING', description: 'The Coolify application UUID.' }, - patch: { type: 'OBJECT', description: 'Fields to update as key-value pairs.' }, + patchJson: { type: 'STRING', description: 'Fields to update as a JSON object string (e.g. \'{"name":"new-name","ports_exposes":"3001"}\').' }, }, - required: ['uuid', 'patch'], + required: ['uuid', 'patchJson'], }, }, { @@ -281,7 +281,11 @@ Auto-domain {name}.{workspace}.vibnai.com is assigned automatically.`, type: 'OBJECT', properties: { uuid: { type: 'STRING', description: 'The Coolify application UUID.' }, - domains: { type: 'ARRAY', description: 'Array of domain strings (e.g. ["myapp.mark.vibnai.com", "api.mark.vibnai.com"]).' }, + domains: { + type: 'ARRAY', + description: 'Array of domain strings (e.g. ["myapp.mark.vibnai.com", "api.mark.vibnai.com"]).', + items: { type: 'STRING' }, + }, service: { type: 'STRING', description: 'For compose apps: the service to attach the domain to (e.g. "server"). Default: "server".' }, }, required: ['uuid', 'domains'], @@ -365,9 +369,9 @@ Auto-domain {name}.{workspace}.vibnai.com is assigned automatically.`, type: 'OBJECT', properties: { uuid: { type: 'STRING', description: 'The Coolify database UUID.' }, - patch: { type: 'OBJECT', description: 'Fields to update.' }, + patchJson: { type: 'STRING', description: 'Fields to update as a JSON object string (e.g. \'{"name":"new-name","is_public":true}\').' }, }, - required: ['uuid', 'patch'], + required: ['uuid', 'patchJson'], }, }, { @@ -425,7 +429,11 @@ Auto-domain {name}.{workspace}.vibnai.com is assigned automatically.`, parameters: { type: 'OBJECT', properties: { - names: { type: 'ARRAY', description: 'Array of domain names to check (e.g. ["myapp.com", "myapp.io"]). Max 25.' }, + names: { + type: 'ARRAY', + description: 'Array of domain names to check (e.g. ["myapp.com", "myapp.io"]). Max 25.', + items: { type: 'STRING' }, + }, period: { type: 'NUMBER', description: 'Registration period in years (default 1). Note: .ai requires 2 years minimum.' }, }, required: ['names'], @@ -468,7 +476,11 @@ Auto-domain {name}.{workspace}.vibnai.com is assigned automatically.`, properties: { domain: { type: 'STRING', description: 'The registered domain name (e.g. "myapp.com").' }, appUuid: { type: 'STRING', description: 'Coolify app UUID to attach the domain to.' }, - subdomains: { type: 'ARRAY', description: 'Subdomains to wire (default ["@", "www"]).' }, + subdomains: { + type: 'ARRAY', + description: 'Subdomains to wire (default ["@", "www"]).', + items: { type: 'STRING' }, + }, }, required: ['domain'], }, @@ -545,7 +557,7 @@ Auto-domain {name}.{workspace}.vibnai.com is assigned automatically.`, type: 'OBJECT', properties: { url: { type: 'STRING', description: 'The full URL to fetch (https preferred).' }, - headers: { type: 'OBJECT', description: 'Optional HTTP headers as key-value pairs.' }, + headersJson: { type: 'STRING', description: 'Optional HTTP headers as a JSON object string (e.g. \'{"Accept":"application/json"}\').' }, }, required: ['url'], }, @@ -574,6 +586,21 @@ export async function executeMcpTool( // Convert underscore tool name → dotted MCP action (apps_create → apps.create) const action = toolName.replace(/_/g, '.'); + // Unpack JSON-string args (Gemini schemas can't represent free-form objects, + // so we accept *Json string fields and parse them server-side). + const params: Record = { ...args }; + for (const key of Object.keys(params)) { + if (key.endsWith('Json') && typeof params[key] === 'string') { + const realKey = key.slice(0, -4); // envsJson → envs, patchJson → patch + try { + params[realKey] = JSON.parse(params[key] as string); + } catch { + return JSON.stringify({ error: `Invalid JSON for ${key}` }); + } + delete params[key]; + } + } + try { const res = await fetch(`${baseUrl}/api/mcp`, { method: 'POST', @@ -581,7 +608,7 @@ export async function executeMcpTool( 'Content-Type': 'application/json', Authorization: `Bearer ${mcpToken}`, }, - body: JSON.stringify({ action, params: args }), + body: JSON.stringify({ action, params }), }); const data = await res.json(); return JSON.stringify(data.result ?? data.error ?? data, null, 2).slice(0, 8000); @@ -655,7 +682,10 @@ async function executeGithubFile(args: Record): Promise async function executeHttpFetch(args: Record): Promise { const url = String(args.url || ''); - const extraHeaders = (args.headers as Record) || {}; + let extraHeaders: Record = {}; + if (typeof args.headersJson === 'string') { + try { extraHeaders = JSON.parse(args.headersJson); } catch { /* ignore */ } + } if (!url.startsWith('http://') && !url.startsWith('https://')) { return JSON.stringify({ error: 'URL must start with http:// or https://' });