@masonator/coolify-mcp 0.7.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -13
- package/dist/__tests__/coolify-client.test.js +568 -0
- package/dist/__tests__/integration/diagnostics.integration.test.d.ts +13 -0
- package/dist/__tests__/integration/diagnostics.integration.test.js +140 -0
- package/dist/__tests__/mcp-server.test.js +166 -0
- package/dist/lib/coolify-client.d.ts +37 -2
- package/dist/lib/coolify-client.js +388 -2
- package/dist/lib/mcp-server.js +8 -2
- package/dist/types/coolify.d.ts +81 -0
- package/package.json +10 -4
|
@@ -71,6 +71,14 @@ function toProjectSummary(proj) {
|
|
|
71
71
|
description: proj.description,
|
|
72
72
|
};
|
|
73
73
|
}
|
|
74
|
+
function toEnvVarSummary(envVar) {
|
|
75
|
+
return {
|
|
76
|
+
uuid: envVar.uuid,
|
|
77
|
+
key: envVar.key,
|
|
78
|
+
value: envVar.value,
|
|
79
|
+
is_build_time: envVar.is_build_time,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
74
82
|
/**
|
|
75
83
|
* HTTP client for the Coolify API
|
|
76
84
|
*/
|
|
@@ -333,8 +341,9 @@ export class CoolifyClient {
|
|
|
333
341
|
// ===========================================================================
|
|
334
342
|
// Application Environment Variables
|
|
335
343
|
// ===========================================================================
|
|
336
|
-
async listApplicationEnvVars(uuid) {
|
|
337
|
-
|
|
344
|
+
async listApplicationEnvVars(uuid, options) {
|
|
345
|
+
const envVars = await this.request(`/applications/${uuid}/envs`);
|
|
346
|
+
return options?.summary ? envVars.map(toEnvVarSummary) : envVars;
|
|
338
347
|
}
|
|
339
348
|
async createApplicationEnvVar(uuid, data) {
|
|
340
349
|
return this.request(`/applications/${uuid}/envs`, {
|
|
@@ -598,4 +607,381 @@ export class CoolifyClient {
|
|
|
598
607
|
method: 'POST',
|
|
599
608
|
});
|
|
600
609
|
}
|
|
610
|
+
// ===========================================================================
|
|
611
|
+
// Smart Lookup Helpers
|
|
612
|
+
// ===========================================================================
|
|
613
|
+
/**
|
|
614
|
+
* Check if a string looks like a UUID (Coolify format or standard format).
|
|
615
|
+
* Coolify UUIDs are alphanumeric strings, typically 24 chars like "xs0sgs4gog044s4k4c88kgsc"
|
|
616
|
+
* Also accepts standard UUID format with hyphens like "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
|
617
|
+
*/
|
|
618
|
+
isLikelyUuid(query) {
|
|
619
|
+
// Coolify UUID format: alphanumeric, 20+ chars
|
|
620
|
+
if (/^[a-z0-9]{20,}$/i.test(query)) {
|
|
621
|
+
return true;
|
|
622
|
+
}
|
|
623
|
+
// Standard UUID format with hyphens (8-4-4-4-12)
|
|
624
|
+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(query)) {
|
|
625
|
+
return true;
|
|
626
|
+
}
|
|
627
|
+
return false;
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Find an application by UUID, name, or domain (FQDN).
|
|
631
|
+
* Returns the UUID if found, throws if not found or multiple matches.
|
|
632
|
+
*/
|
|
633
|
+
async resolveApplicationUuid(query) {
|
|
634
|
+
// If it looks like a UUID, use it directly
|
|
635
|
+
if (this.isLikelyUuid(query)) {
|
|
636
|
+
return query;
|
|
637
|
+
}
|
|
638
|
+
// Otherwise, search by name or domain
|
|
639
|
+
const apps = (await this.listApplications());
|
|
640
|
+
const queryLower = query.toLowerCase();
|
|
641
|
+
const matches = apps.filter((app) => {
|
|
642
|
+
const nameMatch = app.name?.toLowerCase().includes(queryLower);
|
|
643
|
+
const fqdnMatch = app.fqdn?.toLowerCase().includes(queryLower);
|
|
644
|
+
return nameMatch || fqdnMatch;
|
|
645
|
+
});
|
|
646
|
+
if (matches.length === 0) {
|
|
647
|
+
throw new Error(`No application found matching "${query}"`);
|
|
648
|
+
}
|
|
649
|
+
if (matches.length > 1) {
|
|
650
|
+
const matchList = matches.map((a) => `${a.name} (${a.fqdn || 'no domain'})`).join(', ');
|
|
651
|
+
throw new Error(`Multiple applications match "${query}": ${matchList}. Please be more specific or use a UUID.`);
|
|
652
|
+
}
|
|
653
|
+
return matches[0].uuid;
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Find a server by UUID, name, or IP address.
|
|
657
|
+
* Returns the UUID if found, throws if not found or multiple matches.
|
|
658
|
+
*/
|
|
659
|
+
async resolveServerUuid(query) {
|
|
660
|
+
// If it looks like a UUID, use it directly
|
|
661
|
+
if (this.isLikelyUuid(query)) {
|
|
662
|
+
return query;
|
|
663
|
+
}
|
|
664
|
+
// Otherwise, search by name or IP
|
|
665
|
+
const servers = (await this.listServers());
|
|
666
|
+
const queryLower = query.toLowerCase();
|
|
667
|
+
const matches = servers.filter((server) => {
|
|
668
|
+
const nameMatch = server.name?.toLowerCase().includes(queryLower);
|
|
669
|
+
const ipMatch = server.ip?.toLowerCase().includes(queryLower);
|
|
670
|
+
return nameMatch || ipMatch;
|
|
671
|
+
});
|
|
672
|
+
if (matches.length === 0) {
|
|
673
|
+
throw new Error(`No server found matching "${query}"`);
|
|
674
|
+
}
|
|
675
|
+
if (matches.length > 1) {
|
|
676
|
+
const matchList = matches.map((s) => `${s.name} (${s.ip})`).join(', ');
|
|
677
|
+
throw new Error(`Multiple servers match "${query}": ${matchList}. Please be more specific or use a UUID.`);
|
|
678
|
+
}
|
|
679
|
+
return matches[0].uuid;
|
|
680
|
+
}
|
|
681
|
+
// ===========================================================================
|
|
682
|
+
// Diagnostic endpoints (composite tools)
|
|
683
|
+
// ===========================================================================
|
|
684
|
+
/**
|
|
685
|
+
* Get comprehensive diagnostic info for an application.
|
|
686
|
+
* Aggregates: application details, logs, env vars, recent deployments.
|
|
687
|
+
* @param query - Application UUID, name, or domain (FQDN)
|
|
688
|
+
*/
|
|
689
|
+
async diagnoseApplication(query) {
|
|
690
|
+
// Resolve query to UUID
|
|
691
|
+
let uuid;
|
|
692
|
+
try {
|
|
693
|
+
uuid = await this.resolveApplicationUuid(query);
|
|
694
|
+
}
|
|
695
|
+
catch (error) {
|
|
696
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
697
|
+
return {
|
|
698
|
+
application: null,
|
|
699
|
+
health: { status: 'unknown', issues: [] },
|
|
700
|
+
logs: null,
|
|
701
|
+
environment_variables: { count: 0, variables: [] },
|
|
702
|
+
recent_deployments: [],
|
|
703
|
+
errors: [msg],
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
const results = await Promise.allSettled([
|
|
707
|
+
this.getApplication(uuid),
|
|
708
|
+
this.getApplicationLogs(uuid, 50),
|
|
709
|
+
this.listApplicationEnvVars(uuid),
|
|
710
|
+
this.listApplicationDeployments(uuid),
|
|
711
|
+
]);
|
|
712
|
+
const errors = [];
|
|
713
|
+
const extract = (result, name) => {
|
|
714
|
+
if (result.status === 'fulfilled')
|
|
715
|
+
return result.value;
|
|
716
|
+
const msg = result.reason instanceof Error ? result.reason.message : String(result.reason);
|
|
717
|
+
errors.push(`${name}: ${msg}`);
|
|
718
|
+
return null;
|
|
719
|
+
};
|
|
720
|
+
const app = extract(results[0], 'application');
|
|
721
|
+
const logs = extract(results[1], 'logs');
|
|
722
|
+
const envVars = extract(results[2], 'environment_variables');
|
|
723
|
+
const deployments = extract(results[3], 'deployments');
|
|
724
|
+
// Determine health status and issues
|
|
725
|
+
const issues = [];
|
|
726
|
+
let healthStatus = 'unknown';
|
|
727
|
+
if (app) {
|
|
728
|
+
const status = app.status || '';
|
|
729
|
+
if (status.includes('running') && status.includes('healthy')) {
|
|
730
|
+
healthStatus = 'healthy';
|
|
731
|
+
}
|
|
732
|
+
else if (status.includes('exited') ||
|
|
733
|
+
status.includes('unhealthy') ||
|
|
734
|
+
status.includes('error')) {
|
|
735
|
+
healthStatus = 'unhealthy';
|
|
736
|
+
issues.push(`Status: ${status}`);
|
|
737
|
+
}
|
|
738
|
+
else if (status.includes('running')) {
|
|
739
|
+
healthStatus = 'healthy';
|
|
740
|
+
}
|
|
741
|
+
else {
|
|
742
|
+
issues.push(`Status: ${status}`);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
// Check for failed deployments
|
|
746
|
+
if (deployments) {
|
|
747
|
+
const recentFailed = deployments.slice(0, 5).filter((d) => d.status === 'failed');
|
|
748
|
+
if (recentFailed.length > 0) {
|
|
749
|
+
issues.push(`${recentFailed.length} failed deployment(s) in last 5`);
|
|
750
|
+
if (healthStatus === 'healthy')
|
|
751
|
+
healthStatus = 'unhealthy';
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
return {
|
|
755
|
+
application: app
|
|
756
|
+
? {
|
|
757
|
+
uuid: app.uuid,
|
|
758
|
+
name: app.name,
|
|
759
|
+
status: app.status || 'unknown',
|
|
760
|
+
fqdn: app.fqdn || null,
|
|
761
|
+
git_repository: app.git_repository || null,
|
|
762
|
+
git_branch: app.git_branch || null,
|
|
763
|
+
}
|
|
764
|
+
: null,
|
|
765
|
+
health: {
|
|
766
|
+
status: healthStatus,
|
|
767
|
+
issues,
|
|
768
|
+
},
|
|
769
|
+
logs: typeof logs === 'string' ? logs : null,
|
|
770
|
+
environment_variables: {
|
|
771
|
+
count: envVars?.length || 0,
|
|
772
|
+
variables: (envVars || []).map((v) => ({
|
|
773
|
+
key: v.key,
|
|
774
|
+
is_build_time: v.is_build_time ?? false,
|
|
775
|
+
})),
|
|
776
|
+
},
|
|
777
|
+
recent_deployments: (deployments || []).slice(0, 5).map((d) => ({
|
|
778
|
+
uuid: d.uuid,
|
|
779
|
+
status: d.status,
|
|
780
|
+
created_at: d.created_at,
|
|
781
|
+
})),
|
|
782
|
+
...(errors.length > 0 && { errors }),
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Get comprehensive diagnostic info for a server.
|
|
787
|
+
* Aggregates: server details, resources, domains, validation.
|
|
788
|
+
* @param query - Server UUID, name, or IP address
|
|
789
|
+
*/
|
|
790
|
+
async diagnoseServer(query) {
|
|
791
|
+
// Resolve query to UUID
|
|
792
|
+
let uuid;
|
|
793
|
+
try {
|
|
794
|
+
uuid = await this.resolveServerUuid(query);
|
|
795
|
+
}
|
|
796
|
+
catch (error) {
|
|
797
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
798
|
+
return {
|
|
799
|
+
server: null,
|
|
800
|
+
health: { status: 'unknown', issues: [] },
|
|
801
|
+
resources: [],
|
|
802
|
+
domains: [],
|
|
803
|
+
validation: null,
|
|
804
|
+
errors: [msg],
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
const results = await Promise.allSettled([
|
|
808
|
+
this.getServer(uuid),
|
|
809
|
+
this.getServerResources(uuid),
|
|
810
|
+
this.getServerDomains(uuid),
|
|
811
|
+
this.validateServer(uuid),
|
|
812
|
+
]);
|
|
813
|
+
const errors = [];
|
|
814
|
+
const extract = (result, name) => {
|
|
815
|
+
if (result.status === 'fulfilled')
|
|
816
|
+
return result.value;
|
|
817
|
+
const msg = result.reason instanceof Error ? result.reason.message : String(result.reason);
|
|
818
|
+
errors.push(`${name}: ${msg}`);
|
|
819
|
+
return null;
|
|
820
|
+
};
|
|
821
|
+
const server = extract(results[0], 'server');
|
|
822
|
+
const resources = extract(results[1], 'resources');
|
|
823
|
+
const domains = extract(results[2], 'domains');
|
|
824
|
+
const validation = extract(results[3], 'validation');
|
|
825
|
+
// Determine health status and issues
|
|
826
|
+
const issues = [];
|
|
827
|
+
let healthStatus = 'unknown';
|
|
828
|
+
if (server) {
|
|
829
|
+
if (server.is_reachable === true) {
|
|
830
|
+
healthStatus = 'healthy';
|
|
831
|
+
}
|
|
832
|
+
else if (server.is_reachable === false) {
|
|
833
|
+
healthStatus = 'unhealthy';
|
|
834
|
+
issues.push('Server is not reachable');
|
|
835
|
+
}
|
|
836
|
+
if (server.is_usable === false) {
|
|
837
|
+
issues.push('Server is not usable');
|
|
838
|
+
healthStatus = 'unhealthy';
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
// Check for unhealthy resources
|
|
842
|
+
if (resources) {
|
|
843
|
+
const unhealthyResources = resources.filter((r) => r.status.includes('exited') ||
|
|
844
|
+
r.status.includes('unhealthy') ||
|
|
845
|
+
r.status.includes('error'));
|
|
846
|
+
if (unhealthyResources.length > 0) {
|
|
847
|
+
issues.push(`${unhealthyResources.length} unhealthy resource(s)`);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
return {
|
|
851
|
+
server: server
|
|
852
|
+
? {
|
|
853
|
+
uuid: server.uuid,
|
|
854
|
+
name: server.name,
|
|
855
|
+
ip: server.ip,
|
|
856
|
+
status: server.status || null,
|
|
857
|
+
is_reachable: server.is_reachable ?? null,
|
|
858
|
+
}
|
|
859
|
+
: null,
|
|
860
|
+
health: {
|
|
861
|
+
status: healthStatus,
|
|
862
|
+
issues,
|
|
863
|
+
},
|
|
864
|
+
resources: (resources || []).map((r) => ({
|
|
865
|
+
uuid: r.uuid,
|
|
866
|
+
name: r.name,
|
|
867
|
+
type: r.type,
|
|
868
|
+
status: r.status,
|
|
869
|
+
})),
|
|
870
|
+
domains: (domains || []).map((d) => ({
|
|
871
|
+
ip: d.ip,
|
|
872
|
+
domains: d.domains,
|
|
873
|
+
})),
|
|
874
|
+
validation: validation
|
|
875
|
+
? {
|
|
876
|
+
message: validation.message,
|
|
877
|
+
...(validation.validation_logs && { validation_logs: validation.validation_logs }),
|
|
878
|
+
}
|
|
879
|
+
: null,
|
|
880
|
+
...(errors.length > 0 && { errors }),
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
/**
|
|
884
|
+
* Scan infrastructure for common issues.
|
|
885
|
+
* Finds: unreachable servers, unhealthy apps, exited databases, stopped services.
|
|
886
|
+
*/
|
|
887
|
+
async findInfrastructureIssues() {
|
|
888
|
+
const results = await Promise.allSettled([
|
|
889
|
+
this.listServers(),
|
|
890
|
+
this.listApplications(),
|
|
891
|
+
this.listDatabases(),
|
|
892
|
+
this.listServices(),
|
|
893
|
+
]);
|
|
894
|
+
const errors = [];
|
|
895
|
+
const issues = [];
|
|
896
|
+
const extract = (result, name) => {
|
|
897
|
+
if (result.status === 'fulfilled')
|
|
898
|
+
return result.value;
|
|
899
|
+
const msg = result.reason instanceof Error ? result.reason.message : String(result.reason);
|
|
900
|
+
errors.push(`${name}: ${msg}`);
|
|
901
|
+
return null;
|
|
902
|
+
};
|
|
903
|
+
const servers = extract(results[0], 'servers');
|
|
904
|
+
const applications = extract(results[1], 'applications');
|
|
905
|
+
const databases = extract(results[2], 'databases');
|
|
906
|
+
const services = extract(results[3], 'services');
|
|
907
|
+
// Check servers for unreachable
|
|
908
|
+
if (servers) {
|
|
909
|
+
for (const server of servers) {
|
|
910
|
+
if (server.is_reachable === false) {
|
|
911
|
+
issues.push({
|
|
912
|
+
type: 'server',
|
|
913
|
+
uuid: server.uuid,
|
|
914
|
+
name: server.name,
|
|
915
|
+
issue: 'Server is not reachable',
|
|
916
|
+
status: server.status || 'unreachable',
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
// Check applications for unhealthy status
|
|
922
|
+
if (applications) {
|
|
923
|
+
for (const app of applications) {
|
|
924
|
+
const status = app.status || '';
|
|
925
|
+
if (status.includes('exited') ||
|
|
926
|
+
status.includes('unhealthy') ||
|
|
927
|
+
status.includes('error') ||
|
|
928
|
+
status === 'stopped') {
|
|
929
|
+
issues.push({
|
|
930
|
+
type: 'application',
|
|
931
|
+
uuid: app.uuid,
|
|
932
|
+
name: app.name,
|
|
933
|
+
issue: `Application status: ${status}`,
|
|
934
|
+
status,
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
// Check databases for unhealthy status
|
|
940
|
+
if (databases) {
|
|
941
|
+
for (const db of databases) {
|
|
942
|
+
const status = db.status || '';
|
|
943
|
+
if (status.includes('exited') ||
|
|
944
|
+
status.includes('unhealthy') ||
|
|
945
|
+
status.includes('error') ||
|
|
946
|
+
status === 'stopped') {
|
|
947
|
+
issues.push({
|
|
948
|
+
type: 'database',
|
|
949
|
+
uuid: db.uuid,
|
|
950
|
+
name: db.name,
|
|
951
|
+
issue: `Database status: ${status}`,
|
|
952
|
+
status,
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
// Check services for unhealthy status
|
|
958
|
+
if (services) {
|
|
959
|
+
for (const svc of services) {
|
|
960
|
+
const status = svc.status || '';
|
|
961
|
+
if (status.includes('exited') ||
|
|
962
|
+
status.includes('unhealthy') ||
|
|
963
|
+
status.includes('error') ||
|
|
964
|
+
status === 'stopped') {
|
|
965
|
+
issues.push({
|
|
966
|
+
type: 'service',
|
|
967
|
+
uuid: svc.uuid,
|
|
968
|
+
name: svc.name,
|
|
969
|
+
issue: `Service status: ${status}`,
|
|
970
|
+
status,
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
return {
|
|
976
|
+
summary: {
|
|
977
|
+
total_issues: issues.length,
|
|
978
|
+
unhealthy_applications: issues.filter((i) => i.type === 'application').length,
|
|
979
|
+
unhealthy_databases: issues.filter((i) => i.type === 'database').length,
|
|
980
|
+
unhealthy_services: issues.filter((i) => i.type === 'service').length,
|
|
981
|
+
unreachable_servers: issues.filter((i) => i.type === 'server').length,
|
|
982
|
+
},
|
|
983
|
+
issues,
|
|
984
|
+
...(errors.length > 0 && { errors }),
|
|
985
|
+
};
|
|
986
|
+
}
|
|
601
987
|
}
|
package/dist/lib/mcp-server.js
CHANGED
|
@@ -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.
|
|
23
|
+
const VERSION = '0.8.1';
|
|
24
24
|
/** Wrap tool handler with consistent error handling */
|
|
25
25
|
function wrapHandler(fn) {
|
|
26
26
|
return fn()
|
|
@@ -193,7 +193,7 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
193
193
|
lines: z.number().optional().describe('Number of lines'),
|
|
194
194
|
}, async ({ uuid, lines }) => wrapHandler(() => this.client.getApplicationLogs(uuid, lines)));
|
|
195
195
|
// Application env vars
|
|
196
|
-
this.tool('list_application_envs', 'List application environment variables', { uuid: z.string().describe('Application UUID') }, async ({ uuid }) => wrapHandler(() => this.client.listApplicationEnvVars(uuid)));
|
|
196
|
+
this.tool('list_application_envs', 'List application environment variables (returns summary: uuid, key, value, is_build_time)', { uuid: z.string().describe('Application UUID') }, async ({ uuid }) => wrapHandler(() => this.client.listApplicationEnvVars(uuid, { summary: true })));
|
|
197
197
|
this.tool('create_application_env', 'Create application environment variable', {
|
|
198
198
|
uuid: z.string().describe('Application UUID'),
|
|
199
199
|
key: z.string().describe('Variable key'),
|
|
@@ -341,5 +341,11 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
341
341
|
backup_uuid: z.string().describe('Scheduled backup UUID'),
|
|
342
342
|
execution_uuid: z.string().describe('Backup execution UUID'),
|
|
343
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., "stuartmason.co.uk" 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()));
|
|
344
350
|
}
|
|
345
351
|
}
|
package/dist/types/coolify.d.ts
CHANGED
|
@@ -348,6 +348,12 @@ export interface UpdateEnvVarRequest {
|
|
|
348
348
|
export interface BulkUpdateEnvVarsRequest {
|
|
349
349
|
data: CreateEnvVarRequest[];
|
|
350
350
|
}
|
|
351
|
+
export interface EnvVarSummary {
|
|
352
|
+
uuid: string;
|
|
353
|
+
key: string;
|
|
354
|
+
value: string;
|
|
355
|
+
is_build_time: boolean;
|
|
356
|
+
}
|
|
351
357
|
export type DatabaseType = 'postgresql' | 'mysql' | 'mariadb' | 'mongodb' | 'redis' | 'keydb' | 'clickhouse' | 'dragonfly';
|
|
352
358
|
export interface DatabaseLimits {
|
|
353
359
|
memory?: string;
|
|
@@ -653,3 +659,78 @@ export interface HealthCheck {
|
|
|
653
659
|
status: 'healthy' | 'unhealthy';
|
|
654
660
|
version?: string;
|
|
655
661
|
}
|
|
662
|
+
export type DiagnosticHealthStatus = 'healthy' | 'unhealthy' | 'unknown';
|
|
663
|
+
export interface ApplicationDiagnostic {
|
|
664
|
+
application: {
|
|
665
|
+
uuid: string;
|
|
666
|
+
name: string;
|
|
667
|
+
status: string;
|
|
668
|
+
fqdn: string | null;
|
|
669
|
+
git_repository: string | null;
|
|
670
|
+
git_branch: string | null;
|
|
671
|
+
} | null;
|
|
672
|
+
health: {
|
|
673
|
+
status: DiagnosticHealthStatus;
|
|
674
|
+
issues: string[];
|
|
675
|
+
};
|
|
676
|
+
logs: string | null;
|
|
677
|
+
environment_variables: {
|
|
678
|
+
count: number;
|
|
679
|
+
variables: Array<{
|
|
680
|
+
key: string;
|
|
681
|
+
is_build_time: boolean;
|
|
682
|
+
}>;
|
|
683
|
+
};
|
|
684
|
+
recent_deployments: Array<{
|
|
685
|
+
uuid: string;
|
|
686
|
+
status: string;
|
|
687
|
+
created_at: string;
|
|
688
|
+
}>;
|
|
689
|
+
errors?: string[];
|
|
690
|
+
}
|
|
691
|
+
export interface ServerDiagnostic {
|
|
692
|
+
server: {
|
|
693
|
+
uuid: string;
|
|
694
|
+
name: string;
|
|
695
|
+
ip: string;
|
|
696
|
+
status: string | null;
|
|
697
|
+
is_reachable: boolean | null;
|
|
698
|
+
} | null;
|
|
699
|
+
health: {
|
|
700
|
+
status: DiagnosticHealthStatus;
|
|
701
|
+
issues: string[];
|
|
702
|
+
};
|
|
703
|
+
resources: Array<{
|
|
704
|
+
uuid: string;
|
|
705
|
+
name: string;
|
|
706
|
+
type: string;
|
|
707
|
+
status: string;
|
|
708
|
+
}>;
|
|
709
|
+
domains: Array<{
|
|
710
|
+
ip: string;
|
|
711
|
+
domains: string[];
|
|
712
|
+
}>;
|
|
713
|
+
validation: {
|
|
714
|
+
message: string;
|
|
715
|
+
validation_logs?: string;
|
|
716
|
+
} | null;
|
|
717
|
+
errors?: string[];
|
|
718
|
+
}
|
|
719
|
+
export interface InfrastructureIssue {
|
|
720
|
+
type: 'application' | 'database' | 'service' | 'server';
|
|
721
|
+
uuid: string;
|
|
722
|
+
name: string;
|
|
723
|
+
issue: string;
|
|
724
|
+
status: string;
|
|
725
|
+
}
|
|
726
|
+
export interface InfrastructureIssuesReport {
|
|
727
|
+
summary: {
|
|
728
|
+
total_issues: number;
|
|
729
|
+
unhealthy_applications: number;
|
|
730
|
+
unhealthy_databases: number;
|
|
731
|
+
unhealthy_services: number;
|
|
732
|
+
unreachable_servers: number;
|
|
733
|
+
};
|
|
734
|
+
issues: InfrastructureIssue[];
|
|
735
|
+
errors?: string[];
|
|
736
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@masonator/coolify-mcp",
|
|
3
3
|
"scope": "@masonator",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.8.1",
|
|
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",
|