feat: v3.7.0 — Coolify MCP plugin (30 tools)
Some checks failed
Release / Test before release (push) Has been cancelled
Release / Publish to PyPI (push) Has been cancelled
Release / Publish to Docker Hub (push) Has been cancelled
Release / Create GitHub Release (push) Has been cancelled

New plugin: Coolify deployment management (applications, deployments, servers).
10 plugins, 596 tools, 686 tests passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-02 18:55:01 +02:00
parent bf893a743a
commit 151ba8e6e6
20 changed files with 2526 additions and 9 deletions

View File

@@ -11,6 +11,7 @@ v2.3.0 (Phase G): Supabase Self-Hosted Plugin added
from plugins.appwrite.plugin import AppwritePlugin
from plugins.base import BasePlugin, PluginRegistry
from plugins.coolify.plugin import CoolifyPlugin
from plugins.directus.plugin import DirectusPlugin
from plugins.gitea.plugin import GiteaPlugin
from plugins.n8n.plugin import N8nPlugin
@@ -33,6 +34,7 @@ registry.register("supabase", SupabasePlugin)
registry.register("openpanel", OpenPanelPlugin)
registry.register("appwrite", AppwritePlugin)
registry.register("directus", DirectusPlugin)
registry.register("coolify", CoolifyPlugin)
__all__ = [
"BasePlugin",
@@ -47,4 +49,5 @@ __all__ = [
"OpenPanelPlugin",
"AppwritePlugin",
"DirectusPlugin",
"CoolifyPlugin",
]

View File

@@ -0,0 +1,5 @@
"""
Coolify Plugin — AI-driven deployment management for Coolify instances.
F.17: Phase 1 MVP — Applications, Deployments, Servers (~30 tools)
"""

269
plugins/coolify/client.py Normal file
View File

@@ -0,0 +1,269 @@
"""
Coolify REST API Client
Handles all HTTP communication with Coolify REST API.
Separates API communication from business logic.
"""
import logging
from typing import Any
import aiohttp
class CoolifyClient:
"""
Coolify REST API client for HTTP communication.
Handles Bearer token authentication, request formatting,
and error handling for all Coolify API v1 endpoints.
"""
def __init__(self, site_url: str, token: str):
"""
Initialize Coolify API client.
Args:
site_url: Coolify instance URL (e.g., https://coolify.example.com)
token: API token for Bearer authentication
"""
self.site_url = site_url.rstrip("/")
self.api_base = f"{self.site_url}/api/v1"
self.token = token
self.logger = logging.getLogger(f"CoolifyClient.{site_url}")
def _get_headers(self) -> dict[str, str]:
"""Get request headers with Bearer authentication."""
return {
"Authorization": f"Bearer {self.token}",
"Content-Type": "application/json",
"Accept": "application/json",
}
async def request(
self,
method: str,
endpoint: str,
params: dict | None = None,
json_data: dict | None = None,
) -> Any:
"""
Make authenticated request to Coolify REST API.
Args:
method: HTTP method (GET, POST, PATCH, DELETE)
endpoint: API endpoint (without base URL)
params: Query parameters
json_data: JSON body data
Returns:
API response (dict, list, or None)
Raises:
Exception: On API errors with status code and message
"""
url = f"{self.api_base}/{endpoint.lstrip('/')}"
if params:
params = {k: v for k, v in params.items() if v is not None}
if json_data:
json_data = {k: v for k, v in json_data.items() if v is not None}
self.logger.debug(f"{method} {url}")
async with (
aiohttp.ClientSession() as session,
session.request(
method=method,
url=url,
params=params,
json=json_data,
headers=self._get_headers(),
) as response,
):
self.logger.debug(f"Response status: {response.status}")
if response.status == 204:
return {"success": True, "message": "Operation completed successfully"}
try:
response_data = await response.json()
except Exception:
response_text = await response.text()
if response.status >= 400:
raise Exception(
f"Coolify API error (status {response.status}): {response_text}"
)
return {"success": True, "message": response_text}
if response.status >= 400:
error_msg = response_data.get("message", "Unknown error")
raise Exception(f"Coolify API error (status {response.status}): {error_msg}")
return response_data
# --- Applications ---
async def list_applications(self, tag: str | None = None) -> list[dict]:
"""List all applications."""
params = {"tag": tag} if tag else {}
return await self.request("GET", "applications", params=params)
async def get_application(self, uuid: str) -> dict:
"""Get application by UUID."""
return await self.request("GET", f"applications/{uuid}")
async def create_application_public(self, data: dict) -> dict:
"""Create application from public repository."""
return await self.request("POST", "applications/public", json_data=data)
async def create_application_dockerfile(self, data: dict) -> dict:
"""Create application from Dockerfile."""
return await self.request("POST", "applications/dockerfile", json_data=data)
async def create_application_docker_image(self, data: dict) -> dict:
"""Create application from Docker image."""
return await self.request("POST", "applications/dockerimage", json_data=data)
async def create_application_docker_compose(self, data: dict) -> dict:
"""Create application from Docker Compose."""
return await self.request("POST", "applications/dockercompose", json_data=data)
async def update_application(self, uuid: str, data: dict) -> dict:
"""Update application by UUID."""
return await self.request("PATCH", f"applications/{uuid}", json_data=data)
async def delete_application(
self,
uuid: str,
delete_configurations: bool = True,
delete_volumes: bool = True,
docker_cleanup: bool = True,
delete_connected_networks: bool = True,
) -> dict:
"""Delete application by UUID."""
params = {
"delete_configurations": str(delete_configurations).lower(),
"delete_volumes": str(delete_volumes).lower(),
"docker_cleanup": str(docker_cleanup).lower(),
"delete_connected_networks": str(delete_connected_networks).lower(),
}
return await self.request("DELETE", f"applications/{uuid}", params=params)
async def start_application(
self, uuid: str, force: bool = False, instant_deploy: bool = False
) -> dict:
"""Deploy/start application."""
params = {}
if force:
params["force"] = "true"
if instant_deploy:
params["instant_deploy"] = "true"
return await self.request("GET", f"applications/{uuid}/start", params=params)
async def stop_application(self, uuid: str, docker_cleanup: bool = True) -> dict:
"""Stop application."""
params = {"docker_cleanup": str(docker_cleanup).lower()}
return await self.request("GET", f"applications/{uuid}/stop", params=params)
async def restart_application(self, uuid: str) -> dict:
"""Restart application."""
return await self.request("GET", f"applications/{uuid}/restart")
async def get_application_logs(self, uuid: str, lines: int = 100) -> dict:
"""Get application logs."""
return await self.request("GET", f"applications/{uuid}/logs", params={"lines": lines})
# --- Application Environment Variables ---
async def list_application_envs(self, uuid: str) -> list[dict]:
"""List application environment variables."""
return await self.request("GET", f"applications/{uuid}/envs")
async def create_application_env(self, uuid: str, data: dict) -> dict:
"""Create application environment variable."""
return await self.request("POST", f"applications/{uuid}/envs", json_data=data)
async def update_application_env(self, uuid: str, data: dict) -> dict:
"""Update application environment variable."""
return await self.request("PATCH", f"applications/{uuid}/envs", json_data=data)
async def update_application_envs_bulk(self, uuid: str, data: list[dict]) -> dict:
"""Bulk update application environment variables."""
return await self.request(
"PATCH", f"applications/{uuid}/envs/bulk", json_data={"data": data}
)
async def delete_application_env(self, uuid: str, env_uuid: str) -> dict:
"""Delete application environment variable."""
return await self.request("DELETE", f"applications/{uuid}/envs/{env_uuid}")
# --- Deployments ---
async def list_deployments(self) -> list[dict]:
"""List running deployments."""
return await self.request("GET", "deployments")
async def get_deployment(self, uuid: str) -> dict:
"""Get deployment by UUID."""
return await self.request("GET", f"deployments/{uuid}")
async def cancel_deployment(self, uuid: str) -> dict:
"""Cancel a deployment."""
return await self.request("POST", f"deployments/{uuid}/cancel")
async def deploy(
self,
tag: str | None = None,
uuid: str | None = None,
force: bool = False,
) -> dict:
"""Deploy by tag or UUID."""
params = {}
if tag:
params["tag"] = tag
if uuid:
params["uuid"] = uuid
if force:
params["force"] = "true"
return await self.request("GET", "deploy", params=params)
async def list_app_deployments(self, uuid: str, skip: int = 0, take: int = 10) -> list[dict]:
"""List deployments for a specific application."""
params = {"skip": skip, "take": take}
return await self.request("GET", f"deployments/applications/{uuid}", params=params)
# --- Servers ---
async def list_servers(self) -> list[dict]:
"""List all servers."""
return await self.request("GET", "servers")
async def get_server(self, uuid: str) -> dict:
"""Get server by UUID."""
return await self.request("GET", f"servers/{uuid}")
async def create_server(self, data: dict) -> dict:
"""Create a new server."""
return await self.request("POST", "servers", json_data=data)
async def update_server(self, uuid: str, data: dict) -> dict:
"""Update server by UUID."""
return await self.request("PATCH", f"servers/{uuid}", json_data=data)
async def delete_server(self, uuid: str) -> dict:
"""Delete server by UUID."""
return await self.request("DELETE", f"servers/{uuid}")
async def get_server_resources(self, uuid: str) -> list[dict]:
"""Get resources on a server."""
return await self.request("GET", f"servers/{uuid}/resources")
async def get_server_domains(self, uuid: str) -> list[dict]:
"""Get domains configured on a server."""
return await self.request("GET", f"servers/{uuid}/domains")
async def validate_server(self, uuid: str) -> dict:
"""Validate server connectivity and configuration."""
return await self.request("GET", f"servers/{uuid}/validate")

View File

@@ -0,0 +1,13 @@
"""
Coolify Plugin Handlers
All tool handlers for Coolify operations.
"""
from . import applications, deployments, servers
__all__ = [
"applications",
"deployments",
"servers",
]

View File

@@ -0,0 +1,860 @@
"""Application Handler — manages Coolify applications, lifecycle, and env vars."""
import json
from typing import Any
from plugins.coolify.client import CoolifyClient
def get_tool_specifications() -> list[dict[str, Any]]:
"""Return tool specifications for ToolGenerator."""
return [
{
"name": "list_applications",
"method_name": "list_applications",
"description": "List all Coolify applications. Optionally filter by tag name.",
"schema": {
"type": "object",
"properties": {
"tag": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Filter by tag name",
},
},
},
"scope": "read",
},
{
"name": "get_application",
"method_name": "get_application",
"description": "Get details of a specific Coolify application by UUID.",
"schema": {
"type": "object",
"properties": {
"uuid": {
"type": "string",
"description": "Application UUID",
"minLength": 1,
},
},
"required": ["uuid"],
},
"scope": "read",
},
{
"name": "create_application_public",
"method_name": "create_application_public",
"description": (
"Create a Coolify application from a public Git repository. "
"Requires project_uuid, server_uuid, environment, git_repository, "
"git_branch, build_pack, and ports_exposes."
),
"schema": {
"type": "object",
"properties": {
"project_uuid": {
"type": "string",
"description": "Project UUID",
},
"server_uuid": {
"type": "string",
"description": "Server UUID",
},
"environment_name": {
"type": "string",
"description": "Environment name (e.g., 'production')",
},
"git_repository": {
"type": "string",
"description": "Git repository URL",
},
"git_branch": {
"type": "string",
"description": "Git branch name",
},
"build_pack": {
"type": "string",
"description": "Build pack type",
"enum": ["nixpacks", "static", "dockerfile", "dockercompose"],
},
"ports_exposes": {
"type": "string",
"description": "Exposed ports (e.g., '3000')",
},
"name": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Application name",
},
"description": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Application description",
},
"domains": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Comma-separated domain URLs",
},
"instant_deploy": {
"type": "boolean",
"description": "Deploy immediately after creation",
"default": False,
},
},
"required": [
"project_uuid",
"server_uuid",
"environment_name",
"git_repository",
"git_branch",
"build_pack",
"ports_exposes",
],
},
"scope": "write",
},
{
"name": "create_application_dockerfile",
"method_name": "create_application_dockerfile",
"description": (
"Create a Coolify application from a Dockerfile (without git). "
"Requires project_uuid, server_uuid, environment, and dockerfile content."
),
"schema": {
"type": "object",
"properties": {
"project_uuid": {
"type": "string",
"description": "Project UUID",
},
"server_uuid": {
"type": "string",
"description": "Server UUID",
},
"environment_name": {
"type": "string",
"description": "Environment name",
},
"dockerfile": {
"type": "string",
"description": "Dockerfile content",
},
"name": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Application name",
},
"description": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Application description",
},
"domains": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Comma-separated domain URLs",
},
"ports_exposes": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Exposed ports",
},
"instant_deploy": {
"type": "boolean",
"description": "Deploy immediately",
"default": False,
},
},
"required": [
"project_uuid",
"server_uuid",
"environment_name",
"dockerfile",
],
},
"scope": "write",
},
{
"name": "create_application_docker_image",
"method_name": "create_application_docker_image",
"description": (
"Create a Coolify application from a Docker image. "
"Requires project_uuid, server_uuid, environment, "
"docker_registry_image_name, and ports_exposes."
),
"schema": {
"type": "object",
"properties": {
"project_uuid": {
"type": "string",
"description": "Project UUID",
},
"server_uuid": {
"type": "string",
"description": "Server UUID",
},
"environment_name": {
"type": "string",
"description": "Environment name",
},
"docker_registry_image_name": {
"type": "string",
"description": "Docker image name (e.g., 'nginx')",
},
"ports_exposes": {
"type": "string",
"description": "Exposed ports",
},
"docker_registry_image_tag": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Docker image tag (default: latest)",
},
"name": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Application name",
},
"description": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Application description",
},
"domains": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Comma-separated domain URLs",
},
"instant_deploy": {
"type": "boolean",
"description": "Deploy immediately",
"default": False,
},
},
"required": [
"project_uuid",
"server_uuid",
"environment_name",
"docker_registry_image_name",
"ports_exposes",
],
},
"scope": "write",
},
{
"name": "create_application_compose",
"method_name": "create_application_compose",
"description": (
"Create a Coolify application from Docker Compose. "
"Note: Deprecated by Coolify — prefer using services instead."
),
"schema": {
"type": "object",
"properties": {
"project_uuid": {
"type": "string",
"description": "Project UUID",
},
"server_uuid": {
"type": "string",
"description": "Server UUID",
},
"environment_name": {
"type": "string",
"description": "Environment name",
},
"docker_compose_raw": {
"type": "string",
"description": "Raw Docker Compose YAML content",
},
"name": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Application name",
},
"instant_deploy": {
"type": "boolean",
"description": "Deploy immediately",
"default": False,
},
},
"required": [
"project_uuid",
"server_uuid",
"environment_name",
"docker_compose_raw",
],
},
"scope": "write",
},
{
"name": "update_application",
"method_name": "update_application",
"description": (
"Update a Coolify application settings. Supports name, description, "
"domains, build settings, health checks, resource limits, and more."
),
"schema": {
"type": "object",
"properties": {
"uuid": {
"type": "string",
"description": "Application UUID",
"minLength": 1,
},
"name": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Application name",
},
"description": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Application description",
},
"domains": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Comma-separated domain URLs",
},
"git_repository": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Git repository URL",
},
"git_branch": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Git branch",
},
"build_pack": {
"anyOf": [
{
"type": "string",
"enum": [
"nixpacks",
"static",
"dockerfile",
"dockercompose",
],
},
{"type": "null"},
],
"description": "Build pack type",
},
"install_command": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Install command",
},
"build_command": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Build command",
},
"start_command": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Start command",
},
"ports_exposes": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Exposed ports",
},
"is_auto_deploy_enabled": {
"anyOf": [{"type": "boolean"}, {"type": "null"}],
"description": "Enable auto-deploy on push",
},
"instant_deploy": {
"anyOf": [{"type": "boolean"}, {"type": "null"}],
"description": "Deploy instantly",
},
},
"required": ["uuid"],
},
"scope": "write",
},
{
"name": "delete_application",
"method_name": "delete_application",
"description": (
"Delete a Coolify application permanently. "
"Optionally clean up configs, volumes, and networks."
),
"schema": {
"type": "object",
"properties": {
"uuid": {
"type": "string",
"description": "Application UUID",
"minLength": 1,
},
"delete_configurations": {
"type": "boolean",
"description": "Delete configurations",
"default": True,
},
"delete_volumes": {
"type": "boolean",
"description": "Delete volumes",
"default": True,
},
"docker_cleanup": {
"type": "boolean",
"description": "Run Docker cleanup",
"default": True,
},
"delete_connected_networks": {
"type": "boolean",
"description": "Delete connected networks",
"default": True,
},
},
"required": ["uuid"],
},
"scope": "admin",
},
{
"name": "start_application",
"method_name": "start_application",
"description": "Deploy/start a Coolify application. Triggers a new deployment.",
"schema": {
"type": "object",
"properties": {
"uuid": {
"type": "string",
"description": "Application UUID",
"minLength": 1,
},
"force": {
"type": "boolean",
"description": "Force rebuild without cache",
"default": False,
},
"instant_deploy": {
"type": "boolean",
"description": "Skip deployment queue",
"default": False,
},
},
"required": ["uuid"],
},
"scope": "write",
},
{
"name": "stop_application",
"method_name": "stop_application",
"description": "Stop a running Coolify application.",
"schema": {
"type": "object",
"properties": {
"uuid": {
"type": "string",
"description": "Application UUID",
"minLength": 1,
},
"docker_cleanup": {
"type": "boolean",
"description": "Prune networks and volumes",
"default": True,
},
},
"required": ["uuid"],
},
"scope": "write",
},
{
"name": "restart_application",
"method_name": "restart_application",
"description": "Restart a Coolify application.",
"schema": {
"type": "object",
"properties": {
"uuid": {
"type": "string",
"description": "Application UUID",
"minLength": 1,
},
},
"required": ["uuid"],
},
"scope": "write",
},
{
"name": "get_application_logs",
"method_name": "get_application_logs",
"description": "Get logs for a Coolify application.",
"schema": {
"type": "object",
"properties": {
"uuid": {
"type": "string",
"description": "Application UUID",
"minLength": 1,
},
"lines": {
"type": "integer",
"description": "Number of log lines to retrieve",
"default": 100,
"minimum": 1,
"maximum": 10000,
},
},
"required": ["uuid"],
},
"scope": "read",
},
{
"name": "list_application_envs",
"method_name": "list_application_envs",
"description": "List environment variables for a Coolify application.",
"schema": {
"type": "object",
"properties": {
"uuid": {
"type": "string",
"description": "Application UUID",
"minLength": 1,
},
},
"required": ["uuid"],
},
"scope": "read",
},
{
"name": "create_application_env",
"method_name": "create_application_env",
"description": "Create an environment variable for a Coolify application.",
"schema": {
"type": "object",
"properties": {
"uuid": {
"type": "string",
"description": "Application UUID",
"minLength": 1,
},
"key": {
"type": "string",
"description": "Environment variable key",
"minLength": 1,
},
"value": {
"type": "string",
"description": "Environment variable value",
},
"is_preview": {
"type": "boolean",
"description": "Preview deployment only",
"default": False,
},
"is_literal": {
"type": "boolean",
"description": "Treat as literal (no variable interpolation)",
"default": False,
},
"is_multiline": {
"type": "boolean",
"description": "Allow multiline value",
"default": False,
},
"is_shown_once": {
"type": "boolean",
"description": "Show value only once",
"default": False,
},
},
"required": ["uuid", "key", "value"],
},
"scope": "write",
},
{
"name": "update_application_env",
"method_name": "update_application_env",
"description": "Update an environment variable for a Coolify application.",
"schema": {
"type": "object",
"properties": {
"uuid": {
"type": "string",
"description": "Application UUID",
"minLength": 1,
},
"key": {
"type": "string",
"description": "Environment variable key",
"minLength": 1,
},
"value": {
"type": "string",
"description": "New value",
},
"is_preview": {
"anyOf": [{"type": "boolean"}, {"type": "null"}],
"description": "Preview deployment only",
},
"is_literal": {
"anyOf": [{"type": "boolean"}, {"type": "null"}],
"description": "Treat as literal",
},
"is_multiline": {
"anyOf": [{"type": "boolean"}, {"type": "null"}],
"description": "Allow multiline",
},
"is_shown_once": {
"anyOf": [{"type": "boolean"}, {"type": "null"}],
"description": "Show only once",
},
},
"required": ["uuid", "key", "value"],
},
"scope": "write",
},
{
"name": "update_application_envs_bulk",
"method_name": "update_application_envs_bulk",
"description": "Bulk update environment variables for a Coolify application.",
"schema": {
"type": "object",
"properties": {
"uuid": {
"type": "string",
"description": "Application UUID",
"minLength": 1,
},
"data": {
"type": "array",
"description": "Array of env var objects with key, value, and flags",
"items": {
"type": "object",
"properties": {
"key": {"type": "string"},
"value": {"type": "string"},
"is_preview": {"type": "boolean"},
"is_literal": {"type": "boolean"},
"is_multiline": {"type": "boolean"},
"is_shown_once": {"type": "boolean"},
},
"required": ["key", "value"],
},
},
},
"required": ["uuid", "data"],
},
"scope": "write",
},
{
"name": "delete_application_env",
"method_name": "delete_application_env",
"description": "Delete an environment variable from a Coolify application.",
"schema": {
"type": "object",
"properties": {
"uuid": {
"type": "string",
"description": "Application UUID",
"minLength": 1,
},
"env_uuid": {
"type": "string",
"description": "Environment variable UUID",
"minLength": 1,
},
},
"required": ["uuid", "env_uuid"],
},
"scope": "write",
},
]
# --- Handler Functions ---
async def list_applications(client: CoolifyClient, tag: str | None = None) -> str:
"""List all applications."""
apps = await client.list_applications(tag=tag)
result = {"success": True, "count": len(apps), "applications": apps}
return json.dumps(result, indent=2, ensure_ascii=False)
async def get_application(client: CoolifyClient, uuid: str) -> str:
"""Get application details."""
app = await client.get_application(uuid)
result = {"success": True, "application": app}
return json.dumps(result, indent=2, ensure_ascii=False)
async def create_application_public(client: CoolifyClient, **kwargs) -> str:
"""Create application from public repository."""
app = await client.create_application_public(kwargs)
result = {
"success": True,
"message": "Application created successfully",
"application": app,
}
return json.dumps(result, indent=2, ensure_ascii=False)
async def create_application_dockerfile(client: CoolifyClient, **kwargs) -> str:
"""Create application from Dockerfile."""
app = await client.create_application_dockerfile(kwargs)
result = {
"success": True,
"message": "Application created from Dockerfile",
"application": app,
}
return json.dumps(result, indent=2, ensure_ascii=False)
async def create_application_docker_image(client: CoolifyClient, **kwargs) -> str:
"""Create application from Docker image."""
app = await client.create_application_docker_image(kwargs)
result = {
"success": True,
"message": "Application created from Docker image",
"application": app,
}
return json.dumps(result, indent=2, ensure_ascii=False)
async def create_application_compose(client: CoolifyClient, **kwargs) -> str:
"""Create application from Docker Compose."""
app = await client.create_application_docker_compose(kwargs)
result = {
"success": True,
"message": "Application created from Docker Compose",
"application": app,
}
return json.dumps(result, indent=2, ensure_ascii=False)
async def update_application(client: CoolifyClient, uuid: str, **kwargs) -> str:
"""Update application settings."""
data = {k: v for k, v in kwargs.items() if v is not None and k != "uuid"}
app = await client.update_application(uuid, data)
result = {
"success": True,
"message": f"Application '{uuid}' updated successfully",
"application": app,
}
return json.dumps(result, indent=2, ensure_ascii=False)
async def delete_application(
client: CoolifyClient,
uuid: str,
delete_configurations: bool = True,
delete_volumes: bool = True,
docker_cleanup: bool = True,
delete_connected_networks: bool = True,
) -> str:
"""Delete an application."""
result_data = await client.delete_application(
uuid,
delete_configurations=delete_configurations,
delete_volumes=delete_volumes,
docker_cleanup=docker_cleanup,
delete_connected_networks=delete_connected_networks,
)
result = {"success": True, "message": f"Application '{uuid}' deleted", "data": result_data}
return json.dumps(result, indent=2, ensure_ascii=False)
async def start_application(
client: CoolifyClient,
uuid: str,
force: bool = False,
instant_deploy: bool = False,
) -> str:
"""Deploy/start application."""
result_data = await client.start_application(uuid, force=force, instant_deploy=instant_deploy)
result = {
"success": True,
"message": f"Application '{uuid}' deployment queued",
"data": result_data,
}
return json.dumps(result, indent=2, ensure_ascii=False)
async def stop_application(client: CoolifyClient, uuid: str, docker_cleanup: bool = True) -> str:
"""Stop application."""
result_data = await client.stop_application(uuid, docker_cleanup=docker_cleanup)
result = {"success": True, "message": f"Application '{uuid}' stopping", "data": result_data}
return json.dumps(result, indent=2, ensure_ascii=False)
async def restart_application(client: CoolifyClient, uuid: str) -> str:
"""Restart application."""
result_data = await client.restart_application(uuid)
result = {"success": True, "message": f"Application '{uuid}' restarting", "data": result_data}
return json.dumps(result, indent=2, ensure_ascii=False)
async def get_application_logs(client: CoolifyClient, uuid: str, lines: int = 100) -> str:
"""Get application logs."""
logs = await client.get_application_logs(uuid, lines=lines)
result = {"success": True, "logs": logs}
return json.dumps(result, indent=2, ensure_ascii=False)
async def list_application_envs(client: CoolifyClient, uuid: str) -> str:
"""List application environment variables."""
envs = await client.list_application_envs(uuid)
result = {"success": True, "count": len(envs), "envs": envs}
return json.dumps(result, indent=2, ensure_ascii=False)
async def create_application_env(
client: CoolifyClient,
uuid: str,
key: str,
value: str,
is_preview: bool = False,
is_literal: bool = False,
is_multiline: bool = False,
is_shown_once: bool = False,
) -> str:
"""Create application environment variable."""
data = {
"key": key,
"value": value,
"is_preview": is_preview,
"is_literal": is_literal,
"is_multiline": is_multiline,
"is_shown_once": is_shown_once,
}
result_data = await client.create_application_env(uuid, data)
result = {
"success": True,
"message": f"Env var '{key}' created",
"data": result_data,
}
return json.dumps(result, indent=2, ensure_ascii=False)
async def update_application_env(
client: CoolifyClient,
uuid: str,
key: str,
value: str,
is_preview: bool | None = None,
is_literal: bool | None = None,
is_multiline: bool | None = None,
is_shown_once: bool | None = None,
) -> str:
"""Update application environment variable."""
data = {"key": key, "value": value}
if is_preview is not None:
data["is_preview"] = is_preview
if is_literal is not None:
data["is_literal"] = is_literal
if is_multiline is not None:
data["is_multiline"] = is_multiline
if is_shown_once is not None:
data["is_shown_once"] = is_shown_once
result_data = await client.update_application_env(uuid, data)
result = {
"success": True,
"message": f"Env var '{key}' updated",
"data": result_data,
}
return json.dumps(result, indent=2, ensure_ascii=False)
async def update_application_envs_bulk(client: CoolifyClient, uuid: str, data: list[dict]) -> str:
"""Bulk update application environment variables."""
result_data = await client.update_application_envs_bulk(uuid, data)
result = {
"success": True,
"message": f"Bulk update {len(data)} env vars",
"data": result_data,
}
return json.dumps(result, indent=2, ensure_ascii=False)
async def delete_application_env(client: CoolifyClient, uuid: str, env_uuid: str) -> str:
"""Delete application environment variable."""
await client.delete_application_env(uuid, env_uuid)
result = {"success": True, "message": f"Env var '{env_uuid}' deleted"}
return json.dumps(result, indent=2, ensure_ascii=False)

View File

@@ -0,0 +1,162 @@
"""Deployment Handler — manages Coolify deployments."""
import json
from typing import Any
from plugins.coolify.client import CoolifyClient
def get_tool_specifications() -> list[dict[str, Any]]:
"""Return tool specifications for ToolGenerator."""
return [
{
"name": "list_deployments",
"method_name": "list_deployments",
"description": "List all running deployments on the Coolify instance.",
"schema": {
"type": "object",
"properties": {},
},
"scope": "read",
},
{
"name": "get_deployment",
"method_name": "get_deployment",
"description": "Get details of a specific deployment by UUID.",
"schema": {
"type": "object",
"properties": {
"uuid": {
"type": "string",
"description": "Deployment UUID",
"minLength": 1,
},
},
"required": ["uuid"],
},
"scope": "read",
},
{
"name": "cancel_deployment",
"method_name": "cancel_deployment",
"description": "Cancel a running deployment.",
"schema": {
"type": "object",
"properties": {
"uuid": {
"type": "string",
"description": "Deployment UUID",
"minLength": 1,
},
},
"required": ["uuid"],
},
"scope": "write",
},
{
"name": "deploy",
"method_name": "deploy",
"description": (
"Trigger deployment by tag name or resource UUID. "
"Can deploy multiple resources at once using comma-separated values."
),
"schema": {
"type": "object",
"properties": {
"tag": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Tag name(s), comma-separated",
},
"uuid": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Resource UUID(s), comma-separated",
},
"force": {
"type": "boolean",
"description": "Force rebuild without cache",
"default": False,
},
},
},
"scope": "write",
},
{
"name": "list_app_deployments",
"method_name": "list_app_deployments",
"description": "List deployment history for a specific application.",
"schema": {
"type": "object",
"properties": {
"uuid": {
"type": "string",
"description": "Application UUID",
"minLength": 1,
},
"skip": {
"type": "integer",
"description": "Number of deployments to skip",
"default": 0,
"minimum": 0,
},
"take": {
"type": "integer",
"description": "Number of deployments to return",
"default": 10,
"minimum": 1,
"maximum": 100,
},
},
"required": ["uuid"],
},
"scope": "read",
},
]
# --- Handler Functions ---
async def list_deployments(client: CoolifyClient) -> str:
"""List running deployments."""
deployments = await client.list_deployments()
result = {"success": True, "count": len(deployments), "deployments": deployments}
return json.dumps(result, indent=2, ensure_ascii=False)
async def get_deployment(client: CoolifyClient, uuid: str) -> str:
"""Get deployment details."""
deployment = await client.get_deployment(uuid)
result = {"success": True, "deployment": deployment}
return json.dumps(result, indent=2, ensure_ascii=False)
async def cancel_deployment(client: CoolifyClient, uuid: str) -> str:
"""Cancel a deployment."""
result_data = await client.cancel_deployment(uuid)
result = {
"success": True,
"message": f"Deployment '{uuid}' cancelled",
"data": result_data,
}
return json.dumps(result, indent=2, ensure_ascii=False)
async def deploy(
client: CoolifyClient,
tag: str | None = None,
uuid: str | None = None,
force: bool = False,
) -> str:
"""Deploy by tag or UUID."""
result_data = await client.deploy(tag=tag, uuid=uuid, force=force)
result = {"success": True, "message": "Deployment triggered", "data": result_data}
return json.dumps(result, indent=2, ensure_ascii=False)
async def list_app_deployments(
client: CoolifyClient, uuid: str, skip: int = 0, take: int = 10
) -> str:
"""List deployment history for an application."""
deployments = await client.list_app_deployments(uuid, skip=skip, take=take)
result = {"success": True, "count": len(deployments), "deployments": deployments}
return json.dumps(result, indent=2, ensure_ascii=False)

View File

@@ -0,0 +1,286 @@
"""Server Handler — manages Coolify servers."""
import json
from typing import Any
from plugins.coolify.client import CoolifyClient
def get_tool_specifications() -> list[dict[str, Any]]:
"""Return tool specifications for ToolGenerator."""
return [
{
"name": "list_servers",
"method_name": "list_servers",
"description": "List all servers registered in the Coolify instance.",
"schema": {
"type": "object",
"properties": {},
},
"scope": "read",
},
{
"name": "get_server",
"method_name": "get_server",
"description": "Get details of a specific server by UUID, including settings.",
"schema": {
"type": "object",
"properties": {
"uuid": {
"type": "string",
"description": "Server UUID",
"minLength": 1,
},
},
"required": ["uuid"],
},
"scope": "read",
},
{
"name": "create_server",
"method_name": "create_server",
"description": (
"Register a new server in Coolify. "
"Requires name, IP, port, user, and a private key UUID for SSH access."
),
"schema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Server name",
"minLength": 1,
},
"ip": {
"type": "string",
"description": "Server IP address",
"minLength": 1,
},
"port": {
"type": "integer",
"description": "SSH port",
"default": 22,
},
"user": {
"type": "string",
"description": "SSH user",
"default": "root",
},
"private_key_uuid": {
"type": "string",
"description": "Private key UUID for SSH authentication",
"minLength": 1,
},
"description": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Server description",
},
"is_build_server": {
"type": "boolean",
"description": "Use as build server",
"default": False,
},
"instant_validate": {
"type": "boolean",
"description": "Validate server immediately after creation",
"default": False,
},
"proxy_type": {
"type": "string",
"description": "Proxy type for the server",
"enum": ["traefik", "caddy", "none"],
"default": "traefik",
},
},
"required": ["name", "ip", "private_key_uuid"],
},
"scope": "admin",
},
{
"name": "update_server",
"method_name": "update_server",
"description": "Update server configuration.",
"schema": {
"type": "object",
"properties": {
"uuid": {
"type": "string",
"description": "Server UUID",
"minLength": 1,
},
"name": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Server name",
},
"description": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Server description",
},
"ip": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Server IP address",
},
"port": {
"anyOf": [{"type": "integer"}, {"type": "null"}],
"description": "SSH port",
},
"user": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "SSH user",
},
"proxy_type": {
"anyOf": [
{"type": "string", "enum": ["traefik", "caddy", "none"]},
{"type": "null"},
],
"description": "Proxy type",
},
},
"required": ["uuid"],
},
"scope": "write",
},
{
"name": "delete_server",
"method_name": "delete_server",
"description": "Delete a server from Coolify. This cannot be undone!",
"schema": {
"type": "object",
"properties": {
"uuid": {
"type": "string",
"description": "Server UUID",
"minLength": 1,
},
},
"required": ["uuid"],
},
"scope": "admin",
},
{
"name": "get_server_resources",
"method_name": "get_server_resources",
"description": (
"Get all resources (applications, databases, services) "
"deployed on a specific server."
),
"schema": {
"type": "object",
"properties": {
"uuid": {
"type": "string",
"description": "Server UUID",
"minLength": 1,
},
},
"required": ["uuid"],
},
"scope": "read",
},
{
"name": "get_server_domains",
"method_name": "get_server_domains",
"description": "Get all domains configured on a specific server.",
"schema": {
"type": "object",
"properties": {
"uuid": {
"type": "string",
"description": "Server UUID",
"minLength": 1,
},
},
"required": ["uuid"],
},
"scope": "read",
},
{
"name": "validate_server",
"method_name": "validate_server",
"description": "Validate server connectivity and configuration.",
"schema": {
"type": "object",
"properties": {
"uuid": {
"type": "string",
"description": "Server UUID",
"minLength": 1,
},
},
"required": ["uuid"],
},
"scope": "read",
},
]
# --- Handler Functions ---
async def list_servers(client: CoolifyClient) -> str:
"""List all servers."""
servers = await client.list_servers()
result = {"success": True, "count": len(servers), "servers": servers}
return json.dumps(result, indent=2, ensure_ascii=False)
async def get_server(client: CoolifyClient, uuid: str) -> str:
"""Get server details."""
server = await client.get_server(uuid)
result = {"success": True, "server": server}
return json.dumps(result, indent=2, ensure_ascii=False)
async def create_server(client: CoolifyClient, **kwargs) -> str:
"""Create a new server."""
server = await client.create_server(kwargs)
result = {
"success": True,
"message": "Server created successfully",
"server": server,
}
return json.dumps(result, indent=2, ensure_ascii=False)
async def update_server(client: CoolifyClient, uuid: str, **kwargs) -> str:
"""Update server configuration."""
data = {k: v for k, v in kwargs.items() if v is not None and k != "uuid"}
server = await client.update_server(uuid, data)
result = {
"success": True,
"message": f"Server '{uuid}' updated",
"server": server,
}
return json.dumps(result, indent=2, ensure_ascii=False)
async def delete_server(client: CoolifyClient, uuid: str) -> str:
"""Delete a server."""
await client.delete_server(uuid)
result = {"success": True, "message": f"Server '{uuid}' deleted"}
return json.dumps(result, indent=2, ensure_ascii=False)
async def get_server_resources(client: CoolifyClient, uuid: str) -> str:
"""Get server resources."""
resources = await client.get_server_resources(uuid)
result = {"success": True, "count": len(resources), "resources": resources}
return json.dumps(result, indent=2, ensure_ascii=False)
async def get_server_domains(client: CoolifyClient, uuid: str) -> str:
"""Get server domains."""
domains = await client.get_server_domains(uuid)
result = {"success": True, "domains": domains}
return json.dumps(result, indent=2, ensure_ascii=False)
async def validate_server(client: CoolifyClient, uuid: str) -> str:
"""Validate server."""
result_data = await client.validate_server(uuid)
result = {
"success": True,
"message": "Server validation started",
"data": result_data,
}
return json.dumps(result, indent=2, ensure_ascii=False)

106
plugins/coolify/plugin.py Normal file
View File

@@ -0,0 +1,106 @@
"""
Coolify Plugin — Clean Architecture
AI-driven deployment management for Coolify instances.
Modular handlers for applications, deployments, and servers.
"""
from typing import Any
from plugins.base import BasePlugin
from plugins.coolify import handlers
from plugins.coolify.client import CoolifyClient
class CoolifyPlugin(BasePlugin):
"""
Coolify project plugin — Clean architecture.
Provides Coolify deployment management capabilities including:
- Application management (CRUD, lifecycle, env vars, logs)
- Deployment control (list, cancel, deploy by tag/UUID)
- Server management (CRUD, resources, domains, validation)
"""
@staticmethod
def get_plugin_name() -> str:
"""Return plugin type identifier."""
return "coolify"
@staticmethod
def get_required_config_keys() -> list[str]:
"""Return required configuration keys."""
return ["url", "token"]
def __init__(self, config: dict[str, Any], project_id: str | None = None):
"""
Initialize Coolify plugin with handlers.
Args:
config: Configuration dictionary containing:
- url: Coolify instance URL
- token: API token for Bearer authentication
project_id: Optional project ID (auto-generated if not provided)
"""
super().__init__(config, project_id=project_id)
self.client = CoolifyClient(
site_url=config["url"],
token=config["token"],
)
@staticmethod
def get_tool_specifications() -> list[dict[str, Any]]:
"""
Return all tool specifications for ToolGenerator.
Returns:
List of tool specification dictionaries
"""
specs = []
specs.extend(handlers.applications.get_tool_specifications())
specs.extend(handlers.deployments.get_tool_specifications())
specs.extend(handlers.servers.get_tool_specifications())
return specs
def __getattr__(self, name: str):
"""
Dynamically delegate method calls to appropriate handlers.
Args:
name: Method name being called
Returns:
Handler function from the appropriate handler module
"""
handler_modules = [
handlers.applications,
handlers.deployments,
handlers.servers,
]
for module in handler_modules:
if hasattr(module, name):
func = getattr(module, name)
async def wrapper(_func=func, **kwargs):
return await _func(self.client, **kwargs)
return wrapper
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")
async def health_check(self) -> dict[str, Any]:
"""
Check if Coolify instance is accessible.
Returns:
Dict containing health check result
"""
try:
await self.client.request("GET", "version")
return {"healthy": True, "message": "Coolify instance is accessible"}
except Exception as e:
return {"healthy": False, "message": f"Coolify health check failed: {str(e)}"}

View File

@@ -0,0 +1 @@
"""Coolify Plugin Schemas"""

View File

@@ -0,0 +1,55 @@
"""
Common Pydantic Schemas for Coolify Plugin
Shared validation schemas used across Coolify handlers.
"""
from datetime import datetime
from typing import Any
from pydantic import BaseModel, ConfigDict, Field
class Site(BaseModel):
"""Coolify site configuration."""
model_config = ConfigDict(extra="forbid")
site_id: str = Field(..., description="Site identifier")
url: str = Field(..., description="Coolify instance URL")
token: str = Field(..., description="API token for Bearer authentication")
alias: str | None = Field(None, description="Site alias")
class PaginationParams(BaseModel):
"""Pagination parameters for list endpoints."""
model_config = ConfigDict(extra="forbid")
skip: int = Field(default=0, ge=0, description="Number of items to skip")
take: int = Field(default=10, ge=1, le=100, description="Number of items to take")
class ErrorResponse(BaseModel):
"""Standard error response."""
error: bool = Field(default=True)
message: str = Field(..., description="Error message")
details: dict[str, Any] | None = Field(None, description="Additional error details")
class SuccessResponse(BaseModel):
"""Standard success response."""
success: bool = Field(default=True)
message: str = Field(..., description="Success message")
data: dict[str, Any] | None = Field(None, description="Response data")
class CoolifyTimestamps(BaseModel):
"""Common timestamp fields."""
model_config = ConfigDict(extra="allow")
created_at: datetime | None = None
updated_at: datetime | None = None