@masonator/coolify-mcp
Version:
Model Context Protocol server for Coolify
158 lines (157 loc) • 5.12 kB
JavaScript
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);
});