@softtor/coolify-mcp-server 1.0.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 +21 -0
- package/README.md +232 -0
- package/dist/config.d.ts +12 -0
- package/dist/config.js +55 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +127 -0
- package/dist/schemas/common.d.ts +51 -0
- package/dist/schemas/common.js +15 -0
- package/dist/services/coolify-client.d.ts +12 -0
- package/dist/services/coolify-client.js +48 -0
- package/dist/services/error-handler.d.ts +7 -0
- package/dist/services/error-handler.js +59 -0
- package/dist/tools/applications.d.ts +119 -0
- package/dist/tools/applications.js +80 -0
- package/dist/tools/databases.d.ts +96 -0
- package/dist/tools/databases.js +78 -0
- package/dist/tools/deployments.d.ts +100 -0
- package/dist/tools/deployments.js +50 -0
- package/dist/tools/projects.d.ts +81 -0
- package/dist/tools/projects.js +54 -0
- package/dist/tools/servers.d.ts +72 -0
- package/dist/tools/servers.js +52 -0
- package/dist/tools/services.d.ts +84 -0
- package/dist/tools/services.js +65 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Softtor
|
|
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,232 @@
|
|
|
1
|
+
# Coolify MCP Server
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@softtor/coolify-mcp-server)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
MCP (Model Context Protocol) server for [Coolify](https://coolify.io) API integration. Manage your applications, databases, services, servers, and deployments directly from Claude Code or any MCP-compatible client.
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- Multi-team support with dynamic API key configuration
|
|
11
|
+
- 27 tools covering all major Coolify operations
|
|
12
|
+
- Full TypeScript support
|
|
13
|
+
- Compatible with Claude Code and other MCP clients
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
### Using npx (Recommended)
|
|
18
|
+
|
|
19
|
+
No installation required. Configure directly in your MCP settings:
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"mcpServers": {
|
|
24
|
+
"coolify": {
|
|
25
|
+
"command": "npx",
|
|
26
|
+
"args": ["-y", "@softtor/coolify-mcp-server"],
|
|
27
|
+
"env": {
|
|
28
|
+
"COOLIFY_BASE_URL": "https://your-coolify-instance.com",
|
|
29
|
+
"COOLIFY_API_KEY": "your-api-key"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Global Installation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm install -g @softtor/coolify-mcp-server
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Then configure in your MCP settings:
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"mcpServers": {
|
|
47
|
+
"coolify": {
|
|
48
|
+
"command": "coolify-mcp-server",
|
|
49
|
+
"env": {
|
|
50
|
+
"COOLIFY_BASE_URL": "https://your-coolify-instance.com",
|
|
51
|
+
"COOLIFY_API_KEY": "your-api-key"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Configuration
|
|
59
|
+
|
|
60
|
+
### Environment Variables
|
|
61
|
+
|
|
62
|
+
| Variable | Required | Description |
|
|
63
|
+
|----------|----------|-------------|
|
|
64
|
+
| `COOLIFY_BASE_URL` | No | Coolify instance URL (default: `https://cloud.softtor.com.br`) |
|
|
65
|
+
| `COOLIFY_API_KEY` | No* | Default API key |
|
|
66
|
+
| `COOLIFY_DEFAULT_TEAM` | No | Default team name (default: `default`) |
|
|
67
|
+
| `COOLIFY_TEAM_<NAME>_API_KEY` | No* | Team-specific API key |
|
|
68
|
+
|
|
69
|
+
*At least one API key must be provided.
|
|
70
|
+
|
|
71
|
+
### Multi-Team Configuration
|
|
72
|
+
|
|
73
|
+
For organizations with multiple Coolify teams, configure team-specific API keys:
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"mcpServers": {
|
|
78
|
+
"coolify": {
|
|
79
|
+
"command": "npx",
|
|
80
|
+
"args": ["-y", "@softtor/coolify-mcp-server"],
|
|
81
|
+
"env": {
|
|
82
|
+
"COOLIFY_BASE_URL": "https://your-coolify-instance.com",
|
|
83
|
+
"COOLIFY_DEFAULT_TEAM": "production",
|
|
84
|
+
"COOLIFY_TEAM_PRODUCTION_API_KEY": "prod-api-key",
|
|
85
|
+
"COOLIFY_TEAM_STAGING_API_KEY": "staging-api-key",
|
|
86
|
+
"COOLIFY_TEAM_DEV_API_KEY": "dev-api-key"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Then use the `team` parameter in any tool:
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
coolify_list_applications { team: "staging" }
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Getting Your API Key
|
|
100
|
+
|
|
101
|
+
1. Log in to your Coolify instance
|
|
102
|
+
2. Go to **Settings** > **API Tokens**
|
|
103
|
+
3. Create a new token with appropriate permissions
|
|
104
|
+
4. Copy the token and use it as your API key
|
|
105
|
+
|
|
106
|
+
## Available Tools
|
|
107
|
+
|
|
108
|
+
### Applications (6 tools)
|
|
109
|
+
|
|
110
|
+
| Tool | Description |
|
|
111
|
+
|------|-------------|
|
|
112
|
+
| `coolify_list_applications` | List all applications |
|
|
113
|
+
| `coolify_get_application` | Get application details by UUID |
|
|
114
|
+
| `coolify_start_application` | Start/deploy an application |
|
|
115
|
+
| `coolify_stop_application` | Stop a running application |
|
|
116
|
+
| `coolify_restart_application` | Restart an application |
|
|
117
|
+
| `coolify_get_application_logs` | Get container logs |
|
|
118
|
+
|
|
119
|
+
### Databases (6 tools)
|
|
120
|
+
|
|
121
|
+
| Tool | Description |
|
|
122
|
+
|------|-------------|
|
|
123
|
+
| `coolify_list_databases` | List all databases |
|
|
124
|
+
| `coolify_get_database` | Get database details by UUID |
|
|
125
|
+
| `coolify_start_database` | Start a database |
|
|
126
|
+
| `coolify_stop_database` | Stop a running database |
|
|
127
|
+
| `coolify_restart_database` | Restart a database |
|
|
128
|
+
| `coolify_list_database_backups` | List database backups |
|
|
129
|
+
|
|
130
|
+
### Services (5 tools)
|
|
131
|
+
|
|
132
|
+
| Tool | Description |
|
|
133
|
+
|------|-------------|
|
|
134
|
+
| `coolify_list_services` | List all services |
|
|
135
|
+
| `coolify_get_service` | Get service details by UUID |
|
|
136
|
+
| `coolify_start_service` | Start a service |
|
|
137
|
+
| `coolify_stop_service` | Stop a running service |
|
|
138
|
+
| `coolify_restart_service` | Restart a service |
|
|
139
|
+
|
|
140
|
+
### Servers (4 tools)
|
|
141
|
+
|
|
142
|
+
| Tool | Description |
|
|
143
|
+
|------|-------------|
|
|
144
|
+
| `coolify_list_servers` | List all servers |
|
|
145
|
+
| `coolify_get_server` | Get server details by UUID |
|
|
146
|
+
| `coolify_get_server_resources` | Get all resources on a server |
|
|
147
|
+
| `coolify_get_server_domains` | Get all domains mapped on a server |
|
|
148
|
+
|
|
149
|
+
### Deployments (2 tools)
|
|
150
|
+
|
|
151
|
+
| Tool | Description |
|
|
152
|
+
|------|-------------|
|
|
153
|
+
| `coolify_deploy` | Deploy by UUID or tag |
|
|
154
|
+
| `coolify_list_deployments` | List deployment history |
|
|
155
|
+
|
|
156
|
+
### Projects & Teams (4 tools)
|
|
157
|
+
|
|
158
|
+
| Tool | Description |
|
|
159
|
+
|------|-------------|
|
|
160
|
+
| `coolify_list_projects` | List all projects |
|
|
161
|
+
| `coolify_get_project` | Get project details by UUID |
|
|
162
|
+
| `coolify_list_teams` | List all accessible teams |
|
|
163
|
+
| `coolify_get_team` | Get team details by ID |
|
|
164
|
+
|
|
165
|
+
## Usage Examples
|
|
166
|
+
|
|
167
|
+
### List all applications
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
coolify_list_applications
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Deploy an application
|
|
174
|
+
|
|
175
|
+
```
|
|
176
|
+
coolify_deploy { uuid: "app-uuid-here" }
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Deploy with force rebuild
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
coolify_deploy { uuid: "app-uuid-here", force: true }
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Deploy all applications with a tag
|
|
186
|
+
|
|
187
|
+
```
|
|
188
|
+
coolify_deploy { tag: "production" }
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Get application logs
|
|
192
|
+
|
|
193
|
+
```
|
|
194
|
+
coolify_get_application_logs { uuid: "app-uuid-here", since: 3600 }
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Use a specific team
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
coolify_list_applications { team: "staging" }
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Development
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
# Clone the repository
|
|
207
|
+
git clone https://github.com/Softtor/coolify-mcp-server.git
|
|
208
|
+
cd coolify-mcp-server
|
|
209
|
+
|
|
210
|
+
# Install dependencies
|
|
211
|
+
npm install
|
|
212
|
+
|
|
213
|
+
# Build
|
|
214
|
+
npm run build
|
|
215
|
+
|
|
216
|
+
# Run locally
|
|
217
|
+
COOLIFY_API_KEY=your-key npm start
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Contributing
|
|
221
|
+
|
|
222
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
223
|
+
|
|
224
|
+
## License
|
|
225
|
+
|
|
226
|
+
MIT License - see [LICENSE](LICENSE) for details.
|
|
227
|
+
|
|
228
|
+
## Related
|
|
229
|
+
|
|
230
|
+
- [Coolify](https://coolify.io) - Self-hostable Heroku/Netlify alternative
|
|
231
|
+
- [Model Context Protocol](https://modelcontextprotocol.io) - Protocol for AI tool integration
|
|
232
|
+
- [Claude Code](https://claude.ai/code) - Anthropic's CLI for Claude
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface TeamConfig {
|
|
2
|
+
name: string;
|
|
3
|
+
apiKey: string;
|
|
4
|
+
}
|
|
5
|
+
export interface Config {
|
|
6
|
+
baseUrl: string;
|
|
7
|
+
defaultTeam: string;
|
|
8
|
+
teams: Map<string, TeamConfig>;
|
|
9
|
+
}
|
|
10
|
+
export declare function getConfig(): Config;
|
|
11
|
+
export declare function getTeamApiKey(teamName?: string): string;
|
|
12
|
+
export declare function listAvailableTeams(): string[];
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const ConfigSchema = z.object({
|
|
3
|
+
baseUrl: z.string().url(),
|
|
4
|
+
defaultTeam: z.string().min(1),
|
|
5
|
+
});
|
|
6
|
+
function loadTeams() {
|
|
7
|
+
const teams = new Map();
|
|
8
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
9
|
+
const match = key.match(/^COOLIFY_TEAM_(.+)_API_KEY$/);
|
|
10
|
+
if (match && value) {
|
|
11
|
+
const teamName = match[1].toLowerCase();
|
|
12
|
+
teams.set(teamName, { name: teamName, apiKey: value });
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
if (process.env.COOLIFY_API_KEY) {
|
|
16
|
+
teams.set("default", { name: "default", apiKey: process.env.COOLIFY_API_KEY });
|
|
17
|
+
}
|
|
18
|
+
return teams;
|
|
19
|
+
}
|
|
20
|
+
let configInstance = null;
|
|
21
|
+
export function getConfig() {
|
|
22
|
+
if (configInstance) {
|
|
23
|
+
return configInstance;
|
|
24
|
+
}
|
|
25
|
+
const teams = loadTeams();
|
|
26
|
+
if (teams.size === 0) {
|
|
27
|
+
throw new Error("No API keys configured. Set COOLIFY_API_KEY or COOLIFY_TEAM_<NAME>_API_KEY environment variables.");
|
|
28
|
+
}
|
|
29
|
+
const baseUrl = process.env.COOLIFY_BASE_URL || "https://cloud.softtor.com.br";
|
|
30
|
+
const envDefaultTeam = process.env.COOLIFY_DEFAULT_TEAM || "default";
|
|
31
|
+
const defaultTeam = teams.has(envDefaultTeam) ? envDefaultTeam : teams.keys().next().value;
|
|
32
|
+
const validatedConfig = ConfigSchema.parse({
|
|
33
|
+
baseUrl,
|
|
34
|
+
defaultTeam,
|
|
35
|
+
});
|
|
36
|
+
configInstance = {
|
|
37
|
+
...validatedConfig,
|
|
38
|
+
teams,
|
|
39
|
+
};
|
|
40
|
+
return configInstance;
|
|
41
|
+
}
|
|
42
|
+
export function getTeamApiKey(teamName) {
|
|
43
|
+
const config = getConfig();
|
|
44
|
+
const team = teamName || config.defaultTeam;
|
|
45
|
+
const teamConfig = config.teams.get(team.toLowerCase());
|
|
46
|
+
if (!teamConfig) {
|
|
47
|
+
const availableTeams = Array.from(config.teams.keys()).join(", ");
|
|
48
|
+
throw new Error(`Team "${team}" not found. Available teams: ${availableTeams}`);
|
|
49
|
+
}
|
|
50
|
+
return teamConfig.apiKey;
|
|
51
|
+
}
|
|
52
|
+
export function listAvailableTeams() {
|
|
53
|
+
const config = getConfig();
|
|
54
|
+
return Array.from(config.teams.keys());
|
|
55
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { getConfig, listAvailableTeams } from "./config.js";
|
|
7
|
+
import { applicationTools } from "./tools/applications.js";
|
|
8
|
+
import { databaseTools } from "./tools/databases.js";
|
|
9
|
+
import { serviceTools } from "./tools/services.js";
|
|
10
|
+
import { serverTools } from "./tools/servers.js";
|
|
11
|
+
import { deploymentTools } from "./tools/deployments.js";
|
|
12
|
+
import { projectTools } from "./tools/projects.js";
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
14
|
+
const allTools = [
|
|
15
|
+
...applicationTools,
|
|
16
|
+
...databaseTools,
|
|
17
|
+
...serviceTools,
|
|
18
|
+
...serverTools,
|
|
19
|
+
...deploymentTools,
|
|
20
|
+
...projectTools,
|
|
21
|
+
];
|
|
22
|
+
function zodToJsonSchema(schema) {
|
|
23
|
+
if (schema instanceof z.ZodObject) {
|
|
24
|
+
const shape = schema.shape;
|
|
25
|
+
const properties = {};
|
|
26
|
+
const required = [];
|
|
27
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
28
|
+
const zodValue = value;
|
|
29
|
+
properties[key] = zodFieldToJsonSchema(zodValue);
|
|
30
|
+
if (!(zodValue instanceof z.ZodOptional)) {
|
|
31
|
+
required.push(key);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
type: "object",
|
|
36
|
+
properties,
|
|
37
|
+
required: required.length > 0 ? required : undefined,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
if (schema instanceof z.ZodEffects) {
|
|
41
|
+
return zodToJsonSchema(schema._def.schema);
|
|
42
|
+
}
|
|
43
|
+
return { type: "object" };
|
|
44
|
+
}
|
|
45
|
+
function zodFieldToJsonSchema(field) {
|
|
46
|
+
if (field instanceof z.ZodString) {
|
|
47
|
+
return {
|
|
48
|
+
type: "string",
|
|
49
|
+
description: field.description,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (field instanceof z.ZodNumber) {
|
|
53
|
+
return {
|
|
54
|
+
type: "number",
|
|
55
|
+
description: field.description,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (field instanceof z.ZodBoolean) {
|
|
59
|
+
return {
|
|
60
|
+
type: "boolean",
|
|
61
|
+
description: field.description,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
if (field instanceof z.ZodOptional) {
|
|
65
|
+
return zodFieldToJsonSchema(field._def.innerType);
|
|
66
|
+
}
|
|
67
|
+
if (field instanceof z.ZodDefault) {
|
|
68
|
+
return zodFieldToJsonSchema(field._def.innerType);
|
|
69
|
+
}
|
|
70
|
+
return { type: "string" };
|
|
71
|
+
}
|
|
72
|
+
const server = new Server({
|
|
73
|
+
name: "coolify-mcp-server",
|
|
74
|
+
version: "1.0.0",
|
|
75
|
+
}, {
|
|
76
|
+
capabilities: {
|
|
77
|
+
tools: {},
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
81
|
+
const config = getConfig();
|
|
82
|
+
const teams = listAvailableTeams();
|
|
83
|
+
const teamsInfo = `Available teams: ${teams.join(", ")}. Default: ${config.defaultTeam}`;
|
|
84
|
+
return {
|
|
85
|
+
tools: allTools.map((tool) => ({
|
|
86
|
+
name: tool.name,
|
|
87
|
+
description: `${tool.description}\n\n${teamsInfo}`,
|
|
88
|
+
inputSchema: zodToJsonSchema(tool.inputSchema),
|
|
89
|
+
})),
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
93
|
+
const { name, arguments: args } = request.params;
|
|
94
|
+
const tool = allTools.find((t) => t.name === name);
|
|
95
|
+
if (!tool) {
|
|
96
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
const validatedArgs = tool.inputSchema.parse(args);
|
|
100
|
+
const result = await tool.handler(validatedArgs);
|
|
101
|
+
return {
|
|
102
|
+
content: [{ type: "text", text: result }],
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
107
|
+
return {
|
|
108
|
+
content: [{ type: "text", text: `Error: ${errorMessage}` }],
|
|
109
|
+
isError: true,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
async function main() {
|
|
114
|
+
try {
|
|
115
|
+
getConfig();
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
console.error("Configuration error:", error instanceof Error ? error.message : error);
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
const transport = new StdioServerTransport();
|
|
122
|
+
await server.connect(transport);
|
|
123
|
+
}
|
|
124
|
+
main().catch((error) => {
|
|
125
|
+
console.error("Fatal error:", error);
|
|
126
|
+
process.exit(1);
|
|
127
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const TeamParamSchema: z.ZodObject<{
|
|
3
|
+
team: z.ZodOptional<z.ZodString>;
|
|
4
|
+
}, "strip", z.ZodTypeAny, {
|
|
5
|
+
team?: string | undefined;
|
|
6
|
+
}, {
|
|
7
|
+
team?: string | undefined;
|
|
8
|
+
}>;
|
|
9
|
+
export declare const UuidParamSchema: z.ZodObject<{
|
|
10
|
+
team: z.ZodOptional<z.ZodString>;
|
|
11
|
+
} & {
|
|
12
|
+
uuid: z.ZodString;
|
|
13
|
+
}, "strip", z.ZodTypeAny, {
|
|
14
|
+
uuid: string;
|
|
15
|
+
team?: string | undefined;
|
|
16
|
+
}, {
|
|
17
|
+
uuid: string;
|
|
18
|
+
team?: string | undefined;
|
|
19
|
+
}>;
|
|
20
|
+
export declare const DeployParamSchema: z.ZodObject<{
|
|
21
|
+
team: z.ZodOptional<z.ZodString>;
|
|
22
|
+
} & {
|
|
23
|
+
uuid: z.ZodOptional<z.ZodString>;
|
|
24
|
+
tag: z.ZodOptional<z.ZodString>;
|
|
25
|
+
force: z.ZodOptional<z.ZodBoolean>;
|
|
26
|
+
}, "strip", z.ZodTypeAny, {
|
|
27
|
+
team?: string | undefined;
|
|
28
|
+
uuid?: string | undefined;
|
|
29
|
+
tag?: string | undefined;
|
|
30
|
+
force?: boolean | undefined;
|
|
31
|
+
}, {
|
|
32
|
+
team?: string | undefined;
|
|
33
|
+
uuid?: string | undefined;
|
|
34
|
+
tag?: string | undefined;
|
|
35
|
+
force?: boolean | undefined;
|
|
36
|
+
}>;
|
|
37
|
+
export declare const LogsParamSchema: z.ZodObject<{
|
|
38
|
+
team: z.ZodOptional<z.ZodString>;
|
|
39
|
+
} & {
|
|
40
|
+
uuid: z.ZodString;
|
|
41
|
+
} & {
|
|
42
|
+
since: z.ZodOptional<z.ZodNumber>;
|
|
43
|
+
}, "strip", z.ZodTypeAny, {
|
|
44
|
+
uuid: string;
|
|
45
|
+
team?: string | undefined;
|
|
46
|
+
since?: number | undefined;
|
|
47
|
+
}, {
|
|
48
|
+
uuid: string;
|
|
49
|
+
team?: string | undefined;
|
|
50
|
+
since?: number | undefined;
|
|
51
|
+
}>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const TeamParamSchema = z.object({
|
|
3
|
+
team: z.string().optional().describe("Team name to use (default: configured default team)"),
|
|
4
|
+
});
|
|
5
|
+
export const UuidParamSchema = TeamParamSchema.extend({
|
|
6
|
+
uuid: z.string().describe("Resource UUID"),
|
|
7
|
+
});
|
|
8
|
+
export const DeployParamSchema = TeamParamSchema.extend({
|
|
9
|
+
uuid: z.string().optional().describe("Application UUID to deploy"),
|
|
10
|
+
tag: z.string().optional().describe("Tag to deploy (deploys all resources with this tag)"),
|
|
11
|
+
force: z.boolean().optional().describe("Force rebuild without cache"),
|
|
12
|
+
});
|
|
13
|
+
export const LogsParamSchema = UuidParamSchema.extend({
|
|
14
|
+
since: z.number().optional().describe("Get logs since N seconds ago (default: 3600)"),
|
|
15
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
type HttpMethod = "GET" | "POST" | "PATCH" | "DELETE";
|
|
2
|
+
export interface RequestOptions {
|
|
3
|
+
team?: string;
|
|
4
|
+
timeout?: number;
|
|
5
|
+
params?: Record<string, string | number | boolean | undefined>;
|
|
6
|
+
}
|
|
7
|
+
export declare function coolifyRequest<T>(endpoint: string, method?: HttpMethod, data?: unknown, options?: RequestOptions): Promise<T>;
|
|
8
|
+
export declare function coolifyGet<T>(endpoint: string, options?: RequestOptions): Promise<T>;
|
|
9
|
+
export declare function coolifyPost<T>(endpoint: string, data?: unknown, options?: RequestOptions): Promise<T>;
|
|
10
|
+
export declare function coolifyPatch<T>(endpoint: string, data?: unknown, options?: RequestOptions): Promise<T>;
|
|
11
|
+
export declare function coolifyDelete<T>(endpoint: string, options?: RequestOptions): Promise<T>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import { getConfig, getTeamApiKey } from "../config.js";
|
|
3
|
+
import { handleApiError, formatErrorResponse } from "./error-handler.js";
|
|
4
|
+
export async function coolifyRequest(endpoint, method = "GET", data, options = {}) {
|
|
5
|
+
const config = getConfig();
|
|
6
|
+
const apiKey = getTeamApiKey(options.team);
|
|
7
|
+
const axiosConfig = {
|
|
8
|
+
method,
|
|
9
|
+
url: `${config.baseUrl}/api/v1${endpoint}`,
|
|
10
|
+
timeout: options.timeout ?? 30000,
|
|
11
|
+
headers: {
|
|
12
|
+
"Content-Type": "application/json",
|
|
13
|
+
Authorization: `Bearer ${apiKey}`,
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
if (data) {
|
|
17
|
+
axiosConfig.data = data;
|
|
18
|
+
}
|
|
19
|
+
if (options.params) {
|
|
20
|
+
const filteredParams = {};
|
|
21
|
+
for (const [key, value] of Object.entries(options.params)) {
|
|
22
|
+
if (value !== undefined) {
|
|
23
|
+
filteredParams[key] = value;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
axiosConfig.params = filteredParams;
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
const response = await axios(axiosConfig);
|
|
30
|
+
return response.data;
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
const apiError = handleApiError(error);
|
|
34
|
+
throw new Error(formatErrorResponse(apiError));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export async function coolifyGet(endpoint, options = {}) {
|
|
38
|
+
return coolifyRequest(endpoint, "GET", undefined, options);
|
|
39
|
+
}
|
|
40
|
+
export async function coolifyPost(endpoint, data, options = {}) {
|
|
41
|
+
return coolifyRequest(endpoint, "POST", data, options);
|
|
42
|
+
}
|
|
43
|
+
export async function coolifyPatch(endpoint, data, options = {}) {
|
|
44
|
+
return coolifyRequest(endpoint, "PATCH", data, options);
|
|
45
|
+
}
|
|
46
|
+
export async function coolifyDelete(endpoint, options = {}) {
|
|
47
|
+
return coolifyRequest(endpoint, "DELETE", undefined, options);
|
|
48
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { AxiosError } from "axios";
|
|
2
|
+
export function handleApiError(error) {
|
|
3
|
+
if (error instanceof AxiosError) {
|
|
4
|
+
const status = error.response?.status;
|
|
5
|
+
const data = error.response?.data;
|
|
6
|
+
if (status === 401) {
|
|
7
|
+
return {
|
|
8
|
+
message: "Authentication failed. Check your API key.",
|
|
9
|
+
status,
|
|
10
|
+
details: data,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
if (status === 403) {
|
|
14
|
+
return {
|
|
15
|
+
message: "Access denied. Your API key may not have permission for this operation.",
|
|
16
|
+
status,
|
|
17
|
+
details: data,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
if (status === 404) {
|
|
21
|
+
return {
|
|
22
|
+
message: "Resource not found.",
|
|
23
|
+
status,
|
|
24
|
+
details: data,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
if (status === 422) {
|
|
28
|
+
return {
|
|
29
|
+
message: "Validation error.",
|
|
30
|
+
status,
|
|
31
|
+
details: data,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
message: data?.message || error.message || "Unknown API error",
|
|
36
|
+
status,
|
|
37
|
+
details: data,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
if (error instanceof Error) {
|
|
41
|
+
return {
|
|
42
|
+
message: error.message,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
message: "Unknown error occurred",
|
|
47
|
+
details: error,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export function formatErrorResponse(error) {
|
|
51
|
+
let response = `Error: ${error.message}`;
|
|
52
|
+
if (error.status) {
|
|
53
|
+
response += ` (HTTP ${error.status})`;
|
|
54
|
+
}
|
|
55
|
+
if (error.details) {
|
|
56
|
+
response += `\nDetails: ${JSON.stringify(error.details, null, 2)}`;
|
|
57
|
+
}
|
|
58
|
+
return response;
|
|
59
|
+
}
|