mcp-dokploy-fullapi-proxy 1.0.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/src/index.ts ADDED
@@ -0,0 +1,111 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { z } from "zod";
4
+
5
+ const DOKPLOY_URL = process.env.DOKPLOY_URL ?? "http://localhost:3000/api";
6
+ const DOKPLOY_TOKEN = process.env.DOKPLOY_TOKEN ?? process.env.DOKPLOY_API_KEY ?? "";
7
+
8
+ if (!DOKPLOY_TOKEN) {
9
+ console.error("DOKPLOY_TOKEN env var required");
10
+ process.exit(1);
11
+ }
12
+
13
+ const server = new McpServer({
14
+ name: "mcp-dokploy-fullapi-proxy",
15
+ version: "1.0.0",
16
+ });
17
+
18
+ function pickFields(data: unknown, fields: string[]): unknown {
19
+ if (!data || typeof data !== "object") return data;
20
+ if (Array.isArray(data)) return data.map(item => pickFields(item, fields));
21
+ const obj = data as Record<string, unknown>;
22
+ const result: Record<string, unknown> = {};
23
+ for (const key of Object.keys(obj)) {
24
+ if (fields.includes(key)) {
25
+ result[key] = obj[key];
26
+ } else if (typeof obj[key] === "object" && obj[key] !== null) {
27
+ const nested = pickFields(obj[key], fields);
28
+ // nur hinzufügen wenn nested nicht leer ist
29
+ if (Array.isArray(nested) ? (nested as unknown[]).length > 0 : Object.keys(nested as object).length > 0) {
30
+ result[key] = nested;
31
+ }
32
+ }
33
+ }
34
+ return result;
35
+ }
36
+
37
+ server.tool(
38
+ "dokploy",
39
+ "Universal Dokploy API proxy. Calls any tRPC endpoint. " +
40
+ "Use method like 'project.all', 'application.deploy', 'compose.update'. " +
41
+ "Params are passed as JSON body to the API. " +
42
+ "Optional 'pick' filters the response to only include specified field names (e.g. pick: ['mysql'] on project.all returns only mysql arrays).",
43
+ { method: z.string(), params: z.record(z.string(), z.unknown()).optional(), pick: z.array(z.string()).optional() },
44
+ async ({ method, params, pick }) => {
45
+ const baseUrl = `${DOKPLOY_URL}/trpc/${method}`;
46
+ const hasParams = params && Object.keys(params).length > 0;
47
+
48
+ try {
49
+ let res: Response;
50
+ if (hasParams) {
51
+ // tRPC GET queries: params as ?input=<JSON>
52
+ const qs = new URLSearchParams({ input: JSON.stringify({ json: params }) });
53
+ res = await fetch(`${baseUrl}?${qs}`, {
54
+ method: "GET",
55
+ headers: { "x-api-key": DOKPLOY_TOKEN },
56
+ });
57
+ // Fallback to POST for mutations
58
+ if (res.status === 405) {
59
+ res = await fetch(baseUrl, {
60
+ method: "POST",
61
+ headers: { "Content-Type": "application/json", "x-api-key": DOKPLOY_TOKEN },
62
+ body: JSON.stringify({ json: params }),
63
+ });
64
+ }
65
+ } else {
66
+ res = await fetch(baseUrl, {
67
+ method: "GET",
68
+ headers: { "x-api-key": DOKPLOY_TOKEN },
69
+ });
70
+ if (res.status === 405) {
71
+ res = await fetch(baseUrl, {
72
+ method: "POST",
73
+ headers: { "Content-Type": "application/json", "x-api-key": DOKPLOY_TOKEN },
74
+ body: "{}",
75
+ });
76
+ }
77
+ }
78
+
79
+ const text = await res.text();
80
+ let data: unknown;
81
+ try { data = JSON.parse(text); } catch { data = text; }
82
+
83
+ if (!res.ok) {
84
+ const msg = typeof data === "object" ? JSON.stringify(data, null, 2) : String(data);
85
+ return { content: [{ type: "text" as const, text: `❌ ${res.status} ${res.statusText}\n${msg}` }], isError: true };
86
+ }
87
+
88
+ const result = typeof data === "object" && data !== null && "result" in data
89
+ ? (data as Record<string, unknown>).result
90
+ : data;
91
+ const output = typeof result === "object" && result !== null && "data" in result
92
+ ? (result as Record<string, unknown>).data
93
+ : result;
94
+
95
+ const filtered = pick && pick.length > 0 ? pickFields(output, pick) : output;
96
+ return { content: [{ type: "text" as const, text: JSON.stringify(filtered, null, 2) }] };
97
+ } catch (e) {
98
+ return { content: [{ type: "text" as const, text: `❌ Network error: ${e instanceof Error ? e.message : String(e)}` }], isError: true };
99
+ }
100
+ }
101
+ );
102
+
103
+ async function main() {
104
+ const transport = new StdioServerTransport();
105
+ await server.connect(transport);
106
+ }
107
+
108
+ main().catch((e) => {
109
+ console.error("Fatal:", e);
110
+ process.exit(1);
111
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "Node16",
5
+ "moduleResolution": "Node16",
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "declaration": true,
13
+ "sourceMap": true
14
+ },
15
+ "include": ["src/**/*"],
16
+ "exclude": ["node_modules", "dist"]
17
+ }