UNPKG

@masonator/coolify-mcp

Version:

Model Context Protocol server for Coolify

158 lines (157 loc) 5.12 kB
#!/usr/bin/env node import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { z } from 'zod'; const serviceTypes = [ 'wordpress-with-mysql', 'wordpress-without-database', 'code-server', 'gitea', 'nextcloud', 'uptime-kuma', 'vaultwarden' ]; class CoolifyClient { baseUrl; accessToken; constructor(baseUrl, accessToken) { this.baseUrl = baseUrl; this.accessToken = accessToken; if (!baseUrl) throw new Error('Coolify base URL is required'); if (!accessToken) throw new Error('Coolify access token is required'); this.baseUrl = baseUrl.replace(/\/$/, ''); } async request(path, options = {}) { const url = `${this.baseUrl}/api/v1${path}`; const response = await fetch(url, { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${this.accessToken}`, }, ...options, }); const data = await response.json(); if (!response.ok) { throw new Error(data.message || `HTTP ${response.status}`); } return data; } async listServers() { return this.request('/servers'); } async getServer(uuid) { return this.request(`/servers/${uuid}`); } async listServices() { return this.request('/services'); } async createService(data) { return this.request('/services', { method: 'POST', body: JSON.stringify(data), }); } async deleteService(uuid) { return this.request(`/services/${uuid}`, { method: 'DELETE', }); } } class CoolifyMcpServer extends McpServer { client; constructor(config) { super({ name: 'coolify', version: '0.2.15', capabilities: { tools: true, resources: true, prompts: true } }); this.client = new CoolifyClient(config.baseUrl, config.accessToken); this.setupTools(); } setupTools() { this.tool('list_servers', 'List all Coolify servers', {}, async () => { const servers = await this.client.listServers(); return { content: [{ type: 'text', text: JSON.stringify(servers, null, 2) }] }; }); this.tool('get_server', 'Get details about a specific Coolify server', { uuid: z.string() }, async (args) => { const server = await this.client.getServer(args.uuid); return { content: [{ type: 'text', text: JSON.stringify(server, null, 2) }] }; }); this.tool('list_services', 'List all Coolify services', {}, async () => { const services = await this.client.listServices(); return { content: [{ type: 'text', text: JSON.stringify(services, null, 2) }] }; }); this.tool('create_service', 'Create a new Coolify service', { type: z.enum(serviceTypes), project_uuid: z.string(), server_uuid: z.string(), name: z.string().optional(), environment_name: z.string().optional() }, async (args) => { const result = await this.client.createService(args); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; }); this.tool('delete_service', 'Delete a Coolify service', { uuid: z.string() }, async (args) => { const result = await this.client.deleteService(args.uuid); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; }); } async resources_list() { return { resources: [ 'coolify/servers/list', 'coolify/servers/{id}', 'coolify/services/list', 'coolify/services/create', 'coolify/services/{id}/delete' ] }; } async prompts_list() { return { prompts: [ 'Show me all Coolify servers', 'Get details for server {uuid}', 'List all services', 'Create a new {type} service', 'Delete service {uuid}' ] }; } } async function main() { const config = { baseUrl: process.env.COOLIFY_BASE_URL || 'http://localhost:3000', accessToken: process.env.COOLIFY_ACCESS_TOKEN || '', }; if (!config.accessToken) { throw new Error('COOLIFY_ACCESS_TOKEN environment variable is required'); } const server = new CoolifyMcpServer(config); const transport = new StdioServerTransport(); await server.connect(transport); } main().catch((error) => { console.error('Fatal error:', error); process.exit(1); });