1984 lines
69 KiB
TypeScript
1984 lines
69 KiB
TypeScript
/**
|
|
* Vibn MCP tool definitions for the Gemini chat assistant.
|
|
*
|
|
* These mirror the full MCP surface documented in AI_CAPABILITIES.md.
|
|
* Tool names use underscores (e.g. apps_create) which map 1:1 to the
|
|
* MCP action names (e.g. apps.create) by replacing _ with .
|
|
*
|
|
* Non-MCP tools (github_search, github_file, http_fetch) are handled
|
|
* locally at the bottom of this file.
|
|
*/
|
|
import type { ToolDefinition } from "./gemini-chat";
|
|
|
|
const GITHUB_TOKEN = process.env.GITHUB_TOKEN || "";
|
|
|
|
export const VIBN_TOOL_DEFINITIONS: ToolDefinition[] = [
|
|
// ── Workspace & identity ─────────────────────────────────────────────────
|
|
|
|
{
|
|
name: "fs_tree",
|
|
description:
|
|
"Get the directory structure of the project as a tree view (up to 3 levels deep). ALWAYS call this first when exploring a new project so you do not waste time guessing paths.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
projectId: { type: "STRING", description: "The Vibn project ID." },
|
|
path: {
|
|
type: "STRING",
|
|
description:
|
|
"Optional directory path under /workspace. Defaults to root.",
|
|
},
|
|
},
|
|
required: ["projectId"],
|
|
},
|
|
},
|
|
{
|
|
name: "browser_navigate",
|
|
description:
|
|
"Load a URL in a headless browser and return the rendered text, HTTP status, and any uncaught frontend errors.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
projectId: { type: "STRING", description: "The Vibn project ID." },
|
|
url: {
|
|
type: "STRING",
|
|
description: "The URL to navigate to (e.g. the preview URL).",
|
|
},
|
|
},
|
|
required: ["projectId", "url"],
|
|
},
|
|
},
|
|
{
|
|
name: "browser_console",
|
|
description:
|
|
"Load a URL and capture all console.error logs from the browser. Use this to catch hydration errors or runtime JS crashes after scaffolding a component or starting a dev server.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
projectId: { type: "STRING", description: "The Vibn project ID." },
|
|
url: {
|
|
type: "STRING",
|
|
description: "The URL to test (e.g. the preview URL).",
|
|
},
|
|
},
|
|
required: ["projectId", "url"],
|
|
},
|
|
},
|
|
{
|
|
name: "workspace_describe",
|
|
description:
|
|
"Returns workspace details: slug, Coolify project UUID, Gitea org, and provision status.",
|
|
parameters: { type: "OBJECT", properties: {}, required: [] },
|
|
},
|
|
{
|
|
name: "gitea_credentials",
|
|
description:
|
|
"Returns the workspace bot's Gitea username, PAT, clone URL template, and SSH remote template. Use for any git clone/push operations.",
|
|
parameters: { type: "OBJECT", properties: {}, required: [] },
|
|
},
|
|
|
|
// ── Projects ─────────────────────────────────────────────────────────────
|
|
|
|
{
|
|
name: "projects_list",
|
|
description:
|
|
"List all Vibn projects in the workspace (planning records — not live deployments). Use apps_list to see what is actually running.",
|
|
parameters: { type: "OBJECT", properties: {}, required: [] },
|
|
},
|
|
{
|
|
name: "get_design_template",
|
|
description:
|
|
"Get the contents of the SKILL.md file for a specific design template. This file contains the guidelines, patterns, and code structure needed to implement the template.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
template_id: {
|
|
type: "STRING",
|
|
description: "The ID of the template (e.g. 'dashboard', 'blog-post')",
|
|
},
|
|
},
|
|
required: ["template_id"],
|
|
},
|
|
},
|
|
{
|
|
name: "projects_get",
|
|
description:
|
|
"Get details for a single Vibn project by ID: name, status, vision, linked Coolify UUID, Sentry slug + DSN, possibleDeployments, and designKit fields when the founder saved a kit on the Design tab (designKitForCodegen has resolved color ramps + radius + font, DESIGN.md guidelines, and tokens.css). Use these resolved fields and guidelines to align frontend tokens and styling; saving in UI does not edit repo files.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
projectId: { type: "STRING", description: "The Vibn project ID." },
|
|
},
|
|
required: ["projectId"],
|
|
},
|
|
},
|
|
|
|
// ── Market Research & GTM ───────────────────────────────────────────────
|
|
|
|
{
|
|
name: "market_categories_suggest",
|
|
description:
|
|
"Suggests the top 10 most relevant Google Business Profile categories for a given software niche or business type. ALWAYS call this tool FIRST to propose categories to the user before running market_research_run. Let the user approve or reject the list.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
projectId: { type: "STRING", description: "The Vibn project ID." },
|
|
niche: {
|
|
type: "STRING",
|
|
description:
|
|
"The software niche or target market (e.g., 'summer camps', 'dental software').",
|
|
},
|
|
},
|
|
required: ["projectId", "niche"],
|
|
},
|
|
},
|
|
|
|
{
|
|
name: "market_research_run",
|
|
description:
|
|
"Run market research for a specific business category and location. Fetches real business leads from DataForSEO, stores them in the Vibn database (Data Co-op), and returns the TAM (Total Addressable Market) count, competitor data, and a sample of domains/emails for analysis. This tool costs money. You MUST ask the user for explicit permission before calling it. Pass user_explicitly_approved: true once you have permission.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
projectId: { type: "STRING", description: "The Vibn project ID." },
|
|
categories: {
|
|
type: "ARRAY",
|
|
items: { type: "STRING" },
|
|
description:
|
|
'Array of approved Google Business Categories (e.g., ["summer_camp", "camp", "youth_center"]). Max 10.',
|
|
},
|
|
location: {
|
|
type: "STRING",
|
|
description:
|
|
'Location string (e.g., "Victoria, British Columbia, Canada", "California, United States").',
|
|
},
|
|
user_explicitly_approved: {
|
|
type: "BOOLEAN",
|
|
description:
|
|
"Must be true. Indicates the user agreed to run this cost-incurring research.",
|
|
},
|
|
},
|
|
required: [
|
|
"projectId",
|
|
"categories",
|
|
"location",
|
|
"user_explicitly_approved",
|
|
],
|
|
},
|
|
},
|
|
{
|
|
name: "market_seo_analyze",
|
|
description:
|
|
"Analyze a competitor's domain for SEO and Google Ads metrics using DataForSEO. Returns estimated organic traffic, organic keywords, paid Google Ads traffic, estimated monthly Ad Spend, and top paid keywords. Use this to understand a competitor's GTM strategy.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
projectId: { type: "STRING", description: "The Vibn project ID." },
|
|
domain: {
|
|
type: "STRING",
|
|
description:
|
|
'The competitor\'s domain name (e.g., "curvedental.com").',
|
|
},
|
|
},
|
|
required: ["projectId", "domain"],
|
|
},
|
|
},
|
|
|
|
{
|
|
name: "tech_stack_analyze",
|
|
description:
|
|
"Analyze a list of URLs to determine what software, CMS, booking tools, and analytics they use. Returns aggregated statistics. Use this to determine market gaps (e.g. 'How many dentists use WordPress but lack a booking widget?').",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
projectId: { type: "STRING", description: "The Vibn project ID." },
|
|
urls: {
|
|
type: "ARRAY",
|
|
items: { type: "STRING" },
|
|
description: "An array of URLs to analyze (max 100 per call).",
|
|
},
|
|
software_category_id: {
|
|
type: "STRING",
|
|
description:
|
|
"The ID of the software category (e.g., 'dental-practice-management') to dynamically load competitors from BigQuery and check if the leads are using them.",
|
|
},
|
|
custom_checks: {
|
|
type: "ARRAY",
|
|
items: { type: "STRING" },
|
|
description:
|
|
"Optional array of specific technology strings or domains to look for (e.g., ['hubspot.com', 'stripe.com/v3', 'gtag']). Use this if you want to check for competitors that aren't in the database.",
|
|
},
|
|
},
|
|
required: ["projectId", "urls", "software_category_id"],
|
|
},
|
|
},
|
|
|
|
{
|
|
name: "market_competitor_research",
|
|
description:
|
|
"Search the proprietary database for software competitors and open-source starter kits related to a specific niche. Use this to understand the competitive landscape, pricing models, and find MIT-licensed code to fork.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
projectId: { type: "STRING", description: "The Vibn project ID." },
|
|
niche: {
|
|
type: "STRING",
|
|
description:
|
|
"The software niche (e.g., 'dental', 'accounting', 'crm', 'booking').",
|
|
},
|
|
},
|
|
required: ["projectId", "niche"],
|
|
},
|
|
},
|
|
|
|
{
|
|
name: "market_aggregate_insights",
|
|
description:
|
|
"Fetches aggregated insights for a specific market niche. Returns a breakdown of sub-categories, total websites, and most importantly, the top keywords mentioned in customer reviews. Use this to understand patient/customer pain points before writing a business plan or marketing copy.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
projectId: { type: "STRING", description: "The Vibn project ID." },
|
|
category: {
|
|
type: "STRING",
|
|
description:
|
|
"The primary Google Category to aggregate (e.g., 'dentist', 'summer_camp').",
|
|
},
|
|
location: {
|
|
type: "STRING",
|
|
description:
|
|
"The location (e.g., 'Victoria, BC' or '48.4284,-123.3656,20').",
|
|
},
|
|
},
|
|
required: ["projectId", "category", "location"],
|
|
},
|
|
},
|
|
|
|
// ── Sentry (Stage 3 of Sentry-as-product) ───────────────────────────────
|
|
|
|
{
|
|
name: "project_recent_errors",
|
|
description:
|
|
'List recent unresolved Sentry issues for a Vibn project. Each item has id, title, level, count, lastSeen, culprit, permalink. Use this when the user asks "is anything broken?" or before declaring something done. Returns [] if Sentry is not yet provisioned (project too new) — that is fine.',
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
projectId: { type: "STRING", description: "The Vibn project ID." },
|
|
sinceHours: {
|
|
type: "NUMBER",
|
|
description:
|
|
"Look-back window in hours. Default 24, max 168 (1 week).",
|
|
},
|
|
limit: {
|
|
type: "NUMBER",
|
|
description: "Max issues to return (1-50). Default 10.",
|
|
},
|
|
},
|
|
required: ["projectId"],
|
|
},
|
|
},
|
|
{
|
|
name: "project_error_detail",
|
|
description:
|
|
"Fetch the most recent event for a Sentry issue: stack frames (top 12, source-mapped to real filenames), breadcrumbs (last 20 user actions before the error), user/request context, and a Session Replay link if one was captured. Call this AFTER project_recent_errors gives you an issue id.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
projectId: { type: "STRING", description: "The Vibn project ID." },
|
|
issueId: {
|
|
type: "STRING",
|
|
description: "Sentry issue id from project_recent_errors.",
|
|
},
|
|
},
|
|
required: ["projectId", "issueId"],
|
|
},
|
|
},
|
|
{
|
|
name: "project_error_resolve",
|
|
description:
|
|
"Mark a Sentry issue resolved. Call this AFTER you have shipped a fix and either run a verifying test, watched the error stop firing, or had the user confirm. Do NOT mark resolved speculatively — Sentry auto-reopens issues on regression but it is noisy.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
projectId: { type: "STRING", description: "The Vibn project ID." },
|
|
issueId: { type: "STRING", description: "Sentry issue id to resolve." },
|
|
},
|
|
required: ["projectId", "issueId"],
|
|
},
|
|
},
|
|
|
|
// ── Applications ─────────────────────────────────────────────────────────
|
|
|
|
{
|
|
name: "apps_list",
|
|
description:
|
|
"List live applications and services. Without projectId, lists everything across the workspace. With projectId, scopes to that single Vibn project.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
projectId: {
|
|
type: "STRING",
|
|
description:
|
|
"Optional Vibn project ID to scope the list to one project.",
|
|
},
|
|
},
|
|
required: [],
|
|
},
|
|
},
|
|
{
|
|
name: "apps_get",
|
|
description:
|
|
"Get full details for a single app: status, domain, git info, build pack, environment.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
uuid: { type: "STRING", description: "The Coolify application UUID." },
|
|
},
|
|
required: ["uuid"],
|
|
},
|
|
},
|
|
{
|
|
name: "apps_create",
|
|
description: `Create and deploy a new application. Four pathways — pick the right one:
|
|
1. template (PREFERRED for popular apps — Twenty, n8n, WordPress, Ghost, Supabase, etc.): pass { template: "<slug>" }. Always search apps_templates_search first.
|
|
2. image (single Docker container): pass { image: "nginx:latest" }.
|
|
3. composeRaw (custom multi-service stack, no template exists): pass { composeRaw: "<yaml>" }.
|
|
4. repo (user's own code in Gitea): pass { repo: "<repo-name>" }.
|
|
Auto-domain {name}.{workspace}.vibnai.com is assigned automatically.`,
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
projectId: {
|
|
type: "STRING",
|
|
description:
|
|
"The Vibn project ID to deploy this app under. STRONGLY RECOMMENDED — gives the app its own isolated Coolify project so all related resources (databases, services) are grouped together and can be lifecycle-managed as one unit. If omitted, the app lands in the workspace's shared/legacy Coolify project.",
|
|
},
|
|
name: {
|
|
type: "STRING",
|
|
description:
|
|
'App name (slug-friendly, e.g. "my-crm"). Required for all pathways.',
|
|
},
|
|
domain: {
|
|
type: "STRING",
|
|
description:
|
|
'Custom subdomain (e.g. "crm.mark.vibnai.com"). Optional — auto-generated if omitted.',
|
|
},
|
|
template: {
|
|
type: "STRING",
|
|
description:
|
|
'Coolify one-click template slug (e.g. "twenty", "n8n", "wordpress"). Use apps_templates_search to find the slug.',
|
|
},
|
|
image: {
|
|
type: "STRING",
|
|
description:
|
|
'Docker image (e.g. "nginx:latest"). For single-container third-party apps.',
|
|
},
|
|
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.',
|
|
},
|
|
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"],
|
|
},
|
|
},
|
|
{
|
|
name: "apps_update",
|
|
description:
|
|
"Update whitelisted fields on an existing app (name, description, git branch, ports, build commands, base directory, etc.). Returns applied/ignored/rerouted arrays. Setting domains/git_repository returns a rerouted hint pointing at apps_domains_set or apps_rewire_git.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
uuid: { type: "STRING", description: "The Coolify application UUID." },
|
|
patchJson: {
|
|
type: "STRING",
|
|
description:
|
|
'Fields to update as a JSON object string (e.g. \'{"name":"new-name","ports_exposes":"3001"}\').',
|
|
},
|
|
},
|
|
required: ["uuid", "patchJson"],
|
|
},
|
|
},
|
|
{
|
|
name: "apps_rewire_git",
|
|
description:
|
|
"Re-point an app's git_repository at the canonical HTTPS+PAT clone URL. Use to recover older apps created with SSH URLs, or to refresh a rotated bot PAT.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
uuid: { type: "STRING", description: "The Coolify application UUID." },
|
|
repo: {
|
|
type: "STRING",
|
|
description:
|
|
"Gitea repo name. Optional — inferred from current URL if omitted.",
|
|
},
|
|
},
|
|
required: ["uuid"],
|
|
},
|
|
},
|
|
{
|
|
name: "apps_delete",
|
|
description:
|
|
"Destroy an application. Volumes are kept by default. confirm must equal the app's exact name.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
uuid: { type: "STRING", description: "The Coolify application UUID." },
|
|
confirm: {
|
|
type: "STRING",
|
|
description:
|
|
'Must equal the exact app name (e.g. "my-crm"). Prevents accidental deletion.',
|
|
},
|
|
},
|
|
required: ["uuid", "confirm"],
|
|
},
|
|
},
|
|
{
|
|
name: "apps_deploy",
|
|
description: "Trigger a new deployment for an existing application.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
uuid: { type: "STRING", description: "The Coolify application UUID." },
|
|
force: {
|
|
type: "BOOLEAN",
|
|
description: "Force rebuild even if nothing changed.",
|
|
},
|
|
},
|
|
required: ["uuid"],
|
|
},
|
|
},
|
|
{
|
|
name: "apps_deployments",
|
|
description:
|
|
"List recent deployments for an app with their status (finished, in_progress, failed, queued).",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
uuid: { type: "STRING", description: "The Coolify application UUID." },
|
|
},
|
|
required: ["uuid"],
|
|
},
|
|
},
|
|
{
|
|
name: "apps_logs",
|
|
description:
|
|
"Get runtime logs from a running application. Compose-aware: returns per-service logs for multi-service stacks. Use to diagnose crashes, DB errors, or startup failures.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
uuid: { type: "STRING", description: "The Coolify application UUID." },
|
|
service: {
|
|
type: "STRING",
|
|
description:
|
|
'For compose apps: specific service name to filter (e.g. "server", "worker"). Omit for all services.',
|
|
},
|
|
lines: {
|
|
type: "NUMBER",
|
|
description: "Number of log lines (default 200, max 5000).",
|
|
},
|
|
},
|
|
required: ["uuid"],
|
|
},
|
|
},
|
|
{
|
|
name: "apps_exec",
|
|
description:
|
|
"Run a one-shot command inside a running app container (via docker exec). Use for database migrations, seeds, CLI invocations, and debugging. Shell syntax works. Default timeout 60s.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
uuid: { type: "STRING", description: "The Coolify application UUID." },
|
|
command: {
|
|
type: "STRING",
|
|
description:
|
|
'Shell command to run (e.g. "yarn command:prod database:migrate:prod", "psql $DATABASE_URL -c \'select 1\'").',
|
|
},
|
|
service: {
|
|
type: "STRING",
|
|
description:
|
|
'For compose apps with multiple containers: the service to exec into (e.g. "server", "db").',
|
|
},
|
|
user: {
|
|
type: "STRING",
|
|
description:
|
|
"User to run command as (default: container default user).",
|
|
},
|
|
workdir: {
|
|
type: "STRING",
|
|
description: "Working directory inside the container.",
|
|
},
|
|
timeout_ms: {
|
|
type: "NUMBER",
|
|
description: "Timeout in milliseconds (default 60000, max 600000).",
|
|
},
|
|
max_bytes: {
|
|
type: "NUMBER",
|
|
description: "Max output bytes (default 1MB, max 5MB).",
|
|
},
|
|
},
|
|
required: ["uuid", "command"],
|
|
},
|
|
},
|
|
{
|
|
name: "apps_volumes_list",
|
|
description:
|
|
"List Docker volumes belonging to an app (name + size in bytes). Use before apps_volumes_wipe to confirm volume names.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
uuid: { type: "STRING", description: "The Coolify application UUID." },
|
|
},
|
|
required: ["uuid"],
|
|
},
|
|
},
|
|
{
|
|
name: "apps_volumes_wipe",
|
|
description:
|
|
"DESTRUCTIVE. Stop all app containers, remove a specific volume, leave app ready for a fresh deploy. Use to recover from stale DB state on first boot. confirm must equal the exact volume name.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
uuid: { type: "STRING", description: "The Coolify application UUID." },
|
|
volume: {
|
|
type: "STRING",
|
|
description:
|
|
"Exact volume name to wipe (get from apps_volumes_list).",
|
|
},
|
|
confirm: {
|
|
type: "STRING",
|
|
description: "Must equal the exact volume name to confirm deletion.",
|
|
},
|
|
},
|
|
required: ["uuid", "volume", "confirm"],
|
|
},
|
|
},
|
|
{
|
|
name: "apps_containers_up",
|
|
description:
|
|
"Run docker compose up -d directly on the Coolify host. Use when apps_deploy returned started:false or containers are in Created/Exited state. Idempotent.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
uuid: { type: "STRING", description: "The Coolify application UUID." },
|
|
},
|
|
required: ["uuid"],
|
|
},
|
|
},
|
|
{
|
|
name: "apps_containers_ps",
|
|
description:
|
|
"Run docker compose ps to see container states. Diagnoses Created (queue failure), Exited (crash), Restarting (boot loop), Up healthy/unhealthy.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
uuid: { type: "STRING", description: "The Coolify application UUID." },
|
|
},
|
|
required: ["uuid"],
|
|
},
|
|
},
|
|
{
|
|
name: "apps_repair",
|
|
description:
|
|
"Re-run post-deploy fixes on an existing service: proxy network attach, Traefik label injection, proxy restart. Use when a deploy succeeded but the app returns 502/503 or Mixed Content errors.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
uuid: { type: "STRING", description: "The Coolify service UUID." },
|
|
fqdn: {
|
|
type: "STRING",
|
|
description:
|
|
'The public domain the app should be reachable at (e.g. "crm.mark.vibnai.com").',
|
|
},
|
|
publicAppName: {
|
|
type: "STRING",
|
|
description:
|
|
'The name of the public-facing container/service within the stack (e.g. "server", "web").',
|
|
},
|
|
port: {
|
|
type: "NUMBER",
|
|
description:
|
|
"The upstream port the container listens on (e.g. 3000). Optional.",
|
|
},
|
|
},
|
|
required: ["uuid", "fqdn", "publicAppName"],
|
|
},
|
|
},
|
|
{
|
|
name: "apps_unstick",
|
|
description: `Recover a service stuck on a Docker "container name already in use" conflict. Force-removes orphan containers (everything matching name suffix -<uuid>) so the next apps_deploy can boot clean.
|
|
|
|
USE THIS — DO NOT delete-and-recreate the service. Deleting and re-creating produces a NEW uuid + NEW container names, which side-steps the conflict but leaves the orphan running AND forks a duplicate copy of the stack. We've burned ourselves on this before (4 orphan twenty-* services, 12GB RAM eaten).
|
|
|
|
Recipe when a deploy fails with "Conflict. The container name X is already in use":
|
|
1. apps_unstick { uuid: "<service-uuid>" }
|
|
2. apps_deploy { uuid: "<service-uuid>" }
|
|
3. apps_get { uuid: "<service-uuid>" } to confirm fqdn / status.
|
|
|
|
Pass wipeVolumes: true ONLY if the user explicitly said "nuke the data".`,
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
uuid: {
|
|
type: "STRING",
|
|
description: "The Coolify service / app / database UUID.",
|
|
},
|
|
wipeVolumes: {
|
|
type: "BOOLEAN",
|
|
description:
|
|
"If true, also remove anonymous volumes (data loss). Default false.",
|
|
},
|
|
},
|
|
required: ["uuid"],
|
|
},
|
|
},
|
|
{
|
|
name: "apps_templates_list",
|
|
description:
|
|
"Browse the Coolify one-click template catalog (320+ apps: CRMs, AI tools, CMSes, dashboards, databases). Each is deployable via apps_create with { template: slug }.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
limit: {
|
|
type: "NUMBER",
|
|
description: "Number of templates to return (default 50, max 500).",
|
|
},
|
|
offset: { type: "NUMBER", description: "Pagination offset." },
|
|
tag: {
|
|
type: "STRING",
|
|
description:
|
|
'Filter by tag substring (e.g. "crm", "ai", "database").',
|
|
},
|
|
},
|
|
required: [],
|
|
},
|
|
},
|
|
{
|
|
name: "apps_templates_search",
|
|
description:
|
|
"Search for a Coolify template by name or keyword. Always call this before apps_create to find the correct template slug. Returns ranked matches.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
query: {
|
|
type: "STRING",
|
|
description:
|
|
'Search term (e.g. "twenty", "n8n", "ghost blog", "kanban").',
|
|
},
|
|
tag: {
|
|
type: "STRING",
|
|
description:
|
|
'Filter by tag (e.g. "crm", "ai"). Can be used with or without query.',
|
|
},
|
|
limit: {
|
|
type: "NUMBER",
|
|
description: "Max results (default 25, max 100).",
|
|
},
|
|
},
|
|
required: [],
|
|
},
|
|
},
|
|
{
|
|
name: "apps_domains_list",
|
|
description: "List the current domain set for an application.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
uuid: { type: "STRING", description: "The Coolify application UUID." },
|
|
},
|
|
required: ["uuid"],
|
|
},
|
|
},
|
|
{
|
|
name: "apps_domains_set",
|
|
description: `Set the public domain(s) for an application or service. All entries must end with .{workspace}.vibnai.com.
|
|
|
|
Auto-detects whether uuid points to an application (Dockerfile / nixpacks / docker-image / compose buildpack) or a service (template-based, e.g. Twenty CRM, n8n) and uses the right Coolify pipeline for each:
|
|
|
|
- Application: writes to applications.fqdn (or docker_compose_domains for compose buildpack).
|
|
- Service: writes to service_applications.fqdn AND triggers Coolify's updateCompose() + service.parse() so Traefik labels regenerate. Without this dance, the change gets reverted on next deploy. We learned this the hard way with twenty-live.
|
|
|
|
For services with a required port (twenty-crm uses 3000), pass { port: 3000 } so the saved fqdn is "https://host:3000" — Coolify hard-fails the save otherwise. Look up the required port via apps_templates_search if you don't know it.
|
|
|
|
After this returns, ALWAYS call apps_deploy { uuid } to regenerate the live Traefik labels.`,
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
uuid: {
|
|
type: "STRING",
|
|
description: "The Coolify application or service UUID.",
|
|
},
|
|
domains: {
|
|
type: "ARRAY",
|
|
description:
|
|
'Array of domain strings (e.g. ["myapp.mark.vibnai.com"]).',
|
|
items: { type: "STRING" },
|
|
},
|
|
service: {
|
|
type: "STRING",
|
|
description:
|
|
'For compose apps OR services: the inner app/service name to attach the domain to (e.g. "server", "twenty"). Default: auto-pick first non-worker app.',
|
|
},
|
|
port: {
|
|
type: "NUMBER",
|
|
description:
|
|
"Required for services that need a fixed upstream port (Twenty CRM = 3000, n8n = 5678, Ghost = 2368). Look up via apps_templates_search.",
|
|
},
|
|
},
|
|
required: ["uuid", "domains"],
|
|
},
|
|
},
|
|
{
|
|
name: "apps_envs_list",
|
|
description:
|
|
"List environment variables for an application. Secret values (is_shown_once) are redacted.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
uuid: { type: "STRING", description: "The Coolify application UUID." },
|
|
},
|
|
required: ["uuid"],
|
|
},
|
|
},
|
|
{
|
|
name: "apps_envs_upsert",
|
|
description:
|
|
"Create or update a single environment variable on an application.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
uuid: { type: "STRING", description: "The Coolify application UUID." },
|
|
key: {
|
|
type: "STRING",
|
|
description: 'Env var key (uppercase, e.g. "DATABASE_URL").',
|
|
},
|
|
value: { type: "STRING", description: "Env var value." },
|
|
isShownOnce: {
|
|
type: "BOOLEAN",
|
|
description:
|
|
"If true, value is write-only after creation (for secrets). Default false.",
|
|
},
|
|
isMultiline: {
|
|
type: "BOOLEAN",
|
|
description: "If true, value spans multiple lines.",
|
|
},
|
|
},
|
|
required: ["uuid", "key", "value"],
|
|
},
|
|
},
|
|
{
|
|
name: "apps_envs_delete",
|
|
description: "Delete an environment variable from an application.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
uuid: { type: "STRING", description: "The Coolify application UUID." },
|
|
key: { type: "STRING", description: "Env var key to delete." },
|
|
},
|
|
required: ["uuid", "key"],
|
|
},
|
|
},
|
|
|
|
// ── Databases ─────────────────────────────────────────────────────────────
|
|
|
|
{
|
|
name: "databases_list",
|
|
description:
|
|
"List all databases in the workspace across all flavors (Postgres, MySQL, Redis, MongoDB, etc.).",
|
|
parameters: { type: "OBJECT", properties: {}, required: [] },
|
|
},
|
|
{
|
|
name: "databases_create",
|
|
description:
|
|
"Provision a new database. Supported types: postgresql, mysql, mariadb, mongodb, redis, keydb, dragonfly, clickhouse.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
type: {
|
|
type: "STRING",
|
|
description:
|
|
'Database type: "postgresql", "mysql", "mariadb", "mongodb", "redis", "keydb", "dragonfly", or "clickhouse".',
|
|
},
|
|
name: {
|
|
type: "STRING",
|
|
description:
|
|
"Database name (slug-friendly). Auto-generated if omitted.",
|
|
},
|
|
isPublic: {
|
|
type: "BOOLEAN",
|
|
description:
|
|
"Whether to expose a public port. Default false (internal only).",
|
|
},
|
|
publicPort: {
|
|
type: "NUMBER",
|
|
description: "Public port number if isPublic is true.",
|
|
},
|
|
},
|
|
required: ["type"],
|
|
},
|
|
},
|
|
{
|
|
name: "databases_get",
|
|
description:
|
|
"Get details for a database including the internal connection URL.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
uuid: { type: "STRING", description: "The Coolify database UUID." },
|
|
},
|
|
required: ["uuid"],
|
|
},
|
|
},
|
|
{
|
|
name: "databases_update",
|
|
description:
|
|
"Update database settings: name, public visibility, image version, resource limits.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
uuid: { type: "STRING", description: "The Coolify database UUID." },
|
|
patchJson: {
|
|
type: "STRING",
|
|
description:
|
|
'Fields to update as a JSON object string (e.g. \'{"name":"new-name","is_public":true}\').',
|
|
},
|
|
},
|
|
required: ["uuid", "patchJson"],
|
|
},
|
|
},
|
|
{
|
|
name: "databases_delete",
|
|
description:
|
|
"Destroy a database. Volumes kept by default. confirm must equal the database's exact name.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
uuid: { type: "STRING", description: "The Coolify database UUID." },
|
|
confirm: {
|
|
type: "STRING",
|
|
description:
|
|
"Must equal the exact database name to confirm deletion.",
|
|
},
|
|
},
|
|
required: ["uuid", "confirm"],
|
|
},
|
|
},
|
|
|
|
// ── Auth providers ────────────────────────────────────────────────────────
|
|
|
|
{
|
|
name: "auth_list",
|
|
description:
|
|
"List deployed authentication providers in the workspace plus the allowlist of supported providers.",
|
|
parameters: { type: "OBJECT", properties: {}, required: [] },
|
|
},
|
|
{
|
|
name: "auth_create",
|
|
description:
|
|
"Deploy an auth provider from the allowlist. Supported providers: pocketbase, authentik, keycloak, keycloak-with-postgres, pocket-id, pocket-id-with-postgresql, logto, supertokens-with-postgresql.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
provider: {
|
|
type: "STRING",
|
|
description:
|
|
'Provider key (e.g. "pocketbase", "authentik", "keycloak").',
|
|
},
|
|
name: {
|
|
type: "STRING",
|
|
description: "Instance name. Auto-generated if omitted.",
|
|
},
|
|
description: { type: "STRING", description: "Optional description." },
|
|
instantDeploy: {
|
|
type: "BOOLEAN",
|
|
description: "Deploy immediately (default true).",
|
|
},
|
|
},
|
|
required: ["provider"],
|
|
},
|
|
},
|
|
{
|
|
name: "auth_delete",
|
|
description:
|
|
"Destroy an auth provider. User data volumes are kept by default. confirm must equal the service's exact name.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
uuid: { type: "STRING", description: "The Coolify service UUID." },
|
|
confirm: {
|
|
type: "STRING",
|
|
description: "Must equal the exact service name to confirm deletion.",
|
|
},
|
|
},
|
|
required: ["uuid", "confirm"],
|
|
},
|
|
},
|
|
|
|
// ── Domains ───────────────────────────────────────────────────────────────
|
|
|
|
{
|
|
name: "domains_search",
|
|
description:
|
|
"Check availability and pricing for domain names via OpenSRS. Stateless — does not reserve anything.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
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"],
|
|
},
|
|
},
|
|
{
|
|
name: "domains_list",
|
|
description:
|
|
"List all domains owned by the workspace with their status, registrar order ID, expiry, and DNS provider.",
|
|
parameters: { type: "OBJECT", properties: {}, required: [] },
|
|
},
|
|
{
|
|
name: "domains_get",
|
|
description:
|
|
"Get full record and last 20 lifecycle events for a specific domain.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
domain: {
|
|
type: "STRING",
|
|
description: 'The domain name (e.g. "myapp.com").',
|
|
},
|
|
},
|
|
required: ["domain"],
|
|
},
|
|
},
|
|
{
|
|
name: "domains_register",
|
|
description:
|
|
"Register a domain through OpenSRS. Idempotent per (workspace, domain). Confirm availability with domains_search first.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
domain: {
|
|
type: "STRING",
|
|
description: 'Domain name to register (e.g. "myapp.com").',
|
|
},
|
|
period: {
|
|
type: "NUMBER",
|
|
description: "Registration period in years (default 1).",
|
|
},
|
|
whoisPrivacy: {
|
|
type: "BOOLEAN",
|
|
description: "Enable WHOIS privacy (default true).",
|
|
},
|
|
},
|
|
required: ["domain"],
|
|
},
|
|
},
|
|
{
|
|
name: "domains_attach",
|
|
description:
|
|
"Wire a registered domain to a Coolify app: creates Cloud DNS zone, writes A/CNAME records, updates registrar nameservers, appends domain to Coolify app. Idempotent.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
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"]).',
|
|
items: { type: "STRING" },
|
|
},
|
|
},
|
|
required: ["domain"],
|
|
},
|
|
},
|
|
|
|
// ── Storage ───────────────────────────────────────────────────────────────
|
|
|
|
{
|
|
name: "storage_describe",
|
|
description:
|
|
"Report workspace GCS bucket name, region, S3-compatible endpoint, and access key ID. No secret returned.",
|
|
parameters: { type: "OBJECT", properties: {}, required: [] },
|
|
},
|
|
{
|
|
name: "storage_provision",
|
|
description:
|
|
"Idempotently create or reconcile the workspace GCS bucket, service account, IAM binding, and HMAC key. Safe to re-run.",
|
|
parameters: { type: "OBJECT", properties: {}, required: [] },
|
|
},
|
|
{
|
|
name: "storage_inject_env",
|
|
description:
|
|
"Push STORAGE_* env vars (endpoint, region, bucket, access key, secret) into a Coolify app. Secret is written server-side and never returned in the response.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
uuid: {
|
|
type: "STRING",
|
|
description:
|
|
"The Coolify application UUID to inject storage credentials into.",
|
|
},
|
|
prefix: {
|
|
type: "STRING",
|
|
description:
|
|
'Env var prefix (default "STORAGE_"; use "S3_" for AWS-standard names).',
|
|
},
|
|
},
|
|
required: ["uuid"],
|
|
},
|
|
},
|
|
|
|
// ── Gitea — repos & file CRUD (write code, not just deploy it) ────────────
|
|
//
|
|
// All gitea_* tools are scoped to the workspace's Gitea org. The AI can
|
|
// create repos, read & write files, and manage branches inside its own
|
|
// org but never outside it (enforced by requireGiteaOrg + ensureRepoOwnerInOrg
|
|
// in the MCP route).
|
|
|
|
{
|
|
name: "gitea_repos_list",
|
|
description:
|
|
"List every Gitea repo in the workspace org. Use to discover repos already provisioned for projects.",
|
|
parameters: { type: "OBJECT", properties: {}, required: [] },
|
|
},
|
|
{
|
|
name: "gitea_repo_get",
|
|
description:
|
|
"Get metadata for a single Gitea repo (default branch, clone URL, html URL, private flag).",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
repo: {
|
|
type: "STRING",
|
|
description: "Repo name (without org prefix).",
|
|
},
|
|
owner: {
|
|
type: "STRING",
|
|
description:
|
|
"Optional org/user. Defaults to the workspace Gitea org.",
|
|
},
|
|
},
|
|
required: ["repo"],
|
|
},
|
|
},
|
|
{
|
|
name: "gitea_repo_create",
|
|
description:
|
|
"Create a new Gitea repo inside the workspace org. By default initializes with a README and is private. " +
|
|
"Use this when scaffolding a new app the AI is going to write code for and then deploy via apps_create({ repo }).",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
name: {
|
|
type: "STRING",
|
|
description: "Repo name. Will be slugified (lowercase, hyphens).",
|
|
},
|
|
description: {
|
|
type: "STRING",
|
|
description: "Optional repo description.",
|
|
},
|
|
private: {
|
|
type: "BOOLEAN",
|
|
description: "Whether the repo is private (default true).",
|
|
},
|
|
autoInit: {
|
|
type: "BOOLEAN",
|
|
description:
|
|
"Initialize with README (default true). Set false if writing files immediately yourself.",
|
|
},
|
|
},
|
|
required: ["name"],
|
|
},
|
|
},
|
|
// gitea_file_{read,write,delete} were intentionally hard-removed from
|
|
// the AI tool list. The MCP REST endpoints (gitea.file.read / .write
|
|
// / .delete) remain live for 30 days for any external clients still
|
|
// depending on them, but the AI is now expected to use shell.exec +
|
|
// fs.* against the dev container for ALL iterative file work, and
|
|
// `ship` to push the result. See AI_PATH_B_EXECUTION_PLAN.md §5.
|
|
// (Repo-level orchestration tools — gitea_repos_list, gitea_repo_get,
|
|
// gitea_repo_create, gitea_branches_list, gitea_branch_create — are
|
|
// still exposed because they handle one-time setup that doesn't have
|
|
// a clean dev-container equivalent.)
|
|
{
|
|
name: "gitea_branches_list",
|
|
description:
|
|
"List all branches of a workspace Gitea repo with their head SHA.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
repo: { type: "STRING", description: "Repo name." },
|
|
owner: {
|
|
type: "STRING",
|
|
description: "Optional org. Defaults to the workspace Gitea org.",
|
|
},
|
|
},
|
|
required: ["repo"],
|
|
},
|
|
},
|
|
{
|
|
name: "gitea_branch_create",
|
|
description:
|
|
"Create a new branch in a workspace Gitea repo, branched off an existing one (default = repo default branch).",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
repo: { type: "STRING", description: "Repo name." },
|
|
name: { type: "STRING", description: "New branch name." },
|
|
from: {
|
|
type: "STRING",
|
|
description:
|
|
"Existing branch to branch from (default: repo default branch).",
|
|
},
|
|
owner: {
|
|
type: "STRING",
|
|
description: "Optional org. Defaults to the workspace Gitea org.",
|
|
},
|
|
},
|
|
required: ["repo", "name"],
|
|
},
|
|
},
|
|
|
|
// ── Path B: dev container + shell + filesystem (PREFERRED for code authoring) ──
|
|
//
|
|
// These run inside the per-project vibn-dev container. Dramatically faster
|
|
// iteration than gitea_file_* (sub-second feedback vs ~5 min redeploy).
|
|
// Use these for ALL code writing/editing/scaffolding work. Keep gitea_*
|
|
// for orchestration (creating new repos, listing branches) only.
|
|
|
|
{
|
|
name: "devcontainer_ensure",
|
|
description:
|
|
"Ensure a per-project AI dev container exists and is running. Idempotent — first call ~10s (provisions a Coolify service), subsequent calls are instant. " +
|
|
"Call this at the start of any code-authoring session. Returns the dev container service UUID and state.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
projectId: { type: "STRING", description: "The Vibn project ID." },
|
|
},
|
|
required: ["projectId"],
|
|
},
|
|
},
|
|
{
|
|
name: "devcontainer_status",
|
|
description:
|
|
"Cheap status check for the project dev container. Returns { exists, state, serviceUuid }.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
projectId: { type: "STRING", description: "The Vibn project ID." },
|
|
},
|
|
required: ["projectId"],
|
|
},
|
|
},
|
|
{
|
|
name: "shell_exec",
|
|
description:
|
|
"Run a shell command inside the project dev container as the `vibn` user (uid 1000) under /workspace. " +
|
|
"This is your universal escape hatch — install deps (`npm install`), run tests (`npm test`), scaffold code (`npx create-...`), " +
|
|
"inspect output, run migrations. Use this instead of gitea_file_* for any iterative work. " +
|
|
"Output is capped at 1 MB; default timeout 60s, max 600s.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
projectId: { type: "STRING", description: "The Vibn project ID." },
|
|
command: {
|
|
type: "STRING",
|
|
description:
|
|
"Shell command (passes through `sh -lc`, so pipes/redirects work).",
|
|
},
|
|
cwd: {
|
|
type: "STRING",
|
|
description:
|
|
"Working directory (default /workspace). Must stay under /workspace.",
|
|
},
|
|
timeoutMs: {
|
|
type: "NUMBER",
|
|
description: "Timeout in ms. Default 60000, max 600000.",
|
|
},
|
|
},
|
|
required: ["projectId", "command"],
|
|
},
|
|
},
|
|
{
|
|
name: "fs_read",
|
|
description:
|
|
"Read a file inside the project dev container. Returns the full text. Optional offset/limit for windowed reads on big files.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
projectId: { type: "STRING", description: "The Vibn project ID." },
|
|
path: {
|
|
type: "STRING",
|
|
description:
|
|
"File path. Relative paths are resolved under /workspace.",
|
|
},
|
|
offset: {
|
|
type: "NUMBER",
|
|
description: "Optional 0-based starting line.",
|
|
},
|
|
limit: { type: "NUMBER", description: "Optional max lines to return." },
|
|
},
|
|
required: ["projectId", "path"],
|
|
},
|
|
},
|
|
{
|
|
name: "fs_write",
|
|
description:
|
|
"Create or overwrite a file inside the project dev container. Use to scaffold new files. " +
|
|
"For surgical edits to existing files, prefer fs_edit (less brittle, smaller diffs).",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
projectId: { type: "STRING", description: "The Vibn project ID." },
|
|
path: {
|
|
type: "STRING",
|
|
description:
|
|
"File path. Relative paths under /workspace. Parent dirs are mkdir -p'd.",
|
|
},
|
|
content: { type: "STRING", description: "Full file content." },
|
|
},
|
|
required: ["projectId", "path", "content"],
|
|
},
|
|
},
|
|
{
|
|
name: "fs_edit",
|
|
description:
|
|
"Modify a file. You can either use line-number based replacement (PREFERRED) or search-and-replace. " +
|
|
"To use line numbers, provide startLine, endLine, and newString. " +
|
|
"To use search-and-replace, provide oldString and newString. (Always include 2-3 lines of surrounding context in oldString).",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
projectId: { type: "STRING", description: "The Vibn project ID." },
|
|
path: { type: "STRING", description: "File path under /workspace." },
|
|
newString: { type: "STRING", description: "Replacement text." },
|
|
startLine: {
|
|
type: "NUMBER",
|
|
description: "The 1-indexed start line number to replace.",
|
|
},
|
|
endLine: {
|
|
type: "NUMBER",
|
|
description: "The 1-indexed end line number to replace (inclusive).",
|
|
},
|
|
oldString: {
|
|
type: "STRING",
|
|
description:
|
|
"Exact substring to find (used only if line numbers are not provided).",
|
|
},
|
|
replaceAll: {
|
|
type: "BOOLEAN",
|
|
description:
|
|
"If true, replace every occurrence of oldString. Default false.",
|
|
},
|
|
},
|
|
required: ["projectId", "path", "newString"],
|
|
},
|
|
},
|
|
{
|
|
name: "fs_list",
|
|
description:
|
|
"List files in a directory inside the project dev container (`ls -lA`). Capped at 200 entries.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
projectId: { type: "STRING", description: "The Vibn project ID." },
|
|
path: {
|
|
type: "STRING",
|
|
description: "Directory path. Default /workspace.",
|
|
},
|
|
},
|
|
required: ["projectId"],
|
|
},
|
|
},
|
|
{
|
|
name: "fs_delete",
|
|
description:
|
|
"Delete a file or directory inside the project dev container. Set recursive=true to remove a non-empty directory.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
projectId: { type: "STRING", description: "The Vibn project ID." },
|
|
path: {
|
|
type: "STRING",
|
|
description: "Path to delete. Cannot be /workspace itself.",
|
|
},
|
|
recursive: {
|
|
type: "BOOLEAN",
|
|
description: "rm -rf if true. Default false.",
|
|
},
|
|
},
|
|
required: ["projectId", "path"],
|
|
},
|
|
},
|
|
{
|
|
name: "fs_glob",
|
|
description:
|
|
"Find files matching a glob pattern (ripgrep-backed, respects .gitignore). Returns up to 500 paths.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
projectId: { type: "STRING", description: "The Vibn project ID." },
|
|
pattern: {
|
|
type: "STRING",
|
|
description: 'Glob, e.g. "**/*.tsx" or "src/**/*.ts".',
|
|
},
|
|
cwd: {
|
|
type: "STRING",
|
|
description: "Search root (default /workspace).",
|
|
},
|
|
},
|
|
required: ["projectId", "pattern"],
|
|
},
|
|
},
|
|
{
|
|
name: "fs_grep",
|
|
description:
|
|
"ripgrep-backed code search inside the project dev container. Capped at 50 matches per file, 500 total.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
projectId: { type: "STRING", description: "The Vibn project ID." },
|
|
pattern: { type: "STRING", description: "Regex or literal string." },
|
|
glob: {
|
|
type: "STRING",
|
|
description: 'Optional file glob to filter (e.g. "*.ts").',
|
|
},
|
|
cwd: {
|
|
type: "STRING",
|
|
description: "Search root (default /workspace).",
|
|
},
|
|
contextLines: {
|
|
type: "NUMBER",
|
|
description: "Lines of context around each match (0-10).",
|
|
},
|
|
},
|
|
required: ["projectId", "pattern"],
|
|
},
|
|
},
|
|
|
|
// ── Path B: dev servers (preview URLs) ────────────────────────────────────
|
|
|
|
{
|
|
name: "dev_server_start",
|
|
description:
|
|
"Launch a long-running process inside the dev container (e.g. `npm run dev`, `python -m http.server`). " +
|
|
"Returns a preview URL the user can open in a browser. On failure (ok=false), the tool automatically returns the latest 50 lines of server logs in the 'logs' field - read this field immediately to locate compilation or type errors. " +
|
|
"The process keeps running across shell.exec calls. " +
|
|
"IMPORTANT: bind your server to 0.0.0.0 — we set HOST=0.0.0.0 + PORT=<port> automatically, but verify the framework respects them.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
projectId: { type: "STRING", description: "The Vibn project ID." },
|
|
command: {
|
|
type: "STRING",
|
|
description: 'Shell command to run (e.g. "npm run dev").',
|
|
},
|
|
port: {
|
|
type: "NUMBER",
|
|
description: "TCP port the server will listen on (1-65535).",
|
|
},
|
|
name: {
|
|
type: "STRING",
|
|
description:
|
|
"Optional friendly name for the server (used in the preview subdomain).",
|
|
},
|
|
},
|
|
required: ["projectId", "command", "port"],
|
|
},
|
|
},
|
|
{
|
|
name: "dev_server_stop",
|
|
description: "Kill a previously-started dev server by id.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
projectId: { type: "STRING", description: "The Vibn project ID." },
|
|
id: {
|
|
type: "STRING",
|
|
description: "Dev server id from dev_server_start.",
|
|
},
|
|
},
|
|
required: ["projectId", "id"],
|
|
},
|
|
},
|
|
{
|
|
name: "dev_server_list",
|
|
description: "List active (non-stopped) dev servers for a project.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
projectId: { type: "STRING", description: "The Vibn project ID." },
|
|
},
|
|
required: ["projectId"],
|
|
},
|
|
},
|
|
{
|
|
name: "dev_server_logs",
|
|
description:
|
|
"Tail recent stdout+stderr from a dev server (default last 200 lines).",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
projectId: { type: "STRING", description: "The Vibn project ID." },
|
|
id: { type: "STRING", description: "Dev server id." },
|
|
lines: {
|
|
type: "NUMBER",
|
|
description: "Number of trailing lines (1-2000, default 200).",
|
|
},
|
|
},
|
|
required: ["projectId", "id"],
|
|
},
|
|
},
|
|
|
|
{
|
|
name: "request_visual_qa",
|
|
description:
|
|
"Runs a fast background AI agent to critique a UI file (like page.tsx or .css) against a strict 5-dimensional design rubric. Use this before finishing any turn that involves visual changes.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
targetPath: {
|
|
type: "STRING",
|
|
description:
|
|
"The path of the file to critique, e.g. apps/web/app/page.tsx",
|
|
},
|
|
},
|
|
required: ["targetPath"],
|
|
},
|
|
},
|
|
|
|
{
|
|
name: "apps_templates_scaffold",
|
|
description:
|
|
"Scaffold a premium pre-built UI template directly into your project. Replaces empty Next.js setups with high-end boilerplate.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
projectId: { type: "STRING" },
|
|
templateName: {
|
|
type: "STRING",
|
|
description:
|
|
"The template to scaffold. Available: 'dashboard', 'pitch-deck'",
|
|
enum: ["dashboard", "pitch-deck"],
|
|
},
|
|
},
|
|
required: ["projectId", "templateName"],
|
|
},
|
|
},
|
|
|
|
{
|
|
name: "generate_media",
|
|
description:
|
|
"Generate images or motion graphics and save them directly into the workspace to use in your UI.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
projectId: { type: "STRING" },
|
|
prompt: {
|
|
type: "STRING",
|
|
description: "Detailed description of the media to generate",
|
|
},
|
|
type: {
|
|
type: "STRING",
|
|
enum: ["image", "video"],
|
|
description: "The type of media to generate",
|
|
},
|
|
outputPath: {
|
|
type: "STRING",
|
|
description:
|
|
"Where to save the file, e.g. /workspace/<slug>/public/hero.png",
|
|
},
|
|
},
|
|
required: ["projectId", "prompt", "type", "outputPath"],
|
|
},
|
|
},
|
|
|
|
// ── Path B: ship to production ─────────────────────────────────────────────
|
|
|
|
{
|
|
name: "ship",
|
|
description:
|
|
"Graduate the project from dev container to production. Commits everything in /workspace, pushes to the project Gitea repo, " +
|
|
'and triggers a Coolify production deploy if the project is linked to one. Use when the user says "ship it", "deploy this", ' +
|
|
"or after a stable working state has been verified via dev_server_*. Pass `commitMsg` for a meaningful commit; otherwise an ISO-timestamp message is used. " +
|
|
"Returns { commitSha, giteaCommitUrl, deploymentUuid, coolifyDeployUrl, summaryHint }. " +
|
|
"IMPORTANT: do NOT call gitea_*, shell_exec, or apps_* afterwards to verify — the result is authoritative. " +
|
|
"Just report commitSha + coolifyDeployUrl to the user.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
projectId: { type: "STRING", description: "The Vibn project ID." },
|
|
commitMsg: {
|
|
type: "STRING",
|
|
description: 'Commit message (default: "ship: <ISO timestamp>").',
|
|
},
|
|
repo: {
|
|
type: "STRING",
|
|
description: "Repo name in workspace org (defaults to project slug).",
|
|
},
|
|
branch: {
|
|
type: "STRING",
|
|
description: 'Branch to push to (default "main").',
|
|
},
|
|
deploy: {
|
|
type: "BOOLEAN",
|
|
description: "Trigger Coolify deploy after push (default true).",
|
|
},
|
|
},
|
|
required: ["projectId"],
|
|
},
|
|
},
|
|
|
|
// ── Non-MCP: GitHub & web ─────────────────────────────────────────────────
|
|
|
|
{
|
|
name: "github_search",
|
|
description:
|
|
"Search GitHub for public repositories. Use to find open source reference projects, design systems, or starting points. " +
|
|
'Add "license:mit" to ensure permissive licensing. ' +
|
|
'Example queries: "license:mit self-hosted crm", "license:mit kanban react", "license:mit design-system components".',
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
query: {
|
|
type: "STRING",
|
|
description:
|
|
"GitHub search query. Include license:mit unless intentionally looking for non-MIT. " +
|
|
"Supports: language:typescript, stars:>500, topic:self-hosted, filename:docker-compose.yml.",
|
|
},
|
|
sort: {
|
|
type: "STRING",
|
|
description: 'Sort by: "stars" (default), "updated", or "forks".',
|
|
},
|
|
limit: {
|
|
type: "NUMBER",
|
|
description: "Results to return (default 8, max 20).",
|
|
},
|
|
},
|
|
required: ["query"],
|
|
},
|
|
},
|
|
{
|
|
name: "github_file",
|
|
description:
|
|
"Read a specific file from a public GitHub repository. Use to study design systems, component libraries, " +
|
|
"README files, package.json, docker-compose.yml, or any file in an open source project.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
repo: {
|
|
type: "STRING",
|
|
description:
|
|
'Repository in "owner/repo" format (e.g. "makeplane/plane").',
|
|
},
|
|
path: {
|
|
type: "STRING",
|
|
description:
|
|
'File path within the repo (e.g. "README.md", "docker-compose.yml").',
|
|
},
|
|
ref: {
|
|
type: "STRING",
|
|
description: 'Branch or commit ref (default: "main").',
|
|
},
|
|
},
|
|
required: ["repo", "path"],
|
|
},
|
|
},
|
|
{
|
|
name: "http_fetch",
|
|
description:
|
|
"Fetch any public URL and return the response body as text. Use for reading documentation, " +
|
|
"API responses, or any public web resource. Response truncated to 12KB.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
url: {
|
|
type: "STRING",
|
|
description: "The full URL to fetch (https preferred).",
|
|
},
|
|
headersJson: {
|
|
type: "STRING",
|
|
description:
|
|
'Optional HTTP headers as a JSON object string (e.g. \'{"Accept":"application/json"}\').',
|
|
},
|
|
},
|
|
required: ["url"],
|
|
},
|
|
},
|
|
|
|
// ── Plan (vision · tasks · decisions · ideas) ────────────────────────────
|
|
// The Plan tab is where the founder's THINKING lives. The AI is the
|
|
// scribe: capture decisions in the moment so they don't get re-litigated,
|
|
// log tasks the AI commits to next, and parking-lot stray ideas.
|
|
|
|
{
|
|
name: "plan_get",
|
|
description:
|
|
"Read the full Plan for a project: vision, ideas, open + done tasks, and decisions. Use to check what has already been decided BEFORE asking the user to re-decide.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
projectId: { type: "STRING", description: "The Vibn project ID." },
|
|
},
|
|
required: ["projectId"],
|
|
},
|
|
},
|
|
{
|
|
name: "plan_vision_set",
|
|
description:
|
|
"Update the project objective or vision. If a detailed objective document already exists, ONLY call this if you are explicitly appending to it or replacing it with a better, comprehensive version. Do NOT overwrite a detailed brief with a short summary.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
projectId: { type: "STRING", description: "The Vibn project ID." },
|
|
text: {
|
|
type: "STRING",
|
|
description:
|
|
"The new or updated vision statement / objective doc. Be thorough.",
|
|
},
|
|
},
|
|
required: ["projectId", "text"],
|
|
},
|
|
},
|
|
{
|
|
name: "plan_document_update",
|
|
description:
|
|
"Overwrite the content of one of the Blueprint documents in the Plan tab. These documents define the specifications for the product. ALWAYS use this instead of `fs_write` when a user asks you to update the PRD, Spec, or Architecture plan.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
projectId: { type: "STRING" },
|
|
docId: {
|
|
type: "STRING",
|
|
description: "The specific document to update.",
|
|
enum: [
|
|
"stories",
|
|
"acceptance",
|
|
"success",
|
|
"ui_design",
|
|
"tech_context",
|
|
"data_model",
|
|
"file_structure",
|
|
"tasks",
|
|
"checklist",
|
|
],
|
|
},
|
|
content: {
|
|
type: "STRING",
|
|
description:
|
|
"The full markdown content for the document. This will completely overwrite the existing document.",
|
|
},
|
|
},
|
|
required: ["projectId", "docId", "content"],
|
|
},
|
|
},
|
|
{
|
|
name: "plan_task_add",
|
|
description:
|
|
"Add an open task. Tasks are SCOPED UNITS OF WORK with a markdown spec — a feature, refactor, investigation, or migration. Each task should be substantive enough that an autonomous agent could execute it. Provide a verb-led `title` AND a markdown `description` containing: ## Goal, ## Context, ## Acceptance criteria (checklist), and ## Notes if relevant. Don't use this for trivial reminders — only for things that warrant a brief.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
projectId: { type: "STRING", description: "The Vibn project ID." },
|
|
title: {
|
|
type: "STRING",
|
|
description:
|
|
'Short verb-led headline (e.g. "Migrate auth to NextAuth v5").',
|
|
},
|
|
description: {
|
|
type: "STRING",
|
|
description:
|
|
"Markdown spec for this task. Include ## Goal, ## Context, ## Acceptance criteria, optionally ## Notes. Strongly recommended.",
|
|
},
|
|
},
|
|
required: ["projectId", "title"],
|
|
},
|
|
},
|
|
{
|
|
name: "plan_task_edit",
|
|
description:
|
|
'Edit an existing task\'s title, description, or status. When you have finished a task, put it in "review" status unless the user explicitly told you to mark it as "done". Look up the taskId from plan_get first.',
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
projectId: { type: "STRING", description: "The Vibn project ID." },
|
|
taskId: {
|
|
type: "STRING",
|
|
description: "Task id from plan_get.tasks[].id.",
|
|
},
|
|
title: {
|
|
type: "STRING",
|
|
description: "Updated short verb-led headline.",
|
|
},
|
|
description: {
|
|
type: "STRING",
|
|
description: "Updated markdown spec for this task.",
|
|
},
|
|
status: {
|
|
type: "STRING",
|
|
description:
|
|
'Status of the task: "open", "in_progress", "review", "done", "blocked".',
|
|
enum: ["open", "in_progress", "review", "done", "blocked"],
|
|
},
|
|
},
|
|
required: ["projectId", "taskId"],
|
|
},
|
|
},
|
|
{
|
|
name: "plan_task_complete",
|
|
description:
|
|
"Mark an open task done. Call when you (or the user with your help) just finished something already on the task list. Look up the taskId from plan_get first.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
projectId: { type: "STRING", description: "The Vibn project ID." },
|
|
taskId: {
|
|
type: "STRING",
|
|
description: "Task id from plan_get.tasks[].id.",
|
|
},
|
|
},
|
|
required: ["projectId", "taskId"],
|
|
},
|
|
},
|
|
{
|
|
name: "plan_idea_add",
|
|
description:
|
|
"Park an idea the user mentions but isn't ready to act on. Use sparingly — only when the thought is genuinely worth remembering and isn't already a task or decision.",
|
|
parameters: {
|
|
type: "OBJECT",
|
|
properties: {
|
|
projectId: { type: "STRING", description: "The Vibn project ID." },
|
|
text: {
|
|
type: "STRING",
|
|
description: "The idea, in the user's own words when possible.",
|
|
},
|
|
},
|
|
required: ["projectId", "text"],
|
|
},
|
|
},
|
|
];
|
|
|
|
// ── Tool execution ────────────────────────────────────────────────────────────
|
|
|
|
const NON_MCP_TOOLS = new Set(["github_search", "github_file", "http_fetch"]);
|
|
|
|
/**
|
|
* Execute any Vibn tool. Non-MCP tools (GitHub, http_fetch) run locally.
|
|
* All MCP tools forward to POST /api/mcp — the tool name maps to the MCP
|
|
* action by replacing underscores with dots (e.g. apps_create → apps.create).
|
|
*/
|
|
export async function executeMcpTool(
|
|
toolName: string,
|
|
args: Record<string, unknown>,
|
|
mcpToken: string,
|
|
baseUrl: string,
|
|
projectId?: string,
|
|
): Promise<string> {
|
|
if (toolName === "github_search") return executeGithubSearch(args);
|
|
if (toolName === "github_file") return executeGithubFile(args);
|
|
if (toolName === "http_fetch") return executeHttpFetch(args);
|
|
|
|
// 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<string, unknown> = { ...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 headers: Record<string, string> = {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${mcpToken}`,
|
|
};
|
|
if (projectId) {
|
|
headers["X-Vibn-Project-Id"] = projectId;
|
|
}
|
|
|
|
const res = await fetch(`${baseUrl}/api/mcp`, {
|
|
method: "POST",
|
|
headers,
|
|
body: JSON.stringify({ action, params }),
|
|
});
|
|
const data = await res.json();
|
|
return JSON.stringify(data.result ?? data.error ?? data, null, 2).slice(
|
|
0,
|
|
8000,
|
|
);
|
|
} catch (e) {
|
|
return JSON.stringify({
|
|
error: e instanceof Error ? e.message : String(e),
|
|
});
|
|
}
|
|
}
|
|
|
|
// ── Non-MCP implementations ───────────────────────────────────────────────────
|
|
|
|
async function executeGithubSearch(
|
|
args: Record<string, unknown>,
|
|
): Promise<string> {
|
|
const query = String(args.query || "");
|
|
const sort = String(args.sort || "stars");
|
|
const limit = Math.min(Number(args.limit || 8), 20);
|
|
|
|
try {
|
|
const params = new URLSearchParams({
|
|
q: query,
|
|
sort,
|
|
order: "desc",
|
|
per_page: String(limit),
|
|
});
|
|
const headers: Record<string, string> = {
|
|
Accept: "application/vnd.github+json",
|
|
"X-GitHub-Api-Version": "2022-11-28",
|
|
};
|
|
if (GITHUB_TOKEN) headers["Authorization"] = `Bearer ${GITHUB_TOKEN}`;
|
|
|
|
const res = await fetch(
|
|
`https://api.github.com/search/repositories?${params}`,
|
|
{ headers },
|
|
);
|
|
const data = await res.json();
|
|
if (!res.ok)
|
|
return JSON.stringify({ error: data.message || "GitHub API error" });
|
|
|
|
const repos = (data.items || []).map((r: any) => ({
|
|
name: r.full_name,
|
|
description: r.description,
|
|
stars: r.stargazers_count,
|
|
language: r.language,
|
|
license: r.license?.spdx_id,
|
|
topics: r.topics,
|
|
updatedAt: r.pushed_at?.slice(0, 10),
|
|
url: r.html_url,
|
|
defaultBranch: r.default_branch,
|
|
}));
|
|
|
|
return JSON.stringify({ total: data.total_count, repos }, null, 2);
|
|
} catch (e) {
|
|
return JSON.stringify({
|
|
error: e instanceof Error ? e.message : String(e),
|
|
});
|
|
}
|
|
}
|
|
|
|
async function executeGithubFile(
|
|
args: Record<string, unknown>,
|
|
): Promise<string> {
|
|
const repo = String(args.repo || "");
|
|
const path = String(args.path || "");
|
|
const ref = String(args.ref || "main");
|
|
|
|
try {
|
|
const headers: Record<string, string> = {
|
|
Accept: "application/vnd.github.raw+json",
|
|
"X-GitHub-Api-Version": "2022-11-28",
|
|
};
|
|
if (GITHUB_TOKEN) headers["Authorization"] = `Bearer ${GITHUB_TOKEN}`;
|
|
|
|
const res = await fetch(
|
|
`https://api.github.com/repos/${repo}/contents/${path}?ref=${ref}`,
|
|
{ headers },
|
|
);
|
|
if (!res.ok) {
|
|
if (ref === "main") {
|
|
const res2 = await fetch(
|
|
`https://api.github.com/repos/${repo}/contents/${path}?ref=master`,
|
|
{ headers },
|
|
);
|
|
if (res2.ok) return (await res2.text()).slice(0, 12000);
|
|
}
|
|
return JSON.stringify({
|
|
error: `File not found: ${repo}/${path} (ref: ${ref})`,
|
|
});
|
|
}
|
|
return (await res.text()).slice(0, 12000);
|
|
} catch (e) {
|
|
return JSON.stringify({
|
|
error: e instanceof Error ? e.message : String(e),
|
|
});
|
|
}
|
|
}
|
|
|
|
async function executeHttpFetch(
|
|
args: Record<string, unknown>,
|
|
): Promise<string> {
|
|
const url = String(args.url || "");
|
|
let extraHeaders: Record<string, string> = {};
|
|
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://" });
|
|
}
|
|
|
|
try {
|
|
const res = await fetch(url, {
|
|
headers: { "User-Agent": "Vibn-AI/1.0", ...extraHeaders },
|
|
signal: AbortSignal.timeout(10_000),
|
|
});
|
|
const contentType = res.headers.get("content-type") || "";
|
|
const body = contentType.includes("json")
|
|
? JSON.stringify(await res.json(), null, 2)
|
|
: await res.text();
|
|
return `HTTP ${res.status}\nContent-Type: ${contentType}\n\n${body}`.slice(
|
|
0,
|
|
12000,
|
|
);
|
|
} catch (e) {
|
|
return JSON.stringify({
|
|
error: e instanceof Error ? e.message : String(e),
|
|
});
|
|
}
|
|
}
|