@masonator/coolify-mcp 2.7.2 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -9
- package/dist/__tests__/coolify-client.test.js +108 -7
- package/dist/lib/coolify-client.d.ts +17 -1
- package/dist/lib/coolify-client.js +37 -9
- package/dist/lib/docs-search.js +4 -6
- package/dist/lib/mcp-server.js +31 -4
- package/dist/types/coolify.d.ts +16 -0
- package/package.json +24 -8
package/README.md
CHANGED
|
@@ -103,13 +103,14 @@ The Coolify API returns extremely verbose responses - a single application can c
|
|
|
103
103
|
|
|
104
104
|
### Response Size Comparison
|
|
105
105
|
|
|
106
|
-
| Endpoint
|
|
107
|
-
|
|
|
108
|
-
| list_applications
|
|
109
|
-
| list_services
|
|
110
|
-
| list_servers
|
|
111
|
-
| list_application_envs
|
|
112
|
-
| deployment get
|
|
106
|
+
| Endpoint | Full Response | Summary Response | Reduction |
|
|
107
|
+
| ----------------------- | ------------- | ---------------- | --------- |
|
|
108
|
+
| list_applications | ~170KB | ~4.4KB | **97%** |
|
|
109
|
+
| list_services | ~367KB | ~1.2KB | **99%** |
|
|
110
|
+
| list_servers | ~4KB | ~0.4KB | **90%** |
|
|
111
|
+
| list_application_envs | ~3KB/var | ~0.1KB/var | **97%** |
|
|
112
|
+
| deployment get | ~13KB | ~1KB | **92%** |
|
|
113
|
+
| deployment list_for_app | ~1MB | ~4KB | **99.6%** |
|
|
113
114
|
|
|
114
115
|
### HATEOAS-style Response Actions
|
|
115
116
|
|
|
@@ -343,9 +344,10 @@ Power user tools for operating on multiple resources at once:
|
|
|
343
344
|
|
|
344
345
|
## Related Links
|
|
345
346
|
|
|
347
|
+
- [stumason.dev](https://stumason.dev) - Author's site
|
|
348
|
+
- [MCP Registry](https://registry.modelcontextprotocol.io) - Find this server as `io.github.StuMason/coolify`
|
|
346
349
|
- [Coolify](https://coolify.io/) - The open-source & self-hostable Heroku/Netlify/Vercel alternative
|
|
347
350
|
- [Model Context Protocol](https://modelcontextprotocol.io/) - The protocol powering AI tool integrations
|
|
348
|
-
- [MCP Server Registry](https://github.com/modelcontextprotocol/servers) - Official MCP server directory
|
|
349
351
|
|
|
350
352
|
## Contributing
|
|
351
353
|
|
|
@@ -363,5 +365,5 @@ MIT - see [LICENSE](LICENSE) for details.
|
|
|
363
365
|
---
|
|
364
366
|
|
|
365
367
|
<p align="center">
|
|
366
|
-
<
|
|
368
|
+
Built by <a href="https://stumason.dev">Stu Mason</a> · If you find this useful, please ⭐ star the repo!
|
|
367
369
|
</p>
|
|
@@ -849,6 +849,87 @@ describe('CoolifyClient', () => {
|
|
|
849
849
|
expect(callBody.domains).toBe('https://app.example.com');
|
|
850
850
|
expect(callBody.fqdn).toBeUndefined();
|
|
851
851
|
});
|
|
852
|
+
it('should map fqdn to domains in createApplicationDockerImage', async () => {
|
|
853
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ uuid: 'new-app-uuid' }));
|
|
854
|
+
await client.createApplicationDockerImage({
|
|
855
|
+
project_uuid: 'proj-uuid',
|
|
856
|
+
server_uuid: 'server-uuid',
|
|
857
|
+
docker_registry_image_name: 'traefik/whoami',
|
|
858
|
+
ports_exposes: '80',
|
|
859
|
+
fqdn: 'https://whoami.example.com',
|
|
860
|
+
});
|
|
861
|
+
const callBody = JSON.parse(mockFetch.mock.calls[0][1]?.body);
|
|
862
|
+
expect(callBody.domains).toBe('https://whoami.example.com');
|
|
863
|
+
expect(callBody.fqdn).toBeUndefined();
|
|
864
|
+
});
|
|
865
|
+
it('should map fqdn to domains in createApplicationDockerfile', async () => {
|
|
866
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ uuid: 'new-app-uuid' }));
|
|
867
|
+
await client.createApplicationDockerfile({
|
|
868
|
+
project_uuid: 'proj-uuid',
|
|
869
|
+
server_uuid: 'server-uuid',
|
|
870
|
+
dockerfile: 'FROM nginx',
|
|
871
|
+
fqdn: 'https://app.example.com',
|
|
872
|
+
});
|
|
873
|
+
const callBody = JSON.parse(mockFetch.mock.calls[0][1]?.body);
|
|
874
|
+
expect(callBody.domains).toBe('https://app.example.com');
|
|
875
|
+
expect(callBody.fqdn).toBeUndefined();
|
|
876
|
+
});
|
|
877
|
+
it('should map fqdn to domains in createApplicationDockerCompose', async () => {
|
|
878
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ uuid: 'new-app-uuid' }));
|
|
879
|
+
await client.createApplicationDockerCompose({
|
|
880
|
+
project_uuid: 'proj-uuid',
|
|
881
|
+
server_uuid: 'server-uuid',
|
|
882
|
+
docker_compose_raw: 'version: "3"\n',
|
|
883
|
+
fqdn: 'https://compose.example.com',
|
|
884
|
+
});
|
|
885
|
+
const callBody = JSON.parse(mockFetch.mock.calls[0][1]?.body);
|
|
886
|
+
expect(callBody.domains).toBe('https://compose.example.com');
|
|
887
|
+
expect(callBody.fqdn).toBeUndefined();
|
|
888
|
+
});
|
|
889
|
+
it('should accept explicit domains and prefer it over fqdn', async () => {
|
|
890
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ uuid: 'new-app-uuid' }));
|
|
891
|
+
await client.createApplicationDockerImage({
|
|
892
|
+
project_uuid: 'proj-uuid',
|
|
893
|
+
server_uuid: 'server-uuid',
|
|
894
|
+
docker_registry_image_name: 'traefik/whoami',
|
|
895
|
+
ports_exposes: '80',
|
|
896
|
+
fqdn: 'https://from-fqdn.example.com',
|
|
897
|
+
domains: 'https://from-domains.example.com',
|
|
898
|
+
});
|
|
899
|
+
const callBody = JSON.parse(mockFetch.mock.calls[0][1]?.body);
|
|
900
|
+
expect(callBody.domains).toBe('https://from-domains.example.com');
|
|
901
|
+
expect(callBody.fqdn).toBeUndefined();
|
|
902
|
+
});
|
|
903
|
+
it('should pass instant_deploy and custom_* fields through createApplicationDockerImage', async () => {
|
|
904
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ uuid: 'new-app-uuid' }));
|
|
905
|
+
await client.createApplicationDockerImage({
|
|
906
|
+
project_uuid: 'proj-uuid',
|
|
907
|
+
server_uuid: 'server-uuid',
|
|
908
|
+
docker_registry_image_name: 'traefik/whoami',
|
|
909
|
+
ports_exposes: '80',
|
|
910
|
+
instant_deploy: true,
|
|
911
|
+
custom_docker_run_options: '--network=my-net',
|
|
912
|
+
custom_labels: 'dHJhZWZpaw==',
|
|
913
|
+
});
|
|
914
|
+
const callBody = JSON.parse(mockFetch.mock.calls[0][1]?.body);
|
|
915
|
+
expect(callBody.instant_deploy).toBe(true);
|
|
916
|
+
expect(callBody.custom_docker_run_options).toBe('--network=my-net');
|
|
917
|
+
expect(callBody.custom_labels).toBe('dHJhZWZpaw==');
|
|
918
|
+
});
|
|
919
|
+
it('should pass destination_uuid through in createApplicationPublic', async () => {
|
|
920
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ uuid: 'new-app-uuid' }));
|
|
921
|
+
await client.createApplicationPublic({
|
|
922
|
+
project_uuid: 'proj-uuid',
|
|
923
|
+
server_uuid: 'server-uuid',
|
|
924
|
+
destination_uuid: 'dest-uuid',
|
|
925
|
+
git_repository: 'https://github.com/user/repo',
|
|
926
|
+
git_branch: 'main',
|
|
927
|
+
build_pack: 'nixpacks',
|
|
928
|
+
ports_exposes: '3000',
|
|
929
|
+
});
|
|
930
|
+
const callBody = JSON.parse(mockFetch.mock.calls[0][1]?.body);
|
|
931
|
+
expect(callBody.destination_uuid).toBe('dest-uuid');
|
|
932
|
+
});
|
|
852
933
|
it('should map fqdn to domains in updateApplication', async () => {
|
|
853
934
|
mockFetch.mockResolvedValueOnce(mockResponse(mockApplication));
|
|
854
935
|
await client.updateApplication('app-uuid', { fqdn: 'https://new.example.com' });
|
|
@@ -1457,12 +1538,32 @@ describe('CoolifyClient', () => {
|
|
|
1457
1538
|
logs_info: 'Logs available (16 chars). Use lines param to retrieve.',
|
|
1458
1539
|
});
|
|
1459
1540
|
});
|
|
1460
|
-
it('should list application deployments', async () => {
|
|
1461
|
-
|
|
1541
|
+
it('should list application deployments as essential by default (no logs)', async () => {
|
|
1542
|
+
const withLogs = { ...mockDeployment, logs: 'x'.repeat(30000) };
|
|
1543
|
+
// Real Coolify wraps the list in an envelope: { count, deployments: [...] }
|
|
1544
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ count: 1, deployments: [withLogs] }));
|
|
1462
1545
|
const result = await client.listApplicationDeployments('app-uuid');
|
|
1463
|
-
expect(result).
|
|
1546
|
+
expect(result.count).toBe(1);
|
|
1547
|
+
expect(result.deployments).toHaveLength(1);
|
|
1548
|
+
const [first] = result.deployments;
|
|
1549
|
+
// Essential projection: no raw logs, but a `logs_available` breadcrumb.
|
|
1550
|
+
expect(first.logs).toBeUndefined();
|
|
1551
|
+
expect(first.logs_available).toBe(true);
|
|
1552
|
+
// Essential also drops fields like `id`
|
|
1553
|
+
expect(first.id).toBeUndefined();
|
|
1464
1554
|
expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/deployments/applications/app-uuid', expect.any(Object));
|
|
1465
1555
|
});
|
|
1556
|
+
it('should return full deployments when includeLogs is true', async () => {
|
|
1557
|
+
const withLogs = { ...mockDeployment, logs: 'build log stream' };
|
|
1558
|
+
mockFetch.mockResolvedValueOnce(mockResponse({ count: 1, deployments: [withLogs] }));
|
|
1559
|
+
const result = await client.listApplicationDeployments('app-uuid', { includeLogs: true });
|
|
1560
|
+
expect(result).toEqual({ count: 1, deployments: [withLogs] });
|
|
1561
|
+
});
|
|
1562
|
+
it('should tolerate a malformed envelope (missing deployments array)', async () => {
|
|
1563
|
+
mockFetch.mockResolvedValueOnce(mockResponse({}));
|
|
1564
|
+
const result = await client.listApplicationDeployments('app-uuid');
|
|
1565
|
+
expect(result).toEqual({ count: 0, deployments: [] });
|
|
1566
|
+
});
|
|
1466
1567
|
});
|
|
1467
1568
|
// =========================================================================
|
|
1468
1569
|
// Team endpoints - extended coverage
|
|
@@ -1906,7 +2007,7 @@ describe('CoolifyClient', () => {
|
|
|
1906
2007
|
.mockResolvedValueOnce(mockResponse(mockApp))
|
|
1907
2008
|
.mockResolvedValueOnce(mockResponse(mockLogs))
|
|
1908
2009
|
.mockResolvedValueOnce(mockResponse(mockEnvVars))
|
|
1909
|
-
.mockResolvedValueOnce(mockResponse(mockDeployments));
|
|
2010
|
+
.mockResolvedValueOnce(mockResponse({ count: mockDeployments.length, deployments: mockDeployments }));
|
|
1910
2011
|
const result = await client.diagnoseApplication(testAppUuid);
|
|
1911
2012
|
expect(result.application).toEqual({
|
|
1912
2013
|
uuid: testAppUuid,
|
|
@@ -1932,7 +2033,7 @@ describe('CoolifyClient', () => {
|
|
|
1932
2033
|
.mockResolvedValueOnce(mockResponse(unhealthyApp))
|
|
1933
2034
|
.mockResolvedValueOnce(mockResponse(mockLogs))
|
|
1934
2035
|
.mockResolvedValueOnce(mockResponse(mockEnvVars))
|
|
1935
|
-
.mockResolvedValueOnce(mockResponse([]));
|
|
2036
|
+
.mockResolvedValueOnce(mockResponse({ count: 0, deployments: [] }));
|
|
1936
2037
|
const result = await client.diagnoseApplication(testAppUuid);
|
|
1937
2038
|
expect(result.health.status).toBe('unhealthy');
|
|
1938
2039
|
expect(result.health.issues).toContain('Status: exited:unhealthy');
|
|
@@ -1946,7 +2047,7 @@ describe('CoolifyClient', () => {
|
|
|
1946
2047
|
.mockResolvedValueOnce(mockResponse(mockApp))
|
|
1947
2048
|
.mockResolvedValueOnce(mockResponse(mockLogs))
|
|
1948
2049
|
.mockResolvedValueOnce(mockResponse(mockEnvVars))
|
|
1949
|
-
.mockResolvedValueOnce(mockResponse(failedDeployments));
|
|
2050
|
+
.mockResolvedValueOnce(mockResponse({ count: failedDeployments.length, deployments: failedDeployments }));
|
|
1950
2051
|
const result = await client.diagnoseApplication(testAppUuid);
|
|
1951
2052
|
expect(result.health.issues).toContain('2 failed deployment(s) in last 5');
|
|
1952
2053
|
});
|
|
@@ -1955,7 +2056,7 @@ describe('CoolifyClient', () => {
|
|
|
1955
2056
|
.mockResolvedValueOnce(mockResponse(mockApp))
|
|
1956
2057
|
.mockRejectedValueOnce(new Error('Logs unavailable'))
|
|
1957
2058
|
.mockResolvedValueOnce(mockResponse(mockEnvVars))
|
|
1958
|
-
.mockResolvedValueOnce(mockResponse(mockDeployments));
|
|
2059
|
+
.mockResolvedValueOnce(mockResponse({ count: mockDeployments.length, deployments: mockDeployments }));
|
|
1959
2060
|
const result = await client.diagnoseApplication(testAppUuid);
|
|
1960
2061
|
expect(result.application).not.toBeNull();
|
|
1961
2062
|
expect(result.logs).toBeNull();
|
|
@@ -163,7 +163,23 @@ export declare class CoolifyClient {
|
|
|
163
163
|
includeLogs?: boolean;
|
|
164
164
|
}): Promise<Deployment | DeploymentEssential>;
|
|
165
165
|
deployByTagOrUuid(tagOrUuid: string, force?: boolean): Promise<MessageResponse>;
|
|
166
|
-
|
|
166
|
+
/**
|
|
167
|
+
* List deployments for an application.
|
|
168
|
+
*
|
|
169
|
+
* Coolify returns `{ count, deployments: Deployment[] }` for this endpoint
|
|
170
|
+
* (NOT a raw array — upstream @masonator type was incorrect).
|
|
171
|
+
*
|
|
172
|
+
* By default returns a DeploymentEssential summary (no `logs` field) because
|
|
173
|
+
* each deployment's log blob can be 30–100KB, and a typical list has 20–35
|
|
174
|
+
* deployments — exceeding MCP response token limits. Pass `includeLogs: true`
|
|
175
|
+
* to get raw Deployment objects with full build logs.
|
|
176
|
+
*/
|
|
177
|
+
listApplicationDeployments(appUuid: string, options?: {
|
|
178
|
+
includeLogs?: boolean;
|
|
179
|
+
}): Promise<{
|
|
180
|
+
count: number;
|
|
181
|
+
deployments: Deployment[] | DeploymentEssential[];
|
|
182
|
+
}>;
|
|
167
183
|
listTeams(): Promise<Team[]>;
|
|
168
184
|
getTeam(id: number): Promise<Team>;
|
|
169
185
|
getTeamMembers(id: number): Promise<TeamMember[]>;
|
|
@@ -35,6 +35,12 @@ function toBase64(value) {
|
|
|
35
35
|
*/
|
|
36
36
|
function mapFqdnToDomains(data) {
|
|
37
37
|
const { fqdn, ...rest } = data;
|
|
38
|
+
// Explicit `domains` always wins. `fqdn` is only used when `domains` was
|
|
39
|
+
// not provided — kept for backward compatibility because `get_application`
|
|
40
|
+
// surfaces the field as `fqdn` in responses.
|
|
41
|
+
if (rest.domains !== undefined) {
|
|
42
|
+
return rest;
|
|
43
|
+
}
|
|
38
44
|
if (fqdn === undefined) {
|
|
39
45
|
return rest;
|
|
40
46
|
}
|
|
@@ -143,8 +149,10 @@ function toEnvVarSummary(envVar) {
|
|
|
143
149
|
* HTTP client for the Coolify API
|
|
144
150
|
*/
|
|
145
151
|
export class CoolifyClient {
|
|
152
|
+
baseUrl;
|
|
153
|
+
accessToken;
|
|
154
|
+
cachedVersion = null;
|
|
146
155
|
constructor(config) {
|
|
147
|
-
this.cachedVersion = null;
|
|
148
156
|
if (!config.baseUrl) {
|
|
149
157
|
throw new Error('Coolify base URL is required');
|
|
150
158
|
}
|
|
@@ -187,7 +195,7 @@ export class CoolifyClient {
|
|
|
187
195
|
}
|
|
188
196
|
catch (error) {
|
|
189
197
|
if (error instanceof TypeError && error.message.includes('fetch')) {
|
|
190
|
-
throw new Error(`Failed to connect to Coolify server at ${this.baseUrl}. Please check if the server is running and accessible
|
|
198
|
+
throw new Error(`Failed to connect to Coolify server at ${this.baseUrl}. Please check if the server is running and accessible.`, { cause: error });
|
|
191
199
|
}
|
|
192
200
|
throw error;
|
|
193
201
|
}
|
|
@@ -231,7 +239,7 @@ export class CoolifyClient {
|
|
|
231
239
|
await this.getVersion();
|
|
232
240
|
}
|
|
233
241
|
catch (error) {
|
|
234
|
-
throw new Error(`Failed to connect to Coolify server: ${error instanceof Error ? error.message : 'Unknown error'}
|
|
242
|
+
throw new Error(`Failed to connect to Coolify server: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
|
|
235
243
|
}
|
|
236
244
|
}
|
|
237
245
|
// ===========================================================================
|
|
@@ -386,17 +394,18 @@ export class CoolifyClient {
|
|
|
386
394
|
async createApplicationDockerfile(data) {
|
|
387
395
|
return this.request('/applications/dockerfile', {
|
|
388
396
|
method: 'POST',
|
|
389
|
-
body: JSON.stringify(data),
|
|
397
|
+
body: JSON.stringify(mapFqdnToDomains(data)),
|
|
390
398
|
});
|
|
391
399
|
}
|
|
392
400
|
async createApplicationDockerImage(data) {
|
|
393
401
|
return this.request('/applications/dockerimage', {
|
|
394
402
|
method: 'POST',
|
|
395
|
-
body: JSON.stringify(data),
|
|
403
|
+
body: JSON.stringify(mapFqdnToDomains(data)),
|
|
396
404
|
});
|
|
397
405
|
}
|
|
398
406
|
async createApplicationDockerCompose(data) {
|
|
399
|
-
const
|
|
407
|
+
const mapped = mapFqdnToDomains(data);
|
|
408
|
+
const payload = { ...mapped };
|
|
400
409
|
if (payload.docker_compose_raw) {
|
|
401
410
|
payload.docker_compose_raw = toBase64(payload.docker_compose_raw);
|
|
402
411
|
}
|
|
@@ -679,8 +688,24 @@ export class CoolifyClient {
|
|
|
679
688
|
const param = this.isLikelyUuid(tagOrUuid) ? 'uuid' : 'tag';
|
|
680
689
|
return this.request(`/deploy?${param}=${encodeURIComponent(tagOrUuid)}&force=${force}`, { method: 'GET' });
|
|
681
690
|
}
|
|
682
|
-
|
|
683
|
-
|
|
691
|
+
/**
|
|
692
|
+
* List deployments for an application.
|
|
693
|
+
*
|
|
694
|
+
* Coolify returns `{ count, deployments: Deployment[] }` for this endpoint
|
|
695
|
+
* (NOT a raw array — upstream @masonator type was incorrect).
|
|
696
|
+
*
|
|
697
|
+
* By default returns a DeploymentEssential summary (no `logs` field) because
|
|
698
|
+
* each deployment's log blob can be 30–100KB, and a typical list has 20–35
|
|
699
|
+
* deployments — exceeding MCP response token limits. Pass `includeLogs: true`
|
|
700
|
+
* to get raw Deployment objects with full build logs.
|
|
701
|
+
*/
|
|
702
|
+
async listApplicationDeployments(appUuid, options) {
|
|
703
|
+
const envelope = await this.request(`/deployments/applications/${appUuid}`);
|
|
704
|
+
const deployments = Array.isArray(envelope?.deployments) ? envelope.deployments : [];
|
|
705
|
+
return {
|
|
706
|
+
count: typeof envelope?.count === 'number' ? envelope.count : deployments.length,
|
|
707
|
+
deployments: options?.includeLogs ? deployments : deployments.map(toDeploymentEssential),
|
|
708
|
+
};
|
|
684
709
|
}
|
|
685
710
|
// ===========================================================================
|
|
686
711
|
// Team endpoints
|
|
@@ -932,7 +957,10 @@ export class CoolifyClient {
|
|
|
932
957
|
const app = extract(results[0], 'application');
|
|
933
958
|
const logs = extract(results[1], 'logs');
|
|
934
959
|
const envVars = extract(results[2], 'environment_variables');
|
|
935
|
-
|
|
960
|
+
// listApplicationDeployments now returns { count, deployments: [...] } —
|
|
961
|
+
// flatten back to the array that the diagnostics consumer expects.
|
|
962
|
+
const deploymentsEnvelope = extract(results[3], 'deployments');
|
|
963
|
+
const deployments = deploymentsEnvelope?.deployments ?? [];
|
|
936
964
|
// Determine health status and issues
|
|
937
965
|
const issues = [];
|
|
938
966
|
let healthStatus = 'unknown';
|
package/dist/lib/docs-search.js
CHANGED
|
@@ -7,11 +7,9 @@ const DOCS_BASE_URL = 'https://coolify.io';
|
|
|
7
7
|
* The LLM calling this tool handles semantic understanding — we just need good ranking.
|
|
8
8
|
*/
|
|
9
9
|
export class DocsSearchEngine {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
this.loading = null;
|
|
14
|
-
}
|
|
10
|
+
index = null;
|
|
11
|
+
chunks = [];
|
|
12
|
+
loading = null;
|
|
15
13
|
async ensureLoaded() {
|
|
16
14
|
if (this.index)
|
|
17
15
|
return;
|
|
@@ -23,7 +21,7 @@ export class DocsSearchEngine {
|
|
|
23
21
|
async loadAndIndex() {
|
|
24
22
|
try {
|
|
25
23
|
const controller = new AbortController();
|
|
26
|
-
const timeout = setTimeout(() => controller.abort(),
|
|
24
|
+
const timeout = setTimeout(() => controller.abort(), 15_000);
|
|
27
25
|
let response;
|
|
28
26
|
try {
|
|
29
27
|
response = await fetch(DOCS_FULL_URL, { signal: controller.signal });
|
package/dist/lib/mcp-server.js
CHANGED
|
@@ -153,9 +153,10 @@ function wrapWithActions(fn, getActions, getPaginationFn) {
|
|
|
153
153
|
}));
|
|
154
154
|
}
|
|
155
155
|
export class CoolifyMcpServer extends McpServer {
|
|
156
|
+
client;
|
|
157
|
+
docsSearch = new DocsSearchEngine();
|
|
156
158
|
constructor(config) {
|
|
157
159
|
super({ name: 'coolify', version: VERSION });
|
|
158
|
-
this.docsSearch = new DocsSearchEngine();
|
|
159
160
|
this.client = new CoolifyClient(config);
|
|
160
161
|
this.registerTools();
|
|
161
162
|
}
|
|
@@ -308,6 +309,7 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
308
309
|
server_uuid: z.string().optional(),
|
|
309
310
|
github_app_uuid: z.string().optional(),
|
|
310
311
|
private_key_uuid: z.string().optional(),
|
|
312
|
+
destination_uuid: z.string().optional(),
|
|
311
313
|
git_repository: z.string().optional(),
|
|
312
314
|
git_branch: z.string().optional(),
|
|
313
315
|
environment_name: z.string().optional(),
|
|
@@ -321,6 +323,10 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
321
323
|
name: z.string().optional(),
|
|
322
324
|
description: z.string().optional(),
|
|
323
325
|
fqdn: z.string().optional(),
|
|
326
|
+
domains: z.string().optional(),
|
|
327
|
+
custom_docker_run_options: z.string().optional(),
|
|
328
|
+
custom_labels: z.string().optional(),
|
|
329
|
+
instant_deploy: z.boolean().optional(),
|
|
324
330
|
// Health check fields
|
|
325
331
|
health_check_enabled: z.boolean().optional(),
|
|
326
332
|
health_check_path: z.string().optional(),
|
|
@@ -358,6 +364,7 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
358
364
|
return wrap(() => this.client.createApplicationPublic({
|
|
359
365
|
project_uuid: args.project_uuid,
|
|
360
366
|
server_uuid: args.server_uuid,
|
|
367
|
+
destination_uuid: args.destination_uuid,
|
|
361
368
|
git_repository: args.git_repository,
|
|
362
369
|
git_branch: args.git_branch,
|
|
363
370
|
build_pack: args.build_pack,
|
|
@@ -367,6 +374,10 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
367
374
|
name: args.name,
|
|
368
375
|
description: args.description,
|
|
369
376
|
fqdn: args.fqdn,
|
|
377
|
+
domains: args.domains,
|
|
378
|
+
custom_docker_run_options: args.custom_docker_run_options,
|
|
379
|
+
custom_labels: args.custom_labels,
|
|
380
|
+
instant_deploy: args.instant_deploy,
|
|
370
381
|
}));
|
|
371
382
|
case 'create_github':
|
|
372
383
|
if (!args.project_uuid ||
|
|
@@ -387,6 +398,7 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
387
398
|
project_uuid: args.project_uuid,
|
|
388
399
|
server_uuid: args.server_uuid,
|
|
389
400
|
github_app_uuid: args.github_app_uuid,
|
|
401
|
+
destination_uuid: args.destination_uuid,
|
|
390
402
|
git_repository: args.git_repository,
|
|
391
403
|
git_branch: args.git_branch,
|
|
392
404
|
build_pack: args.build_pack,
|
|
@@ -396,6 +408,10 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
396
408
|
name: args.name,
|
|
397
409
|
description: args.description,
|
|
398
410
|
fqdn: args.fqdn,
|
|
411
|
+
domains: args.domains,
|
|
412
|
+
custom_docker_run_options: args.custom_docker_run_options,
|
|
413
|
+
custom_labels: args.custom_labels,
|
|
414
|
+
instant_deploy: args.instant_deploy,
|
|
399
415
|
}));
|
|
400
416
|
case 'create_key':
|
|
401
417
|
if (!args.project_uuid ||
|
|
@@ -416,6 +432,7 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
416
432
|
project_uuid: args.project_uuid,
|
|
417
433
|
server_uuid: args.server_uuid,
|
|
418
434
|
private_key_uuid: args.private_key_uuid,
|
|
435
|
+
destination_uuid: args.destination_uuid,
|
|
419
436
|
git_repository: args.git_repository,
|
|
420
437
|
git_branch: args.git_branch,
|
|
421
438
|
build_pack: args.build_pack,
|
|
@@ -425,6 +442,10 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
425
442
|
name: args.name,
|
|
426
443
|
description: args.description,
|
|
427
444
|
fqdn: args.fqdn,
|
|
445
|
+
domains: args.domains,
|
|
446
|
+
custom_docker_run_options: args.custom_docker_run_options,
|
|
447
|
+
custom_labels: args.custom_labels,
|
|
448
|
+
instant_deploy: args.instant_deploy,
|
|
428
449
|
}));
|
|
429
450
|
case 'create_dockerimage':
|
|
430
451
|
if (!args.project_uuid ||
|
|
@@ -443,6 +464,7 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
443
464
|
return wrap(() => this.client.createApplicationDockerImage({
|
|
444
465
|
project_uuid: args.project_uuid,
|
|
445
466
|
server_uuid: args.server_uuid,
|
|
467
|
+
destination_uuid: args.destination_uuid,
|
|
446
468
|
docker_registry_image_name: args.docker_registry_image_name,
|
|
447
469
|
ports_exposes: args.ports_exposes,
|
|
448
470
|
docker_registry_image_tag: args.docker_registry_image_tag,
|
|
@@ -451,6 +473,10 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
451
473
|
name: args.name,
|
|
452
474
|
description: args.description,
|
|
453
475
|
fqdn: args.fqdn,
|
|
476
|
+
domains: args.domains,
|
|
477
|
+
custom_docker_run_options: args.custom_docker_run_options,
|
|
478
|
+
custom_labels: args.custom_labels,
|
|
479
|
+
instant_deploy: args.instant_deploy,
|
|
454
480
|
}));
|
|
455
481
|
case 'update': {
|
|
456
482
|
if (!uuid)
|
|
@@ -709,13 +735,14 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
709
735
|
// =========================================================================
|
|
710
736
|
this.tool('list_deployments', 'List deployments (summary)', { page: z.number().optional(), per_page: z.number().optional() }, async ({ page, per_page }) => wrapWithActions(() => this.client.listDeployments({ page, per_page, summary: true }), undefined, (result) => getPagination('list_deployments', page, per_page, result.length)));
|
|
711
737
|
this.tool('deploy', 'Deploy by tag/UUID', { tag_or_uuid: z.string(), force: z.boolean().optional() }, async ({ tag_or_uuid, force }) => wrapWithActions(() => this.client.deployByTagOrUuid(tag_or_uuid, force), () => [{ tool: 'list_deployments', args: {}, hint: 'Check deployment status' }]));
|
|
712
|
-
this.tool('deployment', 'Manage deployment: get/cancel/list_for_app
|
|
738
|
+
this.tool('deployment', 'Manage deployment: get/cancel/list_for_app. Logs excluded by default on all actions — for get use `lines` (paginated tail), for list_for_app use `include_logs: true` to include raw build-log blobs.', {
|
|
713
739
|
action: z.enum(['get', 'cancel', 'list_for_app']),
|
|
714
740
|
uuid: z.string(),
|
|
715
741
|
lines: z.number().optional(), // Include logs truncated to last N entries (omit for no logs)
|
|
716
742
|
page: z.number().optional(), // Log page (1=most recent, 2=older, etc.)
|
|
717
743
|
max_chars: z.number().optional(), // Limit log output to last N chars (default: 50000)
|
|
718
|
-
|
|
744
|
+
include_logs: z.boolean().optional(), // list_for_app only: include raw build logs (default false; upstream returns ~30KB per deployment)
|
|
745
|
+
}, async ({ action, uuid, lines, page, max_chars, include_logs }) => {
|
|
719
746
|
switch (action) {
|
|
720
747
|
case 'get':
|
|
721
748
|
// If lines param specified, include logs and truncate
|
|
@@ -760,7 +787,7 @@ export class CoolifyMcpServer extends McpServer {
|
|
|
760
787
|
case 'cancel':
|
|
761
788
|
return wrap(() => this.client.cancelDeployment(uuid));
|
|
762
789
|
case 'list_for_app':
|
|
763
|
-
return wrap(() => this.client.listApplicationDeployments(uuid));
|
|
790
|
+
return wrap(() => this.client.listApplicationDeployments(uuid, { includeLogs: include_logs }));
|
|
764
791
|
}
|
|
765
792
|
});
|
|
766
793
|
// =========================================================================
|
package/dist/types/coolify.d.ts
CHANGED
|
@@ -211,6 +211,7 @@ export interface CreateApplicationPublicRequest {
|
|
|
211
211
|
name?: string;
|
|
212
212
|
description?: string;
|
|
213
213
|
fqdn?: string;
|
|
214
|
+
domains?: string;
|
|
214
215
|
git_repository: string;
|
|
215
216
|
git_branch: string;
|
|
216
217
|
git_commit_sha?: string;
|
|
@@ -222,6 +223,8 @@ export interface CreateApplicationPublicRequest {
|
|
|
222
223
|
install_command?: string;
|
|
223
224
|
build_command?: string;
|
|
224
225
|
start_command?: string;
|
|
226
|
+
custom_docker_run_options?: string;
|
|
227
|
+
custom_labels?: string;
|
|
225
228
|
instant_deploy?: boolean;
|
|
226
229
|
}
|
|
227
230
|
export interface CreateApplicationPrivateGHRequest extends Omit<CreateApplicationPublicRequest, 'build_pack' | 'ports_exposes'> {
|
|
@@ -243,11 +246,14 @@ export interface CreateApplicationDockerfileRequest {
|
|
|
243
246
|
name?: string;
|
|
244
247
|
description?: string;
|
|
245
248
|
fqdn?: string;
|
|
249
|
+
domains?: string;
|
|
246
250
|
dockerfile: string;
|
|
247
251
|
dockerfile_location?: string;
|
|
248
252
|
ports_exposes?: string;
|
|
249
253
|
ports_mappings?: string;
|
|
250
254
|
base_directory?: string;
|
|
255
|
+
custom_docker_run_options?: string;
|
|
256
|
+
custom_labels?: string;
|
|
251
257
|
instant_deploy?: boolean;
|
|
252
258
|
}
|
|
253
259
|
export interface CreateApplicationDockerImageRequest {
|
|
@@ -259,10 +265,13 @@ export interface CreateApplicationDockerImageRequest {
|
|
|
259
265
|
name?: string;
|
|
260
266
|
description?: string;
|
|
261
267
|
fqdn?: string;
|
|
268
|
+
domains?: string;
|
|
262
269
|
docker_registry_image_name: string;
|
|
263
270
|
docker_registry_image_tag?: string;
|
|
264
271
|
ports_exposes: string;
|
|
265
272
|
ports_mappings?: string;
|
|
273
|
+
custom_docker_run_options?: string;
|
|
274
|
+
custom_labels?: string;
|
|
266
275
|
instant_deploy?: boolean;
|
|
267
276
|
}
|
|
268
277
|
export interface CreateApplicationDockerComposeRequest {
|
|
@@ -273,16 +282,23 @@ export interface CreateApplicationDockerComposeRequest {
|
|
|
273
282
|
destination_uuid?: string;
|
|
274
283
|
name?: string;
|
|
275
284
|
description?: string;
|
|
285
|
+
fqdn?: string;
|
|
286
|
+
domains?: string;
|
|
276
287
|
docker_compose_raw: string;
|
|
277
288
|
docker_compose_location?: string;
|
|
278
289
|
docker_compose_custom_start_command?: string;
|
|
279
290
|
docker_compose_custom_build_command?: string;
|
|
291
|
+
custom_docker_run_options?: string;
|
|
292
|
+
custom_labels?: string;
|
|
280
293
|
instant_deploy?: boolean;
|
|
281
294
|
}
|
|
282
295
|
export interface UpdateApplicationRequest {
|
|
283
296
|
name?: string;
|
|
284
297
|
description?: string;
|
|
285
298
|
fqdn?: string;
|
|
299
|
+
domains?: string;
|
|
300
|
+
custom_docker_run_options?: string;
|
|
301
|
+
custom_labels?: string;
|
|
286
302
|
git_repository?: string;
|
|
287
303
|
git_branch?: string;
|
|
288
304
|
git_commit_sha?: string;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@masonator/coolify-mcp",
|
|
3
3
|
"scope": "@masonator",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.8.0",
|
|
5
5
|
"mcpName": "io.github.StuMason/coolify",
|
|
6
6
|
"description": "MCP server for Coolify — 38 optimized tools for infrastructure management, diagnostics, and documentation search",
|
|
7
7
|
"type": "module",
|
|
@@ -37,22 +37,35 @@
|
|
|
37
37
|
"keywords": [
|
|
38
38
|
"coolify",
|
|
39
39
|
"mcp",
|
|
40
|
-
"model-context-protocol"
|
|
40
|
+
"model-context-protocol",
|
|
41
|
+
"infrastructure",
|
|
42
|
+
"deployment",
|
|
43
|
+
"self-hosted",
|
|
44
|
+
"paas",
|
|
45
|
+
"ai",
|
|
46
|
+
"devops"
|
|
41
47
|
],
|
|
42
|
-
"author":
|
|
48
|
+
"author": {
|
|
49
|
+
"name": "Stuart Mason",
|
|
50
|
+
"url": "https://stumason.dev"
|
|
51
|
+
},
|
|
43
52
|
"license": "MIT",
|
|
53
|
+
"homepage": "https://stumason.dev",
|
|
44
54
|
"repository": {
|
|
45
55
|
"type": "git",
|
|
46
56
|
"url": "https://github.com/StuMason/coolify-mcp.git"
|
|
47
57
|
},
|
|
58
|
+
"bugs": {
|
|
59
|
+
"url": "https://github.com/StuMason/coolify-mcp/issues"
|
|
60
|
+
},
|
|
48
61
|
"dependencies": {
|
|
49
62
|
"@modelcontextprotocol/sdk": "^1.23.0",
|
|
50
63
|
"minisearch": "^7.2.0",
|
|
51
64
|
"zod": "^4.3.5"
|
|
52
65
|
},
|
|
53
66
|
"devDependencies": {
|
|
54
|
-
"@eslint/js": "^
|
|
55
|
-
"@types/jest": "^
|
|
67
|
+
"@eslint/js": "^10.0.1",
|
|
68
|
+
"@types/jest": "^30.0.0",
|
|
56
69
|
"@types/node": "^25.0.3",
|
|
57
70
|
"@typescript-eslint/eslint-plugin": "^8.51.0",
|
|
58
71
|
"@typescript-eslint/parser": "^8.51.0",
|
|
@@ -61,10 +74,10 @@
|
|
|
61
74
|
"eslint-config-prettier": "^10.1.8",
|
|
62
75
|
"globals": "^17.0.0",
|
|
63
76
|
"husky": "^9.0.11",
|
|
64
|
-
"jest": "^
|
|
77
|
+
"jest": "^30.3.0",
|
|
65
78
|
"jest-junit": "^16.0.0",
|
|
66
79
|
"lint-staged": "^16.2.7",
|
|
67
|
-
"markdownlint-cli2": "^0.
|
|
80
|
+
"markdownlint-cli2": "^0.22.0",
|
|
68
81
|
"prettier": "^3.5.3",
|
|
69
82
|
"shx": "^0.4.0",
|
|
70
83
|
"ts-jest": "^29.2.6",
|
|
@@ -72,7 +85,10 @@
|
|
|
72
85
|
"typescript-eslint": "^8.51.0"
|
|
73
86
|
},
|
|
74
87
|
"engines": {
|
|
75
|
-
"node": ">=
|
|
88
|
+
"node": ">=20"
|
|
89
|
+
},
|
|
90
|
+
"overrides": {
|
|
91
|
+
"handlebars": "^4.7.9"
|
|
76
92
|
},
|
|
77
93
|
"lint-staged": {
|
|
78
94
|
"*.{ts,js,json,md,yaml,yml}": "prettier --write",
|