From dcaeb3d7fd8b0876e842cbf1a1948a98c05f0c5a Mon Sep 17 00:00:00 2001 From: Macawls Date: Sun, 15 Jun 2025 03:33:52 +0200 Subject: [PATCH] feat: add compose tools --- TOOLS.md | 118 +++++++++++++++++- src/mcp/tools/compose/composeCreate.ts | 58 +++++++++ src/mcp/tools/compose/composeDeploy.ts | 26 ++++ src/mcp/tools/compose/composeOne.ts | 37 ++++++ src/mcp/tools/compose/composeReload.ts | 27 ++++ src/mcp/tools/compose/composeRemove.ts | 26 ++++ .../tools/compose/composeSaveEnvironment.ts | 33 +++++ src/mcp/tools/compose/composeStart.ts | 26 ++++ src/mcp/tools/compose/composeStop.ts | 26 ++++ src/mcp/tools/compose/composeUpdate.ts | 67 ++++++++++ src/mcp/tools/compose/index.ts | 9 ++ src/mcp/tools/index.ts | 2 + 12 files changed, 454 insertions(+), 1 deletion(-) create mode 100644 src/mcp/tools/compose/composeCreate.ts create mode 100644 src/mcp/tools/compose/composeDeploy.ts create mode 100644 src/mcp/tools/compose/composeOne.ts create mode 100644 src/mcp/tools/compose/composeReload.ts create mode 100644 src/mcp/tools/compose/composeRemove.ts create mode 100644 src/mcp/tools/compose/composeSaveEnvironment.ts create mode 100644 src/mcp/tools/compose/composeStart.ts create mode 100644 src/mcp/tools/compose/composeStop.ts create mode 100644 src/mcp/tools/compose/composeUpdate.ts create mode 100644 src/mcp/tools/compose/index.ts diff --git a/TOOLS.md b/TOOLS.md index c9a0b6e..b37989e 100644 --- a/TOOLS.md +++ b/TOOLS.md @@ -4,10 +4,11 @@ This document provides detailed information about all available tools in the Dok ## 📊 Overview -- **Total Tools**: 43 +- **Total Tools**: 52 - **Project Tools**: 6 - **Application Tools**: 24 - **PostgreSQL Tools**: 13 +- **Compose Tools**: 9 All tools include semantic annotations to help MCP clients understand their behavior and are designed to interact with the Dokploy API. @@ -751,6 +752,121 @@ All tools include semantic annotations to help MCP clients understand their beha } ``` +## 🐳 Docker Compose Tools + +### `compose-one` + +- **Description**: Gets a specific compose service by its ID in Dokploy +- **Input Schema**: + ```json + { + "composeId": "string" + } + ``` +- **Annotations**: Read-only, Idempotent +- **Required Fields**: `composeId` + +### `compose-create` + +- **Description**: Creates a new compose service in Dokploy +- **Input Schema**: + ```json + { + "name": "string", + "appName": "string|optional", + "description": "string|null", + "projectId": "string", + "serverId": "string|null", + "composeFile": "string|optional", + "env": "string|null", + "composeType": "docker-compose|stack" + } + ``` +- **Annotations**: Creation tool (non-destructive) +- **Required Fields**: `name`, `projectId` +- **Default Values**: `composeType` defaults to "docker-compose" + +### `compose-update` + +- **Description**: Updates an existing compose service in Dokploy +- **Input Schema**: Complex schema with service configuration fields including name, environment, compose file content, and status +- **Annotations**: Non-destructive, Idempotent +- **Required Fields**: `composeId` + +### `compose-deploy` + +- **Description**: Deploys a compose service in Dokploy +- **Input Schema**: + ```json + { + "composeId": "string" + } + ``` +- **Annotations**: Non-destructive +- **Required Fields**: `composeId` + +### `compose-start` + +- **Description**: Starts a compose service in Dokploy +- **Input Schema**: + ```json + { + "composeId": "string" + } + ``` +- **Annotations**: Non-destructive +- **Required Fields**: `composeId` + +### `compose-stop` + +- **Description**: Stops a compose service in Dokploy +- **Input Schema**: + ```json + { + "composeId": "string" + } + ``` +- **Annotations**: Non-destructive, Idempotent +- **Required Fields**: `composeId` + +### `compose-reload` + +- **Description**: Reloads a compose service in Dokploy +- **Input Schema**: + ```json + { + "composeId": "string", + "appName": "string" + } + ``` +- **Annotations**: Non-destructive +- **Required Fields**: `composeId`, `appName` + +### `compose-remove` + +- **Description**: Removes/deletes a compose service from Dokploy +- **Input Schema**: + ```json + { + "composeId": "string" + } + ``` +- **Annotations**: Destructive +- **Required Fields**: `composeId` + +### `compose-saveEnvironment` + +- **Description**: Saves environment variables for a compose service in Dokploy +- **Input Schema**: + ```json + { + "composeId": "string", + "env": "string|null" + } + ``` +- **Annotations**: Non-destructive, Idempotent +- **Required Fields**: `composeId` + ## 📝 Notes - All nullable fields can accept `null` values but must be provided if marked as required diff --git a/src/mcp/tools/compose/composeCreate.ts b/src/mcp/tools/compose/composeCreate.ts new file mode 100644 index 0000000..70dab82 --- /dev/null +++ b/src/mcp/tools/compose/composeCreate.ts @@ -0,0 +1,58 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { createTool } from "../toolFactory.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; + +export const composeCreate = createTool({ + name: "compose-create", + description: "Creates a new compose service in Dokploy.", + schema: z.object({ + name: z.string().min(1).describe("The name of the compose service."), + appName: z + .string() + .optional() + .describe("The app name for the compose service."), + description: z + .string() + .nullable() + .optional() + .describe("An optional description for the compose service."), + projectId: z + .string() + .min(1) + .describe("The ID of the project where the compose service will be created."), + serverId: z + .string() + .nullable() + .optional() + .describe("The ID of the server where the compose service will be deployed."), + composeFile: z + .string() + .optional() + .describe("The docker-compose.yml content."), + env: z + .string() + .nullable() + .optional() + .describe("Environment variables for the compose service."), + composeType: z + .enum(["docker-compose", "stack"]) + .optional() + .default("docker-compose") + .describe("The type of compose deployment."), + }), + annotations: { + title: "Create Compose Service", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/compose.create", input); + + return ResponseFormatter.success( + `Compose service "${input.name}" created successfully in project "${input.projectId}"`, + response.data + ); + }, +}); diff --git a/src/mcp/tools/compose/composeDeploy.ts b/src/mcp/tools/compose/composeDeploy.ts new file mode 100644 index 0000000..751b50d --- /dev/null +++ b/src/mcp/tools/compose/composeDeploy.ts @@ -0,0 +1,26 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const composeDeploy = createTool({ + name: "compose-deploy", + description: "Deploys a compose service in Dokploy.", + schema: z.object({ + composeId: z.string().describe("The ID of the compose service to deploy."), + }), + annotations: { + title: "Deploy Compose Service", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/compose.deploy", input); + + return ResponseFormatter.success( + `Compose service "${input.composeId}" deployment started successfully`, + response.data + ); + }, +}); diff --git a/src/mcp/tools/compose/composeOne.ts b/src/mcp/tools/compose/composeOne.ts new file mode 100644 index 0000000..7e5b673 --- /dev/null +++ b/src/mcp/tools/compose/composeOne.ts @@ -0,0 +1,37 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { createTool } from "../toolFactory.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; + +export const composeOne = createTool({ + name: "compose-one", + description: "Gets a specific compose service by its ID in Dokploy.", + schema: z.object({ + composeId: z + .string() + .describe("The ID of the compose service to retrieve."), + }), + annotations: { + title: "Get Compose Service Details", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const compose = await apiClient.get( + `/compose.one?composeId=${input.composeId}` + ); + + if (!compose?.data) { + return ResponseFormatter.error( + "Failed to fetch compose service", + `Compose service with ID "${input.composeId}" not found` + ); + } + + return ResponseFormatter.success( + `Successfully fetched compose service "${input.composeId}"`, + compose.data + ); + }, +}); diff --git a/src/mcp/tools/compose/composeReload.ts b/src/mcp/tools/compose/composeReload.ts new file mode 100644 index 0000000..003475e --- /dev/null +++ b/src/mcp/tools/compose/composeReload.ts @@ -0,0 +1,27 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const composeReload = createTool({ + name: "compose-reload", + description: "Reloads a compose service in Dokploy.", + schema: z.object({ + composeId: z.string().describe("The ID of the compose service to reload."), + appName: z.string().describe("The app name of the compose service to reload."), + }), + annotations: { + title: "Reload Compose Service", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/compose.reload", input); + + return ResponseFormatter.success( + `Compose service "${input.composeId}" reloaded successfully`, + response.data + ); + }, +}); diff --git a/src/mcp/tools/compose/composeRemove.ts b/src/mcp/tools/compose/composeRemove.ts new file mode 100644 index 0000000..d2a8ee8 --- /dev/null +++ b/src/mcp/tools/compose/composeRemove.ts @@ -0,0 +1,26 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const composeRemove = createTool({ + name: "compose-remove", + description: "Removes/deletes a compose service from Dokploy.", + schema: z.object({ + composeId: z.string().describe("The ID of the compose service to remove."), + }), + annotations: { + title: "Remove Compose Service", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/compose.remove", input); + + return ResponseFormatter.success( + `Compose service "${input.composeId}" removed successfully`, + response.data + ); + }, +}); diff --git a/src/mcp/tools/compose/composeSaveEnvironment.ts b/src/mcp/tools/compose/composeSaveEnvironment.ts new file mode 100644 index 0000000..54476d9 --- /dev/null +++ b/src/mcp/tools/compose/composeSaveEnvironment.ts @@ -0,0 +1,33 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const composeSaveEnvironment = createTool({ + name: "compose-saveEnvironment", + description: "Saves environment variables for a compose service in Dokploy.", + schema: z.object({ + composeId: z + .string() + .describe("The ID of the compose service to save environment for."), + env: z + .string() + .nullable() + .optional() + .describe("Environment variables to save for the compose service."), + }), + annotations: { + title: "Save Compose Environment Variables", + destructiveHint: false, + idempotentHint: true, + openWorldHint: false, + }, + handler: async (input) => { + const response = await apiClient.post("/compose.saveEnvironment", input); + + return ResponseFormatter.success( + `Environment variables saved for compose service "${input.composeId}"`, + response.data + ); + }, +}); diff --git a/src/mcp/tools/compose/composeStart.ts b/src/mcp/tools/compose/composeStart.ts new file mode 100644 index 0000000..2605659 --- /dev/null +++ b/src/mcp/tools/compose/composeStart.ts @@ -0,0 +1,26 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const composeStart = createTool({ + name: "compose-start", + description: "Starts a compose service in Dokploy.", + schema: z.object({ + composeId: z.string().describe("The ID of the compose service to start."), + }), + annotations: { + title: "Start Compose Service", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/compose.start", input); + + return ResponseFormatter.success( + `Compose service "${input.composeId}" started successfully`, + response.data + ); + }, +}); diff --git a/src/mcp/tools/compose/composeStop.ts b/src/mcp/tools/compose/composeStop.ts new file mode 100644 index 0000000..f0d9d8b --- /dev/null +++ b/src/mcp/tools/compose/composeStop.ts @@ -0,0 +1,26 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const composeStop = createTool({ + name: "compose-stop", + description: "Stops a compose service in Dokploy.", + schema: z.object({ + composeId: z.string().describe("The ID of the compose service to stop."), + }), + annotations: { + title: "Stop Compose Service", + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/compose.stop", input); + + return ResponseFormatter.success( + `Compose service "${input.composeId}" stopped successfully`, + response.data + ); + }, +}); diff --git a/src/mcp/tools/compose/composeUpdate.ts b/src/mcp/tools/compose/composeUpdate.ts new file mode 100644 index 0000000..d449ad9 --- /dev/null +++ b/src/mcp/tools/compose/composeUpdate.ts @@ -0,0 +1,67 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const composeUpdate = createTool({ + name: "compose-update", + description: "Updates an existing compose service in Dokploy.", + schema: z.object({ + composeId: z.string().describe("The ID of the compose service to update."), + name: z + .string() + .min(1) + .optional() + .describe("The new name of the compose service."), + appName: z + .string() + .optional() + .describe("The new app name of the compose service."), + description: z + .string() + .nullable() + .optional() + .describe("The new description for the compose service."), + env: z + .string() + .nullable() + .optional() + .describe("Environment variables for the compose service."), + composeFile: z + .string() + .nullable() + .optional() + .describe("The updated docker-compose.yml content."), + composePath: z + .string() + .optional() + .describe("The path to the compose file."), + composeStatus: z + .enum(["idle", "running", "done", "error"]) + .optional() + .describe("The status of the compose service."), + projectId: z + .string() + .optional() + .describe("The project ID if moving to different project."), + serverId: z + .string() + .nullable() + .optional() + .describe("The server ID for the compose service."), + }), + annotations: { + title: "Update Compose Service", + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/compose.update", input); + + return ResponseFormatter.success( + `Compose service "${input.composeId}" updated successfully`, + response.data + ); + }, +}); diff --git a/src/mcp/tools/compose/index.ts b/src/mcp/tools/compose/index.ts new file mode 100644 index 0000000..0746c41 --- /dev/null +++ b/src/mcp/tools/compose/index.ts @@ -0,0 +1,9 @@ +export { composeCreate } from "./composeCreate.js"; +export { composeDeploy } from "./composeDeploy.js"; +export { composeOne } from "./composeOne.js"; +export { composeReload } from "./composeReload.js"; +export { composeRemove } from "./composeRemove.js"; +export { composeSaveEnvironment } from "./composeSaveEnvironment.js"; +export { composeStart } from "./composeStart.js"; +export { composeStop } from "./composeStop.js"; +export { composeUpdate } from "./composeUpdate.js"; diff --git a/src/mcp/tools/index.ts b/src/mcp/tools/index.ts index 46a3a30..d007a48 100644 --- a/src/mcp/tools/index.ts +++ b/src/mcp/tools/index.ts @@ -1,9 +1,11 @@ import * as applicationTools from "./application/index.js"; +import * as composeTools from "./compose/index.js"; import * as postgresTools from "./postgres/index.js"; import * as projectTools from "./project/index.js"; export const allTools = [ ...Object.values(projectTools), ...Object.values(applicationTools), + ...Object.values(composeTools), ...Object.values(postgresTools), ];