🚀. Socket Launch Week Day 3:Socket Firewall Now Blocks Malicious VS Code and Open VSX Extensions.Learn more
Sign In

@dokploy/mcp

Package Overview
Dependencies
Maintainers
1
Versions
6
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@dokploy/mcp - npm Package Compare versions

Comparing version
0.29.2
to
0.29.3
+80
build/utils/redactSensitive.js
export const DEFAULT_REDACTED_FIELDS = [
"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",
];
const REDACTED_PLACEHOLDER = "[REDACTED]";
export function redactSensitive(data, fields) {
if (fields.length === 0)
return data;
const lowered = new Set(fields.map((f) => f.toLowerCase()));
return walk(data, lowered, new WeakSet());
}
function isPlainObject(value) {
if (value === null || typeof value !== "object")
return false;
const proto = Object.getPrototypeOf(value);
return proto === Object.prototype || proto === null;
}
function walk(value, fields, seen) {
if (Array.isArray(value)) {
if (seen.has(value))
return value;
seen.add(value);
return value.map((item) => walk(item, fields, seen));
}
if (isPlainObject(value)) {
if (seen.has(value))
return value;
seen.add(value);
const out = Object.create(null);
for (const [key, val] of Object.entries(value)) {
if (key === "__proto__" || key === "constructor" || key === "prototype")
continue;
if (fields.has(key.toLowerCase())) {
out[key] = val === null || val === undefined ? val : REDACTED_PLACEHOLDER;
}
else {
out[key] = walk(val, fields, seen);
}
}
return out;
}
return value;
}
+6
-2
import apiClient from "./utils/apiClient.js";
import { getClientConfig } from "./utils/clientConfig.js";
import { createLogger } from "./utils/logger.js";
import { redactSensitive } from "./utils/redactSensitive.js";
import { ResponseFormatter } from "./utils/responseFormatter.js";

@@ -7,8 +9,10 @@ const logger = createLogger("ToolHandler");

return async (input) => {
const { redactEnv, redactFields } = getClientConfig();
const redact = (value) => (redactEnv ? redactSensitive(value, redactFields) : value);
try {
logger.info(`Executing tool: ${tool.name}`, { input });
logger.info(`Executing tool: ${tool.name}`, { input: redact(input) });
const response = tool.method === "GET"
? await apiClient.get(tool.path, { params: input })
: await apiClient.post(tool.path, input);
return ResponseFormatter.success(`${tool.name} completed successfully`, response.data);
return ResponseFormatter.success(`${tool.name} completed successfully`, redact(response.data));
}

@@ -15,0 +19,0 @@ catch (error) {

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
import { zodToJsonSchema } from "zod-to-json-schema";
import { generatedTools } from "./generated/tools.js";

@@ -6,2 +8,3 @@ import { createHandler } from "./handler.js";

const logger = createLogger("MCP-Server");
const JSON_SCHEMA_2020_12 = "https://json-schema.org/draft/2020-12/schema";
function getEnabledTools() {

@@ -24,2 +27,34 @@ const enabledTags = process.env.DOKPLOY_ENABLED_TAGS;

}
function stripNestedSchemaKeys(value) {
if (value === null || typeof value !== "object")
return;
if (Array.isArray(value)) {
for (const item of value)
stripNestedSchemaKeys(item);
return;
}
const record = value;
for (const key of Object.keys(record)) {
if (key === "$schema") {
delete record[key];
}
else {
stripNestedSchemaKeys(record[key]);
}
}
}
// Claude's API requires JSON Schema draft 2020-12. The MCP SDK's built-in
// Zod→JSON Schema converter emits draft-07 by default, which causes a 400
// error on tools/list. We bypass the SDK's auto-generated handler by
// registering our own with pre-converted draft-2020-12 schemas.
// See https://github.com/Dokploy/mcp/issues/32
function toDraft2020_12JsonSchema(schema) {
const result = zodToJsonSchema(schema, {
target: "jsonSchema2019-09",
strictUnions: true,
});
stripNestedSchemaKeys(result);
result.$schema = JSON_SCHEMA_2020_12;
return result;
}
export function createServer() {

@@ -34,3 +69,12 @@ const server = new McpServer({

}
const toolList = tools.map((tool) => ({
name: tool.name,
description: tool.description,
inputSchema: toDraft2020_12JsonSchema(tool.schema),
annotations: tool.annotations,
}));
server.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: toolList,
}));
return server;
}

@@ -12,3 +12,4 @@ import axios from "axios";

Accept: "application/json",
"x-api-key": config.authToken, // Use the same auth mechanism as httpClient
...config.customHeaders,
"x-api-key": config.authToken,
};

@@ -15,0 +16,0 @@ // Create axios instance with configuration from clientConfig

@@ -0,1 +1,32 @@

import { DEFAULT_REDACTED_FIELDS } from "./redactSensitive.js";
const RESERVED_CUSTOM_HEADER_NAMES = new Set(["x-api-key", "content-type", "accept"]);
export function parseCustomHeaders(rawHeaders) {
if (rawHeaders === undefined) {
return {};
}
let parsed;
try {
parsed = JSON.parse(rawHeaders);
}
catch (error) {
throw new Error("Environment variable DOKPLOY_CUSTOM_HEADERS must be valid JSON containing an object of string header names to string values", { cause: error });
}
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
throw new Error("Environment variable DOKPLOY_CUSTOM_HEADERS must be a JSON object of string header names to string values");
}
const customHeaders = {};
for (const [name, value] of Object.entries(parsed)) {
if (name.trim() === "") {
throw new Error("Environment variable DOKPLOY_CUSTOM_HEADERS contains an empty header name");
}
if (RESERVED_CUSTOM_HEADER_NAMES.has(name.toLowerCase())) {
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");
}
if (typeof value !== "string") {
throw new Error("Environment variable DOKPLOY_CUSTOM_HEADERS must contain only string header values");
}
customHeaders[name] = value;
}
return customHeaders;
}
class ConfigManager {

@@ -26,8 +57,16 @@ static instance;

}
const redactEnv = parseBoolean(process.env.DOKPLOY_REDACT_ENV, false);
const parsedFields = process.env.DOKPLOY_REDACT_FIELDS?.split(",")
.map((f) => f.trim())
.filter((f) => f.length > 0) ?? [];
const redactFields = parsedFields.length > 0 ? parsedFields : DEFAULT_REDACTED_FIELDS;
return {
dokployUrl,
authToken,
customHeaders: parseCustomHeaders(process.env.DOKPLOY_CUSTOM_HEADERS),
timeout: parseInt(process.env.DOKPLOY_TIMEOUT || "30000", 10),
retryAttempts: parseInt(process.env.DOKPLOY_RETRY_ATTEMPTS || "3", 10),
retryDelay: parseInt(process.env.DOKPLOY_RETRY_DELAY || "1000", 10),
redactEnv,
redactFields,
};

@@ -39,1 +78,11 @@ }

}
function parseBoolean(value, fallback) {
if (value === undefined)
return fallback;
const normalized = value.trim().toLowerCase();
if (["true", "1", "yes", "on"].includes(normalized))
return true;
if (["false", "0", "no", "off", ""].includes(normalized))
return false;
return fallback;
}

@@ -6,3 +6,3 @@ export class ResponseFormatter {

message,
...(data && typeof data === "object" && data !== null ? { data } : {}),
...(data !== undefined && data !== null ? { data } : {}),
};

@@ -9,0 +9,0 @@ return {

{
"name": "@dokploy/mcp",
"version": "0.29.2",
"version": "0.29.3",
"description": "MCP Server for Dokploy API",

@@ -33,3 +33,4 @@ "main": "build/index.js",

"hono": "^4.12.12",
"zod": "^3.25.28"
"zod": "^3.25.28",
"zod-to-json-schema": "^3.25.2"
},

@@ -41,3 +42,4 @@ "devDependencies": {

"tsx": "^4.21.0",
"typescript": "^5.8.3"
"typescript": "^5.8.3",
"vitest": "^4.1.6"
},

@@ -62,4 +64,4 @@ "scripts": {

"precommit": "biome check && pnpm run type-check",
"test": "echo \"Error: no test spcified\" && exit 1"
"test": "vitest run"
}
}

@@ -290,2 +290,3 @@ # Dokploy MCP Server

| `DOKPLOY_API_KEY` | Yes | Your Dokploy API authentication token |
| `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`. |
| `DOKPLOY_ENABLED_TAGS` | No | Comma-separated list of tags to filter which tools are loaded (e.g., `project,application,postgres`) |

@@ -295,3 +296,11 @@ | `DOKPLOY_TIMEOUT` | No | Request timeout in milliseconds (default: `30000`) |

| `DOKPLOY_RETRY_DELAY` | No | Delay between retries in milliseconds (default: `1000`) |
| `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. |
| `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`. |
For Dokploy instances behind Cloudflare Access or a similar reverse proxy, pass service-token headers with placeholder values like this:
```bash
DOKPLOY_CUSTOM_HEADERS='{"CF-Access-Client-Id":"your-client-id.access","CF-Access-Client-Secret":"your-client-secret"}'
```
## Transport Modes

@@ -298,0 +307,0 @@