@masonator/coolify-mcp 0.6.0 → 0.7.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.
package/README.md CHANGED
@@ -6,7 +6,7 @@ A Model Context Protocol (MCP) server for [Coolify](https://coolify.io/), enabli
6
6
 
7
7
  ## Features
8
8
 
9
- This MCP server provides 47 tools focused on **debugging, management, and deployment**:
9
+ This MCP server provides **58 tools** focused on **debugging, management, and deployment**:
10
10
 
11
11
  | Category | Tools |
12
12
  | ------------------ | -------------------------------------------------------------------------------------------------------- |
@@ -15,9 +15,10 @@ This MCP server provides 47 tools focused on **debugging, management, and deploy
15
15
  | **Projects** | list, get, create, update, delete |
16
16
  | **Environments** | list, get, create, delete |
17
17
  | **Applications** | list, get, update, delete, start, stop, restart, logs, env vars (CRUD), create (private-gh, private-key) |
18
- | **Databases** | list, get, start, stop, restart |
18
+ | **Databases** | list, get, start, stop, restart, backups (list, get), backup executions (list, get) |
19
19
  | **Services** | list, get, create, update, delete, start, stop, restart, env vars (list, create, delete) |
20
- | **Deployments** | list, get, deploy, list by application |
20
+ | **Deployments** | list, get, deploy, cancel, list by application |
21
+ | **Private Keys** | list, get, create, update, delete |
21
22
 
22
23
  ## Installation
23
24
 
@@ -222,6 +223,10 @@ node dist/index.js
222
223
  - `start_database` - Start a database
223
224
  - `stop_database` - Stop a database
224
225
  - `restart_database` - Restart a database
226
+ - `list_database_backups` - List scheduled backups for a database
227
+ - `get_database_backup` - Get details of a scheduled backup
228
+ - `list_backup_executions` - List execution history for a scheduled backup
229
+ - `get_backup_execution` - Get details of a specific backup execution
225
230
 
226
231
  ### Services
227
232
 
@@ -242,8 +247,17 @@ node dist/index.js
242
247
  - `list_deployments` - List running deployments (returns summary)
243
248
  - `get_deployment` - Get deployment details
244
249
  - `deploy` - Deploy by tag or UUID
250
+ - `cancel_deployment` - Cancel a running deployment
245
251
  - `list_application_deployments` - List deployments for an application
246
252
 
253
+ ### Private Keys
254
+
255
+ - `list_private_keys` - List all private keys (SSH keys for deployments)
256
+ - `get_private_key` - Get private key details
257
+ - `create_private_key` - Create a new private key for deployments
258
+ - `update_private_key` - Update a private key
259
+ - `delete_private_key` - Delete a private key
260
+
247
261
  ## Contributing
248
262
 
249
263
  Contributions welcome! Please open an issue first to discuss major changes.
@@ -745,14 +745,26 @@ describe('CoolifyClient', () => {
745
745
  expect(result).toEqual(mockBackups);
746
746
  expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/databases/db-uuid/backups', expect.any(Object));
747
747
  });
748
- it('should create a database backup', async () => {
749
- mockFetch.mockResolvedValueOnce(mockResponse({ uuid: 'new-backup-uuid', message: 'Backup created' }));
750
- const result = await client.createDatabaseBackup('db-uuid', {
751
- frequency: 'daily',
752
- backup_retention: 7,
753
- });
754
- expect(result).toEqual({ uuid: 'new-backup-uuid', message: 'Backup created' });
755
- expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/databases/db-uuid/backups', expect.objectContaining({ method: 'POST' }));
748
+ it('should get a database backup', async () => {
749
+ const mockBackup = { uuid: 'backup-uuid', enabled: true, frequency: '0 0 * * *' };
750
+ mockFetch.mockResolvedValueOnce(mockResponse(mockBackup));
751
+ const result = await client.getDatabaseBackup('db-uuid', 'backup-uuid');
752
+ expect(result).toEqual(mockBackup);
753
+ expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/databases/db-uuid/backups/backup-uuid', expect.any(Object));
754
+ });
755
+ it('should list backup executions', async () => {
756
+ const mockExecutions = [{ uuid: 'exec-uuid', status: 'success' }];
757
+ mockFetch.mockResolvedValueOnce(mockResponse(mockExecutions));
758
+ const result = await client.listBackupExecutions('db-uuid', 'backup-uuid');
759
+ expect(result).toEqual(mockExecutions);
760
+ expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/databases/db-uuid/backups/backup-uuid/executions', expect.any(Object));
761
+ });
762
+ it('should get a backup execution', async () => {
763
+ const mockExecution = { uuid: 'exec-uuid', status: 'success', size: 1024 };
764
+ mockFetch.mockResolvedValueOnce(mockResponse(mockExecution));
765
+ const result = await client.getBackupExecution('db-uuid', 'backup-uuid', 'exec-uuid');
766
+ expect(result).toEqual(mockExecution);
767
+ expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/databases/db-uuid/backups/backup-uuid/executions/exec-uuid', expect.any(Object));
756
768
  });
757
769
  });
758
770
  // =========================================================================
@@ -1022,4 +1034,71 @@ describe('CoolifyClient', () => {
1022
1034
  expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/deploy?tag=my-tag&force=false', expect.any(Object));
1023
1035
  });
1024
1036
  });
1037
+ // ===========================================================================
1038
+ // Database Backup Tests
1039
+ // ===========================================================================
1040
+ describe('Database Backups', () => {
1041
+ const mockBackups = [
1042
+ {
1043
+ id: 1,
1044
+ uuid: 'backup-uuid-1',
1045
+ database_id: 1,
1046
+ database_type: 'postgresql',
1047
+ database_uuid: 'db-uuid',
1048
+ enabled: true,
1049
+ frequency: '0 0 * * *',
1050
+ save_s3: false,
1051
+ created_at: '2024-01-01',
1052
+ updated_at: '2024-01-01',
1053
+ },
1054
+ ];
1055
+ const mockExecutions = [
1056
+ {
1057
+ id: 1,
1058
+ uuid: 'exec-uuid-1',
1059
+ scheduled_database_backup_id: 1,
1060
+ status: 'success',
1061
+ message: 'Backup completed',
1062
+ size: 1024,
1063
+ filename: 'backup-20240101.sql',
1064
+ created_at: '2024-01-01',
1065
+ updated_at: '2024-01-01',
1066
+ },
1067
+ ];
1068
+ it('should list database backups', async () => {
1069
+ mockFetch.mockResolvedValueOnce(mockResponse(mockBackups));
1070
+ const result = await client.listDatabaseBackups('db-uuid');
1071
+ expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/databases/db-uuid/backups', expect.any(Object));
1072
+ expect(result).toEqual(mockBackups);
1073
+ });
1074
+ it('should get a specific database backup', async () => {
1075
+ mockFetch.mockResolvedValueOnce(mockResponse(mockBackups[0]));
1076
+ const result = await client.getDatabaseBackup('db-uuid', 'backup-uuid-1');
1077
+ expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/databases/db-uuid/backups/backup-uuid-1', expect.any(Object));
1078
+ expect(result).toEqual(mockBackups[0]);
1079
+ });
1080
+ it('should list backup executions', async () => {
1081
+ mockFetch.mockResolvedValueOnce(mockResponse(mockExecutions));
1082
+ const result = await client.listBackupExecutions('db-uuid', 'backup-uuid-1');
1083
+ expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/databases/db-uuid/backups/backup-uuid-1/executions', expect.any(Object));
1084
+ expect(result).toEqual(mockExecutions);
1085
+ });
1086
+ it('should get a specific backup execution', async () => {
1087
+ mockFetch.mockResolvedValueOnce(mockResponse(mockExecutions[0]));
1088
+ const result = await client.getBackupExecution('db-uuid', 'backup-uuid-1', 'exec-uuid-1');
1089
+ expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/databases/db-uuid/backups/backup-uuid-1/executions/exec-uuid-1', expect.any(Object));
1090
+ expect(result).toEqual(mockExecutions[0]);
1091
+ });
1092
+ });
1093
+ // ===========================================================================
1094
+ // Deployment Control Tests
1095
+ // ===========================================================================
1096
+ describe('Deployment Control', () => {
1097
+ it('should cancel a deployment', async () => {
1098
+ mockFetch.mockResolvedValueOnce(mockResponse({ message: 'Deployment cancelled' }));
1099
+ const result = await client.cancelDeployment('deploy-uuid');
1100
+ expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/deployments/deploy-uuid/cancel', expect.objectContaining({ method: 'POST' }));
1101
+ expect(result).toEqual({ message: 'Deployment cancelled' });
1102
+ });
1103
+ });
1025
1104
  });
@@ -2,7 +2,7 @@
2
2
  * Coolify API Client
3
3
  * Complete HTTP client for the Coolify API v1
4
4
  */
5
- import type { CoolifyConfig, DeleteOptions, MessageResponse, UuidResponse, Server, ServerResource, ServerDomain, ServerValidation, CreateServerRequest, UpdateServerRequest, Project, CreateProjectRequest, UpdateProjectRequest, Environment, CreateEnvironmentRequest, Application, CreateApplicationPublicRequest, CreateApplicationPrivateGHRequest, CreateApplicationPrivateKeyRequest, CreateApplicationDockerfileRequest, CreateApplicationDockerImageRequest, CreateApplicationDockerComposeRequest, UpdateApplicationRequest, ApplicationActionResponse, EnvironmentVariable, CreateEnvVarRequest, UpdateEnvVarRequest, BulkUpdateEnvVarsRequest, Database, UpdateDatabaseRequest, DatabaseBackup, CreateDatabaseBackupRequest, Service, CreateServiceRequest, UpdateServiceRequest, ServiceCreateResponse, Deployment, Team, TeamMember, PrivateKey, CreatePrivateKeyRequest, UpdatePrivateKeyRequest, CloudToken, CreateCloudTokenRequest, UpdateCloudTokenRequest, CloudTokenValidation, Version } from '../types/coolify.js';
5
+ import type { CoolifyConfig, DeleteOptions, MessageResponse, UuidResponse, Server, ServerResource, ServerDomain, ServerValidation, CreateServerRequest, UpdateServerRequest, Project, CreateProjectRequest, UpdateProjectRequest, Environment, CreateEnvironmentRequest, Application, CreateApplicationPublicRequest, CreateApplicationPrivateGHRequest, CreateApplicationPrivateKeyRequest, CreateApplicationDockerfileRequest, CreateApplicationDockerImageRequest, CreateApplicationDockerComposeRequest, UpdateApplicationRequest, ApplicationActionResponse, EnvironmentVariable, CreateEnvVarRequest, UpdateEnvVarRequest, BulkUpdateEnvVarsRequest, Database, UpdateDatabaseRequest, DatabaseBackup, BackupExecution, Service, CreateServiceRequest, UpdateServiceRequest, ServiceCreateResponse, Deployment, Team, TeamMember, PrivateKey, CreatePrivateKeyRequest, UpdatePrivateKeyRequest, CloudToken, CreateCloudTokenRequest, UpdateCloudTokenRequest, CloudTokenValidation, Version } from '../types/coolify.js';
6
6
  export interface ListOptions {
7
7
  page?: number;
8
8
  per_page?: number;
@@ -112,8 +112,6 @@ export declare class CoolifyClient {
112
112
  startDatabase(uuid: string): Promise<MessageResponse>;
113
113
  stopDatabase(uuid: string): Promise<MessageResponse>;
114
114
  restartDatabase(uuid: string): Promise<MessageResponse>;
115
- listDatabaseBackups(uuid: string): Promise<DatabaseBackup[]>;
116
- createDatabaseBackup(uuid: string, data: CreateDatabaseBackupRequest): Promise<UuidResponse & MessageResponse>;
117
115
  listServices(options?: ListOptions): Promise<Service[] | ServiceSummary[]>;
118
116
  getService(uuid: string): Promise<Service>;
119
117
  createService(data: CreateServiceRequest): Promise<ServiceCreateResponse>;
@@ -146,4 +144,9 @@ export declare class CoolifyClient {
146
144
  updateCloudToken(uuid: string, data: UpdateCloudTokenRequest): Promise<CloudToken>;
147
145
  deleteCloudToken(uuid: string): Promise<MessageResponse>;
148
146
  validateCloudToken(uuid: string): Promise<CloudTokenValidation>;
147
+ listDatabaseBackups(databaseUuid: string): Promise<DatabaseBackup[]>;
148
+ getDatabaseBackup(databaseUuid: string, backupUuid: string): Promise<DatabaseBackup>;
149
+ listBackupExecutions(databaseUuid: string, backupUuid: string): Promise<BackupExecution[]>;
150
+ getBackupExecution(databaseUuid: string, backupUuid: string, executionUuid: string): Promise<BackupExecution>;
151
+ cancelDeployment(uuid: string): Promise<MessageResponse>;
149
152
  }
@@ -3,13 +3,13 @@
3
3
  * Complete HTTP client for the Coolify API v1
4
4
  */
5
5
  /**
6
- * Remove undefined values and false booleans from an object.
7
- * Coolify API rejects requests with explicit false values for optional booleans.
6
+ * Remove undefined values from an object.
7
+ * Keeps explicit false values so features like HTTP Basic Auth can be disabled.
8
8
  */
9
9
  function cleanRequestData(data) {
10
10
  const cleaned = {};
11
11
  for (const [key, value] of Object.entries(data)) {
12
- if (value !== undefined && value !== false) {
12
+ if (value !== undefined) {
13
13
  cleaned[key] = value;
14
14
  }
15
15
  }
@@ -406,18 +406,6 @@ export class CoolifyClient {
406
406
  });
407
407
  }
408
408
  // ===========================================================================
409
- // Database Backups
410
- // ===========================================================================
411
- async listDatabaseBackups(uuid) {
412
- return this.request(`/databases/${uuid}/backups`);
413
- }
414
- async createDatabaseBackup(uuid, data) {
415
- return this.request(`/databases/${uuid}/backups`, {
416
- method: 'POST',
417
- body: JSON.stringify(data),
418
- });
419
- }
420
- // ===========================================================================
421
409
  // Service endpoints
422
410
  // ===========================================================================
423
411
  async listServices(options) {
@@ -587,4 +575,27 @@ export class CoolifyClient {
587
575
  async validateCloudToken(uuid) {
588
576
  return this.request(`/cloud-tokens/${uuid}/validate`, { method: 'POST' });
589
577
  }
578
+ // ===========================================================================
579
+ // Database Backup endpoints
580
+ // ===========================================================================
581
+ async listDatabaseBackups(databaseUuid) {
582
+ return this.request(`/databases/${databaseUuid}/backups`);
583
+ }
584
+ async getDatabaseBackup(databaseUuid, backupUuid) {
585
+ return this.request(`/databases/${databaseUuid}/backups/${backupUuid}`);
586
+ }
587
+ async listBackupExecutions(databaseUuid, backupUuid) {
588
+ return this.request(`/databases/${databaseUuid}/backups/${backupUuid}/executions`);
589
+ }
590
+ async getBackupExecution(databaseUuid, backupUuid, executionUuid) {
591
+ return this.request(`/databases/${databaseUuid}/backups/${backupUuid}/executions/${executionUuid}`);
592
+ }
593
+ // ===========================================================================
594
+ // Deployment Control endpoints
595
+ // ===========================================================================
596
+ async cancelDeployment(uuid) {
597
+ return this.request(`/deployments/${uuid}/cancel`, {
598
+ method: 'POST',
599
+ });
600
+ }
590
601
  }
@@ -20,7 +20,7 @@
20
20
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
21
21
  import { z } from 'zod';
22
22
  import { CoolifyClient, } from './coolify-client.js';
23
- const VERSION = '0.6.0';
23
+ const VERSION = '0.7.0';
24
24
  /** Wrap tool handler with consistent error handling */
25
25
  function wrapHandler(fn) {
26
26
  return fn()
@@ -306,5 +306,40 @@ export class CoolifyMcpServer extends McpServer {
306
306
  force: z.boolean().optional().describe('Force rebuild'),
307
307
  }, async ({ tag_or_uuid, force }) => wrapHandler(() => this.client.deployByTagOrUuid(tag_or_uuid, force)));
308
308
  this.tool('list_application_deployments', 'List deployments for an application', { uuid: z.string().describe('Application UUID') }, async ({ uuid }) => wrapHandler(() => this.client.listApplicationDeployments(uuid)));
309
+ this.tool('cancel_deployment', 'Cancel a running deployment', { uuid: z.string().describe('Deployment UUID') }, async ({ uuid }) => wrapHandler(() => this.client.cancelDeployment(uuid)));
310
+ // =========================================================================
311
+ // Private Keys (5 tools)
312
+ // =========================================================================
313
+ this.tool('list_private_keys', 'List all private keys (SSH keys for deployments)', {}, async () => wrapHandler(() => this.client.listPrivateKeys()));
314
+ this.tool('get_private_key', 'Get private key details', { uuid: z.string().describe('Private key UUID') }, async ({ uuid }) => wrapHandler(() => this.client.getPrivateKey(uuid)));
315
+ this.tool('create_private_key', 'Create a new private key for deployments', {
316
+ private_key: z.string().describe('The private key content (PEM format)'),
317
+ name: z.string().optional().describe('Name for the key'),
318
+ description: z.string().optional().describe('Description'),
319
+ }, async (args) => wrapHandler(() => this.client.createPrivateKey(args)));
320
+ this.tool('update_private_key', 'Update a private key', {
321
+ uuid: z.string().describe('Private key UUID'),
322
+ name: z.string().optional().describe('Name for the key'),
323
+ description: z.string().optional().describe('Description'),
324
+ private_key: z.string().optional().describe('The private key content (PEM format)'),
325
+ }, async ({ uuid, ...data }) => wrapHandler(() => this.client.updatePrivateKey(uuid, data)));
326
+ this.tool('delete_private_key', 'Delete a private key', { uuid: z.string().describe('Private key UUID') }, async ({ uuid }) => wrapHandler(() => this.client.deletePrivateKey(uuid)));
327
+ // =========================================================================
328
+ // Database Backups (4 tools)
329
+ // =========================================================================
330
+ this.tool('list_database_backups', 'List scheduled backups for a database', { uuid: z.string().describe('Database UUID') }, async ({ uuid }) => wrapHandler(() => this.client.listDatabaseBackups(uuid)));
331
+ this.tool('get_database_backup', 'Get details of a scheduled backup', {
332
+ database_uuid: z.string().describe('Database UUID'),
333
+ backup_uuid: z.string().describe('Scheduled backup UUID'),
334
+ }, async ({ database_uuid, backup_uuid }) => wrapHandler(() => this.client.getDatabaseBackup(database_uuid, backup_uuid)));
335
+ this.tool('list_backup_executions', 'List execution history for a scheduled backup', {
336
+ database_uuid: z.string().describe('Database UUID'),
337
+ backup_uuid: z.string().describe('Scheduled backup UUID'),
338
+ }, async ({ database_uuid, backup_uuid }) => wrapHandler(() => this.client.listBackupExecutions(database_uuid, backup_uuid)));
339
+ this.tool('get_backup_execution', 'Get details of a specific backup execution', {
340
+ database_uuid: z.string().describe('Database UUID'),
341
+ backup_uuid: z.string().describe('Scheduled backup UUID'),
342
+ execution_uuid: z.string().describe('Backup execution UUID'),
343
+ }, async ({ database_uuid, backup_uuid, execution_uuid }) => wrapHandler(() => this.client.getBackupExecution(database_uuid, backup_uuid, execution_uuid)));
309
344
  }
310
345
  }
@@ -300,6 +300,9 @@ export interface UpdateApplicationRequest {
300
300
  limits_memory?: string;
301
301
  limits_memory_swap?: string;
302
302
  limits_cpus?: string;
303
+ is_http_basic_auth_enabled?: boolean;
304
+ http_basic_auth_username?: string;
305
+ http_basic_auth_password?: string;
303
306
  }
304
307
  export interface ApplicationActionResponse {
305
308
  message: string;
@@ -470,6 +473,17 @@ export interface CreateDatabaseBackupRequest {
470
473
  backup_retention?: number;
471
474
  backup_retention_days?: number;
472
475
  }
476
+ export interface BackupExecution {
477
+ id: number;
478
+ uuid: string;
479
+ scheduled_database_backup_id: number;
480
+ status: 'pending' | 'running' | 'success' | 'failed';
481
+ message?: string;
482
+ size?: number;
483
+ filename?: string;
484
+ created_at: string;
485
+ updated_at: string;
486
+ }
473
487
  /**
474
488
  * Available one-click service types in Coolify.
475
489
  * This is a string type to avoid TypeScript memory issues with large const arrays.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@masonator/coolify-mcp",
3
3
  "scope": "@masonator",
4
- "version": "0.6.0",
4
+ "version": "0.7.0",
5
5
  "description": "MCP server implementation for Coolify",
6
6
  "type": "module",
7
7
  "main": "./dist/index.js",
@@ -61,5 +61,9 @@
61
61
  },
62
62
  "engines": {
63
63
  "node": ">=18"
64
+ },
65
+ "lint-staged": {
66
+ "*.{ts,js,json,md,yaml,yml}": "prettier --write",
67
+ "*.ts": "eslint --fix"
64
68
  }
65
69
  }