import { Tool } from '@modelcontextprotocol/sdk/types.js';
import { CoolifyApiClient } from '../client/coolify-api';
import { DeployTemplateSchema } from '../schemas';
import { getTemplate, listTemplates } from '../templates';
import { logger, logToolInvocation } from '../utils/logger';
export const deployTemplateTool: Tool = {
name: 'coolify.deploy_template',
description: 'Deploy an application from a predefined template',
inputSchema: {
type: 'object',
properties: {
templateName: {
type: 'string',
description: 'Name of the template to deploy (e.g., "plausible", "strapi")',
},
projectId: {
type: 'string',
description: 'Project ID to deploy the template to',
},
appName: {
type: 'string',
description: 'Name for the new application',
},
environment: {
type: 'object',
description: 'Environment variables for the template',
additionalProperties: {
type: 'string',
},
},
gitRepo: {
type: 'string',
description: 'Override git repository URL (for git-based templates)',
},
dockerImage: {
type: 'string',
description: 'Override Docker image (for image-based templates)',
},
buildType: {
type: 'string',
enum: ['dockerfile', 'image', 'compose'],
description: 'Override build type',
},
branch: {
type: 'string',
description: 'Git branch to use (for git-based templates)',
},
overwrite: {
type: 'boolean',
description: 'Overwrite if app already exists',
default: false,
},
},
required: ['templateName', 'projectId', 'appName', 'environment'],
},
};
export const listTemplatesTool: Tool = {
name: 'coolify.list_templates',
description: 'List all available templates',
inputSchema: {
type: 'object',
properties: {
search: {
type: 'string',
description: 'Optional search term to filter templates',
},
},
},
};
export async function handleDeployTemplate(args: any, apiClient: CoolifyApiClient) {
logToolInvocation('coolify.deploy_template', args);
try {
const validated = DeployTemplateSchema.parse(args);
// Get the template
const template = getTemplate(validated.templateName);
if (!template) {
throw new Error(`Template '${validated.templateName}' not found`);
}
logger.info(`Deploying template: ${template.name}`);
// Validate required environment variables
const missingVars = template.requiredEnvVars
.filter(envVar => envVar.required && !validated.environment[envVar.key])
.map(envVar => envVar.key);
if (missingVars.length > 0) {
throw new Error(`Missing required environment variables: ${missingVars.join(', ')}`);
}
// Check for name conflicts
const isNameAvailable = await apiClient.checkNameAvailability(
validated.projectId,
validated.appName
);
if (!isNameAvailable && !validated.overwrite) {
throw new Error(`Application name '${validated.appName}' already exists in project`);
}
// Build application configuration
const appConfig: any = {
name: validated.appName,
description: `Deployed from template: ${template.name}`,
type: validated.buildType || template.buildType,
environment: { ...validated.environment },
ports: template.ports,
};
// Set source based on template type
if (validated.gitRepo || template.source.type === 'git') {
appConfig.gitRepository = {
url: validated.gitRepo || template.source.repository,
branch: validated.branch || template.source.branch || 'main',
};
} else if (validated.dockerImage || template.source.type === 'docker_image') {
appConfig.dockerImage = validated.dockerImage || template.source.image;
}
// Create or update the application
let app;
const apps = await apiClient.listApplications(validated.projectId);
const existingApp = apps.find(a => a.name === validated.appName);
if (existingApp && validated.overwrite) {
// Update existing app
app = await apiClient.updateApplication(existingApp.id, appConfig);
logger.info(`Updated existing application: ${app.name} (${app.id})`);
} else if (!existingApp) {
// Create new app
app = await apiClient.createApplication(validated.projectId, appConfig);
logger.info(`Created application: ${app.name} (${app.id})`);
} else {
throw new Error(`Application '${validated.appName}' already exists and overwrite is false`);
}
// Deploy the application
const deployment = await apiClient.deployApplication(app.id, true);
// Wait for deployment to complete
const maxPolls = 60;
let polls = 0;
let completedDeployment = deployment;
while (polls < maxPolls && ['pending', 'running'].includes(completedDeployment.status)) {
await new Promise(resolve => setTimeout(resolve, 10000));
completedDeployment = await apiClient.getDeploymentStatus(deployment.id);
polls++;
}
if (completedDeployment.status === 'success') {
logger.info(`Template deployment completed successfully: ${app.name}`);
return {
success: true,
template: template.name,
app,
deployment: completedDeployment,
message: `Successfully deployed ${template.name} as '${validated.appName}'`,
};
} else {
const logs = await apiClient.getDeploymentLogs(deployment.id);
return {
success: false,
template: template.name,
app,
deployment: completedDeployment,
logs: logs.slice(-20),
message: `Deployment of ${template.name} failed`,
};
}
} catch (error: any) {
logger.error('Failed to deploy template', { error: error.message });
return {
success: false,
error: {
code: error.code || 'DEPLOY_TEMPLATE_ERROR',
message: error.message,
},
};
}
}
export async function handleListTemplates(args: any) {
logToolInvocation('coolify.list_templates', args);
try {
let templates = listTemplates();
if (args.search) {
const search = args.search.toLowerCase();
templates = templates.filter(template =>
template.name.toLowerCase().includes(search) ||
template.description.toLowerCase().includes(search) ||
template.tags?.some(tag => tag.toLowerCase().includes(search))
);
}
return {
success: true,
templates: templates.map(template => ({
name: template.name,
description: template.description,
buildType: template.buildType,
ports: template.ports,
requiredEnvVars: template.requiredEnvVars.map(v => ({
key: v.key,
description: v.description,
required: v.required,
hasDefault: !!v.defaultValue,
})),
optionalServices: template.optionalServices || [],
tags: template.tags || [],
})),
count: templates.length,
};
} catch (error: any) {
logger.error('Failed to list templates', { error: error.message });
return {
success: false,
error: {
code: error.code || 'LIST_TEMPLATES_ERROR',
message: error.message,
},
};
}
}