@masonator/coolify-mcp 2.9.0 → 2.10.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.
@@ -969,6 +969,131 @@ describe('CoolifyClient', () => {
969
969
  expect(callBody.custom_docker_run_options).toBe('--network=my-net');
970
970
  expect(callBody.custom_labels).toBe('dHJhZWZpaw==');
971
971
  });
972
+ // Regression for #178 — verify build-config and health_check_* fields reach
973
+ // the wire, not just zod-accepted then silently stripped by the hand-pick.
974
+ it('should pass build-config and health_check fields through createApplicationPublic', async () => {
975
+ mockFetch.mockResolvedValueOnce(mockResponse({ uuid: 'new-app-uuid' }));
976
+ await client.createApplicationPublic({
977
+ project_uuid: 'proj-uuid',
978
+ server_uuid: 'server-uuid',
979
+ git_repository: 'https://github.com/user/monorepo',
980
+ git_branch: 'main',
981
+ build_pack: 'dockerfile',
982
+ ports_exposes: '3000',
983
+ base_directory: '/apps/api',
984
+ publish_directory: '/dist',
985
+ install_command: 'pnpm install',
986
+ build_command: 'pnpm build',
987
+ start_command: 'node dist/main.js',
988
+ dockerfile_location: '/apps/api/Dockerfile',
989
+ watch_paths: 'apps/api/**',
990
+ health_check_enabled: true,
991
+ health_check_path: '/health',
992
+ health_check_port: 3000,
993
+ health_check_start_period: 60,
994
+ });
995
+ const callBody = JSON.parse(mockFetch.mock.calls[0][1]?.body);
996
+ expect(callBody.base_directory).toBe('/apps/api');
997
+ expect(callBody.publish_directory).toBe('/dist');
998
+ expect(callBody.install_command).toBe('pnpm install');
999
+ expect(callBody.build_command).toBe('pnpm build');
1000
+ expect(callBody.start_command).toBe('node dist/main.js');
1001
+ expect(callBody.dockerfile_location).toBe('/apps/api/Dockerfile');
1002
+ expect(callBody.watch_paths).toBe('apps/api/**');
1003
+ expect(callBody.health_check_enabled).toBe(true);
1004
+ expect(callBody.health_check_path).toBe('/health');
1005
+ expect(callBody.health_check_port).toBe(3000);
1006
+ expect(callBody.health_check_start_period).toBe(60);
1007
+ });
1008
+ it('should pass build-config and health_check fields through createApplicationPrivateGH', async () => {
1009
+ mockFetch.mockResolvedValueOnce(mockResponse({ uuid: 'new-app-uuid' }));
1010
+ await client.createApplicationPrivateGH({
1011
+ project_uuid: 'proj-uuid',
1012
+ server_uuid: 'server-uuid',
1013
+ github_app_uuid: 'gh-app-uuid',
1014
+ git_repository: 'org/monorepo',
1015
+ git_branch: 'main',
1016
+ base_directory: '/apps/api',
1017
+ dockerfile_location: '/apps/api/Dockerfile',
1018
+ watch_paths: 'apps/api/**',
1019
+ health_check_enabled: true,
1020
+ health_check_path: '/health',
1021
+ });
1022
+ const callBody = JSON.parse(mockFetch.mock.calls[0][1]?.body);
1023
+ expect(callBody.base_directory).toBe('/apps/api');
1024
+ expect(callBody.dockerfile_location).toBe('/apps/api/Dockerfile');
1025
+ expect(callBody.watch_paths).toBe('apps/api/**');
1026
+ expect(callBody.health_check_enabled).toBe(true);
1027
+ expect(callBody.health_check_path).toBe('/health');
1028
+ });
1029
+ it('should pass build-config and health_check fields through createApplicationPrivateKey', async () => {
1030
+ mockFetch.mockResolvedValueOnce(mockResponse({ uuid: 'new-app-uuid' }));
1031
+ await client.createApplicationPrivateKey({
1032
+ project_uuid: 'proj-uuid',
1033
+ server_uuid: 'server-uuid',
1034
+ private_key_uuid: 'key-uuid',
1035
+ git_repository: 'git@github.com:org/monorepo.git',
1036
+ git_branch: 'main',
1037
+ base_directory: '/apps/api',
1038
+ publish_directory: '/dist',
1039
+ install_command: 'pnpm install',
1040
+ build_command: 'pnpm build',
1041
+ start_command: 'node dist/main.js',
1042
+ dockerfile_location: '/apps/api/Dockerfile',
1043
+ watch_paths: 'apps/api/**',
1044
+ health_check_enabled: true,
1045
+ health_check_path: '/health',
1046
+ health_check_port: 3000,
1047
+ });
1048
+ const callBody = JSON.parse(mockFetch.mock.calls[0][1]?.body);
1049
+ expect(callBody.base_directory).toBe('/apps/api');
1050
+ expect(callBody.dockerfile_location).toBe('/apps/api/Dockerfile');
1051
+ expect(callBody.watch_paths).toBe('apps/api/**');
1052
+ expect(callBody.health_check_path).toBe('/health');
1053
+ expect(callBody.health_check_port).toBe(3000);
1054
+ });
1055
+ it('should pass health_check fields through createApplicationDockerImage (build-config N/A)', async () => {
1056
+ mockFetch.mockResolvedValueOnce(mockResponse({ uuid: 'new-app-uuid' }));
1057
+ await client.createApplicationDockerImage({
1058
+ project_uuid: 'proj-uuid',
1059
+ server_uuid: 'server-uuid',
1060
+ docker_registry_image_name: 'traefik/whoami',
1061
+ ports_exposes: '80',
1062
+ health_check_enabled: true,
1063
+ health_check_path: '/health',
1064
+ health_check_port: 80,
1065
+ health_check_method: 'GET',
1066
+ health_check_scheme: 'http',
1067
+ health_check_return_code: 200,
1068
+ health_check_interval: 30,
1069
+ health_check_timeout: 5,
1070
+ health_check_retries: 3,
1071
+ health_check_start_period: 60,
1072
+ });
1073
+ const callBody = JSON.parse(mockFetch.mock.calls[0][1]?.body);
1074
+ expect(callBody.health_check_enabled).toBe(true);
1075
+ expect(callBody.health_check_path).toBe('/health');
1076
+ expect(callBody.health_check_port).toBe(80);
1077
+ expect(callBody.health_check_method).toBe('GET');
1078
+ expect(callBody.health_check_scheme).toBe('http');
1079
+ expect(callBody.health_check_return_code).toBe(200);
1080
+ expect(callBody.health_check_interval).toBe(30);
1081
+ expect(callBody.health_check_timeout).toBe(5);
1082
+ expect(callBody.health_check_retries).toBe(3);
1083
+ expect(callBody.health_check_start_period).toBe(60);
1084
+ });
1085
+ it('should pass dockerfile_target_build through updateApplication (PATCH-only field)', async () => {
1086
+ mockFetch.mockResolvedValueOnce(mockResponse(mockApplication));
1087
+ await client.updateApplication('app-uuid', {
1088
+ dockerfile_location: '/apps/api/Dockerfile',
1089
+ dockerfile_target_build: 'production',
1090
+ base_directory: '/apps/api',
1091
+ });
1092
+ const callBody = JSON.parse(mockFetch.mock.calls[0][1]?.body);
1093
+ expect(callBody.dockerfile_location).toBe('/apps/api/Dockerfile');
1094
+ expect(callBody.dockerfile_target_build).toBe('production');
1095
+ expect(callBody.base_directory).toBe('/apps/api');
1096
+ });
972
1097
  it('should pass destination_uuid through in createApplicationPublic', async () => {
973
1098
  mockFetch.mockResolvedValueOnce(mockResponse({ uuid: 'new-app-uuid' }));
974
1099
  await client.createApplicationPublic({
@@ -263,6 +263,166 @@ describe('CoolifyMcpServer v2', () => {
263
263
  expect(spy).toHaveBeenCalledWith(['app-1', 'app-2'], 'PEM_KEY', 'multiline', false, true);
264
264
  });
265
265
  });
266
+ describe('application tool handler', () => {
267
+ // Regression for #178 — verify the application tool's create_* hand-picks
268
+ // forward build-config and health_check_* fields to the client. Previously
269
+ // these fields were accepted by zod but silently dropped by the hand-pick.
270
+ const callApplication = async (srv, args) => {
271
+ const tool = srv._registeredTools['application'];
272
+ return tool.handler(args, {});
273
+ };
274
+ const baseCreatePublic = {
275
+ action: 'create_public',
276
+ project_uuid: 'proj-uuid',
277
+ server_uuid: 'server-uuid',
278
+ git_repository: 'https://github.com/org/monorepo',
279
+ git_branch: 'main',
280
+ build_pack: 'dockerfile',
281
+ ports_exposes: '3000',
282
+ };
283
+ it('forwards build-config and health_check fields in create_public', async () => {
284
+ const spy = jest
285
+ .spyOn(server['client'], 'createApplicationPublic')
286
+ .mockResolvedValue({ uuid: 'app-1' });
287
+ await callApplication(server, {
288
+ ...baseCreatePublic,
289
+ base_directory: '/apps/api',
290
+ publish_directory: '/dist',
291
+ install_command: 'pnpm install',
292
+ build_command: 'pnpm build',
293
+ start_command: 'node dist/main.js',
294
+ dockerfile_location: '/apps/api/Dockerfile',
295
+ watch_paths: 'apps/api/**',
296
+ health_check_enabled: true,
297
+ health_check_path: '/health',
298
+ health_check_port: 3000,
299
+ health_check_start_period: 60,
300
+ });
301
+ expect(spy).toHaveBeenCalledWith(expect.objectContaining({
302
+ base_directory: '/apps/api',
303
+ publish_directory: '/dist',
304
+ install_command: 'pnpm install',
305
+ build_command: 'pnpm build',
306
+ start_command: 'node dist/main.js',
307
+ dockerfile_location: '/apps/api/Dockerfile',
308
+ watch_paths: 'apps/api/**',
309
+ health_check_enabled: true,
310
+ health_check_path: '/health',
311
+ health_check_port: 3000,
312
+ health_check_start_period: 60,
313
+ }));
314
+ });
315
+ it('forwards build-config and health_check fields in create_github', async () => {
316
+ const spy = jest
317
+ .spyOn(server['client'], 'createApplicationPrivateGH')
318
+ .mockResolvedValue({ uuid: 'app-2' });
319
+ await callApplication(server, {
320
+ action: 'create_github',
321
+ project_uuid: 'proj-uuid',
322
+ server_uuid: 'server-uuid',
323
+ github_app_uuid: 'gh-app-uuid',
324
+ git_repository: 'org/monorepo',
325
+ git_branch: 'main',
326
+ base_directory: '/apps/api',
327
+ dockerfile_location: '/apps/api/Dockerfile',
328
+ watch_paths: 'apps/api/**',
329
+ health_check_enabled: true,
330
+ health_check_path: '/health',
331
+ });
332
+ expect(spy).toHaveBeenCalledWith(expect.objectContaining({
333
+ base_directory: '/apps/api',
334
+ dockerfile_location: '/apps/api/Dockerfile',
335
+ watch_paths: 'apps/api/**',
336
+ health_check_enabled: true,
337
+ health_check_path: '/health',
338
+ }));
339
+ });
340
+ it('forwards build-config and health_check fields in create_key', async () => {
341
+ const spy = jest
342
+ .spyOn(server['client'], 'createApplicationPrivateKey')
343
+ .mockResolvedValue({ uuid: 'app-3' });
344
+ await callApplication(server, {
345
+ action: 'create_key',
346
+ project_uuid: 'proj-uuid',
347
+ server_uuid: 'server-uuid',
348
+ private_key_uuid: 'key-uuid',
349
+ git_repository: 'git@github.com:org/monorepo.git',
350
+ git_branch: 'main',
351
+ base_directory: '/apps/api',
352
+ publish_directory: '/dist',
353
+ install_command: 'pnpm install',
354
+ build_command: 'pnpm build',
355
+ start_command: 'node dist/main.js',
356
+ dockerfile_location: '/apps/api/Dockerfile',
357
+ watch_paths: 'apps/api/**',
358
+ health_check_enabled: true,
359
+ health_check_path: '/health',
360
+ health_check_port: 3000,
361
+ });
362
+ expect(spy).toHaveBeenCalledWith(expect.objectContaining({
363
+ base_directory: '/apps/api',
364
+ publish_directory: '/dist',
365
+ install_command: 'pnpm install',
366
+ build_command: 'pnpm build',
367
+ start_command: 'node dist/main.js',
368
+ dockerfile_location: '/apps/api/Dockerfile',
369
+ watch_paths: 'apps/api/**',
370
+ health_check_enabled: true,
371
+ health_check_path: '/health',
372
+ health_check_port: 3000,
373
+ }));
374
+ });
375
+ it('forwards health_check fields in create_dockerimage (build-config intentionally dropped)', async () => {
376
+ const spy = jest
377
+ .spyOn(server['client'], 'createApplicationDockerImage')
378
+ .mockResolvedValue({ uuid: 'app-4' });
379
+ // Caller passes both healthcheck AND build-config. Coolify's /applications/dockerimage
380
+ // endpoint doesn't accept build-config (pre-built image), so handler must drop those.
381
+ await callApplication(server, {
382
+ action: 'create_dockerimage',
383
+ project_uuid: 'proj-uuid',
384
+ server_uuid: 'server-uuid',
385
+ docker_registry_image_name: 'traefik/whoami',
386
+ ports_exposes: '80',
387
+ // Should be forwarded:
388
+ health_check_enabled: true,
389
+ health_check_path: '/health',
390
+ health_check_port: 80,
391
+ // Should NOT be forwarded (build-config not applicable to prebuilt image):
392
+ base_directory: '/should-be-dropped',
393
+ install_command: 'should-be-dropped',
394
+ dockerfile_location: '/should-be-dropped',
395
+ });
396
+ const forwarded = spy.mock.calls[0]?.[0];
397
+ expect(forwarded).toEqual(expect.objectContaining({
398
+ health_check_enabled: true,
399
+ health_check_path: '/health',
400
+ health_check_port: 80,
401
+ }));
402
+ expect(forwarded).not.toHaveProperty('base_directory');
403
+ expect(forwarded).not.toHaveProperty('install_command');
404
+ expect(forwarded).not.toHaveProperty('dockerfile_location');
405
+ });
406
+ it('forwards dockerfile_target_build through update (PATCH-only)', async () => {
407
+ const spy = jest.spyOn(server['client'], 'updateApplication').mockResolvedValue({});
408
+ await callApplication(server, {
409
+ action: 'update',
410
+ uuid: 'app-uuid',
411
+ dockerfile_location: '/apps/api/Dockerfile',
412
+ dockerfile_target_build: 'production',
413
+ base_directory: '/apps/api',
414
+ });
415
+ expect(spy).toHaveBeenCalledWith('app-uuid', expect.objectContaining({
416
+ dockerfile_location: '/apps/api/Dockerfile',
417
+ dockerfile_target_build: 'production',
418
+ base_directory: '/apps/api',
419
+ }));
420
+ // Confirm the update spread strips routing fields.
421
+ const updateData = spy.mock.calls[0]?.[1];
422
+ expect(updateData).not.toHaveProperty('action');
423
+ expect(updateData).not.toHaveProperty('uuid');
424
+ });
425
+ });
266
426
  });
267
427
  describe('truncateLogs', () => {
268
428
  // Plain text log tests
@@ -340,6 +340,18 @@ export class CoolifyMcpServer extends McpServer {
340
340
  health_check_timeout: z.number().optional(),
341
341
  health_check_retries: z.number().optional(),
342
342
  health_check_start_period: z.number().optional(),
343
+ // Build configuration fields (accepted on create_public/github/key + update;
344
+ // create_dockerimage ignores these — pre-built image, no build step)
345
+ base_directory: z.string().optional(),
346
+ publish_directory: z.string().optional(),
347
+ install_command: z.string().optional(),
348
+ build_command: z.string().optional(),
349
+ start_command: z.string().optional(),
350
+ dockerfile_location: z.string().optional(),
351
+ watch_paths: z.string().optional(),
352
+ // Update-only: Coolify strips dockerfile_target_build on every create endpoint
353
+ // (controller $allowedFields line 1014) but accepts on PATCH (line 2497).
354
+ dockerfile_target_build: z.string().optional(),
343
355
  // Delete fields
344
356
  delete_volumes: z.boolean().optional(),
345
357
  }, async (args) => {
@@ -375,6 +387,25 @@ export class CoolifyMcpServer extends McpServer {
375
387
  description: args.description,
376
388
  fqdn: args.fqdn,
377
389
  domains: args.domains,
390
+ base_directory: args.base_directory,
391
+ publish_directory: args.publish_directory,
392
+ install_command: args.install_command,
393
+ build_command: args.build_command,
394
+ start_command: args.start_command,
395
+ dockerfile_location: args.dockerfile_location,
396
+ watch_paths: args.watch_paths,
397
+ health_check_enabled: args.health_check_enabled,
398
+ health_check_path: args.health_check_path,
399
+ health_check_port: args.health_check_port,
400
+ health_check_host: args.health_check_host,
401
+ health_check_method: args.health_check_method,
402
+ health_check_return_code: args.health_check_return_code,
403
+ health_check_scheme: args.health_check_scheme,
404
+ health_check_response_text: args.health_check_response_text,
405
+ health_check_interval: args.health_check_interval,
406
+ health_check_timeout: args.health_check_timeout,
407
+ health_check_retries: args.health_check_retries,
408
+ health_check_start_period: args.health_check_start_period,
378
409
  custom_docker_run_options: args.custom_docker_run_options,
379
410
  custom_labels: args.custom_labels,
380
411
  instant_deploy: args.instant_deploy,
@@ -409,6 +440,25 @@ export class CoolifyMcpServer extends McpServer {
409
440
  description: args.description,
410
441
  fqdn: args.fqdn,
411
442
  domains: args.domains,
443
+ base_directory: args.base_directory,
444
+ publish_directory: args.publish_directory,
445
+ install_command: args.install_command,
446
+ build_command: args.build_command,
447
+ start_command: args.start_command,
448
+ dockerfile_location: args.dockerfile_location,
449
+ watch_paths: args.watch_paths,
450
+ health_check_enabled: args.health_check_enabled,
451
+ health_check_path: args.health_check_path,
452
+ health_check_port: args.health_check_port,
453
+ health_check_host: args.health_check_host,
454
+ health_check_method: args.health_check_method,
455
+ health_check_return_code: args.health_check_return_code,
456
+ health_check_scheme: args.health_check_scheme,
457
+ health_check_response_text: args.health_check_response_text,
458
+ health_check_interval: args.health_check_interval,
459
+ health_check_timeout: args.health_check_timeout,
460
+ health_check_retries: args.health_check_retries,
461
+ health_check_start_period: args.health_check_start_period,
412
462
  custom_docker_run_options: args.custom_docker_run_options,
413
463
  custom_labels: args.custom_labels,
414
464
  instant_deploy: args.instant_deploy,
@@ -443,6 +493,25 @@ export class CoolifyMcpServer extends McpServer {
443
493
  description: args.description,
444
494
  fqdn: args.fqdn,
445
495
  domains: args.domains,
496
+ base_directory: args.base_directory,
497
+ publish_directory: args.publish_directory,
498
+ install_command: args.install_command,
499
+ build_command: args.build_command,
500
+ start_command: args.start_command,
501
+ dockerfile_location: args.dockerfile_location,
502
+ watch_paths: args.watch_paths,
503
+ health_check_enabled: args.health_check_enabled,
504
+ health_check_path: args.health_check_path,
505
+ health_check_port: args.health_check_port,
506
+ health_check_host: args.health_check_host,
507
+ health_check_method: args.health_check_method,
508
+ health_check_return_code: args.health_check_return_code,
509
+ health_check_scheme: args.health_check_scheme,
510
+ health_check_response_text: args.health_check_response_text,
511
+ health_check_interval: args.health_check_interval,
512
+ health_check_timeout: args.health_check_timeout,
513
+ health_check_retries: args.health_check_retries,
514
+ health_check_start_period: args.health_check_start_period,
446
515
  custom_docker_run_options: args.custom_docker_run_options,
447
516
  custom_labels: args.custom_labels,
448
517
  instant_deploy: args.instant_deploy,
@@ -474,6 +543,21 @@ export class CoolifyMcpServer extends McpServer {
474
543
  description: args.description,
475
544
  fqdn: args.fqdn,
476
545
  domains: args.domains,
546
+ // Build-config fields (base_directory, install_command, etc.)
547
+ // are intentionally NOT forwarded: /applications/dockerimage is
548
+ // for pre-built registry images and has no build step.
549
+ health_check_enabled: args.health_check_enabled,
550
+ health_check_path: args.health_check_path,
551
+ health_check_port: args.health_check_port,
552
+ health_check_host: args.health_check_host,
553
+ health_check_method: args.health_check_method,
554
+ health_check_return_code: args.health_check_return_code,
555
+ health_check_scheme: args.health_check_scheme,
556
+ health_check_response_text: args.health_check_response_text,
557
+ health_check_interval: args.health_check_interval,
558
+ health_check_timeout: args.health_check_timeout,
559
+ health_check_retries: args.health_check_retries,
560
+ health_check_start_period: args.health_check_start_period,
477
561
  custom_docker_run_options: args.custom_docker_run_options,
478
562
  custom_labels: args.custom_labels,
479
563
  instant_deploy: args.instant_deploy,
@@ -224,6 +224,20 @@ export interface CreateApplicationPublicRequest {
224
224
  install_command?: string;
225
225
  build_command?: string;
226
226
  start_command?: string;
227
+ dockerfile_location?: string;
228
+ watch_paths?: string;
229
+ health_check_enabled?: boolean;
230
+ health_check_path?: string;
231
+ health_check_port?: number;
232
+ health_check_host?: string;
233
+ health_check_method?: string;
234
+ health_check_return_code?: number;
235
+ health_check_scheme?: string;
236
+ health_check_response_text?: string;
237
+ health_check_interval?: number;
238
+ health_check_timeout?: number;
239
+ health_check_retries?: number;
240
+ health_check_start_period?: number;
227
241
  custom_docker_run_options?: string;
228
242
  custom_labels?: string;
229
243
  instant_deploy?: boolean;
@@ -271,6 +285,18 @@ export interface CreateApplicationDockerImageRequest {
271
285
  docker_registry_image_tag?: string;
272
286
  ports_exposes: string;
273
287
  ports_mappings?: string;
288
+ health_check_enabled?: boolean;
289
+ health_check_path?: string;
290
+ health_check_port?: number;
291
+ health_check_host?: string;
292
+ health_check_method?: string;
293
+ health_check_return_code?: number;
294
+ health_check_scheme?: string;
295
+ health_check_response_text?: string;
296
+ health_check_interval?: number;
297
+ health_check_timeout?: number;
298
+ health_check_retries?: number;
299
+ health_check_start_period?: number;
274
300
  custom_docker_run_options?: string;
275
301
  custom_labels?: string;
276
302
  instant_deploy?: boolean;
@@ -316,6 +342,8 @@ export interface UpdateApplicationRequest {
316
342
  install_command?: string;
317
343
  build_command?: string;
318
344
  start_command?: string;
345
+ dockerfile_target_build?: string;
346
+ watch_paths?: string;
319
347
  health_check_enabled?: boolean;
320
348
  health_check_path?: string;
321
349
  health_check_port?: number;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@masonator/coolify-mcp",
3
3
  "scope": "@masonator",
4
- "version": "2.9.0",
4
+ "version": "2.10.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",