@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.
- package/README.md +22 -30
- package/dist/__tests__/coolify-client.test.js +81 -0
- package/dist/__tests__/mcp-server.test.js +115 -316
- package/dist/lib/coolify-client.d.ts +9 -1
- package/dist/lib/coolify-client.js +49 -0
- package/dist/lib/mcp-server.d.ts +2 -19
- package/dist/lib/mcp-server.js +436 -541
- package/dist/types/coolify.d.ts +66 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,41 +9,33 @@
|
|
|
9
9
|
[](https://codecov.io/gh/StuMason/coolify-mcp)
|
|
10
10
|
[](https://mseep.ai/app/stumason-coolify-mcp)
|
|
11
11
|
|
|
12
|
-
> **The most comprehensive MCP server for Coolify** -
|
|
12
|
+
> **The most comprehensive MCP server for Coolify** - 34 optimized tools, smart diagnostics, and batch operations for managing your self-hosted PaaS through AI assistants.
|
|
13
13
|
|
|
14
14
|
A Model Context Protocol (MCP) server for [Coolify](https://coolify.io/), enabling AI assistants to manage and debug your Coolify instances through natural language.
|
|
15
15
|
|
|
16
16
|
## Features
|
|
17
17
|
|
|
18
|
-
This MCP server provides **
|
|
19
|
-
|
|
20
|
-
| Category | Tools
|
|
21
|
-
| -------------------- |
|
|
22
|
-
| **Infrastructure** |
|
|
23
|
-
| **Diagnostics** | diagnose_app
|
|
24
|
-
| **Batch Operations** | restart_project_apps
|
|
25
|
-
| **Servers** |
|
|
26
|
-
| **Projects** | list, get, create, update, delete
|
|
27
|
-
| **Environments** | list, get, create, delete
|
|
28
|
-
| **Applications** |
|
|
29
|
-
| **Databases** |
|
|
30
|
-
| **Services** |
|
|
31
|
-
| **
|
|
32
|
-
| **
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
| ------------------ | ----------------------------------------------------------- |
|
|
40
|
-
| `debug-app` | Debug an application - gathers logs, status, env vars |
|
|
41
|
-
| `health-check` | Full infrastructure health analysis |
|
|
42
|
-
| `deploy-app` | Step-by-step deployment wizard from Git repository |
|
|
43
|
-
| `troubleshoot-ssl` | SSL/TLS certificate diagnosis workflow |
|
|
44
|
-
| `restart-project` | Safely restart all apps in a project with status monitoring |
|
|
45
|
-
| `env-audit` | Audit and compare environment variables across applications |
|
|
46
|
-
| `backup-status` | Check database backup status and history |
|
|
18
|
+
This MCP server provides **34 token-optimized tools** for **debugging, management, and deployment**:
|
|
19
|
+
|
|
20
|
+
| Category | Tools |
|
|
21
|
+
| -------------------- | ----------------------------------------------------------------------------------------- |
|
|
22
|
+
| **Infrastructure** | `get_infrastructure_overview`, `get_mcp_version`, `get_version` |
|
|
23
|
+
| **Diagnostics** | `diagnose_app`, `diagnose_server`, `find_issues` |
|
|
24
|
+
| **Batch Operations** | `restart_project_apps`, `bulk_env_update`, `stop_all_apps`, `redeploy_project` |
|
|
25
|
+
| **Servers** | `list_servers`, `get_server`, `validate_server`, `server_resources`, `server_domains` |
|
|
26
|
+
| **Projects** | `projects` (list, get, create, update, delete via action param) |
|
|
27
|
+
| **Environments** | `environments` (list, get, create, delete via action param) |
|
|
28
|
+
| **Applications** | `list_applications`, `get_application`, `application` (CRUD), `application_logs` |
|
|
29
|
+
| **Databases** | `list_databases`, `get_database`, `database` (create 8 types, delete), `database_backups` |
|
|
30
|
+
| **Services** | `list_services`, `get_service`, `service` (create, update, delete) |
|
|
31
|
+
| **Control** | `control` (start/stop/restart for apps, databases, services) |
|
|
32
|
+
| **Env Vars** | `env_vars` (CRUD for application and service env vars) |
|
|
33
|
+
| **Deployments** | `list_deployments`, `deploy`, `deployment` (get, cancel, list_for_app) |
|
|
34
|
+
| **Private Keys** | `private_keys` (list, get, create, update, delete via action param) |
|
|
35
|
+
|
|
36
|
+
### v2.0.0 Token Diet
|
|
37
|
+
|
|
38
|
+
v2.0.0 reduced token usage by **85%** (from ~43,000 to ~6,600 tokens) by consolidating related operations into single tools with action parameters. This prevents context window exhaustion in AI assistants.
|
|
47
39
|
|
|
48
40
|
## Installation
|
|
49
41
|
|
|
@@ -794,6 +794,87 @@ describe('CoolifyClient', () => {
|
|
|
794
794
|
expect(result).toEqual(mockExecution);
|
|
795
795
|
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/databases/db-uuid/backups/backup-uuid/executions/exec-uuid', expect.any(Object));
|
|
796
796
|
});
|
|
797
|
+
it('should create a PostgreSQL database', async () => {
|
|
798
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ uuid: 'pg-uuid' }));
|
|
799
|
+
const result = await client.createPostgresql({
|
|
800
|
+
server_uuid: 'server-uuid',
|
|
801
|
+
project_uuid: 'project-uuid',
|
|
802
|
+
environment_name: 'production',
|
|
803
|
+
postgres_user: 'myuser',
|
|
804
|
+
postgres_db: 'mydb',
|
|
805
|
+
});
|
|
806
|
+
expect(result).toEqual({ uuid: 'pg-uuid' });
|
|
807
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/databases/postgresql', expect.objectContaining({ method: 'POST' }));
|
|
808
|
+
});
|
|
809
|
+
it('should create a MySQL database', async () => {
|
|
810
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ uuid: 'mysql-uuid' }));
|
|
811
|
+
const result = await client.createMysql({
|
|
812
|
+
server_uuid: 'server-uuid',
|
|
813
|
+
project_uuid: 'project-uuid',
|
|
814
|
+
mysql_user: 'myuser',
|
|
815
|
+
mysql_database: 'mydb',
|
|
816
|
+
});
|
|
817
|
+
expect(result).toEqual({ uuid: 'mysql-uuid' });
|
|
818
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/databases/mysql', expect.objectContaining({ method: 'POST' }));
|
|
819
|
+
});
|
|
820
|
+
it('should create a MariaDB database', async () => {
|
|
821
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ uuid: 'mariadb-uuid' }));
|
|
822
|
+
const result = await client.createMariadb({
|
|
823
|
+
server_uuid: 'server-uuid',
|
|
824
|
+
project_uuid: 'project-uuid',
|
|
825
|
+
});
|
|
826
|
+
expect(result).toEqual({ uuid: 'mariadb-uuid' });
|
|
827
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/databases/mariadb', expect.objectContaining({ method: 'POST' }));
|
|
828
|
+
});
|
|
829
|
+
it('should create a MongoDB database', async () => {
|
|
830
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ uuid: 'mongo-uuid' }));
|
|
831
|
+
const result = await client.createMongodb({
|
|
832
|
+
server_uuid: 'server-uuid',
|
|
833
|
+
project_uuid: 'project-uuid',
|
|
834
|
+
mongo_initdb_root_username: 'admin',
|
|
835
|
+
});
|
|
836
|
+
expect(result).toEqual({ uuid: 'mongo-uuid' });
|
|
837
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/databases/mongodb', expect.objectContaining({ method: 'POST' }));
|
|
838
|
+
});
|
|
839
|
+
it('should create a Redis database', async () => {
|
|
840
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ uuid: 'redis-uuid' }));
|
|
841
|
+
const result = await client.createRedis({
|
|
842
|
+
server_uuid: 'server-uuid',
|
|
843
|
+
project_uuid: 'project-uuid',
|
|
844
|
+
redis_password: 'secret',
|
|
845
|
+
});
|
|
846
|
+
expect(result).toEqual({ uuid: 'redis-uuid' });
|
|
847
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/databases/redis', expect.objectContaining({ method: 'POST' }));
|
|
848
|
+
});
|
|
849
|
+
it('should create a KeyDB database', async () => {
|
|
850
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ uuid: 'keydb-uuid' }));
|
|
851
|
+
const result = await client.createKeydb({
|
|
852
|
+
server_uuid: 'server-uuid',
|
|
853
|
+
project_uuid: 'project-uuid',
|
|
854
|
+
});
|
|
855
|
+
expect(result).toEqual({ uuid: 'keydb-uuid' });
|
|
856
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/databases/keydb', expect.objectContaining({ method: 'POST' }));
|
|
857
|
+
});
|
|
858
|
+
it('should create a ClickHouse database', async () => {
|
|
859
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ uuid: 'clickhouse-uuid' }));
|
|
860
|
+
const result = await client.createClickhouse({
|
|
861
|
+
server_uuid: 'server-uuid',
|
|
862
|
+
project_uuid: 'project-uuid',
|
|
863
|
+
clickhouse_admin_user: 'admin',
|
|
864
|
+
});
|
|
865
|
+
expect(result).toEqual({ uuid: 'clickhouse-uuid' });
|
|
866
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/databases/clickhouse', expect.objectContaining({ method: 'POST' }));
|
|
867
|
+
});
|
|
868
|
+
it('should create a Dragonfly database', async () => {
|
|
869
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ uuid: 'dragonfly-uuid' }));
|
|
870
|
+
const result = await client.createDragonfly({
|
|
871
|
+
server_uuid: 'server-uuid',
|
|
872
|
+
project_uuid: 'project-uuid',
|
|
873
|
+
dragonfly_password: 'secret',
|
|
874
|
+
});
|
|
875
|
+
expect(result).toEqual({ uuid: 'dragonfly-uuid' });
|
|
876
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/databases/dragonfly', expect.objectContaining({ method: 'POST' }));
|
|
877
|
+
});
|
|
797
878
|
});
|
|
798
879
|
// =========================================================================
|
|
799
880
|
// Service endpoints - extended coverage
|
|
@@ -1,333 +1,132 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* MCP Server Tests
|
|
2
|
+
* MCP Server Tests v2.0.0
|
|
3
3
|
*
|
|
4
|
-
* Tests for the MCP
|
|
5
|
-
* -
|
|
6
|
-
*
|
|
4
|
+
* Tests for the consolidated MCP tool layer.
|
|
5
|
+
* CoolifyClient methods are fully tested in coolify-client.test.ts (174 tests).
|
|
6
|
+
* These tests verify MCP server instantiation and structure.
|
|
7
7
|
*/
|
|
8
|
-
import {
|
|
9
|
-
// Create typed mock functions
|
|
10
|
-
const mockListServers = jest.fn();
|
|
11
|
-
const mockListProjects = jest.fn();
|
|
12
|
-
const mockListApplications = jest.fn();
|
|
13
|
-
const mockListDatabases = jest.fn();
|
|
14
|
-
const mockListServices = jest.fn();
|
|
15
|
-
const mockDiagnoseApplication = jest.fn();
|
|
16
|
-
const mockDiagnoseServer = jest.fn();
|
|
17
|
-
const mockFindInfrastructureIssues = jest.fn();
|
|
18
|
-
const mockDeleteDatabase = jest.fn();
|
|
19
|
-
// Mock the CoolifyClient module
|
|
20
|
-
jest.mock('../lib/coolify-client.js', () => ({
|
|
21
|
-
CoolifyClient: jest.fn().mockImplementation(() => ({
|
|
22
|
-
listServers: mockListServers,
|
|
23
|
-
listProjects: mockListProjects,
|
|
24
|
-
listApplications: mockListApplications,
|
|
25
|
-
listDatabases: mockListDatabases,
|
|
26
|
-
listServices: mockListServices,
|
|
27
|
-
diagnoseApplication: mockDiagnoseApplication,
|
|
28
|
-
diagnoseServer: mockDiagnoseServer,
|
|
29
|
-
findInfrastructureIssues: mockFindInfrastructureIssues,
|
|
30
|
-
deleteDatabase: mockDeleteDatabase,
|
|
31
|
-
getVersion: jest.fn(),
|
|
32
|
-
})),
|
|
33
|
-
}));
|
|
34
|
-
// Import after mocking
|
|
8
|
+
import { describe, it, expect, beforeEach } from '@jest/globals';
|
|
35
9
|
import { CoolifyMcpServer } from '../lib/mcp-server.js';
|
|
36
|
-
describe('CoolifyMcpServer', () => {
|
|
10
|
+
describe('CoolifyMcpServer v2', () => {
|
|
11
|
+
let server;
|
|
37
12
|
beforeEach(() => {
|
|
38
|
-
|
|
13
|
+
server = new CoolifyMcpServer({
|
|
14
|
+
baseUrl: 'http://localhost:3000',
|
|
15
|
+
accessToken: 'test-token',
|
|
16
|
+
});
|
|
39
17
|
});
|
|
40
18
|
describe('constructor', () => {
|
|
41
|
-
it('should create server instance
|
|
42
|
-
const server = new CoolifyMcpServer({
|
|
43
|
-
baseUrl: 'http://localhost:3000',
|
|
44
|
-
accessToken: 'test-token',
|
|
45
|
-
});
|
|
19
|
+
it('should create server instance', () => {
|
|
46
20
|
expect(server).toBeInstanceOf(CoolifyMcpServer);
|
|
47
21
|
});
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
it('should call all list methods with summary: true for aggregation', async () => {
|
|
51
|
-
// Setup mock responses
|
|
52
|
-
mockListServers.mockResolvedValue([
|
|
53
|
-
{ uuid: 'srv-1', name: 'server-1', ip: '10.0.0.1', status: 'running', is_reachable: true },
|
|
54
|
-
]);
|
|
55
|
-
mockListProjects.mockResolvedValue([
|
|
56
|
-
{ uuid: 'proj-1', name: 'project-1', description: 'Test' },
|
|
57
|
-
]);
|
|
58
|
-
mockListApplications.mockResolvedValue([
|
|
59
|
-
{ uuid: 'app-1', name: 'app-1', status: 'running', fqdn: 'https://app.com' },
|
|
60
|
-
]);
|
|
61
|
-
mockListDatabases.mockResolvedValue([
|
|
62
|
-
{ uuid: 'db-1', name: 'db-1', type: 'postgresql', status: 'running', is_public: false },
|
|
63
|
-
]);
|
|
64
|
-
mockListServices.mockResolvedValue([
|
|
65
|
-
{ uuid: 'svc-1', name: 'svc-1', type: 'redis', status: 'running' },
|
|
66
|
-
]);
|
|
67
|
-
// Create server (this registers tools)
|
|
68
|
-
new CoolifyMcpServer({
|
|
69
|
-
baseUrl: 'http://localhost:3000',
|
|
70
|
-
accessToken: 'test-token',
|
|
71
|
-
});
|
|
72
|
-
// Simulate what get_infrastructure_overview does
|
|
73
|
-
await Promise.all([
|
|
74
|
-
mockListServers({ summary: true }),
|
|
75
|
-
mockListProjects({ summary: true }),
|
|
76
|
-
mockListApplications({ summary: true }),
|
|
77
|
-
mockListDatabases({ summary: true }),
|
|
78
|
-
mockListServices({ summary: true }),
|
|
79
|
-
]);
|
|
80
|
-
// Verify all methods were called with summary: true
|
|
81
|
-
expect(mockListServers).toHaveBeenCalledWith({ summary: true });
|
|
82
|
-
expect(mockListProjects).toHaveBeenCalledWith({ summary: true });
|
|
83
|
-
expect(mockListApplications).toHaveBeenCalledWith({ summary: true });
|
|
84
|
-
expect(mockListDatabases).toHaveBeenCalledWith({ summary: true });
|
|
85
|
-
expect(mockListServices).toHaveBeenCalledWith({ summary: true });
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
describe('list tools use summary mode by default', () => {
|
|
89
|
-
beforeEach(() => {
|
|
90
|
-
// Create fresh server for each test
|
|
91
|
-
new CoolifyMcpServer({
|
|
92
|
-
baseUrl: 'http://localhost:3000',
|
|
93
|
-
accessToken: 'test-token',
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
it('list_servers should use summary: true', async () => {
|
|
97
|
-
mockListServers.mockResolvedValue([]);
|
|
98
|
-
// Call as the MCP tool would
|
|
99
|
-
await mockListServers({ page: undefined, per_page: undefined, summary: true });
|
|
100
|
-
expect(mockListServers).toHaveBeenCalledWith({
|
|
101
|
-
page: undefined,
|
|
102
|
-
per_page: undefined,
|
|
103
|
-
summary: true,
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
it('list_applications should use summary: true', async () => {
|
|
107
|
-
mockListApplications.mockResolvedValue([]);
|
|
108
|
-
await mockListApplications({ page: undefined, per_page: undefined, summary: true });
|
|
109
|
-
expect(mockListApplications).toHaveBeenCalledWith({
|
|
110
|
-
page: undefined,
|
|
111
|
-
per_page: undefined,
|
|
112
|
-
summary: true,
|
|
113
|
-
});
|
|
114
|
-
});
|
|
115
|
-
it('list_services should use summary: true', async () => {
|
|
116
|
-
mockListServices.mockResolvedValue([]);
|
|
117
|
-
await mockListServices({ page: undefined, per_page: undefined, summary: true });
|
|
118
|
-
expect(mockListServices).toHaveBeenCalledWith({
|
|
119
|
-
page: undefined,
|
|
120
|
-
per_page: undefined,
|
|
121
|
-
summary: true,
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
it('list_databases should use summary: true', async () => {
|
|
125
|
-
mockListDatabases.mockResolvedValue([]);
|
|
126
|
-
await mockListDatabases({ page: undefined, per_page: undefined, summary: true });
|
|
127
|
-
expect(mockListDatabases).toHaveBeenCalledWith({
|
|
128
|
-
page: undefined,
|
|
129
|
-
per_page: undefined,
|
|
130
|
-
summary: true,
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
it('list_projects should use summary: true', async () => {
|
|
134
|
-
mockListProjects.mockResolvedValue([]);
|
|
135
|
-
await mockListProjects({ page: undefined, per_page: undefined, summary: true });
|
|
136
|
-
expect(mockListProjects).toHaveBeenCalledWith({
|
|
137
|
-
page: undefined,
|
|
138
|
-
per_page: undefined,
|
|
139
|
-
summary: true,
|
|
140
|
-
});
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
describe('error handling', () => {
|
|
144
|
-
it('should handle client errors', async () => {
|
|
145
|
-
mockListServers.mockRejectedValue(new Error('Connection failed'));
|
|
146
|
-
await expect(mockListServers({ summary: true })).rejects.toThrow('Connection failed');
|
|
22
|
+
it('should be an MCP server with connect method', () => {
|
|
23
|
+
expect(typeof server.connect).toBe('function');
|
|
147
24
|
});
|
|
148
25
|
});
|
|
149
|
-
describe('
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
accessToken: 'test-token',
|
|
154
|
-
});
|
|
155
|
-
});
|
|
156
|
-
describe('diagnose_app', () => {
|
|
157
|
-
it('should call diagnoseApplication with the query', async () => {
|
|
158
|
-
mockDiagnoseApplication.mockResolvedValue({
|
|
159
|
-
application: {
|
|
160
|
-
uuid: 'app-uuid-123',
|
|
161
|
-
name: 'test-app',
|
|
162
|
-
status: 'running',
|
|
163
|
-
fqdn: 'https://test.example.com',
|
|
164
|
-
git_repository: 'org/repo',
|
|
165
|
-
git_branch: 'main',
|
|
166
|
-
},
|
|
167
|
-
health: { status: 'healthy', issues: [] },
|
|
168
|
-
logs: 'Application started',
|
|
169
|
-
environment_variables: {
|
|
170
|
-
count: 2,
|
|
171
|
-
variables: [{ key: 'NODE_ENV', is_build_time: false }],
|
|
172
|
-
},
|
|
173
|
-
recent_deployments: [],
|
|
174
|
-
});
|
|
175
|
-
await mockDiagnoseApplication('test-app');
|
|
176
|
-
expect(mockDiagnoseApplication).toHaveBeenCalledWith('test-app');
|
|
177
|
-
});
|
|
178
|
-
it('should call diagnoseApplication with a domain', async () => {
|
|
179
|
-
mockDiagnoseApplication.mockResolvedValue({
|
|
180
|
-
application: null,
|
|
181
|
-
health: { status: 'unknown', issues: [] },
|
|
182
|
-
logs: null,
|
|
183
|
-
environment_variables: { count: 0, variables: [] },
|
|
184
|
-
recent_deployments: [],
|
|
185
|
-
errors: ['Application not found'],
|
|
186
|
-
});
|
|
187
|
-
await mockDiagnoseApplication('tidylinker.com');
|
|
188
|
-
expect(mockDiagnoseApplication).toHaveBeenCalledWith('tidylinker.com');
|
|
189
|
-
});
|
|
190
|
-
it('should call diagnoseApplication with a UUID', async () => {
|
|
191
|
-
mockDiagnoseApplication.mockResolvedValue({
|
|
192
|
-
application: {
|
|
193
|
-
uuid: 'xs0sgs4gog044s4k4c88kgsc',
|
|
194
|
-
name: 'test-app',
|
|
195
|
-
status: 'running',
|
|
196
|
-
fqdn: null,
|
|
197
|
-
git_repository: null,
|
|
198
|
-
git_branch: null,
|
|
199
|
-
},
|
|
200
|
-
health: { status: 'healthy', issues: [] },
|
|
201
|
-
logs: null,
|
|
202
|
-
environment_variables: { count: 0, variables: [] },
|
|
203
|
-
recent_deployments: [],
|
|
204
|
-
});
|
|
205
|
-
await mockDiagnoseApplication('xs0sgs4gog044s4k4c88kgsc');
|
|
206
|
-
expect(mockDiagnoseApplication).toHaveBeenCalledWith('xs0sgs4gog044s4k4c88kgsc');
|
|
207
|
-
});
|
|
208
|
-
});
|
|
209
|
-
describe('diagnose_server', () => {
|
|
210
|
-
it('should call diagnoseServer with the query', async () => {
|
|
211
|
-
mockDiagnoseServer.mockResolvedValue({
|
|
212
|
-
server: {
|
|
213
|
-
uuid: 'srv-uuid-123',
|
|
214
|
-
name: 'production-server',
|
|
215
|
-
ip: '192.168.1.100',
|
|
216
|
-
status: 'running',
|
|
217
|
-
is_reachable: true,
|
|
218
|
-
},
|
|
219
|
-
health: { status: 'healthy', issues: [] },
|
|
220
|
-
resources: [],
|
|
221
|
-
domains: [],
|
|
222
|
-
validation: { message: 'Server is reachable' },
|
|
223
|
-
});
|
|
224
|
-
await mockDiagnoseServer('production-server');
|
|
225
|
-
expect(mockDiagnoseServer).toHaveBeenCalledWith('production-server');
|
|
226
|
-
});
|
|
227
|
-
it('should call diagnoseServer with an IP address', async () => {
|
|
228
|
-
mockDiagnoseServer.mockResolvedValue({
|
|
229
|
-
server: {
|
|
230
|
-
uuid: 'srv-uuid-123',
|
|
231
|
-
name: 'production-server',
|
|
232
|
-
ip: '192.168.1.100',
|
|
233
|
-
status: 'running',
|
|
234
|
-
is_reachable: true,
|
|
235
|
-
},
|
|
236
|
-
health: { status: 'healthy', issues: [] },
|
|
237
|
-
resources: [],
|
|
238
|
-
domains: [],
|
|
239
|
-
validation: { message: 'Server is reachable' },
|
|
240
|
-
});
|
|
241
|
-
await mockDiagnoseServer('192.168.1.100');
|
|
242
|
-
expect(mockDiagnoseServer).toHaveBeenCalledWith('192.168.1.100');
|
|
243
|
-
});
|
|
244
|
-
it('should call diagnoseServer with a UUID', async () => {
|
|
245
|
-
mockDiagnoseServer.mockResolvedValue({
|
|
246
|
-
server: {
|
|
247
|
-
uuid: 'ggkk8w4c08gw48oowsg4g0oc',
|
|
248
|
-
name: 'coolify-apps',
|
|
249
|
-
ip: '10.0.0.1',
|
|
250
|
-
status: 'running',
|
|
251
|
-
is_reachable: true,
|
|
252
|
-
},
|
|
253
|
-
health: { status: 'healthy', issues: [] },
|
|
254
|
-
resources: [],
|
|
255
|
-
domains: [],
|
|
256
|
-
validation: { message: 'Server is reachable' },
|
|
257
|
-
});
|
|
258
|
-
await mockDiagnoseServer('ggkk8w4c08gw48oowsg4g0oc');
|
|
259
|
-
expect(mockDiagnoseServer).toHaveBeenCalledWith('ggkk8w4c08gw48oowsg4g0oc');
|
|
260
|
-
});
|
|
261
|
-
});
|
|
262
|
-
describe('find_issues', () => {
|
|
263
|
-
it('should call findInfrastructureIssues', async () => {
|
|
264
|
-
mockFindInfrastructureIssues.mockResolvedValue({
|
|
265
|
-
summary: {
|
|
266
|
-
total_issues: 2,
|
|
267
|
-
unhealthy_applications: 1,
|
|
268
|
-
unhealthy_databases: 0,
|
|
269
|
-
unhealthy_services: 1,
|
|
270
|
-
unreachable_servers: 0,
|
|
271
|
-
},
|
|
272
|
-
issues: [
|
|
273
|
-
{
|
|
274
|
-
type: 'application',
|
|
275
|
-
uuid: 'app-1',
|
|
276
|
-
name: 'broken-app',
|
|
277
|
-
issue: 'Application is unhealthy',
|
|
278
|
-
status: 'exited:unhealthy',
|
|
279
|
-
},
|
|
280
|
-
{
|
|
281
|
-
type: 'service',
|
|
282
|
-
uuid: 'svc-1',
|
|
283
|
-
name: 'broken-service',
|
|
284
|
-
issue: 'Service has exited',
|
|
285
|
-
status: 'exited',
|
|
286
|
-
},
|
|
287
|
-
],
|
|
288
|
-
});
|
|
289
|
-
await mockFindInfrastructureIssues();
|
|
290
|
-
expect(mockFindInfrastructureIssues).toHaveBeenCalled();
|
|
291
|
-
});
|
|
292
|
-
it('should return empty issues when infrastructure is healthy', async () => {
|
|
293
|
-
mockFindInfrastructureIssues.mockResolvedValue({
|
|
294
|
-
summary: {
|
|
295
|
-
total_issues: 0,
|
|
296
|
-
unhealthy_applications: 0,
|
|
297
|
-
unhealthy_databases: 0,
|
|
298
|
-
unhealthy_services: 0,
|
|
299
|
-
unreachable_servers: 0,
|
|
300
|
-
},
|
|
301
|
-
issues: [],
|
|
302
|
-
});
|
|
303
|
-
const result = await mockFindInfrastructureIssues();
|
|
304
|
-
expect(result.summary.total_issues).toBe(0);
|
|
305
|
-
expect(result.issues).toHaveLength(0);
|
|
306
|
-
});
|
|
26
|
+
describe('client', () => {
|
|
27
|
+
it('should have client instance', () => {
|
|
28
|
+
const client = server['client'];
|
|
29
|
+
expect(client).toBeDefined();
|
|
307
30
|
});
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
31
|
+
it('should have all required client methods', () => {
|
|
32
|
+
const client = server['client'];
|
|
33
|
+
// Core methods
|
|
34
|
+
expect(typeof client.getVersion).toBe('function');
|
|
35
|
+
// Server operations
|
|
36
|
+
expect(typeof client.listServers).toBe('function');
|
|
37
|
+
expect(typeof client.getServer).toBe('function');
|
|
38
|
+
expect(typeof client.getServerResources).toBe('function');
|
|
39
|
+
expect(typeof client.getServerDomains).toBe('function');
|
|
40
|
+
expect(typeof client.validateServer).toBe('function');
|
|
41
|
+
// Project operations
|
|
42
|
+
expect(typeof client.listProjects).toBe('function');
|
|
43
|
+
expect(typeof client.getProject).toBe('function');
|
|
44
|
+
expect(typeof client.createProject).toBe('function');
|
|
45
|
+
expect(typeof client.updateProject).toBe('function');
|
|
46
|
+
expect(typeof client.deleteProject).toBe('function');
|
|
47
|
+
// Environment operations
|
|
48
|
+
expect(typeof client.listProjectEnvironments).toBe('function');
|
|
49
|
+
expect(typeof client.getProjectEnvironment).toBe('function');
|
|
50
|
+
expect(typeof client.createProjectEnvironment).toBe('function');
|
|
51
|
+
expect(typeof client.deleteProjectEnvironment).toBe('function');
|
|
52
|
+
// Application operations
|
|
53
|
+
expect(typeof client.listApplications).toBe('function');
|
|
54
|
+
expect(typeof client.getApplication).toBe('function');
|
|
55
|
+
expect(typeof client.createApplicationPrivateGH).toBe('function');
|
|
56
|
+
expect(typeof client.createApplicationPrivateKey).toBe('function');
|
|
57
|
+
expect(typeof client.updateApplication).toBe('function');
|
|
58
|
+
expect(typeof client.deleteApplication).toBe('function');
|
|
59
|
+
expect(typeof client.getApplicationLogs).toBe('function');
|
|
60
|
+
// Control operations
|
|
61
|
+
expect(typeof client.startApplication).toBe('function');
|
|
62
|
+
expect(typeof client.stopApplication).toBe('function');
|
|
63
|
+
expect(typeof client.restartApplication).toBe('function');
|
|
64
|
+
expect(typeof client.startDatabase).toBe('function');
|
|
65
|
+
expect(typeof client.stopDatabase).toBe('function');
|
|
66
|
+
expect(typeof client.restartDatabase).toBe('function');
|
|
67
|
+
expect(typeof client.startService).toBe('function');
|
|
68
|
+
expect(typeof client.stopService).toBe('function');
|
|
69
|
+
expect(typeof client.restartService).toBe('function');
|
|
70
|
+
// Database operations
|
|
71
|
+
expect(typeof client.listDatabases).toBe('function');
|
|
72
|
+
expect(typeof client.getDatabase).toBe('function');
|
|
73
|
+
expect(typeof client.deleteDatabase).toBe('function');
|
|
74
|
+
expect(typeof client.createPostgresql).toBe('function');
|
|
75
|
+
expect(typeof client.createMysql).toBe('function');
|
|
76
|
+
expect(typeof client.createMariadb).toBe('function');
|
|
77
|
+
expect(typeof client.createMongodb).toBe('function');
|
|
78
|
+
expect(typeof client.createRedis).toBe('function');
|
|
79
|
+
expect(typeof client.createKeydb).toBe('function');
|
|
80
|
+
expect(typeof client.createClickhouse).toBe('function');
|
|
81
|
+
expect(typeof client.createDragonfly).toBe('function');
|
|
82
|
+
// Service operations
|
|
83
|
+
expect(typeof client.listServices).toBe('function');
|
|
84
|
+
expect(typeof client.getService).toBe('function');
|
|
85
|
+
expect(typeof client.createService).toBe('function');
|
|
86
|
+
expect(typeof client.updateService).toBe('function');
|
|
87
|
+
expect(typeof client.deleteService).toBe('function');
|
|
88
|
+
// Environment variable operations
|
|
89
|
+
expect(typeof client.listApplicationEnvVars).toBe('function');
|
|
90
|
+
expect(typeof client.createApplicationEnvVar).toBe('function');
|
|
91
|
+
expect(typeof client.updateApplicationEnvVar).toBe('function');
|
|
92
|
+
expect(typeof client.deleteApplicationEnvVar).toBe('function');
|
|
93
|
+
expect(typeof client.listServiceEnvVars).toBe('function');
|
|
94
|
+
expect(typeof client.createServiceEnvVar).toBe('function');
|
|
95
|
+
expect(typeof client.deleteServiceEnvVar).toBe('function');
|
|
96
|
+
// Deployment operations
|
|
97
|
+
expect(typeof client.listDeployments).toBe('function');
|
|
98
|
+
expect(typeof client.getDeployment).toBe('function');
|
|
99
|
+
expect(typeof client.deployByTagOrUuid).toBe('function');
|
|
100
|
+
expect(typeof client.listApplicationDeployments).toBe('function');
|
|
101
|
+
expect(typeof client.cancelDeployment).toBe('function');
|
|
102
|
+
// Private key operations
|
|
103
|
+
expect(typeof client.listPrivateKeys).toBe('function');
|
|
104
|
+
expect(typeof client.getPrivateKey).toBe('function');
|
|
105
|
+
expect(typeof client.createPrivateKey).toBe('function');
|
|
106
|
+
expect(typeof client.updatePrivateKey).toBe('function');
|
|
107
|
+
expect(typeof client.deletePrivateKey).toBe('function');
|
|
108
|
+
// Backup operations
|
|
109
|
+
expect(typeof client.listDatabaseBackups).toBe('function');
|
|
110
|
+
expect(typeof client.getDatabaseBackup).toBe('function');
|
|
111
|
+
expect(typeof client.listBackupExecutions).toBe('function');
|
|
112
|
+
expect(typeof client.getBackupExecution).toBe('function');
|
|
113
|
+
// Diagnostic operations
|
|
114
|
+
expect(typeof client.diagnoseApplication).toBe('function');
|
|
115
|
+
expect(typeof client.diagnoseServer).toBe('function');
|
|
116
|
+
expect(typeof client.findInfrastructureIssues).toBe('function');
|
|
117
|
+
// Batch operations
|
|
118
|
+
expect(typeof client.restartProjectApps).toBe('function');
|
|
119
|
+
expect(typeof client.bulkEnvUpdate).toBe('function');
|
|
120
|
+
expect(typeof client.stopAllApps).toBe('function');
|
|
121
|
+
expect(typeof client.redeployProjectApps).toBe('function');
|
|
319
122
|
});
|
|
320
123
|
});
|
|
321
|
-
describe('
|
|
322
|
-
it('
|
|
323
|
-
|
|
324
|
-
//
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
name: '@masonator/coolify-mcp',
|
|
328
|
-
};
|
|
329
|
-
expect(expectedResponse.version).toBe('1.1.0');
|
|
330
|
-
expect(expectedResponse.name).toBe('@masonator/coolify-mcp');
|
|
124
|
+
describe('server configuration', () => {
|
|
125
|
+
it('should store baseUrl and accessToken in client', () => {
|
|
126
|
+
const client = server['client'];
|
|
127
|
+
// CoolifyClient stores base URL without /api/v1 suffix
|
|
128
|
+
expect(client['baseUrl']).toBe('http://localhost:3000');
|
|
129
|
+
expect(client['accessToken']).toBe('test-token');
|
|
331
130
|
});
|
|
332
131
|
});
|
|
333
132
|
});
|
|
@@ -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, EnvVarSummary, CreateEnvVarRequest, UpdateEnvVarRequest, BulkUpdateEnvVarsRequest, Database, UpdateDatabaseRequest, DatabaseBackup, BackupExecution, Service, CreateServiceRequest, UpdateServiceRequest, ServiceCreateResponse, Deployment, Team, TeamMember, PrivateKey, CreatePrivateKeyRequest, UpdatePrivateKeyRequest, CloudToken, CreateCloudTokenRequest, UpdateCloudTokenRequest, CloudTokenValidation, Version, ApplicationDiagnostic, ServerDiagnostic, InfrastructureIssuesReport, BatchOperationResult } 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, EnvVarSummary, CreateEnvVarRequest, UpdateEnvVarRequest, BulkUpdateEnvVarsRequest, Database, UpdateDatabaseRequest, CreatePostgresqlRequest, CreateMysqlRequest, CreateMariadbRequest, CreateMongodbRequest, CreateRedisRequest, CreateKeydbRequest, CreateClickhouseRequest, CreateDragonflyRequest, CreateDatabaseResponse, DatabaseBackup, BackupExecution, Service, CreateServiceRequest, UpdateServiceRequest, ServiceCreateResponse, Deployment, Team, TeamMember, PrivateKey, CreatePrivateKeyRequest, UpdatePrivateKeyRequest, CloudToken, CreateCloudTokenRequest, UpdateCloudTokenRequest, CloudTokenValidation, Version, ApplicationDiagnostic, ServerDiagnostic, InfrastructureIssuesReport, BatchOperationResult } from '../types/coolify.js';
|
|
6
6
|
export interface ListOptions {
|
|
7
7
|
page?: number;
|
|
8
8
|
per_page?: number;
|
|
@@ -114,6 +114,14 @@ export declare class CoolifyClient {
|
|
|
114
114
|
startDatabase(uuid: string): Promise<MessageResponse>;
|
|
115
115
|
stopDatabase(uuid: string): Promise<MessageResponse>;
|
|
116
116
|
restartDatabase(uuid: string): Promise<MessageResponse>;
|
|
117
|
+
createPostgresql(data: CreatePostgresqlRequest): Promise<CreateDatabaseResponse>;
|
|
118
|
+
createMysql(data: CreateMysqlRequest): Promise<CreateDatabaseResponse>;
|
|
119
|
+
createMariadb(data: CreateMariadbRequest): Promise<CreateDatabaseResponse>;
|
|
120
|
+
createMongodb(data: CreateMongodbRequest): Promise<CreateDatabaseResponse>;
|
|
121
|
+
createRedis(data: CreateRedisRequest): Promise<CreateDatabaseResponse>;
|
|
122
|
+
createKeydb(data: CreateKeydbRequest): Promise<CreateDatabaseResponse>;
|
|
123
|
+
createClickhouse(data: CreateClickhouseRequest): Promise<CreateDatabaseResponse>;
|
|
124
|
+
createDragonfly(data: CreateDragonflyRequest): Promise<CreateDatabaseResponse>;
|
|
117
125
|
listServices(options?: ListOptions): Promise<Service[] | ServiceSummary[]>;
|
|
118
126
|
getService(uuid: string): Promise<Service>;
|
|
119
127
|
createService(data: CreateServiceRequest): Promise<ServiceCreateResponse>;
|