Expand chat panel to full MCP tool surface (35+ tools)
vibn-tools.ts previously exposed only 12 of the 35+ MCP tools. Now includes the complete surface from AI_CAPABILITIES.md: - workspace.describe, gitea.credentials - apps: get, update, rewire_git, delete, deploy, deployments, exec, volumes.list/wipe, containers.up/ps, repair, domains.list/set, envs.list/upsert/delete - databases: list, create, get, update, delete - auth: list, create, delete - domains: search, get, attach (+ existing register, list) - storage: describe, provision, inject_env Action dispatch simplified: toolName.replace(/_/g, '.') maps any tool name to the MCP action with no explicit lookup table needed. System prompt updated to reflect full capability set. Made-with: Cursor
This commit is contained in:
@@ -73,20 +73,22 @@ When a user asks what's live, call \`apps_list\` — not \`projects_get\`. Proje
|
||||
${projectsText}
|
||||
|
||||
## Your capabilities
|
||||
You can take real actions by calling tools:
|
||||
- List/inspect projects and deployed apps (\`apps_list\` shows live services)
|
||||
- Deploy new applications from templates (n8n, WordPress, Twenty CRM, etc.) or Docker images
|
||||
- Register and manage custom domains
|
||||
- View application logs
|
||||
- Search available app templates
|
||||
- Search GitHub for open-source projects to reference or fork
|
||||
- Fetch any public URL or file
|
||||
You have full access to the Vibn platform. You can:
|
||||
- **Apps**: list, create, update, delete, deploy, get logs, exec commands, manage domains, manage env vars, manage volumes, repair broken deploys
|
||||
- **Databases**: provision Postgres/MySQL/Redis/MongoDB and 5 other flavors, get connection URLs
|
||||
- **Auth providers**: deploy Pocketbase, Authentik, Keycloak, Logto, SuperTokens, and others
|
||||
- **Domains**: search availability, register, attach to apps with full DNS wiring
|
||||
- **Storage**: provision GCS buckets, inject S3-compatible credentials into apps
|
||||
- **Templates**: search and deploy from 320+ one-click app templates (n8n, Ghost, Supabase, etc.)
|
||||
- **GitHub**: search open-source repos, read any file from a public repo
|
||||
- **Web**: fetch any public URL or documentation page
|
||||
|
||||
## Guidelines
|
||||
- Be concise and action-oriented. If the user wants to deploy something, do it — don't just describe how.
|
||||
- When deploying, always confirm the app name and domain with the user first unless they've been explicit.
|
||||
- After a tool call, summarize what happened in plain language.
|
||||
- If a deployment takes time, let the user know it may take a few minutes.
|
||||
## Key rules
|
||||
- Call \`apps_list\` (not \`projects_list\`) when the user asks what is running or what has a domain.
|
||||
- Always call \`apps_templates_search\` before \`apps_create\` for any popular third-party app.
|
||||
- Be concise and action-oriented — if the user wants to deploy something, do it, don't just describe how.
|
||||
- Confirm app name and domain before deploying unless the user has been explicit.
|
||||
- After tool calls, summarize in plain language what happened.
|
||||
- Format code, URLs, and technical values in backticks.
|
||||
- Today's date: ${new Date().toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })}.`;
|
||||
}
|
||||
|
||||
@@ -1,118 +1,522 @@
|
||||
/**
|
||||
* Vibn MCP tool definitions for the chat assistant.
|
||||
* These are surfaced to Gemini as function declarations so the model
|
||||
* can take actions (deploy apps, list projects, etc.) on behalf of the user.
|
||||
* 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: '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 projects in the user\'s workspace with their status, last activity, and session counts.',
|
||||
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: 'projects_get',
|
||||
description: 'Get detailed information about a specific project by its ID.',
|
||||
description: 'Get details for a single Vibn project by ID (name, status, vision, linked Coolify UUID).',
|
||||
parameters: {
|
||||
type: 'OBJECT',
|
||||
properties: {
|
||||
projectId: { type: 'STRING', description: 'The project ID to retrieve.' },
|
||||
projectId: { type: 'STRING', description: 'The Vibn project ID.' },
|
||||
},
|
||||
required: ['projectId'],
|
||||
},
|
||||
},
|
||||
|
||||
// ── Applications ─────────────────────────────────────────────────────────
|
||||
|
||||
{
|
||||
name: 'apps_list',
|
||||
description: 'List all deployed applications in the Coolify workspace.',
|
||||
description: 'List all live applications and services deployed in the Coolify workspace. Use this (not projects_list) when the user asks what is running or what has a domain.',
|
||||
parameters: { type: 'OBJECT', properties: {}, required: [] },
|
||||
},
|
||||
{
|
||||
name: 'apps_create',
|
||||
description: 'Create and deploy a new application. Can deploy from a template (e.g. n8n, wordpress, twenty) or a Docker image.',
|
||||
name: 'apps_get',
|
||||
description: 'Get full details for a single app: status, domain, git info, build pack, environment.',
|
||||
parameters: {
|
||||
type: 'OBJECT',
|
||||
properties: {
|
||||
name: { type: 'STRING', description: 'App name (slug-friendly, e.g. "my-app").' },
|
||||
domain: { type: 'STRING', description: 'Custom domain (e.g. "myapp.mark.vibnai.com").' },
|
||||
template: { type: 'STRING', description: 'Coolify service template name (e.g. "n8n", "wordpress", "twenty").' },
|
||||
dockerImage: { type: 'STRING', description: 'Docker image to deploy (e.g. "nginx:latest"). Use instead of template.' },
|
||||
uuid: { type: 'STRING', description: 'The Coolify application UUID.' },
|
||||
},
|
||||
required: ['name', 'domain'],
|
||||
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: {
|
||||
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.' },
|
||||
envs: { type: 'OBJECT', description: 'Environment variables as key-value pairs to inject at creation.' },
|
||||
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.' },
|
||||
patch: { type: 'OBJECT', description: 'Fields to update as key-value pairs.' },
|
||||
},
|
||||
required: ['uuid', 'patch'],
|
||||
},
|
||||
},
|
||||
{
|
||||
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_templates_list',
|
||||
description: 'List available one-click application templates (n8n, wordpress, twenty, etc.).',
|
||||
parameters: { type: 'OBJECT', properties: {}, required: [] },
|
||||
},
|
||||
{
|
||||
name: 'apps_templates_search',
|
||||
description: 'Search for a specific application template by name or keyword.',
|
||||
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: {
|
||||
query: { type: 'STRING', description: 'Search query (e.g. "crm", "blog", "automation").' },
|
||||
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: ['query'],
|
||||
required: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'domains_register',
|
||||
description: 'Register a new domain name via OpenSRS.',
|
||||
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: {
|
||||
domain: { type: 'STRING', description: 'Domain name to register (e.g. "myapp.com").' },
|
||||
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: 'Replace the domain set for an application. All entries must end with .{workspace}.vibnai.com. For compose apps, use the service parameter to target a specific service.',
|
||||
parameters: {
|
||||
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"]).' },
|
||||
service: { type: 'STRING', description: 'For compose apps: the service to attach the domain to (e.g. "server"). Default: "server".' },
|
||||
},
|
||||
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.' },
|
||||
patch: { type: 'OBJECT', description: 'Fields to update.' },
|
||||
},
|
||||
required: ['uuid', 'patch'],
|
||||
},
|
||||
},
|
||||
{
|
||||
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.' },
|
||||
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_list',
|
||||
description: 'List all domains registered or attached in the workspace.',
|
||||
parameters: { type: 'OBJECT', properties: {}, required: [] },
|
||||
},
|
||||
{
|
||||
name: 'apps_logs',
|
||||
description: 'Get recent runtime logs from a deployed application.',
|
||||
name: 'domains_register',
|
||||
description: 'Register a domain through OpenSRS. Idempotent per (workspace, domain). Confirm availability with domains_search first.',
|
||||
parameters: {
|
||||
type: 'OBJECT',
|
||||
properties: {
|
||||
appUuid: { type: 'STRING', description: 'The Coolify application UUID.' },
|
||||
lines: { type: 'NUMBER', description: 'Number of log lines to return (default 50).' },
|
||||
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: ['appUuid'],
|
||||
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"]).' },
|
||||
},
|
||||
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'],
|
||||
},
|
||||
},
|
||||
|
||||
// ── Non-MCP: GitHub & web ─────────────────────────────────────────────────
|
||||
|
||||
{
|
||||
name: 'github_search',
|
||||
description:
|
||||
'Search GitHub for public repositories. Use this to find open source projects, ' +
|
||||
'reference implementations, design systems, or starting points. ' +
|
||||
'Always add "license:mit" to the query to ensure permissive licensing. ' +
|
||||
'Example queries: "license:mit self-hosted crm", "license:mit kanban board react", ' +
|
||||
'"license:mit design-system components".',
|
||||
'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 you have a reason not to. ' +
|
||||
'You can filter by language (language:typescript), stars (stars:>500), ' +
|
||||
'topics (topic:self-hosted), and file presence (filename:docker-compose.yml).',
|
||||
},
|
||||
sort: {
|
||||
type: 'STRING',
|
||||
description: 'Sort by: "stars" (default), "updated", or "forks".',
|
||||
},
|
||||
limit: {
|
||||
type: 'NUMBER',
|
||||
description: 'Number of results to return (default 8, max 20).',
|
||||
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'],
|
||||
},
|
||||
@@ -120,26 +524,14 @@ export const VIBN_TOOL_DEFINITIONS: ToolDefinition[] = [
|
||||
{
|
||||
name: 'github_file',
|
||||
description:
|
||||
'Read a specific file from a public GitHub repository. ' +
|
||||
'Use this to study a project\'s design system, component library, README, ' +
|
||||
'package.json, docker-compose.yml, or any other file. ' +
|
||||
'Great for understanding how an existing open source project is structured ' +
|
||||
'before building something inspired by it.',
|
||||
'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", "packages/ui/src/index.ts", "docker-compose.yml").',
|
||||
},
|
||||
ref: {
|
||||
type: 'STRING',
|
||||
description: 'Branch or commit ref (default: "main").',
|
||||
},
|
||||
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'],
|
||||
},
|
||||
@@ -147,30 +539,27 @@ export const VIBN_TOOL_DEFINITIONS: ToolDefinition[] = [
|
||||
{
|
||||
name: 'http_fetch',
|
||||
description:
|
||||
'Fetch any public URL and return the response body as text. ' +
|
||||
'Use for reading documentation, API responses, public datasets, ' +
|
||||
'or any web resource relevant to the user\'s request. ' +
|
||||
'Response is truncated to 12KB to fit context.',
|
||||
'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 (must be publicly accessible, https preferred).',
|
||||
},
|
||||
headers: {
|
||||
type: 'OBJECT',
|
||||
description: 'Optional HTTP headers as key-value pairs.',
|
||||
},
|
||||
url: { type: 'STRING', description: 'The full URL to fetch (https preferred).' },
|
||||
headers: { type: 'OBJECT', description: 'Optional HTTP headers as key-value pairs.' },
|
||||
},
|
||||
required: ['url'],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// ── Tool execution ────────────────────────────────────────────────────────────
|
||||
|
||||
const NON_MCP_TOOLS = new Set(['github_search', 'github_file', 'http_fetch']);
|
||||
|
||||
/**
|
||||
* Execute a Vibn MCP tool call by forwarding to the internal MCP API.
|
||||
* Called server-side from the /api/chat route, using the user's workspace API key.
|
||||
* 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,
|
||||
@@ -178,28 +567,12 @@ export async function executeMcpTool(
|
||||
mcpToken: string,
|
||||
baseUrl: string,
|
||||
): Promise<string> {
|
||||
// Handle non-MCP tools directly
|
||||
if (toolName === 'github_search') return executeGithubSearch(args);
|
||||
if (toolName === 'github_file') return executeGithubFile(args);
|
||||
if (toolName === 'http_fetch') return executeHttpFetch(args);
|
||||
|
||||
// Map tool names (underscored) back to MCP action names (dotted)
|
||||
const actionMap: Record<string, string> = {
|
||||
projects_list: 'projects.list',
|
||||
projects_get: 'projects.get',
|
||||
apps_list: 'apps.list',
|
||||
apps_create: 'apps.create',
|
||||
apps_templates_list: 'apps.templates.list',
|
||||
apps_templates_search: 'apps.templates.search',
|
||||
domains_register: 'domains.register',
|
||||
domains_list: 'domains.list',
|
||||
apps_logs: 'apps.logs',
|
||||
};
|
||||
|
||||
const action = actionMap[toolName];
|
||||
if (!action) {
|
||||
return JSON.stringify({ error: `Unknown tool: ${toolName}` });
|
||||
}
|
||||
// Convert underscore tool name → dotted MCP action (apps_create → apps.create)
|
||||
const action = toolName.replace(/_/g, '.');
|
||||
|
||||
try {
|
||||
const res = await fetch(`${baseUrl}/api/mcp`, {
|
||||
@@ -217,7 +590,7 @@ export async function executeMcpTool(
|
||||
}
|
||||
}
|
||||
|
||||
// ── Non-MCP tool implementations ──────────────────────────────────────────────
|
||||
// ── Non-MCP implementations ───────────────────────────────────────────────────
|
||||
|
||||
async function executeGithubSearch(args: Record<string, unknown>): Promise<string> {
|
||||
const query = String(args.query || '');
|
||||
@@ -225,12 +598,7 @@ async function executeGithubSearch(args: Record<string, unknown>): Promise<strin
|
||||
const limit = Math.min(Number(args.limit || 8), 20);
|
||||
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
q: query,
|
||||
sort,
|
||||
order: 'desc',
|
||||
per_page: String(limit),
|
||||
});
|
||||
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',
|
||||
@@ -239,7 +607,6 @@ async function executeGithubSearch(args: Record<string, unknown>): Promise<strin
|
||||
|
||||
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) => ({
|
||||
@@ -249,7 +616,6 @@ async function executeGithubSearch(args: Record<string, unknown>): Promise<strin
|
||||
language: r.language,
|
||||
license: r.license?.spdx_id,
|
||||
topics: r.topics,
|
||||
hasDockerfile: r.topics?.includes('docker') || false,
|
||||
updatedAt: r.pushed_at?.slice(0, 10),
|
||||
url: r.html_url,
|
||||
defaultBranch: r.default_branch,
|
||||
@@ -273,29 +639,15 @@ async function executeGithubFile(args: Record<string, unknown>): Promise<string>
|
||||
};
|
||||
if (GITHUB_TOKEN) headers['Authorization'] = `Bearer ${GITHUB_TOKEN}`;
|
||||
|
||||
const res = await fetch(
|
||||
`https://api.github.com/repos/${repo}/contents/${path}?ref=${ref}`,
|
||||
{ headers },
|
||||
);
|
||||
|
||||
const res = await fetch(`https://api.github.com/repos/${repo}/contents/${path}?ref=${ref}`, { headers });
|
||||
if (!res.ok) {
|
||||
// Try alternate branch if main fails
|
||||
if (ref === 'main') {
|
||||
const res2 = await fetch(
|
||||
`https://api.github.com/repos/${repo}/contents/${path}?ref=master`,
|
||||
{ headers },
|
||||
);
|
||||
if (res2.ok) {
|
||||
const text = await res2.text();
|
||||
return text.slice(0, 12000);
|
||||
}
|
||||
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})` });
|
||||
}
|
||||
|
||||
const text = await res.text();
|
||||
// GitHub returns base64-encoded content for files; raw+json accept header gets raw text
|
||||
return text.slice(0, 12000);
|
||||
return (await res.text()).slice(0, 12000);
|
||||
} catch (e) {
|
||||
return JSON.stringify({ error: e instanceof Error ? e.message : String(e) });
|
||||
}
|
||||
@@ -311,23 +663,13 @@ async function executeHttpFetch(args: Record<string, unknown>): Promise<string>
|
||||
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
headers: {
|
||||
'User-Agent': 'Vibn-AI/1.0',
|
||||
...extraHeaders,
|
||||
},
|
||||
headers: { 'User-Agent': 'Vibn-AI/1.0', ...extraHeaders },
|
||||
signal: AbortSignal.timeout(10_000),
|
||||
});
|
||||
|
||||
const contentType = res.headers.get('content-type') || '';
|
||||
let body: string;
|
||||
|
||||
if (contentType.includes('json')) {
|
||||
const json = await res.json();
|
||||
body = JSON.stringify(json, null, 2);
|
||||
} else {
|
||||
body = await res.text();
|
||||
}
|
||||
|
||||
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) });
|
||||
|
||||
Reference in New Issue
Block a user