@masonator/coolify-mcp 2.4.0 → 2.5.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 +1 -1
- package/dist/__tests__/mcp-server.test.js +47 -1
- package/dist/lib/mcp-server.d.ts +5 -0
- package/dist/lib/mcp-server.js +100 -24
- package/dist/types/coolify.d.ts +6 -2
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -43,7 +43,7 @@ The server uses **85% fewer tokens** than a naive implementation (6,600 vs 43,00
|
|
|
43
43
|
### Prerequisites
|
|
44
44
|
|
|
45
45
|
- Node.js >= 18
|
|
46
|
-
- A running Coolify instance
|
|
46
|
+
- A running Coolify instance (tested with v4.0.0-beta.460)
|
|
47
47
|
- Coolify API access token (generate in Coolify Settings > API)
|
|
48
48
|
|
|
49
49
|
### Claude Desktop
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* These tests verify MCP server instantiation and structure.
|
|
7
7
|
*/
|
|
8
8
|
import { describe, it, expect, beforeEach } from '@jest/globals';
|
|
9
|
-
import { CoolifyMcpServer } from '../lib/mcp-server.js';
|
|
9
|
+
import { CoolifyMcpServer, truncateLogs } from '../lib/mcp-server.js';
|
|
10
10
|
describe('CoolifyMcpServer v2', () => {
|
|
11
11
|
let server;
|
|
12
12
|
beforeEach(() => {
|
|
@@ -140,3 +140,49 @@ describe('CoolifyMcpServer v2', () => {
|
|
|
140
140
|
});
|
|
141
141
|
});
|
|
142
142
|
});
|
|
143
|
+
describe('truncateLogs', () => {
|
|
144
|
+
it('should return logs unchanged when within limits', () => {
|
|
145
|
+
const logs = 'line1\nline2\nline3';
|
|
146
|
+
const result = truncateLogs(logs, 200, 50000);
|
|
147
|
+
expect(result).toBe(logs);
|
|
148
|
+
});
|
|
149
|
+
it('should truncate to last N lines', () => {
|
|
150
|
+
const logs = 'line1\nline2\nline3\nline4\nline5';
|
|
151
|
+
const result = truncateLogs(logs, 3, 50000);
|
|
152
|
+
expect(result).toBe('line3\nline4\nline5');
|
|
153
|
+
});
|
|
154
|
+
it('should truncate by character limit when lines are huge', () => {
|
|
155
|
+
const hugeLine = 'x'.repeat(100);
|
|
156
|
+
const logs = `${hugeLine}\n${hugeLine}\n${hugeLine}`;
|
|
157
|
+
const result = truncateLogs(logs, 200, 50);
|
|
158
|
+
expect(result.length).toBeLessThanOrEqual(50);
|
|
159
|
+
expect(result.startsWith('...[truncated]...')).toBe(true);
|
|
160
|
+
});
|
|
161
|
+
it('should not add truncation prefix when under char limit', () => {
|
|
162
|
+
const logs = 'line1\nline2\nline3';
|
|
163
|
+
const result = truncateLogs(logs, 200, 50000);
|
|
164
|
+
expect(result.startsWith('...[truncated]...')).toBe(false);
|
|
165
|
+
});
|
|
166
|
+
it('should handle empty logs', () => {
|
|
167
|
+
const result = truncateLogs('', 200, 50000);
|
|
168
|
+
expect(result).toBe('');
|
|
169
|
+
});
|
|
170
|
+
it('should use default limits when not specified', () => {
|
|
171
|
+
const logs = 'line1\nline2';
|
|
172
|
+
const result = truncateLogs(logs);
|
|
173
|
+
expect(result).toBe(logs);
|
|
174
|
+
});
|
|
175
|
+
it('should respect custom line limit', () => {
|
|
176
|
+
const lines = Array.from({ length: 300 }, (_, i) => `line${i + 1}`).join('\n');
|
|
177
|
+
const result = truncateLogs(lines, 50, 50000);
|
|
178
|
+
const resultLines = result.split('\n');
|
|
179
|
+
expect(resultLines.length).toBe(50);
|
|
180
|
+
expect(resultLines[0]).toBe('line251');
|
|
181
|
+
expect(resultLines[49]).toBe('line300');
|
|
182
|
+
});
|
|
183
|
+
it('should respect custom char limit', () => {
|
|
184
|
+
const logs = 'x'.repeat(1000);
|
|
185
|
+
const result = truncateLogs(logs, 200, 100);
|
|
186
|
+
expect(result.length).toBe(100);
|
|
187
|
+
});
|
|
188
|
+
});
|
package/dist/lib/mcp-server.d.ts
CHANGED
|
@@ -5,6 +5,11 @@
|
|
|
5
5
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
6
6
|
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
|
7
7
|
import type { CoolifyConfig } from '../types/coolify.js';
|
|
8
|
+
/**
|
|
9
|
+
* Truncate logs by line count and character count.
|
|
10
|
+
* Exported for testing.
|
|
11
|
+
*/
|
|
12
|
+
export declare function truncateLogs(logs: string, lineLimit?: number, charLimit?: number): string;
|
|
8
13
|
export declare class CoolifyMcpServer extends McpServer {
|
|
9
14
|
private readonly client;
|
|
10
15
|
constructor(config: CoolifyConfig);
|
package/dist/lib/mcp-server.js
CHANGED
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
* Coolify MCP Server v2.4.0
|
|
3
3
|
* Consolidated tools for efficient token usage
|
|
4
4
|
*/
|
|
5
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
6
5
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
7
6
|
import { z } from 'zod';
|
|
8
7
|
import { CoolifyClient, } from './coolify-client.js';
|
|
9
|
-
const VERSION = '2.
|
|
8
|
+
const VERSION = '2.5.0';
|
|
10
9
|
/** Wrap handler with error handling */
|
|
11
10
|
function wrap(fn) {
|
|
12
11
|
return fn()
|
|
@@ -22,6 +21,24 @@ function wrap(fn) {
|
|
|
22
21
|
],
|
|
23
22
|
}));
|
|
24
23
|
}
|
|
24
|
+
const TRUNCATION_PREFIX = '...[truncated]...\n';
|
|
25
|
+
/**
|
|
26
|
+
* Truncate logs by line count and character count.
|
|
27
|
+
* Exported for testing.
|
|
28
|
+
*/
|
|
29
|
+
export function truncateLogs(logs, lineLimit = 200, charLimit = 50000) {
|
|
30
|
+
// First: limit by lines
|
|
31
|
+
const logLines = logs.split('\n');
|
|
32
|
+
const limitedLines = logLines.slice(-lineLimit);
|
|
33
|
+
let truncatedLogs = limitedLines.join('\n');
|
|
34
|
+
// Second: limit by characters (safety net for huge lines)
|
|
35
|
+
if (truncatedLogs.length > charLimit) {
|
|
36
|
+
// Account for prefix length to stay within limit
|
|
37
|
+
const prefixLen = TRUNCATION_PREFIX.length;
|
|
38
|
+
truncatedLogs = TRUNCATION_PREFIX + truncatedLogs.slice(-(charLimit - prefixLen));
|
|
39
|
+
}
|
|
40
|
+
return truncatedLogs;
|
|
41
|
+
}
|
|
25
42
|
export class CoolifyMcpServer extends McpServer {
|
|
26
43
|
constructor(config) {
|
|
27
44
|
super({ name: 'coolify', version: VERSION });
|
|
@@ -205,8 +222,7 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
205
222
|
// Delete fields
|
|
206
223
|
delete_volumes: z.boolean().optional(),
|
|
207
224
|
}, async (args) => {
|
|
208
|
-
|
|
209
|
-
const { action, uuid, delete_volumes, ...apiData } = args;
|
|
225
|
+
const { action, uuid, delete_volumes } = args;
|
|
210
226
|
switch (action) {
|
|
211
227
|
case 'create_public':
|
|
212
228
|
if (!args.project_uuid ||
|
|
@@ -224,7 +240,19 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
224
240
|
],
|
|
225
241
|
};
|
|
226
242
|
}
|
|
227
|
-
return wrap(() => this.client.createApplicationPublic(
|
|
243
|
+
return wrap(() => this.client.createApplicationPublic({
|
|
244
|
+
project_uuid: args.project_uuid,
|
|
245
|
+
server_uuid: args.server_uuid,
|
|
246
|
+
git_repository: args.git_repository,
|
|
247
|
+
git_branch: args.git_branch,
|
|
248
|
+
build_pack: args.build_pack,
|
|
249
|
+
ports_exposes: args.ports_exposes,
|
|
250
|
+
environment_name: args.environment_name,
|
|
251
|
+
environment_uuid: args.environment_uuid,
|
|
252
|
+
name: args.name,
|
|
253
|
+
description: args.description,
|
|
254
|
+
fqdn: args.fqdn,
|
|
255
|
+
}));
|
|
228
256
|
case 'create_github':
|
|
229
257
|
if (!args.project_uuid ||
|
|
230
258
|
!args.server_uuid ||
|
|
@@ -240,7 +268,20 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
240
268
|
],
|
|
241
269
|
};
|
|
242
270
|
}
|
|
243
|
-
return wrap(() => this.client.createApplicationPrivateGH(
|
|
271
|
+
return wrap(() => this.client.createApplicationPrivateGH({
|
|
272
|
+
project_uuid: args.project_uuid,
|
|
273
|
+
server_uuid: args.server_uuid,
|
|
274
|
+
github_app_uuid: args.github_app_uuid,
|
|
275
|
+
git_repository: args.git_repository,
|
|
276
|
+
git_branch: args.git_branch,
|
|
277
|
+
build_pack: args.build_pack,
|
|
278
|
+
ports_exposes: args.ports_exposes,
|
|
279
|
+
environment_name: args.environment_name,
|
|
280
|
+
environment_uuid: args.environment_uuid,
|
|
281
|
+
name: args.name,
|
|
282
|
+
description: args.description,
|
|
283
|
+
fqdn: args.fqdn,
|
|
284
|
+
}));
|
|
244
285
|
case 'create_key':
|
|
245
286
|
if (!args.project_uuid ||
|
|
246
287
|
!args.server_uuid ||
|
|
@@ -256,7 +297,20 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
256
297
|
],
|
|
257
298
|
};
|
|
258
299
|
}
|
|
259
|
-
return wrap(() => this.client.createApplicationPrivateKey(
|
|
300
|
+
return wrap(() => this.client.createApplicationPrivateKey({
|
|
301
|
+
project_uuid: args.project_uuid,
|
|
302
|
+
server_uuid: args.server_uuid,
|
|
303
|
+
private_key_uuid: args.private_key_uuid,
|
|
304
|
+
git_repository: args.git_repository,
|
|
305
|
+
git_branch: args.git_branch,
|
|
306
|
+
build_pack: args.build_pack,
|
|
307
|
+
ports_exposes: args.ports_exposes,
|
|
308
|
+
environment_name: args.environment_name,
|
|
309
|
+
environment_uuid: args.environment_uuid,
|
|
310
|
+
name: args.name,
|
|
311
|
+
description: args.description,
|
|
312
|
+
fqdn: args.fqdn,
|
|
313
|
+
}));
|
|
260
314
|
case 'create_dockerimage':
|
|
261
315
|
if (!args.project_uuid ||
|
|
262
316
|
!args.server_uuid ||
|
|
@@ -271,11 +325,25 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
271
325
|
],
|
|
272
326
|
};
|
|
273
327
|
}
|
|
274
|
-
return wrap(() => this.client.createApplicationDockerImage(
|
|
275
|
-
|
|
328
|
+
return wrap(() => this.client.createApplicationDockerImage({
|
|
329
|
+
project_uuid: args.project_uuid,
|
|
330
|
+
server_uuid: args.server_uuid,
|
|
331
|
+
docker_registry_image_name: args.docker_registry_image_name,
|
|
332
|
+
ports_exposes: args.ports_exposes,
|
|
333
|
+
docker_registry_image_tag: args.docker_registry_image_tag,
|
|
334
|
+
environment_name: args.environment_name,
|
|
335
|
+
environment_uuid: args.environment_uuid,
|
|
336
|
+
name: args.name,
|
|
337
|
+
description: args.description,
|
|
338
|
+
fqdn: args.fqdn,
|
|
339
|
+
}));
|
|
340
|
+
case 'update': {
|
|
276
341
|
if (!uuid)
|
|
277
342
|
return { content: [{ type: 'text', text: 'Error: uuid required' }] };
|
|
278
|
-
|
|
343
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
344
|
+
const { action: _, uuid: __, delete_volumes: ___, ...updateData } = args;
|
|
345
|
+
return wrap(() => this.client.updateApplication(uuid, updateData));
|
|
346
|
+
}
|
|
279
347
|
case 'delete':
|
|
280
348
|
if (!uuid)
|
|
281
349
|
return { content: [{ type: 'text', text: 'Error: uuid required' }] };
|
|
@@ -378,8 +446,7 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
378
446
|
docker_compose_raw: z.string().optional(),
|
|
379
447
|
delete_volumes: z.boolean().optional(),
|
|
380
448
|
}, async (args) => {
|
|
381
|
-
|
|
382
|
-
const { action, uuid, delete_volumes, ...apiData } = args;
|
|
449
|
+
const { action, uuid, delete_volumes } = args;
|
|
383
450
|
switch (action) {
|
|
384
451
|
case 'create':
|
|
385
452
|
if (!args.server_uuid || !args.project_uuid) {
|
|
@@ -389,11 +456,23 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
389
456
|
],
|
|
390
457
|
};
|
|
391
458
|
}
|
|
392
|
-
return wrap(() => this.client.createService(
|
|
393
|
-
|
|
459
|
+
return wrap(() => this.client.createService({
|
|
460
|
+
project_uuid: args.project_uuid,
|
|
461
|
+
server_uuid: args.server_uuid,
|
|
462
|
+
type: args.type,
|
|
463
|
+
name: args.name,
|
|
464
|
+
description: args.description,
|
|
465
|
+
environment_name: args.environment_name,
|
|
466
|
+
instant_deploy: args.instant_deploy,
|
|
467
|
+
docker_compose_raw: args.docker_compose_raw,
|
|
468
|
+
}));
|
|
469
|
+
case 'update': {
|
|
394
470
|
if (!uuid)
|
|
395
471
|
return { content: [{ type: 'text', text: 'Error: uuid required' }] };
|
|
396
|
-
|
|
472
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
473
|
+
const { action: _, uuid: __, delete_volumes: ___, ...updateData } = args;
|
|
474
|
+
return wrap(() => this.client.updateService(uuid, updateData));
|
|
475
|
+
}
|
|
397
476
|
case 'delete':
|
|
398
477
|
if (!uuid)
|
|
399
478
|
return { content: [{ type: 'text', text: 'Error: uuid required' }] };
|
|
@@ -483,21 +562,18 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
483
562
|
// =========================================================================
|
|
484
563
|
this.tool('list_deployments', 'List deployments (summary)', { page: z.number().optional(), per_page: z.number().optional() }, async ({ page, per_page }) => wrap(() => this.client.listDeployments({ page, per_page, summary: true })));
|
|
485
564
|
this.tool('deploy', 'Deploy by tag/UUID', { tag_or_uuid: z.string(), force: z.boolean().optional() }, async ({ tag_or_uuid, force }) => wrap(() => this.client.deployByTagOrUuid(tag_or_uuid, force)));
|
|
486
|
-
this.tool('deployment', 'Manage deployment: get/cancel/list_for_app', {
|
|
565
|
+
this.tool('deployment', 'Manage deployment: get/cancel/list_for_app (logs truncated to last 200 lines/50K chars by default)', {
|
|
487
566
|
action: z.enum(['get', 'cancel', 'list_for_app']),
|
|
488
567
|
uuid: z.string(),
|
|
489
|
-
lines: z.number().optional(), // Limit log output to last N lines (
|
|
490
|
-
|
|
568
|
+
lines: z.number().optional(), // Limit log output to last N lines (default: 200)
|
|
569
|
+
max_chars: z.number().optional(), // Limit log output to last N chars (default: 50000)
|
|
570
|
+
}, async ({ action, uuid, lines, max_chars }) => {
|
|
491
571
|
switch (action) {
|
|
492
572
|
case 'get':
|
|
493
573
|
return wrap(async () => {
|
|
494
574
|
const deployment = await this.client.getDeployment(uuid);
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
const logLines = deployment.logs.split('\n');
|
|
498
|
-
if (logLines.length > lines) {
|
|
499
|
-
deployment.logs = logLines.slice(-lines).join('\n');
|
|
500
|
-
}
|
|
575
|
+
if (deployment.logs) {
|
|
576
|
+
deployment.logs = truncateLogs(deployment.logs, lines ?? 200, max_chars ?? 50000);
|
|
501
577
|
}
|
|
502
578
|
return deployment;
|
|
503
579
|
});
|
package/dist/types/coolify.d.ts
CHANGED
|
@@ -224,11 +224,15 @@ export interface CreateApplicationPublicRequest {
|
|
|
224
224
|
start_command?: string;
|
|
225
225
|
instant_deploy?: boolean;
|
|
226
226
|
}
|
|
227
|
-
export interface CreateApplicationPrivateGHRequest extends CreateApplicationPublicRequest {
|
|
227
|
+
export interface CreateApplicationPrivateGHRequest extends Omit<CreateApplicationPublicRequest, 'build_pack' | 'ports_exposes'> {
|
|
228
228
|
github_app_uuid: string;
|
|
229
|
+
build_pack?: BuildPack;
|
|
230
|
+
ports_exposes?: string;
|
|
229
231
|
}
|
|
230
|
-
export interface CreateApplicationPrivateKeyRequest extends CreateApplicationPublicRequest {
|
|
232
|
+
export interface CreateApplicationPrivateKeyRequest extends Omit<CreateApplicationPublicRequest, 'build_pack' | 'ports_exposes'> {
|
|
231
233
|
private_key_uuid: string;
|
|
234
|
+
build_pack?: BuildPack;
|
|
235
|
+
ports_exposes?: string;
|
|
232
236
|
}
|
|
233
237
|
export interface CreateApplicationDockerfileRequest {
|
|
234
238
|
project_uuid: string;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@masonator/coolify-mcp",
|
|
3
3
|
"scope": "@masonator",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.5.0",
|
|
5
5
|
"description": "MCP server implementation for Coolify",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.js",
|
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
"globals": "^17.0.0",
|
|
60
60
|
"husky": "^9.0.11",
|
|
61
61
|
"jest": "^29.7.0",
|
|
62
|
+
"jest-junit": "^16.0.0",
|
|
62
63
|
"lint-staged": "^16.2.7",
|
|
63
64
|
"markdownlint-cli2": "^0.20.0",
|
|
64
65
|
"prettier": "^3.5.3",
|