Skip to content

Commit de70dcc

Browse files
authored
feat(dokploy): cover complete MCP action catalog (#85)
## Summary - add exact coverage for all 524 tools committed in `Dokploy/mcp@db18449eafdfc8dbd438d392b95c46292069c658` - expose a static, searchable action catalog split across 48 Dokploy domains, backed by one lazy shared runtime - preserve Dokploy MCP tool names and request schemas, with 186 GET actions and 338 POST actions - support the two multipart tools through OpenConnector transit files: - `application-dropDeployment` - `docker-uploadFileToContainer` - keep `x-api-key` authentication, public-instance URL validation, request timeouts, bounded responses, and redacted error details ## Scope This PR targets the generated tool catalog committed in the pinned Dokploy MCP revision above. It does **not** claim parity with Dokploy's newer repository-root OpenAPI document, which currently contains additional endpoints that are not yet part of that MCP catalog. All 524 actions are statically defined and locally executable; this does not add an unrestricted API proxy. Operations that can remove, stop, or otherwise disrupt resources receive an explicit warning when the upstream specification does not provide a description. ## Reproducible generation `npm run generate:provider -- dokploy` dispatches to the provider-owned generator, which downloads the pinned generated OpenAPI document and verifies SHA-256 `225972ade1e545cc9a44638e4ff32d73a23ea48298617ca3a37fb5cfff6a058e` before generating and formatting the 48 domain modules. Additional arguments are forwarded only to that generator. An optional local source is accepted only when it has the same digest. The generation pipeline was run twice with identical output. ## Generated-schema exception The repository guideline normally prefers hand-authored `s` schema helpers for provider definitions. This PR requests a deliberate large-provider exception, following the existing generated-provider precedent used by integrations such as UnifAPI and Postman: - 524 upstream schemas are too large and change-prone to maintain safely by hand - the source is pinned by commit and content hash - generated output is split by domain instead of placed in one monolithic file - an independent 524-name snapshot test detects missing, duplicate, or renamed MCP tools - parity tests also enforce 48 tags, action/handler coverage, representative schema constraints, and exactly two multipart operations ## Runtime behavior - maps path, repeated query, JSON-body, and multipart fields from static operation metadata - preserves successful token and SSH-key responses; only error details are recursively redacted - limits response bodies to 10 MiB and error messages to 16 KiB - preserves upstream error statuses, including bounded plain-text error responses - requires a public HTTP(S) Dokploy instance URL and rejects embedded credentials and private/reserved targets ## Validation - Dokploy generation pipeline: 524 actions / 48 domains / 0 unsupported; repeated run produced identical output - provider registry and catalog generation: 1,064 apps / 10,768 actions - TypeScript checks: `src`, `scripts-all`, and `examples` passed - lint and formatting checks passed - full test suite: 38 files / 246 tests passed
1 parent 6f044f0 commit de70dcc

58 files changed

Lines changed: 29591 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"typecheck": "npm run generate:registry && node scripts/typecheck.ts src scripts-all examples",
2020
"generate:registry": "node scripts/generate-provider-registry.ts",
2121
"generate:catalog": "npm run generate:registry && node scripts/generate-catalog.ts",
22+
"generate:provider": "node scripts/generate-provider.ts",
2223
"runtime:data": "node scripts/runtime-data.ts",
2324
"lint": "oxlint .",
2425
"lint:fix": "oxlint . --fix",

scripts/generate-provider.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { spawnSync } from "node:child_process";
2+
import { stat } from "node:fs/promises";
3+
import { join } from "node:path";
4+
import { assertProviderId } from "../src/core/provider-id.ts";
5+
6+
const [provider, ...generatorArguments] = process.argv.slice(2);
7+
if (!provider) {
8+
throw new Error("Usage: npm run generate:provider -- <provider> [...generator arguments]");
9+
}
10+
11+
assertProviderId(provider, "provider generator id");
12+
const generatorPath = join(process.cwd(), "src/providers", provider, "generate.ts");
13+
if (!(await isFile(generatorPath))) {
14+
throw new Error(`Provider does not define a generator: ${provider}`);
15+
}
16+
17+
const result = spawnSync(process.execPath, [generatorPath, ...generatorArguments], {
18+
cwd: process.cwd(),
19+
stdio: "inherit",
20+
});
21+
if (result.error) throw result.error;
22+
if (result.status !== 0) process.exit(result.status ?? 1);
23+
24+
async function isFile(path: string): Promise<boolean> {
25+
try {
26+
return (await stat(path)).isFile();
27+
} catch (error) {
28+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
29+
throw error;
30+
}
31+
}

src/providers/dokploy/actions.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import type { ProviderActionDefinition } from "../../core/provider-definition.ts";
2+
3+
import { defineProviderAction } from "../../core/provider-definition.ts";
4+
import { dokployOperations } from "./operations.ts";
5+
6+
export type { DokployActionName } from "./operations.ts";
7+
8+
const service = "dokploy";
9+
10+
export const dokployActions: ProviderActionDefinition[] = dokployOperations.map((operation) =>
11+
defineProviderAction(service, {
12+
name: operation.name,
13+
description: operation.description,
14+
inputSchema: operation.inputSchema,
15+
outputSchema: operation.outputSchema,
16+
}),
17+
);
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import type { ProviderDefinition } from "../../core/types.ts";
2+
3+
import { dokployActions } from "./actions.ts";
4+
5+
const service = "dokploy";
6+
7+
export const provider: ProviderDefinition = {
8+
service,
9+
displayName: "Dokploy",
10+
description: "Manage infrastructure, services, deployments, access, and settings on a self-hosted Dokploy instance.",
11+
categories: ["Developer Tools", "Infrastructure"],
12+
authTypes: ["api_key"],
13+
auth: [
14+
{
15+
type: "api_key",
16+
label: "API Key",
17+
placeholder: "Enter your Dokploy API key",
18+
description:
19+
"An API key created from the Dokploy dashboard under Settings > API Keys. The key is sent in the x-api-key header.",
20+
extraFields: [
21+
{
22+
key: "baseUrl",
23+
label: "Instance URL",
24+
inputType: "text",
25+
required: true,
26+
secret: false,
27+
placeholder: "https://dokploy.example.com",
28+
description:
29+
"The public HTTP or HTTPS URL of your Dokploy instance, without an API endpoint path. See https://docs.dokploy.com/docs/core/api.",
30+
},
31+
],
32+
},
33+
],
34+
homepageUrl: "https://dokploy.com",
35+
actions: dokployActions,
36+
};

src/providers/dokploy/executors.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import type {
2+
CredentialValidationResult,
3+
CredentialValidators,
4+
ExecutionContext,
5+
ProviderExecutors,
6+
} from "../../core/types.ts";
7+
import type { DokployActionContext } from "./runtime.ts";
8+
9+
import { defineProviderExecutors, requireApiKeyCredential } from "../provider-runtime.ts";
10+
import { createDokployContext, dokployActionHandlers, validateDokployCredential } from "./runtime.ts";
11+
12+
const service = "dokploy";
13+
14+
export const executors: ProviderExecutors = defineProviderExecutors<DokployActionContext>({
15+
service,
16+
handlers: dokployActionHandlers,
17+
async createContext(context: ExecutionContext, fetcher: typeof fetch): Promise<DokployActionContext> {
18+
const credential = await requireApiKeyCredential(context, service);
19+
return createDokployContext(credential.values, credential.apiKey, fetcher, context.signal, context.transitFiles);
20+
},
21+
fallbackMessage: "Dokploy request failed",
22+
});
23+
24+
export const credentialValidators: CredentialValidators = {
25+
apiKey(input, { fetcher, signal }): Promise<CredentialValidationResult> {
26+
return validateDokployCredential(input.values, input.apiKey, fetcher, signal);
27+
},
28+
};

0 commit comments

Comments
 (0)