@masonator/coolify-mcp 2.2.0 → 2.4.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 +10 -5
- package/dist/__tests__/coolify-client.test.js +142 -0
- package/dist/__tests__/mcp-server.test.js +6 -0
- package/dist/lib/coolify-client.d.ts +13 -1
- package/dist/lib/coolify-client.js +34 -0
- package/dist/lib/mcp-server.d.ts +1 -1
- package/dist/lib/mcp-server.js +124 -13
- package/dist/types/coolify.d.ts +58 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,13 +9,13 @@
|
|
|
9
9
|
[](https://codecov.io/gh/StuMason/coolify-mcp)
|
|
10
10
|
[](https://mseep.ai/app/stumason-coolify-mcp)
|
|
11
11
|
|
|
12
|
-
> **The most comprehensive MCP server for Coolify** -
|
|
12
|
+
> **The most comprehensive MCP server for Coolify** - 35 optimized tools, smart diagnostics, and batch operations for managing your self-hosted PaaS through AI assistants.
|
|
13
13
|
|
|
14
14
|
A Model Context Protocol (MCP) server for [Coolify](https://coolify.io/), enabling AI assistants to manage and debug your Coolify instances through natural language.
|
|
15
15
|
|
|
16
16
|
## Features
|
|
17
17
|
|
|
18
|
-
This MCP server provides **
|
|
18
|
+
This MCP server provides **35 token-optimized tools** for **debugging, management, and deployment**:
|
|
19
19
|
|
|
20
20
|
| Category | Tools |
|
|
21
21
|
| -------------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -32,6 +32,7 @@ This MCP server provides **34 token-optimized tools** for **debugging, managemen
|
|
|
32
32
|
| **Env Vars** | `env_vars` (CRUD for application and service env vars) |
|
|
33
33
|
| **Deployments** | `list_deployments`, `deploy`, `deployment` (get, cancel, list_for_app) |
|
|
34
34
|
| **Private Keys** | `private_keys` (list, get, create, update, delete via action param) |
|
|
35
|
+
| **GitHub Apps** | `github_apps` (list, get, create, update, delete via action param) |
|
|
35
36
|
|
|
36
37
|
### Token-Optimized Design
|
|
37
38
|
|
|
@@ -111,7 +112,7 @@ The Coolify API returns extremely verbose responses - a single application can c
|
|
|
111
112
|
1. **Start with overview**: `get_infrastructure_overview` - see everything at once
|
|
112
113
|
2. **Find your target**: `list_applications` - get UUIDs of what you need
|
|
113
114
|
3. **Dive deep**: `get_application(uuid)` - full details for one resource
|
|
114
|
-
4. **Take action**: `
|
|
115
|
+
4. **Take action**: `control(resource: 'application', action: 'restart')`, `application_logs(uuid)`, etc.
|
|
115
116
|
|
|
116
117
|
### Pagination
|
|
117
118
|
|
|
@@ -161,6 +162,8 @@ Update the DATABASE_URL env var for application {uuid}
|
|
|
161
162
|
Create a new project called "my-app"
|
|
162
163
|
Create a staging environment in project {uuid}
|
|
163
164
|
Deploy my app from private GitHub repo org/repo on branch main
|
|
165
|
+
Deploy nginx:latest from Docker Hub
|
|
166
|
+
Deploy from public repo https://github.com/org/repo
|
|
164
167
|
```
|
|
165
168
|
|
|
166
169
|
## Environment Variables
|
|
@@ -227,7 +230,9 @@ These tools accept human-friendly identifiers instead of just UUIDs:
|
|
|
227
230
|
- `list_applications` - List all applications (returns summary)
|
|
228
231
|
- `get_application` - Get application details
|
|
229
232
|
- `application_logs` - Get application logs
|
|
230
|
-
- `application` - Create, update, or delete apps with `action: create_github|create_key|update|delete`
|
|
233
|
+
- `application` - Create, update, or delete apps with `action: create_public|create_github|create_key|create_dockerimage|update|delete`
|
|
234
|
+
- Deploy from public repos, private GitHub, SSH keys, or Docker images
|
|
235
|
+
- Configure health checks (path, interval, retries, etc.)
|
|
231
236
|
- `env_vars` - Manage env vars with `resource: application, action: list|create|update|delete`
|
|
232
237
|
- `control` - Start/stop/restart with `resource: application, action: start|stop|restart`
|
|
233
238
|
|
|
@@ -254,7 +259,7 @@ These tools accept human-friendly identifiers instead of just UUIDs:
|
|
|
254
259
|
|
|
255
260
|
- `list_deployments` - List running deployments (returns summary)
|
|
256
261
|
- `deploy` - Deploy by tag or UUID
|
|
257
|
-
- `deployment` - Manage deployments with `action: get|cancel|list_for_app`
|
|
262
|
+
- `deployment` - Manage deployments with `action: get|cancel|list_for_app` (supports `lines` param to limit log output)
|
|
258
263
|
|
|
259
264
|
### Private Keys
|
|
260
265
|
|
|
@@ -359,6 +359,83 @@ describe('CoolifyClient', () => {
|
|
|
359
359
|
expect(result).toEqual({ uuid: 'new-key-uuid' });
|
|
360
360
|
});
|
|
361
361
|
});
|
|
362
|
+
describe('github apps', () => {
|
|
363
|
+
const mockGitHubApp = {
|
|
364
|
+
id: 1,
|
|
365
|
+
uuid: 'gh-app-uuid',
|
|
366
|
+
name: 'my-github-app',
|
|
367
|
+
organization: null,
|
|
368
|
+
api_url: 'https://api.github.com',
|
|
369
|
+
html_url: 'https://github.com',
|
|
370
|
+
custom_user: 'git',
|
|
371
|
+
custom_port: 22,
|
|
372
|
+
app_id: 12345,
|
|
373
|
+
installation_id: 67890,
|
|
374
|
+
client_id: 'client-123',
|
|
375
|
+
is_system_wide: false,
|
|
376
|
+
is_public: false,
|
|
377
|
+
private_key_id: 1,
|
|
378
|
+
team_id: 0,
|
|
379
|
+
type: 'github',
|
|
380
|
+
administration: null,
|
|
381
|
+
contents: null,
|
|
382
|
+
metadata: null,
|
|
383
|
+
pull_requests: null,
|
|
384
|
+
created_at: '2024-01-01',
|
|
385
|
+
updated_at: '2024-01-01',
|
|
386
|
+
};
|
|
387
|
+
it('should list github apps', async () => {
|
|
388
|
+
mockFetch.mockResolvedValueOnce(mockResponse([mockGitHubApp]));
|
|
389
|
+
const result = await client.listGitHubApps();
|
|
390
|
+
expect(result).toEqual([mockGitHubApp]);
|
|
391
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/github-apps', expect.any(Object));
|
|
392
|
+
});
|
|
393
|
+
it('should list github apps with summary', async () => {
|
|
394
|
+
mockFetch.mockResolvedValueOnce(mockResponse([mockGitHubApp]));
|
|
395
|
+
const result = await client.listGitHubApps({ summary: true });
|
|
396
|
+
expect(result).toEqual([
|
|
397
|
+
{
|
|
398
|
+
id: 1,
|
|
399
|
+
uuid: 'gh-app-uuid',
|
|
400
|
+
name: 'my-github-app',
|
|
401
|
+
organization: null,
|
|
402
|
+
is_public: false,
|
|
403
|
+
app_id: 12345,
|
|
404
|
+
},
|
|
405
|
+
]);
|
|
406
|
+
});
|
|
407
|
+
it('should create a github app', async () => {
|
|
408
|
+
mockFetch.mockResolvedValueOnce(mockResponse(mockGitHubApp));
|
|
409
|
+
const result = await client.createGitHubApp({
|
|
410
|
+
name: 'my-github-app',
|
|
411
|
+
api_url: 'https://api.github.com',
|
|
412
|
+
html_url: 'https://github.com',
|
|
413
|
+
app_id: 12345,
|
|
414
|
+
installation_id: 67890,
|
|
415
|
+
client_id: 'client-123',
|
|
416
|
+
client_secret: 'secret-456',
|
|
417
|
+
private_key_uuid: 'key-uuid',
|
|
418
|
+
});
|
|
419
|
+
expect(result).toEqual(mockGitHubApp);
|
|
420
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/github-apps', expect.objectContaining({ method: 'POST' }));
|
|
421
|
+
});
|
|
422
|
+
it('should update a github app', async () => {
|
|
423
|
+
const updateResponse = { message: 'GitHub app updated successfully', data: mockGitHubApp };
|
|
424
|
+
mockFetch.mockResolvedValueOnce(mockResponse(updateResponse));
|
|
425
|
+
const result = await client.updateGitHubApp(1, { name: 'updated-app' });
|
|
426
|
+
expect(result).toEqual(updateResponse);
|
|
427
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/github-apps/1', expect.objectContaining({
|
|
428
|
+
method: 'PATCH',
|
|
429
|
+
body: JSON.stringify({ name: 'updated-app' }),
|
|
430
|
+
}));
|
|
431
|
+
});
|
|
432
|
+
it('should delete a github app', async () => {
|
|
433
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ message: 'GitHub app deleted successfully' }));
|
|
434
|
+
const result = await client.deleteGitHubApp(1);
|
|
435
|
+
expect(result).toEqual({ message: 'GitHub app deleted successfully' });
|
|
436
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/github-apps/1', expect.objectContaining({ method: 'DELETE' }));
|
|
437
|
+
});
|
|
438
|
+
});
|
|
362
439
|
describe('error handling', () => {
|
|
363
440
|
it('should handle network errors', async () => {
|
|
364
441
|
mockFetch.mockRejectedValueOnce(new TypeError('fetch failed'));
|
|
@@ -610,11 +687,76 @@ describe('CoolifyClient', () => {
|
|
|
610
687
|
expect(result).toEqual({ uuid: 'new-app-uuid' });
|
|
611
688
|
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/applications/dockercompose', expect.objectContaining({ method: 'POST' }));
|
|
612
689
|
});
|
|
690
|
+
/**
|
|
691
|
+
* Issue #76 - Client Layer Behavior Test
|
|
692
|
+
*
|
|
693
|
+
* This test documents that the client passes through whatever data it receives.
|
|
694
|
+
* The client itself is NOT buggy - it correctly sends all fields to the API.
|
|
695
|
+
*
|
|
696
|
+
* The FIX for #76 is in mcp-server.ts which now strips 'action' before
|
|
697
|
+
* calling client methods. This test ensures the client behavior remains
|
|
698
|
+
* predictable (pass-through) so the MCP server layer must handle filtering.
|
|
699
|
+
*/
|
|
700
|
+
it('client passes through action field when included in create data (documents #76 fix location)', async () => {
|
|
701
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ uuid: 'new-app-uuid' }));
|
|
702
|
+
// This simulates what mcp-server.ts does: passing full args with action
|
|
703
|
+
const argsFromMcpTool = {
|
|
704
|
+
action: 'create_public', // This should NOT be sent to the API
|
|
705
|
+
project_uuid: 'proj-uuid',
|
|
706
|
+
server_uuid: 'server-uuid',
|
|
707
|
+
git_repository: 'https://github.com/user/repo',
|
|
708
|
+
git_branch: 'main',
|
|
709
|
+
build_pack: 'nixpacks',
|
|
710
|
+
ports_exposes: '3000',
|
|
711
|
+
};
|
|
712
|
+
await client.createApplicationPublic(argsFromMcpTool);
|
|
713
|
+
// This assertion proves the bug: 'action' IS included in the request body
|
|
714
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/applications/public', expect.objectContaining({
|
|
715
|
+
method: 'POST',
|
|
716
|
+
body: expect.stringContaining('"action":"create_public"'),
|
|
717
|
+
}));
|
|
718
|
+
});
|
|
613
719
|
it('should update an application', async () => {
|
|
614
720
|
mockFetch.mockResolvedValueOnce(mockResponse({ ...mockApplication, name: 'updated-app' }));
|
|
615
721
|
const result = await client.updateApplication('app-uuid', { name: 'updated-app' });
|
|
616
722
|
expect(result.name).toBe('updated-app');
|
|
617
723
|
});
|
|
724
|
+
it('should update an application and verify request body', async () => {
|
|
725
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ ...mockApplication, name: 'updated-app' }));
|
|
726
|
+
await client.updateApplication('app-uuid', { name: 'updated-app', description: 'new desc' });
|
|
727
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/applications/app-uuid', expect.objectContaining({
|
|
728
|
+
method: 'PATCH',
|
|
729
|
+
body: JSON.stringify({ name: 'updated-app', description: 'new desc' }),
|
|
730
|
+
}));
|
|
731
|
+
});
|
|
732
|
+
/**
|
|
733
|
+
* Issue #76 - Client Layer Behavior Test
|
|
734
|
+
*
|
|
735
|
+
* This test documents that the client passes through whatever data it receives.
|
|
736
|
+
* The client itself is NOT buggy - it correctly sends all fields to the API.
|
|
737
|
+
*
|
|
738
|
+
* The FIX for #76 is in mcp-server.ts which now strips 'action' before
|
|
739
|
+
* calling client methods. This test ensures the client behavior remains
|
|
740
|
+
* predictable (pass-through) so the MCP server layer must handle filtering.
|
|
741
|
+
*/
|
|
742
|
+
it('client passes through action field when included in update data (documents #76 fix location)', async () => {
|
|
743
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ ...mockApplication, name: 'updated-app' }));
|
|
744
|
+
// This simulates what mcp-server.ts does: passing the full args object including 'action'
|
|
745
|
+
const argsFromMcpTool = {
|
|
746
|
+
action: 'update', // This should NOT be sent to the API
|
|
747
|
+
uuid: 'app-uuid', // This is extracted separately
|
|
748
|
+
name: 'updated-app',
|
|
749
|
+
description: 'new desc',
|
|
750
|
+
};
|
|
751
|
+
// The client passes whatever it receives to the API
|
|
752
|
+
await client.updateApplication('app-uuid', argsFromMcpTool);
|
|
753
|
+
// This assertion proves the bug: 'action' IS included in the request body
|
|
754
|
+
// The Coolify API will reject this with "action: This field is not allowed"
|
|
755
|
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/applications/app-uuid', expect.objectContaining({
|
|
756
|
+
method: 'PATCH',
|
|
757
|
+
body: expect.stringContaining('"action":"update"'),
|
|
758
|
+
}));
|
|
759
|
+
});
|
|
618
760
|
it('should delete an application', async () => {
|
|
619
761
|
mockFetch.mockResolvedValueOnce(mockResponse({ message: 'Deleted' }));
|
|
620
762
|
const result = await client.deleteApplication('app-uuid');
|
|
@@ -52,6 +52,7 @@ describe('CoolifyMcpServer v2', () => {
|
|
|
52
52
|
// Application operations
|
|
53
53
|
expect(typeof client.listApplications).toBe('function');
|
|
54
54
|
expect(typeof client.getApplication).toBe('function');
|
|
55
|
+
expect(typeof client.createApplicationPublic).toBe('function');
|
|
55
56
|
expect(typeof client.createApplicationPrivateGH).toBe('function');
|
|
56
57
|
expect(typeof client.createApplicationPrivateKey).toBe('function');
|
|
57
58
|
expect(typeof client.createApplicationDockerImage).toBe('function');
|
|
@@ -106,6 +107,11 @@ describe('CoolifyMcpServer v2', () => {
|
|
|
106
107
|
expect(typeof client.createPrivateKey).toBe('function');
|
|
107
108
|
expect(typeof client.updatePrivateKey).toBe('function');
|
|
108
109
|
expect(typeof client.deletePrivateKey).toBe('function');
|
|
110
|
+
// GitHub App operations
|
|
111
|
+
expect(typeof client.listGitHubApps).toBe('function');
|
|
112
|
+
expect(typeof client.createGitHubApp).toBe('function');
|
|
113
|
+
expect(typeof client.updateGitHubApp).toBe('function');
|
|
114
|
+
expect(typeof client.deleteGitHubApp).toBe('function');
|
|
109
115
|
// Backup operations
|
|
110
116
|
expect(typeof client.listDatabaseBackups).toBe('function');
|
|
111
117
|
expect(typeof client.getDatabaseBackup).toBe('function');
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Coolify API Client
|
|
3
3
|
* Complete HTTP client for the Coolify API v1
|
|
4
4
|
*/
|
|
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, EnvVarSummary, CreateEnvVarRequest, UpdateEnvVarRequest, BulkUpdateEnvVarsRequest, Database, UpdateDatabaseRequest, CreatePostgresqlRequest, CreateMysqlRequest, CreateMariadbRequest, CreateMongodbRequest, CreateRedisRequest, CreateKeydbRequest, CreateClickhouseRequest, CreateDragonflyRequest, CreateDatabaseResponse, DatabaseBackup, BackupExecution, CreateDatabaseBackupRequest, UpdateDatabaseBackupRequest, Service, CreateServiceRequest, UpdateServiceRequest, ServiceCreateResponse, Deployment, Team, TeamMember, PrivateKey, CreatePrivateKeyRequest, UpdatePrivateKeyRequest, CloudToken, CreateCloudTokenRequest, UpdateCloudTokenRequest, CloudTokenValidation, Version, ApplicationDiagnostic, ServerDiagnostic, InfrastructureIssuesReport, BatchOperationResult } from '../types/coolify.js';
|
|
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, EnvVarSummary, CreateEnvVarRequest, UpdateEnvVarRequest, BulkUpdateEnvVarsRequest, Database, UpdateDatabaseRequest, CreatePostgresqlRequest, CreateMysqlRequest, CreateMariadbRequest, CreateMongodbRequest, CreateRedisRequest, CreateKeydbRequest, CreateClickhouseRequest, CreateDragonflyRequest, CreateDatabaseResponse, DatabaseBackup, BackupExecution, CreateDatabaseBackupRequest, UpdateDatabaseBackupRequest, Service, CreateServiceRequest, UpdateServiceRequest, ServiceCreateResponse, Deployment, Team, TeamMember, PrivateKey, CreatePrivateKeyRequest, UpdatePrivateKeyRequest, GitHubApp, CreateGitHubAppRequest, UpdateGitHubAppRequest, GitHubAppUpdateResponse, CloudToken, CreateCloudTokenRequest, UpdateCloudTokenRequest, CloudTokenValidation, Version, ApplicationDiagnostic, ServerDiagnostic, InfrastructureIssuesReport, BatchOperationResult } from '../types/coolify.js';
|
|
6
6
|
export interface ListOptions {
|
|
7
7
|
page?: number;
|
|
8
8
|
per_page?: number;
|
|
@@ -55,6 +55,14 @@ export interface ProjectSummary {
|
|
|
55
55
|
name: string;
|
|
56
56
|
description?: string;
|
|
57
57
|
}
|
|
58
|
+
export interface GitHubAppSummary {
|
|
59
|
+
id: number;
|
|
60
|
+
uuid: string;
|
|
61
|
+
name: string;
|
|
62
|
+
organization: string | null;
|
|
63
|
+
is_public: boolean;
|
|
64
|
+
app_id: number | null;
|
|
65
|
+
}
|
|
58
66
|
/**
|
|
59
67
|
* HTTP client for the Coolify API
|
|
60
68
|
*/
|
|
@@ -148,6 +156,10 @@ export declare class CoolifyClient {
|
|
|
148
156
|
createPrivateKey(data: CreatePrivateKeyRequest): Promise<UuidResponse>;
|
|
149
157
|
updatePrivateKey(uuid: string, data: UpdatePrivateKeyRequest): Promise<PrivateKey>;
|
|
150
158
|
deletePrivateKey(uuid: string): Promise<MessageResponse>;
|
|
159
|
+
listGitHubApps(options?: ListOptions): Promise<GitHubApp[] | GitHubAppSummary[]>;
|
|
160
|
+
createGitHubApp(data: CreateGitHubAppRequest): Promise<GitHubApp>;
|
|
161
|
+
updateGitHubApp(id: number, data: UpdateGitHubAppRequest): Promise<GitHubAppUpdateResponse>;
|
|
162
|
+
deleteGitHubApp(id: number): Promise<MessageResponse>;
|
|
151
163
|
listCloudTokens(): Promise<CloudToken[]>;
|
|
152
164
|
getCloudToken(uuid: string): Promise<CloudToken>;
|
|
153
165
|
createCloudToken(data: CreateCloudTokenRequest): Promise<UuidResponse>;
|
|
@@ -71,6 +71,16 @@ function toProjectSummary(proj) {
|
|
|
71
71
|
description: proj.description,
|
|
72
72
|
};
|
|
73
73
|
}
|
|
74
|
+
function toGitHubAppSummary(app) {
|
|
75
|
+
return {
|
|
76
|
+
id: app.id,
|
|
77
|
+
uuid: app.uuid,
|
|
78
|
+
name: app.name,
|
|
79
|
+
organization: app.organization,
|
|
80
|
+
is_public: app.is_public,
|
|
81
|
+
app_id: app.app_id,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
74
84
|
function toEnvVarSummary(envVar) {
|
|
75
85
|
return {
|
|
76
86
|
uuid: envVar.uuid,
|
|
@@ -613,6 +623,30 @@ export class CoolifyClient {
|
|
|
613
623
|
});
|
|
614
624
|
}
|
|
615
625
|
// ===========================================================================
|
|
626
|
+
// GitHub App endpoints
|
|
627
|
+
// ===========================================================================
|
|
628
|
+
async listGitHubApps(options) {
|
|
629
|
+
const apps = await this.request('/github-apps');
|
|
630
|
+
return options?.summary && Array.isArray(apps) ? apps.map(toGitHubAppSummary) : apps;
|
|
631
|
+
}
|
|
632
|
+
async createGitHubApp(data) {
|
|
633
|
+
return this.request('/github-apps', {
|
|
634
|
+
method: 'POST',
|
|
635
|
+
body: JSON.stringify(data),
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
async updateGitHubApp(id, data) {
|
|
639
|
+
return this.request(`/github-apps/${id}`, {
|
|
640
|
+
method: 'PATCH',
|
|
641
|
+
body: JSON.stringify(cleanRequestData(data)),
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
async deleteGitHubApp(id) {
|
|
645
|
+
return this.request(`/github-apps/${id}`, {
|
|
646
|
+
method: 'DELETE',
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
// ===========================================================================
|
|
616
650
|
// Cloud Token endpoints (Hetzner, DigitalOcean)
|
|
617
651
|
// ===========================================================================
|
|
618
652
|
async listCloudTokens() {
|
package/dist/lib/mcp-server.d.ts
CHANGED
package/dist/lib/mcp-server.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Coolify MCP Server v2.
|
|
2
|
+
* Coolify MCP Server v2.4.0
|
|
3
3
|
* Consolidated tools for efficient token usage
|
|
4
4
|
*/
|
|
5
5
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
6
6
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
7
7
|
import { z } from 'zod';
|
|
8
8
|
import { CoolifyClient, } from './coolify-client.js';
|
|
9
|
-
const VERSION = '2.
|
|
9
|
+
const VERSION = '2.4.0';
|
|
10
10
|
/** Wrap handler with error handling */
|
|
11
11
|
function wrap(fn) {
|
|
12
12
|
return fn()
|
|
@@ -162,7 +162,14 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
162
162
|
this.tool('list_applications', 'List apps (summary)', { page: z.number().optional(), per_page: z.number().optional() }, async ({ page, per_page }) => wrap(() => this.client.listApplications({ page, per_page, summary: true })));
|
|
163
163
|
this.tool('get_application', 'App details', { uuid: z.string() }, async ({ uuid }) => wrap(() => this.client.getApplication(uuid)));
|
|
164
164
|
this.tool('application', 'Manage app: create/update/delete', {
|
|
165
|
-
action: z.enum([
|
|
165
|
+
action: z.enum([
|
|
166
|
+
'create_public',
|
|
167
|
+
'create_github',
|
|
168
|
+
'create_key',
|
|
169
|
+
'create_dockerimage',
|
|
170
|
+
'update',
|
|
171
|
+
'delete',
|
|
172
|
+
]),
|
|
166
173
|
uuid: z.string().optional(),
|
|
167
174
|
// Create fields
|
|
168
175
|
project_uuid: z.string().optional(),
|
|
@@ -172,6 +179,7 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
172
179
|
git_repository: z.string().optional(),
|
|
173
180
|
git_branch: z.string().optional(),
|
|
174
181
|
environment_name: z.string().optional(),
|
|
182
|
+
environment_uuid: z.string().optional(),
|
|
175
183
|
build_pack: z.string().optional(),
|
|
176
184
|
ports_exposes: z.string().optional(),
|
|
177
185
|
// Docker image fields
|
|
@@ -197,8 +205,26 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
197
205
|
// Delete fields
|
|
198
206
|
delete_volumes: z.boolean().optional(),
|
|
199
207
|
}, async (args) => {
|
|
200
|
-
|
|
208
|
+
// Strip MCP-internal fields before passing to API (fixes #76)
|
|
209
|
+
const { action, uuid, delete_volumes, ...apiData } = args;
|
|
201
210
|
switch (action) {
|
|
211
|
+
case 'create_public':
|
|
212
|
+
if (!args.project_uuid ||
|
|
213
|
+
!args.server_uuid ||
|
|
214
|
+
!args.git_repository ||
|
|
215
|
+
!args.git_branch ||
|
|
216
|
+
!args.build_pack ||
|
|
217
|
+
!args.ports_exposes) {
|
|
218
|
+
return {
|
|
219
|
+
content: [
|
|
220
|
+
{
|
|
221
|
+
type: 'text',
|
|
222
|
+
text: 'Error: project_uuid, server_uuid, git_repository, git_branch, build_pack, ports_exposes required',
|
|
223
|
+
},
|
|
224
|
+
],
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
return wrap(() => this.client.createApplicationPublic(apiData));
|
|
202
228
|
case 'create_github':
|
|
203
229
|
if (!args.project_uuid ||
|
|
204
230
|
!args.server_uuid ||
|
|
@@ -214,7 +240,7 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
214
240
|
],
|
|
215
241
|
};
|
|
216
242
|
}
|
|
217
|
-
return wrap(() => this.client.createApplicationPrivateGH(
|
|
243
|
+
return wrap(() => this.client.createApplicationPrivateGH(apiData));
|
|
218
244
|
case 'create_key':
|
|
219
245
|
if (!args.project_uuid ||
|
|
220
246
|
!args.server_uuid ||
|
|
@@ -230,7 +256,7 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
230
256
|
],
|
|
231
257
|
};
|
|
232
258
|
}
|
|
233
|
-
return wrap(() => this.client.createApplicationPrivateKey(
|
|
259
|
+
return wrap(() => this.client.createApplicationPrivateKey(apiData));
|
|
234
260
|
case 'create_dockerimage':
|
|
235
261
|
if (!args.project_uuid ||
|
|
236
262
|
!args.server_uuid ||
|
|
@@ -245,15 +271,15 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
245
271
|
],
|
|
246
272
|
};
|
|
247
273
|
}
|
|
248
|
-
return wrap(() => this.client.createApplicationDockerImage(
|
|
274
|
+
return wrap(() => this.client.createApplicationDockerImage(apiData));
|
|
249
275
|
case 'update':
|
|
250
276
|
if (!uuid)
|
|
251
277
|
return { content: [{ type: 'text', text: 'Error: uuid required' }] };
|
|
252
|
-
return wrap(() => this.client.updateApplication(uuid,
|
|
278
|
+
return wrap(() => this.client.updateApplication(uuid, apiData));
|
|
253
279
|
case 'delete':
|
|
254
280
|
if (!uuid)
|
|
255
281
|
return { content: [{ type: 'text', text: 'Error: uuid required' }] };
|
|
256
|
-
return wrap(() => this.client.deleteApplication(uuid, { deleteVolumes:
|
|
282
|
+
return wrap(() => this.client.deleteApplication(uuid, { deleteVolumes: delete_volumes }));
|
|
257
283
|
}
|
|
258
284
|
});
|
|
259
285
|
this.tool('application_logs', 'Get app logs', { uuid: z.string(), lines: z.number().optional() }, async ({ uuid, lines }) => wrap(() => this.client.getApplicationLogs(uuid, lines)));
|
|
@@ -352,7 +378,8 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
352
378
|
docker_compose_raw: z.string().optional(),
|
|
353
379
|
delete_volumes: z.boolean().optional(),
|
|
354
380
|
}, async (args) => {
|
|
355
|
-
|
|
381
|
+
// Strip MCP-internal fields before passing to API (fixes #76)
|
|
382
|
+
const { action, uuid, delete_volumes, ...apiData } = args;
|
|
356
383
|
switch (action) {
|
|
357
384
|
case 'create':
|
|
358
385
|
if (!args.server_uuid || !args.project_uuid) {
|
|
@@ -362,15 +389,15 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
362
389
|
],
|
|
363
390
|
};
|
|
364
391
|
}
|
|
365
|
-
return wrap(() => this.client.createService(
|
|
392
|
+
return wrap(() => this.client.createService(apiData));
|
|
366
393
|
case 'update':
|
|
367
394
|
if (!uuid)
|
|
368
395
|
return { content: [{ type: 'text', text: 'Error: uuid required' }] };
|
|
369
|
-
return wrap(() => this.client.updateService(uuid,
|
|
396
|
+
return wrap(() => this.client.updateService(uuid, apiData));
|
|
370
397
|
case 'delete':
|
|
371
398
|
if (!uuid)
|
|
372
399
|
return { content: [{ type: 'text', text: 'Error: uuid required' }] };
|
|
373
|
-
return wrap(() => this.client.deleteService(uuid, { deleteVolumes:
|
|
400
|
+
return wrap(() => this.client.deleteService(uuid, { deleteVolumes: delete_volumes }));
|
|
374
401
|
}
|
|
375
402
|
});
|
|
376
403
|
// =========================================================================
|
|
@@ -516,6 +543,90 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
516
543
|
}
|
|
517
544
|
});
|
|
518
545
|
// =========================================================================
|
|
546
|
+
// GitHub Apps (1 tool - consolidated)
|
|
547
|
+
// =========================================================================
|
|
548
|
+
this.tool('github_apps', 'Manage GitHub Apps: list/get/create/update/delete', {
|
|
549
|
+
action: z.enum(['list', 'get', 'create', 'update', 'delete']),
|
|
550
|
+
// GitHub apps use integer id, not uuid
|
|
551
|
+
id: z.number().optional(),
|
|
552
|
+
// Create/Update fields
|
|
553
|
+
name: z.string().optional(),
|
|
554
|
+
organization: z.string().optional(),
|
|
555
|
+
api_url: z.string().optional(),
|
|
556
|
+
html_url: z.string().optional(),
|
|
557
|
+
custom_user: z.string().optional(),
|
|
558
|
+
custom_port: z.number().optional(),
|
|
559
|
+
app_id: z.number().optional(),
|
|
560
|
+
installation_id: z.number().optional(),
|
|
561
|
+
client_id: z.string().optional(),
|
|
562
|
+
client_secret: z.string().optional(),
|
|
563
|
+
webhook_secret: z.string().optional(),
|
|
564
|
+
private_key_uuid: z.string().optional(),
|
|
565
|
+
is_system_wide: z.boolean().optional(),
|
|
566
|
+
}, async (args) => {
|
|
567
|
+
const { action, id, ...apiData } = args;
|
|
568
|
+
switch (action) {
|
|
569
|
+
case 'list':
|
|
570
|
+
return wrap(async () => {
|
|
571
|
+
const apps = (await this.client.listGitHubApps({
|
|
572
|
+
summary: true,
|
|
573
|
+
}));
|
|
574
|
+
return apps;
|
|
575
|
+
});
|
|
576
|
+
case 'get':
|
|
577
|
+
if (!id)
|
|
578
|
+
return { content: [{ type: 'text', text: 'Error: id required' }] };
|
|
579
|
+
return wrap(async () => {
|
|
580
|
+
const apps = (await this.client.listGitHubApps());
|
|
581
|
+
const app = apps.find((a) => a.id === id);
|
|
582
|
+
if (!app)
|
|
583
|
+
throw new Error(`GitHub App with id ${id} not found`);
|
|
584
|
+
return app;
|
|
585
|
+
});
|
|
586
|
+
case 'create':
|
|
587
|
+
if (!apiData.name ||
|
|
588
|
+
!apiData.api_url ||
|
|
589
|
+
!apiData.html_url ||
|
|
590
|
+
!apiData.app_id ||
|
|
591
|
+
!apiData.installation_id ||
|
|
592
|
+
!apiData.client_id ||
|
|
593
|
+
!apiData.client_secret ||
|
|
594
|
+
!apiData.private_key_uuid) {
|
|
595
|
+
return {
|
|
596
|
+
content: [
|
|
597
|
+
{
|
|
598
|
+
type: 'text',
|
|
599
|
+
text: 'Error: name, api_url, html_url, app_id, installation_id, client_id, client_secret, private_key_uuid required',
|
|
600
|
+
},
|
|
601
|
+
],
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
return wrap(() => this.client.createGitHubApp({
|
|
605
|
+
name: apiData.name,
|
|
606
|
+
api_url: apiData.api_url,
|
|
607
|
+
html_url: apiData.html_url,
|
|
608
|
+
app_id: apiData.app_id,
|
|
609
|
+
installation_id: apiData.installation_id,
|
|
610
|
+
client_id: apiData.client_id,
|
|
611
|
+
client_secret: apiData.client_secret,
|
|
612
|
+
private_key_uuid: apiData.private_key_uuid,
|
|
613
|
+
organization: apiData.organization,
|
|
614
|
+
custom_user: apiData.custom_user,
|
|
615
|
+
custom_port: apiData.custom_port,
|
|
616
|
+
webhook_secret: apiData.webhook_secret,
|
|
617
|
+
is_system_wide: apiData.is_system_wide,
|
|
618
|
+
}));
|
|
619
|
+
case 'update':
|
|
620
|
+
if (!id)
|
|
621
|
+
return { content: [{ type: 'text', text: 'Error: id required' }] };
|
|
622
|
+
return wrap(() => this.client.updateGitHubApp(id, apiData));
|
|
623
|
+
case 'delete':
|
|
624
|
+
if (!id)
|
|
625
|
+
return { content: [{ type: 'text', text: 'Error: id required' }] };
|
|
626
|
+
return wrap(() => this.client.deleteGitHubApp(id));
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
// =========================================================================
|
|
519
630
|
// Database Backups (1 tool - consolidated)
|
|
520
631
|
// =========================================================================
|
|
521
632
|
this.tool('database_backups', 'Manage backups: list_schedules/get_schedule/list_executions/get_execution/create/update/delete', {
|
package/dist/types/coolify.d.ts
CHANGED
|
@@ -718,6 +718,64 @@ export interface UpdatePrivateKeyRequest {
|
|
|
718
718
|
description?: string;
|
|
719
719
|
private_key?: string;
|
|
720
720
|
}
|
|
721
|
+
export interface GitHubApp {
|
|
722
|
+
id: number;
|
|
723
|
+
uuid: string;
|
|
724
|
+
name: string;
|
|
725
|
+
organization: string | null;
|
|
726
|
+
api_url: string;
|
|
727
|
+
html_url: string;
|
|
728
|
+
custom_user: string;
|
|
729
|
+
custom_port: number;
|
|
730
|
+
app_id: number | null;
|
|
731
|
+
installation_id: number | null;
|
|
732
|
+
client_id: string | null;
|
|
733
|
+
is_system_wide: boolean;
|
|
734
|
+
is_public: boolean;
|
|
735
|
+
private_key_id: number | null;
|
|
736
|
+
team_id: number;
|
|
737
|
+
type: string;
|
|
738
|
+
administration: string | null;
|
|
739
|
+
contents: string | null;
|
|
740
|
+
metadata: string | null;
|
|
741
|
+
pull_requests: string | null;
|
|
742
|
+
created_at: string;
|
|
743
|
+
updated_at: string;
|
|
744
|
+
}
|
|
745
|
+
export interface CreateGitHubAppRequest {
|
|
746
|
+
name: string;
|
|
747
|
+
api_url: string;
|
|
748
|
+
html_url: string;
|
|
749
|
+
app_id: number;
|
|
750
|
+
installation_id: number;
|
|
751
|
+
client_id: string;
|
|
752
|
+
client_secret: string;
|
|
753
|
+
private_key_uuid: string;
|
|
754
|
+
organization?: string;
|
|
755
|
+
custom_user?: string;
|
|
756
|
+
custom_port?: number;
|
|
757
|
+
webhook_secret?: string;
|
|
758
|
+
is_system_wide?: boolean;
|
|
759
|
+
}
|
|
760
|
+
export interface UpdateGitHubAppRequest {
|
|
761
|
+
name?: string;
|
|
762
|
+
organization?: string;
|
|
763
|
+
api_url?: string;
|
|
764
|
+
html_url?: string;
|
|
765
|
+
custom_user?: string;
|
|
766
|
+
custom_port?: number;
|
|
767
|
+
app_id?: number;
|
|
768
|
+
installation_id?: number;
|
|
769
|
+
client_id?: string;
|
|
770
|
+
client_secret?: string;
|
|
771
|
+
webhook_secret?: string;
|
|
772
|
+
private_key_uuid?: string;
|
|
773
|
+
is_system_wide?: boolean;
|
|
774
|
+
}
|
|
775
|
+
export interface GitHubAppUpdateResponse {
|
|
776
|
+
message: string;
|
|
777
|
+
data: GitHubApp;
|
|
778
|
+
}
|
|
721
779
|
export type CloudProvider = 'hetzner' | 'digitalocean';
|
|
722
780
|
export interface CloudToken {
|
|
723
781
|
id: number;
|