@masonator/coolify-mcp 1.5.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.5.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,524 +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 (6 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 })));
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)));
239
229
  // =========================================================================
240
- // Services (11 tools)
230
+ // Databases (3 tools)
241
231
  // =========================================================================
242
- this.tool('list_services', 'List all services (returns summary: uuid, name, type, status, domains). Use get_service for full details.', {
243
- page: z.number().optional().describe('Page number for pagination'),
244
- per_page: z.number().optional().describe('Items per page (default: all)'),
245
- }, async ({ page, per_page }) => wrapHandler(() => this.client.listServices({ page, per_page, summary: true })));
246
- this.tool('get_service', 'Get service details', { uuid: z.string().describe('Service UUID') }, async ({ uuid }) => wrapHandler(() => this.client.getService(uuid)));
247
- 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']),
248
236
  type: z
249
- .string()
250
- .optional()
251
- .describe('Service type (e.g., pocketbase, mysql, redis, postgresql, mongodb, wordpress, etc.)'),
252
- server_uuid: z.string().describe('Server UUID'),
253
- project_uuid: z.string().describe('Project UUID'),
254
- environment_name: z.string().optional().describe('Environment name (e.g., production)'),
255
- environment_uuid: z
256
- .string()
257
- .optional()
258
- .describe('Environment UUID (alternative to environment_name)'),
259
- name: z.string().optional().describe('Service name'),
260
- description: z.string().optional().describe('Service description'),
261
- destination_uuid: z.string().optional().describe('Destination UUID'),
262
- instant_deploy: z.boolean().optional().describe('Deploy immediately after creation'),
263
- docker_compose_raw: z
264
- .string()
265
- .optional()
266
- .describe('Base64 encoded docker-compose YAML with SERVICE_FQDN_* env var for custom domain (alternative to type)'),
267
- }, async (args) => wrapHandler(() => this.client.createService(args)));
268
- this.tool('delete_service', 'Delete a service', {
269
- uuid: z.string().describe('Service UUID'),
270
- delete_configurations: z
271
- .boolean()
272
- .optional()
273
- .describe('Delete configurations (default: true)'),
274
- delete_volumes: z.boolean().optional().describe('Delete volumes (default: true)'),
275
- docker_cleanup: z
276
- .boolean()
277
- .optional()
278
- .describe('Clean up Docker resources (default: true)'),
279
- delete_connected_networks: z
280
- .boolean()
281
- .optional()
282
- .describe('Delete connected networks (default: true)'),
283
- }, async ({ uuid, delete_configurations, delete_volumes, docker_cleanup, delete_connected_networks, }) => wrapHandler(() => this.client.deleteService(uuid, {
284
- deleteConfigurations: delete_configurations,
285
- deleteVolumes: delete_volumes,
286
- dockerCleanup: docker_cleanup,
287
- deleteConnectedNetworks: delete_connected_networks,
288
- })));
289
- this.tool('update_service', 'Update a service (IMPORTANT: See UpdateServiceRequest type docs for Traefik basic auth requirements)', {
290
- uuid: z.string().describe('Service UUID'),
291
- name: z.string().optional().describe('Service name'),
292
- description: z.string().optional().describe('Description'),
293
- docker_compose_raw: z
294
- .string()
295
- .optional()
296
- .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 $$.'),
297
- }, async ({ uuid, ...data }) => wrapHandler(() => this.client.updateService(uuid, data)));
298
- this.tool('start_service', 'Start a service', { uuid: z.string().describe('Service UUID') }, async ({ uuid }) => wrapHandler(() => this.client.startService(uuid)));
299
- this.tool('stop_service', 'Stop a service', { uuid: z.string().describe('Service UUID') }, async ({ uuid }) => wrapHandler(() => this.client.stopService(uuid)));
300
- this.tool('restart_service', 'Restart a service', { uuid: z.string().describe('Service UUID') }, async ({ uuid }) => wrapHandler(() => this.client.restartService(uuid)));
301
- // Service env vars
302
- this.tool('list_service_envs', 'List service environment variables', { uuid: z.string().describe('Service UUID') }, async ({ uuid }) => wrapHandler(() => this.client.listServiceEnvVars(uuid)));
303
- this.tool('create_service_env', 'Create service environment variable', {
304
- uuid: z.string().describe('Service UUID'),
305
- key: z.string().describe('Variable key'),
306
- value: z.string().describe('Variable value'),
307
- }, async ({ uuid, ...data }) => wrapHandler(() => this.client.createServiceEnvVar(uuid, data)));
308
- this.tool('delete_service_env', 'Delete service environment variable', {
309
- uuid: z.string().describe('Service UUID'),
310
- env_uuid: z.string().describe('Env variable UUID'),
311
- }, async ({ uuid, env_uuid }) => wrapHandler(() => this.client.deleteServiceEnvVar(uuid, env_uuid)));
312
- // =========================================================================
313
- // Deployments (4 tools)
314
- // =========================================================================
315
- this.tool('list_deployments', 'List running deployments (returns summary: uuid, deployment_uuid, application_name, status). Use get_deployment for full details.', {
316
- page: z.number().optional().describe('Page number for pagination'),
317
- per_page: z.number().optional().describe('Items per page (default: all)'),
318
- }, async ({ page, per_page }) => wrapHandler(() => this.client.listDeployments({ page, per_page, summary: true })));
319
- this.tool('get_deployment', 'Get deployment details', { uuid: z.string().describe('Deployment UUID') }, async ({ uuid }) => wrapHandler(() => this.client.getDeployment(uuid)));
320
- this.tool('deploy', 'Deploy by tag or UUID', {
321
- tag_or_uuid: z.string().describe('Tag or UUID'),
322
- force: z.boolean().optional().describe('Force rebuild'),
323
- }, async ({ tag_or_uuid, force }) => wrapHandler(() => this.client.deployByTagOrUuid(tag_or_uuid, force)));
324
- this.tool('list_application_deployments', 'List deployments for an application', { uuid: z.string().describe('Application UUID') }, async ({ uuid }) => wrapHandler(() => this.client.listApplicationDeployments(uuid)));
325
- this.tool('cancel_deployment', 'Cancel a running deployment', { uuid: z.string().describe('Deployment UUID') }, async ({ uuid }) => wrapHandler(() => this.client.cancelDeployment(uuid)));
326
- // =========================================================================
327
- // Private Keys (5 tools)
328
- // =========================================================================
329
- this.tool('list_private_keys', 'List all private keys (SSH keys for deployments)', {}, async () => wrapHandler(() => this.client.listPrivateKeys()));
330
- this.tool('get_private_key', 'Get private key details', { uuid: z.string().describe('Private key UUID') }, async ({ uuid }) => wrapHandler(() => this.client.getPrivateKey(uuid)));
331
- this.tool('create_private_key', 'Create a new private key for deployments', {
332
- private_key: z.string().describe('The private key content (PEM format)'),
333
- name: z.string().optional().describe('Name for the key'),
334
- description: z.string().optional().describe('Description'),
335
- }, async (args) => wrapHandler(() => this.client.createPrivateKey(args)));
336
- this.tool('update_private_key', 'Update a private key', {
337
- uuid: z.string().describe('Private key UUID'),
338
- name: z.string().optional().describe('Name for the key'),
339
- description: z.string().optional().describe('Description'),
340
- private_key: z.string().optional().describe('The private key content (PEM format)'),
341
- }, async ({ uuid, ...data }) => wrapHandler(() => this.client.updatePrivateKey(uuid, data)));
342
- this.tool('delete_private_key', 'Delete a private key', { uuid: z.string().describe('Private key UUID') }, async ({ uuid }) => wrapHandler(() => this.client.deletePrivateKey(uuid)));
343
- // =========================================================================
344
- // Database Backups (4 tools)
345
- // =========================================================================
346
- this.tool('list_database_backups', 'List scheduled backups for a database', { uuid: z.string().describe('Database UUID') }, async ({ uuid }) => wrapHandler(() => this.client.listDatabaseBackups(uuid)));
347
- this.tool('get_database_backup', 'Get details of a scheduled backup', {
348
- database_uuid: z.string().describe('Database UUID'),
349
- backup_uuid: z.string().describe('Scheduled backup UUID'),
350
- }, async ({ database_uuid, backup_uuid }) => wrapHandler(() => this.client.getDatabaseBackup(database_uuid, backup_uuid)));
351
- this.tool('list_backup_executions', 'List execution history for a scheduled backup', {
352
- database_uuid: z.string().describe('Database UUID'),
353
- backup_uuid: z.string().describe('Scheduled backup UUID'),
354
- }, async ({ database_uuid, backup_uuid }) => wrapHandler(() => this.client.listBackupExecutions(database_uuid, backup_uuid)));
355
- this.tool('get_backup_execution', 'Get details of a specific backup execution', {
356
- database_uuid: z.string().describe('Database UUID'),
357
- backup_uuid: z.string().describe('Scheduled backup UUID'),
358
- execution_uuid: z.string().describe('Backup execution UUID'),
359
- }, async ({ database_uuid, backup_uuid, execution_uuid }) => wrapHandler(() => this.client.getBackupExecution(database_uuid, backup_uuid, execution_uuid)));
360
- // =========================================================================
361
- // Diagnostics (3 tools) - Composite tools for debugging
362
- // =========================================================================
363
- 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)));
364
- 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)));
365
- 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()));
366
- // =========================================================================
367
- // Batch Operations (4 tools) - Operate on multiple resources at once
368
- // =========================================================================
369
- 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)));
370
- this.tool('bulk_env_update', 'Update or create an environment variable across multiple applications (upsert behavior). Returns summary of succeeded/failed updates.', {
371
- app_uuids: z.array(z.string()).describe('Array of application UUIDs'),
372
- key: z.string().describe('Environment variable key'),
373
- value: z.string().describe('Environment variable value'),
374
- is_build_time: z.boolean().optional().describe('Build-time variable (default: false)'),
375
- }, async ({ app_uuids, key, value, is_build_time }) => wrapHandler(() => this.client.bulkEnvUpdate(app_uuids, key, value, is_build_time)));
376
- 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!', {
377
- confirm: z.literal(true).describe('Must be true to confirm this dangerous operation'),
378
- }, async ({ confirm }) => {
379
- 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) {
380
288
  return {
381
289
  content: [
382
- { type: 'text', text: 'Error: Must set confirm=true to stop all apps' },
290
+ { type: 'text', text: 'Error: type, server_uuid, project_uuid required' },
383
291
  ],
384
292
  };
385
293
  }
386
- return wrapHandler(() => this.client.stopAllApps());
387
- });
388
- this.tool('redeploy_project', 'Redeploy all applications in a project with force rebuild. Returns summary of succeeded/failed deployments.', {
389
- project_uuid: z.string().describe('Project UUID'),
390
- force: z.boolean().optional().describe('Force rebuild (default: true)'),
391
- }, async ({ project_uuid, force }) => wrapHandler(() => this.client.redeployProjectApps(project_uuid, force ?? true)));
392
- }
393
- // ===========================================================================
394
- // Prompt Templates - Guided Workflows
395
- // ===========================================================================
396
- registerPrompts() {
397
- // -------------------------------------------------------------------------
398
- // debug-app: Comprehensive application debugging workflow
399
- // -------------------------------------------------------------------------
400
- this.prompt('debug-app', 'Debug an application - gathers status, logs, env vars, and recent deployments to diagnose issues', {
401
- query: z
402
- .string()
403
- .describe('Application identifier: UUID, name, or domain (e.g., "my-app" or "example.com")'),
404
- }, async ({ query }) => {
405
- return {
406
- messages: [
407
- {
408
- role: 'user',
409
- content: {
410
- type: 'text',
411
- text: `I need help debugging my Coolify application "${query}". Please:
412
-
413
- 1. First, use the diagnose_app tool with query="${query}" to get comprehensive diagnostics
414
- 2. Analyze the results and identify:
415
- - Current health status and any issues
416
- - Recent deployment failures or errors in logs
417
- - Missing or misconfigured environment variables
418
- - Any patterns suggesting the root cause
419
- 3. Provide a clear diagnosis with:
420
- - What's wrong (if anything)
421
- - Likely root cause
422
- - Recommended fix steps
423
- 4. If the app seems healthy, confirm this and suggest any optimizations
424
-
425
- Start by running diagnose_app now.`,
426
- },
427
- },
428
- ],
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),
429
303
  };
304
+ return wrap(() => dbMethods[type](args));
430
305
  });
431
- // -------------------------------------------------------------------------
432
- // health-check: Full infrastructure health analysis
433
- // -------------------------------------------------------------------------
434
- this.prompt('health-check', 'Perform a comprehensive health check of your entire Coolify infrastructure', {}, async () => {
435
- return {
436
- messages: [
437
- {
438
- role: 'user',
439
- content: {
440
- type: 'text',
441
- text: `Please perform a comprehensive health check of my Coolify infrastructure:
442
-
443
- 1. Run find_issues to scan for problems across all servers, apps, databases, and services
444
- 2. Run get_infrastructure_overview to get the full picture
445
- 3. For any issues found, provide:
446
- - Severity (critical/warning/info)
447
- - Affected resource and current status
448
- - Recommended remediation steps
449
- 4. Summarize the overall health:
450
- - Total resources and their states
451
- - Any immediate actions needed
452
- - Preventive recommendations
453
-
454
- Start by running find_issues now.`,
455
- },
456
- },
457
- ],
458
- };
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
+ }
459
344
  });
460
- // -------------------------------------------------------------------------
461
- // deploy-app: Step-by-step deployment wizard
462
- // -------------------------------------------------------------------------
463
- this.prompt('deploy-app', 'Step-by-step wizard to deploy a new application from a Git repository', {
464
- repo: z.string().describe('Git repository URL or org/repo format'),
465
- branch: z.string().optional().describe('Branch to deploy (default: main)'),
466
- }, async ({ repo, branch }) => {
467
- const branchName = branch || 'main';
468
- return {
469
- messages: [
470
- {
471
- role: 'user',
472
- content: {
473
- type: 'text',
474
- text: `I want to deploy a new application from ${repo} (branch: ${branchName}). Please guide me through the process:
475
-
476
- 1. First, run list_projects to show available projects
477
- 2. Ask me which project to deploy to (or help create a new one)
478
- 3. Run list_servers to show available servers
479
- 4. Ask me which server to deploy on
480
- 5. Run list_private_keys to check available deploy keys
481
- 6. Based on the repository type:
482
- - If GitHub and we have a GitHub App configured, use create_application_private_gh
483
- - Otherwise, help set up a deploy key and use create_application_private_key
484
- 7. After creation, ask about:
485
- - Environment variables needed
486
- - Domain/FQDN configuration
487
- - Whether to deploy immediately
488
-
489
- Start by showing me the available projects.`,
490
- },
491
- },
492
- ],
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
+ },
493
369
  };
370
+ return wrap(() => methods[resource][action](uuid));
494
371
  });
495
- // -------------------------------------------------------------------------
496
- // troubleshoot-ssl: SSL certificate diagnosis workflow
497
- // -------------------------------------------------------------------------
498
- this.prompt('troubleshoot-ssl', 'Diagnose SSL/TLS certificate issues for a domain', {
499
- domain: z.string().describe('Domain having SSL issues (e.g., "example.com")'),
500
- }, async ({ domain }) => {
501
- return {
502
- messages: [
503
- {
504
- role: 'user',
505
- content: {
506
- type: 'text',
507
- text: `I'm having SSL/TLS certificate issues with the domain "${domain}". Please help me diagnose:
508
-
509
- 1. First, use diagnose_app with query="${domain}" to find the application
510
- 2. Check the application's FQDN configuration
511
- 3. Look for common SSL issues:
512
- - Is the domain correctly configured in the FQDN field?
513
- - Are there any proxy/redirect issues in the logs?
514
- - Is Let's Encrypt renewal working (check for ACME errors)?
515
- 4. Check the server's domain configuration using get_server_domains
516
- 5. Provide remediation steps:
517
- - If domain misconfiguration: show how to fix with update_application
518
- - If SSL renewal issue: suggest checking DNS and Traefik config
519
- - If proxy issue: suggest checking Traefik labels
520
-
521
- Start by finding the application for this domain.`,
522
- },
523
- },
524
- ],
525
- };
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
+ }
526
422
  });
527
- // -------------------------------------------------------------------------
528
- // restart-project: Safely restart all apps in a project
529
- // -------------------------------------------------------------------------
530
- this.prompt('restart-project', 'Safely restart all applications in a project with status monitoring', {
531
- project: z.string().describe('Project UUID or name'),
532
- }, async ({ project }) => {
533
- return {
534
- messages: [
535
- {
536
- role: 'user',
537
- content: {
538
- type: 'text',
539
- text: `I need to restart all applications in the project "${project}". Please handle this safely:
540
-
541
- 1. First, run list_projects to find the project UUID (if a name was given)
542
- 2. Run get_project to confirm the project details and list its environments
543
- 3. Run list_applications to find all apps in this project
544
- 4. Show me a summary of what will be restarted:
545
- - List each application with current status
546
- - Warn about any that are already unhealthy
547
- 5. Ask for my confirmation before proceeding
548
- 6. If confirmed, run restart_project_apps with the project UUID
549
- 7. After restart, check the results and report:
550
- - Which apps restarted successfully
551
- - Any failures and why
552
- - Current status of each app
553
-
554
- Start by finding the project.`,
555
- },
556
- },
557
- ],
558
- };
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
+ }
559
440
  });
560
- // -------------------------------------------------------------------------
561
- // env-audit: Audit environment variables across apps
562
- // -------------------------------------------------------------------------
563
- this.prompt('env-audit', 'Audit and compare environment variables across applications', {
564
- apps: z
565
- .string()
566
- .optional()
567
- .describe('Comma-separated app names/UUIDs to audit (optional, defaults to all)'),
568
- key: z.string().optional().describe('Specific env var key to check across apps'),
569
- }, async ({ apps, key }) => {
570
- return {
571
- messages: [
572
- {
573
- role: 'user',
574
- content: {
575
- type: 'text',
576
- text: `Please audit environment variables across my applications${apps ? ` (${apps})` : ''}${key ? ` focusing on the "${key}" variable` : ''}:
577
-
578
- 1. Run list_applications to get the list of apps
579
- 2. For ${apps ? 'the specified apps' : 'each application'}, run list_application_envs
580
- 3. Analyze the environment variables:
581
- ${key ? `- Check if "${key}" is set consistently across all apps` : '- Identify common variables that differ between apps'}
582
- - Flag any sensitive-looking values that might be exposed
583
- - Identify missing variables that exist in some apps but not others
584
- - Check for any empty or placeholder values
585
- 4. Provide a summary:
586
- - Table showing variable presence across apps
587
- - Recommendations for standardization
588
- - Any security concerns
589
-
590
- Start by listing the applications.`,
591
- },
592
- },
593
- ],
594
- };
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
+ }
595
475
  });
596
- // -------------------------------------------------------------------------
597
- // backup-status: Check database backup status
598
- // -------------------------------------------------------------------------
599
- this.prompt('backup-status', 'Check backup status and history for all databases', {}, async () => {
600
- return {
601
- messages: [
602
- {
603
- role: 'user',
604
- content: {
605
- type: 'text',
606
- text: `Please check the backup status of all my databases:
607
-
608
- 1. Run list_databases to get all databases
609
- 2. For each database, run list_database_backups to check scheduled backups
610
- 3. For databases with backups configured, run list_backup_executions to check recent history
611
- 4. Report:
612
- - Databases WITHOUT any backup schedules (critical!)
613
- - Last successful backup for each database
614
- - Any failed backups in the last 7 days
615
- - Backup frequency and retention settings
616
- 5. Provide recommendations:
617
- - Which databases need backup configuration
618
- - Any backup schedules that seem too infrequent
619
- - Storage concerns if backups are piling up
620
-
621
- Start by listing all databases.`,
622
- },
623
- },
624
- ],
625
- };
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());
626
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)));
627
522
  }
628
523
  }