@masonator/coolify-mcp 0.6.0 → 0.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.
@@ -3,13 +3,13 @@
3
3
  * Complete HTTP client for the Coolify API v1
4
4
  */
5
5
  /**
6
- * Remove undefined values and false booleans from an object.
7
- * Coolify API rejects requests with explicit false values for optional booleans.
6
+ * Remove undefined values from an object.
7
+ * Keeps explicit false values so features like HTTP Basic Auth can be disabled.
8
8
  */
9
9
  function cleanRequestData(data) {
10
10
  const cleaned = {};
11
11
  for (const [key, value] of Object.entries(data)) {
12
- if (value !== undefined && value !== false) {
12
+ if (value !== undefined) {
13
13
  cleaned[key] = value;
14
14
  }
15
15
  }
@@ -406,18 +406,6 @@ export class CoolifyClient {
406
406
  });
407
407
  }
408
408
  // ===========================================================================
409
- // Database Backups
410
- // ===========================================================================
411
- async listDatabaseBackups(uuid) {
412
- return this.request(`/databases/${uuid}/backups`);
413
- }
414
- async createDatabaseBackup(uuid, data) {
415
- return this.request(`/databases/${uuid}/backups`, {
416
- method: 'POST',
417
- body: JSON.stringify(data),
418
- });
419
- }
420
- // ===========================================================================
421
409
  // Service endpoints
422
410
  // ===========================================================================
423
411
  async listServices(options) {
@@ -587,4 +575,404 @@ export class CoolifyClient {
587
575
  async validateCloudToken(uuid) {
588
576
  return this.request(`/cloud-tokens/${uuid}/validate`, { method: 'POST' });
589
577
  }
578
+ // ===========================================================================
579
+ // Database Backup endpoints
580
+ // ===========================================================================
581
+ async listDatabaseBackups(databaseUuid) {
582
+ return this.request(`/databases/${databaseUuid}/backups`);
583
+ }
584
+ async getDatabaseBackup(databaseUuid, backupUuid) {
585
+ return this.request(`/databases/${databaseUuid}/backups/${backupUuid}`);
586
+ }
587
+ async listBackupExecutions(databaseUuid, backupUuid) {
588
+ return this.request(`/databases/${databaseUuid}/backups/${backupUuid}/executions`);
589
+ }
590
+ async getBackupExecution(databaseUuid, backupUuid, executionUuid) {
591
+ return this.request(`/databases/${databaseUuid}/backups/${backupUuid}/executions/${executionUuid}`);
592
+ }
593
+ // ===========================================================================
594
+ // Deployment Control endpoints
595
+ // ===========================================================================
596
+ async cancelDeployment(uuid) {
597
+ return this.request(`/deployments/${uuid}/cancel`, {
598
+ method: 'POST',
599
+ });
600
+ }
601
+ // ===========================================================================
602
+ // Smart Lookup Helpers
603
+ // ===========================================================================
604
+ /**
605
+ * Check if a string looks like a UUID (Coolify format or standard format).
606
+ * Coolify UUIDs are alphanumeric strings, typically 24 chars like "xs0sgs4gog044s4k4c88kgsc"
607
+ * Also accepts standard UUID format with hyphens like "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
608
+ */
609
+ isLikelyUuid(query) {
610
+ // Coolify UUID format: alphanumeric, 20+ chars
611
+ if (/^[a-z0-9]{20,}$/i.test(query)) {
612
+ return true;
613
+ }
614
+ // Standard UUID format with hyphens (8-4-4-4-12)
615
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(query)) {
616
+ return true;
617
+ }
618
+ return false;
619
+ }
620
+ /**
621
+ * Find an application by UUID, name, or domain (FQDN).
622
+ * Returns the UUID if found, throws if not found or multiple matches.
623
+ */
624
+ async resolveApplicationUuid(query) {
625
+ // If it looks like a UUID, use it directly
626
+ if (this.isLikelyUuid(query)) {
627
+ return query;
628
+ }
629
+ // Otherwise, search by name or domain
630
+ const apps = (await this.listApplications());
631
+ const queryLower = query.toLowerCase();
632
+ const matches = apps.filter((app) => {
633
+ const nameMatch = app.name?.toLowerCase().includes(queryLower);
634
+ const fqdnMatch = app.fqdn?.toLowerCase().includes(queryLower);
635
+ return nameMatch || fqdnMatch;
636
+ });
637
+ if (matches.length === 0) {
638
+ throw new Error(`No application found matching "${query}"`);
639
+ }
640
+ if (matches.length > 1) {
641
+ const matchList = matches.map((a) => `${a.name} (${a.fqdn || 'no domain'})`).join(', ');
642
+ throw new Error(`Multiple applications match "${query}": ${matchList}. Please be more specific or use a UUID.`);
643
+ }
644
+ return matches[0].uuid;
645
+ }
646
+ /**
647
+ * Find a server by UUID, name, or IP address.
648
+ * Returns the UUID if found, throws if not found or multiple matches.
649
+ */
650
+ async resolveServerUuid(query) {
651
+ // If it looks like a UUID, use it directly
652
+ if (this.isLikelyUuid(query)) {
653
+ return query;
654
+ }
655
+ // Otherwise, search by name or IP
656
+ const servers = (await this.listServers());
657
+ const queryLower = query.toLowerCase();
658
+ const matches = servers.filter((server) => {
659
+ const nameMatch = server.name?.toLowerCase().includes(queryLower);
660
+ const ipMatch = server.ip?.toLowerCase().includes(queryLower);
661
+ return nameMatch || ipMatch;
662
+ });
663
+ if (matches.length === 0) {
664
+ throw new Error(`No server found matching "${query}"`);
665
+ }
666
+ if (matches.length > 1) {
667
+ const matchList = matches.map((s) => `${s.name} (${s.ip})`).join(', ');
668
+ throw new Error(`Multiple servers match "${query}": ${matchList}. Please be more specific or use a UUID.`);
669
+ }
670
+ return matches[0].uuid;
671
+ }
672
+ // ===========================================================================
673
+ // Diagnostic endpoints (composite tools)
674
+ // ===========================================================================
675
+ /**
676
+ * Get comprehensive diagnostic info for an application.
677
+ * Aggregates: application details, logs, env vars, recent deployments.
678
+ * @param query - Application UUID, name, or domain (FQDN)
679
+ */
680
+ async diagnoseApplication(query) {
681
+ // Resolve query to UUID
682
+ let uuid;
683
+ try {
684
+ uuid = await this.resolveApplicationUuid(query);
685
+ }
686
+ catch (error) {
687
+ const msg = error instanceof Error ? error.message : String(error);
688
+ return {
689
+ application: null,
690
+ health: { status: 'unknown', issues: [] },
691
+ logs: null,
692
+ environment_variables: { count: 0, variables: [] },
693
+ recent_deployments: [],
694
+ errors: [msg],
695
+ };
696
+ }
697
+ const results = await Promise.allSettled([
698
+ this.getApplication(uuid),
699
+ this.getApplicationLogs(uuid, 50),
700
+ this.listApplicationEnvVars(uuid),
701
+ this.listApplicationDeployments(uuid),
702
+ ]);
703
+ const errors = [];
704
+ const extract = (result, name) => {
705
+ if (result.status === 'fulfilled')
706
+ return result.value;
707
+ const msg = result.reason instanceof Error ? result.reason.message : String(result.reason);
708
+ errors.push(`${name}: ${msg}`);
709
+ return null;
710
+ };
711
+ const app = extract(results[0], 'application');
712
+ const logs = extract(results[1], 'logs');
713
+ const envVars = extract(results[2], 'environment_variables');
714
+ const deployments = extract(results[3], 'deployments');
715
+ // Determine health status and issues
716
+ const issues = [];
717
+ let healthStatus = 'unknown';
718
+ if (app) {
719
+ const status = app.status || '';
720
+ if (status.includes('running') && status.includes('healthy')) {
721
+ healthStatus = 'healthy';
722
+ }
723
+ else if (status.includes('exited') ||
724
+ status.includes('unhealthy') ||
725
+ status.includes('error')) {
726
+ healthStatus = 'unhealthy';
727
+ issues.push(`Status: ${status}`);
728
+ }
729
+ else if (status.includes('running')) {
730
+ healthStatus = 'healthy';
731
+ }
732
+ else {
733
+ issues.push(`Status: ${status}`);
734
+ }
735
+ }
736
+ // Check for failed deployments
737
+ if (deployments) {
738
+ const recentFailed = deployments.slice(0, 5).filter((d) => d.status === 'failed');
739
+ if (recentFailed.length > 0) {
740
+ issues.push(`${recentFailed.length} failed deployment(s) in last 5`);
741
+ if (healthStatus === 'healthy')
742
+ healthStatus = 'unhealthy';
743
+ }
744
+ }
745
+ return {
746
+ application: app
747
+ ? {
748
+ uuid: app.uuid,
749
+ name: app.name,
750
+ status: app.status || 'unknown',
751
+ fqdn: app.fqdn || null,
752
+ git_repository: app.git_repository || null,
753
+ git_branch: app.git_branch || null,
754
+ }
755
+ : null,
756
+ health: {
757
+ status: healthStatus,
758
+ issues,
759
+ },
760
+ logs: typeof logs === 'string' ? logs : null,
761
+ environment_variables: {
762
+ count: envVars?.length || 0,
763
+ variables: (envVars || []).map((v) => ({
764
+ key: v.key,
765
+ is_build_time: v.is_build_time ?? false,
766
+ })),
767
+ },
768
+ recent_deployments: (deployments || []).slice(0, 5).map((d) => ({
769
+ uuid: d.uuid,
770
+ status: d.status,
771
+ created_at: d.created_at,
772
+ })),
773
+ ...(errors.length > 0 && { errors }),
774
+ };
775
+ }
776
+ /**
777
+ * Get comprehensive diagnostic info for a server.
778
+ * Aggregates: server details, resources, domains, validation.
779
+ * @param query - Server UUID, name, or IP address
780
+ */
781
+ async diagnoseServer(query) {
782
+ // Resolve query to UUID
783
+ let uuid;
784
+ try {
785
+ uuid = await this.resolveServerUuid(query);
786
+ }
787
+ catch (error) {
788
+ const msg = error instanceof Error ? error.message : String(error);
789
+ return {
790
+ server: null,
791
+ health: { status: 'unknown', issues: [] },
792
+ resources: [],
793
+ domains: [],
794
+ validation: null,
795
+ errors: [msg],
796
+ };
797
+ }
798
+ const results = await Promise.allSettled([
799
+ this.getServer(uuid),
800
+ this.getServerResources(uuid),
801
+ this.getServerDomains(uuid),
802
+ this.validateServer(uuid),
803
+ ]);
804
+ const errors = [];
805
+ const extract = (result, name) => {
806
+ if (result.status === 'fulfilled')
807
+ return result.value;
808
+ const msg = result.reason instanceof Error ? result.reason.message : String(result.reason);
809
+ errors.push(`${name}: ${msg}`);
810
+ return null;
811
+ };
812
+ const server = extract(results[0], 'server');
813
+ const resources = extract(results[1], 'resources');
814
+ const domains = extract(results[2], 'domains');
815
+ const validation = extract(results[3], 'validation');
816
+ // Determine health status and issues
817
+ const issues = [];
818
+ let healthStatus = 'unknown';
819
+ if (server) {
820
+ if (server.is_reachable === true) {
821
+ healthStatus = 'healthy';
822
+ }
823
+ else if (server.is_reachable === false) {
824
+ healthStatus = 'unhealthy';
825
+ issues.push('Server is not reachable');
826
+ }
827
+ if (server.is_usable === false) {
828
+ issues.push('Server is not usable');
829
+ healthStatus = 'unhealthy';
830
+ }
831
+ }
832
+ // Check for unhealthy resources
833
+ if (resources) {
834
+ const unhealthyResources = resources.filter((r) => r.status.includes('exited') ||
835
+ r.status.includes('unhealthy') ||
836
+ r.status.includes('error'));
837
+ if (unhealthyResources.length > 0) {
838
+ issues.push(`${unhealthyResources.length} unhealthy resource(s)`);
839
+ }
840
+ }
841
+ return {
842
+ server: server
843
+ ? {
844
+ uuid: server.uuid,
845
+ name: server.name,
846
+ ip: server.ip,
847
+ status: server.status || null,
848
+ is_reachable: server.is_reachable ?? null,
849
+ }
850
+ : null,
851
+ health: {
852
+ status: healthStatus,
853
+ issues,
854
+ },
855
+ resources: (resources || []).map((r) => ({
856
+ uuid: r.uuid,
857
+ name: r.name,
858
+ type: r.type,
859
+ status: r.status,
860
+ })),
861
+ domains: (domains || []).map((d) => ({
862
+ ip: d.ip,
863
+ domains: d.domains,
864
+ })),
865
+ validation: validation
866
+ ? {
867
+ message: validation.message,
868
+ ...(validation.validation_logs && { validation_logs: validation.validation_logs }),
869
+ }
870
+ : null,
871
+ ...(errors.length > 0 && { errors }),
872
+ };
873
+ }
874
+ /**
875
+ * Scan infrastructure for common issues.
876
+ * Finds: unreachable servers, unhealthy apps, exited databases, stopped services.
877
+ */
878
+ async findInfrastructureIssues() {
879
+ const results = await Promise.allSettled([
880
+ this.listServers(),
881
+ this.listApplications(),
882
+ this.listDatabases(),
883
+ this.listServices(),
884
+ ]);
885
+ const errors = [];
886
+ const issues = [];
887
+ const extract = (result, name) => {
888
+ if (result.status === 'fulfilled')
889
+ return result.value;
890
+ const msg = result.reason instanceof Error ? result.reason.message : String(result.reason);
891
+ errors.push(`${name}: ${msg}`);
892
+ return null;
893
+ };
894
+ const servers = extract(results[0], 'servers');
895
+ const applications = extract(results[1], 'applications');
896
+ const databases = extract(results[2], 'databases');
897
+ const services = extract(results[3], 'services');
898
+ // Check servers for unreachable
899
+ if (servers) {
900
+ for (const server of servers) {
901
+ if (server.is_reachable === false) {
902
+ issues.push({
903
+ type: 'server',
904
+ uuid: server.uuid,
905
+ name: server.name,
906
+ issue: 'Server is not reachable',
907
+ status: server.status || 'unreachable',
908
+ });
909
+ }
910
+ }
911
+ }
912
+ // Check applications for unhealthy status
913
+ if (applications) {
914
+ for (const app of applications) {
915
+ const status = app.status || '';
916
+ if (status.includes('exited') ||
917
+ status.includes('unhealthy') ||
918
+ status.includes('error') ||
919
+ status === 'stopped') {
920
+ issues.push({
921
+ type: 'application',
922
+ uuid: app.uuid,
923
+ name: app.name,
924
+ issue: `Application status: ${status}`,
925
+ status,
926
+ });
927
+ }
928
+ }
929
+ }
930
+ // Check databases for unhealthy status
931
+ if (databases) {
932
+ for (const db of databases) {
933
+ const status = db.status || '';
934
+ if (status.includes('exited') ||
935
+ status.includes('unhealthy') ||
936
+ status.includes('error') ||
937
+ status === 'stopped') {
938
+ issues.push({
939
+ type: 'database',
940
+ uuid: db.uuid,
941
+ name: db.name,
942
+ issue: `Database status: ${status}`,
943
+ status,
944
+ });
945
+ }
946
+ }
947
+ }
948
+ // Check services for unhealthy status
949
+ if (services) {
950
+ for (const svc of services) {
951
+ const status = svc.status || '';
952
+ if (status.includes('exited') ||
953
+ status.includes('unhealthy') ||
954
+ status.includes('error') ||
955
+ status === 'stopped') {
956
+ issues.push({
957
+ type: 'service',
958
+ uuid: svc.uuid,
959
+ name: svc.name,
960
+ issue: `Service status: ${status}`,
961
+ status,
962
+ });
963
+ }
964
+ }
965
+ }
966
+ return {
967
+ summary: {
968
+ total_issues: issues.length,
969
+ unhealthy_applications: issues.filter((i) => i.type === 'application').length,
970
+ unhealthy_databases: issues.filter((i) => i.type === 'database').length,
971
+ unhealthy_services: issues.filter((i) => i.type === 'service').length,
972
+ unreachable_servers: issues.filter((i) => i.type === 'server').length,
973
+ },
974
+ issues,
975
+ ...(errors.length > 0 && { errors }),
976
+ };
977
+ }
590
978
  }
@@ -20,7 +20,7 @@
20
20
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
21
21
  import { z } from 'zod';
22
22
  import { CoolifyClient, } from './coolify-client.js';
23
- const VERSION = '0.6.0';
23
+ const VERSION = '0.8.0';
24
24
  /** Wrap tool handler with consistent error handling */
25
25
  function wrapHandler(fn) {
26
26
  return fn()
@@ -306,5 +306,46 @@ export class CoolifyMcpServer extends McpServer {
306
306
  force: z.boolean().optional().describe('Force rebuild'),
307
307
  }, async ({ tag_or_uuid, force }) => wrapHandler(() => this.client.deployByTagOrUuid(tag_or_uuid, force)));
308
308
  this.tool('list_application_deployments', 'List deployments for an application', { uuid: z.string().describe('Application UUID') }, async ({ uuid }) => wrapHandler(() => this.client.listApplicationDeployments(uuid)));
309
+ this.tool('cancel_deployment', 'Cancel a running deployment', { uuid: z.string().describe('Deployment UUID') }, async ({ uuid }) => wrapHandler(() => this.client.cancelDeployment(uuid)));
310
+ // =========================================================================
311
+ // Private Keys (5 tools)
312
+ // =========================================================================
313
+ this.tool('list_private_keys', 'List all private keys (SSH keys for deployments)', {}, async () => wrapHandler(() => this.client.listPrivateKeys()));
314
+ this.tool('get_private_key', 'Get private key details', { uuid: z.string().describe('Private key UUID') }, async ({ uuid }) => wrapHandler(() => this.client.getPrivateKey(uuid)));
315
+ this.tool('create_private_key', 'Create a new private key for deployments', {
316
+ private_key: z.string().describe('The private key content (PEM format)'),
317
+ name: z.string().optional().describe('Name for the key'),
318
+ description: z.string().optional().describe('Description'),
319
+ }, async (args) => wrapHandler(() => this.client.createPrivateKey(args)));
320
+ this.tool('update_private_key', 'Update a private key', {
321
+ uuid: z.string().describe('Private key UUID'),
322
+ name: z.string().optional().describe('Name for the key'),
323
+ description: z.string().optional().describe('Description'),
324
+ private_key: z.string().optional().describe('The private key content (PEM format)'),
325
+ }, async ({ uuid, ...data }) => wrapHandler(() => this.client.updatePrivateKey(uuid, data)));
326
+ this.tool('delete_private_key', 'Delete a private key', { uuid: z.string().describe('Private key UUID') }, async ({ uuid }) => wrapHandler(() => this.client.deletePrivateKey(uuid)));
327
+ // =========================================================================
328
+ // Database Backups (4 tools)
329
+ // =========================================================================
330
+ this.tool('list_database_backups', 'List scheduled backups for a database', { uuid: z.string().describe('Database UUID') }, async ({ uuid }) => wrapHandler(() => this.client.listDatabaseBackups(uuid)));
331
+ this.tool('get_database_backup', 'Get details of a scheduled backup', {
332
+ database_uuid: z.string().describe('Database UUID'),
333
+ backup_uuid: z.string().describe('Scheduled backup UUID'),
334
+ }, async ({ database_uuid, backup_uuid }) => wrapHandler(() => this.client.getDatabaseBackup(database_uuid, backup_uuid)));
335
+ this.tool('list_backup_executions', 'List execution history for a scheduled backup', {
336
+ database_uuid: z.string().describe('Database UUID'),
337
+ backup_uuid: z.string().describe('Scheduled backup UUID'),
338
+ }, async ({ database_uuid, backup_uuid }) => wrapHandler(() => this.client.listBackupExecutions(database_uuid, backup_uuid)));
339
+ this.tool('get_backup_execution', 'Get details of a specific backup execution', {
340
+ database_uuid: z.string().describe('Database UUID'),
341
+ backup_uuid: z.string().describe('Scheduled backup UUID'),
342
+ execution_uuid: z.string().describe('Backup execution UUID'),
343
+ }, async ({ database_uuid, backup_uuid, execution_uuid }) => wrapHandler(() => this.client.getBackupExecution(database_uuid, backup_uuid, execution_uuid)));
344
+ // =========================================================================
345
+ // Diagnostics (3 tools) - Composite tools for debugging
346
+ // =========================================================================
347
+ this.tool('diagnose_app', 'Get comprehensive diagnostic info for an application. Accepts UUID, name, or domain (e.g., "tidylinker.com" or "my-app"). Aggregates: status, health assessment, logs (last 50 lines), environment variables (keys only, values hidden), and recent deployments. Use this for debugging application issues.', { query: z.string().describe('Application UUID, name, or domain (FQDN)') }, async ({ query }) => wrapHandler(() => this.client.diagnoseApplication(query)));
348
+ this.tool('diagnose_server', 'Get comprehensive diagnostic info for a server. Accepts UUID, name, or IP address (e.g., "coolify-apps" or "192.168.1.100"). Aggregates: server status, health assessment, running resources, configured domains, and connection validation. Use this for debugging server issues.', { query: z.string().describe('Server UUID, name, or IP address') }, async ({ query }) => wrapHandler(() => this.client.diagnoseServer(query)));
349
+ this.tool('find_issues', 'Scan entire infrastructure for common issues. Finds: unreachable servers, unhealthy/stopped applications, exited databases, and stopped services. Returns a summary with issue counts and detailed list of problems.', {}, async () => wrapHandler(() => this.client.findInfrastructureIssues()));
309
350
  }
310
351
  }
@@ -300,6 +300,9 @@ export interface UpdateApplicationRequest {
300
300
  limits_memory?: string;
301
301
  limits_memory_swap?: string;
302
302
  limits_cpus?: string;
303
+ is_http_basic_auth_enabled?: boolean;
304
+ http_basic_auth_username?: string;
305
+ http_basic_auth_password?: string;
303
306
  }
304
307
  export interface ApplicationActionResponse {
305
308
  message: string;
@@ -470,6 +473,17 @@ export interface CreateDatabaseBackupRequest {
470
473
  backup_retention?: number;
471
474
  backup_retention_days?: number;
472
475
  }
476
+ export interface BackupExecution {
477
+ id: number;
478
+ uuid: string;
479
+ scheduled_database_backup_id: number;
480
+ status: 'pending' | 'running' | 'success' | 'failed';
481
+ message?: string;
482
+ size?: number;
483
+ filename?: string;
484
+ created_at: string;
485
+ updated_at: string;
486
+ }
473
487
  /**
474
488
  * Available one-click service types in Coolify.
475
489
  * This is a string type to avoid TypeScript memory issues with large const arrays.
@@ -639,3 +653,78 @@ export interface HealthCheck {
639
653
  status: 'healthy' | 'unhealthy';
640
654
  version?: string;
641
655
  }
656
+ export type DiagnosticHealthStatus = 'healthy' | 'unhealthy' | 'unknown';
657
+ export interface ApplicationDiagnostic {
658
+ application: {
659
+ uuid: string;
660
+ name: string;
661
+ status: string;
662
+ fqdn: string | null;
663
+ git_repository: string | null;
664
+ git_branch: string | null;
665
+ } | null;
666
+ health: {
667
+ status: DiagnosticHealthStatus;
668
+ issues: string[];
669
+ };
670
+ logs: string | null;
671
+ environment_variables: {
672
+ count: number;
673
+ variables: Array<{
674
+ key: string;
675
+ is_build_time: boolean;
676
+ }>;
677
+ };
678
+ recent_deployments: Array<{
679
+ uuid: string;
680
+ status: string;
681
+ created_at: string;
682
+ }>;
683
+ errors?: string[];
684
+ }
685
+ export interface ServerDiagnostic {
686
+ server: {
687
+ uuid: string;
688
+ name: string;
689
+ ip: string;
690
+ status: string | null;
691
+ is_reachable: boolean | null;
692
+ } | null;
693
+ health: {
694
+ status: DiagnosticHealthStatus;
695
+ issues: string[];
696
+ };
697
+ resources: Array<{
698
+ uuid: string;
699
+ name: string;
700
+ type: string;
701
+ status: string;
702
+ }>;
703
+ domains: Array<{
704
+ ip: string;
705
+ domains: string[];
706
+ }>;
707
+ validation: {
708
+ message: string;
709
+ validation_logs?: string;
710
+ } | null;
711
+ errors?: string[];
712
+ }
713
+ export interface InfrastructureIssue {
714
+ type: 'application' | 'database' | 'service' | 'server';
715
+ uuid: string;
716
+ name: string;
717
+ issue: string;
718
+ status: string;
719
+ }
720
+ export interface InfrastructureIssuesReport {
721
+ summary: {
722
+ total_issues: number;
723
+ unhealthy_applications: number;
724
+ unhealthy_databases: number;
725
+ unhealthy_services: number;
726
+ unreachable_servers: number;
727
+ };
728
+ issues: InfrastructureIssue[];
729
+ errors?: string[];
730
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@masonator/coolify-mcp",
3
3
  "scope": "@masonator",
4
- "version": "0.6.0",
4
+ "version": "0.8.0",
5
5
  "description": "MCP server implementation for Coolify",
6
6
  "type": "module",
7
7
  "main": "./dist/index.js",
@@ -21,9 +21,10 @@
21
21
  "scripts": {
22
22
  "build": "tsc && shx chmod +x dist/*.js",
23
23
  "dev": "tsc --watch",
24
- "test": "NODE_OPTIONS=--experimental-vm-modules jest",
25
- "test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch",
26
- "test:coverage": "NODE_OPTIONS=--experimental-vm-modules jest --coverage",
24
+ "test": "NODE_OPTIONS=--experimental-vm-modules jest --testPathIgnorePatterns=integration",
25
+ "test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch --testPathIgnorePatterns=integration",
26
+ "test:coverage": "NODE_OPTIONS=--experimental-vm-modules jest --coverage --testPathIgnorePatterns=integration",
27
+ "test:integration": "NODE_OPTIONS=--experimental-vm-modules jest --testPathPattern=integration --testTimeout=60000",
27
28
  "lint": "eslint . --ext .ts",
28
29
  "lint:fix": "eslint . --ext .ts --fix",
29
30
  "format": "prettier --write .",
@@ -39,6 +40,10 @@
39
40
  ],
40
41
  "author": "Stuart Mason",
41
42
  "license": "MIT",
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "https://github.com/StuMason/coolify-mcp.git"
46
+ },
42
47
  "dependencies": {
43
48
  "@modelcontextprotocol/sdk": "^1.6.1",
44
49
  "zod": "^3.24.2"
@@ -48,6 +53,7 @@
48
53
  "@types/node": "^20.17.23",
49
54
  "@typescript-eslint/eslint-plugin": "^7.18.0",
50
55
  "@typescript-eslint/parser": "^7.18.0",
56
+ "dotenv": "^16.4.5",
51
57
  "eslint": "^8.56.0",
52
58
  "eslint-config-prettier": "^9.1.0",
53
59
  "husky": "^9.0.11",
@@ -61,5 +67,9 @@
61
67
  },
62
68
  "engines": {
63
69
  "node": ">=18"
70
+ },
71
+ "lint-staged": {
72
+ "*.{ts,js,json,md,yaml,yml}": "prettier --write",
73
+ "*.ts": "eslint --fix"
64
74
  }
65
75
  }