@vibetools/dokploy-mcp 0.5.0 → 1.2.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 +39 -13
- package/dist/api/client.d.ts +2 -0
- package/dist/api/client.js +51 -16
- package/dist/config/resolver.js +33 -50
- package/dist/server.js +1 -1
- package/dist/tools/_database.d.ts +12 -0
- package/dist/tools/_database.js +115 -0
- package/dist/tools/_factory.d.ts +3 -1
- package/dist/tools/_factory.js +36 -17
- package/dist/tools/application.js +219 -82
- package/dist/tools/backup.js +30 -0
- package/dist/tools/compose.js +273 -35
- package/dist/tools/deployment.js +82 -2
- package/dist/tools/docker.js +62 -2
- package/dist/tools/domain.js +15 -2
- package/dist/tools/environment.d.ts +2 -0
- package/dist/tools/environment.js +104 -0
- package/dist/tools/git-provider.d.ts +2 -0
- package/dist/tools/git-provider.js +22 -0
- package/dist/tools/github.d.ts +2 -0
- package/dist/tools/github.js +66 -0
- package/dist/tools/gitlab.d.ts +2 -0
- package/dist/tools/gitlab.js +98 -0
- package/dist/tools/index.js +24 -0
- package/dist/tools/mariadb.d.ts +1 -2
- package/dist/tools/mariadb.js +9 -165
- package/dist/tools/mongo.d.ts +1 -2
- package/dist/tools/mongo.js +9 -164
- package/dist/tools/mounts.js +53 -9
- package/dist/tools/mysql.d.ts +1 -2
- package/dist/tools/mysql.js +9 -165
- package/dist/tools/notification.d.ts +2 -0
- package/dist/tools/notification.js +559 -0
- package/dist/tools/patch.d.ts +2 -0
- package/dist/tools/patch.js +179 -0
- package/dist/tools/postgres.d.ts +1 -2
- package/dist/tools/postgres.js +8 -164
- package/dist/tools/preview-deployment.d.ts +2 -0
- package/dist/tools/preview-deployment.js +50 -0
- package/dist/tools/project.js +32 -1
- package/dist/tools/redis.d.ts +1 -2
- package/dist/tools/redis.js +8 -164
- package/dist/tools/rollback.d.ts +2 -0
- package/dist/tools/rollback.js +28 -0
- package/dist/tools/schedule.d.ts +2 -0
- package/dist/tools/schedule.js +92 -0
- package/dist/tools/server.d.ts +2 -0
- package/dist/tools/server.js +192 -0
- package/dist/tools/settings.js +251 -0
- package/dist/tools/ssh-key.d.ts +2 -0
- package/dist/tools/ssh-key.js +74 -0
- package/dist/tools/user.js +75 -2
- package/dist/tools/volume-backups.d.ts +2 -0
- package/dist/tools/volume-backups.js +96 -0
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
[](https://nodejs.org/)
|
|
6
6
|
|
|
7
|
-
MCP server for the Dokploy API.
|
|
7
|
+
MCP server for the Dokploy API. 377 tools across 35 modules. Your AI agent can now deploy apps, manage databases, configure domains, and handle backups -- without you touching a dashboard.
|
|
8
|
+
|
|
9
|
+
Forked from [Dokploy/mcp](https://github.com/Dokploy/mcp) and rebuilt with expanded API coverage, tool annotations, Zod v4 schemas, lazy config loading, and a setup wizard. The original had 67 tools. This one has 377. Standing on shoulders, etc.
|
|
8
10
|
|
|
9
11
|
## Quick Start
|
|
10
12
|
|
|
@@ -43,7 +45,7 @@ If you already have the [Dokploy CLI](https://github.com/Dokploy/cli) installed
|
|
|
43
45
|
|
|
44
46
|
## Features
|
|
45
47
|
|
|
46
|
-
- **
|
|
48
|
+
- **377 tools, 35 modules** -- applications, compose, environments, servers, Git providers, notifications, databases (Postgres/MySQL/MariaDB/MongoDB/Redis), domains, backups, deployment queues, rollback, patching, Docker, settings, preview deployments, schedules, and more
|
|
47
49
|
- **Tool annotations** -- `readOnlyHint`, `destructiveHint`, `idempotentHint` so clients can warn before you nuke something
|
|
48
50
|
- **Type-safe schemas** -- Zod v4 validation on every parameter
|
|
49
51
|
- **Lazy config loading** -- validates credentials on first API call, not at startup
|
|
@@ -134,18 +136,24 @@ Already ran `setup` or have Dokploy CLI authenticated? Drop the `env` block enti
|
|
|
134
136
|
|
|
135
137
|
| Module | Tools | Module | Tools |
|
|
136
138
|
|--------|-------|--------|-------|
|
|
137
|
-
| Project |
|
|
138
|
-
|
|
|
139
|
-
|
|
|
140
|
-
|
|
|
141
|
-
|
|
|
142
|
-
|
|
|
143
|
-
|
|
|
144
|
-
|
|
|
145
|
-
|
|
|
139
|
+
| Project | 8 | Deployment | 8 |
|
|
140
|
+
| Environment | 7 | Docker | 7 |
|
|
141
|
+
| Application | 29 | Server | 16 |
|
|
142
|
+
| Compose | 28 | Certificates | 4 |
|
|
143
|
+
| Domain | 9 | Registry | 6 |
|
|
144
|
+
| Patch | 12 | SSH Key | 6 |
|
|
145
|
+
| Git Provider | 2 | GitHub | 6 |
|
|
146
|
+
| GitLab | 7 | PostgreSQL | 14 |
|
|
147
|
+
| Notification | 38 | MySQL | 14 |
|
|
148
|
+
| Destination | 6 | MariaDB | 14 |
|
|
149
|
+
| Backup | 11 | MongoDB | 14 |
|
|
150
|
+
| Mounts | 6 | Redis | 14 |
|
|
151
|
+
| Port | 4 | Volume Backups | 6 |
|
|
152
|
+
| Redirects | 4 | Rollback | 2 |
|
|
153
|
+
| Preview Deployment | 4 | Schedule | 6 |
|
|
146
154
|
| Security | 4 | Cluster | 4 |
|
|
147
|
-
| Settings |
|
|
148
|
-
| User |
|
|
155
|
+
| Settings | 49 | Admin | 1 |
|
|
156
|
+
| User | 7 | | |
|
|
149
157
|
|
|
150
158
|
Full reference with parameters and descriptions: **[docs/tools.md](docs/tools.md)**
|
|
151
159
|
|
|
@@ -207,6 +215,24 @@ Test with the MCP Inspector:
|
|
|
207
215
|
npx @modelcontextprotocol/inspector node dist/index.js
|
|
208
216
|
```
|
|
209
217
|
|
|
218
|
+
## Standing on the Shoulders of People Who Actually Did the Work
|
|
219
|
+
|
|
220
|
+
This project is a fork of [Dokploy/mcp](https://github.com/Dokploy/mcp). I rewrote most of it, tripled the tool count, and added things like a setup wizard and config resolution chain -- but "rewrote" is easy when someone else already built the thing you're rewriting.
|
|
221
|
+
|
|
222
|
+
[Mauricio Siu](https://github.com/Siumauricio) created [Dokploy](https://dokploy.com) itself -- a genuinely impressive open-source PaaS -- and kicked off the MCP server repo. Without Dokploy, there's no API. Without the API, there's no MCP server. Without the MCP server, I'd have had to start from zero instead of "from scratch."
|
|
223
|
+
|
|
224
|
+
[Henrique Andrade](https://github.com/andradehenrique) did the actual heavy lifting on the original MCP. Projects, applications, PostgreSQL, MySQL, domains -- that was all him. 15 commits, every merged PR. The kind of contributor who doesn't just open issues, he closes them.
|
|
225
|
+
|
|
226
|
+
And to everyone who opened PRs on the original repo -- merged or not -- your code and ideas shaped what this became:
|
|
227
|
+
|
|
228
|
+
[Joshua Macauley](https://github.com/Macawls) · [lucasleal-developer](https://github.com/lucasleal-developer) · [Nour Eddine Hamaidi](https://github.com/HenkDz) · [Corey](https://github.com/limehawk) · [Azil0ne](https://github.com/Azilone)
|
|
229
|
+
|
|
230
|
+
Unmerged PRs are still blueprints. Someone reads your compose tools PR and thinks "right, I should cover that." Someone sees your consolidation approach and borrows the idea. That's how open source actually works -- not through clean merge histories, but through stolen inspiration with better commit messages.
|
|
231
|
+
|
|
232
|
+
Cheers to all of you. I owe you mass-produced coffee at minimum.
|
|
233
|
+
|
|
210
234
|
## License
|
|
211
235
|
|
|
212
236
|
MIT - [Vibe Code](https://vcode.sh)
|
|
237
|
+
|
|
238
|
+
Original work by [Henrique Andrade](https://github.com/andradehenrique) under Apache 2.0 -- see [LICENSE-ORIGINAL](LICENSE-ORIGINAL).
|
package/dist/api/client.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export declare function unwrapTrpcResponse(data: unknown): unknown;
|
|
1
2
|
export declare class ApiError extends Error {
|
|
2
3
|
readonly status: number;
|
|
3
4
|
readonly statusText: string;
|
|
@@ -5,6 +6,7 @@ export declare class ApiError extends Error {
|
|
|
5
6
|
readonly endpoint: string;
|
|
6
7
|
constructor(status: number, statusText: string, body: unknown, endpoint: string);
|
|
7
8
|
}
|
|
9
|
+
export declare function buildQueryString(body: unknown): string;
|
|
8
10
|
export declare const api: {
|
|
9
11
|
get: <T = unknown>(path: string, params?: Record<string, unknown>) => Promise<T>;
|
|
10
12
|
post: <T = unknown>(path: string, body?: unknown) => Promise<T>;
|
package/dist/api/client.js
CHANGED
|
@@ -1,4 +1,38 @@
|
|
|
1
1
|
import { resolveConfig } from '../config/resolver.js';
|
|
2
|
+
const DEFAULT_TIMEOUT = 30_000;
|
|
3
|
+
function getErrorMessage(body, statusText) {
|
|
4
|
+
if (typeof body !== 'object' || body === null) {
|
|
5
|
+
return statusText;
|
|
6
|
+
}
|
|
7
|
+
if ('message' in body && typeof body.message === 'string') {
|
|
8
|
+
return body.message;
|
|
9
|
+
}
|
|
10
|
+
if ('error' in body && typeof body.error === 'object' && body.error !== null) {
|
|
11
|
+
const error = body.error;
|
|
12
|
+
if ('message' in error && typeof error.message === 'string') {
|
|
13
|
+
return error.message;
|
|
14
|
+
}
|
|
15
|
+
if ('json' in error && typeof error.json === 'object' && error.json !== null) {
|
|
16
|
+
const json = error.json;
|
|
17
|
+
if ('message' in json && typeof json.message === 'string') {
|
|
18
|
+
return json.message;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return statusText;
|
|
23
|
+
}
|
|
24
|
+
export function unwrapTrpcResponse(data) {
|
|
25
|
+
if (typeof data !== 'object' || data === null)
|
|
26
|
+
return data;
|
|
27
|
+
const outer = data;
|
|
28
|
+
if (typeof outer.result !== 'object' || outer.result === null)
|
|
29
|
+
return data;
|
|
30
|
+
const result = outer.result;
|
|
31
|
+
if (typeof result.data !== 'object' || result.data === null)
|
|
32
|
+
return data;
|
|
33
|
+
const inner = result.data;
|
|
34
|
+
return 'json' in inner ? inner.json : data;
|
|
35
|
+
}
|
|
2
36
|
function getConfig() {
|
|
3
37
|
const resolved = resolveConfig();
|
|
4
38
|
if (!resolved) {
|
|
@@ -14,7 +48,7 @@ function getConfig() {
|
|
|
14
48
|
return {
|
|
15
49
|
baseUrl: resolved.url.replace(/\/+$/, ''),
|
|
16
50
|
apiKey: resolved.apiKey,
|
|
17
|
-
timeout: resolved.timeout,
|
|
51
|
+
timeout: resolved.timeout || DEFAULT_TIMEOUT,
|
|
18
52
|
};
|
|
19
53
|
}
|
|
20
54
|
let _config = null;
|
|
@@ -28,9 +62,7 @@ export class ApiError extends Error {
|
|
|
28
62
|
body;
|
|
29
63
|
endpoint;
|
|
30
64
|
constructor(status, statusText, body, endpoint) {
|
|
31
|
-
const msg =
|
|
32
|
-
? body.message
|
|
33
|
-
: statusText;
|
|
65
|
+
const msg = getErrorMessage(body, statusText);
|
|
34
66
|
super(`Dokploy API error (${status}): ${msg}`);
|
|
35
67
|
this.status = status;
|
|
36
68
|
this.statusText = statusText;
|
|
@@ -39,18 +71,21 @@ export class ApiError extends Error {
|
|
|
39
71
|
this.name = 'ApiError';
|
|
40
72
|
}
|
|
41
73
|
}
|
|
42
|
-
function buildQueryString(body) {
|
|
43
|
-
if (
|
|
74
|
+
export function buildQueryString(body) {
|
|
75
|
+
if (body == null)
|
|
44
76
|
return '';
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
return params.toString();
|
|
77
|
+
if (typeof body !== 'object')
|
|
78
|
+
return '';
|
|
79
|
+
const params = Object.fromEntries(Object.entries(body).filter(([, value]) => value != null));
|
|
80
|
+
return new URLSearchParams({
|
|
81
|
+
input: JSON.stringify({ json: params }),
|
|
82
|
+
}).toString();
|
|
53
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Checks whether an error was caused by an aborted fetch.
|
|
86
|
+
* Both checks are needed: older Node versions throw DOMException,
|
|
87
|
+
* while newer versions throw an Error with name 'AbortError'.
|
|
88
|
+
*/
|
|
54
89
|
function isAbortError(error) {
|
|
55
90
|
return error instanceof DOMException || (error instanceof Error && error.name === 'AbortError');
|
|
56
91
|
}
|
|
@@ -68,7 +103,7 @@ async function request(method, path, body) {
|
|
|
68
103
|
Accept: 'application/json',
|
|
69
104
|
'x-api-key': apiKey,
|
|
70
105
|
},
|
|
71
|
-
body: method === 'POST' && body ? JSON.stringify(body) : undefined,
|
|
106
|
+
body: method === 'POST' && body ? JSON.stringify({ json: body }) : undefined,
|
|
72
107
|
signal: controller.signal,
|
|
73
108
|
});
|
|
74
109
|
const text = await response.text();
|
|
@@ -82,7 +117,7 @@ async function request(method, path, body) {
|
|
|
82
117
|
if (!response.ok) {
|
|
83
118
|
throw new ApiError(response.status, response.statusText, data, path);
|
|
84
119
|
}
|
|
85
|
-
return data;
|
|
120
|
+
return unwrapTrpcResponse(data);
|
|
86
121
|
}
|
|
87
122
|
catch (error) {
|
|
88
123
|
if (error instanceof ApiError) {
|
package/dist/config/resolver.js
CHANGED
|
@@ -1,7 +1,28 @@
|
|
|
1
1
|
import { execSync } from 'node:child_process';
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
+
import { z } from 'zod';
|
|
4
5
|
import { getConfigDir, getConfigFilePath } from './types.js';
|
|
6
|
+
const configFileSchema = z.object({
|
|
7
|
+
url: z.string().min(1),
|
|
8
|
+
apiKey: z.string().min(1),
|
|
9
|
+
});
|
|
10
|
+
const dokployCliSchema = z.object({
|
|
11
|
+
url: z.string().min(1),
|
|
12
|
+
token: z.string().min(1),
|
|
13
|
+
});
|
|
14
|
+
const userSchema = z
|
|
15
|
+
.object({
|
|
16
|
+
email: z.string().optional(),
|
|
17
|
+
user: z
|
|
18
|
+
.object({
|
|
19
|
+
email: z.string().optional(),
|
|
20
|
+
firstName: z.string().optional(),
|
|
21
|
+
})
|
|
22
|
+
.optional(),
|
|
23
|
+
})
|
|
24
|
+
.passthrough();
|
|
25
|
+
const versionSchema = z.union([z.string(), z.object({ version: z.string() }).passthrough()]);
|
|
5
26
|
/**
|
|
6
27
|
* Normalizes a Dokploy URL to the tRPC API base.
|
|
7
28
|
* Accepts any of these formats:
|
|
@@ -74,20 +95,8 @@ function readConfigFile() {
|
|
|
74
95
|
try {
|
|
75
96
|
const content = readFileSync(filePath, 'utf8');
|
|
76
97
|
const parsed = JSON.parse(content);
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
!('url' in parsed) ||
|
|
80
|
-
!('apiKey' in parsed)) {
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
83
|
-
const record = parsed;
|
|
84
|
-
if (typeof record.url !== 'string' || typeof record.apiKey !== 'string') {
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
if (!(record.url && record.apiKey)) {
|
|
88
|
-
return null;
|
|
89
|
-
}
|
|
90
|
-
return { url: record.url, apiKey: record.apiKey };
|
|
98
|
+
const result = configFileSchema.safeParse(parsed);
|
|
99
|
+
return result.success ? result.data : null;
|
|
91
100
|
}
|
|
92
101
|
catch {
|
|
93
102
|
return null;
|
|
@@ -107,20 +116,8 @@ function readDokployCliConfig() {
|
|
|
107
116
|
}
|
|
108
117
|
const content = readFileSync(cliConfigPath, 'utf8');
|
|
109
118
|
const parsed = JSON.parse(content);
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
!('url' in parsed) ||
|
|
113
|
-
!('token' in parsed)) {
|
|
114
|
-
return null;
|
|
115
|
-
}
|
|
116
|
-
const record = parsed;
|
|
117
|
-
if (typeof record.url !== 'string' || typeof record.token !== 'string') {
|
|
118
|
-
return null;
|
|
119
|
-
}
|
|
120
|
-
if (!(record.url && record.token)) {
|
|
121
|
-
return null;
|
|
122
|
-
}
|
|
123
|
-
return { url: record.url, apiKey: record.token };
|
|
119
|
+
const result = dokployCliSchema.safeParse(parsed);
|
|
120
|
+
return result.success ? { url: result.data.url, apiKey: result.data.token } : null;
|
|
124
121
|
}
|
|
125
122
|
catch {
|
|
126
123
|
return null;
|
|
@@ -212,21 +209,11 @@ function unwrapTrpc(data) {
|
|
|
212
209
|
}
|
|
213
210
|
function parseUser(data) {
|
|
214
211
|
const unwrapped = unwrapTrpc(data);
|
|
215
|
-
|
|
212
|
+
const result = userSchema.safeParse(unwrapped);
|
|
213
|
+
if (!result.success)
|
|
216
214
|
return undefined;
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
if (typeof record.email === 'string')
|
|
220
|
-
return record.email;
|
|
221
|
-
// Nested user object (tRPC user.get response)
|
|
222
|
-
if (typeof record.user === 'object' && record.user !== null) {
|
|
223
|
-
const user = record.user;
|
|
224
|
-
if (typeof user.email === 'string')
|
|
225
|
-
return user.email;
|
|
226
|
-
if (typeof user.firstName === 'string')
|
|
227
|
-
return user.firstName;
|
|
228
|
-
}
|
|
229
|
-
return undefined;
|
|
215
|
+
const { email, user } = result.data;
|
|
216
|
+
return email ?? user?.email ?? user?.firstName;
|
|
230
217
|
}
|
|
231
218
|
async function fetchVersion(baseUrl, apiKey) {
|
|
232
219
|
const controller = new AbortController();
|
|
@@ -241,14 +228,10 @@ async function fetchVersion(baseUrl, apiKey) {
|
|
|
241
228
|
return undefined;
|
|
242
229
|
const data = await response.json();
|
|
243
230
|
const unwrapped = unwrapTrpc(data);
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
if (typeof record.version === 'string')
|
|
249
|
-
return record.version;
|
|
250
|
-
}
|
|
251
|
-
return undefined;
|
|
231
|
+
const result = versionSchema.safeParse(unwrapped);
|
|
232
|
+
if (!result.success)
|
|
233
|
+
return undefined;
|
|
234
|
+
return typeof result.data === 'string' ? result.data : result.data.version;
|
|
252
235
|
}
|
|
253
236
|
catch {
|
|
254
237
|
return undefined;
|
package/dist/server.js
CHANGED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { type ToolDefinition } from './_factory.js';
|
|
3
|
+
type AnyZodObject = z.ZodObject;
|
|
4
|
+
export interface DatabaseConfig {
|
|
5
|
+
type: string;
|
|
6
|
+
idField: string;
|
|
7
|
+
displayName: string;
|
|
8
|
+
defaultImage: string;
|
|
9
|
+
createFields: AnyZodObject;
|
|
10
|
+
}
|
|
11
|
+
export declare function createDatabaseTools(config: DatabaseConfig): ToolDefinition[];
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { getTool, postTool } from './_factory.js';
|
|
3
|
+
export function createDatabaseTools(config) {
|
|
4
|
+
const { type, idField, displayName, defaultImage, createFields } = config;
|
|
5
|
+
const idSchema = z
|
|
6
|
+
.object({ [idField]: z.string().min(1).describe(`Unique ${displayName} database ID`) })
|
|
7
|
+
.strict();
|
|
8
|
+
function tool(action, title, description, schema, opts = {}) {
|
|
9
|
+
const endpoint = `/${type}.${action}`;
|
|
10
|
+
const name = `dokploy_${type}_${action.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`)}`;
|
|
11
|
+
if (opts.get) {
|
|
12
|
+
return getTool({ name, title, description, schema, endpoint, annotations: opts.annotations });
|
|
13
|
+
}
|
|
14
|
+
return postTool({ name, title, description, schema, endpoint, annotations: opts.annotations });
|
|
15
|
+
}
|
|
16
|
+
const one = tool('one', `Get ${displayName} Details`, `Retrieve detailed information about a specific ${displayName} database managed by Dokploy. Returns the full configuration including connection settings, resource limits, environment variables, and current status. Requires the unique ${displayName} database ID.`, idSchema, { get: true });
|
|
17
|
+
const create = tool('create', `Create ${displayName} Database`, `Create a new ${displayName} database instance inside a Dokploy environment. Requires a display name and the target environment ID. Optionally specify an app-level identifier, Docker image, description, or remote server. Returns the newly created database record.`, z
|
|
18
|
+
.object({
|
|
19
|
+
name: z.string().min(1).describe('Display name for the database'),
|
|
20
|
+
appName: z
|
|
21
|
+
.string()
|
|
22
|
+
.min(1)
|
|
23
|
+
.max(63)
|
|
24
|
+
.regex(/^[a-zA-Z0-9._-]+$/)
|
|
25
|
+
.optional()
|
|
26
|
+
.describe('Unique app-level identifier'),
|
|
27
|
+
...createFields.shape,
|
|
28
|
+
environmentId: z.string().min(1).describe('Environment ID to create the database in'),
|
|
29
|
+
dockerImage: z.string().optional().describe(`Docker image (default: ${defaultImage})`),
|
|
30
|
+
description: z.string().nullable().optional().describe('Optional description'),
|
|
31
|
+
serverId: z.string().nullable().optional().describe('Target server ID (null for local)'),
|
|
32
|
+
})
|
|
33
|
+
.strict());
|
|
34
|
+
const update = tool('update', `Update ${displayName} Database`, `Update the configuration of an existing ${displayName} database in Dokploy. Supports modifying the display name, Docker image, resource limits (CPU and memory), custom start command, environment variables, and external port. Requires the ${displayName} database ID. Only the provided fields are updated.`, z
|
|
35
|
+
.object({
|
|
36
|
+
[idField]: z.string().min(1).describe(`Unique ${displayName} database ID`),
|
|
37
|
+
name: z.string().min(1).optional().describe('Display name'),
|
|
38
|
+
appName: z.string().min(1).optional().describe('App-level identifier'),
|
|
39
|
+
description: z.string().nullable().optional().describe('Description'),
|
|
40
|
+
dockerImage: z.string().optional().describe('Docker image'),
|
|
41
|
+
memoryReservation: z.number().nullable().optional().describe('Memory reservation in MB'),
|
|
42
|
+
memoryLimit: z.number().nullable().optional().describe('Memory limit in MB'),
|
|
43
|
+
cpuReservation: z.number().nullable().optional().describe('CPU reservation'),
|
|
44
|
+
cpuLimit: z.number().nullable().optional().describe('CPU limit'),
|
|
45
|
+
command: z.string().nullable().optional().describe('Custom start command'),
|
|
46
|
+
env: z.string().nullable().optional().describe('Environment variables'),
|
|
47
|
+
externalPort: z.number().nullable().optional().describe('External port'),
|
|
48
|
+
})
|
|
49
|
+
.strict());
|
|
50
|
+
const remove = tool('remove', `Remove ${displayName} Database`, `Permanently delete a ${displayName} database from Dokploy. This action removes the database container, its data, and all associated configuration. Requires the ${displayName} database ID. This operation is destructive and cannot be undone.`, idSchema, { annotations: { destructiveHint: true } });
|
|
51
|
+
const move = tool('move', `Move ${displayName} Database`, `Move a ${displayName} database from its current environment to a different environment within Dokploy. Requires the ${displayName} database ID and the destination environment ID. The database configuration and data are preserved during the move.`, z
|
|
52
|
+
.object({
|
|
53
|
+
[idField]: z.string().min(1).describe(`Unique ${displayName} database ID`),
|
|
54
|
+
targetEnvironmentId: z.string().min(1).describe('Destination environment ID'),
|
|
55
|
+
})
|
|
56
|
+
.strict());
|
|
57
|
+
const deploy = tool('deploy', `Deploy ${displayName} Database`, `Deploy a ${displayName} database container in Dokploy. Triggers the build and start process for the specified database. Requires the ${displayName} database ID. Returns the deployment status.`, idSchema);
|
|
58
|
+
const start = tool('start', `Start ${displayName} Database`, `Start a previously stopped ${displayName} database container in Dokploy. The database must already be deployed. Requires the ${displayName} database ID. Returns the updated status after starting.`, idSchema);
|
|
59
|
+
const stop = tool('stop', `Stop ${displayName} Database`, `Stop a running ${displayName} database container in Dokploy. The database data is preserved but the container will no longer accept connections. Requires the ${displayName} database ID. This is a destructive action as it interrupts active connections.`, idSchema, { annotations: { destructiveHint: true } });
|
|
60
|
+
const reload = tool('reload', `Reload ${displayName} Database`, `Reload the ${displayName} database container in Dokploy without a full restart. Applies configuration changes that do not require a rebuild. Requires the ${displayName} database ID and the app-level identifier. Returns the reload status.`, z
|
|
61
|
+
.object({
|
|
62
|
+
[idField]: z.string().min(1).describe(`Unique ${displayName} database ID`),
|
|
63
|
+
appName: z.string().min(1).describe('App-level identifier'),
|
|
64
|
+
})
|
|
65
|
+
.strict());
|
|
66
|
+
const rebuild = tool('rebuild', `Rebuild ${displayName} Database`, `Rebuild the ${displayName} database container from scratch in Dokploy. This tears down the existing container and recreates it with the current configuration. Requires the ${displayName} database ID. Useful after changing the Docker image or when the container is in a broken state.`, idSchema);
|
|
67
|
+
const changeStatus = tool('changeStatus', `Change ${displayName} Status`, `Manually set the application status of a ${displayName} database in Dokploy. Accepts one of: idle, running, done, or error. Requires the ${displayName} database ID and the new status value. Useful for correcting a stale or incorrect status.`, z
|
|
68
|
+
.object({
|
|
69
|
+
[idField]: z.string().min(1).describe(`Unique ${displayName} database ID`),
|
|
70
|
+
applicationStatus: z
|
|
71
|
+
.enum(['idle', 'running', 'done', 'error'])
|
|
72
|
+
.describe('New application status'),
|
|
73
|
+
})
|
|
74
|
+
.strict());
|
|
75
|
+
const saveExternalPort = tool('saveExternalPort', `Save ${displayName} External Port`, `Set or clear the external port mapping for a ${displayName} database in Dokploy. When set, the database is accessible from outside the Docker network on the specified port. Pass null to remove the external port. Requires the ${displayName} database ID.`, z
|
|
76
|
+
.object({
|
|
77
|
+
[idField]: z.string().min(1).describe(`Unique ${displayName} database ID`),
|
|
78
|
+
externalPort: z.number().nullable().describe('External port number (null to remove)'),
|
|
79
|
+
})
|
|
80
|
+
.strict());
|
|
81
|
+
const saveEnvironment = tool('saveEnvironment', `Save ${displayName} Environment`, `Overwrite the environment variables for a ${displayName} database in Dokploy. Replaces all existing environment variables with the provided value. Pass the variables as a single string (one per line, KEY=VALUE format). Requires the ${displayName} database ID.`, z
|
|
82
|
+
.object({
|
|
83
|
+
[idField]: z.string().min(1).describe(`Unique ${displayName} database ID`),
|
|
84
|
+
env: z.string().nullable().optional().describe('Environment variables as a string'),
|
|
85
|
+
})
|
|
86
|
+
.strict());
|
|
87
|
+
const search = tool('search', `Search ${displayName} Databases`, `Search ${displayName} databases in Dokploy by free text or field-specific filters. Supports pagination through limit and offset.`, z
|
|
88
|
+
.object({
|
|
89
|
+
q: z.string().optional().describe('Free-text query'),
|
|
90
|
+
name: z.string().optional().describe('Display name'),
|
|
91
|
+
appName: z.string().optional().describe('App-level identifier'),
|
|
92
|
+
description: z.string().optional().describe('Description'),
|
|
93
|
+
projectId: z.string().optional().describe('Project ID'),
|
|
94
|
+
environmentId: z.string().optional().describe('Environment ID'),
|
|
95
|
+
limit: z.number().min(1).max(100).optional().describe('Maximum number of results'),
|
|
96
|
+
offset: z.number().min(0).optional().describe('Number of results to skip'),
|
|
97
|
+
})
|
|
98
|
+
.strict(), { get: true });
|
|
99
|
+
return [
|
|
100
|
+
one,
|
|
101
|
+
create,
|
|
102
|
+
update,
|
|
103
|
+
remove,
|
|
104
|
+
move,
|
|
105
|
+
deploy,
|
|
106
|
+
start,
|
|
107
|
+
stop,
|
|
108
|
+
reload,
|
|
109
|
+
rebuild,
|
|
110
|
+
changeStatus,
|
|
111
|
+
saveExternalPort,
|
|
112
|
+
saveEnvironment,
|
|
113
|
+
search,
|
|
114
|
+
];
|
|
115
|
+
}
|
package/dist/tools/_factory.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ export interface ToolDefinition {
|
|
|
12
12
|
name: string;
|
|
13
13
|
title: string;
|
|
14
14
|
description: string;
|
|
15
|
+
endpoint?: string;
|
|
16
|
+
method?: 'GET' | 'POST';
|
|
15
17
|
schema: AnyZodObject;
|
|
16
18
|
annotations: ToolAnnotations;
|
|
17
19
|
handler: (input: Record<string, unknown>) => Promise<{
|
|
@@ -28,7 +30,7 @@ export declare function createTool<T extends AnyZodObject>(def: {
|
|
|
28
30
|
title: string;
|
|
29
31
|
description: string;
|
|
30
32
|
schema: T;
|
|
31
|
-
annotations
|
|
33
|
+
annotations?: Partial<ToolAnnotations>;
|
|
32
34
|
handler: (params: {
|
|
33
35
|
input: z.infer<T>;
|
|
34
36
|
api: typeof api;
|
package/dist/tools/_factory.js
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import { ApiError, api } from '../api/client.js';
|
|
2
|
+
function wrapStructured(data) {
|
|
3
|
+
if (Array.isArray(data))
|
|
4
|
+
return { items: data };
|
|
5
|
+
if (data === null || data === undefined || typeof data !== 'object')
|
|
6
|
+
return { value: data };
|
|
7
|
+
return data;
|
|
8
|
+
}
|
|
2
9
|
function success(data) {
|
|
3
10
|
return {
|
|
4
11
|
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
5
|
-
structuredContent: data,
|
|
12
|
+
structuredContent: wrapStructured(data),
|
|
6
13
|
};
|
|
7
14
|
}
|
|
8
15
|
function error(message, details) {
|
|
@@ -16,19 +23,22 @@ function error(message, details) {
|
|
|
16
23
|
isError: true,
|
|
17
24
|
};
|
|
18
25
|
}
|
|
26
|
+
const ERROR_MAP = {
|
|
27
|
+
401: ['Authentication failed', () => 'Check your DOKPLOY_API_KEY environment variable.'],
|
|
28
|
+
403: ['Permission denied', () => 'Your API key lacks permission for this operation.'],
|
|
29
|
+
404: ['Resource not found', (err) => err.message],
|
|
30
|
+
422: [
|
|
31
|
+
'Validation error',
|
|
32
|
+
(err) => typeof err.body === 'object' && err.body !== null ? JSON.stringify(err.body) : err.message,
|
|
33
|
+
],
|
|
34
|
+
};
|
|
19
35
|
function mapApiError(err) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return error('Permission denied', 'Your API key lacks permission for this operation.');
|
|
25
|
-
case 404:
|
|
26
|
-
return error('Resource not found', err.message);
|
|
27
|
-
case 422:
|
|
28
|
-
return error('Validation error', typeof err.body === 'object' && err.body !== null ? JSON.stringify(err.body) : err.message);
|
|
29
|
-
default:
|
|
30
|
-
return error(`Dokploy API error (${err.status})`, err.message);
|
|
36
|
+
const entry = ERROR_MAP[err.status];
|
|
37
|
+
if (entry) {
|
|
38
|
+
const [message, getDetails] = entry;
|
|
39
|
+
return error(message, getDetails(err));
|
|
31
40
|
}
|
|
41
|
+
return error(`Dokploy API error (${err.status})`, err.message);
|
|
32
42
|
}
|
|
33
43
|
export function createTool(def) {
|
|
34
44
|
return {
|
|
@@ -52,17 +62,22 @@ export function createTool(def) {
|
|
|
52
62
|
};
|
|
53
63
|
}
|
|
54
64
|
export function postTool(opts) {
|
|
55
|
-
|
|
65
|
+
const tool = createTool({
|
|
56
66
|
name: opts.name,
|
|
57
67
|
title: opts.title,
|
|
58
68
|
description: opts.description,
|
|
59
69
|
schema: opts.schema,
|
|
60
|
-
annotations:
|
|
70
|
+
annotations: opts.annotations,
|
|
61
71
|
handler: async ({ input, api }) => api.post(opts.endpoint, input),
|
|
62
72
|
});
|
|
73
|
+
return {
|
|
74
|
+
...tool,
|
|
75
|
+
endpoint: opts.endpoint,
|
|
76
|
+
method: 'POST',
|
|
77
|
+
};
|
|
63
78
|
}
|
|
64
79
|
export function getTool(opts) {
|
|
65
|
-
|
|
80
|
+
const tool = createTool({
|
|
66
81
|
name: opts.name,
|
|
67
82
|
title: opts.title,
|
|
68
83
|
description: opts.description,
|
|
@@ -70,7 +85,6 @@ export function getTool(opts) {
|
|
|
70
85
|
annotations: {
|
|
71
86
|
readOnlyHint: true,
|
|
72
87
|
idempotentHint: true,
|
|
73
|
-
openWorldHint: true,
|
|
74
88
|
...opts.annotations,
|
|
75
89
|
},
|
|
76
90
|
handler: async ({ input, api }) => {
|
|
@@ -80,7 +94,12 @@ export function getTool(opts) {
|
|
|
80
94
|
params[k] = v;
|
|
81
95
|
}
|
|
82
96
|
}
|
|
83
|
-
return api.get(opts.endpoint,
|
|
97
|
+
return api.get(opts.endpoint, params);
|
|
84
98
|
},
|
|
85
99
|
});
|
|
100
|
+
return {
|
|
101
|
+
...tool,
|
|
102
|
+
endpoint: opts.endpoint,
|
|
103
|
+
method: 'GET',
|
|
104
|
+
};
|
|
86
105
|
}
|