@masonator/coolify-mcp 2.7.3 → 2.8.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
@@ -103,13 +103,14 @@ The Coolify API returns extremely verbose responses - a single application can c
103
103
 
104
104
  ### Response Size Comparison
105
105
 
106
- | Endpoint | Full Response | Summary Response | Reduction |
107
- | --------------------- | ------------- | ---------------- | --------- |
108
- | list_applications | ~170KB | ~4.4KB | **97%** |
109
- | list_services | ~367KB | ~1.2KB | **99%** |
110
- | list_servers | ~4KB | ~0.4KB | **90%** |
111
- | list_application_envs | ~3KB/var | ~0.1KB/var | **97%** |
112
- | deployment get | ~13KB | ~1KB | **92%** |
106
+ | Endpoint | Full Response | Summary Response | Reduction |
107
+ | ----------------------- | ------------- | ---------------- | --------- |
108
+ | list_applications | ~170KB | ~4.4KB | **97%** |
109
+ | list_services | ~367KB | ~1.2KB | **99%** |
110
+ | list_servers | ~4KB | ~0.4KB | **90%** |
111
+ | list_application_envs | ~3KB/var | ~0.1KB/var | **97%** |
112
+ | deployment get | ~13KB | ~1KB | **92%** |
113
+ | deployment list_for_app | ~1MB | ~4KB | **99.6%** |
113
114
 
114
115
  ### HATEOAS-style Response Actions
115
116
 
@@ -849,6 +849,87 @@ describe('CoolifyClient', () => {
849
849
  expect(callBody.domains).toBe('https://app.example.com');
850
850
  expect(callBody.fqdn).toBeUndefined();
851
851
  });
852
+ it('should map fqdn to domains in createApplicationDockerImage', async () => {
853
+ mockFetch.mockResolvedValueOnce(mockResponse({ uuid: 'new-app-uuid' }));
854
+ await client.createApplicationDockerImage({
855
+ project_uuid: 'proj-uuid',
856
+ server_uuid: 'server-uuid',
857
+ docker_registry_image_name: 'traefik/whoami',
858
+ ports_exposes: '80',
859
+ fqdn: 'https://whoami.example.com',
860
+ });
861
+ const callBody = JSON.parse(mockFetch.mock.calls[0][1]?.body);
862
+ expect(callBody.domains).toBe('https://whoami.example.com');
863
+ expect(callBody.fqdn).toBeUndefined();
864
+ });
865
+ it('should map fqdn to domains in createApplicationDockerfile', async () => {
866
+ mockFetch.mockResolvedValueOnce(mockResponse({ uuid: 'new-app-uuid' }));
867
+ await client.createApplicationDockerfile({
868
+ project_uuid: 'proj-uuid',
869
+ server_uuid: 'server-uuid',
870
+ dockerfile: 'FROM nginx',
871
+ fqdn: 'https://app.example.com',
872
+ });
873
+ const callBody = JSON.parse(mockFetch.mock.calls[0][1]?.body);
874
+ expect(callBody.domains).toBe('https://app.example.com');
875
+ expect(callBody.fqdn).toBeUndefined();
876
+ });
877
+ it('should map fqdn to domains in createApplicationDockerCompose', async () => {
878
+ mockFetch.mockResolvedValueOnce(mockResponse({ uuid: 'new-app-uuid' }));
879
+ await client.createApplicationDockerCompose({
880
+ project_uuid: 'proj-uuid',
881
+ server_uuid: 'server-uuid',
882
+ docker_compose_raw: 'version: "3"\n',
883
+ fqdn: 'https://compose.example.com',
884
+ });
885
+ const callBody = JSON.parse(mockFetch.mock.calls[0][1]?.body);
886
+ expect(callBody.domains).toBe('https://compose.example.com');
887
+ expect(callBody.fqdn).toBeUndefined();
888
+ });
889
+ it('should accept explicit domains and prefer it over fqdn', async () => {
890
+ mockFetch.mockResolvedValueOnce(mockResponse({ uuid: 'new-app-uuid' }));
891
+ await client.createApplicationDockerImage({
892
+ project_uuid: 'proj-uuid',
893
+ server_uuid: 'server-uuid',
894
+ docker_registry_image_name: 'traefik/whoami',
895
+ ports_exposes: '80',
896
+ fqdn: 'https://from-fqdn.example.com',
897
+ domains: 'https://from-domains.example.com',
898
+ });
899
+ const callBody = JSON.parse(mockFetch.mock.calls[0][1]?.body);
900
+ expect(callBody.domains).toBe('https://from-domains.example.com');
901
+ expect(callBody.fqdn).toBeUndefined();
902
+ });
903
+ it('should pass instant_deploy and custom_* fields through createApplicationDockerImage', async () => {
904
+ mockFetch.mockResolvedValueOnce(mockResponse({ uuid: 'new-app-uuid' }));
905
+ await client.createApplicationDockerImage({
906
+ project_uuid: 'proj-uuid',
907
+ server_uuid: 'server-uuid',
908
+ docker_registry_image_name: 'traefik/whoami',
909
+ ports_exposes: '80',
910
+ instant_deploy: true,
911
+ custom_docker_run_options: '--network=my-net',
912
+ custom_labels: 'dHJhZWZpaw==',
913
+ });
914
+ const callBody = JSON.parse(mockFetch.mock.calls[0][1]?.body);
915
+ expect(callBody.instant_deploy).toBe(true);
916
+ expect(callBody.custom_docker_run_options).toBe('--network=my-net');
917
+ expect(callBody.custom_labels).toBe('dHJhZWZpaw==');
918
+ });
919
+ it('should pass destination_uuid through in createApplicationPublic', async () => {
920
+ mockFetch.mockResolvedValueOnce(mockResponse({ uuid: 'new-app-uuid' }));
921
+ await client.createApplicationPublic({
922
+ project_uuid: 'proj-uuid',
923
+ server_uuid: 'server-uuid',
924
+ destination_uuid: 'dest-uuid',
925
+ git_repository: 'https://github.com/user/repo',
926
+ git_branch: 'main',
927
+ build_pack: 'nixpacks',
928
+ ports_exposes: '3000',
929
+ });
930
+ const callBody = JSON.parse(mockFetch.mock.calls[0][1]?.body);
931
+ expect(callBody.destination_uuid).toBe('dest-uuid');
932
+ });
852
933
  it('should map fqdn to domains in updateApplication', async () => {
853
934
  mockFetch.mockResolvedValueOnce(mockResponse(mockApplication));
854
935
  await client.updateApplication('app-uuid', { fqdn: 'https://new.example.com' });
@@ -1457,12 +1538,32 @@ describe('CoolifyClient', () => {
1457
1538
  logs_info: 'Logs available (16 chars). Use lines param to retrieve.',
1458
1539
  });
1459
1540
  });
1460
- it('should list application deployments', async () => {
1461
- mockFetch.mockResolvedValueOnce(mockResponse([mockDeployment]));
1541
+ it('should list application deployments as essential by default (no logs)', async () => {
1542
+ const withLogs = { ...mockDeployment, logs: 'x'.repeat(30000) };
1543
+ // Real Coolify wraps the list in an envelope: { count, deployments: [...] }
1544
+ mockFetch.mockResolvedValueOnce(mockResponse({ count: 1, deployments: [withLogs] }));
1462
1545
  const result = await client.listApplicationDeployments('app-uuid');
1463
- expect(result).toEqual([mockDeployment]);
1546
+ expect(result.count).toBe(1);
1547
+ expect(result.deployments).toHaveLength(1);
1548
+ const [first] = result.deployments;
1549
+ // Essential projection: no raw logs, but a `logs_available` breadcrumb.
1550
+ expect(first.logs).toBeUndefined();
1551
+ expect(first.logs_available).toBe(true);
1552
+ // Essential also drops fields like `id`
1553
+ expect(first.id).toBeUndefined();
1464
1554
  expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/deployments/applications/app-uuid', expect.any(Object));
1465
1555
  });
1556
+ it('should return full deployments when includeLogs is true', async () => {
1557
+ const withLogs = { ...mockDeployment, logs: 'build log stream' };
1558
+ mockFetch.mockResolvedValueOnce(mockResponse({ count: 1, deployments: [withLogs] }));
1559
+ const result = await client.listApplicationDeployments('app-uuid', { includeLogs: true });
1560
+ expect(result).toEqual({ count: 1, deployments: [withLogs] });
1561
+ });
1562
+ it('should tolerate a malformed envelope (missing deployments array)', async () => {
1563
+ mockFetch.mockResolvedValueOnce(mockResponse({}));
1564
+ const result = await client.listApplicationDeployments('app-uuid');
1565
+ expect(result).toEqual({ count: 0, deployments: [] });
1566
+ });
1466
1567
  });
1467
1568
  // =========================================================================
1468
1569
  // Team endpoints - extended coverage
@@ -1906,7 +2007,7 @@ describe('CoolifyClient', () => {
1906
2007
  .mockResolvedValueOnce(mockResponse(mockApp))
1907
2008
  .mockResolvedValueOnce(mockResponse(mockLogs))
1908
2009
  .mockResolvedValueOnce(mockResponse(mockEnvVars))
1909
- .mockResolvedValueOnce(mockResponse(mockDeployments));
2010
+ .mockResolvedValueOnce(mockResponse({ count: mockDeployments.length, deployments: mockDeployments }));
1910
2011
  const result = await client.diagnoseApplication(testAppUuid);
1911
2012
  expect(result.application).toEqual({
1912
2013
  uuid: testAppUuid,
@@ -1932,7 +2033,7 @@ describe('CoolifyClient', () => {
1932
2033
  .mockResolvedValueOnce(mockResponse(unhealthyApp))
1933
2034
  .mockResolvedValueOnce(mockResponse(mockLogs))
1934
2035
  .mockResolvedValueOnce(mockResponse(mockEnvVars))
1935
- .mockResolvedValueOnce(mockResponse([]));
2036
+ .mockResolvedValueOnce(mockResponse({ count: 0, deployments: [] }));
1936
2037
  const result = await client.diagnoseApplication(testAppUuid);
1937
2038
  expect(result.health.status).toBe('unhealthy');
1938
2039
  expect(result.health.issues).toContain('Status: exited:unhealthy');
@@ -1946,7 +2047,7 @@ describe('CoolifyClient', () => {
1946
2047
  .mockResolvedValueOnce(mockResponse(mockApp))
1947
2048
  .mockResolvedValueOnce(mockResponse(mockLogs))
1948
2049
  .mockResolvedValueOnce(mockResponse(mockEnvVars))
1949
- .mockResolvedValueOnce(mockResponse(failedDeployments));
2050
+ .mockResolvedValueOnce(mockResponse({ count: failedDeployments.length, deployments: failedDeployments }));
1950
2051
  const result = await client.diagnoseApplication(testAppUuid);
1951
2052
  expect(result.health.issues).toContain('2 failed deployment(s) in last 5');
1952
2053
  });
@@ -1955,7 +2056,7 @@ describe('CoolifyClient', () => {
1955
2056
  .mockResolvedValueOnce(mockResponse(mockApp))
1956
2057
  .mockRejectedValueOnce(new Error('Logs unavailable'))
1957
2058
  .mockResolvedValueOnce(mockResponse(mockEnvVars))
1958
- .mockResolvedValueOnce(mockResponse(mockDeployments));
2059
+ .mockResolvedValueOnce(mockResponse({ count: mockDeployments.length, deployments: mockDeployments }));
1959
2060
  const result = await client.diagnoseApplication(testAppUuid);
1960
2061
  expect(result.application).not.toBeNull();
1961
2062
  expect(result.logs).toBeNull();
@@ -163,7 +163,23 @@ export declare class CoolifyClient {
163
163
  includeLogs?: boolean;
164
164
  }): Promise<Deployment | DeploymentEssential>;
165
165
  deployByTagOrUuid(tagOrUuid: string, force?: boolean): Promise<MessageResponse>;
166
- listApplicationDeployments(appUuid: string): Promise<Deployment[]>;
166
+ /**
167
+ * List deployments for an application.
168
+ *
169
+ * Coolify returns `{ count, deployments: Deployment[] }` for this endpoint
170
+ * (NOT a raw array — upstream @masonator type was incorrect).
171
+ *
172
+ * By default returns a DeploymentEssential summary (no `logs` field) because
173
+ * each deployment's log blob can be 30–100KB, and a typical list has 20–35
174
+ * deployments — exceeding MCP response token limits. Pass `includeLogs: true`
175
+ * to get raw Deployment objects with full build logs.
176
+ */
177
+ listApplicationDeployments(appUuid: string, options?: {
178
+ includeLogs?: boolean;
179
+ }): Promise<{
180
+ count: number;
181
+ deployments: Deployment[] | DeploymentEssential[];
182
+ }>;
167
183
  listTeams(): Promise<Team[]>;
168
184
  getTeam(id: number): Promise<Team>;
169
185
  getTeamMembers(id: number): Promise<TeamMember[]>;
@@ -35,6 +35,12 @@ function toBase64(value) {
35
35
  */
36
36
  function mapFqdnToDomains(data) {
37
37
  const { fqdn, ...rest } = data;
38
+ // Explicit `domains` always wins. `fqdn` is only used when `domains` was
39
+ // not provided — kept for backward compatibility because `get_application`
40
+ // surfaces the field as `fqdn` in responses.
41
+ if (rest.domains !== undefined) {
42
+ return rest;
43
+ }
38
44
  if (fqdn === undefined) {
39
45
  return rest;
40
46
  }
@@ -143,8 +149,10 @@ function toEnvVarSummary(envVar) {
143
149
  * HTTP client for the Coolify API
144
150
  */
145
151
  export class CoolifyClient {
152
+ baseUrl;
153
+ accessToken;
154
+ cachedVersion = null;
146
155
  constructor(config) {
147
- this.cachedVersion = null;
148
156
  if (!config.baseUrl) {
149
157
  throw new Error('Coolify base URL is required');
150
158
  }
@@ -187,7 +195,7 @@ export class CoolifyClient {
187
195
  }
188
196
  catch (error) {
189
197
  if (error instanceof TypeError && error.message.includes('fetch')) {
190
- throw new Error(`Failed to connect to Coolify server at ${this.baseUrl}. Please check if the server is running and accessible.`);
198
+ throw new Error(`Failed to connect to Coolify server at ${this.baseUrl}. Please check if the server is running and accessible.`, { cause: error });
191
199
  }
192
200
  throw error;
193
201
  }
@@ -231,7 +239,7 @@ export class CoolifyClient {
231
239
  await this.getVersion();
232
240
  }
233
241
  catch (error) {
234
- throw new Error(`Failed to connect to Coolify server: ${error instanceof Error ? error.message : 'Unknown error'}`);
242
+ throw new Error(`Failed to connect to Coolify server: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
235
243
  }
236
244
  }
237
245
  // ===========================================================================
@@ -386,17 +394,18 @@ export class CoolifyClient {
386
394
  async createApplicationDockerfile(data) {
387
395
  return this.request('/applications/dockerfile', {
388
396
  method: 'POST',
389
- body: JSON.stringify(data),
397
+ body: JSON.stringify(mapFqdnToDomains(data)),
390
398
  });
391
399
  }
392
400
  async createApplicationDockerImage(data) {
393
401
  return this.request('/applications/dockerimage', {
394
402
  method: 'POST',
395
- body: JSON.stringify(data),
403
+ body: JSON.stringify(mapFqdnToDomains(data)),
396
404
  });
397
405
  }
398
406
  async createApplicationDockerCompose(data) {
399
- const payload = { ...data };
407
+ const mapped = mapFqdnToDomains(data);
408
+ const payload = { ...mapped };
400
409
  if (payload.docker_compose_raw) {
401
410
  payload.docker_compose_raw = toBase64(payload.docker_compose_raw);
402
411
  }
@@ -679,8 +688,24 @@ export class CoolifyClient {
679
688
  const param = this.isLikelyUuid(tagOrUuid) ? 'uuid' : 'tag';
680
689
  return this.request(`/deploy?${param}=${encodeURIComponent(tagOrUuid)}&force=${force}`, { method: 'GET' });
681
690
  }
682
- async listApplicationDeployments(appUuid) {
683
- return this.request(`/deployments/applications/${appUuid}`);
691
+ /**
692
+ * List deployments for an application.
693
+ *
694
+ * Coolify returns `{ count, deployments: Deployment[] }` for this endpoint
695
+ * (NOT a raw array — upstream @masonator type was incorrect).
696
+ *
697
+ * By default returns a DeploymentEssential summary (no `logs` field) because
698
+ * each deployment's log blob can be 30–100KB, and a typical list has 20–35
699
+ * deployments — exceeding MCP response token limits. Pass `includeLogs: true`
700
+ * to get raw Deployment objects with full build logs.
701
+ */
702
+ async listApplicationDeployments(appUuid, options) {
703
+ const envelope = await this.request(`/deployments/applications/${appUuid}`);
704
+ const deployments = Array.isArray(envelope?.deployments) ? envelope.deployments : [];
705
+ return {
706
+ count: typeof envelope?.count === 'number' ? envelope.count : deployments.length,
707
+ deployments: options?.includeLogs ? deployments : deployments.map(toDeploymentEssential),
708
+ };
684
709
  }
685
710
  // ===========================================================================
686
711
  // Team endpoints
@@ -932,7 +957,10 @@ export class CoolifyClient {
932
957
  const app = extract(results[0], 'application');
933
958
  const logs = extract(results[1], 'logs');
934
959
  const envVars = extract(results[2], 'environment_variables');
935
- const deployments = extract(results[3], 'deployments');
960
+ // listApplicationDeployments now returns { count, deployments: [...] } —
961
+ // flatten back to the array that the diagnostics consumer expects.
962
+ const deploymentsEnvelope = extract(results[3], 'deployments');
963
+ const deployments = deploymentsEnvelope?.deployments ?? [];
936
964
  // Determine health status and issues
937
965
  const issues = [];
938
966
  let healthStatus = 'unknown';
@@ -7,11 +7,9 @@ const DOCS_BASE_URL = 'https://coolify.io';
7
7
  * The LLM calling this tool handles semantic understanding — we just need good ranking.
8
8
  */
9
9
  export class DocsSearchEngine {
10
- constructor() {
11
- this.index = null;
12
- this.chunks = [];
13
- this.loading = null;
14
- }
10
+ index = null;
11
+ chunks = [];
12
+ loading = null;
15
13
  async ensureLoaded() {
16
14
  if (this.index)
17
15
  return;
@@ -23,7 +21,7 @@ export class DocsSearchEngine {
23
21
  async loadAndIndex() {
24
22
  try {
25
23
  const controller = new AbortController();
26
- const timeout = setTimeout(() => controller.abort(), 15000);
24
+ const timeout = setTimeout(() => controller.abort(), 15_000);
27
25
  let response;
28
26
  try {
29
27
  response = await fetch(DOCS_FULL_URL, { signal: controller.signal });
@@ -153,9 +153,10 @@ function wrapWithActions(fn, getActions, getPaginationFn) {
153
153
  }));
154
154
  }
155
155
  export class CoolifyMcpServer extends McpServer {
156
+ client;
157
+ docsSearch = new DocsSearchEngine();
156
158
  constructor(config) {
157
159
  super({ name: 'coolify', version: VERSION });
158
- this.docsSearch = new DocsSearchEngine();
159
160
  this.client = new CoolifyClient(config);
160
161
  this.registerTools();
161
162
  }
@@ -308,6 +309,7 @@ export class CoolifyMcpServer extends McpServer {
308
309
  server_uuid: z.string().optional(),
309
310
  github_app_uuid: z.string().optional(),
310
311
  private_key_uuid: z.string().optional(),
312
+ destination_uuid: z.string().optional(),
311
313
  git_repository: z.string().optional(),
312
314
  git_branch: z.string().optional(),
313
315
  environment_name: z.string().optional(),
@@ -321,6 +323,10 @@ export class CoolifyMcpServer extends McpServer {
321
323
  name: z.string().optional(),
322
324
  description: z.string().optional(),
323
325
  fqdn: z.string().optional(),
326
+ domains: z.string().optional(),
327
+ custom_docker_run_options: z.string().optional(),
328
+ custom_labels: z.string().optional(),
329
+ instant_deploy: z.boolean().optional(),
324
330
  // Health check fields
325
331
  health_check_enabled: z.boolean().optional(),
326
332
  health_check_path: z.string().optional(),
@@ -358,6 +364,7 @@ export class CoolifyMcpServer extends McpServer {
358
364
  return wrap(() => this.client.createApplicationPublic({
359
365
  project_uuid: args.project_uuid,
360
366
  server_uuid: args.server_uuid,
367
+ destination_uuid: args.destination_uuid,
361
368
  git_repository: args.git_repository,
362
369
  git_branch: args.git_branch,
363
370
  build_pack: args.build_pack,
@@ -367,6 +374,10 @@ export class CoolifyMcpServer extends McpServer {
367
374
  name: args.name,
368
375
  description: args.description,
369
376
  fqdn: args.fqdn,
377
+ domains: args.domains,
378
+ custom_docker_run_options: args.custom_docker_run_options,
379
+ custom_labels: args.custom_labels,
380
+ instant_deploy: args.instant_deploy,
370
381
  }));
371
382
  case 'create_github':
372
383
  if (!args.project_uuid ||
@@ -387,6 +398,7 @@ export class CoolifyMcpServer extends McpServer {
387
398
  project_uuid: args.project_uuid,
388
399
  server_uuid: args.server_uuid,
389
400
  github_app_uuid: args.github_app_uuid,
401
+ destination_uuid: args.destination_uuid,
390
402
  git_repository: args.git_repository,
391
403
  git_branch: args.git_branch,
392
404
  build_pack: args.build_pack,
@@ -396,6 +408,10 @@ export class CoolifyMcpServer extends McpServer {
396
408
  name: args.name,
397
409
  description: args.description,
398
410
  fqdn: args.fqdn,
411
+ domains: args.domains,
412
+ custom_docker_run_options: args.custom_docker_run_options,
413
+ custom_labels: args.custom_labels,
414
+ instant_deploy: args.instant_deploy,
399
415
  }));
400
416
  case 'create_key':
401
417
  if (!args.project_uuid ||
@@ -416,6 +432,7 @@ export class CoolifyMcpServer extends McpServer {
416
432
  project_uuid: args.project_uuid,
417
433
  server_uuid: args.server_uuid,
418
434
  private_key_uuid: args.private_key_uuid,
435
+ destination_uuid: args.destination_uuid,
419
436
  git_repository: args.git_repository,
420
437
  git_branch: args.git_branch,
421
438
  build_pack: args.build_pack,
@@ -425,6 +442,10 @@ export class CoolifyMcpServer extends McpServer {
425
442
  name: args.name,
426
443
  description: args.description,
427
444
  fqdn: args.fqdn,
445
+ domains: args.domains,
446
+ custom_docker_run_options: args.custom_docker_run_options,
447
+ custom_labels: args.custom_labels,
448
+ instant_deploy: args.instant_deploy,
428
449
  }));
429
450
  case 'create_dockerimage':
430
451
  if (!args.project_uuid ||
@@ -443,6 +464,7 @@ export class CoolifyMcpServer extends McpServer {
443
464
  return wrap(() => this.client.createApplicationDockerImage({
444
465
  project_uuid: args.project_uuid,
445
466
  server_uuid: args.server_uuid,
467
+ destination_uuid: args.destination_uuid,
446
468
  docker_registry_image_name: args.docker_registry_image_name,
447
469
  ports_exposes: args.ports_exposes,
448
470
  docker_registry_image_tag: args.docker_registry_image_tag,
@@ -451,6 +473,10 @@ export class CoolifyMcpServer extends McpServer {
451
473
  name: args.name,
452
474
  description: args.description,
453
475
  fqdn: args.fqdn,
476
+ domains: args.domains,
477
+ custom_docker_run_options: args.custom_docker_run_options,
478
+ custom_labels: args.custom_labels,
479
+ instant_deploy: args.instant_deploy,
454
480
  }));
455
481
  case 'update': {
456
482
  if (!uuid)
@@ -709,13 +735,14 @@ export class CoolifyMcpServer extends McpServer {
709
735
  // =========================================================================
710
736
  this.tool('list_deployments', 'List deployments (summary)', { page: z.number().optional(), per_page: z.number().optional() }, async ({ page, per_page }) => wrapWithActions(() => this.client.listDeployments({ page, per_page, summary: true }), undefined, (result) => getPagination('list_deployments', page, per_page, result.length)));
711
737
  this.tool('deploy', 'Deploy by tag/UUID', { tag_or_uuid: z.string(), force: z.boolean().optional() }, async ({ tag_or_uuid, force }) => wrapWithActions(() => this.client.deployByTagOrUuid(tag_or_uuid, force), () => [{ tool: 'list_deployments', args: {}, hint: 'Check deployment status' }]));
712
- this.tool('deployment', 'Manage deployment: get/cancel/list_for_app (logs excluded by default, use lines param to include)', {
738
+ this.tool('deployment', 'Manage deployment: get/cancel/list_for_app. Logs excluded by default on all actions — for get use `lines` (paginated tail), for list_for_app use `include_logs: true` to include raw build-log blobs.', {
713
739
  action: z.enum(['get', 'cancel', 'list_for_app']),
714
740
  uuid: z.string(),
715
741
  lines: z.number().optional(), // Include logs truncated to last N entries (omit for no logs)
716
742
  page: z.number().optional(), // Log page (1=most recent, 2=older, etc.)
717
743
  max_chars: z.number().optional(), // Limit log output to last N chars (default: 50000)
718
- }, async ({ action, uuid, lines, page, max_chars }) => {
744
+ include_logs: z.boolean().optional(), // list_for_app only: include raw build logs (default false; upstream returns ~30KB per deployment)
745
+ }, async ({ action, uuid, lines, page, max_chars, include_logs }) => {
719
746
  switch (action) {
720
747
  case 'get':
721
748
  // If lines param specified, include logs and truncate
@@ -760,7 +787,7 @@ export class CoolifyMcpServer extends McpServer {
760
787
  case 'cancel':
761
788
  return wrap(() => this.client.cancelDeployment(uuid));
762
789
  case 'list_for_app':
763
- return wrap(() => this.client.listApplicationDeployments(uuid));
790
+ return wrap(() => this.client.listApplicationDeployments(uuid, { includeLogs: include_logs }));
764
791
  }
765
792
  });
766
793
  // =========================================================================
@@ -211,6 +211,7 @@ export interface CreateApplicationPublicRequest {
211
211
  name?: string;
212
212
  description?: string;
213
213
  fqdn?: string;
214
+ domains?: string;
214
215
  git_repository: string;
215
216
  git_branch: string;
216
217
  git_commit_sha?: string;
@@ -222,6 +223,8 @@ export interface CreateApplicationPublicRequest {
222
223
  install_command?: string;
223
224
  build_command?: string;
224
225
  start_command?: string;
226
+ custom_docker_run_options?: string;
227
+ custom_labels?: string;
225
228
  instant_deploy?: boolean;
226
229
  }
227
230
  export interface CreateApplicationPrivateGHRequest extends Omit<CreateApplicationPublicRequest, 'build_pack' | 'ports_exposes'> {
@@ -243,11 +246,14 @@ export interface CreateApplicationDockerfileRequest {
243
246
  name?: string;
244
247
  description?: string;
245
248
  fqdn?: string;
249
+ domains?: string;
246
250
  dockerfile: string;
247
251
  dockerfile_location?: string;
248
252
  ports_exposes?: string;
249
253
  ports_mappings?: string;
250
254
  base_directory?: string;
255
+ custom_docker_run_options?: string;
256
+ custom_labels?: string;
251
257
  instant_deploy?: boolean;
252
258
  }
253
259
  export interface CreateApplicationDockerImageRequest {
@@ -259,10 +265,13 @@ export interface CreateApplicationDockerImageRequest {
259
265
  name?: string;
260
266
  description?: string;
261
267
  fqdn?: string;
268
+ domains?: string;
262
269
  docker_registry_image_name: string;
263
270
  docker_registry_image_tag?: string;
264
271
  ports_exposes: string;
265
272
  ports_mappings?: string;
273
+ custom_docker_run_options?: string;
274
+ custom_labels?: string;
266
275
  instant_deploy?: boolean;
267
276
  }
268
277
  export interface CreateApplicationDockerComposeRequest {
@@ -273,16 +282,23 @@ export interface CreateApplicationDockerComposeRequest {
273
282
  destination_uuid?: string;
274
283
  name?: string;
275
284
  description?: string;
285
+ fqdn?: string;
286
+ domains?: string;
276
287
  docker_compose_raw: string;
277
288
  docker_compose_location?: string;
278
289
  docker_compose_custom_start_command?: string;
279
290
  docker_compose_custom_build_command?: string;
291
+ custom_docker_run_options?: string;
292
+ custom_labels?: string;
280
293
  instant_deploy?: boolean;
281
294
  }
282
295
  export interface UpdateApplicationRequest {
283
296
  name?: string;
284
297
  description?: string;
285
298
  fqdn?: string;
299
+ domains?: string;
300
+ custom_docker_run_options?: string;
301
+ custom_labels?: string;
286
302
  git_repository?: string;
287
303
  git_branch?: string;
288
304
  git_commit_sha?: string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@masonator/coolify-mcp",
3
3
  "scope": "@masonator",
4
- "version": "2.7.3",
4
+ "version": "2.8.0",
5
5
  "mcpName": "io.github.StuMason/coolify",
6
6
  "description": "MCP server for Coolify — 38 optimized tools for infrastructure management, diagnostics, and documentation search",
7
7
  "type": "module",
@@ -64,8 +64,8 @@
64
64
  "zod": "^4.3.5"
65
65
  },
66
66
  "devDependencies": {
67
- "@eslint/js": "^9.39.3",
68
- "@types/jest": "^29.5.14",
67
+ "@eslint/js": "^10.0.1",
68
+ "@types/jest": "^30.0.0",
69
69
  "@types/node": "^25.0.3",
70
70
  "@typescript-eslint/eslint-plugin": "^8.51.0",
71
71
  "@typescript-eslint/parser": "^8.51.0",
@@ -74,10 +74,10 @@
74
74
  "eslint-config-prettier": "^10.1.8",
75
75
  "globals": "^17.0.0",
76
76
  "husky": "^9.0.11",
77
- "jest": "^29.7.0",
77
+ "jest": "^30.3.0",
78
78
  "jest-junit": "^16.0.0",
79
79
  "lint-staged": "^16.2.7",
80
- "markdownlint-cli2": "^0.21.0",
80
+ "markdownlint-cli2": "^0.22.0",
81
81
  "prettier": "^3.5.3",
82
82
  "shx": "^0.4.0",
83
83
  "ts-jest": "^29.2.6",
@@ -85,7 +85,10 @@
85
85
  "typescript-eslint": "^8.51.0"
86
86
  },
87
87
  "engines": {
88
- "node": ">=18"
88
+ "node": ">=20"
89
+ },
90
+ "overrides": {
91
+ "handlebars": "^4.7.9"
89
92
  },
90
93
  "lint-staged": {
91
94
  "*.{ts,js,json,md,yaml,yml}": "prettier --write",