@masonator/coolify-mcp 2.3.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 +9 -4
- package/dist/__tests__/coolify-client.test.js +142 -0
- package/dist/__tests__/mcp-server.test.js +5 -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 +99 -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
|
|
@@ -228,6 +231,8 @@ These tools accept human-friendly identifiers instead of just UUIDs:
|
|
|
228
231
|
- `get_application` - Get application details
|
|
229
232
|
- `application_logs` - Get application logs
|
|
230
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');
|
|
@@ -107,6 +107,11 @@ describe('CoolifyMcpServer v2', () => {
|
|
|
107
107
|
expect(typeof client.createPrivateKey).toBe('function');
|
|
108
108
|
expect(typeof client.updatePrivateKey).toBe('function');
|
|
109
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');
|
|
110
115
|
// Backup operations
|
|
111
116
|
expect(typeof client.listDatabaseBackups).toBe('function');
|
|
112
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()
|
|
@@ -205,7 +205,8 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
205
205
|
// Delete fields
|
|
206
206
|
delete_volumes: z.boolean().optional(),
|
|
207
207
|
}, async (args) => {
|
|
208
|
-
|
|
208
|
+
// Strip MCP-internal fields before passing to API (fixes #76)
|
|
209
|
+
const { action, uuid, delete_volumes, ...apiData } = args;
|
|
209
210
|
switch (action) {
|
|
210
211
|
case 'create_public':
|
|
211
212
|
if (!args.project_uuid ||
|
|
@@ -223,7 +224,7 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
223
224
|
],
|
|
224
225
|
};
|
|
225
226
|
}
|
|
226
|
-
return wrap(() => this.client.createApplicationPublic(
|
|
227
|
+
return wrap(() => this.client.createApplicationPublic(apiData));
|
|
227
228
|
case 'create_github':
|
|
228
229
|
if (!args.project_uuid ||
|
|
229
230
|
!args.server_uuid ||
|
|
@@ -239,7 +240,7 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
239
240
|
],
|
|
240
241
|
};
|
|
241
242
|
}
|
|
242
|
-
return wrap(() => this.client.createApplicationPrivateGH(
|
|
243
|
+
return wrap(() => this.client.createApplicationPrivateGH(apiData));
|
|
243
244
|
case 'create_key':
|
|
244
245
|
if (!args.project_uuid ||
|
|
245
246
|
!args.server_uuid ||
|
|
@@ -255,7 +256,7 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
255
256
|
],
|
|
256
257
|
};
|
|
257
258
|
}
|
|
258
|
-
return wrap(() => this.client.createApplicationPrivateKey(
|
|
259
|
+
return wrap(() => this.client.createApplicationPrivateKey(apiData));
|
|
259
260
|
case 'create_dockerimage':
|
|
260
261
|
if (!args.project_uuid ||
|
|
261
262
|
!args.server_uuid ||
|
|
@@ -270,15 +271,15 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
270
271
|
],
|
|
271
272
|
};
|
|
272
273
|
}
|
|
273
|
-
return wrap(() => this.client.createApplicationDockerImage(
|
|
274
|
+
return wrap(() => this.client.createApplicationDockerImage(apiData));
|
|
274
275
|
case 'update':
|
|
275
276
|
if (!uuid)
|
|
276
277
|
return { content: [{ type: 'text', text: 'Error: uuid required' }] };
|
|
277
|
-
return wrap(() => this.client.updateApplication(uuid,
|
|
278
|
+
return wrap(() => this.client.updateApplication(uuid, apiData));
|
|
278
279
|
case 'delete':
|
|
279
280
|
if (!uuid)
|
|
280
281
|
return { content: [{ type: 'text', text: 'Error: uuid required' }] };
|
|
281
|
-
return wrap(() => this.client.deleteApplication(uuid, { deleteVolumes:
|
|
282
|
+
return wrap(() => this.client.deleteApplication(uuid, { deleteVolumes: delete_volumes }));
|
|
282
283
|
}
|
|
283
284
|
});
|
|
284
285
|
this.tool('application_logs', 'Get app logs', { uuid: z.string(), lines: z.number().optional() }, async ({ uuid, lines }) => wrap(() => this.client.getApplicationLogs(uuid, lines)));
|
|
@@ -377,7 +378,8 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
377
378
|
docker_compose_raw: z.string().optional(),
|
|
378
379
|
delete_volumes: z.boolean().optional(),
|
|
379
380
|
}, async (args) => {
|
|
380
|
-
|
|
381
|
+
// Strip MCP-internal fields before passing to API (fixes #76)
|
|
382
|
+
const { action, uuid, delete_volumes, ...apiData } = args;
|
|
381
383
|
switch (action) {
|
|
382
384
|
case 'create':
|
|
383
385
|
if (!args.server_uuid || !args.project_uuid) {
|
|
@@ -387,15 +389,15 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
387
389
|
],
|
|
388
390
|
};
|
|
389
391
|
}
|
|
390
|
-
return wrap(() => this.client.createService(
|
|
392
|
+
return wrap(() => this.client.createService(apiData));
|
|
391
393
|
case 'update':
|
|
392
394
|
if (!uuid)
|
|
393
395
|
return { content: [{ type: 'text', text: 'Error: uuid required' }] };
|
|
394
|
-
return wrap(() => this.client.updateService(uuid,
|
|
396
|
+
return wrap(() => this.client.updateService(uuid, apiData));
|
|
395
397
|
case 'delete':
|
|
396
398
|
if (!uuid)
|
|
397
399
|
return { content: [{ type: 'text', text: 'Error: uuid required' }] };
|
|
398
|
-
return wrap(() => this.client.deleteService(uuid, { deleteVolumes:
|
|
400
|
+
return wrap(() => this.client.deleteService(uuid, { deleteVolumes: delete_volumes }));
|
|
399
401
|
}
|
|
400
402
|
});
|
|
401
403
|
// =========================================================================
|
|
@@ -541,6 +543,90 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
541
543
|
}
|
|
542
544
|
});
|
|
543
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
|
+
// =========================================================================
|
|
544
630
|
// Database Backups (1 tool - consolidated)
|
|
545
631
|
// =========================================================================
|
|
546
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;
|