@vibetools/dokploy-mcp 2.2.0 → 2.2.1
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
CHANGED
|
@@ -94,6 +94,26 @@ await dokploy.application.one({
|
|
|
94
94
|
|
|
95
95
|
Without shaping params, behavior is identical to the raw Dokploy API -- fully backward compatible.
|
|
96
96
|
|
|
97
|
+
## Secret redaction
|
|
98
|
+
|
|
99
|
+
Git provider credentials (GitHub App private keys, client secrets, webhook secrets, Gitea/GitLab/Bitbucket tokens) are **automatically redacted** from all responses. Your AI agent sees `[REDACTED]` instead of the real values -- because leaking your private key into a context window is the kind of mistake you only make once.
|
|
100
|
+
|
|
101
|
+
Affected procedures: `application.one`, `application.many`, `github.one`, `gitea.one`, `gitlab.one`, `bitbucket.one`, `github.githubProviders`, `gitProvider.getAll`.
|
|
102
|
+
|
|
103
|
+
If you actually need the raw secrets (rotation scripts, migration, etc.), opt in explicitly:
|
|
104
|
+
|
|
105
|
+
```js
|
|
106
|
+
await dokploy.application.one({
|
|
107
|
+
applicationId: "id",
|
|
108
|
+
includeSecrets: true // you asked for it
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
await dokploy.github.one({
|
|
112
|
+
githubId: "id",
|
|
113
|
+
includeSecrets: true
|
|
114
|
+
})
|
|
115
|
+
```
|
|
116
|
+
|
|
97
117
|
## Virtual helpers
|
|
98
118
|
|
|
99
119
|
Code Mode includes MCP-side helpers for common multi-call patterns. They run inside `execute`, fan out to real Dokploy API calls, and charge every underlying call against the sandbox budget honestly.
|
|
@@ -1,5 +1,77 @@
|
|
|
1
1
|
import { procedureSchemas } from '../../generated/dokploy-schemas.js';
|
|
2
|
-
|
|
2
|
+
// Keys that hold credentials in git-provider objects (github, gitea, gitlab, bitbucket).
|
|
3
|
+
// Redacted by default — callers must pass includeSecrets: true to receive them.
|
|
4
|
+
const gitProviderSecretKeys = new Set([
|
|
5
|
+
// GitHub App
|
|
6
|
+
'githubClientSecret',
|
|
7
|
+
'githubPrivateKey',
|
|
8
|
+
'githubWebhookSecret',
|
|
9
|
+
// Gitea
|
|
10
|
+
'clientSecret',
|
|
11
|
+
'accessToken',
|
|
12
|
+
'refreshToken',
|
|
13
|
+
// GitLab
|
|
14
|
+
'secret',
|
|
15
|
+
// Bitbucket
|
|
16
|
+
'appPassword',
|
|
17
|
+
'apiToken',
|
|
18
|
+
// SSH / generic
|
|
19
|
+
'privateKey',
|
|
20
|
+
'privateKeyPass',
|
|
21
|
+
]);
|
|
22
|
+
// Top-level keys on an application object that contain nested git-provider data
|
|
23
|
+
const gitProviderNestingKeys = new Set(['github', 'gitea', 'gitlab', 'bitbucket']);
|
|
24
|
+
function redactRecord(value) {
|
|
25
|
+
const redacted = {};
|
|
26
|
+
for (const [key, val] of Object.entries(value)) {
|
|
27
|
+
if (gitProviderSecretKeys.has(key)) {
|
|
28
|
+
redacted[key] = '[REDACTED]';
|
|
29
|
+
}
|
|
30
|
+
else if (isRecord(val)) {
|
|
31
|
+
redacted[key] = redactRecord(val);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
redacted[key] = val;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return redacted;
|
|
38
|
+
}
|
|
39
|
+
function redactGitProviderSecrets(data) {
|
|
40
|
+
if (!isRecord(data)) {
|
|
41
|
+
return data;
|
|
42
|
+
}
|
|
43
|
+
if (Array.isArray(data)) {
|
|
44
|
+
return data;
|
|
45
|
+
}
|
|
46
|
+
let changed = false;
|
|
47
|
+
const result = {};
|
|
48
|
+
for (const [key, value] of Object.entries(data)) {
|
|
49
|
+
if (gitProviderSecretKeys.has(key)) {
|
|
50
|
+
result[key] = '[REDACTED]';
|
|
51
|
+
changed = true;
|
|
52
|
+
}
|
|
53
|
+
else if (gitProviderNestingKeys.has(key) && isRecord(value)) {
|
|
54
|
+
result[key] = redactRecord(value);
|
|
55
|
+
changed = true;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
result[key] = value;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return changed ? result : data;
|
|
62
|
+
}
|
|
63
|
+
function redactGitProviderArray(data) {
|
|
64
|
+
if (!Array.isArray(data)) {
|
|
65
|
+
return redactGitProviderSecrets(data);
|
|
66
|
+
}
|
|
67
|
+
return data.map((item) => redactGitProviderSecrets(item));
|
|
68
|
+
}
|
|
69
|
+
const applicationOneMcpOnlyKeys = new Set([
|
|
70
|
+
'select',
|
|
71
|
+
'includeDeployments',
|
|
72
|
+
'deploymentLimit',
|
|
73
|
+
'includeSecrets',
|
|
74
|
+
]);
|
|
3
75
|
const applicationOneInputSchema = {
|
|
4
76
|
type: 'object',
|
|
5
77
|
properties: {
|
|
@@ -19,6 +91,9 @@ const applicationOneInputSchema = {
|
|
|
19
91
|
deploymentLimit: {
|
|
20
92
|
type: 'integer',
|
|
21
93
|
},
|
|
94
|
+
includeSecrets: {
|
|
95
|
+
type: 'boolean',
|
|
96
|
+
},
|
|
22
97
|
},
|
|
23
98
|
required: ['applicationId'],
|
|
24
99
|
additionalProperties: false,
|
|
@@ -99,7 +174,28 @@ function transformApplicationOneResponse(data, input) {
|
|
|
99
174
|
return data;
|
|
100
175
|
}
|
|
101
176
|
const selected = pickSelectedFields(data, input.select);
|
|
102
|
-
|
|
177
|
+
const shaped = applyDeploymentControls(selected, input);
|
|
178
|
+
return input.includeSecrets === true ? shaped : redactGitProviderSecrets(shaped);
|
|
179
|
+
}
|
|
180
|
+
const includeSecretsMcpOnlyKeys = new Set(['includeSecrets']);
|
|
181
|
+
function mapIncludeSecretsInput(input) {
|
|
182
|
+
return Object.fromEntries(Object.entries(input).filter(([key]) => !includeSecretsMcpOnlyKeys.has(key)));
|
|
183
|
+
}
|
|
184
|
+
function transformWithSecretGate(data, input) {
|
|
185
|
+
return input.includeSecrets === true ? data : redactGitProviderSecrets(data);
|
|
186
|
+
}
|
|
187
|
+
function transformArrayWithSecretGate(data, input) {
|
|
188
|
+
return input.includeSecrets === true ? data : redactGitProviderArray(data);
|
|
189
|
+
}
|
|
190
|
+
function withIncludeSecrets(schema) {
|
|
191
|
+
const properties = (schema.properties ?? {});
|
|
192
|
+
return {
|
|
193
|
+
...schema,
|
|
194
|
+
properties: {
|
|
195
|
+
...properties,
|
|
196
|
+
includeSecrets: { type: 'boolean' },
|
|
197
|
+
},
|
|
198
|
+
};
|
|
103
199
|
}
|
|
104
200
|
const procedureOverrides = {
|
|
105
201
|
'application.one': {
|
|
@@ -108,6 +204,52 @@ const procedureOverrides = {
|
|
|
108
204
|
validateInput: validateApplicationOneInput,
|
|
109
205
|
transformResponse: transformApplicationOneResponse,
|
|
110
206
|
},
|
|
207
|
+
'github.one': {
|
|
208
|
+
inputSchema: withIncludeSecrets({
|
|
209
|
+
type: 'object',
|
|
210
|
+
properties: { githubId: { type: 'string', minLength: 1 } },
|
|
211
|
+
required: ['githubId'],
|
|
212
|
+
additionalProperties: false,
|
|
213
|
+
}),
|
|
214
|
+
mapInput: mapIncludeSecretsInput,
|
|
215
|
+
transformResponse: transformWithSecretGate,
|
|
216
|
+
},
|
|
217
|
+
'github.githubProviders': {
|
|
218
|
+
transformResponse: transformArrayWithSecretGate,
|
|
219
|
+
},
|
|
220
|
+
'gitea.one': {
|
|
221
|
+
inputSchema: withIncludeSecrets({
|
|
222
|
+
type: 'object',
|
|
223
|
+
properties: { giteaId: { type: 'string', minLength: 1 } },
|
|
224
|
+
required: ['giteaId'],
|
|
225
|
+
additionalProperties: false,
|
|
226
|
+
}),
|
|
227
|
+
mapInput: mapIncludeSecretsInput,
|
|
228
|
+
transformResponse: transformWithSecretGate,
|
|
229
|
+
},
|
|
230
|
+
'gitlab.one': {
|
|
231
|
+
inputSchema: withIncludeSecrets({
|
|
232
|
+
type: 'object',
|
|
233
|
+
properties: { gitlabId: { type: 'string', minLength: 1 } },
|
|
234
|
+
required: ['gitlabId'],
|
|
235
|
+
additionalProperties: false,
|
|
236
|
+
}),
|
|
237
|
+
mapInput: mapIncludeSecretsInput,
|
|
238
|
+
transformResponse: transformWithSecretGate,
|
|
239
|
+
},
|
|
240
|
+
'bitbucket.one': {
|
|
241
|
+
inputSchema: withIncludeSecrets({
|
|
242
|
+
type: 'object',
|
|
243
|
+
properties: { bitbucketId: { type: 'string', minLength: 1 } },
|
|
244
|
+
required: ['bitbucketId'],
|
|
245
|
+
additionalProperties: false,
|
|
246
|
+
}),
|
|
247
|
+
mapInput: mapIncludeSecretsInput,
|
|
248
|
+
transformResponse: transformWithSecretGate,
|
|
249
|
+
},
|
|
250
|
+
'gitProvider.getAll': {
|
|
251
|
+
transformResponse: transformArrayWithSecretGate,
|
|
252
|
+
},
|
|
111
253
|
};
|
|
112
254
|
function getGeneratedProcedureSchema(procedure) {
|
|
113
255
|
return procedureSchemas[procedure];
|
|
@@ -20,6 +20,9 @@ function createApplicationManyInputSchema() {
|
|
|
20
20
|
deploymentLimit: {
|
|
21
21
|
type: 'integer',
|
|
22
22
|
},
|
|
23
|
+
includeSecrets: {
|
|
24
|
+
type: 'boolean',
|
|
25
|
+
},
|
|
23
26
|
},
|
|
24
27
|
required: ['applicationIds'],
|
|
25
28
|
additionalProperties: false,
|
|
@@ -93,6 +96,9 @@ function buildApplicationOneInput(applicationId, input) {
|
|
|
93
96
|
if ('deploymentLimit' in input) {
|
|
94
97
|
nextInput.deploymentLimit = input.deploymentLimit;
|
|
95
98
|
}
|
|
99
|
+
if ('includeSecrets' in input) {
|
|
100
|
+
nextInput.includeSecrets = input.includeSecrets;
|
|
101
|
+
}
|
|
96
102
|
return nextInput;
|
|
97
103
|
}
|
|
98
104
|
async function executeApplicationMany(input, context) {
|
|
@@ -257,7 +263,7 @@ const virtualProcedureDefinitions = {
|
|
|
257
263
|
description: 'MCP-only virtual helper that fans out to application.one while preserving input order and execute call budgeting.',
|
|
258
264
|
inputKind: 'body',
|
|
259
265
|
requiredInputs: ['applicationIds'],
|
|
260
|
-
optionalInputs: ['select', 'includeDeployments', 'deploymentLimit'],
|
|
266
|
+
optionalInputs: ['select', 'includeDeployments', 'deploymentLimit', 'includeSecrets'],
|
|
261
267
|
response: {
|
|
262
268
|
type: 'object',
|
|
263
269
|
keys: ['items', 'total'],
|