@fndchagas/coolify-mcp 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Fernando Chagas
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,174 @@
1
+ # coolify-mcp
2
+
3
+ MCP server for Coolify, pinned to a specific Coolify OpenAPI version.
4
+
5
+ ## Pinned Coolify version
6
+
7
+ This repo is pinned to:
8
+
9
+ - `v4.0.0-beta.460`
10
+ - OpenAPI file: `openapi/coolify/v4.0.0-beta.460.json`
11
+
12
+ ## Requirements
13
+
14
+ - Node 18+
15
+ - A Coolify API token
16
+
17
+ ## Install (package)
18
+
19
+ ```bash
20
+ npm install -g @fndchagas/coolify-mcp
21
+ # or
22
+ npx -y @fndchagas/coolify-mcp
23
+ ```
24
+
25
+ ## Use with Claude Code CLI (stdio)
26
+
27
+ ```bash
28
+ claude mcp add coolify \
29
+ --env COOLIFY_BASE_URL="https://dashboard.coolify.fortetecnologias.com.br" \
30
+ --env COOLIFY_TOKEN="<token>" \
31
+ -- npx -y @fndchagas/coolify-mcp
32
+ ```
33
+
34
+ Optional: disable write tools (deploy/upsert) by adding:
35
+
36
+ ```bash
37
+ --env COOLIFY_ALLOW_WRITE=false
38
+ ```
39
+
40
+ ## Use with OpenAI Codex CLI (stdio)
41
+
42
+ ```bash
43
+ codex mcp add coolify \
44
+ --env COOLIFY_BASE_URL="https://dashboard.coolify.fortetecnologias.com.br" \
45
+ --env COOLIFY_TOKEN="<token>" \
46
+ -- npx -y @fndchagas/coolify-mcp
47
+ ```
48
+
49
+ Or edit `~/.codex/config.toml`:
50
+
51
+ ```toml
52
+ [mcp_servers.coolify]
53
+ command = "npx"
54
+ args = ["-y", "@fndchagas/coolify-mcp"]
55
+ env = { COOLIFY_BASE_URL = "https://dashboard.coolify.fortetecnologias.com.br", COOLIFY_TOKEN = "<token>" }
56
+ ```
57
+
58
+ ## Install (dev)
59
+
60
+ ```bash
61
+ npm install
62
+ ```
63
+
64
+ ## Generate types (if OpenAPI changes)
65
+
66
+ ```bash
67
+ npm run generate:openapi
68
+ ```
69
+
70
+ ## Run (stdio)
71
+
72
+ ```bash
73
+ COOLIFY_BASE_URL="https://dashboard.coolify.fortetecnologias.com.br" \
74
+ COOLIFY_TOKEN="<token>" \
75
+ MCP_TRANSPORT=stdio \
76
+ npm run dev
77
+ ```
78
+
79
+ ## Run (HTTP)
80
+
81
+ ```bash
82
+ COOLIFY_BASE_URL="https://dashboard.coolify.fortetecnologias.com.br" \
83
+ COOLIFY_TOKEN="<token>" \
84
+ MCP_TRANSPORT=http \
85
+ PORT=7331 \
86
+ npm run dev
87
+ ```
88
+
89
+ Endpoint: `POST http://localhost:7331/mcp`
90
+
91
+ ## Run (both)
92
+
93
+ ```bash
94
+ MCP_TRANSPORT=both npm run dev
95
+ ```
96
+
97
+ ## Environment variables
98
+
99
+ - `COOLIFY_BASE_URL` (required)
100
+ - `COOLIFY_TOKEN` (required)
101
+ - `COOLIFY_OPENAPI_REF` (default: `v4.0.0-beta.460`)
102
+ - `COOLIFY_STRICT_VERSION` (default: `false`)
103
+ - `COOLIFY_ALLOW_WRITE` (default: `true`)
104
+ - `MCP_TRANSPORT` (`stdio`, `http`, `both`)
105
+ - `PORT` (HTTP port, default `7331`)
106
+
107
+ ## Tools
108
+
109
+ - `coolify.listResources`
110
+ - `coolify.getApplication`
111
+ - `coolify.listEnvs`
112
+ - `coolify.upsertEnv`
113
+ - `coolify.deploy`
114
+ - `coolify.getDeployment`
115
+ - `coolify.getLogs`
116
+ - `coolify.listDatabases`
117
+ - `coolify.getDatabase`
118
+
119
+ ## MCP usage examples
120
+
121
+ ### HTTP client (Streamable HTTP)
122
+
123
+ ```ts
124
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
125
+ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
126
+
127
+ const client = new Client({ name: 'coolify-client', version: '1.0.0' });
128
+ const transport = new StreamableHTTPClientTransport(
129
+ new URL('http://localhost:7331/mcp')
130
+ );
131
+
132
+ await client.connect(transport);
133
+
134
+ const tools = await client.listTools();
135
+ console.log(tools.tools.map((t) => t.name));
136
+
137
+ const resources = await client.callTool({
138
+ name: 'coolify.listResources',
139
+ arguments: {},
140
+ });
141
+ console.log(resources.structuredContent);
142
+
143
+ const logs = await client.callTool({
144
+ name: 'coolify.getLogs',
145
+ arguments: { appUuid: 'nwggo800g800oosow8ks4c88' },
146
+ });
147
+ console.log(logs.structuredContent);
148
+
149
+ await client.close();
150
+ ```
151
+
152
+ ### Stdio client (spawn server)
153
+
154
+ ```ts
155
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
156
+ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
157
+
158
+ // Ensure COOLIFY_BASE_URL and COOLIFY_TOKEN are set in the environment
159
+ const client = new Client({ name: 'coolify-client', version: '1.0.0' });
160
+ const transport = new StdioClientTransport({
161
+ command: 'node',
162
+ args: ['dist/server.js'],
163
+ });
164
+
165
+ await client.connect(transport);
166
+
167
+ const result = await client.callTool({
168
+ name: 'coolify.getApplication',
169
+ arguments: { uuid: 'nwggo800g800oosow8ks4c88' },
170
+ });
171
+ console.log(result.structuredContent);
172
+
173
+ await client.close();
174
+ ```
package/dist/config.js ADDED
@@ -0,0 +1,7 @@
1
+ export const COOLIFY_BASE_URL = process.env.COOLIFY_BASE_URL;
2
+ export const COOLIFY_TOKEN = process.env.COOLIFY_TOKEN;
3
+ export const COOLIFY_OPENAPI_REF = process.env.COOLIFY_OPENAPI_REF ?? 'v4.0.0-beta.460';
4
+ export const MCP_TRANSPORT = process.env.MCP_TRANSPORT ?? 'stdio';
5
+ export const MCP_HTTP_PORT = Number(process.env.PORT ?? '7331');
6
+ export const COOLIFY_STRICT_VERSION = process.env.COOLIFY_STRICT_VERSION === 'true';
7
+ export const COOLIFY_ALLOW_WRITE = process.env.COOLIFY_ALLOW_WRITE !== 'false';
@@ -0,0 +1,53 @@
1
+ import { COOLIFY_BASE_URL, COOLIFY_TOKEN } from '../config.js';
2
+ function requireEnv(value, name) {
3
+ if (!value) {
4
+ throw new Error(`${name} is required`);
5
+ }
6
+ return value;
7
+ }
8
+ function buildUrl(path, query) {
9
+ const base = requireEnv(COOLIFY_BASE_URL, 'COOLIFY_BASE_URL');
10
+ const url = new URL(path, base);
11
+ if (query) {
12
+ for (const [key, value] of Object.entries(query)) {
13
+ if (value !== undefined) {
14
+ url.searchParams.set(key, value);
15
+ }
16
+ }
17
+ }
18
+ return url.toString();
19
+ }
20
+ export async function request(method, path, options) {
21
+ const token = requireEnv(COOLIFY_TOKEN, 'COOLIFY_TOKEN');
22
+ const url = buildUrl(path, options?.query);
23
+ const headers = {
24
+ Authorization: `Bearer ${token}`,
25
+ Accept: 'application/json',
26
+ };
27
+ let body;
28
+ if (options?.body !== undefined) {
29
+ headers['Content-Type'] = 'application/json';
30
+ body = JSON.stringify(options.body);
31
+ }
32
+ const response = await fetch(url, { method, headers, body });
33
+ const contentType = response.headers.get('content-type') ?? '';
34
+ if (!response.ok) {
35
+ const text = await response.text();
36
+ throw new Error(`Coolify API error ${response.status}: ${text || response.statusText}`);
37
+ }
38
+ if (contentType.includes('application/json')) {
39
+ return (await response.json());
40
+ }
41
+ return (await response.text());
42
+ }
43
+ export async function getVersion() {
44
+ const data = await request('GET', '/api/v1/version');
45
+ if (typeof data === 'string') {
46
+ return data.trim();
47
+ }
48
+ if (data && typeof data === 'object' && 'version' in data) {
49
+ const version = data.version;
50
+ return version ?? 'unknown';
51
+ }
52
+ return 'unknown';
53
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * This file was auto-generated by openapi-typescript.
3
+ * Do not make direct changes to the file.
4
+ */
5
+ export {};
package/dist/server.js ADDED
@@ -0,0 +1,66 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
4
+ import express from 'express';
5
+ import { registerCoolifyTools } from './tools/coolify.js';
6
+ import { COOLIFY_OPENAPI_REF, COOLIFY_STRICT_VERSION, MCP_HTTP_PORT, MCP_TRANSPORT, } from './config.js';
7
+ import { getVersion } from './coolify/client.js';
8
+ const server = new McpServer({
9
+ name: 'coolify-mcp',
10
+ version: '0.1.0',
11
+ });
12
+ registerCoolifyTools(server);
13
+ function normalizeVersion(value) {
14
+ return value.replace(/^v/i, '');
15
+ }
16
+ async function checkVersion() {
17
+ try {
18
+ const current = await getVersion();
19
+ if (normalizeVersion(current) !== normalizeVersion(COOLIFY_OPENAPI_REF)) {
20
+ const message = `Coolify version mismatch. Server=${current}, OpenAPI=${COOLIFY_OPENAPI_REF}.`;
21
+ if (COOLIFY_STRICT_VERSION) {
22
+ throw new Error(message);
23
+ }
24
+ console.warn(message);
25
+ }
26
+ }
27
+ catch (error) {
28
+ if (COOLIFY_STRICT_VERSION) {
29
+ throw error;
30
+ }
31
+ console.warn('Version check failed:', error instanceof Error ? error.message : error);
32
+ }
33
+ }
34
+ async function startStdio() {
35
+ const transport = new StdioServerTransport();
36
+ await server.connect(transport);
37
+ }
38
+ async function startHttp() {
39
+ const app = express();
40
+ app.use(express.json());
41
+ app.post('/mcp', async (req, res) => {
42
+ const transport = new StreamableHTTPServerTransport({
43
+ sessionIdGenerator: undefined,
44
+ enableJsonResponse: true,
45
+ });
46
+ res.on('close', () => transport.close());
47
+ await server.connect(transport);
48
+ await transport.handleRequest(req, res, req.body);
49
+ });
50
+ app.listen(MCP_HTTP_PORT, () => {
51
+ console.log(`MCP HTTP server listening on :${MCP_HTTP_PORT}/mcp`);
52
+ });
53
+ }
54
+ await checkVersion();
55
+ if (MCP_TRANSPORT === 'stdio') {
56
+ await startStdio();
57
+ }
58
+ else if (MCP_TRANSPORT === 'http') {
59
+ await startHttp();
60
+ }
61
+ else if (MCP_TRANSPORT === 'both') {
62
+ await Promise.all([startStdio(), startHttp()]);
63
+ }
64
+ else {
65
+ throw new Error(`Unknown MCP_TRANSPORT: ${MCP_TRANSPORT}`);
66
+ }
@@ -0,0 +1,130 @@
1
+ import * as z from 'zod';
2
+ import { request } from '../coolify/client.js';
3
+ import { COOLIFY_ALLOW_WRITE } from '../config.js';
4
+ function ensureWriteAllowed() {
5
+ if (!COOLIFY_ALLOW_WRITE) {
6
+ throw new Error('Write operations are disabled (COOLIFY_ALLOW_WRITE=false).');
7
+ }
8
+ }
9
+ export function registerCoolifyTools(server) {
10
+ server.registerTool('coolify.listResources', {
11
+ title: 'List resources',
12
+ description: 'List Coolify resources (apps, databases, etc).',
13
+ inputSchema: {},
14
+ outputSchema: { resources: z.array(z.unknown()) },
15
+ }, async () => {
16
+ const data = await request('GET', '/api/v1/resources');
17
+ return {
18
+ content: [{ type: 'text', text: 'Resources fetched.' }],
19
+ structuredContent: { resources: data },
20
+ };
21
+ });
22
+ server.registerTool('coolify.getApplication', {
23
+ title: 'Get application',
24
+ description: 'Get application details by UUID.',
25
+ inputSchema: { uuid: z.string() },
26
+ outputSchema: { application: z.unknown() },
27
+ }, async ({ uuid }) => {
28
+ const data = await request('GET', `/api/v1/applications/${uuid}`);
29
+ return {
30
+ content: [{ type: 'text', text: `Application ${uuid} fetched.` }],
31
+ structuredContent: { application: data },
32
+ };
33
+ });
34
+ server.registerTool('coolify.listEnvs', {
35
+ title: 'List application env vars',
36
+ description: 'List environment variables for an application.',
37
+ inputSchema: { appUuid: z.string() },
38
+ outputSchema: { envs: z.array(z.unknown()) },
39
+ }, async ({ appUuid }) => {
40
+ const data = await request('GET', `/api/v1/applications/${appUuid}/envs`);
41
+ return {
42
+ content: [{ type: 'text', text: `Env vars for ${appUuid} fetched.` }],
43
+ structuredContent: { envs: data },
44
+ };
45
+ });
46
+ server.registerTool('coolify.upsertEnv', {
47
+ title: 'Upsert environment variable',
48
+ description: 'Upsert an environment variable for an application.',
49
+ inputSchema: {
50
+ appUuid: z.string(),
51
+ key: z.string(),
52
+ value: z.string(),
53
+ is_buildtime: z.boolean().optional(),
54
+ is_runtime: z.boolean().optional(),
55
+ },
56
+ outputSchema: { env: z.unknown() },
57
+ }, async ({ appUuid, key, value, is_buildtime = true, is_runtime = true }) => {
58
+ ensureWriteAllowed();
59
+ const data = await request('PATCH', `/api/v1/applications/${appUuid}/envs`, {
60
+ body: { key, value, is_buildtime, is_runtime },
61
+ });
62
+ return {
63
+ content: [{ type: 'text', text: `Env ${key} upserted for ${appUuid}.` }],
64
+ structuredContent: { env: data },
65
+ };
66
+ });
67
+ server.registerTool('coolify.deploy', {
68
+ title: 'Trigger deploy',
69
+ description: 'Trigger a deployment for an application.',
70
+ inputSchema: { appUuid: z.string(), force: z.boolean().optional() },
71
+ outputSchema: { deployment: z.unknown() },
72
+ }, async ({ appUuid, force = true }) => {
73
+ ensureWriteAllowed();
74
+ const data = await request('POST', '/api/v1/deploy', {
75
+ query: { uuid: appUuid, force: String(force) },
76
+ });
77
+ return {
78
+ content: [{ type: 'text', text: `Deploy triggered for ${appUuid}.` }],
79
+ structuredContent: { deployment: data },
80
+ };
81
+ });
82
+ server.registerTool('coolify.getDeployment', {
83
+ title: 'Get deployment',
84
+ description: 'Get deployment status by UUID.',
85
+ inputSchema: { deploymentUuid: z.string() },
86
+ outputSchema: { deployment: z.unknown() },
87
+ }, async ({ deploymentUuid }) => {
88
+ const data = await request('GET', `/api/v1/deployments/${deploymentUuid}`);
89
+ return {
90
+ content: [{ type: 'text', text: `Deployment ${deploymentUuid} fetched.` }],
91
+ structuredContent: { deployment: data },
92
+ };
93
+ });
94
+ server.registerTool('coolify.getLogs', {
95
+ title: 'Get application logs',
96
+ description: 'Fetch runtime logs for an application.',
97
+ inputSchema: { appUuid: z.string() },
98
+ outputSchema: { logs: z.string() },
99
+ }, async ({ appUuid }) => {
100
+ const data = await request('GET', `/api/v1/applications/${appUuid}/logs`);
101
+ return {
102
+ content: [{ type: 'text', text: 'Logs fetched.' }],
103
+ structuredContent: { logs: data.logs ?? '' },
104
+ };
105
+ });
106
+ server.registerTool('coolify.listDatabases', {
107
+ title: 'List databases',
108
+ description: 'List Coolify databases.',
109
+ inputSchema: {},
110
+ outputSchema: { databases: z.array(z.unknown()) },
111
+ }, async () => {
112
+ const data = await request('GET', '/api/v1/databases');
113
+ return {
114
+ content: [{ type: 'text', text: 'Databases fetched.' }],
115
+ structuredContent: { databases: data },
116
+ };
117
+ });
118
+ server.registerTool('coolify.getDatabase', {
119
+ title: 'Get database',
120
+ description: 'Get database details by UUID.',
121
+ inputSchema: { uuid: z.string() },
122
+ outputSchema: { database: z.unknown() },
123
+ }, async ({ uuid }) => {
124
+ const data = await request('GET', `/api/v1/databases/${uuid}`);
125
+ return {
126
+ content: [{ type: 'text', text: `Database ${uuid} fetched.` }],
127
+ structuredContent: { database: data },
128
+ };
129
+ });
130
+ }