@masonator/coolify-mcp 0.2.18 → 0.3.1

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.
@@ -0,0 +1,502 @@
1
+ /**
2
+ * Coolify API Client
3
+ * Complete HTTP client for the Coolify API v1
4
+ */
5
+ /**
6
+ * Remove undefined values and false booleans from an object.
7
+ * Coolify API rejects requests with explicit false values for optional booleans.
8
+ */
9
+ function cleanRequestData(data) {
10
+ const cleaned = {};
11
+ for (const [key, value] of Object.entries(data)) {
12
+ if (value !== undefined && value !== false) {
13
+ cleaned[key] = value;
14
+ }
15
+ }
16
+ return cleaned;
17
+ }
18
+ /**
19
+ * HTTP client for the Coolify API
20
+ */
21
+ export class CoolifyClient {
22
+ constructor(config) {
23
+ if (!config.baseUrl) {
24
+ throw new Error('Coolify base URL is required');
25
+ }
26
+ if (!config.accessToken) {
27
+ throw new Error('Coolify access token is required');
28
+ }
29
+ this.baseUrl = config.baseUrl.replace(/\/$/, '');
30
+ this.accessToken = config.accessToken;
31
+ }
32
+ // ===========================================================================
33
+ // Private HTTP methods
34
+ // ===========================================================================
35
+ async request(path, options = {}) {
36
+ const url = `${this.baseUrl}/api/v1${path}`;
37
+ try {
38
+ const response = await fetch(url, {
39
+ ...options,
40
+ headers: {
41
+ 'Content-Type': 'application/json',
42
+ Authorization: `Bearer ${this.accessToken}`,
43
+ ...options.headers,
44
+ },
45
+ });
46
+ // Handle empty responses (204 No Content, etc.)
47
+ const text = await response.text();
48
+ const data = text ? JSON.parse(text) : {};
49
+ if (!response.ok) {
50
+ const error = data;
51
+ throw new Error(error.message || `HTTP ${response.status}: ${response.statusText}`);
52
+ }
53
+ return data;
54
+ }
55
+ catch (error) {
56
+ if (error instanceof TypeError && error.message.includes('fetch')) {
57
+ throw new Error(`Failed to connect to Coolify server at ${this.baseUrl}. Please check if the server is running and accessible.`);
58
+ }
59
+ throw error;
60
+ }
61
+ }
62
+ buildQueryString(params) {
63
+ const searchParams = new URLSearchParams();
64
+ for (const [key, value] of Object.entries(params)) {
65
+ if (value !== undefined && value !== null) {
66
+ searchParams.set(key, String(value));
67
+ }
68
+ }
69
+ const queryString = searchParams.toString();
70
+ return queryString ? `?${queryString}` : '';
71
+ }
72
+ // ===========================================================================
73
+ // Health & Version
74
+ // ===========================================================================
75
+ async getVersion() {
76
+ // The /version endpoint returns plain text, not JSON
77
+ const url = `${this.baseUrl}/api/v1/version`;
78
+ const response = await fetch(url, {
79
+ headers: {
80
+ Authorization: `Bearer ${this.accessToken}`,
81
+ },
82
+ });
83
+ if (!response.ok) {
84
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
85
+ }
86
+ const version = await response.text();
87
+ return { version: version.trim() };
88
+ }
89
+ async validateConnection() {
90
+ try {
91
+ await this.getVersion();
92
+ }
93
+ catch (error) {
94
+ throw new Error(`Failed to connect to Coolify server: ${error instanceof Error ? error.message : 'Unknown error'}`);
95
+ }
96
+ }
97
+ // ===========================================================================
98
+ // Server endpoints
99
+ // ===========================================================================
100
+ async listServers() {
101
+ return this.request('/servers');
102
+ }
103
+ async getServer(uuid) {
104
+ return this.request(`/servers/${uuid}`);
105
+ }
106
+ async createServer(data) {
107
+ return this.request('/servers', {
108
+ method: 'POST',
109
+ body: JSON.stringify(data),
110
+ });
111
+ }
112
+ async updateServer(uuid, data) {
113
+ return this.request(`/servers/${uuid}`, {
114
+ method: 'PATCH',
115
+ body: JSON.stringify(data),
116
+ });
117
+ }
118
+ async deleteServer(uuid) {
119
+ return this.request(`/servers/${uuid}`, {
120
+ method: 'DELETE',
121
+ });
122
+ }
123
+ async getServerResources(uuid) {
124
+ return this.request(`/servers/${uuid}/resources`);
125
+ }
126
+ async getServerDomains(uuid) {
127
+ return this.request(`/servers/${uuid}/domains`);
128
+ }
129
+ async validateServer(uuid) {
130
+ return this.request(`/servers/${uuid}/validate`);
131
+ }
132
+ // ===========================================================================
133
+ // Project endpoints
134
+ // ===========================================================================
135
+ async listProjects() {
136
+ return this.request('/projects');
137
+ }
138
+ async getProject(uuid) {
139
+ return this.request(`/projects/${uuid}`);
140
+ }
141
+ async createProject(data) {
142
+ return this.request('/projects', {
143
+ method: 'POST',
144
+ body: JSON.stringify(data),
145
+ });
146
+ }
147
+ async updateProject(uuid, data) {
148
+ return this.request(`/projects/${uuid}`, {
149
+ method: 'PATCH',
150
+ body: JSON.stringify(data),
151
+ });
152
+ }
153
+ async deleteProject(uuid) {
154
+ return this.request(`/projects/${uuid}`, {
155
+ method: 'DELETE',
156
+ });
157
+ }
158
+ // ===========================================================================
159
+ // Environment endpoints
160
+ // ===========================================================================
161
+ async listProjectEnvironments(projectUuid) {
162
+ return this.request(`/projects/${projectUuid}/environments`);
163
+ }
164
+ async getProjectEnvironment(projectUuid, environmentNameOrUuid) {
165
+ return this.request(`/projects/${projectUuid}/${environmentNameOrUuid}`);
166
+ }
167
+ async createProjectEnvironment(projectUuid, data) {
168
+ return this.request(`/projects/${projectUuid}/environments`, {
169
+ method: 'POST',
170
+ body: JSON.stringify(data),
171
+ });
172
+ }
173
+ async deleteProjectEnvironment(environmentUuid) {
174
+ return this.request(`/projects/environments/${environmentUuid}`, {
175
+ method: 'DELETE',
176
+ });
177
+ }
178
+ // ===========================================================================
179
+ // Application endpoints
180
+ // ===========================================================================
181
+ async listApplications() {
182
+ return this.request('/applications');
183
+ }
184
+ async getApplication(uuid) {
185
+ return this.request(`/applications/${uuid}`);
186
+ }
187
+ async createApplicationPublic(data) {
188
+ return this.request('/applications/public', {
189
+ method: 'POST',
190
+ body: JSON.stringify(data),
191
+ });
192
+ }
193
+ async createApplicationPrivateGH(data) {
194
+ return this.request('/applications/private-github-app', {
195
+ method: 'POST',
196
+ body: JSON.stringify(data),
197
+ });
198
+ }
199
+ async createApplicationPrivateKey(data) {
200
+ return this.request('/applications/private-deploy-key', {
201
+ method: 'POST',
202
+ body: JSON.stringify(data),
203
+ });
204
+ }
205
+ async createApplicationDockerfile(data) {
206
+ return this.request('/applications/dockerfile', {
207
+ method: 'POST',
208
+ body: JSON.stringify(data),
209
+ });
210
+ }
211
+ async createApplicationDockerImage(data) {
212
+ return this.request('/applications/dockerimage', {
213
+ method: 'POST',
214
+ body: JSON.stringify(data),
215
+ });
216
+ }
217
+ async createApplicationDockerCompose(data) {
218
+ return this.request('/applications/dockercompose', {
219
+ method: 'POST',
220
+ body: JSON.stringify(data),
221
+ });
222
+ }
223
+ async updateApplication(uuid, data) {
224
+ return this.request(`/applications/${uuid}`, {
225
+ method: 'PATCH',
226
+ body: JSON.stringify(data),
227
+ });
228
+ }
229
+ async deleteApplication(uuid, options) {
230
+ const query = this.buildQueryString({
231
+ delete_configurations: options?.deleteConfigurations,
232
+ delete_volumes: options?.deleteVolumes,
233
+ docker_cleanup: options?.dockerCleanup,
234
+ delete_connected_networks: options?.deleteConnectedNetworks,
235
+ });
236
+ return this.request(`/applications/${uuid}${query}`, {
237
+ method: 'DELETE',
238
+ });
239
+ }
240
+ async getApplicationLogs(uuid, lines = 100) {
241
+ return this.request(`/applications/${uuid}/logs?lines=${lines}`);
242
+ }
243
+ async startApplication(uuid, options) {
244
+ const query = this.buildQueryString({
245
+ force: options?.force,
246
+ instant_deploy: options?.instant_deploy,
247
+ });
248
+ return this.request(`/applications/${uuid}/start${query}`, {
249
+ method: 'POST',
250
+ });
251
+ }
252
+ async stopApplication(uuid) {
253
+ return this.request(`/applications/${uuid}/stop`, {
254
+ method: 'POST',
255
+ });
256
+ }
257
+ async restartApplication(uuid) {
258
+ return this.request(`/applications/${uuid}/restart`, {
259
+ method: 'POST',
260
+ });
261
+ }
262
+ // ===========================================================================
263
+ // Application Environment Variables
264
+ // ===========================================================================
265
+ async listApplicationEnvVars(uuid) {
266
+ return this.request(`/applications/${uuid}/envs`);
267
+ }
268
+ async createApplicationEnvVar(uuid, data) {
269
+ return this.request(`/applications/${uuid}/envs`, {
270
+ method: 'POST',
271
+ body: JSON.stringify(cleanRequestData(data)),
272
+ });
273
+ }
274
+ async updateApplicationEnvVar(uuid, data) {
275
+ return this.request(`/applications/${uuid}/envs`, {
276
+ method: 'PATCH',
277
+ body: JSON.stringify(cleanRequestData(data)),
278
+ });
279
+ }
280
+ async bulkUpdateApplicationEnvVars(uuid, data) {
281
+ return this.request(`/applications/${uuid}/envs/bulk`, {
282
+ method: 'PATCH',
283
+ body: JSON.stringify(data),
284
+ });
285
+ }
286
+ async deleteApplicationEnvVar(uuid, envUuid) {
287
+ return this.request(`/applications/${uuid}/envs/${envUuid}`, {
288
+ method: 'DELETE',
289
+ });
290
+ }
291
+ // ===========================================================================
292
+ // Database endpoints
293
+ // ===========================================================================
294
+ async listDatabases() {
295
+ return this.request('/databases');
296
+ }
297
+ async getDatabase(uuid) {
298
+ return this.request(`/databases/${uuid}`);
299
+ }
300
+ async updateDatabase(uuid, data) {
301
+ return this.request(`/databases/${uuid}`, {
302
+ method: 'PATCH',
303
+ body: JSON.stringify(data),
304
+ });
305
+ }
306
+ async deleteDatabase(uuid, options) {
307
+ const query = this.buildQueryString({
308
+ delete_configurations: options?.deleteConfigurations,
309
+ delete_volumes: options?.deleteVolumes,
310
+ docker_cleanup: options?.dockerCleanup,
311
+ delete_connected_networks: options?.deleteConnectedNetworks,
312
+ });
313
+ return this.request(`/databases/${uuid}${query}`, {
314
+ method: 'DELETE',
315
+ });
316
+ }
317
+ async startDatabase(uuid) {
318
+ return this.request(`/databases/${uuid}/start`, {
319
+ method: 'POST',
320
+ });
321
+ }
322
+ async stopDatabase(uuid) {
323
+ return this.request(`/databases/${uuid}/stop`, {
324
+ method: 'POST',
325
+ });
326
+ }
327
+ async restartDatabase(uuid) {
328
+ return this.request(`/databases/${uuid}/restart`, {
329
+ method: 'POST',
330
+ });
331
+ }
332
+ // ===========================================================================
333
+ // Database Backups
334
+ // ===========================================================================
335
+ async listDatabaseBackups(uuid) {
336
+ return this.request(`/databases/${uuid}/backups`);
337
+ }
338
+ async createDatabaseBackup(uuid, data) {
339
+ return this.request(`/databases/${uuid}/backups`, {
340
+ method: 'POST',
341
+ body: JSON.stringify(data),
342
+ });
343
+ }
344
+ // ===========================================================================
345
+ // Service endpoints
346
+ // ===========================================================================
347
+ async listServices() {
348
+ return this.request('/services');
349
+ }
350
+ async getService(uuid) {
351
+ return this.request(`/services/${uuid}`);
352
+ }
353
+ async createService(data) {
354
+ return this.request('/services', {
355
+ method: 'POST',
356
+ body: JSON.stringify(data),
357
+ });
358
+ }
359
+ async updateService(uuid, data) {
360
+ return this.request(`/services/${uuid}`, {
361
+ method: 'PATCH',
362
+ body: JSON.stringify(data),
363
+ });
364
+ }
365
+ async deleteService(uuid, options) {
366
+ const query = this.buildQueryString({
367
+ delete_configurations: options?.deleteConfigurations,
368
+ delete_volumes: options?.deleteVolumes,
369
+ docker_cleanup: options?.dockerCleanup,
370
+ delete_connected_networks: options?.deleteConnectedNetworks,
371
+ });
372
+ return this.request(`/services/${uuid}${query}`, {
373
+ method: 'DELETE',
374
+ });
375
+ }
376
+ async startService(uuid) {
377
+ return this.request(`/services/${uuid}/start`, {
378
+ method: 'GET',
379
+ });
380
+ }
381
+ async stopService(uuid) {
382
+ return this.request(`/services/${uuid}/stop`, {
383
+ method: 'GET',
384
+ });
385
+ }
386
+ async restartService(uuid) {
387
+ return this.request(`/services/${uuid}/restart`, {
388
+ method: 'GET',
389
+ });
390
+ }
391
+ // ===========================================================================
392
+ // Service Environment Variables
393
+ // ===========================================================================
394
+ async listServiceEnvVars(uuid) {
395
+ return this.request(`/services/${uuid}/envs`);
396
+ }
397
+ async createServiceEnvVar(uuid, data) {
398
+ return this.request(`/services/${uuid}/envs`, {
399
+ method: 'POST',
400
+ body: JSON.stringify(cleanRequestData(data)),
401
+ });
402
+ }
403
+ async updateServiceEnvVar(uuid, data) {
404
+ return this.request(`/services/${uuid}/envs`, {
405
+ method: 'PATCH',
406
+ body: JSON.stringify(cleanRequestData(data)),
407
+ });
408
+ }
409
+ async deleteServiceEnvVar(uuid, envUuid) {
410
+ return this.request(`/services/${uuid}/envs/${envUuid}`, {
411
+ method: 'DELETE',
412
+ });
413
+ }
414
+ // ===========================================================================
415
+ // Deployment endpoints
416
+ // ===========================================================================
417
+ async listDeployments() {
418
+ return this.request('/deployments');
419
+ }
420
+ async getDeployment(uuid) {
421
+ return this.request(`/deployments/${uuid}`);
422
+ }
423
+ async deployByTagOrUuid(tagOrUuid, force = false) {
424
+ return this.request(`/deploy?tag=${encodeURIComponent(tagOrUuid)}&force=${force}`, { method: 'GET' });
425
+ }
426
+ async listApplicationDeployments(appUuid) {
427
+ return this.request(`/applications/${appUuid}/deployments`);
428
+ }
429
+ // ===========================================================================
430
+ // Team endpoints
431
+ // ===========================================================================
432
+ async listTeams() {
433
+ return this.request('/teams');
434
+ }
435
+ async getTeam(id) {
436
+ return this.request(`/teams/${id}`);
437
+ }
438
+ async getTeamMembers(id) {
439
+ return this.request(`/teams/${id}/members`);
440
+ }
441
+ async getCurrentTeam() {
442
+ return this.request('/teams/current');
443
+ }
444
+ async getCurrentTeamMembers() {
445
+ return this.request('/teams/current/members');
446
+ }
447
+ // ===========================================================================
448
+ // Private Key endpoints
449
+ // ===========================================================================
450
+ async listPrivateKeys() {
451
+ return this.request('/security/keys');
452
+ }
453
+ async getPrivateKey(uuid) {
454
+ return this.request(`/security/keys/${uuid}`);
455
+ }
456
+ async createPrivateKey(data) {
457
+ return this.request('/security/keys', {
458
+ method: 'POST',
459
+ body: JSON.stringify(data),
460
+ });
461
+ }
462
+ async updatePrivateKey(uuid, data) {
463
+ return this.request(`/security/keys/${uuid}`, {
464
+ method: 'PATCH',
465
+ body: JSON.stringify(data),
466
+ });
467
+ }
468
+ async deletePrivateKey(uuid) {
469
+ return this.request(`/security/keys/${uuid}`, {
470
+ method: 'DELETE',
471
+ });
472
+ }
473
+ // ===========================================================================
474
+ // Cloud Token endpoints (Hetzner, DigitalOcean)
475
+ // ===========================================================================
476
+ async listCloudTokens() {
477
+ return this.request('/cloud-tokens');
478
+ }
479
+ async getCloudToken(uuid) {
480
+ return this.request(`/cloud-tokens/${uuid}`);
481
+ }
482
+ async createCloudToken(data) {
483
+ return this.request('/cloud-tokens', {
484
+ method: 'POST',
485
+ body: JSON.stringify(data),
486
+ });
487
+ }
488
+ async updateCloudToken(uuid, data) {
489
+ return this.request(`/cloud-tokens/${uuid}`, {
490
+ method: 'PATCH',
491
+ body: JSON.stringify(data),
492
+ });
493
+ }
494
+ async deleteCloudToken(uuid) {
495
+ return this.request(`/cloud-tokens/${uuid}`, {
496
+ method: 'DELETE',
497
+ });
498
+ }
499
+ async validateCloudToken(uuid) {
500
+ return this.request(`/cloud-tokens/${uuid}/validate`, { method: 'POST' });
501
+ }
502
+ }
@@ -0,0 +1,29 @@
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.
17
+ */
18
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
19
+ import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
20
+ import type { CoolifyConfig } from '../types/coolify.js';
21
+ /**
22
+ * Coolify MCP Server
23
+ */
24
+ export declare class CoolifyMcpServer extends McpServer {
25
+ private readonly client;
26
+ constructor(config: CoolifyConfig);
27
+ connect(transport: Transport): Promise<void>;
28
+ private registerTools;
29
+ }