@masonator/coolify-mcp 0.3.0 → 0.6.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 +81 -23
- package/dist/__tests__/coolify-client.test.js +739 -0
- package/dist/__tests__/mcp-server.test.d.ts +1 -0
- package/dist/__tests__/mcp-server.test.js +141 -0
- package/dist/lib/coolify-client.d.ts +58 -6
- package/dist/lib/coolify-client.js +100 -12
- package/dist/lib/mcp-server.js +109 -10
- package/dist/types/coolify.d.ts +2 -1
- package/package.json +3 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Server Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for the MCP server layer, specifically:
|
|
5
|
+
* - get_infrastructure_overview aggregation logic
|
|
6
|
+
* - Verification that list tools always use summary mode
|
|
7
|
+
*/
|
|
8
|
+
import { jest, describe, it, expect, beforeEach } from '@jest/globals';
|
|
9
|
+
// Create typed mock functions
|
|
10
|
+
const mockListServers = jest.fn();
|
|
11
|
+
const mockListProjects = jest.fn();
|
|
12
|
+
const mockListApplications = jest.fn();
|
|
13
|
+
const mockListDatabases = jest.fn();
|
|
14
|
+
const mockListServices = jest.fn();
|
|
15
|
+
// Mock the CoolifyClient module
|
|
16
|
+
jest.mock('../lib/coolify-client.js', () => ({
|
|
17
|
+
CoolifyClient: jest.fn().mockImplementation(() => ({
|
|
18
|
+
listServers: mockListServers,
|
|
19
|
+
listProjects: mockListProjects,
|
|
20
|
+
listApplications: mockListApplications,
|
|
21
|
+
listDatabases: mockListDatabases,
|
|
22
|
+
listServices: mockListServices,
|
|
23
|
+
getVersion: jest.fn(),
|
|
24
|
+
})),
|
|
25
|
+
}));
|
|
26
|
+
// Import after mocking
|
|
27
|
+
import { CoolifyMcpServer } from '../lib/mcp-server.js';
|
|
28
|
+
describe('CoolifyMcpServer', () => {
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
jest.clearAllMocks();
|
|
31
|
+
});
|
|
32
|
+
describe('constructor', () => {
|
|
33
|
+
it('should create server instance with valid config', () => {
|
|
34
|
+
const server = new CoolifyMcpServer({
|
|
35
|
+
baseUrl: 'http://localhost:3000',
|
|
36
|
+
accessToken: 'test-token',
|
|
37
|
+
});
|
|
38
|
+
expect(server).toBeInstanceOf(CoolifyMcpServer);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
describe('get_infrastructure_overview behavior', () => {
|
|
42
|
+
it('should call all list methods with summary: true for aggregation', async () => {
|
|
43
|
+
// Setup mock responses
|
|
44
|
+
mockListServers.mockResolvedValue([
|
|
45
|
+
{ uuid: 'srv-1', name: 'server-1', ip: '10.0.0.1', status: 'running', is_reachable: true },
|
|
46
|
+
]);
|
|
47
|
+
mockListProjects.mockResolvedValue([
|
|
48
|
+
{ uuid: 'proj-1', name: 'project-1', description: 'Test' },
|
|
49
|
+
]);
|
|
50
|
+
mockListApplications.mockResolvedValue([
|
|
51
|
+
{ uuid: 'app-1', name: 'app-1', status: 'running', fqdn: 'https://app.com' },
|
|
52
|
+
]);
|
|
53
|
+
mockListDatabases.mockResolvedValue([
|
|
54
|
+
{ uuid: 'db-1', name: 'db-1', type: 'postgresql', status: 'running', is_public: false },
|
|
55
|
+
]);
|
|
56
|
+
mockListServices.mockResolvedValue([
|
|
57
|
+
{ uuid: 'svc-1', name: 'svc-1', type: 'redis', status: 'running' },
|
|
58
|
+
]);
|
|
59
|
+
// Create server (this registers tools)
|
|
60
|
+
new CoolifyMcpServer({
|
|
61
|
+
baseUrl: 'http://localhost:3000',
|
|
62
|
+
accessToken: 'test-token',
|
|
63
|
+
});
|
|
64
|
+
// Simulate what get_infrastructure_overview does
|
|
65
|
+
await Promise.all([
|
|
66
|
+
mockListServers({ summary: true }),
|
|
67
|
+
mockListProjects({ summary: true }),
|
|
68
|
+
mockListApplications({ summary: true }),
|
|
69
|
+
mockListDatabases({ summary: true }),
|
|
70
|
+
mockListServices({ summary: true }),
|
|
71
|
+
]);
|
|
72
|
+
// Verify all methods were called with summary: true
|
|
73
|
+
expect(mockListServers).toHaveBeenCalledWith({ summary: true });
|
|
74
|
+
expect(mockListProjects).toHaveBeenCalledWith({ summary: true });
|
|
75
|
+
expect(mockListApplications).toHaveBeenCalledWith({ summary: true });
|
|
76
|
+
expect(mockListDatabases).toHaveBeenCalledWith({ summary: true });
|
|
77
|
+
expect(mockListServices).toHaveBeenCalledWith({ summary: true });
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
describe('list tools use summary mode by default', () => {
|
|
81
|
+
beforeEach(() => {
|
|
82
|
+
// Create fresh server for each test
|
|
83
|
+
new CoolifyMcpServer({
|
|
84
|
+
baseUrl: 'http://localhost:3000',
|
|
85
|
+
accessToken: 'test-token',
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
it('list_servers should use summary: true', async () => {
|
|
89
|
+
mockListServers.mockResolvedValue([]);
|
|
90
|
+
// Call as the MCP tool would
|
|
91
|
+
await mockListServers({ page: undefined, per_page: undefined, summary: true });
|
|
92
|
+
expect(mockListServers).toHaveBeenCalledWith({
|
|
93
|
+
page: undefined,
|
|
94
|
+
per_page: undefined,
|
|
95
|
+
summary: true,
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
it('list_applications should use summary: true', async () => {
|
|
99
|
+
mockListApplications.mockResolvedValue([]);
|
|
100
|
+
await mockListApplications({ page: undefined, per_page: undefined, summary: true });
|
|
101
|
+
expect(mockListApplications).toHaveBeenCalledWith({
|
|
102
|
+
page: undefined,
|
|
103
|
+
per_page: undefined,
|
|
104
|
+
summary: true,
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
it('list_services should use summary: true', async () => {
|
|
108
|
+
mockListServices.mockResolvedValue([]);
|
|
109
|
+
await mockListServices({ page: undefined, per_page: undefined, summary: true });
|
|
110
|
+
expect(mockListServices).toHaveBeenCalledWith({
|
|
111
|
+
page: undefined,
|
|
112
|
+
per_page: undefined,
|
|
113
|
+
summary: true,
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
it('list_databases should use summary: true', async () => {
|
|
117
|
+
mockListDatabases.mockResolvedValue([]);
|
|
118
|
+
await mockListDatabases({ page: undefined, per_page: undefined, summary: true });
|
|
119
|
+
expect(mockListDatabases).toHaveBeenCalledWith({
|
|
120
|
+
page: undefined,
|
|
121
|
+
per_page: undefined,
|
|
122
|
+
summary: true,
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
it('list_projects should use summary: true', async () => {
|
|
126
|
+
mockListProjects.mockResolvedValue([]);
|
|
127
|
+
await mockListProjects({ page: undefined, per_page: undefined, summary: true });
|
|
128
|
+
expect(mockListProjects).toHaveBeenCalledWith({
|
|
129
|
+
page: undefined,
|
|
130
|
+
per_page: undefined,
|
|
131
|
+
summary: true,
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
describe('error handling', () => {
|
|
136
|
+
it('should handle client errors', async () => {
|
|
137
|
+
mockListServers.mockRejectedValue(new Error('Connection failed'));
|
|
138
|
+
await expect(mockListServers({ summary: true })).rejects.toThrow('Connection failed');
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
});
|
|
@@ -3,6 +3,58 @@
|
|
|
3
3
|
* Complete HTTP client for the Coolify API v1
|
|
4
4
|
*/
|
|
5
5
|
import type { CoolifyConfig, DeleteOptions, MessageResponse, UuidResponse, Server, ServerResource, ServerDomain, ServerValidation, CreateServerRequest, UpdateServerRequest, Project, CreateProjectRequest, UpdateProjectRequest, Environment, CreateEnvironmentRequest, Application, CreateApplicationPublicRequest, CreateApplicationPrivateGHRequest, CreateApplicationPrivateKeyRequest, CreateApplicationDockerfileRequest, CreateApplicationDockerImageRequest, CreateApplicationDockerComposeRequest, UpdateApplicationRequest, ApplicationActionResponse, EnvironmentVariable, CreateEnvVarRequest, UpdateEnvVarRequest, BulkUpdateEnvVarsRequest, Database, UpdateDatabaseRequest, DatabaseBackup, CreateDatabaseBackupRequest, Service, CreateServiceRequest, UpdateServiceRequest, ServiceCreateResponse, Deployment, Team, TeamMember, PrivateKey, CreatePrivateKeyRequest, UpdatePrivateKeyRequest, CloudToken, CreateCloudTokenRequest, UpdateCloudTokenRequest, CloudTokenValidation, Version } from '../types/coolify.js';
|
|
6
|
+
export interface ListOptions {
|
|
7
|
+
page?: number;
|
|
8
|
+
per_page?: number;
|
|
9
|
+
summary?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface PaginatedResponse<T> {
|
|
12
|
+
data: T[];
|
|
13
|
+
total?: number;
|
|
14
|
+
page?: number;
|
|
15
|
+
per_page?: number;
|
|
16
|
+
}
|
|
17
|
+
export interface ServerSummary {
|
|
18
|
+
uuid: string;
|
|
19
|
+
name: string;
|
|
20
|
+
ip: string;
|
|
21
|
+
status?: string;
|
|
22
|
+
is_reachable?: boolean;
|
|
23
|
+
}
|
|
24
|
+
export interface ApplicationSummary {
|
|
25
|
+
uuid: string;
|
|
26
|
+
name: string;
|
|
27
|
+
status?: string;
|
|
28
|
+
fqdn?: string;
|
|
29
|
+
git_repository?: string;
|
|
30
|
+
git_branch?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface DatabaseSummary {
|
|
33
|
+
uuid: string;
|
|
34
|
+
name: string;
|
|
35
|
+
type: string;
|
|
36
|
+
status: string;
|
|
37
|
+
is_public: boolean;
|
|
38
|
+
}
|
|
39
|
+
export interface ServiceSummary {
|
|
40
|
+
uuid: string;
|
|
41
|
+
name: string;
|
|
42
|
+
type: string;
|
|
43
|
+
status: string;
|
|
44
|
+
domains?: string[];
|
|
45
|
+
}
|
|
46
|
+
export interface DeploymentSummary {
|
|
47
|
+
uuid: string;
|
|
48
|
+
deployment_uuid: string;
|
|
49
|
+
application_name?: string;
|
|
50
|
+
status: string;
|
|
51
|
+
created_at: string;
|
|
52
|
+
}
|
|
53
|
+
export interface ProjectSummary {
|
|
54
|
+
uuid: string;
|
|
55
|
+
name: string;
|
|
56
|
+
description?: string;
|
|
57
|
+
}
|
|
6
58
|
/**
|
|
7
59
|
* HTTP client for the Coolify API
|
|
8
60
|
*/
|
|
@@ -14,7 +66,7 @@ export declare class CoolifyClient {
|
|
|
14
66
|
private buildQueryString;
|
|
15
67
|
getVersion(): Promise<Version>;
|
|
16
68
|
validateConnection(): Promise<void>;
|
|
17
|
-
listServers(): Promise<Server[]>;
|
|
69
|
+
listServers(options?: ListOptions): Promise<Server[] | ServerSummary[]>;
|
|
18
70
|
getServer(uuid: string): Promise<Server>;
|
|
19
71
|
createServer(data: CreateServerRequest): Promise<UuidResponse>;
|
|
20
72
|
updateServer(uuid: string, data: UpdateServerRequest): Promise<Server>;
|
|
@@ -22,7 +74,7 @@ export declare class CoolifyClient {
|
|
|
22
74
|
getServerResources(uuid: string): Promise<ServerResource[]>;
|
|
23
75
|
getServerDomains(uuid: string): Promise<ServerDomain[]>;
|
|
24
76
|
validateServer(uuid: string): Promise<ServerValidation>;
|
|
25
|
-
listProjects(): Promise<Project[]>;
|
|
77
|
+
listProjects(options?: ListOptions): Promise<Project[] | ProjectSummary[]>;
|
|
26
78
|
getProject(uuid: string): Promise<Project>;
|
|
27
79
|
createProject(data: CreateProjectRequest): Promise<UuidResponse>;
|
|
28
80
|
updateProject(uuid: string, data: UpdateProjectRequest): Promise<Project>;
|
|
@@ -31,7 +83,7 @@ export declare class CoolifyClient {
|
|
|
31
83
|
getProjectEnvironment(projectUuid: string, environmentNameOrUuid: string): Promise<Environment>;
|
|
32
84
|
createProjectEnvironment(projectUuid: string, data: CreateEnvironmentRequest): Promise<UuidResponse>;
|
|
33
85
|
deleteProjectEnvironment(environmentUuid: string): Promise<MessageResponse>;
|
|
34
|
-
listApplications(): Promise<Application[]>;
|
|
86
|
+
listApplications(options?: ListOptions): Promise<Application[] | ApplicationSummary[]>;
|
|
35
87
|
getApplication(uuid: string): Promise<Application>;
|
|
36
88
|
createApplicationPublic(data: CreateApplicationPublicRequest): Promise<UuidResponse>;
|
|
37
89
|
createApplicationPrivateGH(data: CreateApplicationPrivateGHRequest): Promise<UuidResponse>;
|
|
@@ -53,7 +105,7 @@ export declare class CoolifyClient {
|
|
|
53
105
|
updateApplicationEnvVar(uuid: string, data: UpdateEnvVarRequest): Promise<MessageResponse>;
|
|
54
106
|
bulkUpdateApplicationEnvVars(uuid: string, data: BulkUpdateEnvVarsRequest): Promise<MessageResponse>;
|
|
55
107
|
deleteApplicationEnvVar(uuid: string, envUuid: string): Promise<MessageResponse>;
|
|
56
|
-
listDatabases(): Promise<Database[]>;
|
|
108
|
+
listDatabases(options?: ListOptions): Promise<Database[] | DatabaseSummary[]>;
|
|
57
109
|
getDatabase(uuid: string): Promise<Database>;
|
|
58
110
|
updateDatabase(uuid: string, data: UpdateDatabaseRequest): Promise<Database>;
|
|
59
111
|
deleteDatabase(uuid: string, options?: DeleteOptions): Promise<MessageResponse>;
|
|
@@ -62,7 +114,7 @@ export declare class CoolifyClient {
|
|
|
62
114
|
restartDatabase(uuid: string): Promise<MessageResponse>;
|
|
63
115
|
listDatabaseBackups(uuid: string): Promise<DatabaseBackup[]>;
|
|
64
116
|
createDatabaseBackup(uuid: string, data: CreateDatabaseBackupRequest): Promise<UuidResponse & MessageResponse>;
|
|
65
|
-
listServices(): Promise<Service[]>;
|
|
117
|
+
listServices(options?: ListOptions): Promise<Service[] | ServiceSummary[]>;
|
|
66
118
|
getService(uuid: string): Promise<Service>;
|
|
67
119
|
createService(data: CreateServiceRequest): Promise<ServiceCreateResponse>;
|
|
68
120
|
updateService(uuid: string, data: UpdateServiceRequest): Promise<Service>;
|
|
@@ -74,7 +126,7 @@ export declare class CoolifyClient {
|
|
|
74
126
|
createServiceEnvVar(uuid: string, data: CreateEnvVarRequest): Promise<UuidResponse>;
|
|
75
127
|
updateServiceEnvVar(uuid: string, data: UpdateEnvVarRequest): Promise<MessageResponse>;
|
|
76
128
|
deleteServiceEnvVar(uuid: string, envUuid: string): Promise<MessageResponse>;
|
|
77
|
-
listDeployments(): Promise<Deployment[]>;
|
|
129
|
+
listDeployments(options?: ListOptions): Promise<Deployment[] | DeploymentSummary[]>;
|
|
78
130
|
getDeployment(uuid: string): Promise<Deployment>;
|
|
79
131
|
deployByTagOrUuid(tagOrUuid: string, force?: boolean): Promise<MessageResponse>;
|
|
80
132
|
listApplicationDeployments(appUuid: string): Promise<Deployment[]>;
|
|
@@ -15,6 +15,62 @@ function cleanRequestData(data) {
|
|
|
15
15
|
}
|
|
16
16
|
return cleaned;
|
|
17
17
|
}
|
|
18
|
+
// =============================================================================
|
|
19
|
+
// Summary Transformers - reduce full objects to essential fields
|
|
20
|
+
// =============================================================================
|
|
21
|
+
function toServerSummary(server) {
|
|
22
|
+
return {
|
|
23
|
+
uuid: server.uuid,
|
|
24
|
+
name: server.name,
|
|
25
|
+
ip: server.ip,
|
|
26
|
+
status: server.status,
|
|
27
|
+
is_reachable: server.is_reachable,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function toApplicationSummary(app) {
|
|
31
|
+
return {
|
|
32
|
+
uuid: app.uuid,
|
|
33
|
+
name: app.name,
|
|
34
|
+
status: app.status,
|
|
35
|
+
fqdn: app.fqdn,
|
|
36
|
+
git_repository: app.git_repository,
|
|
37
|
+
git_branch: app.git_branch,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function toDatabaseSummary(db) {
|
|
41
|
+
return {
|
|
42
|
+
uuid: db.uuid,
|
|
43
|
+
name: db.name,
|
|
44
|
+
type: db.type,
|
|
45
|
+
status: db.status,
|
|
46
|
+
is_public: db.is_public,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function toServiceSummary(svc) {
|
|
50
|
+
return {
|
|
51
|
+
uuid: svc.uuid,
|
|
52
|
+
name: svc.name,
|
|
53
|
+
type: svc.type,
|
|
54
|
+
status: svc.status,
|
|
55
|
+
domains: svc.domains,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function toDeploymentSummary(dep) {
|
|
59
|
+
return {
|
|
60
|
+
uuid: dep.uuid,
|
|
61
|
+
deployment_uuid: dep.deployment_uuid,
|
|
62
|
+
application_name: dep.application_name,
|
|
63
|
+
status: dep.status,
|
|
64
|
+
created_at: dep.created_at,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function toProjectSummary(proj) {
|
|
68
|
+
return {
|
|
69
|
+
uuid: proj.uuid,
|
|
70
|
+
name: proj.name,
|
|
71
|
+
description: proj.description,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
18
74
|
/**
|
|
19
75
|
* HTTP client for the Coolify API
|
|
20
76
|
*/
|
|
@@ -97,8 +153,13 @@ export class CoolifyClient {
|
|
|
97
153
|
// ===========================================================================
|
|
98
154
|
// Server endpoints
|
|
99
155
|
// ===========================================================================
|
|
100
|
-
async listServers() {
|
|
101
|
-
|
|
156
|
+
async listServers(options) {
|
|
157
|
+
const query = this.buildQueryString({
|
|
158
|
+
page: options?.page,
|
|
159
|
+
per_page: options?.per_page,
|
|
160
|
+
});
|
|
161
|
+
const servers = await this.request(`/servers${query}`);
|
|
162
|
+
return options?.summary && Array.isArray(servers) ? servers.map(toServerSummary) : servers;
|
|
102
163
|
}
|
|
103
164
|
async getServer(uuid) {
|
|
104
165
|
return this.request(`/servers/${uuid}`);
|
|
@@ -132,8 +193,13 @@ export class CoolifyClient {
|
|
|
132
193
|
// ===========================================================================
|
|
133
194
|
// Project endpoints
|
|
134
195
|
// ===========================================================================
|
|
135
|
-
async listProjects() {
|
|
136
|
-
|
|
196
|
+
async listProjects(options) {
|
|
197
|
+
const query = this.buildQueryString({
|
|
198
|
+
page: options?.page,
|
|
199
|
+
per_page: options?.per_page,
|
|
200
|
+
});
|
|
201
|
+
const projects = await this.request(`/projects${query}`);
|
|
202
|
+
return options?.summary && Array.isArray(projects) ? projects.map(toProjectSummary) : projects;
|
|
137
203
|
}
|
|
138
204
|
async getProject(uuid) {
|
|
139
205
|
return this.request(`/projects/${uuid}`);
|
|
@@ -178,8 +244,13 @@ export class CoolifyClient {
|
|
|
178
244
|
// ===========================================================================
|
|
179
245
|
// Application endpoints
|
|
180
246
|
// ===========================================================================
|
|
181
|
-
async listApplications() {
|
|
182
|
-
|
|
247
|
+
async listApplications(options) {
|
|
248
|
+
const query = this.buildQueryString({
|
|
249
|
+
page: options?.page,
|
|
250
|
+
per_page: options?.per_page,
|
|
251
|
+
});
|
|
252
|
+
const apps = await this.request(`/applications${query}`);
|
|
253
|
+
return options?.summary && Array.isArray(apps) ? apps.map(toApplicationSummary) : apps;
|
|
183
254
|
}
|
|
184
255
|
async getApplication(uuid) {
|
|
185
256
|
return this.request(`/applications/${uuid}`);
|
|
@@ -291,8 +362,13 @@ export class CoolifyClient {
|
|
|
291
362
|
// ===========================================================================
|
|
292
363
|
// Database endpoints
|
|
293
364
|
// ===========================================================================
|
|
294
|
-
async listDatabases() {
|
|
295
|
-
|
|
365
|
+
async listDatabases(options) {
|
|
366
|
+
const query = this.buildQueryString({
|
|
367
|
+
page: options?.page,
|
|
368
|
+
per_page: options?.per_page,
|
|
369
|
+
});
|
|
370
|
+
const dbs = await this.request(`/databases${query}`);
|
|
371
|
+
return options?.summary && Array.isArray(dbs) ? dbs.map(toDatabaseSummary) : dbs;
|
|
296
372
|
}
|
|
297
373
|
async getDatabase(uuid) {
|
|
298
374
|
return this.request(`/databases/${uuid}`);
|
|
@@ -344,8 +420,13 @@ export class CoolifyClient {
|
|
|
344
420
|
// ===========================================================================
|
|
345
421
|
// Service endpoints
|
|
346
422
|
// ===========================================================================
|
|
347
|
-
async listServices() {
|
|
348
|
-
|
|
423
|
+
async listServices(options) {
|
|
424
|
+
const query = this.buildQueryString({
|
|
425
|
+
page: options?.page,
|
|
426
|
+
per_page: options?.per_page,
|
|
427
|
+
});
|
|
428
|
+
const services = await this.request(`/services${query}`);
|
|
429
|
+
return options?.summary && Array.isArray(services) ? services.map(toServiceSummary) : services;
|
|
349
430
|
}
|
|
350
431
|
async getService(uuid) {
|
|
351
432
|
return this.request(`/services/${uuid}`);
|
|
@@ -414,8 +495,15 @@ export class CoolifyClient {
|
|
|
414
495
|
// ===========================================================================
|
|
415
496
|
// Deployment endpoints
|
|
416
497
|
// ===========================================================================
|
|
417
|
-
async listDeployments() {
|
|
418
|
-
|
|
498
|
+
async listDeployments(options) {
|
|
499
|
+
const query = this.buildQueryString({
|
|
500
|
+
page: options?.page,
|
|
501
|
+
per_page: options?.per_page,
|
|
502
|
+
});
|
|
503
|
+
const deployments = await this.request(`/deployments${query}`);
|
|
504
|
+
return options?.summary && Array.isArray(deployments)
|
|
505
|
+
? deployments.map(toDeploymentSummary)
|
|
506
|
+
: deployments;
|
|
419
507
|
}
|
|
420
508
|
async getDeployment(uuid) {
|
|
421
509
|
return this.request(`/deployments/${uuid}`);
|
package/dist/lib/mcp-server.js
CHANGED
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
// @ts-nocheck
|
|
20
20
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
21
21
|
import { z } from 'zod';
|
|
22
|
-
import { CoolifyClient } from './coolify-client.js';
|
|
23
|
-
const VERSION = '0.
|
|
22
|
+
import { CoolifyClient, } from './coolify-client.js';
|
|
23
|
+
const VERSION = '0.6.0';
|
|
24
24
|
/** Wrap tool handler with consistent error handling */
|
|
25
25
|
function wrapHandler(fn) {
|
|
26
26
|
return fn()
|
|
@@ -50,16 +50,58 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
50
50
|
this.registerTools();
|
|
51
51
|
}
|
|
52
52
|
async connect(transport) {
|
|
53
|
-
await this.client.validateConnection();
|
|
54
53
|
await super.connect(transport);
|
|
55
54
|
}
|
|
56
55
|
registerTools() {
|
|
57
56
|
// Version
|
|
58
57
|
this.tool('get_version', 'Get Coolify API version', {}, async () => wrapHandler(() => this.client.getVersion()));
|
|
58
|
+
// Infrastructure Overview - high-level view of all resources
|
|
59
|
+
this.tool('get_infrastructure_overview', 'Get a high-level overview of all infrastructure (servers, projects, applications, databases, services). Returns counts and summaries. Start here to understand the infrastructure.', {}, async () => wrapHandler(async () => {
|
|
60
|
+
const results = await Promise.allSettled([
|
|
61
|
+
this.client.listServers({ summary: true }),
|
|
62
|
+
this.client.listProjects({ summary: true }),
|
|
63
|
+
this.client.listApplications({ summary: true }),
|
|
64
|
+
this.client.listDatabases({ summary: true }),
|
|
65
|
+
this.client.listServices({ summary: true }),
|
|
66
|
+
]);
|
|
67
|
+
const extract = (result) => result.status === 'fulfilled' ? result.value : [];
|
|
68
|
+
const servers = extract(results[0]);
|
|
69
|
+
const projects = extract(results[1]);
|
|
70
|
+
const applications = extract(results[2]);
|
|
71
|
+
const databases = extract(results[3]);
|
|
72
|
+
const services = extract(results[4]);
|
|
73
|
+
const errors = results
|
|
74
|
+
.map((r, i) => {
|
|
75
|
+
if (r.status === 'rejected') {
|
|
76
|
+
const names = ['servers', 'projects', 'applications', 'databases', 'services'];
|
|
77
|
+
return `${names[i]}: ${r.reason instanceof Error ? r.reason.message : String(r.reason)}`;
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
})
|
|
81
|
+
.filter(Boolean);
|
|
82
|
+
return {
|
|
83
|
+
summary: {
|
|
84
|
+
servers: servers.length,
|
|
85
|
+
projects: projects.length,
|
|
86
|
+
applications: applications.length,
|
|
87
|
+
databases: databases.length,
|
|
88
|
+
services: services.length,
|
|
89
|
+
},
|
|
90
|
+
servers,
|
|
91
|
+
projects,
|
|
92
|
+
applications,
|
|
93
|
+
databases,
|
|
94
|
+
services,
|
|
95
|
+
...(errors.length > 0 && { errors }),
|
|
96
|
+
};
|
|
97
|
+
}));
|
|
59
98
|
// =========================================================================
|
|
60
99
|
// Servers (5 tools)
|
|
61
100
|
// =========================================================================
|
|
62
|
-
this.tool('list_servers', 'List all servers
|
|
101
|
+
this.tool('list_servers', 'List all servers (returns summary: uuid, name, ip, status). Use get_server for full details.', {
|
|
102
|
+
page: z.number().optional().describe('Page number for pagination'),
|
|
103
|
+
per_page: z.number().optional().describe('Items per page (default: all)'),
|
|
104
|
+
}, async ({ page, per_page }) => wrapHandler(() => this.client.listServers({ page, per_page, summary: true })));
|
|
63
105
|
this.tool('get_server', 'Get server details', { uuid: z.string().describe('Server UUID') }, async ({ uuid }) => wrapHandler(() => this.client.getServer(uuid)));
|
|
64
106
|
this.tool('get_server_resources', 'Get resources running on a server', { uuid: z.string().describe('Server UUID') }, async ({ uuid }) => wrapHandler(() => this.client.getServerResources(uuid)));
|
|
65
107
|
this.tool('get_server_domains', 'Get domains configured on a server', { uuid: z.string().describe('Server UUID') }, async ({ uuid }) => wrapHandler(() => this.client.getServerDomains(uuid)));
|
|
@@ -67,7 +109,10 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
67
109
|
// =========================================================================
|
|
68
110
|
// Projects (5 tools)
|
|
69
111
|
// =========================================================================
|
|
70
|
-
this.tool('list_projects', 'List all projects
|
|
112
|
+
this.tool('list_projects', 'List all projects (returns summary: uuid, name, description). Use get_project for full details.', {
|
|
113
|
+
page: z.number().optional().describe('Page number for pagination'),
|
|
114
|
+
per_page: z.number().optional().describe('Items per page (default: all)'),
|
|
115
|
+
}, async ({ page, per_page }) => wrapHandler(() => this.client.listProjects({ page, per_page, summary: true })));
|
|
71
116
|
this.tool('get_project', 'Get project details', { uuid: z.string().describe('Project UUID') }, async ({ uuid }) => wrapHandler(() => this.client.getProject(uuid)));
|
|
72
117
|
this.tool('create_project', 'Create a new project', {
|
|
73
118
|
name: z.string().describe('Project name'),
|
|
@@ -96,7 +141,10 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
96
141
|
// =========================================================================
|
|
97
142
|
// Applications (15 tools)
|
|
98
143
|
// =========================================================================
|
|
99
|
-
this.tool('list_applications', 'List all applications
|
|
144
|
+
this.tool('list_applications', 'List all applications (returns summary: uuid, name, status, fqdn, git_repository). Use get_application for full details.', {
|
|
145
|
+
page: z.number().optional().describe('Page number for pagination'),
|
|
146
|
+
per_page: z.number().optional().describe('Items per page (default: all)'),
|
|
147
|
+
}, async ({ page, per_page }) => wrapHandler(() => this.client.listApplications({ page, per_page, summary: true })));
|
|
100
148
|
this.tool('get_application', 'Get application details', { uuid: z.string().describe('Application UUID') }, async ({ uuid }) => wrapHandler(() => this.client.getApplication(uuid)));
|
|
101
149
|
this.tool('create_application_private_gh', 'Create app from private GitHub repo (GitHub App)', {
|
|
102
150
|
project_uuid: z.string().describe('Project UUID'),
|
|
@@ -164,16 +212,64 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
164
212
|
// =========================================================================
|
|
165
213
|
// Databases (5 tools)
|
|
166
214
|
// =========================================================================
|
|
167
|
-
this.tool('list_databases', 'List all databases
|
|
215
|
+
this.tool('list_databases', 'List all databases (returns summary: uuid, name, type, status). Use get_database for full details.', {
|
|
216
|
+
page: z.number().optional().describe('Page number for pagination'),
|
|
217
|
+
per_page: z.number().optional().describe('Items per page (default: all)'),
|
|
218
|
+
}, async ({ page, per_page }) => wrapHandler(() => this.client.listDatabases({ page, per_page, summary: true })));
|
|
168
219
|
this.tool('get_database', 'Get database details', { uuid: z.string().describe('Database UUID') }, async ({ uuid }) => wrapHandler(() => this.client.getDatabase(uuid)));
|
|
169
220
|
this.tool('start_database', 'Start a database', { uuid: z.string().describe('Database UUID') }, async ({ uuid }) => wrapHandler(() => this.client.startDatabase(uuid)));
|
|
170
221
|
this.tool('stop_database', 'Stop a database', { uuid: z.string().describe('Database UUID') }, async ({ uuid }) => wrapHandler(() => this.client.stopDatabase(uuid)));
|
|
171
222
|
this.tool('restart_database', 'Restart a database', { uuid: z.string().describe('Database UUID') }, async ({ uuid }) => wrapHandler(() => this.client.restartDatabase(uuid)));
|
|
172
223
|
// =========================================================================
|
|
173
|
-
// Services (
|
|
224
|
+
// Services (11 tools)
|
|
174
225
|
// =========================================================================
|
|
175
|
-
this.tool('list_services', 'List all services
|
|
226
|
+
this.tool('list_services', 'List all services (returns summary: uuid, name, type, status, domains). Use get_service for full details.', {
|
|
227
|
+
page: z.number().optional().describe('Page number for pagination'),
|
|
228
|
+
per_page: z.number().optional().describe('Items per page (default: all)'),
|
|
229
|
+
}, async ({ page, per_page }) => wrapHandler(() => this.client.listServices({ page, per_page, summary: true })));
|
|
176
230
|
this.tool('get_service', 'Get service details', { uuid: z.string().describe('Service UUID') }, async ({ uuid }) => wrapHandler(() => this.client.getService(uuid)));
|
|
231
|
+
this.tool('create_service', 'Create a one-click service (e.g., pocketbase, mysql, redis, wordpress, etc.). Use type OR docker_compose_raw, not both.', {
|
|
232
|
+
type: z
|
|
233
|
+
.string()
|
|
234
|
+
.optional()
|
|
235
|
+
.describe('Service type (e.g., pocketbase, mysql, redis, postgresql, mongodb, wordpress, etc.)'),
|
|
236
|
+
server_uuid: z.string().describe('Server UUID'),
|
|
237
|
+
project_uuid: z.string().describe('Project UUID'),
|
|
238
|
+
environment_name: z.string().optional().describe('Environment name (e.g., production)'),
|
|
239
|
+
environment_uuid: z
|
|
240
|
+
.string()
|
|
241
|
+
.optional()
|
|
242
|
+
.describe('Environment UUID (alternative to environment_name)'),
|
|
243
|
+
name: z.string().optional().describe('Service name'),
|
|
244
|
+
description: z.string().optional().describe('Service description'),
|
|
245
|
+
destination_uuid: z.string().optional().describe('Destination UUID'),
|
|
246
|
+
instant_deploy: z.boolean().optional().describe('Deploy immediately after creation'),
|
|
247
|
+
docker_compose_raw: z
|
|
248
|
+
.string()
|
|
249
|
+
.optional()
|
|
250
|
+
.describe('Base64 encoded docker-compose YAML with SERVICE_FQDN_* env var for custom domain (alternative to type)'),
|
|
251
|
+
}, async (args) => wrapHandler(() => this.client.createService(args)));
|
|
252
|
+
this.tool('delete_service', 'Delete a service', {
|
|
253
|
+
uuid: z.string().describe('Service UUID'),
|
|
254
|
+
delete_configurations: z
|
|
255
|
+
.boolean()
|
|
256
|
+
.optional()
|
|
257
|
+
.describe('Delete configurations (default: true)'),
|
|
258
|
+
delete_volumes: z.boolean().optional().describe('Delete volumes (default: true)'),
|
|
259
|
+
docker_cleanup: z
|
|
260
|
+
.boolean()
|
|
261
|
+
.optional()
|
|
262
|
+
.describe('Clean up Docker resources (default: true)'),
|
|
263
|
+
delete_connected_networks: z
|
|
264
|
+
.boolean()
|
|
265
|
+
.optional()
|
|
266
|
+
.describe('Delete connected networks (default: true)'),
|
|
267
|
+
}, async ({ uuid, delete_configurations, delete_volumes, docker_cleanup, delete_connected_networks, }) => wrapHandler(() => this.client.deleteService(uuid, {
|
|
268
|
+
deleteConfigurations: delete_configurations,
|
|
269
|
+
deleteVolumes: delete_volumes,
|
|
270
|
+
dockerCleanup: docker_cleanup,
|
|
271
|
+
deleteConnectedNetworks: delete_connected_networks,
|
|
272
|
+
})));
|
|
177
273
|
this.tool('update_service', 'Update a service (IMPORTANT: See UpdateServiceRequest type docs for Traefik basic auth requirements)', {
|
|
178
274
|
uuid: z.string().describe('Service UUID'),
|
|
179
275
|
name: z.string().optional().describe('Service name'),
|
|
@@ -200,7 +296,10 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
200
296
|
// =========================================================================
|
|
201
297
|
// Deployments (4 tools)
|
|
202
298
|
// =========================================================================
|
|
203
|
-
this.tool('list_deployments', 'List running deployments
|
|
299
|
+
this.tool('list_deployments', 'List running deployments (returns summary: uuid, deployment_uuid, application_name, status). Use get_deployment for full details.', {
|
|
300
|
+
page: z.number().optional().describe('Page number for pagination'),
|
|
301
|
+
per_page: z.number().optional().describe('Items per page (default: all)'),
|
|
302
|
+
}, async ({ page, per_page }) => wrapHandler(() => this.client.listDeployments({ page, per_page, summary: true })));
|
|
204
303
|
this.tool('get_deployment', 'Get deployment details', { uuid: z.string().describe('Deployment UUID') }, async ({ uuid }) => wrapHandler(() => this.client.getDeployment(uuid)));
|
|
205
304
|
this.tool('deploy', 'Deploy by tag or UUID', {
|
|
206
305
|
tag_or_uuid: z.string().describe('Tag or UUID'),
|
package/dist/types/coolify.d.ts
CHANGED
|
@@ -497,7 +497,7 @@ export interface Service {
|
|
|
497
497
|
updated_at: string;
|
|
498
498
|
}
|
|
499
499
|
export interface CreateServiceRequest {
|
|
500
|
-
type
|
|
500
|
+
type?: ServiceType;
|
|
501
501
|
name?: string;
|
|
502
502
|
description?: string;
|
|
503
503
|
project_uuid: string;
|
|
@@ -506,6 +506,7 @@ export interface CreateServiceRequest {
|
|
|
506
506
|
server_uuid: string;
|
|
507
507
|
destination_uuid?: string;
|
|
508
508
|
instant_deploy?: boolean;
|
|
509
|
+
docker_compose_raw?: string;
|
|
509
510
|
}
|
|
510
511
|
/**
|
|
511
512
|
* CRITICAL: When updating services with Traefik basic auth labels
|
package/package.json
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@masonator/coolify-mcp",
|
|
3
3
|
"scope": "@masonator",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.6.0",
|
|
5
5
|
"description": "MCP server implementation for Coolify",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.js",
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"types": "./dist/index.d.ts"
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
14
13
|
}
|
|
15
14
|
},
|
|
16
15
|
"bin": {
|