@masonator/coolify-mcp 0.1.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 ADDED
@@ -0,0 +1,375 @@
1
+ # Coolify MCP Server
2
+
3
+ A Model Context Protocol (MCP) server implementation for [Coolify](https://coolify.io/), enabling AI assistants to interact with your Coolify instances through natural language.
4
+
5
+ ## Claude Desktop, Cursor, and other MCP compatible IDEs
6
+
7
+ ### Claude
8
+
9
+ ```json
10
+ "coolify": {
11
+ "command": "npx",
12
+ "args": [
13
+ "-y", "@stumason/coolify-mcp"
14
+ ],
15
+ "env": {
16
+ "COOLIFY_ACCESS_TOKEN": "0|your-secret-token",
17
+ "COOLIFY_BASE_URL": "https://your-coolify-instance.com"
18
+ }
19
+ },
20
+ ```
21
+
22
+ ### Cursor
23
+
24
+ command:
25
+
26
+ `env COOLIFY_ACCESS_TOKEN:0|your-secret-token COOLIFY_BASE_URL:https://your-coolify-instance.com npx -y @stumason/coolify-mcp`
27
+
28
+ ## Overview
29
+
30
+ This MCP server provides a standardized interface for AI models to:
31
+
32
+ - Query Coolify server information
33
+ - Manage projects and environments
34
+ - Handle deployments and resources
35
+ - Monitor server status
36
+ - And more...
37
+
38
+ By implementing the [Model Context Protocol](https://modelcontextprotocol.io), this server allows AI assistants to understand and interact with your Coolify infrastructure in a secure and controlled manner.
39
+
40
+ ## Prerequisites
41
+
42
+ - Node.js >= 18
43
+ - A running Coolify instance
44
+ - Coolify API access token
45
+
46
+ ## Installation
47
+
48
+ ```bash
49
+ # Clone the repository
50
+ git clone https://github.com/stumason/coolify-mcp.git
51
+ cd coolify-mcp
52
+
53
+ # Install dependencies
54
+ npm install
55
+ ```
56
+
57
+ ## Configuration
58
+
59
+ The server requires the following environment variables:
60
+
61
+ ```bash
62
+ # Required
63
+ COOLIFY_ACCESS_TOKEN=your_access_token_here
64
+
65
+ # Optional (defaults to http://localhost:3000)
66
+ COOLIFY_BASE_URL=https://your.coolify.instance
67
+ ```
68
+
69
+ ## Available Tools
70
+
71
+ ### Server Management
72
+
73
+ #### list_servers
74
+
75
+ Lists all Coolify servers in your instance.
76
+
77
+ ```typescript
78
+ // Returns: ServerInfo[]
79
+ await client.list_servers();
80
+ ```
81
+
82
+ #### get_server
83
+
84
+ Get detailed information about a specific server.
85
+
86
+ ```typescript
87
+ // Returns: ServerInfo
88
+ await client.get_server(uuid: string)
89
+ ```
90
+
91
+ #### get_server_resources
92
+
93
+ Get current resources (applications, databases, etc.) running on a server.
94
+
95
+ ```typescript
96
+ // Returns: ServerResources[]
97
+ await client.get_server_resources(uuid: string)
98
+ ```
99
+
100
+ #### get_server_domains
101
+
102
+ Get domains associated with a server.
103
+
104
+ ```typescript
105
+ // Returns: ServerDomain[]
106
+ await client.get_server_domains(uuid: string)
107
+ ```
108
+
109
+ #### validate_server
110
+
111
+ Validate the connection to a specific server.
112
+
113
+ ```typescript
114
+ // Returns: ValidationResponse
115
+ await client.validate_server(uuid: string)
116
+ ```
117
+
118
+ ### Project Management
119
+
120
+ #### list_projects
121
+
122
+ List all projects in your Coolify instance.
123
+
124
+ ```typescript
125
+ // Returns: Project[]
126
+ await client.list_projects();
127
+ ```
128
+
129
+ #### get_project
130
+
131
+ Get detailed information about a specific project.
132
+
133
+ ```typescript
134
+ // Returns: Project
135
+ await client.get_project(uuid: string)
136
+ ```
137
+
138
+ #### create_project
139
+
140
+ Create a new project.
141
+
142
+ ```typescript
143
+ // Returns: { uuid: string }
144
+ await client.create_project({
145
+ name: string,
146
+ description?: string
147
+ })
148
+ ```
149
+
150
+ #### update_project
151
+
152
+ Update an existing project's details.
153
+
154
+ ```typescript
155
+ // Returns: Project
156
+ await client.update_project(uuid: string, {
157
+ name?: string,
158
+ description?: string
159
+ })
160
+ ```
161
+
162
+ #### delete_project
163
+
164
+ Delete a project.
165
+
166
+ ```typescript
167
+ // Returns: { message: string }
168
+ await client.delete_project(uuid: string)
169
+ ```
170
+
171
+ ### Environment Management
172
+
173
+ #### list_environments
174
+
175
+ List all environments or environments for a specific project.
176
+
177
+ ```typescript
178
+ // Returns: Environment[]
179
+ await client.list_environments(project_uuid?: string)
180
+ ```
181
+
182
+ #### get_environment
183
+
184
+ Get detailed information about a specific environment.
185
+
186
+ ```typescript
187
+ // Returns: Environment
188
+ await client.get_environment(uuid: string)
189
+ ```
190
+
191
+ #### get_project_environment
192
+
193
+ Get a specific environment within a project.
194
+
195
+ ```typescript
196
+ // Returns: Environment
197
+ await client.get_project_environment(
198
+ project_uuid: string,
199
+ environment_name_or_uuid: string
200
+ )
201
+ ```
202
+
203
+ #### create_environment
204
+
205
+ Create a new environment.
206
+
207
+ ```typescript
208
+ // Returns: { uuid: string }
209
+ await client.create_environment({
210
+ name: string,
211
+ project_uuid: string,
212
+ variables?: Record<string, string>
213
+ })
214
+ ```
215
+
216
+ #### update_environment_variables
217
+
218
+ Update variables for a specific environment.
219
+
220
+ ```typescript
221
+ // Returns: Environment
222
+ await client.update_environment_variables(
223
+ uuid: string,
224
+ { variables: Record<string, string> }
225
+ )
226
+ ```
227
+
228
+ #### delete_environment
229
+
230
+ Delete an environment.
231
+
232
+ ```typescript
233
+ // Returns: { message: string }
234
+ await client.delete_environment(uuid: string)
235
+ ```
236
+
237
+ ## Type Definitions
238
+
239
+ ### ServerInfo
240
+
241
+ ```typescript
242
+ interface ServerInfo {
243
+ uuid: string;
244
+ name: string;
245
+ status: 'running' | 'stopped' | 'error';
246
+ version: string;
247
+ resources: {
248
+ cpu: number;
249
+ memory: number;
250
+ disk: number;
251
+ };
252
+ }
253
+ ```
254
+
255
+ ### Environment
256
+
257
+ ```typescript
258
+ interface Environment {
259
+ id: number;
260
+ uuid: string;
261
+ name: string;
262
+ project_uuid: string;
263
+ variables?: Record<string, string>;
264
+ created_at: string;
265
+ updated_at: string;
266
+ }
267
+ ```
268
+
269
+ ### Project
270
+
271
+ ```typescript
272
+ interface Project {
273
+ id: number;
274
+ uuid: string;
275
+ name: string;
276
+ description?: string;
277
+ environments?: Environment[];
278
+ }
279
+ ```
280
+
281
+ ## Development
282
+
283
+ ```bash
284
+ # Run in development mode
285
+ npm run build -- --watch
286
+ ```
287
+
288
+ ### Testing
289
+
290
+ ```bash
291
+ # Run all tests
292
+ npm test
293
+
294
+ # Run tests in watch mode
295
+ npm test -- --watch
296
+
297
+ # Run tests with coverage
298
+ npm test -- --coverage
299
+ ```
300
+
301
+ ### Code Quality
302
+
303
+ ```bash
304
+ # Run linter
305
+ npm run lint
306
+
307
+ # Fix linting issues
308
+ npm run lint -- --fix
309
+
310
+ # Format code
311
+ npm run format
312
+ ```
313
+
314
+ ## Architecture
315
+
316
+ The project follows a modular architecture:
317
+
318
+ ```
319
+ src/
320
+ ├── lib/
321
+ │ ├── coolify-client.ts # Coolify API client
322
+ │ └── mcp-server.ts # MCP server implementation
323
+ ├── types/
324
+ │ └── coolify.ts # TypeScript type definitions
325
+ └── index.ts # Entry point
326
+ ```
327
+
328
+ ### Key Components
329
+
330
+ 1. **CoolifyClient**: Handles communication with the Coolify API
331
+
332
+ - Authentication
333
+ - API request handling
334
+ - Error management
335
+
336
+ 2. **CoolifyMcpServer**: Implements the MCP server
337
+ - Resource management
338
+ - Tool implementations
339
+ - Client request handling
340
+
341
+ ## Contributing
342
+
343
+ 1. Fork the repository
344
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
345
+ 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
346
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
347
+ 5. Open a Pull Request
348
+
349
+ ### Development Guidelines
350
+
351
+ - Follow TypeScript best practices
352
+ - Maintain test coverage
353
+ - Update documentation as needed
354
+ - Follow the established code style
355
+
356
+ ## CI/CD
357
+
358
+ GitHub Actions workflows are configured to:
359
+
360
+ - Run tests on Node.js 18.x and 20.x
361
+ - Check code formatting
362
+ - Verify build process
363
+ - Run linting checks
364
+
365
+ ## License
366
+
367
+ MIT
368
+
369
+ ## Support
370
+
371
+ For support, please:
372
+
373
+ 1. Check the [issues](https://github.com/stumason/coolify-mcp/issues) page
374
+ 2. Create a new issue if needed
375
+ 3. Join the Coolify community
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,325 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const coolify_client_js_1 = require("../lib/coolify-client.js");
4
+ // Mock fetch globally
5
+ const mockFetch = jest.fn();
6
+ global.fetch = mockFetch;
7
+ describe('CoolifyClient', () => {
8
+ let client;
9
+ beforeEach(() => {
10
+ client = new coolify_client_js_1.CoolifyClient({
11
+ baseUrl: 'http://test.coolify.io',
12
+ accessToken: 'test-token',
13
+ });
14
+ mockFetch.mockClear();
15
+ // Reset any global mock implementations
16
+ mockFetch.mockReset();
17
+ });
18
+ describe('listServers', () => {
19
+ const mockServers = [
20
+ {
21
+ uuid: 'test-id-1',
22
+ name: 'test-server-1',
23
+ status: 'running',
24
+ version: '1.0.0',
25
+ resources: {
26
+ cpu: 50,
27
+ memory: 60,
28
+ disk: 70,
29
+ },
30
+ },
31
+ {
32
+ uuid: 'test-id-2',
33
+ name: 'test-server-2',
34
+ status: 'stopped',
35
+ version: '1.0.0',
36
+ resources: {
37
+ cpu: 0,
38
+ memory: 0,
39
+ disk: 70,
40
+ },
41
+ },
42
+ ];
43
+ it('should fetch server list successfully', async () => {
44
+ mockFetch.mockResolvedValueOnce({
45
+ ok: true,
46
+ json: async () => mockServers,
47
+ });
48
+ const result = await client.listServers();
49
+ expect(result).toEqual(mockServers);
50
+ expect(mockFetch).toHaveBeenCalledWith('http://test.coolify.io/api/v1/servers', expect.objectContaining({
51
+ headers: expect.objectContaining({
52
+ Authorization: 'Bearer test-token',
53
+ 'Content-Type': 'application/json',
54
+ }),
55
+ }));
56
+ });
57
+ it('should handle error responses', async () => {
58
+ const errorResponse = {
59
+ error: 'Unauthorized',
60
+ status: 401,
61
+ message: 'Invalid token',
62
+ };
63
+ mockFetch.mockResolvedValueOnce({
64
+ ok: false,
65
+ json: async () => errorResponse,
66
+ });
67
+ await expect(client.listServers()).rejects.toThrow('Invalid token');
68
+ });
69
+ });
70
+ describe('getServer', () => {
71
+ const mockServerInfo = {
72
+ uuid: 'test-id',
73
+ name: 'test-server',
74
+ status: 'running',
75
+ version: '1.0.0',
76
+ resources: {
77
+ cpu: 50,
78
+ memory: 60,
79
+ disk: 70,
80
+ },
81
+ };
82
+ it('should fetch server info successfully', async () => {
83
+ mockFetch.mockResolvedValueOnce({
84
+ ok: true,
85
+ json: async () => mockServerInfo,
86
+ });
87
+ const result = await client.getServer('test-id');
88
+ expect(result).toEqual(mockServerInfo);
89
+ expect(mockFetch).toHaveBeenCalledWith('http://test.coolify.io/api/v1/servers/test-id', expect.objectContaining({
90
+ headers: expect.objectContaining({
91
+ Authorization: 'Bearer test-token',
92
+ 'Content-Type': 'application/json',
93
+ }),
94
+ }));
95
+ });
96
+ it('should handle error responses', async () => {
97
+ const errorResponse = {
98
+ error: 'Not Found',
99
+ status: 404,
100
+ message: 'Server not found',
101
+ };
102
+ mockFetch.mockResolvedValueOnce({
103
+ ok: false,
104
+ json: async () => errorResponse,
105
+ });
106
+ await expect(client.getServer('invalid-id')).rejects.toThrow('Server not found');
107
+ });
108
+ });
109
+ describe('getServerResources', () => {
110
+ const mockServerResources = [
111
+ {
112
+ id: 1,
113
+ uuid: 'test-id',
114
+ name: 'test-app',
115
+ type: 'application',
116
+ created_at: '2024-03-19T12:00:00Z',
117
+ updated_at: '2024-03-19T12:00:00Z',
118
+ status: 'running:healthy',
119
+ },
120
+ ];
121
+ it('should fetch server resources successfully', async () => {
122
+ mockFetch.mockResolvedValueOnce({
123
+ ok: true,
124
+ json: async () => mockServerResources,
125
+ });
126
+ const result = await client.getServerResources('test-id');
127
+ expect(result).toEqual(mockServerResources);
128
+ expect(mockFetch).toHaveBeenCalledWith('http://test.coolify.io/api/v1/servers/test-id/resources', expect.objectContaining({
129
+ headers: expect.objectContaining({
130
+ Authorization: 'Bearer test-token',
131
+ 'Content-Type': 'application/json',
132
+ }),
133
+ }));
134
+ });
135
+ it('should handle error responses', async () => {
136
+ const errorResponse = {
137
+ error: 'Server Error',
138
+ status: 500,
139
+ message: 'Internal server error',
140
+ };
141
+ mockFetch.mockResolvedValueOnce({
142
+ ok: false,
143
+ json: async () => errorResponse,
144
+ });
145
+ await expect(client.getServerResources('test-id')).rejects.toThrow('Internal server error');
146
+ });
147
+ });
148
+ test('getServer returns server info', async () => {
149
+ const mockResponse = {
150
+ uuid: 'test-uuid',
151
+ name: 'test-server',
152
+ status: 'running',
153
+ version: '1.0.0',
154
+ resources: {
155
+ cpu: 50,
156
+ memory: 60,
157
+ disk: 70,
158
+ },
159
+ };
160
+ mockFetch.mockResolvedValueOnce({
161
+ ok: true,
162
+ json: () => Promise.resolve(mockResponse),
163
+ });
164
+ const server = await client.getServer('test-uuid');
165
+ expect(server).toEqual(mockResponse);
166
+ expect(mockFetch).toHaveBeenCalledWith('http://test.coolify.io/api/v1/servers/test-uuid', expect.objectContaining({
167
+ headers: {
168
+ Authorization: 'Bearer test-token',
169
+ 'Content-Type': 'application/json',
170
+ },
171
+ }));
172
+ });
173
+ test('getServerResources returns server resources', async () => {
174
+ const mockResponse = [
175
+ {
176
+ id: 1,
177
+ uuid: 'test-uuid',
178
+ name: 'test-app',
179
+ type: 'application',
180
+ created_at: '2025-03-05T13:41:12.000Z',
181
+ updated_at: '2025-03-05T13:41:12.000Z',
182
+ status: 'running:healthy',
183
+ },
184
+ ];
185
+ mockFetch.mockResolvedValueOnce({
186
+ ok: true,
187
+ json: () => Promise.resolve(mockResponse),
188
+ });
189
+ const resources = await client.getServerResources('test-uuid');
190
+ expect(resources).toEqual(mockResponse);
191
+ expect(mockFetch).toHaveBeenCalledWith('http://test.coolify.io/api/v1/servers/test-uuid/resources', expect.objectContaining({
192
+ headers: {
193
+ Authorization: 'Bearer test-token',
194
+ 'Content-Type': 'application/json',
195
+ },
196
+ }));
197
+ });
198
+ describe('Environment Management', () => {
199
+ const mockEnvironment = {
200
+ id: 1,
201
+ uuid: 'env-test-id',
202
+ name: 'test-env',
203
+ project_uuid: 'project-test-id',
204
+ variables: { KEY: 'value' },
205
+ created_at: '2024-03-19T12:00:00Z',
206
+ updated_at: '2024-03-19T12:00:00Z',
207
+ };
208
+ beforeEach(() => {
209
+ mockFetch.mockClear();
210
+ });
211
+ describe('listEnvironments', () => {
212
+ it('should fetch all environments successfully', async () => {
213
+ mockFetch.mockResolvedValueOnce({
214
+ ok: true,
215
+ json: () => Promise.resolve([mockEnvironment]),
216
+ });
217
+ const result = await client.listEnvironments();
218
+ expect(result).toEqual([mockEnvironment]);
219
+ expect(mockFetch).toHaveBeenCalledWith('http://test.coolify.io/api/v1/environments', expect.objectContaining({
220
+ headers: expect.objectContaining({
221
+ Authorization: 'Bearer test-token',
222
+ 'Content-Type': 'application/json',
223
+ }),
224
+ }));
225
+ });
226
+ it('should fetch environments by project UUID', async () => {
227
+ mockFetch.mockResolvedValueOnce({
228
+ ok: true,
229
+ json: () => Promise.resolve([mockEnvironment]),
230
+ });
231
+ const result = await client.listEnvironments('project-test-id');
232
+ expect(result).toEqual([mockEnvironment]);
233
+ expect(mockFetch).toHaveBeenCalledWith('http://test.coolify.io/api/v1/environments?project_uuid=project-test-id', expect.objectContaining({
234
+ headers: expect.objectContaining({
235
+ Authorization: 'Bearer test-token',
236
+ 'Content-Type': 'application/json',
237
+ }),
238
+ }));
239
+ });
240
+ });
241
+ describe('getEnvironment', () => {
242
+ it('should fetch environment by UUID successfully', async () => {
243
+ mockFetch.mockResolvedValueOnce({
244
+ ok: true,
245
+ json: () => Promise.resolve(mockEnvironment),
246
+ });
247
+ const result = await client.getEnvironment('env-test-id');
248
+ expect(result).toEqual(mockEnvironment);
249
+ expect(mockFetch).toHaveBeenCalledWith('http://test.coolify.io/api/v1/environments/env-test-id', expect.objectContaining({
250
+ headers: expect.objectContaining({
251
+ Authorization: 'Bearer test-token',
252
+ 'Content-Type': 'application/json',
253
+ }),
254
+ }));
255
+ });
256
+ });
257
+ describe('createEnvironment', () => {
258
+ it('should create environment successfully', async () => {
259
+ const createRequest = {
260
+ name: 'test-env',
261
+ project_uuid: 'project-test-id',
262
+ variables: { KEY: 'value' },
263
+ };
264
+ const mockResponse = { uuid: 'env-test-id' };
265
+ mockFetch.mockResolvedValueOnce({
266
+ ok: true,
267
+ json: () => Promise.resolve(mockResponse),
268
+ });
269
+ const result = await client.createEnvironment(createRequest);
270
+ expect(result).toEqual(mockResponse);
271
+ expect(mockFetch).toHaveBeenCalledWith('http://test.coolify.io/api/v1/environments', expect.objectContaining({
272
+ method: 'POST',
273
+ body: JSON.stringify(createRequest),
274
+ headers: expect.objectContaining({
275
+ Authorization: 'Bearer test-token',
276
+ 'Content-Type': 'application/json',
277
+ }),
278
+ }));
279
+ });
280
+ });
281
+ describe('updateEnvironmentVariables', () => {
282
+ it('should update environment variables successfully', async () => {
283
+ const updateRequest = {
284
+ variables: { NEW_KEY: 'new-value' },
285
+ };
286
+ const mockUpdatedEnvironment = {
287
+ ...mockEnvironment,
288
+ variables: updateRequest.variables,
289
+ };
290
+ mockFetch.mockResolvedValueOnce({
291
+ ok: true,
292
+ json: () => Promise.resolve(mockUpdatedEnvironment),
293
+ });
294
+ const result = await client.updateEnvironmentVariables('env-test-id', updateRequest);
295
+ expect(result).toEqual(mockUpdatedEnvironment);
296
+ expect(mockFetch).toHaveBeenCalledWith('http://test.coolify.io/api/v1/environments/env-test-id/variables', expect.objectContaining({
297
+ method: 'PUT',
298
+ body: JSON.stringify(updateRequest),
299
+ headers: expect.objectContaining({
300
+ Authorization: 'Bearer test-token',
301
+ 'Content-Type': 'application/json',
302
+ }),
303
+ }));
304
+ });
305
+ });
306
+ describe('deleteEnvironment', () => {
307
+ it('should delete environment successfully', async () => {
308
+ const mockResponse = { message: 'Environment deleted successfully' };
309
+ mockFetch.mockResolvedValueOnce({
310
+ ok: true,
311
+ json: () => Promise.resolve(mockResponse),
312
+ });
313
+ const result = await client.deleteEnvironment('env-test-id');
314
+ expect(result).toEqual(mockResponse);
315
+ expect(mockFetch).toHaveBeenCalledWith('http://test.coolify.io/api/v1/environments/env-test-id', expect.objectContaining({
316
+ method: 'DELETE',
317
+ headers: expect.objectContaining({
318
+ Authorization: 'Bearer test-token',
319
+ 'Content-Type': 'application/json',
320
+ }),
321
+ }));
322
+ });
323
+ });
324
+ });
325
+ });
@@ -0,0 +1 @@
1
+ export {};