@dokploy/mcp 0.29.2 → 0.29.4
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 +9 -0
- package/build/generated/tools.js +8 -8
- package/build/handler.js +6 -2
- package/build/server.js +44 -0
- package/build/utils/apiClient.js +2 -1
- package/build/utils/clientConfig.js +49 -0
- package/build/utils/redactSensitive.js +80 -0
- package/build/utils/responseFormatter.js +1 -1
- package/package.json +6 -4
package/README.md
CHANGED
|
@@ -287,10 +287,19 @@ The configuration on Windows is slightly different compared to Linux or macOS. U
|
|
|
287
287
|
|----------|----------|-------------|
|
|
288
288
|
| `DOKPLOY_URL` | Yes | Your Dokploy server URL (e.g., `https://your-dokploy-server.com`) |
|
|
289
289
|
| `DOKPLOY_API_KEY` | Yes | Your Dokploy API authentication token |
|
|
290
|
+
| `DOKPLOY_CUSTOM_HEADERS` | No | JSON object of additional upstream request headers. Header names and values must be strings. Reserved headers cannot be set here: `x-api-key`, `content-type`, `accept`. |
|
|
290
291
|
| `DOKPLOY_ENABLED_TAGS` | No | Comma-separated list of tags to filter which tools are loaded (e.g., `project,application,postgres`) |
|
|
291
292
|
| `DOKPLOY_TIMEOUT` | No | Request timeout in milliseconds (default: `30000`) |
|
|
292
293
|
| `DOKPLOY_RETRY_ATTEMPTS` | No | Number of retry attempts (default: `3`) |
|
|
293
294
|
| `DOKPLOY_RETRY_DELAY` | No | Delay between retries in milliseconds (default: `1000`) |
|
|
295
|
+
| `DOKPLOY_REDACT_ENV` | No | When `true`, redacts secret-bearing fields from API responses before they reach the MCP client (default: `false`). Useful when an LLM consumes responses and you don't want env vars or compose files in its context. |
|
|
296
|
+
| `DOKPLOY_REDACT_FIELDS` | No | Comma-separated list of response field names to redact when `DOKPLOY_REDACT_ENV=true`. Matched case-insensitively at any nesting depth. Defaults to: `env`, `buildArgs`, `composeFile`, `dockerCompose`, `environment`, `buildSecrets`, `previewBuildSecrets`, `password`, `currentPassword`, `appPassword`, `databasePassword`, `databaseRootPassword`, `redisPassword`, `mariadbPassword`, `mongoPassword`, `mysqlPassword`, `postgresPassword`, `registryPassword`, `token`, `accessToken`, `appToken`, `apiToken`, `botToken`, `refreshToken`, `secret`, `clientSecret`, `apiKey`, `secretAccessKey`, `accessKey`, `licenseKey`, `userKey`, `privateKey`, `privateKeyPass`, `encPrivateKey`, `encPrivateKeyPass`, `sshKey`, `sshPrivateKey`, `customGitSSHKey`, `dockerAuth`. |
|
|
297
|
+
|
|
298
|
+
For Dokploy instances behind Cloudflare Access or a similar reverse proxy, pass service-token headers with placeholder values like this:
|
|
299
|
+
|
|
300
|
+
```bash
|
|
301
|
+
DOKPLOY_CUSTOM_HEADERS='{"CF-Access-Client-Id":"your-client-id.access","CF-Access-Client-Secret":"your-client-secret"}'
|
|
302
|
+
```
|
|
294
303
|
|
|
295
304
|
## Transport Modes
|
|
296
305
|
|
package/build/generated/tools.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// AUTO-GENERATED FILE — DO NOT EDIT MANUALLY
|
|
2
|
-
// Generated from openapi.json on 2026-
|
|
2
|
+
// Generated from openapi.json on 2026-08-06
|
|
3
3
|
// Run `pnpm generate` to regenerate
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
export const generatedTools = [
|
|
@@ -129,7 +129,7 @@ export const generatedTools = [
|
|
|
129
129
|
tag: "application",
|
|
130
130
|
method: "POST",
|
|
131
131
|
path: "/application.saveGithubProvider",
|
|
132
|
-
schema: z.object({ "applicationId": z.string(), "repository": z.union([z.string(), z.null()]), "
|
|
132
|
+
schema: z.object({ "applicationId": z.string(), "repository": z.union([z.string(), z.null()]), "owner": z.union([z.string(), z.null()]), "buildPath": z.union([z.string(), z.null()]), "githubId": z.union([z.string(), z.null()]), "branch": z.string().regex(new RegExp("^[a-zA-Z0-9._\\-/]+$")).min(1), "triggerType": z.enum(["push", "tag"]).default("push"), "enableSubmodules": z.boolean().optional(), "watchPaths": z.union([z.array(z.string()), z.null()]).optional() }),
|
|
133
133
|
annotations: {
|
|
134
134
|
title: "Application SaveGithubProvider",
|
|
135
135
|
...{ "idempotentHint": true, "openWorldHint": true },
|
|
@@ -141,7 +141,7 @@ export const generatedTools = [
|
|
|
141
141
|
tag: "application",
|
|
142
142
|
method: "POST",
|
|
143
143
|
path: "/application.saveGitlabProvider",
|
|
144
|
-
schema: z.object({ "applicationId": z.string(), "
|
|
144
|
+
schema: z.object({ "applicationId": z.string(), "gitlabBuildPath": z.union([z.string(), z.null()]), "gitlabOwner": z.union([z.string(), z.null()]), "gitlabRepository": z.union([z.string(), z.null()]), "gitlabId": z.union([z.string(), z.null()]), "gitlabProjectId": z.union([z.number(), z.null()]), "gitlabPathNamespace": z.union([z.string(), z.null()]), "gitlabBranch": z.string().regex(new RegExp("^[a-zA-Z0-9._\\-/]+$")).min(1), "enableSubmodules": z.boolean().optional(), "watchPaths": z.union([z.array(z.string()), z.null()]).optional() }),
|
|
145
145
|
annotations: {
|
|
146
146
|
title: "Application SaveGitlabProvider",
|
|
147
147
|
...{ "idempotentHint": true, "openWorldHint": true },
|
|
@@ -153,7 +153,7 @@ export const generatedTools = [
|
|
|
153
153
|
tag: "application",
|
|
154
154
|
method: "POST",
|
|
155
155
|
path: "/application.saveBitbucketProvider",
|
|
156
|
-
schema: z.object({ "
|
|
156
|
+
schema: z.object({ "bitbucketBuildPath": z.union([z.string(), z.null()]), "bitbucketOwner": z.union([z.string(), z.null()]), "bitbucketRepository": z.union([z.string(), z.null()]), "bitbucketRepositorySlug": z.union([z.string(), z.null()]), "bitbucketId": z.union([z.string(), z.null()]), "applicationId": z.string(), "bitbucketBranch": z.string().regex(new RegExp("^[a-zA-Z0-9._\\-/]+$")).min(1), "enableSubmodules": z.boolean().optional(), "watchPaths": z.union([z.array(z.string()), z.null()]).optional() }),
|
|
157
157
|
annotations: {
|
|
158
158
|
title: "Application SaveBitbucketProvider",
|
|
159
159
|
...{ "idempotentHint": true, "openWorldHint": true },
|
|
@@ -165,7 +165,7 @@ export const generatedTools = [
|
|
|
165
165
|
tag: "application",
|
|
166
166
|
method: "POST",
|
|
167
167
|
path: "/application.saveGiteaProvider",
|
|
168
|
-
schema: z.object({ "applicationId": z.string(), "
|
|
168
|
+
schema: z.object({ "applicationId": z.string(), "giteaBuildPath": z.union([z.string(), z.null()]), "giteaOwner": z.union([z.string(), z.null()]), "giteaRepository": z.union([z.string(), z.null()]), "giteaId": z.union([z.string(), z.null()]), "giteaBranch": z.string().regex(new RegExp("^[a-zA-Z0-9._\\-/]+$")).min(1), "enableSubmodules": z.boolean().optional(), "watchPaths": z.union([z.array(z.string()), z.null()]).optional() }),
|
|
169
169
|
annotations: {
|
|
170
170
|
title: "Application SaveGiteaProvider",
|
|
171
171
|
...{ "idempotentHint": true, "openWorldHint": true },
|
|
@@ -189,7 +189,7 @@ export const generatedTools = [
|
|
|
189
189
|
tag: "application",
|
|
190
190
|
method: "POST",
|
|
191
191
|
path: "/application.saveGitProvider",
|
|
192
|
-
schema: z.object({ "
|
|
192
|
+
schema: z.object({ "applicationId": z.string(), "customGitBuildPath": z.union([z.string(), z.null()]), "customGitUrl": z.union([z.string(), z.null()]), "watchPaths": z.union([z.array(z.string()), z.null()]), "enableSubmodules": z.boolean().optional(), "customGitBranch": z.string().regex(new RegExp("^[a-zA-Z0-9._\\-/]+$")).min(1), "customGitSSHKeyId": z.union([z.string(), z.null()]).optional() }),
|
|
193
193
|
annotations: {
|
|
194
194
|
title: "Application SaveGitProvider",
|
|
195
195
|
...{ "idempotentHint": true, "openWorldHint": true },
|
|
@@ -5805,7 +5805,7 @@ export const generatedTools = [
|
|
|
5805
5805
|
tag: "schedule",
|
|
5806
5806
|
method: "POST",
|
|
5807
5807
|
path: "/schedule.create",
|
|
5808
|
-
schema: z.object({ "scheduleId": z.string().optional(), "name": z.string(), "cronExpression": z.string(), "appName": z.string().optional(), "serviceName": z.union([z.string(), z.null()]).optional(), "shellType": z.enum(["bash", "sh"]).optional(), "scheduleType": z.enum(["application", "compose", "server", "dokploy-server"]).optional(), "command": z.string(), "script": z.union([z.string(), z.null()]).optional(), "applicationId": z.union([z.string(), z.null()]).optional(), "composeId": z.union([z.string(), z.null()]).optional(), "serverId": z.union([z.string(), z.null()]).optional(), "userId": z.union([z.string(), z.null()]).optional(), "enabled": z.boolean().optional(), "timezone": z.union([z.string(), z.null()]).optional(), "createdAt": z.string().optional() }),
|
|
5808
|
+
schema: z.object({ "scheduleId": z.string().optional(), "name": z.string(), "description": z.union([z.string(), z.null()]).optional(), "cronExpression": z.string(), "appName": z.string().optional(), "serviceName": z.union([z.string(), z.null()]).optional(), "shellType": z.enum(["bash", "sh"]).optional(), "scheduleType": z.enum(["application", "compose", "server", "dokploy-server"]).optional(), "command": z.string(), "script": z.union([z.string(), z.null()]).optional(), "applicationId": z.union([z.string(), z.null()]).optional(), "composeId": z.union([z.string(), z.null()]).optional(), "serverId": z.union([z.string(), z.null()]).optional(), "userId": z.union([z.string(), z.null()]).optional(), "enabled": z.boolean().optional(), "timezone": z.union([z.string(), z.null()]).optional(), "createdAt": z.string().optional() }),
|
|
5809
5809
|
annotations: {
|
|
5810
5810
|
title: "Schedule Create",
|
|
5811
5811
|
...{ "openWorldHint": true },
|
|
@@ -5817,7 +5817,7 @@ export const generatedTools = [
|
|
|
5817
5817
|
tag: "schedule",
|
|
5818
5818
|
method: "POST",
|
|
5819
5819
|
path: "/schedule.update",
|
|
5820
|
-
schema: z.object({ "scheduleId": z.string().min(1), "name": z.string(), "cronExpression": z.string(), "appName": z.string().optional(), "serviceName": z.union([z.string(), z.null()]).optional(), "shellType": z.enum(["bash", "sh"]).optional(), "scheduleType": z.enum(["application", "compose", "server", "dokploy-server"]).optional(), "command": z.string(), "script": z.union([z.string(), z.null()]).optional(), "applicationId": z.union([z.string(), z.null()]).optional(), "composeId": z.union([z.string(), z.null()]).optional(), "serverId": z.union([z.string(), z.null()]).optional(), "userId": z.union([z.string(), z.null()]).optional(), "enabled": z.boolean().optional(), "timezone": z.union([z.string(), z.null()]).optional(), "createdAt": z.string().optional() }),
|
|
5820
|
+
schema: z.object({ "scheduleId": z.string().min(1), "name": z.string(), "description": z.union([z.string(), z.null()]).optional(), "cronExpression": z.string(), "appName": z.string().optional(), "serviceName": z.union([z.string(), z.null()]).optional(), "shellType": z.enum(["bash", "sh"]).optional(), "scheduleType": z.enum(["application", "compose", "server", "dokploy-server"]).optional(), "command": z.string(), "script": z.union([z.string(), z.null()]).optional(), "applicationId": z.union([z.string(), z.null()]).optional(), "composeId": z.union([z.string(), z.null()]).optional(), "serverId": z.union([z.string(), z.null()]).optional(), "userId": z.union([z.string(), z.null()]).optional(), "enabled": z.boolean().optional(), "timezone": z.union([z.string(), z.null()]).optional(), "createdAt": z.string().optional() }),
|
|
5821
5821
|
annotations: {
|
|
5822
5822
|
title: "Schedule Update",
|
|
5823
5823
|
...{ "idempotentHint": true, "openWorldHint": true },
|
package/build/handler.js
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import apiClient from "./utils/apiClient.js";
|
|
2
|
+
import { getClientConfig } from "./utils/clientConfig.js";
|
|
2
3
|
import { createLogger } from "./utils/logger.js";
|
|
4
|
+
import { redactSensitive } from "./utils/redactSensitive.js";
|
|
3
5
|
import { ResponseFormatter } from "./utils/responseFormatter.js";
|
|
4
6
|
const logger = createLogger("ToolHandler");
|
|
5
7
|
export function createHandler(tool) {
|
|
6
8
|
return async (input) => {
|
|
9
|
+
const { redactEnv, redactFields } = getClientConfig();
|
|
10
|
+
const redact = (value) => (redactEnv ? redactSensitive(value, redactFields) : value);
|
|
7
11
|
try {
|
|
8
|
-
logger.info(`Executing tool: ${tool.name}`, { input });
|
|
12
|
+
logger.info(`Executing tool: ${tool.name}`, { input: redact(input) });
|
|
9
13
|
const response = tool.method === "GET"
|
|
10
14
|
? await apiClient.get(tool.path, { params: input })
|
|
11
15
|
: await apiClient.post(tool.path, input);
|
|
12
|
-
return ResponseFormatter.success(`${tool.name} completed successfully`, response.data);
|
|
16
|
+
return ResponseFormatter.success(`${tool.name} completed successfully`, redact(response.data));
|
|
13
17
|
}
|
|
14
18
|
catch (error) {
|
|
15
19
|
logger.error(`Tool execution failed: ${tool.name}`, {
|
package/build/server.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
2
4
|
import { generatedTools } from "./generated/tools.js";
|
|
3
5
|
import { createHandler } from "./handler.js";
|
|
4
6
|
import { createLogger } from "./utils/logger.js";
|
|
5
7
|
const logger = createLogger("MCP-Server");
|
|
8
|
+
const JSON_SCHEMA_2020_12 = "https://json-schema.org/draft/2020-12/schema";
|
|
6
9
|
function getEnabledTools() {
|
|
7
10
|
const enabledTags = process.env.DOKPLOY_ENABLED_TAGS;
|
|
8
11
|
if (!enabledTags) {
|
|
@@ -20,6 +23,38 @@ function getEnabledTools() {
|
|
|
20
23
|
});
|
|
21
24
|
return filtered;
|
|
22
25
|
}
|
|
26
|
+
function stripNestedSchemaKeys(value) {
|
|
27
|
+
if (value === null || typeof value !== "object")
|
|
28
|
+
return;
|
|
29
|
+
if (Array.isArray(value)) {
|
|
30
|
+
for (const item of value)
|
|
31
|
+
stripNestedSchemaKeys(item);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const record = value;
|
|
35
|
+
for (const key of Object.keys(record)) {
|
|
36
|
+
if (key === "$schema") {
|
|
37
|
+
delete record[key];
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
stripNestedSchemaKeys(record[key]);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// Claude's API requires JSON Schema draft 2020-12. The MCP SDK's built-in
|
|
45
|
+
// Zod→JSON Schema converter emits draft-07 by default, which causes a 400
|
|
46
|
+
// error on tools/list. We bypass the SDK's auto-generated handler by
|
|
47
|
+
// registering our own with pre-converted draft-2020-12 schemas.
|
|
48
|
+
// See https://github.com/Dokploy/mcp/issues/32
|
|
49
|
+
function toDraft2020_12JsonSchema(schema) {
|
|
50
|
+
const result = zodToJsonSchema(schema, {
|
|
51
|
+
target: "jsonSchema2019-09",
|
|
52
|
+
strictUnions: true,
|
|
53
|
+
});
|
|
54
|
+
stripNestedSchemaKeys(result);
|
|
55
|
+
result.$schema = JSON_SCHEMA_2020_12;
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
23
58
|
export function createServer() {
|
|
24
59
|
const server = new McpServer({
|
|
25
60
|
name: "dokploy",
|
|
@@ -29,5 +64,14 @@ export function createServer() {
|
|
|
29
64
|
for (const tool of tools) {
|
|
30
65
|
server.tool(tool.name, tool.description, tool.schema.shape, tool.annotations ?? {}, createHandler(tool));
|
|
31
66
|
}
|
|
67
|
+
const toolList = tools.map((tool) => ({
|
|
68
|
+
name: tool.name,
|
|
69
|
+
description: tool.description,
|
|
70
|
+
inputSchema: toDraft2020_12JsonSchema(tool.schema),
|
|
71
|
+
annotations: tool.annotations,
|
|
72
|
+
}));
|
|
73
|
+
server.server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
74
|
+
tools: toolList,
|
|
75
|
+
}));
|
|
32
76
|
return server;
|
|
33
77
|
}
|
package/build/utils/apiClient.js
CHANGED
|
@@ -9,7 +9,8 @@ const config = getClientConfig();
|
|
|
9
9
|
const DEFAULT_HEADERS = {
|
|
10
10
|
"Content-Type": "application/json",
|
|
11
11
|
Accept: "application/json",
|
|
12
|
-
|
|
12
|
+
...config.customHeaders,
|
|
13
|
+
"x-api-key": config.authToken,
|
|
13
14
|
};
|
|
14
15
|
// Create axios instance with configuration from clientConfig
|
|
15
16
|
// Ensure baseURL includes /api prefix for Dokploy API routes
|
|
@@ -1,3 +1,34 @@
|
|
|
1
|
+
import { DEFAULT_REDACTED_FIELDS } from "./redactSensitive.js";
|
|
2
|
+
const RESERVED_CUSTOM_HEADER_NAMES = new Set(["x-api-key", "content-type", "accept"]);
|
|
3
|
+
export function parseCustomHeaders(rawHeaders) {
|
|
4
|
+
if (rawHeaders === undefined) {
|
|
5
|
+
return {};
|
|
6
|
+
}
|
|
7
|
+
let parsed;
|
|
8
|
+
try {
|
|
9
|
+
parsed = JSON.parse(rawHeaders);
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
throw new Error("Environment variable DOKPLOY_CUSTOM_HEADERS must be valid JSON containing an object of string header names to string values", { cause: error });
|
|
13
|
+
}
|
|
14
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
15
|
+
throw new Error("Environment variable DOKPLOY_CUSTOM_HEADERS must be a JSON object of string header names to string values");
|
|
16
|
+
}
|
|
17
|
+
const customHeaders = {};
|
|
18
|
+
for (const [name, value] of Object.entries(parsed)) {
|
|
19
|
+
if (name.trim() === "") {
|
|
20
|
+
throw new Error("Environment variable DOKPLOY_CUSTOM_HEADERS contains an empty header name");
|
|
21
|
+
}
|
|
22
|
+
if (RESERVED_CUSTOM_HEADER_NAMES.has(name.toLowerCase())) {
|
|
23
|
+
throw new Error("Environment variable DOKPLOY_CUSTOM_HEADERS cannot override reserved headers x-api-key, content-type, or accept; configure Dokploy authentication with DOKPLOY_API_KEY");
|
|
24
|
+
}
|
|
25
|
+
if (typeof value !== "string") {
|
|
26
|
+
throw new Error("Environment variable DOKPLOY_CUSTOM_HEADERS must contain only string header values");
|
|
27
|
+
}
|
|
28
|
+
customHeaders[name] = value;
|
|
29
|
+
}
|
|
30
|
+
return customHeaders;
|
|
31
|
+
}
|
|
1
32
|
class ConfigManager {
|
|
2
33
|
static instance;
|
|
3
34
|
config = null;
|
|
@@ -23,15 +54,33 @@ class ConfigManager {
|
|
|
23
54
|
if (!authToken) {
|
|
24
55
|
throw new Error("Environment variable DOKPLOY_API_KEY is not defined");
|
|
25
56
|
}
|
|
57
|
+
const redactEnv = parseBoolean(process.env.DOKPLOY_REDACT_ENV, false);
|
|
58
|
+
const parsedFields = process.env.DOKPLOY_REDACT_FIELDS?.split(",")
|
|
59
|
+
.map((f) => f.trim())
|
|
60
|
+
.filter((f) => f.length > 0) ?? [];
|
|
61
|
+
const redactFields = parsedFields.length > 0 ? parsedFields : DEFAULT_REDACTED_FIELDS;
|
|
26
62
|
return {
|
|
27
63
|
dokployUrl,
|
|
28
64
|
authToken,
|
|
65
|
+
customHeaders: parseCustomHeaders(process.env.DOKPLOY_CUSTOM_HEADERS),
|
|
29
66
|
timeout: parseInt(process.env.DOKPLOY_TIMEOUT || "30000", 10),
|
|
30
67
|
retryAttempts: parseInt(process.env.DOKPLOY_RETRY_ATTEMPTS || "3", 10),
|
|
31
68
|
retryDelay: parseInt(process.env.DOKPLOY_RETRY_DELAY || "1000", 10),
|
|
69
|
+
redactEnv,
|
|
70
|
+
redactFields,
|
|
32
71
|
};
|
|
33
72
|
}
|
|
34
73
|
}
|
|
35
74
|
export function getClientConfig() {
|
|
36
75
|
return ConfigManager.getInstance().getConfig();
|
|
37
76
|
}
|
|
77
|
+
function parseBoolean(value, fallback) {
|
|
78
|
+
if (value === undefined)
|
|
79
|
+
return fallback;
|
|
80
|
+
const normalized = value.trim().toLowerCase();
|
|
81
|
+
if (["true", "1", "yes", "on"].includes(normalized))
|
|
82
|
+
return true;
|
|
83
|
+
if (["false", "0", "no", "off", ""].includes(normalized))
|
|
84
|
+
return false;
|
|
85
|
+
return fallback;
|
|
86
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
export const DEFAULT_REDACTED_FIELDS = [
|
|
2
|
+
"env",
|
|
3
|
+
"buildArgs",
|
|
4
|
+
"composeFile",
|
|
5
|
+
"dockerCompose",
|
|
6
|
+
"environment",
|
|
7
|
+
"buildSecrets",
|
|
8
|
+
"previewBuildSecrets",
|
|
9
|
+
"password",
|
|
10
|
+
"currentPassword",
|
|
11
|
+
"appPassword",
|
|
12
|
+
"databasePassword",
|
|
13
|
+
"databaseRootPassword",
|
|
14
|
+
"redisPassword",
|
|
15
|
+
"mariadbPassword",
|
|
16
|
+
"mongoPassword",
|
|
17
|
+
"mysqlPassword",
|
|
18
|
+
"postgresPassword",
|
|
19
|
+
"registryPassword",
|
|
20
|
+
"token",
|
|
21
|
+
"accessToken",
|
|
22
|
+
"appToken",
|
|
23
|
+
"apiToken",
|
|
24
|
+
"botToken",
|
|
25
|
+
"refreshToken",
|
|
26
|
+
"secret",
|
|
27
|
+
"clientSecret",
|
|
28
|
+
"apiKey",
|
|
29
|
+
"secretAccessKey",
|
|
30
|
+
"accessKey",
|
|
31
|
+
"licenseKey",
|
|
32
|
+
"userKey",
|
|
33
|
+
"privateKey",
|
|
34
|
+
"privateKeyPass",
|
|
35
|
+
"encPrivateKey",
|
|
36
|
+
"encPrivateKeyPass",
|
|
37
|
+
"sshKey",
|
|
38
|
+
"sshPrivateKey",
|
|
39
|
+
"customGitSSHKey",
|
|
40
|
+
"dockerAuth",
|
|
41
|
+
];
|
|
42
|
+
const REDACTED_PLACEHOLDER = "[REDACTED]";
|
|
43
|
+
export function redactSensitive(data, fields) {
|
|
44
|
+
if (fields.length === 0)
|
|
45
|
+
return data;
|
|
46
|
+
const lowered = new Set(fields.map((f) => f.toLowerCase()));
|
|
47
|
+
return walk(data, lowered, new WeakSet());
|
|
48
|
+
}
|
|
49
|
+
function isPlainObject(value) {
|
|
50
|
+
if (value === null || typeof value !== "object")
|
|
51
|
+
return false;
|
|
52
|
+
const proto = Object.getPrototypeOf(value);
|
|
53
|
+
return proto === Object.prototype || proto === null;
|
|
54
|
+
}
|
|
55
|
+
function walk(value, fields, seen) {
|
|
56
|
+
if (Array.isArray(value)) {
|
|
57
|
+
if (seen.has(value))
|
|
58
|
+
return value;
|
|
59
|
+
seen.add(value);
|
|
60
|
+
return value.map((item) => walk(item, fields, seen));
|
|
61
|
+
}
|
|
62
|
+
if (isPlainObject(value)) {
|
|
63
|
+
if (seen.has(value))
|
|
64
|
+
return value;
|
|
65
|
+
seen.add(value);
|
|
66
|
+
const out = Object.create(null);
|
|
67
|
+
for (const [key, val] of Object.entries(value)) {
|
|
68
|
+
if (key === "__proto__" || key === "constructor" || key === "prototype")
|
|
69
|
+
continue;
|
|
70
|
+
if (fields.has(key.toLowerCase())) {
|
|
71
|
+
out[key] = val === null || val === undefined ? val : REDACTED_PLACEHOLDER;
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
out[key] = walk(val, fields, seen);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
return value;
|
|
80
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dokploy/mcp",
|
|
3
|
-
"version": "0.29.
|
|
3
|
+
"version": "0.29.4",
|
|
4
4
|
"description": "MCP Server for Dokploy API",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -30,14 +30,16 @@
|
|
|
30
30
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
31
31
|
"axios": "^1.9.0",
|
|
32
32
|
"hono": "^4.12.12",
|
|
33
|
-
"zod": "^3.25.28"
|
|
33
|
+
"zod": "^3.25.28",
|
|
34
|
+
"zod-to-json-schema": "^3.25.2"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
36
37
|
"@biomejs/biome": "^2.4.10",
|
|
37
38
|
"@types/node": "^22.15.21",
|
|
38
39
|
"json-schema-to-zod": "^2.8.1",
|
|
39
40
|
"tsx": "^4.21.0",
|
|
40
|
-
"typescript": "^5.8.3"
|
|
41
|
+
"typescript": "^5.8.3",
|
|
42
|
+
"vitest": "^4.1.6"
|
|
41
43
|
},
|
|
42
44
|
"scripts": {
|
|
43
45
|
"build": "pnpm run clean && tsc && chmod 755 build/index.js",
|
|
@@ -57,6 +59,6 @@
|
|
|
57
59
|
"type-check": "tsc --noEmit",
|
|
58
60
|
"clean": "rm -rf build",
|
|
59
61
|
"precommit": "biome check && pnpm run type-check",
|
|
60
|
-
"test": "
|
|
62
|
+
"test": "vitest run"
|
|
61
63
|
}
|
|
62
64
|
}
|