@masonator/coolify-mcp 1.6.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,28 +1,14 @@
1
1
  /**
2
- * Coolify MCP Server
3
- * Model Context Protocol server for Coolify API
4
- *
5
- * Tools focused on debugging, management, and deployment:
6
- * - Servers: list, get, validate, resources, domains
7
- * - Projects: CRUD
8
- * - Environments: CRUD
9
- * - Applications: list, get, update, delete, start/stop/restart, logs, env vars, deploy (private-gh, private-key)
10
- * - Databases: list, get, start/stop/restart
11
- * - Services: list, get, update, start/stop/restart, env vars
12
- * - Deployments: list, get, deploy
13
- *
14
- * Note: @ts-nocheck is required because the MCP SDK's tool() method causes
15
- * TypeScript type instantiation depth errors with 40+ zod-typed tools.
16
- * The client and types are still fully type-checked.
2
+ * Coolify MCP Server v2.0.0
3
+ * Consolidated tools for efficient token usage
17
4
  */
18
- /* eslint-disable @typescript-eslint/ban-ts-comment */
19
- // @ts-nocheck
5
+ /* eslint-disable @typescript-eslint/no-explicit-any */
20
6
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
21
7
  import { z } from 'zod';
22
8
  import { CoolifyClient, } from './coolify-client.js';
23
- const VERSION = '1.6.0';
24
- /** Wrap tool handler with consistent error handling */
25
- function wrapHandler(fn) {
9
+ const VERSION = '2.0.0';
10
+ /** Wrap handler with error handling */
11
+ function wrap(fn) {
26
12
  return fn()
27
13
  .then((result) => ({
28
14
  content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
@@ -36,36 +22,32 @@ function wrapHandler(fn) {
36
22
  ],
37
23
  }));
38
24
  }
39
- /**
40
- * Coolify MCP Server
41
- */
42
25
  export class CoolifyMcpServer extends McpServer {
43
26
  constructor(config) {
44
- super({
45
- name: 'coolify',
46
- version: VERSION,
47
- capabilities: { tools: {}, prompts: {} },
48
- });
27
+ super({ name: 'coolify', version: VERSION });
49
28
  this.client = new CoolifyClient(config);
50
29
  this.registerTools();
51
- this.registerPrompts();
52
30
  }
53
31
  async connect(transport) {
54
32
  await super.connect(transport);
55
33
  }
56
34
  registerTools() {
57
- // Version tools
58
- this.tool('get_version', 'Get Coolify API version', {}, async () => wrapHandler(() => this.client.getVersion()));
59
- this.tool('get_mcp_version', 'Get the version of this MCP server (coolify-mcp). Useful to verify which version is installed.', {}, async () => ({
35
+ // =========================================================================
36
+ // Meta (2 tools)
37
+ // =========================================================================
38
+ this.tool('get_version', 'Coolify API version', {}, async () => wrap(() => this.client.getVersion()));
39
+ this.tool('get_mcp_version', 'MCP server version', {}, async () => ({
60
40
  content: [
61
41
  {
62
42
  type: 'text',
63
- text: JSON.stringify({ version: VERSION, name: '@masonator/coolify-mcp' }, null, 2),
43
+ text: JSON.stringify({ version: VERSION, name: '@masonator/coolify-mcp' }),
64
44
  },
65
45
  ],
66
46
  }));
67
- // Infrastructure Overview - high-level view of all resources
68
- this.tool('get_infrastructure_overview', 'Get a high-level overview of all infrastructure (servers, projects, applications, databases, services). Returns counts and summaries. Start here to understand the infrastructure.', {}, async () => wrapHandler(async () => {
47
+ // =========================================================================
48
+ // Infrastructure Overview (1 tool)
49
+ // =========================================================================
50
+ this.tool('get_infrastructure_overview', 'Overview of all resources with counts', {}, async () => wrap(async () => {
69
51
  const results = await Promise.allSettled([
70
52
  this.client.listServers({ summary: true }),
71
53
  this.client.listProjects({ summary: true }),
@@ -73,20 +55,18 @@ export class CoolifyMcpServer extends McpServer {
73
55
  this.client.listDatabases({ summary: true }),
74
56
  this.client.listServices({ summary: true }),
75
57
  ]);
76
- const extract = (result) => result.status === 'fulfilled' ? result.value : [];
77
- const servers = extract(results[0]);
78
- const projects = extract(results[1]);
79
- const applications = extract(results[2]);
80
- const databases = extract(results[3]);
81
- const services = extract(results[4]);
58
+ const extract = (r) => r.status === 'fulfilled' ? r.value : [];
59
+ const [servers, projects, applications, databases, services] = [
60
+ extract(results[0]),
61
+ extract(results[1]),
62
+ extract(results[2]),
63
+ extract(results[3]),
64
+ extract(results[4]),
65
+ ];
82
66
  const errors = results
83
- .map((r, i) => {
84
- if (r.status === 'rejected') {
85
- const names = ['servers', 'projects', 'applications', 'databases', 'services'];
86
- return `${names[i]}: ${r.reason instanceof Error ? r.reason.message : String(r.reason)}`;
87
- }
88
- return null;
89
- })
67
+ .map((r, i) => r.status === 'rejected'
68
+ ? `${['servers', 'projects', 'applications', 'databases', 'services'][i]}: ${r.reason}`
69
+ : null)
90
70
  .filter(Boolean);
91
71
  return {
92
72
  summary: {
@@ -105,596 +85,439 @@ export class CoolifyMcpServer extends McpServer {
105
85
  };
106
86
  }));
107
87
  // =========================================================================
108
- // Servers (5 tools)
88
+ // Diagnostics (3 tools)
109
89
  // =========================================================================
110
- this.tool('list_servers', 'List all servers (returns summary: uuid, name, ip, status). Use get_server for full details.', {
111
- page: z.number().optional().describe('Page number for pagination'),
112
- per_page: z.number().optional().describe('Items per page (default: all)'),
113
- }, async ({ page, per_page }) => wrapHandler(() => this.client.listServers({ page, per_page, summary: true })));
114
- this.tool('get_server', 'Get server details', { uuid: z.string().describe('Server UUID') }, async ({ uuid }) => wrapHandler(() => this.client.getServer(uuid)));
115
- this.tool('get_server_resources', 'Get resources running on a server', { uuid: z.string().describe('Server UUID') }, async ({ uuid }) => wrapHandler(() => this.client.getServerResources(uuid)));
116
- this.tool('get_server_domains', 'Get domains configured on a server', { uuid: z.string().describe('Server UUID') }, async ({ uuid }) => wrapHandler(() => this.client.getServerDomains(uuid)));
117
- this.tool('validate_server', 'Validate server connection', { uuid: z.string().describe('Server UUID') }, async ({ uuid }) => wrapHandler(() => this.client.validateServer(uuid)));
90
+ this.tool('diagnose_app', 'App diagnostics by UUID/name/domain', { query: z.string() }, async ({ query }) => wrap(() => this.client.diagnoseApplication(query)));
91
+ this.tool('diagnose_server', 'Server diagnostics by UUID/name/IP', { query: z.string() }, async ({ query }) => wrap(() => this.client.diagnoseServer(query)));
92
+ this.tool('find_issues', 'Scan infrastructure for problems', {}, async () => wrap(() => this.client.findInfrastructureIssues()));
118
93
  // =========================================================================
119
- // Projects (5 tools)
94
+ // Servers (5 tools)
120
95
  // =========================================================================
121
- this.tool('list_projects', 'List all projects (returns summary: uuid, name, description). Use get_project for full details.', {
122
- page: z.number().optional().describe('Page number for pagination'),
123
- per_page: z.number().optional().describe('Items per page (default: all)'),
124
- }, async ({ page, per_page }) => wrapHandler(() => this.client.listProjects({ page, per_page, summary: true })));
125
- this.tool('get_project', 'Get project details', { uuid: z.string().describe('Project UUID') }, async ({ uuid }) => wrapHandler(() => this.client.getProject(uuid)));
126
- this.tool('create_project', 'Create a new project', {
127
- name: z.string().describe('Project name'),
128
- description: z.string().optional().describe('Description'),
129
- }, async (args) => wrapHandler(() => this.client.createProject(args)));
130
- this.tool('update_project', 'Update a project', {
131
- uuid: z.string().describe('Project UUID'),
132
- name: z.string().optional().describe('Project name'),
133
- description: z.string().optional().describe('Description'),
134
- }, async ({ uuid, ...data }) => wrapHandler(() => this.client.updateProject(uuid, data)));
135
- this.tool('delete_project', 'Delete a project', { uuid: z.string().describe('Project UUID') }, async ({ uuid }) => wrapHandler(() => this.client.deleteProject(uuid)));
96
+ this.tool('list_servers', 'List servers (summary)', { page: z.number().optional(), per_page: z.number().optional() }, async ({ page, per_page }) => wrap(() => this.client.listServers({ page, per_page, summary: true })));
97
+ this.tool('get_server', 'Server details', { uuid: z.string() }, async ({ uuid }) => wrap(() => this.client.getServer(uuid)));
98
+ this.tool('server_resources', 'Resources on server', { uuid: z.string() }, async ({ uuid }) => wrap(() => this.client.getServerResources(uuid)));
99
+ this.tool('server_domains', 'Domains on server', { uuid: z.string() }, async ({ uuid }) => wrap(() => this.client.getServerDomains(uuid)));
100
+ this.tool('validate_server', 'Validate server connection', { uuid: z.string() }, async ({ uuid }) => wrap(() => this.client.validateServer(uuid)));
136
101
  // =========================================================================
137
- // Environments (4 tools)
102
+ // Projects (1 tool - consolidated CRUD)
138
103
  // =========================================================================
139
- this.tool('list_environments', 'List environments in a project', { project_uuid: z.string().describe('Project UUID') }, async ({ project_uuid }) => wrapHandler(() => this.client.listProjectEnvironments(project_uuid)));
140
- this.tool('get_environment', 'Get environment details', {
141
- project_uuid: z.string().describe('Project UUID'),
142
- environment: z.string().describe('Environment name or UUID'),
143
- }, async ({ project_uuid, environment }) => wrapHandler(() => this.client.getProjectEnvironment(project_uuid, environment)));
144
- this.tool('create_environment', 'Create environment in a project', {
145
- project_uuid: z.string().describe('Project UUID'),
146
- name: z.string().describe('Environment name'),
147
- description: z.string().optional().describe('Description'),
148
- }, async ({ project_uuid, ...data }) => wrapHandler(() => this.client.createProjectEnvironment(project_uuid, data)));
149
- this.tool('delete_environment', 'Delete an environment. Environment must be empty (no resources).', {
150
- project_uuid: z.string().describe('Project UUID'),
151
- environment_name_or_uuid: z.string().describe('Environment name or UUID'),
152
- }, async ({ project_uuid, environment_name_or_uuid }) => wrapHandler(() => this.client.deleteProjectEnvironment(project_uuid, environment_name_or_uuid)));
104
+ this.tool('projects', 'Manage projects: list/get/create/update/delete', {
105
+ action: z.enum(['list', 'get', 'create', 'update', 'delete']),
106
+ uuid: z.string().optional(),
107
+ name: z.string().optional(),
108
+ description: z.string().optional(),
109
+ page: z.number().optional(),
110
+ per_page: z.number().optional(),
111
+ }, async ({ action, uuid, name, description, page, per_page }) => {
112
+ switch (action) {
113
+ case 'list':
114
+ return wrap(() => this.client.listProjects({ page, per_page, summary: true }));
115
+ case 'get':
116
+ if (!uuid)
117
+ return { content: [{ type: 'text', text: 'Error: uuid required' }] };
118
+ return wrap(() => this.client.getProject(uuid));
119
+ case 'create':
120
+ if (!name)
121
+ return { content: [{ type: 'text', text: 'Error: name required' }] };
122
+ return wrap(() => this.client.createProject({ name, description }));
123
+ case 'update':
124
+ if (!uuid)
125
+ return { content: [{ type: 'text', text: 'Error: uuid required' }] };
126
+ return wrap(() => this.client.updateProject(uuid, { name, description }));
127
+ case 'delete':
128
+ if (!uuid)
129
+ return { content: [{ type: 'text', text: 'Error: uuid required' }] };
130
+ return wrap(() => this.client.deleteProject(uuid));
131
+ }
132
+ });
153
133
  // =========================================================================
154
- // Applications (15 tools)
134
+ // Environments (1 tool - consolidated CRUD)
155
135
  // =========================================================================
156
- this.tool('list_applications', 'List all applications (returns summary: uuid, name, status, fqdn, git_repository). Use get_application for full details.', {
157
- page: z.number().optional().describe('Page number for pagination'),
158
- per_page: z.number().optional().describe('Items per page (default: all)'),
159
- }, async ({ page, per_page }) => wrapHandler(() => this.client.listApplications({ page, per_page, summary: true })));
160
- this.tool('get_application', 'Get application details', { uuid: z.string().describe('Application UUID') }, async ({ uuid }) => wrapHandler(() => this.client.getApplication(uuid)));
161
- this.tool('create_application_private_gh', 'Create app from private GitHub repo (GitHub App)', {
162
- project_uuid: z.string().describe('Project UUID'),
163
- server_uuid: z.string().describe('Server UUID'),
164
- github_app_uuid: z.string().describe('GitHub App UUID'),
165
- git_repository: z.string().describe('Repository (org/repo)'),
166
- git_branch: z.string().describe('Branch'),
167
- environment_name: z.string().optional().describe('Environment name'),
168
- destination_uuid: z.string().optional().describe('Destination UUID'),
169
- build_pack: z.string().optional().describe('Build pack'),
170
- ports_exposes: z.string().optional().describe('Ports to expose'),
171
- }, async (args) => wrapHandler(() => this.client.createApplicationPrivateGH(args)));
172
- this.tool('create_application_private_key', 'Create app from private repo using deploy key', {
173
- project_uuid: z.string().describe('Project UUID'),
174
- server_uuid: z.string().describe('Server UUID'),
175
- private_key_uuid: z.string().describe('Private key UUID'),
176
- git_repository: z.string().describe('Repository URL'),
177
- git_branch: z.string().describe('Branch'),
178
- environment_name: z.string().optional().describe('Environment name'),
179
- destination_uuid: z.string().optional().describe('Destination UUID'),
180
- build_pack: z.string().optional().describe('Build pack'),
181
- ports_exposes: z.string().optional().describe('Ports to expose'),
182
- }, async (args) => wrapHandler(() => this.client.createApplicationPrivateKey(args)));
183
- this.tool('update_application', 'Update an application', {
184
- uuid: z.string().describe('Application UUID'),
185
- name: z.string().optional().describe('Name'),
186
- description: z.string().optional().describe('Description'),
187
- fqdn: z.string().optional().describe('Domain'),
188
- git_branch: z.string().optional().describe('Git branch'),
189
- is_http_basic_auth_enabled: z
190
- .boolean()
191
- .optional()
192
- .describe('Enable HTTP basic authentication'),
193
- http_basic_auth_username: z.string().optional().describe('HTTP basic auth username'),
194
- http_basic_auth_password: z.string().optional().describe('HTTP basic auth password'),
195
- }, async ({ uuid, ...data }) => wrapHandler(() => this.client.updateApplication(uuid, data)));
196
- this.tool('delete_application', 'Delete an application', {
197
- uuid: z.string().describe('Application UUID'),
198
- delete_volumes: z.boolean().optional().describe('Delete volumes'),
199
- }, async ({ uuid, delete_volumes }) => wrapHandler(() => this.client.deleteApplication(uuid, { deleteVolumes: delete_volumes })));
200
- this.tool('start_application', 'Start an application', { uuid: z.string().describe('Application UUID') }, async ({ uuid }) => wrapHandler(() => this.client.startApplication(uuid)));
201
- this.tool('stop_application', 'Stop an application', { uuid: z.string().describe('Application UUID') }, async ({ uuid }) => wrapHandler(() => this.client.stopApplication(uuid)));
202
- this.tool('restart_application', 'Restart an application', { uuid: z.string().describe('Application UUID') }, async ({ uuid }) => wrapHandler(() => this.client.restartApplication(uuid)));
203
- this.tool('get_application_logs', 'Get application logs', {
204
- uuid: z.string().describe('Application UUID'),
205
- lines: z.number().optional().describe('Number of lines'),
206
- }, async ({ uuid, lines }) => wrapHandler(() => this.client.getApplicationLogs(uuid, lines)));
207
- // Application env vars
208
- this.tool('list_application_envs', 'List application environment variables (returns summary: uuid, key, value, is_build_time)', { uuid: z.string().describe('Application UUID') }, async ({ uuid }) => wrapHandler(() => this.client.listApplicationEnvVars(uuid, { summary: true })));
209
- this.tool('create_application_env', 'Create application environment variable', {
210
- uuid: z.string().describe('Application UUID'),
211
- key: z.string().describe('Variable key'),
212
- value: z.string().describe('Variable value'),
213
- is_build_time: z.boolean().optional().describe('Build time variable'),
214
- }, async ({ uuid, ...data }) => wrapHandler(() => this.client.createApplicationEnvVar(uuid, data)));
215
- this.tool('update_application_env', 'Update application environment variable', {
216
- uuid: z.string().describe('Application UUID'),
217
- key: z.string().describe('Variable key'),
218
- value: z.string().describe('Variable value'),
219
- }, async ({ uuid, ...data }) => wrapHandler(() => this.client.updateApplicationEnvVar(uuid, data)));
220
- this.tool('delete_application_env', 'Delete application environment variable', {
221
- uuid: z.string().describe('Application UUID'),
222
- env_uuid: z.string().describe('Env variable UUID'),
223
- }, async ({ uuid, env_uuid }) => wrapHandler(() => this.client.deleteApplicationEnvVar(uuid, env_uuid)));
136
+ this.tool('environments', 'Manage environments: list/get/create/delete', {
137
+ action: z.enum(['list', 'get', 'create', 'delete']),
138
+ project_uuid: z.string(),
139
+ name: z.string().optional(),
140
+ description: z.string().optional(),
141
+ }, async ({ action, project_uuid, name, description }) => {
142
+ switch (action) {
143
+ case 'list':
144
+ return wrap(() => this.client.listProjectEnvironments(project_uuid));
145
+ case 'get':
146
+ if (!name)
147
+ return { content: [{ type: 'text', text: 'Error: name required' }] };
148
+ return wrap(() => this.client.getProjectEnvironment(project_uuid, name));
149
+ case 'create':
150
+ if (!name)
151
+ return { content: [{ type: 'text', text: 'Error: name required' }] };
152
+ return wrap(() => this.client.createProjectEnvironment(project_uuid, { name, description }));
153
+ case 'delete':
154
+ if (!name)
155
+ return { content: [{ type: 'text', text: 'Error: name required' }] };
156
+ return wrap(() => this.client.deleteProjectEnvironment(project_uuid, name));
157
+ }
158
+ });
224
159
  // =========================================================================
225
- // Databases (14 tools)
160
+ // Applications (4 tools)
226
161
  // =========================================================================
227
- this.tool('list_databases', 'List all databases (returns summary: uuid, name, type, status). Use get_database for full details.', {
228
- page: z.number().optional().describe('Page number for pagination'),
229
- per_page: z.number().optional().describe('Items per page (default: all)'),
230
- }, async ({ page, per_page }) => wrapHandler(() => this.client.listDatabases({ page, per_page, summary: true })));
231
- this.tool('get_database', 'Get database details', { uuid: z.string().describe('Database UUID') }, async ({ uuid }) => wrapHandler(() => this.client.getDatabase(uuid)));
232
- this.tool('start_database', 'Start a database', { uuid: z.string().describe('Database UUID') }, async ({ uuid }) => wrapHandler(() => this.client.startDatabase(uuid)));
233
- this.tool('stop_database', 'Stop a database', { uuid: z.string().describe('Database UUID') }, async ({ uuid }) => wrapHandler(() => this.client.stopDatabase(uuid)));
234
- this.tool('restart_database', 'Restart a database', { uuid: z.string().describe('Database UUID') }, async ({ uuid }) => wrapHandler(() => this.client.restartDatabase(uuid)));
235
- this.tool('delete_database', 'Delete a database. WARNING: This permanently deletes the database and optionally its volumes. Data cannot be recovered unless you have backups.', {
236
- uuid: z.string().describe('Database UUID'),
237
- delete_volumes: z.boolean().optional().describe('Delete volumes (default: false)'),
238
- }, async ({ uuid, delete_volumes }) => wrapHandler(() => this.client.deleteDatabase(uuid, { deleteVolumes: delete_volumes })));
239
- // Database creation tools - shared schema for base fields
240
- const databaseBaseSchema = {
241
- server_uuid: z.string().describe('Server UUID'),
242
- project_uuid: z.string().describe('Project UUID'),
243
- environment_name: z.string().optional().describe('Environment name'),
244
- environment_uuid: z.string().optional().describe('Environment UUID'),
245
- destination_uuid: z.string().optional().describe('Destination UUID'),
246
- name: z.string().optional().describe('Database name'),
247
- description: z.string().optional().describe('Database description'),
248
- image: z.string().optional().describe('Docker image'),
249
- is_public: z.boolean().optional().describe('Make database publicly accessible'),
250
- public_port: z.number().optional().describe('Public port'),
251
- limits_memory: z.string().optional().describe('Memory limit'),
252
- limits_memory_swap: z.string().optional().describe('Memory swap limit'),
253
- limits_memory_swappiness: z.number().optional().describe('Memory swappiness'),
254
- limits_memory_reservation: z.string().optional().describe('Memory reservation'),
255
- limits_cpus: z.string().optional().describe('CPU limit'),
256
- limits_cpuset: z.string().optional().describe('CPU set'),
257
- limits_cpu_shares: z.number().optional().describe('CPU shares'),
258
- instant_deploy: z.boolean().optional().describe('Deploy immediately after creation'),
259
- };
260
- this.tool('create_postgresql', 'Create a new PostgreSQL database', {
261
- ...databaseBaseSchema,
262
- postgres_user: z.string().optional().describe('PostgreSQL user'),
263
- postgres_password: z.string().optional().describe('PostgreSQL password'),
264
- postgres_db: z.string().optional().describe('PostgreSQL database name'),
265
- postgres_initdb_args: z.string().optional().describe('PostgreSQL initdb args'),
266
- postgres_host_auth_method: z.string().optional().describe('PostgreSQL host auth method'),
267
- postgres_conf: z.string().optional().describe('PostgreSQL configuration'),
268
- }, async (args) => wrapHandler(() => this.client.createPostgresql(args)));
269
- this.tool('create_mysql', 'Create a new MySQL database', {
270
- ...databaseBaseSchema,
271
- mysql_root_password: z.string().optional().describe('MySQL root password'),
272
- mysql_user: z.string().optional().describe('MySQL user'),
273
- mysql_password: z.string().optional().describe('MySQL password'),
274
- mysql_database: z.string().optional().describe('MySQL database name'),
275
- mysql_conf: z.string().optional().describe('MySQL configuration'),
276
- }, async (args) => wrapHandler(() => this.client.createMysql(args)));
277
- this.tool('create_mariadb', 'Create a new MariaDB database', {
278
- ...databaseBaseSchema,
279
- mariadb_root_password: z.string().optional().describe('MariaDB root password'),
280
- mariadb_user: z.string().optional().describe('MariaDB user'),
281
- mariadb_password: z.string().optional().describe('MariaDB password'),
282
- mariadb_database: z.string().optional().describe('MariaDB database name'),
283
- mariadb_conf: z.string().optional().describe('MariaDB configuration'),
284
- }, async (args) => wrapHandler(() => this.client.createMariadb(args)));
285
- this.tool('create_mongodb', 'Create a new MongoDB database', {
286
- ...databaseBaseSchema,
287
- mongo_initdb_root_username: z.string().optional().describe('MongoDB root username'),
288
- mongo_initdb_root_password: z.string().optional().describe('MongoDB root password'),
289
- mongo_initdb_database: z.string().optional().describe('MongoDB database name'),
290
- mongo_conf: z.string().optional().describe('MongoDB configuration'),
291
- }, async (args) => wrapHandler(() => this.client.createMongodb(args)));
292
- this.tool('create_redis', 'Create a new Redis database', {
293
- ...databaseBaseSchema,
294
- redis_password: z.string().optional().describe('Redis password'),
295
- redis_conf: z.string().optional().describe('Redis configuration'),
296
- }, async (args) => wrapHandler(() => this.client.createRedis(args)));
297
- this.tool('create_keydb', 'Create a new KeyDB database', {
298
- ...databaseBaseSchema,
299
- keydb_password: z.string().optional().describe('KeyDB password'),
300
- keydb_conf: z.string().optional().describe('KeyDB configuration'),
301
- }, async (args) => wrapHandler(() => this.client.createKeydb(args)));
302
- this.tool('create_clickhouse', 'Create a new ClickHouse database', {
303
- ...databaseBaseSchema,
304
- clickhouse_admin_user: z.string().optional().describe('ClickHouse admin user'),
305
- clickhouse_admin_password: z.string().optional().describe('ClickHouse admin password'),
306
- }, async (args) => wrapHandler(() => this.client.createClickhouse(args)));
307
- this.tool('create_dragonfly', 'Create a new Dragonfly database (Redis-compatible)', {
308
- ...databaseBaseSchema,
309
- dragonfly_password: z.string().optional().describe('Dragonfly password'),
310
- }, async (args) => wrapHandler(() => this.client.createDragonfly(args)));
162
+ this.tool('list_applications', 'List apps (summary)', { page: z.number().optional(), per_page: z.number().optional() }, async ({ page, per_page }) => wrap(() => this.client.listApplications({ page, per_page, summary: true })));
163
+ this.tool('get_application', 'App details', { uuid: z.string() }, async ({ uuid }) => wrap(() => this.client.getApplication(uuid)));
164
+ this.tool('application', 'Manage app: create/update/delete', {
165
+ action: z.enum(['create_github', 'create_key', 'update', 'delete']),
166
+ uuid: z.string().optional(),
167
+ // Create fields
168
+ project_uuid: z.string().optional(),
169
+ server_uuid: z.string().optional(),
170
+ github_app_uuid: z.string().optional(),
171
+ private_key_uuid: z.string().optional(),
172
+ git_repository: z.string().optional(),
173
+ git_branch: z.string().optional(),
174
+ environment_name: z.string().optional(),
175
+ build_pack: z.string().optional(),
176
+ ports_exposes: z.string().optional(),
177
+ // Update fields
178
+ name: z.string().optional(),
179
+ description: z.string().optional(),
180
+ fqdn: z.string().optional(),
181
+ // Delete fields
182
+ delete_volumes: z.boolean().optional(),
183
+ }, async (args) => {
184
+ const { action, uuid } = args;
185
+ switch (action) {
186
+ case 'create_github':
187
+ if (!args.project_uuid ||
188
+ !args.server_uuid ||
189
+ !args.github_app_uuid ||
190
+ !args.git_repository ||
191
+ !args.git_branch) {
192
+ return {
193
+ content: [
194
+ {
195
+ type: 'text',
196
+ text: 'Error: project_uuid, server_uuid, github_app_uuid, git_repository, git_branch required',
197
+ },
198
+ ],
199
+ };
200
+ }
201
+ return wrap(() => this.client.createApplicationPrivateGH(args));
202
+ case 'create_key':
203
+ if (!args.project_uuid ||
204
+ !args.server_uuid ||
205
+ !args.private_key_uuid ||
206
+ !args.git_repository ||
207
+ !args.git_branch) {
208
+ return {
209
+ content: [
210
+ {
211
+ type: 'text',
212
+ text: 'Error: project_uuid, server_uuid, private_key_uuid, git_repository, git_branch required',
213
+ },
214
+ ],
215
+ };
216
+ }
217
+ return wrap(() => this.client.createApplicationPrivateKey(args));
218
+ case 'update':
219
+ if (!uuid)
220
+ return { content: [{ type: 'text', text: 'Error: uuid required' }] };
221
+ return wrap(() => this.client.updateApplication(uuid, args));
222
+ case 'delete':
223
+ if (!uuid)
224
+ return { content: [{ type: 'text', text: 'Error: uuid required' }] };
225
+ return wrap(() => this.client.deleteApplication(uuid, { deleteVolumes: args.delete_volumes }));
226
+ }
227
+ });
228
+ this.tool('application_logs', 'Get app logs', { uuid: z.string(), lines: z.number().optional() }, async ({ uuid, lines }) => wrap(() => this.client.getApplicationLogs(uuid, lines)));
311
229
  // =========================================================================
312
- // Services (11 tools)
230
+ // Databases (3 tools)
313
231
  // =========================================================================
314
- this.tool('list_services', 'List all services (returns summary: uuid, name, type, status, domains). Use get_service for full details.', {
315
- page: z.number().optional().describe('Page number for pagination'),
316
- per_page: z.number().optional().describe('Items per page (default: all)'),
317
- }, async ({ page, per_page }) => wrapHandler(() => this.client.listServices({ page, per_page, summary: true })));
318
- this.tool('get_service', 'Get service details', { uuid: z.string().describe('Service UUID') }, async ({ uuid }) => wrapHandler(() => this.client.getService(uuid)));
319
- this.tool('create_service', 'Create a one-click service (e.g., pocketbase, mysql, redis, wordpress, etc.). Use type OR docker_compose_raw, not both.', {
232
+ this.tool('list_databases', 'List databases (summary)', { page: z.number().optional(), per_page: z.number().optional() }, async ({ page, per_page }) => wrap(() => this.client.listDatabases({ page, per_page, summary: true })));
233
+ this.tool('get_database', 'Database details', { uuid: z.string() }, async ({ uuid }) => wrap(() => this.client.getDatabase(uuid)));
234
+ this.tool('database', 'Manage database: create/delete', {
235
+ action: z.enum(['create', 'delete']),
320
236
  type: z
321
- .string()
322
- .optional()
323
- .describe('Service type (e.g., pocketbase, mysql, redis, postgresql, mongodb, wordpress, etc.)'),
324
- server_uuid: z.string().describe('Server UUID'),
325
- project_uuid: z.string().describe('Project UUID'),
326
- environment_name: z.string().optional().describe('Environment name (e.g., production)'),
327
- environment_uuid: z
328
- .string()
329
- .optional()
330
- .describe('Environment UUID (alternative to environment_name)'),
331
- name: z.string().optional().describe('Service name'),
332
- description: z.string().optional().describe('Service description'),
333
- destination_uuid: z.string().optional().describe('Destination UUID'),
334
- instant_deploy: z.boolean().optional().describe('Deploy immediately after creation'),
335
- docker_compose_raw: z
336
- .string()
337
- .optional()
338
- .describe('Base64 encoded docker-compose YAML with SERVICE_FQDN_* env var for custom domain (alternative to type)'),
339
- }, async (args) => wrapHandler(() => this.client.createService(args)));
340
- this.tool('delete_service', 'Delete a service', {
341
- uuid: z.string().describe('Service UUID'),
342
- delete_configurations: z
343
- .boolean()
344
- .optional()
345
- .describe('Delete configurations (default: true)'),
346
- delete_volumes: z.boolean().optional().describe('Delete volumes (default: true)'),
347
- docker_cleanup: z
348
- .boolean()
349
- .optional()
350
- .describe('Clean up Docker resources (default: true)'),
351
- delete_connected_networks: z
352
- .boolean()
353
- .optional()
354
- .describe('Delete connected networks (default: true)'),
355
- }, async ({ uuid, delete_configurations, delete_volumes, docker_cleanup, delete_connected_networks, }) => wrapHandler(() => this.client.deleteService(uuid, {
356
- deleteConfigurations: delete_configurations,
357
- deleteVolumes: delete_volumes,
358
- dockerCleanup: docker_cleanup,
359
- deleteConnectedNetworks: delete_connected_networks,
360
- })));
361
- this.tool('update_service', 'Update a service (IMPORTANT: See UpdateServiceRequest type docs for Traefik basic auth requirements)', {
362
- uuid: z.string().describe('Service UUID'),
363
- name: z.string().optional().describe('Service name'),
364
- description: z.string().optional().describe('Description'),
365
- docker_compose_raw: z
366
- .string()
367
- .optional()
368
- .describe('Base64 encoded docker-compose YAML. CRITICAL FOR BASIC AUTH: (1) Manually disable label escaping in Coolify UI first (no API). (2) Use $$ in htpasswd hashes even with escaping disabled (Traefik requirement). (3) Generate: htpasswd -nb user pass, then replace $ with $$.'),
369
- }, async ({ uuid, ...data }) => wrapHandler(() => this.client.updateService(uuid, data)));
370
- this.tool('start_service', 'Start a service', { uuid: z.string().describe('Service UUID') }, async ({ uuid }) => wrapHandler(() => this.client.startService(uuid)));
371
- this.tool('stop_service', 'Stop a service', { uuid: z.string().describe('Service UUID') }, async ({ uuid }) => wrapHandler(() => this.client.stopService(uuid)));
372
- this.tool('restart_service', 'Restart a service', { uuid: z.string().describe('Service UUID') }, async ({ uuid }) => wrapHandler(() => this.client.restartService(uuid)));
373
- // Service env vars
374
- this.tool('list_service_envs', 'List service environment variables', { uuid: z.string().describe('Service UUID') }, async ({ uuid }) => wrapHandler(() => this.client.listServiceEnvVars(uuid)));
375
- this.tool('create_service_env', 'Create service environment variable', {
376
- uuid: z.string().describe('Service UUID'),
377
- key: z.string().describe('Variable key'),
378
- value: z.string().describe('Variable value'),
379
- }, async ({ uuid, ...data }) => wrapHandler(() => this.client.createServiceEnvVar(uuid, data)));
380
- this.tool('delete_service_env', 'Delete service environment variable', {
381
- uuid: z.string().describe('Service UUID'),
382
- env_uuid: z.string().describe('Env variable UUID'),
383
- }, async ({ uuid, env_uuid }) => wrapHandler(() => this.client.deleteServiceEnvVar(uuid, env_uuid)));
384
- // =========================================================================
385
- // Deployments (4 tools)
386
- // =========================================================================
387
- this.tool('list_deployments', 'List running deployments (returns summary: uuid, deployment_uuid, application_name, status). Use get_deployment for full details.', {
388
- page: z.number().optional().describe('Page number for pagination'),
389
- per_page: z.number().optional().describe('Items per page (default: all)'),
390
- }, async ({ page, per_page }) => wrapHandler(() => this.client.listDeployments({ page, per_page, summary: true })));
391
- this.tool('get_deployment', 'Get deployment details', { uuid: z.string().describe('Deployment UUID') }, async ({ uuid }) => wrapHandler(() => this.client.getDeployment(uuid)));
392
- this.tool('deploy', 'Deploy by tag or UUID', {
393
- tag_or_uuid: z.string().describe('Tag or UUID'),
394
- force: z.boolean().optional().describe('Force rebuild'),
395
- }, async ({ tag_or_uuid, force }) => wrapHandler(() => this.client.deployByTagOrUuid(tag_or_uuid, force)));
396
- this.tool('list_application_deployments', 'List deployments for an application', { uuid: z.string().describe('Application UUID') }, async ({ uuid }) => wrapHandler(() => this.client.listApplicationDeployments(uuid)));
397
- this.tool('cancel_deployment', 'Cancel a running deployment', { uuid: z.string().describe('Deployment UUID') }, async ({ uuid }) => wrapHandler(() => this.client.cancelDeployment(uuid)));
398
- // =========================================================================
399
- // Private Keys (5 tools)
400
- // =========================================================================
401
- this.tool('list_private_keys', 'List all private keys (SSH keys for deployments)', {}, async () => wrapHandler(() => this.client.listPrivateKeys()));
402
- this.tool('get_private_key', 'Get private key details', { uuid: z.string().describe('Private key UUID') }, async ({ uuid }) => wrapHandler(() => this.client.getPrivateKey(uuid)));
403
- this.tool('create_private_key', 'Create a new private key for deployments', {
404
- private_key: z.string().describe('The private key content (PEM format)'),
405
- name: z.string().optional().describe('Name for the key'),
406
- description: z.string().optional().describe('Description'),
407
- }, async (args) => wrapHandler(() => this.client.createPrivateKey(args)));
408
- this.tool('update_private_key', 'Update a private key', {
409
- uuid: z.string().describe('Private key UUID'),
410
- name: z.string().optional().describe('Name for the key'),
411
- description: z.string().optional().describe('Description'),
412
- private_key: z.string().optional().describe('The private key content (PEM format)'),
413
- }, async ({ uuid, ...data }) => wrapHandler(() => this.client.updatePrivateKey(uuid, data)));
414
- this.tool('delete_private_key', 'Delete a private key', { uuid: z.string().describe('Private key UUID') }, async ({ uuid }) => wrapHandler(() => this.client.deletePrivateKey(uuid)));
415
- // =========================================================================
416
- // Database Backups (4 tools)
417
- // =========================================================================
418
- this.tool('list_database_backups', 'List scheduled backups for a database', { uuid: z.string().describe('Database UUID') }, async ({ uuid }) => wrapHandler(() => this.client.listDatabaseBackups(uuid)));
419
- this.tool('get_database_backup', 'Get details of a scheduled backup', {
420
- database_uuid: z.string().describe('Database UUID'),
421
- backup_uuid: z.string().describe('Scheduled backup UUID'),
422
- }, async ({ database_uuid, backup_uuid }) => wrapHandler(() => this.client.getDatabaseBackup(database_uuid, backup_uuid)));
423
- this.tool('list_backup_executions', 'List execution history for a scheduled backup', {
424
- database_uuid: z.string().describe('Database UUID'),
425
- backup_uuid: z.string().describe('Scheduled backup UUID'),
426
- }, async ({ database_uuid, backup_uuid }) => wrapHandler(() => this.client.listBackupExecutions(database_uuid, backup_uuid)));
427
- this.tool('get_backup_execution', 'Get details of a specific backup execution', {
428
- database_uuid: z.string().describe('Database UUID'),
429
- backup_uuid: z.string().describe('Scheduled backup UUID'),
430
- execution_uuid: z.string().describe('Backup execution UUID'),
431
- }, async ({ database_uuid, backup_uuid, execution_uuid }) => wrapHandler(() => this.client.getBackupExecution(database_uuid, backup_uuid, execution_uuid)));
432
- // =========================================================================
433
- // Diagnostics (3 tools) - Composite tools for debugging
434
- // =========================================================================
435
- this.tool('diagnose_app', 'Get comprehensive diagnostic info for an application. Accepts UUID, name, or domain (e.g., "stuartmason.co.uk" or "my-app"). Aggregates: status, health assessment, logs (last 50 lines), environment variables (keys only, values hidden), and recent deployments. Use this for debugging application issues.', { query: z.string().describe('Application UUID, name, or domain (FQDN)') }, async ({ query }) => wrapHandler(() => this.client.diagnoseApplication(query)));
436
- this.tool('diagnose_server', 'Get comprehensive diagnostic info for a server. Accepts UUID, name, or IP address (e.g., "coolify-apps" or "192.168.1.100"). Aggregates: server status, health assessment, running resources, configured domains, and connection validation. Use this for debugging server issues.', { query: z.string().describe('Server UUID, name, or IP address') }, async ({ query }) => wrapHandler(() => this.client.diagnoseServer(query)));
437
- this.tool('find_issues', 'Scan entire infrastructure for common issues. Finds: unreachable servers, unhealthy/stopped applications, exited databases, and stopped services. Returns a summary with issue counts and detailed list of problems.', {}, async () => wrapHandler(() => this.client.findInfrastructureIssues()));
438
- // =========================================================================
439
- // Batch Operations (4 tools) - Operate on multiple resources at once
440
- // =========================================================================
441
- this.tool('restart_project_apps', 'Restart all applications in a project. Returns a summary of succeeded/failed restarts with details.', { project_uuid: z.string().describe('Project UUID') }, async ({ project_uuid }) => wrapHandler(() => this.client.restartProjectApps(project_uuid)));
442
- this.tool('bulk_env_update', 'Update or create an environment variable across multiple applications (upsert behavior). Returns summary of succeeded/failed updates.', {
443
- app_uuids: z.array(z.string()).describe('Array of application UUIDs'),
444
- key: z.string().describe('Environment variable key'),
445
- value: z.string().describe('Environment variable value'),
446
- is_build_time: z.boolean().optional().describe('Build-time variable (default: false)'),
447
- }, async ({ app_uuids, key, value, is_build_time }) => wrapHandler(() => this.client.bulkEnvUpdate(app_uuids, key, value, is_build_time)));
448
- this.tool('stop_all_apps', 'EMERGENCY: Stop ALL running applications across entire infrastructure. Only stops apps that are currently running or healthy. Use with caution!', {
449
- confirm: z.literal(true).describe('Must be true to confirm this dangerous operation'),
450
- }, async ({ confirm }) => {
451
- if (!confirm) {
237
+ .enum([
238
+ 'postgresql',
239
+ 'mysql',
240
+ 'mariadb',
241
+ 'mongodb',
242
+ 'redis',
243
+ 'keydb',
244
+ 'clickhouse',
245
+ 'dragonfly',
246
+ ])
247
+ .optional(),
248
+ uuid: z.string().optional(),
249
+ server_uuid: z.string().optional(),
250
+ project_uuid: z.string().optional(),
251
+ environment_name: z.string().optional(),
252
+ name: z.string().optional(),
253
+ description: z.string().optional(),
254
+ image: z.string().optional(),
255
+ is_public: z.boolean().optional(),
256
+ public_port: z.number().optional(),
257
+ instant_deploy: z.boolean().optional(),
258
+ delete_volumes: z.boolean().optional(),
259
+ // DB-specific optional fields
260
+ postgres_user: z.string().optional(),
261
+ postgres_password: z.string().optional(),
262
+ postgres_db: z.string().optional(),
263
+ mysql_root_password: z.string().optional(),
264
+ mysql_user: z.string().optional(),
265
+ mysql_password: z.string().optional(),
266
+ mysql_database: z.string().optional(),
267
+ mariadb_root_password: z.string().optional(),
268
+ mariadb_user: z.string().optional(),
269
+ mariadb_password: z.string().optional(),
270
+ mariadb_database: z.string().optional(),
271
+ mongo_initdb_root_username: z.string().optional(),
272
+ mongo_initdb_root_password: z.string().optional(),
273
+ mongo_initdb_database: z.string().optional(),
274
+ redis_password: z.string().optional(),
275
+ keydb_password: z.string().optional(),
276
+ clickhouse_admin_user: z.string().optional(),
277
+ clickhouse_admin_password: z.string().optional(),
278
+ dragonfly_password: z.string().optional(),
279
+ }, async (args) => {
280
+ const { action, type, uuid } = args;
281
+ if (action === 'delete') {
282
+ if (!uuid)
283
+ return { content: [{ type: 'text', text: 'Error: uuid required' }] };
284
+ return wrap(() => this.client.deleteDatabase(uuid, { deleteVolumes: args.delete_volumes }));
285
+ }
286
+ // create
287
+ if (!type || !args.server_uuid || !args.project_uuid) {
452
288
  return {
453
289
  content: [
454
- { type: 'text', text: 'Error: Must set confirm=true to stop all apps' },
290
+ { type: 'text', text: 'Error: type, server_uuid, project_uuid required' },
455
291
  ],
456
292
  };
457
293
  }
458
- return wrapHandler(() => this.client.stopAllApps());
459
- });
460
- this.tool('redeploy_project', 'Redeploy all applications in a project with force rebuild. Returns summary of succeeded/failed deployments.', {
461
- project_uuid: z.string().describe('Project UUID'),
462
- force: z.boolean().optional().describe('Force rebuild (default: true)'),
463
- }, async ({ project_uuid, force }) => wrapHandler(() => this.client.redeployProjectApps(project_uuid, force ?? true)));
464
- }
465
- // ===========================================================================
466
- // Prompt Templates - Guided Workflows
467
- // ===========================================================================
468
- registerPrompts() {
469
- // -------------------------------------------------------------------------
470
- // debug-app: Comprehensive application debugging workflow
471
- // -------------------------------------------------------------------------
472
- this.prompt('debug-app', 'Debug an application - gathers status, logs, env vars, and recent deployments to diagnose issues', {
473
- query: z
474
- .string()
475
- .describe('Application identifier: UUID, name, or domain (e.g., "my-app" or "example.com")'),
476
- }, async ({ query }) => {
477
- return {
478
- messages: [
479
- {
480
- role: 'user',
481
- content: {
482
- type: 'text',
483
- text: `I need help debugging my Coolify application "${query}". Please:
484
-
485
- 1. First, use the diagnose_app tool with query="${query}" to get comprehensive diagnostics
486
- 2. Analyze the results and identify:
487
- - Current health status and any issues
488
- - Recent deployment failures or errors in logs
489
- - Missing or misconfigured environment variables
490
- - Any patterns suggesting the root cause
491
- 3. Provide a clear diagnosis with:
492
- - What's wrong (if anything)
493
- - Likely root cause
494
- - Recommended fix steps
495
- 4. If the app seems healthy, confirm this and suggest any optimizations
496
-
497
- Start by running diagnose_app now.`,
498
- },
499
- },
500
- ],
294
+ const dbMethods = {
295
+ postgresql: (d) => this.client.createPostgresql(d),
296
+ mysql: (d) => this.client.createMysql(d),
297
+ mariadb: (d) => this.client.createMariadb(d),
298
+ mongodb: (d) => this.client.createMongodb(d),
299
+ redis: (d) => this.client.createRedis(d),
300
+ keydb: (d) => this.client.createKeydb(d),
301
+ clickhouse: (d) => this.client.createClickhouse(d),
302
+ dragonfly: (d) => this.client.createDragonfly(d),
501
303
  };
304
+ return wrap(() => dbMethods[type](args));
502
305
  });
503
- // -------------------------------------------------------------------------
504
- // health-check: Full infrastructure health analysis
505
- // -------------------------------------------------------------------------
506
- this.prompt('health-check', 'Perform a comprehensive health check of your entire Coolify infrastructure', {}, async () => {
507
- return {
508
- messages: [
509
- {
510
- role: 'user',
511
- content: {
512
- type: 'text',
513
- text: `Please perform a comprehensive health check of my Coolify infrastructure:
514
-
515
- 1. Run find_issues to scan for problems across all servers, apps, databases, and services
516
- 2. Run get_infrastructure_overview to get the full picture
517
- 3. For any issues found, provide:
518
- - Severity (critical/warning/info)
519
- - Affected resource and current status
520
- - Recommended remediation steps
521
- 4. Summarize the overall health:
522
- - Total resources and their states
523
- - Any immediate actions needed
524
- - Preventive recommendations
525
-
526
- Start by running find_issues now.`,
527
- },
528
- },
529
- ],
530
- };
306
+ // =========================================================================
307
+ // Services (3 tools)
308
+ // =========================================================================
309
+ this.tool('list_services', 'List services (summary)', { page: z.number().optional(), per_page: z.number().optional() }, async ({ page, per_page }) => wrap(() => this.client.listServices({ page, per_page, summary: true })));
310
+ this.tool('get_service', 'Service details', { uuid: z.string() }, async ({ uuid }) => wrap(() => this.client.getService(uuid)));
311
+ this.tool('service', 'Manage service: create/update/delete', {
312
+ action: z.enum(['create', 'update', 'delete']),
313
+ uuid: z.string().optional(),
314
+ type: z.string().optional(),
315
+ server_uuid: z.string().optional(),
316
+ project_uuid: z.string().optional(),
317
+ environment_name: z.string().optional(),
318
+ name: z.string().optional(),
319
+ description: z.string().optional(),
320
+ instant_deploy: z.boolean().optional(),
321
+ docker_compose_raw: z.string().optional(),
322
+ delete_volumes: z.boolean().optional(),
323
+ }, async (args) => {
324
+ const { action, uuid } = args;
325
+ switch (action) {
326
+ case 'create':
327
+ if (!args.server_uuid || !args.project_uuid) {
328
+ return {
329
+ content: [
330
+ { type: 'text', text: 'Error: server_uuid, project_uuid required' },
331
+ ],
332
+ };
333
+ }
334
+ return wrap(() => this.client.createService(args));
335
+ case 'update':
336
+ if (!uuid)
337
+ return { content: [{ type: 'text', text: 'Error: uuid required' }] };
338
+ return wrap(() => this.client.updateService(uuid, args));
339
+ case 'delete':
340
+ if (!uuid)
341
+ return { content: [{ type: 'text', text: 'Error: uuid required' }] };
342
+ return wrap(() => this.client.deleteService(uuid, { deleteVolumes: args.delete_volumes }));
343
+ }
531
344
  });
532
- // -------------------------------------------------------------------------
533
- // deploy-app: Step-by-step deployment wizard
534
- // -------------------------------------------------------------------------
535
- this.prompt('deploy-app', 'Step-by-step wizard to deploy a new application from a Git repository', {
536
- repo: z.string().describe('Git repository URL or org/repo format'),
537
- branch: z.string().optional().describe('Branch to deploy (default: main)'),
538
- }, async ({ repo, branch }) => {
539
- const branchName = branch || 'main';
540
- return {
541
- messages: [
542
- {
543
- role: 'user',
544
- content: {
545
- type: 'text',
546
- text: `I want to deploy a new application from ${repo} (branch: ${branchName}). Please guide me through the process:
547
-
548
- 1. First, run list_projects to show available projects
549
- 2. Ask me which project to deploy to (or help create a new one)
550
- 3. Run list_servers to show available servers
551
- 4. Ask me which server to deploy on
552
- 5. Run list_private_keys to check available deploy keys
553
- 6. Based on the repository type:
554
- - If GitHub and we have a GitHub App configured, use create_application_private_gh
555
- - Otherwise, help set up a deploy key and use create_application_private_key
556
- 7. After creation, ask about:
557
- - Environment variables needed
558
- - Domain/FQDN configuration
559
- - Whether to deploy immediately
560
-
561
- Start by showing me the available projects.`,
562
- },
563
- },
564
- ],
345
+ // =========================================================================
346
+ // Resource Control (1 tool - start/stop/restart for all types)
347
+ // =========================================================================
348
+ this.tool('control', 'Start/stop/restart app, database, or service', {
349
+ resource: z.enum(['application', 'database', 'service']),
350
+ action: z.enum(['start', 'stop', 'restart']),
351
+ uuid: z.string(),
352
+ }, async ({ resource, action, uuid }) => {
353
+ const methods = {
354
+ application: {
355
+ start: (u) => this.client.startApplication(u),
356
+ stop: (u) => this.client.stopApplication(u),
357
+ restart: (u) => this.client.restartApplication(u),
358
+ },
359
+ database: {
360
+ start: (u) => this.client.startDatabase(u),
361
+ stop: (u) => this.client.stopDatabase(u),
362
+ restart: (u) => this.client.restartDatabase(u),
363
+ },
364
+ service: {
365
+ start: (u) => this.client.startService(u),
366
+ stop: (u) => this.client.stopService(u),
367
+ restart: (u) => this.client.restartService(u),
368
+ },
565
369
  };
370
+ return wrap(() => methods[resource][action](uuid));
566
371
  });
567
- // -------------------------------------------------------------------------
568
- // troubleshoot-ssl: SSL certificate diagnosis workflow
569
- // -------------------------------------------------------------------------
570
- this.prompt('troubleshoot-ssl', 'Diagnose SSL/TLS certificate issues for a domain', {
571
- domain: z.string().describe('Domain having SSL issues (e.g., "example.com")'),
572
- }, async ({ domain }) => {
573
- return {
574
- messages: [
575
- {
576
- role: 'user',
577
- content: {
578
- type: 'text',
579
- text: `I'm having SSL/TLS certificate issues with the domain "${domain}". Please help me diagnose:
580
-
581
- 1. First, use diagnose_app with query="${domain}" to find the application
582
- 2. Check the application's FQDN configuration
583
- 3. Look for common SSL issues:
584
- - Is the domain correctly configured in the FQDN field?
585
- - Are there any proxy/redirect issues in the logs?
586
- - Is Let's Encrypt renewal working (check for ACME errors)?
587
- 4. Check the server's domain configuration using get_server_domains
588
- 5. Provide remediation steps:
589
- - If domain misconfiguration: show how to fix with update_application
590
- - If SSL renewal issue: suggest checking DNS and Traefik config
591
- - If proxy issue: suggest checking Traefik labels
592
-
593
- Start by finding the application for this domain.`,
594
- },
595
- },
596
- ],
597
- };
372
+ // =========================================================================
373
+ // Environment Variables (1 tool - consolidated)
374
+ // =========================================================================
375
+ this.tool('env_vars', 'Manage env vars for app or service', {
376
+ resource: z.enum(['application', 'service']),
377
+ action: z.enum(['list', 'create', 'update', 'delete']),
378
+ uuid: z.string(),
379
+ key: z.string().optional(),
380
+ value: z.string().optional(),
381
+ env_uuid: z.string().optional(),
382
+ is_build_time: z.boolean().optional(),
383
+ }, async ({ resource, action, uuid, key, value, env_uuid, is_build_time }) => {
384
+ if (resource === 'application') {
385
+ switch (action) {
386
+ case 'list':
387
+ return wrap(() => this.client.listApplicationEnvVars(uuid, { summary: true }));
388
+ case 'create':
389
+ if (!key || !value)
390
+ return { content: [{ type: 'text', text: 'Error: key, value required' }] };
391
+ return wrap(() => this.client.createApplicationEnvVar(uuid, { key, value, is_build_time }));
392
+ case 'update':
393
+ if (!key || !value)
394
+ return { content: [{ type: 'text', text: 'Error: key, value required' }] };
395
+ return wrap(() => this.client.updateApplicationEnvVar(uuid, { key, value }));
396
+ case 'delete':
397
+ if (!env_uuid)
398
+ return { content: [{ type: 'text', text: 'Error: env_uuid required' }] };
399
+ return wrap(() => this.client.deleteApplicationEnvVar(uuid, env_uuid));
400
+ }
401
+ }
402
+ else {
403
+ switch (action) {
404
+ case 'list':
405
+ return wrap(() => this.client.listServiceEnvVars(uuid));
406
+ case 'create':
407
+ if (!key || !value)
408
+ return { content: [{ type: 'text', text: 'Error: key, value required' }] };
409
+ return wrap(() => this.client.createServiceEnvVar(uuid, { key, value }));
410
+ case 'update':
411
+ return {
412
+ content: [
413
+ { type: 'text', text: 'Error: service env update not supported' },
414
+ ],
415
+ };
416
+ case 'delete':
417
+ if (!env_uuid)
418
+ return { content: [{ type: 'text', text: 'Error: env_uuid required' }] };
419
+ return wrap(() => this.client.deleteServiceEnvVar(uuid, env_uuid));
420
+ }
421
+ }
598
422
  });
599
- // -------------------------------------------------------------------------
600
- // restart-project: Safely restart all apps in a project
601
- // -------------------------------------------------------------------------
602
- this.prompt('restart-project', 'Safely restart all applications in a project with status monitoring', {
603
- project: z.string().describe('Project UUID or name'),
604
- }, async ({ project }) => {
605
- return {
606
- messages: [
607
- {
608
- role: 'user',
609
- content: {
610
- type: 'text',
611
- text: `I need to restart all applications in the project "${project}". Please handle this safely:
612
-
613
- 1. First, run list_projects to find the project UUID (if a name was given)
614
- 2. Run get_project to confirm the project details and list its environments
615
- 3. Run list_applications to find all apps in this project
616
- 4. Show me a summary of what will be restarted:
617
- - List each application with current status
618
- - Warn about any that are already unhealthy
619
- 5. Ask for my confirmation before proceeding
620
- 6. If confirmed, run restart_project_apps with the project UUID
621
- 7. After restart, check the results and report:
622
- - Which apps restarted successfully
623
- - Any failures and why
624
- - Current status of each app
625
-
626
- Start by finding the project.`,
627
- },
628
- },
629
- ],
630
- };
423
+ // =========================================================================
424
+ // Deployments (3 tools)
425
+ // =========================================================================
426
+ this.tool('list_deployments', 'List deployments (summary)', { page: z.number().optional(), per_page: z.number().optional() }, async ({ page, per_page }) => wrap(() => this.client.listDeployments({ page, per_page, summary: true })));
427
+ this.tool('deploy', 'Deploy by tag/UUID', { tag_or_uuid: z.string(), force: z.boolean().optional() }, async ({ tag_or_uuid, force }) => wrap(() => this.client.deployByTagOrUuid(tag_or_uuid, force)));
428
+ this.tool('deployment', 'Manage deployment: get/cancel/list_for_app', {
429
+ action: z.enum(['get', 'cancel', 'list_for_app']),
430
+ uuid: z.string(),
431
+ }, async ({ action, uuid }) => {
432
+ switch (action) {
433
+ case 'get':
434
+ return wrap(() => this.client.getDeployment(uuid));
435
+ case 'cancel':
436
+ return wrap(() => this.client.cancelDeployment(uuid));
437
+ case 'list_for_app':
438
+ return wrap(() => this.client.listApplicationDeployments(uuid));
439
+ }
631
440
  });
632
- // -------------------------------------------------------------------------
633
- // env-audit: Audit environment variables across apps
634
- // -------------------------------------------------------------------------
635
- this.prompt('env-audit', 'Audit and compare environment variables across applications', {
636
- apps: z
637
- .string()
638
- .optional()
639
- .describe('Comma-separated app names/UUIDs to audit (optional, defaults to all)'),
640
- key: z.string().optional().describe('Specific env var key to check across apps'),
641
- }, async ({ apps, key }) => {
642
- return {
643
- messages: [
644
- {
645
- role: 'user',
646
- content: {
647
- type: 'text',
648
- text: `Please audit environment variables across my applications${apps ? ` (${apps})` : ''}${key ? ` focusing on the "${key}" variable` : ''}:
649
-
650
- 1. Run list_applications to get the list of apps
651
- 2. For ${apps ? 'the specified apps' : 'each application'}, run list_application_envs
652
- 3. Analyze the environment variables:
653
- ${key ? `- Check if "${key}" is set consistently across all apps` : '- Identify common variables that differ between apps'}
654
- - Flag any sensitive-looking values that might be exposed
655
- - Identify missing variables that exist in some apps but not others
656
- - Check for any empty or placeholder values
657
- 4. Provide a summary:
658
- - Table showing variable presence across apps
659
- - Recommendations for standardization
660
- - Any security concerns
661
-
662
- Start by listing the applications.`,
663
- },
664
- },
665
- ],
666
- };
441
+ // =========================================================================
442
+ // Private Keys (1 tool - consolidated)
443
+ // =========================================================================
444
+ this.tool('private_keys', 'Manage SSH keys: list/get/create/update/delete', {
445
+ action: z.enum(['list', 'get', 'create', 'update', 'delete']),
446
+ uuid: z.string().optional(),
447
+ name: z.string().optional(),
448
+ description: z.string().optional(),
449
+ private_key: z.string().optional(),
450
+ }, async ({ action, uuid, name, description, private_key }) => {
451
+ switch (action) {
452
+ case 'list':
453
+ return wrap(() => this.client.listPrivateKeys());
454
+ case 'get':
455
+ if (!uuid)
456
+ return { content: [{ type: 'text', text: 'Error: uuid required' }] };
457
+ return wrap(() => this.client.getPrivateKey(uuid));
458
+ case 'create':
459
+ if (!private_key)
460
+ return { content: [{ type: 'text', text: 'Error: private_key required' }] };
461
+ return wrap(() => this.client.createPrivateKey({
462
+ private_key,
463
+ name: name || 'unnamed-key',
464
+ description,
465
+ }));
466
+ case 'update':
467
+ if (!uuid)
468
+ return { content: [{ type: 'text', text: 'Error: uuid required' }] };
469
+ return wrap(() => this.client.updatePrivateKey(uuid, { name, description, private_key }));
470
+ case 'delete':
471
+ if (!uuid)
472
+ return { content: [{ type: 'text', text: 'Error: uuid required' }] };
473
+ return wrap(() => this.client.deletePrivateKey(uuid));
474
+ }
667
475
  });
668
- // -------------------------------------------------------------------------
669
- // backup-status: Check database backup status
670
- // -------------------------------------------------------------------------
671
- this.prompt('backup-status', 'Check backup status and history for all databases', {}, async () => {
672
- return {
673
- messages: [
674
- {
675
- role: 'user',
676
- content: {
677
- type: 'text',
678
- text: `Please check the backup status of all my databases:
679
-
680
- 1. Run list_databases to get all databases
681
- 2. For each database, run list_database_backups to check scheduled backups
682
- 3. For databases with backups configured, run list_backup_executions to check recent history
683
- 4. Report:
684
- - Databases WITHOUT any backup schedules (critical!)
685
- - Last successful backup for each database
686
- - Any failed backups in the last 7 days
687
- - Backup frequency and retention settings
688
- 5. Provide recommendations:
689
- - Which databases need backup configuration
690
- - Any backup schedules that seem too infrequent
691
- - Storage concerns if backups are piling up
692
-
693
- Start by listing all databases.`,
694
- },
695
- },
696
- ],
697
- };
476
+ // =========================================================================
477
+ // Database Backups (1 tool - consolidated)
478
+ // =========================================================================
479
+ this.tool('database_backups', 'Manage backups: list_schedules/get_schedule/list_executions/get_execution', {
480
+ action: z.enum(['list_schedules', 'get_schedule', 'list_executions', 'get_execution']),
481
+ database_uuid: z.string(),
482
+ backup_uuid: z.string().optional(),
483
+ execution_uuid: z.string().optional(),
484
+ }, async ({ action, database_uuid, backup_uuid, execution_uuid }) => {
485
+ switch (action) {
486
+ case 'list_schedules':
487
+ return wrap(() => this.client.listDatabaseBackups(database_uuid));
488
+ case 'get_schedule':
489
+ if (!backup_uuid)
490
+ return { content: [{ type: 'text', text: 'Error: backup_uuid required' }] };
491
+ return wrap(() => this.client.getDatabaseBackup(database_uuid, backup_uuid));
492
+ case 'list_executions':
493
+ if (!backup_uuid)
494
+ return { content: [{ type: 'text', text: 'Error: backup_uuid required' }] };
495
+ return wrap(() => this.client.listBackupExecutions(database_uuid, backup_uuid));
496
+ case 'get_execution':
497
+ if (!backup_uuid || !execution_uuid)
498
+ return {
499
+ content: [
500
+ { type: 'text', text: 'Error: backup_uuid, execution_uuid required' },
501
+ ],
502
+ };
503
+ return wrap(() => this.client.getBackupExecution(database_uuid, backup_uuid, execution_uuid));
504
+ }
505
+ });
506
+ // =========================================================================
507
+ // Batch Operations (4 tools)
508
+ // =========================================================================
509
+ this.tool('restart_project_apps', 'Restart all apps in project', { project_uuid: z.string() }, async ({ project_uuid }) => wrap(() => this.client.restartProjectApps(project_uuid)));
510
+ this.tool('bulk_env_update', 'Update env var across multiple apps', {
511
+ app_uuids: z.array(z.string()),
512
+ key: z.string(),
513
+ value: z.string(),
514
+ is_build_time: z.boolean().optional(),
515
+ }, async ({ app_uuids, key, value, is_build_time }) => wrap(() => this.client.bulkEnvUpdate(app_uuids, key, value, is_build_time)));
516
+ this.tool('stop_all_apps', 'EMERGENCY: Stop all running apps', { confirm: z.literal(true) }, async ({ confirm }) => {
517
+ if (!confirm)
518
+ return { content: [{ type: 'text', text: 'Error: confirm=true required' }] };
519
+ return wrap(() => this.client.stopAllApps());
698
520
  });
521
+ this.tool('redeploy_project', 'Redeploy all apps in project', { project_uuid: z.string(), force: z.boolean().optional() }, async ({ project_uuid, force }) => wrap(() => this.client.redeployProjectApps(project_uuid, force ?? true)));
699
522
  }
700
523
  }