Two problems found while measuring the MCP surface of @dokploy/mcp@0.30.2 against a live Dokploy instance. The first is a hard bug; the second is a usability issue that shows up specifically with LLM clients.
1. dnsProvider-createRecord and dnsProvider-updateRecord are rejected by the Anthropic API
Both tools are dropped at connection time by Claude clients, with:
schema/properties/ttl/exclusiveMinimum must be number
exclusiveMinimum is being emitted as a boolean (JSON Schema draft-04 style, where it modified minimum) instead of a number (draft 2020-12, which the MCP spec and the Anthropic API follow).
Effect: those two tools silently do not exist for any Claude-based client. The rest of the server's 597 tools load fine, so the failure is easy to miss — nothing errors, DNS record creation is just absent from the tool list.
Likely origin: a .positive() / exclusiveMinimum: true on the ttl field surviving a zod-to-json-schema conversion pinned to an older draft. Worth grepping for other numeric constraints emitted the same way — exclusiveMaximum has the identical draft-04/2020-12 split.
2. All 597 tool descriptions are the bare route line
Every description is the HTTP verb plus the tRPC path:
application-create -> "POST /application.create"
application-one -> "GET /application.one"
admin-setupMonitoring -> "POST /admin.setupMonitoring"
Mean description length across all 597 tools: 25 characters. Total: 15 133 chars of description against 222 983 chars of schema.
The problem this creates is concrete. Ask an agent to redeploy a stack and it must choose between compose-deploy, compose-redeploy, compose-start and compose-reload — four tools whose descriptions are four route strings. The name is the only signal, and the names do not disambiguate. Same for application-reload vs application-redeploy vs application-markRunning, and for settings-cleanAll / -cleanDockerPrune / -cleanUnusedImages / -cleanUnusedVolumes / -cleanStoppedContainers.
The agent then either guesses, or spends a turn on docs, or calls the wrong one — and several of these are destructive on a production host.
One sentence per tool would fix it, at roughly 60 chars each:
compose-redeploy -> "Redeploy an existing compose stack, reusing the current build."
compose-reload -> "Restart the running services without redeploying."
That is ~36 KB of descriptions added against 218 KB of schema already being shipped — a small relative cost for the thing that makes the schema usable. Prioritising the destructive and near-synonymous tools first would capture most of the benefit.
Both would also help clients that surface tools through a search step rather than loading all 597 eagerly: search ranks on name and description, and right now there is effectively only the name to rank on.
Environment
@dokploy/mcp@0.30.2 via npx -y, stdio transport
- Dokploy server: self-hosted
- Client: Claude Code
Captured with:
{ printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"probe","version":"1"}}}' \
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}'; sleep 25; } \
| DOKPLOY_URL=https://your-instance DOKPLOY_API_KEY=... npx -y @dokploy/mcp > raw.txt
Unrelated but worth saying: DOKPLOY_ENABLED_TAGS is exactly the right escape hatch for the size of this surface, and it works well — 597 tools down to 162 for a deploy-focused tag set. Thanks for shipping it.
Two problems found while measuring the MCP surface of
@dokploy/mcp@0.30.2against a live Dokploy instance. The first is a hard bug; the second is a usability issue that shows up specifically with LLM clients.1.
dnsProvider-createRecordanddnsProvider-updateRecordare rejected by the Anthropic APIBoth tools are dropped at connection time by Claude clients, with:
exclusiveMinimumis being emitted as a boolean (JSON Schema draft-04 style, where it modifiedminimum) instead of a number (draft 2020-12, which the MCP spec and the Anthropic API follow).Effect: those two tools silently do not exist for any Claude-based client. The rest of the server's 597 tools load fine, so the failure is easy to miss — nothing errors, DNS record creation is just absent from the tool list.
Likely origin: a
.positive()/exclusiveMinimum: trueon thettlfield surviving a zod-to-json-schema conversion pinned to an older draft. Worth grepping for other numeric constraints emitted the same way —exclusiveMaximumhas the identical draft-04/2020-12 split.2. All 597 tool descriptions are the bare route line
Every description is the HTTP verb plus the tRPC path:
Mean description length across all 597 tools: 25 characters. Total: 15 133 chars of description against 222 983 chars of schema.
The problem this creates is concrete. Ask an agent to redeploy a stack and it must choose between
compose-deploy,compose-redeploy,compose-startandcompose-reload— four tools whose descriptions are four route strings. The name is the only signal, and the names do not disambiguate. Same forapplication-reloadvsapplication-redeployvsapplication-markRunning, and forsettings-cleanAll/-cleanDockerPrune/-cleanUnusedImages/-cleanUnusedVolumes/-cleanStoppedContainers.The agent then either guesses, or spends a turn on docs, or calls the wrong one — and several of these are destructive on a production host.
One sentence per tool would fix it, at roughly 60 chars each:
That is ~36 KB of descriptions added against 218 KB of schema already being shipped — a small relative cost for the thing that makes the schema usable. Prioritising the destructive and near-synonymous tools first would capture most of the benefit.
Both would also help clients that surface tools through a search step rather than loading all 597 eagerly: search ranks on name and description, and right now there is effectively only the name to rank on.
Environment
@dokploy/mcp@0.30.2vianpx -y, stdio transportCaptured with:
{ printf '%s\n' \ '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"probe","version":"1"}}}' \ '{"jsonrpc":"2.0","method":"notifications/initialized"}' \ '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'; sleep 25; } \ | DOKPLOY_URL=https://your-instance DOKPLOY_API_KEY=... npx -y @dokploy/mcp > raw.txtUnrelated but worth saying:
DOKPLOY_ENABLED_TAGSis exactly the right escape hatch for the size of this surface, and it works well — 597 tools down to 162 for a deploy-focused tag set. Thanks for shipping it.