diff --git a/.gitignore b/.gitignore index af474db..f7bd3d1 100644 --- a/.gitignore +++ b/.gitignore @@ -131,3 +131,14 @@ junit.xml # Misc *.tgz *.tar.gz + +# Local MCP configuration (contains credentials) +.mcp.json +mcp.json + +# Bun lockfile +bun.lock + +# Local-only files +.talismanrc +docs/superpowers/ diff --git a/README.md b/README.md index 7d08ff3..5679eca 100644 --- a/README.md +++ b/README.md @@ -336,79 +336,67 @@ For detailed transport mode documentation and client examples, refer to the conf ## 📚 Available Tools -This MCP server provides **67 tools** organized into five main categories: +This MCP server exposes **2 tools** that provide full coverage of the entire Dokploy API (300+ operations): -### 🗂️ Project Management (6 tools) +### `dokploy-api` — Execute any Dokploy API operation -- `project-all` - List all projects -- `project-one` - Get project by ID -- `project-create` - Create new project -- `project-update` - Update project configuration -- `project-duplicate` - Duplicate project with optional service selection -- `project-remove` - Delete project +A single, generic tool that can call any Dokploy API endpoint. The HTTP method (GET/POST) is **auto-detected from the live OpenAPI spec** — no static configuration to maintain. -### 🚀 Application Management (26 tools) - -**Core Operations:** -- `application-one`, `application-create`, `application-update`, `application-delete` -- `application-deploy`, `application-redeploy`, `application-start`, `application-stop`, `application-reload` -- `application-move`, `application-markRunning`, `application-cancelDeployment` - -**Git Providers:** -- `application-saveGithubProvider`, `application-saveGitlabProvider`, `application-saveBitbucketProvider` -- `application-saveGiteaProvider`, `application-saveGitProvider`, `application-disconnectGitProvider` - -**Configuration:** -- `application-saveBuildType`, `application-saveEnvironment`, `application-saveDockerProvider` -- `application-readAppMonitoring`, `application-readTraefikConfig`, `application-updateTraefikConfig` -- `application-refreshToken`, `application-cleanQueues` - -### 🌐 Domain Management (9 tools) +```json +{ "operation": "application.create", "params": { "name": "my-app", "projectId": "..." } } +{ "operation": "project.all" } +{ "operation": "settings.health" } +``` -- `domain-byApplicationId` - List domains by application ID -- `domain-byComposeId` - List domains by compose service ID -- `domain-one` - Get domain by ID -- `domain-create` - Create domain (application/compose/preview) -- `domain-update` - Update domain configuration -- `domain-delete` - Delete domain -- `domain-validateDomain` - Validate domain DNS/target -- `domain-generateDomain` - Suggest a domain for an app -- `domain-canGenerateTraefikMeDomains` - Check Traefik.me availability on a server +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `operation` | string | Yes | API operation path, e.g. `"application.create"`, `"server.one"` | +| `params` | object | No | Parameters — sent as JSON body (POST) or query string (GET) | -### 🐘 PostgreSQL Database Management (13 tools) +### `dokploy-api-schema` — Discover operations and parameters -**Core Operations:** -- `postgres-create`, `postgres-one`, `postgres-update`, `postgres-remove`, `postgres-move` -- `postgres-deploy`, `postgres-start`, `postgres-stop`, `postgres-reload`, `postgres-rebuild` +Introspects the Dokploy OpenAPI spec to list available categories, operations, and their full parameter schemas. Call with no params for a category overview, with `category` to list operations, or with `operation` for full parameter details. -**Configuration:** -- `postgres-changeStatus`, `postgres-saveExternalPort`, `postgres-saveEnvironment` +```json +{} // → list all categories +{ "category": "application" } // → list operations in category +{ "operation": "application.create" } // → full parameter schema +``` -### 🐬 MySQL Database Management (13 tools) +### Why 2 tools instead of 300+? -**Core Operations:** -- `mysql-create`, `mysql-one`, `mysql-update`, `mysql-remove`, `mysql-move` -- `mysql-deploy`, `mysql-start`, `mysql-stop`, `mysql-reload`, `mysql-rebuild` +Previous versions registered a separate MCP tool for every API endpoint. This created maintenance overhead — every Dokploy update required manually adding new tools. The current architecture derives everything from the live OpenAPI spec at startup: -**Configuration:** -- `mysql-changeStatus`, `mysql-saveExternalPort`, `mysql-saveEnvironment` +- **Zero maintenance**: New Dokploy API endpoints are available automatically +- **Accurate method detection**: GET vs POST determined from the spec, not a static list +- **Rich discovery**: The schema tool resolves `$ref`, `allOf`, `oneOf` so AI clients get complete parameter information +- **Full coverage**: Every operation Dokploy exposes is accessible, not just a curated subset -**Tool Annotations:** -All tools include semantic annotations (`readOnlyHint`, `destructiveHint`, `idempotentHint`) to help MCP clients understand their behavior and safety characteristics. +**Tool Annotations:** Both tools include semantic annotations (`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`) to help MCP clients understand their behavior. -For detailed schemas, parameters, and usage examples, see **[TOOLS.md](TOOLS.md)**. +For detailed documentation, see **[TOOLS.md](TOOLS.md)**. ## 🏗️ Architecture Built with **@modelcontextprotocol/sdk**, **TypeScript**, and **Zod** for type-safe schema validation: -- **67 Tools** covering projects, applications, domains, PostgreSQL, and MySQL management +- **2 Tools, Full API Coverage**: A generic executor + schema discovery tool covering 300+ Dokploy operations +- **Dynamic OpenAPI Spec Derivation**: GET/POST method detection, operation lists, and parameter schemas are all derived from the live Dokploy OpenAPI spec at startup — cached after first fetch - **Multiple Transports**: Stdio (default) and HTTP (Streamable HTTP + legacy SSE) -- **Multiple Git Providers**: GitHub, GitLab, Bitbucket, Gitea, custom Git -- **Robust Error Handling**: Centralized API client with retry logic +- **Structured Error Handling**: Status-specific error messages (400/401/403/404/422/5xx) that surface Dokploy's actual validation details - **Type Safety**: Full TypeScript support with Zod schema validation - **Tool Annotations**: Semantic hints for MCP client behavior understanding +### Key Files + +| File | Purpose | +|------|---------| +| `src/mcp/tools/api.ts` | Generic API executor — routes any operation to the correct endpoint | +| `src/mcp/tools/apiSchema.ts` | Schema discovery — resolves `$ref`, `allOf`, `oneOf` from OpenAPI spec | +| `src/utils/openApiSpec.ts` | Shared OpenAPI spec cache — single fetch, used by both tools | +| `src/server.ts` | MCP server setup and tool registration | +| `src/http-server.ts` | Express server with Streamable HTTP + legacy SSE transport | + ## 🔧 Development Clone the project and install dependencies: diff --git a/TOOLS.md b/TOOLS.md index feca276..4003d07 100644 --- a/TOOLS.md +++ b/TOOLS.md @@ -1,1022 +1,111 @@ # Dokploy MCP Server - Tools Documentation -This document provides detailed information about all available tools in the Dokploy MCP Server. +## Overview -## 📊 Overview +The Dokploy MCP server exposes **2 tools** that provide full coverage of the Dokploy API: -- **Total Tools**: 67 -- **Project Tools**: 6 -- **Application Tools**: 26 -- **Domain Tools**: 9 -- **PostgreSQL Tools**: 13 -- **MySQL Tools**: 13 +| Tool | Purpose | +|------|---------| +| `dokploy-api` | Execute any Dokploy API operation | +| `dokploy-api-schema` | Discover available operations and their parameters | -All tools include semantic annotations (`readOnlyHint`, `destructiveHint`, `idempotentHint`) to help MCP clients understand their behavior. +## `dokploy-api` -## 🗂️ Project Management Tools +Executes any Dokploy API operation. HTTP method (GET/POST) is auto-detected. -### `project-all` +### Parameters -- **Description**: Lists all projects in Dokploy -- **Input Schema**: None -- **Annotations**: Read-only, Idempotent -- **Example**: `{}` +| Name | Type | Required | Description | +|------|------|----------|-------------| +| `operation` | string | Yes | API operation path, e.g. `"application.create"`, `"server.one"` | +| `params` | object | No | Parameters sent as JSON body (POST) or query string (GET) | -### `project-one` +### Examples -- **Description**: Gets a specific project by its ID in Dokploy -- **Input Schema**: - ```json - { - "projectId": "string" - } - ``` -- **Annotations**: Read-only, Idempotent -- **Required Fields**: `projectId` - -### `project-create` - -- **Description**: Creates a new project in Dokploy -- **Input Schema**: - ```json - { - "name": "string", - "description": "string|null", - "env": "string" - } - ``` -- **Annotations**: Non-destructive, Non-idempotent -- **Required Fields**: `name` -- **Optional Fields**: `description`, `env` - -### `project-update` - -- **Description**: Updates an existing project in Dokploy -- **Input Schema**: - ```json - { - "projectId": "string", - "name": "string", - "description": "string|null", - "createdAt": "string", - "organizationId": "string", - "env": "string" - } - ``` -- **Annotations**: Destructive -- **Required Fields**: `projectId` -- **Optional Fields**: `name`, `description`, `createdAt`, `organizationId`, `env` - -### `project-duplicate` - -- **Description**: Duplicates an existing project in Dokploy with optional service selection -- **Input Schema**: - ```json - { - "sourceProjectId": "string", - "name": "string", - "description": "string", - "includeServices": "boolean", - "selectedServices": [ - { - "id": "string", - "type": "application|postgres|mariadb|mongo|mysql|redis|compose" - } - ] - } - ``` -- **Annotations**: Creation tool (non-destructive) -- **Required Fields**: `sourceProjectId`, `name` -- **Optional Fields**: `description`, `includeServices`, `selectedServices` - -### `project-remove` - -- **Description**: Removes/deletes an existing project in Dokploy -- **Input Schema**: - ```json - { - "projectId": "string" - } - ``` -- **Annotations**: Destructive -- **Required Fields**: `projectId` - -## 🚀 Application Management Tools - -### Core Application Operations - -#### `application-one` - -- **Description**: Gets a specific application by its ID in Dokploy -- **Input Schema**: - ```json - { - "applicationId": "string" - } - ``` -- **Annotations**: Read-only, Idempotent -- **Required Fields**: `applicationId` - -#### `application-create` - -- **Description**: Creates a new application in Dokploy -- **Input Schema**: - ```json - { - "name": "string", - "appName": "string", - "description": "string|null", - "projectId": "string", - "serverId": "string|null" - } - ``` -- **Annotations**: Non-destructive, Non-idempotent -- **Required Fields**: `name`, `projectId` -- **Optional Fields**: `appName`, `description`, `serverId` - -#### `application-update` - -- **Description**: Updates an existing application in Dokploy -- **Input Schema**: Complex schema with 60+ fields including deployment settings, resource limits, networking, and monitoring configurations -- **Annotations**: Destructive -- **Required Fields**: `applicationId` -- **Optional Fields**: All application configuration fields (build settings, environment variables, resource limits, etc.) - -#### `application-delete` - -- **Description**: Deletes an application from Dokploy -- **Input Schema**: - ```json - { - "applicationId": "string" - } - ``` -- **Annotations**: Destructive -- **Required Fields**: `applicationId` - -#### `application-move` - -- **Description**: Moves an application to a different project -- **Input Schema**: - ```json - { - "applicationId": "string", - "targetProjectId": "string" - } - ``` -- **Annotations**: Destructive -- **Required Fields**: `applicationId`, `targetProjectId` - -### Deployment & Lifecycle Operations - -#### `application-deploy` - -- **Description**: Deploys an application in Dokploy -- **Input Schema**: - ```json - { - "applicationId": "string" - } - ``` -- **Annotations**: Non-destructive, Non-idempotent -- **Required Fields**: `applicationId` - -#### `application-redeploy` - -- **Description**: Redeploys an application in Dokploy -- **Input Schema**: - ```json - { - "applicationId": "string" - } - ``` -- **Annotations**: Non-destructive, Non-idempotent -- **Required Fields**: `applicationId` - -#### `application-start` - -- **Description**: Starts an application in Dokploy -- **Input Schema**: - ```json - { - "applicationId": "string" - } - ``` -- **Annotations**: Non-destructive, Non-idempotent -- **Required Fields**: `applicationId` - -#### `application-stop` - -- **Description**: Stops an application in Dokploy -- **Input Schema**: - ```json - { - "applicationId": "string" - } - ``` -- **Annotations**: Destructive, Non-idempotent -- **Required Fields**: `applicationId` - -#### `application-cancelDeployment` - -- **Description**: Cancels an ongoing deployment for an application -- **Input Schema**: - ```json - { - "applicationId": "string" - } - ``` -- **Annotations**: Destructive, Non-idempotent -- **Required Fields**: `applicationId` - -#### `application-reload` - -- **Description**: Reloads an application in Dokploy -- **Input Schema**: - ```json - { - "applicationId": "string", - "appName": "string" - } - ``` -- **Annotations**: Non-destructive, Non-idempotent -- **Required Fields**: `applicationId`, `appName` - -#### `application-markRunning` - -- **Description**: Marks an application as running in Dokploy -- **Input Schema**: - ```json - { - "applicationId": "string" - } - ``` -- **Annotations**: Destructive, Non-idempotent -- **Required Fields**: `applicationId` - -### Configuration Management - -#### `application-saveBuildType` - -- **Description**: Saves build type configuration for an application -- **Input Schema**: - ```json - { - "applicationId": "string", - "buildType": "dockerfile|heroku|nixpacks|buildpacks|docker", - "dockerContextPath": "string|null", - "dockerBuildStage": "string|null", - "herokuVersion": "string|null" - } - ``` -- **Annotations**: Destructive -- **Required Fields**: `applicationId`, `buildType` -- **Optional Fields**: `dockerContextPath`, `dockerBuildStage`, `herokuVersion` - -#### `application-saveEnvironment` - -- **Description**: Saves environment configuration for an application -- **Input Schema**: - ```json - { - "applicationId": "string", - "env": "string|null", - "buildArgs": "string|null" - } - ``` -- **Annotations**: Destructive -- **Required Fields**: `applicationId` -- **Optional Fields**: `env`, `buildArgs` - -### Git Provider Configurations - -#### `application-saveGithubProvider` - -- **Description**: Saves GitHub provider configuration for an application -- **Input Schema**: - ```json - { - "applicationId": "string", - "repository": "string|null", - "branch": "string|null", - "owner": "string|null", - "buildPath": "string|null", - "githubId": "string|null", - "watchPaths": ["string"]|null, - "enableSubmodules": "boolean", - "triggerType": "push|tag" - } - ``` -- **Annotations**: Destructive -- **Required Fields**: `applicationId`, `owner`, `githubId`, `enableSubmodules` -- **Optional Fields**: `repository`, `branch`, `buildPath`, `watchPaths`, `triggerType` - -#### `application-saveGitlabProvider` - -- **Description**: Saves GitLab provider configuration for an application -- **Input Schema**: - ```json - { - "applicationId": "string", - "gitlabBranch": "string|null", - "gitlabBuildPath": "string|null", - "gitlabOwner": "string|null", - "gitlabRepository": "string|null", - "gitlabId": "string|null", - "gitlabProjectId": "number|null", - "gitlabPathNamespace": "string|null", - "watchPaths": ["string"]|null, - "enableSubmodules": "boolean" - } - ``` -- **Annotations**: Destructive -- **Required Fields**: `applicationId`, `gitlabBranch`, `gitlabBuildPath`, `gitlabOwner`, `gitlabRepository`, `gitlabId`, `gitlabProjectId`, `gitlabPathNamespace`, `enableSubmodules` -- **Optional Fields**: `watchPaths` - -#### `application-saveBitbucketProvider` - -- **Description**: Saves Bitbucket provider configuration for an application -- **Input Schema**: - ```json - { - "applicationId": "string", - "bitbucketBranch": "string|null", - "bitbucketBuildPath": "string|null", - "bitbucketOwner": "string|null", - "bitbucketRepository": "string|null", - "bitbucketId": "string|null", - "watchPaths": ["string"]|null, - "enableSubmodules": "boolean" - } - ``` -- **Annotations**: Destructive -- **Required Fields**: `applicationId`, `bitbucketBranch`, `bitbucketBuildPath`, `bitbucketOwner`, `bitbucketRepository`, `bitbucketId`, `enableSubmodules` -- **Optional Fields**: `watchPaths` - -#### `application-saveGiteaProvider` - -- **Description**: Saves Gitea provider configuration for an application -- **Input Schema**: - ```json - { - "applicationId": "string", - "giteaBranch": "string|null", - "giteaBuildPath": "string|null", - "giteaOwner": "string|null", - "giteaRepository": "string|null", - "giteaId": "string|null", - "watchPaths": ["string"]|null, - "enableSubmodules": "boolean" - } - ``` -- **Annotations**: Destructive -- **Required Fields**: `applicationId`, `giteaBranch`, `giteaBuildPath`, `giteaOwner`, `giteaRepository`, `giteaId`, `enableSubmodules` -- **Optional Fields**: `watchPaths` - -#### `application-saveGitProvider` - -- **Description**: Saves Git provider configuration for an application -- **Input Schema**: - ```json - { - "applicationId": "string", - "customGitUrl": "string|null", - "customGitBranch": "string|null", - "customGitBuildPath": "string|null", - "customGitSSHKeyId": "string|null", - "watchPaths": ["string"]|null, - "enableSubmodules": "boolean" - } - ``` -- **Annotations**: Destructive -- **Required Fields**: `applicationId`, `enableSubmodules` -- **Optional Fields**: `customGitUrl`, `customGitBranch`, `customGitBuildPath`, `customGitSSHKeyId`, `watchPaths` - -#### `application-saveDockerProvider` - -- **Description**: Saves Docker provider configuration for an application -- **Input Schema**: - ```json - { - "applicationId": "string", - "dockerImage": "string|null" - } - ``` -- **Annotations**: Destructive -- **Required Fields**: `applicationId`, `dockerImage` - -#### `application-disconnectGitProvider` - -- **Description**: Disconnects Git provider configuration from an application -- **Input Schema**: - ```json - { - "applicationId": "string" - } - ``` -- **Annotations**: Destructive -- **Required Fields**: `applicationId` - -### Monitoring & Configuration - -#### `application-readAppMonitoring` - -- **Description**: Reads monitoring data for an application -- **Input Schema**: - ```json - { - "appName": "string" - } - ``` -- **Annotations**: Read-only, Idempotent -- **Required Fields**: `appName` - -#### `application-readTraefikConfig` - -- **Description**: Reads Traefik configuration for an application -- **Input Schema**: - ```json - { - "applicationId": "string" - } - ``` -- **Annotations**: Read-only, Idempotent -- **Required Fields**: `applicationId` - -#### `application-updateTraefikConfig` - -- **Description**: Updates Traefik configuration for an application -- **Input Schema**: - ```json - { - "applicationId": "string", - "traefikConfig": "string" - } - ``` -- **Annotations**: Destructive -- **Required Fields**: `applicationId`, `traefikConfig` - -### Utility Operations - -#### `application-refreshToken` - -- **Description**: Refreshes the token for an application -- **Input Schema**: - ```json - { - "applicationId": "string" - } - ``` -- **Annotations**: Non-destructive -- **Required Fields**: `applicationId` - -#### `application-cleanQueues` - -- **Description**: Cleans deployment queues for an application -- **Input Schema**: - ```json - { - "applicationId": "string" - } - ``` -- **Annotations**: Destructive -- **Required Fields**: `applicationId` - -## 🌐 Domain Management Tools - -These tools manage domains for applications, compose services, and preview deployments, including SSL/TLS, routing, validation, and automatic suggestions. - -### `domain-byApplicationId` - -- **Description**: Retrieves all domains associated with a specific application in Dokploy -- **Input Schema**: - ```json - { - "applicationId": "string" - } - ``` -- **Annotations**: Read-only, Idempotent -- **Required Fields**: `applicationId` - -### `domain-byComposeId` - -- **Description**: Retrieves all domains associated with a specific compose stack/service in Dokploy -- **Input Schema**: - ```json - { - "composeId": "string" - } - ``` -- **Annotations**: Read-only, Idempotent -- **Required Fields**: `composeId` - -### `domain-one` - -- **Description**: Retrieves a specific domain configuration by its ID in Dokploy -- **Input Schema**: - ```json - { - "domainId": "string" - } - ``` -- **Annotations**: Read-only, Idempotent -- **Required Fields**: `domainId` - -### `domain-create` - -- **Description**: Creates a new domain configuration (application, compose, or preview) with SSL options -- **Input Schema**: - ```json - { - "host": "string", - "path": "string|null", - "port": "number|null", - "https": "boolean", - "applicationId": "string|null", - "certificateType": "letsencrypt|none|custom", - "customCertResolver": "string|null", - "composeId": "string|null", - "serviceName": "string|null", - "domainType": "compose|application|preview|null", - "previewDeploymentId": "string|null", - "internalPath": "string|null", - "stripPath": "boolean" - } - ``` -- **Annotations**: Non-destructive, Non-idempotent -- **Required Fields**: `host`, `https`, `certificateType`, `stripPath` -- **Optional Fields**: `path`, `port`, `applicationId`, `customCertResolver`, `composeId`, `serviceName`, `domainType`, `previewDeploymentId`, `internalPath` - -### `domain-update` - -- **Description**: Updates an existing domain configuration (host, SSL, routing, service associations) -- **Input Schema**: - ```json - { - "domainId": "string", - "host": "string", - "path": "string|null", - "port": "number|null", - "https": "boolean", - "certificateType": "letsencrypt|none|custom", - "customCertResolver": "string|null", - "serviceName": "string|null", - "domainType": "compose|application|preview|null", - "internalPath": "string|null", - "stripPath": "boolean" - } - ``` -- **Annotations**: Non-destructive, Idempotent -- **Required Fields**: `domainId`, `host`, `https`, `certificateType`, `stripPath` -- **Optional Fields**: `path`, `port`, `customCertResolver`, `serviceName`, `domainType`, `internalPath` - -### `domain-delete` - -- **Description**: Deletes a domain configuration by ID -- **Input Schema**: - ```json - { - "domainId": "string" - } - ``` -- **Annotations**: Destructive, Non-idempotent -- **Required Fields**: `domainId` - -### `domain-validateDomain` - -- **Description**: Validates if a domain is correctly configured, optionally against a specific server IP -- **Input Schema**: - ```json - { - "domain": "string", - "serverIp": "string(optional)" - } - ``` -- **Annotations**: Read-only, Idempotent -- **Required Fields**: `domain` -- **Optional Fields**: `serverIp` - -### `domain-generateDomain` - -- **Description**: Generates a suggested domain for an application name, optionally scoped to a server -- **Input Schema**: - ```json - { - "appName": "string", - "serverId": "string(optional)" - } - ``` -- **Annotations**: Read-only, Idempotent -- **Required Fields**: `appName` -- **Optional Fields**: `serverId` - -### `domain-canGenerateTraefikMeDomains` - -- **Description**: Checks whether Traefik.me domains can be generated for a specific server -- **Input Schema**: - ```json - { - "serverId": "string" - } - ``` -- **Annotations**: Read-only, Idempotent -- **Required Fields**: `serverId` - -## 🐘 PostgreSQL Database Management Tools - -### Core Database Operations - -#### `postgres-create` - -- **Description**: Creates a new PostgreSQL database in Dokploy -- **Input Schema**: - ```json - { - "name": "string", - "appName": "string", - "databaseName": "string", - "databaseUser": "string", - "databasePassword": "string", - "dockerImage": "string", - "projectId": "string", - "description": "string|null", - "serverId": "string|null" - } - ``` -- **Annotations**: Creation tool (non-destructive) -- **Required Fields**: `name`, `appName`, `databaseName`, `databaseUser`, `databasePassword`, `projectId` -- **Optional Fields**: `dockerImage`, `description`, `serverId` - -#### `postgres-one` - -- **Description**: Gets a specific PostgreSQL database by its ID in Dokploy -- **Input Schema**: - ```json - { - "postgresId": "string" - } - ``` -- **Annotations**: Read-only, Idempotent -- **Required Fields**: `postgresId` - -#### `postgres-update` - -- **Description**: Updates an existing PostgreSQL database in Dokploy -- **Input Schema**: Complex schema with database configuration fields including name, credentials, resource limits, and Docker settings -- **Annotations**: Destructive -- **Required Fields**: `postgresId` -- **Optional Fields**: All database configuration fields (name, credentials, memory/CPU limits, etc.) - -#### `postgres-remove` - -- **Description**: Removes/deletes a PostgreSQL database from Dokploy -- **Input Schema**: - ```json - { - "postgresId": "string" - } - ``` -- **Annotations**: Destructive -- **Required Fields**: `postgresId` - -#### `postgres-move` - -- **Description**: Moves a PostgreSQL database to a different project -- **Input Schema**: - ```json - { - "postgresId": "string", - "targetProjectId": "string" - } - ``` -- **Annotations**: Destructive -- **Required Fields**: `postgresId`, `targetProjectId` - -### Lifecycle Management - -#### `postgres-deploy` - -- **Description**: Deploys a PostgreSQL database in Dokploy -- **Input Schema**: - ```json - { - "postgresId": "string" - } - ``` -- **Annotations**: Destructive -- **Required Fields**: `postgresId` - -#### `postgres-start` - -- **Description**: Starts a PostgreSQL database in Dokploy -- **Input Schema**: - ```json - { - "postgresId": "string" - } - ``` -- **Annotations**: Destructive -- **Required Fields**: `postgresId` - -#### `postgres-stop` - -- **Description**: Stops a PostgreSQL database in Dokploy -- **Input Schema**: - ```json - { - "postgresId": "string" - } - ``` -- **Annotations**: Destructive -- **Required Fields**: `postgresId` - -#### `postgres-reload` - -- **Description**: Reloads a PostgreSQL database in Dokploy -- **Input Schema**: - ```json - { - "postgresId": "string", - "appName": "string" - } - ``` -- **Annotations**: Destructive -- **Required Fields**: `postgresId`, `appName` - -#### `postgres-rebuild` - -- **Description**: Rebuilds a PostgreSQL database in Dokploy -- **Input Schema**: - ```json - { - "postgresId": "string" - } - ``` -- **Annotations**: Destructive -- **Required Fields**: `postgresId` - -### Configuration Management - -#### `postgres-changeStatus` - -- **Description**: Changes the status of a PostgreSQL database -- **Input Schema**: - ```json - { - "postgresId": "string", - "applicationStatus": "idle|running|done|error" - } - ``` -- **Annotations**: Destructive -- **Required Fields**: `postgresId`, `applicationStatus` - -#### `postgres-saveExternalPort` - -- **Description**: Saves external port configuration for a PostgreSQL database -- **Input Schema**: - ```json - { - "postgresId": "string", - "externalPort": "number|null" - } - ``` -- **Annotations**: Destructive -- **Required Fields**: `postgresId`, `externalPort` - -#### `postgres-saveEnvironment` - -- **Description**: Saves environment variables for a PostgreSQL database -- **Input Schema**: - ```json - { - "postgresId": "string", - "env": "string|null" - } - ``` -- **Annotations**: Destructive -- **Required Fields**: `postgresId` -- **Optional Fields**: `env` - -## 🐬 MySQL Database Management Tools - -Dokploy includes comprehensive MySQL database management capabilities. These tools mirror the PostgreSQL functionality but are tailored for MySQL databases with MySQL-specific features like root password management. - -### Core Database Operations - -#### `mysql-create` - -- **Description**: Creates a new MySQL database in Dokploy -- **Input Schema**: - ```json - { - "name": "string", - "appName": "string", - "databaseName": "string", - "databaseUser": "string", - "databasePassword": "string", - "databaseRootPassword": "string", - "dockerImage": "string", - "projectId": "string", - "description": "string|null", - "serverId": "string|null" - } - ``` -- **Annotations**: Creation tool (non-destructive) -- **Required Fields**: `name`, `appName`, `databaseName`, `databaseUser`, `databasePassword`, `databaseRootPassword`, `projectId` -- **Optional Fields**: `dockerImage` (defaults to "mysql:8"), `description`, `serverId` - -#### `mysql-one` - -- **Description**: Gets a specific MySQL database by its ID in Dokploy -- **Input Schema**: - ```json - { - "mysqlId": "string" - } - ``` -- **Annotations**: Read-only, Idempotent -- **Required Fields**: `mysqlId` - -#### `mysql-update` - -- **Description**: Updates an existing MySQL database in Dokploy -- **Input Schema**: Complex schema with database configuration fields including name, credentials, resource limits, and Docker settings -- **Annotations**: Destructive -- **Required Fields**: `mysqlId` -- **Optional Fields**: All database configuration fields (name, credentials, memory/CPU limits, etc.) - -#### `mysql-remove` - -- **Description**: Removes/deletes a MySQL database from Dokploy -- **Input Schema**: - ```json - { - "mysqlId": "string" - } - ``` -- **Annotations**: Destructive -- **Required Fields**: `mysqlId` - -#### `mysql-move` - -- **Description**: Moves a MySQL database to a different project -- **Input Schema**: - ```json - { - "mysqlId": "string", - "targetProjectId": "string" - } - ``` -- **Annotations**: Destructive -- **Required Fields**: `mysqlId`, `targetProjectId` - -### Lifecycle Management - -#### `mysql-deploy` - -- **Description**: Deploys a MySQL database in Dokploy -- **Input Schema**: - ```json - { - "mysqlId": "string" - } - ``` -- **Annotations**: Destructive -- **Required Fields**: `mysqlId` - -#### `mysql-start` - -- **Description**: Starts a MySQL database in Dokploy -- **Input Schema**: - ```json - { - "mysqlId": "string" - } - ``` -- **Annotations**: Destructive -- **Required Fields**: `mysqlId` - -#### `mysql-stop` - -- **Description**: Stops a MySQL database in Dokploy -- **Input Schema**: - ```json - { - "mysqlId": "string" - } - ``` -- **Annotations**: Destructive -- **Required Fields**: `mysqlId` - -#### `mysql-reload` - -- **Description**: Reloads a MySQL database in Dokploy -- **Input Schema**: - ```json - { - "mysqlId": "string", - "appName": "string" - } - ``` -- **Annotations**: Destructive -- **Required Fields**: `mysqlId`, `appName` - -#### `mysql-rebuild` - -- **Description**: Rebuilds a MySQL database in Dokploy -- **Input Schema**: - ```json - { - "mysqlId": "string" - } - ``` -- **Annotations**: Destructive -- **Required Fields**: `mysqlId` - -### Configuration Management - -#### `mysql-changeStatus` - -- **Description**: Changes the status of a MySQL database -- **Input Schema**: - ```json - { - "mysqlId": "string", - "applicationStatus": "idle|running|done|error" - } - ``` -- **Annotations**: Destructive -- **Required Fields**: `mysqlId`, `applicationStatus` - -#### `mysql-saveExternalPort` - -- **Description**: Saves external port configuration for a MySQL database -- **Input Schema**: - ```json - { - "mysqlId": "string", - "externalPort": "number|null" - } - ``` -- **Annotations**: Destructive -- **Required Fields**: `mysqlId`, `externalPort` - -#### `mysql-saveEnvironment` - -- **Description**: Saves environment variables for a MySQL database -- **Input Schema**: - ```json - { - "mysqlId": "string", - "env": "string|null" - } - ``` -- **Annotations**: Destructive -- **Required Fields**: `mysqlId` -- **Optional Fields**: `env` - -## 🏷️ Tool Annotations - -All tools include semantic annotations to help MCP clients understand their behavior: - -- **Read-Only** (`readOnlyHint: true`): Safe operations that only retrieve data - - Examples: `project-all`, `project-one`, `application-one`, `application-readTraefikConfig`, `postgres-one`, `mysql-one` - -- **Destructive** (`destructiveHint: true`): Operations that modify or delete resources irreversibly - - Examples: `project-update`, `project-remove`, `application-delete`, `application-stop`, `application-cancelDeployment` - -- **Non-Destructive** (`destructiveHint: false`): Operations that create resources or perform safe actions - - Examples: All create operations, deploy, start, reload operations +```json +// Create an application +{ "operation": "application.create", "params": { "name": "my-app", "environmentId": "env-123" } } -- **Idempotent** (`idempotentHint: true`): Operations safe to repeat without side effects - - Examples: All read-only operations +// Get a project +{ "operation": "project.one", "params": { "projectId": "proj-456" } } -- **External API** (`openWorldHint: true`): All tools interact with external Dokploy API +// List all servers +{ "operation": "server.all" } -## 🔧 Quick Start Examples +// Deploy a compose service +{ "operation": "compose.deploy", "params": { "composeId": "comp-789" } } -### Project & Application Workflow -```json -// Create project → Create application → Configure Git → Deploy -{"tool": "project-create", "input": {"name": "my-project"}} -{"tool": "application-create", "input": {"name": "my-app", "projectId": "..."}} -{"tool": "application-saveGithubProvider", "input": {"applicationId": "...", "repository": "owner/repo", "branch": "main"}} -{"tool": "application-deploy", "input": {"applicationId": "..."}} +// Check server health +{ "operation": "settings.health" } ``` -### Database Workflow +### Available Operations + +Operations are grouped by category. Use `dokploy-api-schema` to get parameter details. + +- **admin**: setupMonitoring +- **ai**: create, delete, deploy, get, getAll, getModels, one, suggest, update +- **application**: cancelDeployment, cleanQueues, create, delete, deploy, disconnectGitProvider, markRunning, move, one, readAppMonitoring, readTraefikConfig, redeploy, refreshToken, reload, saveBitbucketProvider, saveBuildType, saveDockerProvider, saveEnvironment, saveGitProvider, saveGiteaProvider, saveGithubProvider, saveGitlabProvider, start, stop, update, updateTraefikConfig +- **backup**: create, listBackupFiles, one, remove, update +- **bitbucket**: getBitbucketBranches, getBitbucketRepositories +- **certificates**: all, create, one, remove +- **cluster**: addManager, addWorker, getNodes, removeWorker +- **compose**: cancelDeployment, cleanQueues, create, delete, deploy, deployTemplate, disconnectGitProvider, fetchSourceType, getConvertedCompose, getDefaultCommand, getTags, import, isolatedDeployment, killBuild, loadMountsByService, loadServices, move, one, processTemplate, randomizeCompose, redeploy, refreshToken, start, stop, templates, update +- **deployment**: all, allByCompose, allByServer, allByType, killProcess +- **destination**: all, create, one, remove, testConnection, update +- **docker**: getConfig, getContainers, getContainersByAppLabel, getContainersByAppNameMatch, getServiceContainersByAppName, getStackContainersByAppName, restartContainer +- **domain**: byApplicationId, byComposeId, canGenerateTraefikMeDomains, create, delete, generateDomain, one, update, validateDomain +- **environment**: byProjectId, create, duplicate, one, remove, update +- **gitea**: getGiteaBranches, getGiteaRepositories, getGiteaUrl +- **github**: getGithubBranches, getGithubRepositories, githubProviders +- **gitlab**: getGitlabBranches, getGitlabRepositories +- **gitProvider**: create, getAll, one, remove, testConnection, update +- **mariadb**: changeStatus, create, deploy, move, one, rebuild, reload, remove, saveEnvironment, saveExternalPort, start, stop, update +- **mongo**: changeStatus, create, deploy, move, one, rebuild, reload, remove, saveEnvironment, saveExternalPort, start, stop, update +- **mounts**: create, one, remove, update +- **mysql**: changeStatus, create, deploy, move, one, rebuild, reload, remove, saveEnvironment, saveExternalPort, start, stop, update +- **notification**: all, create, getEmailProviders, one, receiveNotification, remove, test, update +- **organization**: all, allInvitations, create, delete, one, removeInvitation, setDefault, update +- **port**: create, delete, one, update +- **postgres**: changeStatus, create, deploy, move, one, rebuild, reload, remove, saveEnvironment, saveExternalPort, start, stop, update +- **previewDeployment**: all, delete, one +- **project**: all, create, duplicate, one, remove, update +- **redirects**: create, delete, one, update +- **redis**: changeStatus, create, deploy, move, one, rebuild, reload, remove, saveEnvironment, saveExternalPort, start, stop, update +- **registry**: all, create, one, remove, testRegistry, update +- **rollback**: delete, rollback +- **schedule**: create, delete, list, one, runManually, update +- **security**: create, delete, one, update +- **server**: all, buildServers, count, create, getDefaultCommand, getServerMetrics, getServerTime, one, publicIp, remove, security, setup, setupMonitoring, update, validate, withSSHKey +- **settings**: assignDomainServer, checkGPUStatus, cleanAll, cleanDockerBuilder, cleanDockerPrune, cleanMonitoring, cleanRedis, cleanSSHPrivateKey, cleanStoppedContainers, cleanUnusedImages, cleanUnusedVolumes, getDokployCloudIps, getDokployVersion, getIp, getLogCleanupStatus, getOpenApiDocument, getReleaseTag, getTraefikPorts, getUpdateData, haveActivateRequests, haveTraefikDashboardPortEnabled, health, isCloud, isUserSubscribed, readDirectories, readMiddlewareTraefikConfig, readTraefikConfig, readTraefikEnv, readTraefikFile, readWebServerTraefikConfig, reloadRedis, reloadServer, reloadTraefik, saveSSHPrivateKey, setupGPU, toggleDashboard, toggleRequests, updateDockerCleanup, updateLogCleanup, updateMiddlewareTraefikConfig, updateServer, updateTraefikConfig, updateTraefikFile, updateTraefikPorts, updateWebServerTraefikConfig, writeTraefikEnv +- **sshKey**: all, create, generate, one, remove, update +- **stripe**: canCreateMoreServers, createCheckoutSession, createCustomerPortalSession, getProducts +- **swarm**: getNodeApps, getNodeInfo, getNodes +- **user**: all, assignPermissions, checkUserOrganizations, createApiKey, deleteApiKey, generateToken, get, getBackups, getUserByToken, getContainerMetrics, getInvitations, getMetricsToken, getServerMetrics, haveRootAccess, one, remove, sendInvitation, update +- **volumeBackups**: create, delete, list, one, runManually, update + +## `dokploy-api-schema` + +Discovers operation parameters from the Dokploy OpenAPI spec. Fetches the spec on first call and caches it. + +### Parameters + +| Name | Type | Required | Description | +|------|------|----------|-------------| +| `category` | string | No | Filter by category, e.g. `"application"`, `"server"` | +| `operation` | string | No | Get details for a specific operation, e.g. `"application.create"` | + +If neither parameter is provided, returns a summary of all categories with operation counts. + +### Examples + ```json -// Create → Deploy → Configure -{"tool": "postgres-create", "input": {"name": "my-db", "databaseName": "app", "databaseUser": "user", "databasePassword": "pass", "projectId": "..."}} -{"tool": "postgres-deploy", "input": {"postgresId": "..."}} -{"tool": "postgres-saveExternalPort", "input": {"postgresId": "...", "externalPort": 5432}} -``` +// Get all categories +{} -## 📝 Important Notes +// List operations in the application category +{ "category": "application" } -- Nullable fields accept `null` but must be provided if marked required -- Provider tools use prefixed fields: `gitlabBranch`, `giteaOwner`, `bitbucketRepository` -- Resource limits use string format: `"512m"`, `"1g"`, `"0.5"` -- MySQL requires both `databasePassword` and `databaseRootPassword` -- Default images: PostgreSQL `postgres:latest`, MySQL `mysql:8` -- All tools include comprehensive error handling and Zod validation +// Get parameter details for a specific operation +{ "operation": "application.create" } +``` diff --git a/package.json b/package.json index 7fa52e5..b72b389 100644 --- a/package.json +++ b/package.json @@ -43,10 +43,10 @@ "license": "Apache-2.0", "type": "module", "dependencies": { - "@modelcontextprotocol/sdk": "^1.12.0", + "@modelcontextprotocol/sdk": "1.12.0", "axios": "^1.9.0", "express": "^5.1.0", - "zod": "^3.25.28" + "zod": "3.25.28" }, "devDependencies": { "@types/eslint": "^9.6.1", diff --git a/src/http-server.ts b/src/http-server.ts index d386742..5b6ab3a 100644 --- a/src/http-server.ts +++ b/src/http-server.ts @@ -1,4 +1,4 @@ -#!/usr/bin/env node +#!/usr/bin/env bun import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; diff --git a/src/index.ts b/src/index.ts index 9311cb2..c354b63 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -#!/usr/bin/env node +#!/usr/bin/env bun import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { createServer } from "./server.js"; diff --git a/src/mcp/tools/api.ts b/src/mcp/tools/api.ts new file mode 100644 index 0000000..715c154 --- /dev/null +++ b/src/mcp/tools/api.ts @@ -0,0 +1,138 @@ +import { z } from "zod"; +import { AxiosError } from "axios"; +import apiClient from "../../utils/apiClient.js"; +import { createLogger } from "../../utils/logger.js"; +import { ResponseFormatter } from "../../utils/responseFormatter.js"; +import { + getGetOperations, + getOperationsList, +} from "../../utils/openApiSpec.js"; + +const logger = createLogger("DokployApi"); + +async function getMethod(operation: string): Promise<"GET" | "POST"> { + const getOps = await getGetOperations(); + return getOps.has(operation) ? "GET" : "POST"; +} + +export const schema = { + operation: z + .string() + .min(1) + .describe( + 'The API operation path, e.g. "application.create", "server.one", "postgres.deploy"' + ), + params: z + .record(z.any()) + .optional() + .describe( + "Parameters object. Sent as JSON body for mutations, query string for reads." + ), +}; + +export const name = "dokploy-api"; + +// The description is built dynamically on first use, but we need a static +// string for tool registration. We use a base description and the tool +// handler enriches error messages with available operations when needed. +export let description = + "Execute any Dokploy API operation. HTTP method is auto-detected from the OpenAPI spec. Use dokploy-api-schema to discover parameters for an operation."; + +// Lazy-initialize the full description with operations list on first call +let descriptionInitialized = false; +async function ensureDescription(): Promise { + if (descriptionInitialized) return; + try { + const opsList = await getOperationsList(); + description = `Execute any Dokploy API operation. HTTP method is auto-detected from the OpenAPI spec. Use dokploy-api-schema to discover parameters for an operation.\n\n${opsList}`; + descriptionInitialized = true; + } catch { + // If spec fetch fails, keep the base description — tool still works + logger.warn("Could not fetch operations list for description"); + } +} + +export const annotations = { + title: "Dokploy API", + readOnlyHint: false, + openWorldHint: true, +}; + +export async function handler(input: { + operation: string; + params?: Record; +}) { + // Ensure description is populated for future registrations + await ensureDescription(); + + const { operation, params } = input; + const method = await getMethod(operation); + const endpoint = `/${operation}`; + + logger.info(`Executing ${method} ${endpoint}`, { hasParams: !!params }); + + try { + const response = + method === "POST" + ? await apiClient.post(endpoint, params ?? {}) + : await apiClient.get(endpoint, { params }); + + return ResponseFormatter.success( + `${method} ${operation} succeeded`, + response.data + ); + } catch (error) { + logger.error(`${method} ${endpoint} failed`, { + error: error instanceof Error ? error.message : "Unknown error", + }); + + if (error instanceof AxiosError && error.response) { + const status = error.response.status; + const data = error.response.data as Record | undefined; + const detail = + (data?.message as string) || (data?.error as string) || error.message; + + if (status === 400) { + return ResponseFormatter.error( + `Bad request for ${operation}`, + detail + ); + } + if (status === 401) { + return ResponseFormatter.error( + "Authentication failed", + "Please check your DOKPLOY_API_KEY configuration" + ); + } + if (status === 403) { + return ResponseFormatter.error( + "Access denied", + `Insufficient permissions for ${operation}` + ); + } + if (status === 404) { + return ResponseFormatter.error( + "Resource not found", + `Operation ${operation} not found or resource does not exist` + ); + } + if (status === 422) { + return ResponseFormatter.error( + `Validation error for ${operation}`, + detail + ); + } + if (status >= 500) { + return ResponseFormatter.error( + "Server error", + `Dokploy server error while processing ${operation}` + ); + } + } + + return ResponseFormatter.error( + `Failed: ${operation}`, + `Error: ${error instanceof Error ? error.message : "Unknown error"}` + ); + } +} diff --git a/src/mcp/tools/apiSchema.ts b/src/mcp/tools/apiSchema.ts new file mode 100644 index 0000000..01687e1 --- /dev/null +++ b/src/mcp/tools/apiSchema.ts @@ -0,0 +1,227 @@ +import { z } from "zod"; +import { createLogger } from "../../utils/logger.js"; +import { ResponseFormatter } from "../../utils/responseFormatter.js"; +import { getOpenApiSpec } from "../../utils/openApiSpec.js"; + +const logger = createLogger("DokployApiSchema"); + +interface OperationInfo { + name: string; + method: string; + description?: string; + parameters?: Record; +} + +function extractOperations(spec: Record): OperationInfo[] { + const paths = (spec as any).paths || {}; + const operations: OperationInfo[] = []; + + for (const [path, methods] of Object.entries(paths)) { + const operationName = path.replace(/^\//, ""); + const methodsObj = methods as Record; + + for (const [method, details] of Object.entries(methodsObj)) { + if (!["get", "post"].includes(method)) continue; + + const op: OperationInfo = { + name: operationName, + method: method.toUpperCase(), + description: details.summary || details.description, + }; + + // Extract request body schema for POST + if (method === "post" && details.requestBody) { + const content = details.requestBody.content; + const jsonSchema = content?.["application/json"]?.schema; + if (jsonSchema) { + op.parameters = resolveSchema(spec, jsonSchema); + } + } + + // Extract query parameters for GET + if (method === "get" && details.parameters) { + const params: Record = {}; + for (const param of details.parameters) { + if (param.in === "query") { + params[param.name] = { + type: param.schema?.type || "string", + required: param.required || false, + description: param.description, + }; + } + } + if (Object.keys(params).length > 0) { + op.parameters = params; + } + } + + operations.push(op); + } + } + + return operations; +} + +function resolveSchema( + spec: Record, + schema: Record, + depth = 0 +): Record { + // Guard against circular references + if (depth > 10) return { note: "Max resolution depth reached" }; + + // Handle $ref + if (schema.$ref) { + const refPath = schema.$ref.replace("#/", "").split("/"); + let resolved: any = spec; + for (const segment of refPath) { + resolved = resolved?.[segment]; + } + if (resolved) { + return resolveSchema(spec, resolved, depth + 1); + } + return { $ref: schema.$ref, note: "Could not resolve reference" }; + } + + // Handle allOf (merge all sub-schemas) + if (schema.allOf) { + let merged: Record = {}; + for (const sub of schema.allOf) { + const resolved = resolveSchema(spec, sub, depth + 1); + merged = { ...merged, ...resolved }; + } + return merged; + } + + // Handle oneOf/anyOf (list variants) + if (schema.oneOf || schema.anyOf) { + const variants = schema.oneOf || schema.anyOf; + return { + oneOf: variants.map((v: any) => resolveSchema(spec, v, depth + 1)), + }; + } + + // Handle object with properties + if (schema.type === "object" && schema.properties) { + const result: Record = {}; + const required = new Set(schema.required || []); + for (const [propName, propSchema] of Object.entries(schema.properties)) { + const prop = propSchema as Record; + if (prop.$ref || prop.allOf || prop.oneOf || prop.anyOf) { + // Recursively resolve nested schemas + const resolved = resolveSchema(spec, prop, depth + 1); + result[propName] = { + ...resolved, + required: required.has(propName), + }; + } else { + result[propName] = { + type: prop.type || "unknown", + required: required.has(propName), + ...(prop.description && { description: prop.description }), + ...(prop.enum && { enum: prop.enum }), + ...(prop.nullable && { nullable: true }), + ...(prop.default !== undefined && { default: prop.default }), + }; + } + } + return result; + } + + return schema; +} + +function getCategory(operationName: string): string { + const dotIndex = operationName.indexOf("."); + return dotIndex > 0 ? operationName.substring(0, dotIndex) : operationName; +} + +export const schema = { + category: z + .string() + .optional() + .describe( + 'Filter by category, e.g. "application", "server", "postgres"' + ), + operation: z + .string() + .optional() + .describe( + 'Get details for a specific operation, e.g. "application.create"' + ), +}; + +export const name = "dokploy-api-schema"; + +export const description = + "Get parameter details for Dokploy API operations. Returns operation schemas from the OpenAPI spec. Call with no params for a category summary, with category to list operations, or with operation for full parameter details."; + +export const annotations = { + title: "Dokploy API Schema", + readOnlyHint: true, + idempotentHint: true, +}; + +export async function handler(input: { + category?: string; + operation?: string; +}) { + try { + const spec = await getOpenApiSpec(); + const operations = extractOperations(spec); + + // Specific operation requested + if (input.operation) { + const op = operations.find((o) => o.name === input.operation); + if (!op) { + return ResponseFormatter.error( + "Operation not found", + `No operation named "${input.operation}". Use without params to see available categories.` + ); + } + return ResponseFormatter.success( + `Schema for ${input.operation}`, + op + ); + } + + // Category filter + if (input.category) { + const categoryOps = operations.filter( + (o) => getCategory(o.name) === input.category + ); + if (categoryOps.length === 0) { + return ResponseFormatter.error( + "Category not found", + `No operations in category "${input.category}". Use without params to see available categories.` + ); + } + return ResponseFormatter.success( + `Operations in ${input.category}`, + { + category: input.category, + operations: categoryOps, + } + ); + } + + // No filter — return category summary + const categories: Record = {}; + for (const op of operations) { + const cat = getCategory(op.name); + categories[cat] = (categories[cat] || 0) + 1; + } + return ResponseFormatter.success("Available categories", { + totalOperations: operations.length, + categories, + }); + } catch (error) { + logger.error("Failed to fetch API schema", { + error: error instanceof Error ? error.message : "Unknown error", + }); + return ResponseFormatter.error( + "Failed to fetch API schema", + `Could not retrieve OpenAPI spec: ${error instanceof Error ? error.message : "Unknown error"}` + ); + } +} diff --git a/src/mcp/tools/application/applicationCancelDeployment.ts b/src/mcp/tools/application/applicationCancelDeployment.ts deleted file mode 100644 index 6f09ded..0000000 --- a/src/mcp/tools/application/applicationCancelDeployment.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const applicationCancelDeployment = createTool({ - name: "application-cancelDeployment", - description: "Cancels an ongoing deployment for an application in Dokploy.", - schema: z.object({ - applicationId: z - .string() - .describe("The ID of the application to cancel deployment for."), - }), - annotations: { - title: "Cancel Application Deployment", - destructiveHint: true, - idempotentHint: false, - openWorldHint: false, - }, - handler: async (input) => { - const response = await apiClient.post( - "/application.cancelDeployment", - input - ); - - return ResponseFormatter.success( - `Deployment for application "${input.applicationId}" cancelled successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/application/applicationCleanQueues.ts b/src/mcp/tools/application/applicationCleanQueues.ts deleted file mode 100644 index 77d39c0..0000000 --- a/src/mcp/tools/application/applicationCleanQueues.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const applicationCleanQueues = createTool({ - name: "application-cleanQueues", - description: "Cleans the queues for an application in Dokploy.", - schema: z.object({ - applicationId: z - .string() - .describe("The ID of the application to clean queues for."), - }), - annotations: { - title: "Clean Application Queues", - destructiveHint: false, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/application.cleanQueues", input); - - return ResponseFormatter.success( - `Queues for application "${input.applicationId}" cleaned successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/application/applicationCreate.ts b/src/mcp/tools/application/applicationCreate.ts deleted file mode 100644 index 02f262e..0000000 --- a/src/mcp/tools/application/applicationCreate.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { createTool } from "../toolFactory.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; - -export const applicationCreate = createTool({ - name: "application-create", - description: "Creates a new application in Dokploy.", - schema: z.object({ - name: z.string().min(1).describe("The name of the application."), - appName: z - .string() - .optional() - .describe("The app name for the application."), - description: z - .string() - .nullable() - .optional() - .describe("An optional description for the application."), - environmentId: z - .string() - .min(1) - .describe( - "The ID of the environment where the application will be created." - ), - serverId: z - .string() - .nullable() - .optional() - .describe("The ID of the server where the application will be deployed."), - }), - annotations: { - title: "Create Application", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/application.create", input); - - return ResponseFormatter.success( - `Application "${input.name}" created successfully in environment "${input.environmentId}"`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/application/applicationDelete.ts b/src/mcp/tools/application/applicationDelete.ts deleted file mode 100644 index e5524df..0000000 --- a/src/mcp/tools/application/applicationDelete.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const applicationDelete = createTool({ - name: "application-delete", - description: "Deletes an application in Dokploy.", - schema: z.object({ - applicationId: z.string().describe("The ID of the application to delete."), - }), - annotations: { - title: "Delete Application", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/application.delete", input); - - return ResponseFormatter.success( - `Application "${input.applicationId}" deleted successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/application/applicationDeploy.ts b/src/mcp/tools/application/applicationDeploy.ts deleted file mode 100644 index e013dae..0000000 --- a/src/mcp/tools/application/applicationDeploy.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const applicationDeploy = createTool({ - name: "application-deploy", - description: "Deploys an application in Dokploy.", - schema: z.object({ - applicationId: z - .string() - .min(1) - .describe("The ID of the application to deploy."), - title: z.string().optional().describe("Optional title for the deployment."), - description: z - .string() - .optional() - .describe("Optional description for the deployment."), - }), - annotations: { - title: "Deploy Application", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/application.deploy", input); - - return ResponseFormatter.success( - `Application "${input.applicationId}" deployment started successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/application/applicationDisconnectGitProvider.ts b/src/mcp/tools/application/applicationDisconnectGitProvider.ts deleted file mode 100644 index bdab3b1..0000000 --- a/src/mcp/tools/application/applicationDisconnectGitProvider.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const applicationDisconnectGitProvider = createTool({ - name: "application-disconnectGitProvider", - description: - "Disconnects Git provider configuration from an application in Dokploy.", - schema: z.object({ - applicationId: z - .string() - .describe("The ID of the application to disconnect Git provider from."), - }), - annotations: { - title: "Disconnect Application Git Provider", - destructiveHint: true, - idempotentHint: false, - openWorldHint: false, - }, - handler: async (input) => { - const response = await apiClient.post( - "/application.disconnectGitProvider", - input - ); - - return ResponseFormatter.success( - `Git provider for application "${input.applicationId}" disconnected successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/application/applicationMarkRunning.ts b/src/mcp/tools/application/applicationMarkRunning.ts deleted file mode 100644 index b4af9a2..0000000 --- a/src/mcp/tools/application/applicationMarkRunning.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const applicationMarkRunning = createTool({ - name: "application-markRunning", - description: "Marks an application as running in Dokploy.", - schema: z.object({ - applicationId: z - .string() - .describe("The ID of the application to mark as running."), - }), - annotations: { - title: "Mark Application as Running", - destructiveHint: false, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/application.markRunning", input); - - return ResponseFormatter.success( - `Application "${input.applicationId}" marked as running successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/application/applicationMove.ts b/src/mcp/tools/application/applicationMove.ts deleted file mode 100644 index f65bd27..0000000 --- a/src/mcp/tools/application/applicationMove.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const applicationMove = createTool({ - name: "application-move", - description: "Moves an application to a different environment in Dokploy.", - schema: z.object({ - applicationId: z.string().describe("The ID of the application to move."), - targetEnvironmentId: z - .string() - .describe("The ID of the destination environment."), - }), - annotations: { - title: "Move Application", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/application.move", input); - - return ResponseFormatter.success( - `Application "${input.applicationId}" moved to environment "${input.targetEnvironmentId}" successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/application/applicationOne.ts b/src/mcp/tools/application/applicationOne.ts deleted file mode 100644 index 72e5d92..0000000 --- a/src/mcp/tools/application/applicationOne.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { createTool } from "../toolFactory.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; - -export const applicationOne = createTool({ - name: "application-one", - description: "Gets a specific application by its ID in Dokploy.", - schema: z.object({ - applicationId: z - .string() - .describe("The ID of the application to retrieve."), - }), - annotations: { - title: "Get Application Details", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const application = await apiClient.get( - `/application.one?applicationId=${input.applicationId}` - ); - - if (!application?.data) { - return ResponseFormatter.error( - "Failed to fetch application", - `Application with ID "${input.applicationId}" not found` - ); - } - - return ResponseFormatter.success( - `Successfully fetched application "${input.applicationId}"`, - application.data - ); - }, -}); diff --git a/src/mcp/tools/application/applicationReadAppMonitoring.ts b/src/mcp/tools/application/applicationReadAppMonitoring.ts deleted file mode 100644 index 695ea96..0000000 --- a/src/mcp/tools/application/applicationReadAppMonitoring.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const applicationReadAppMonitoring = createTool({ - name: "application-readAppMonitoring", - description: "Reads monitoring data for an application in Dokploy.", - schema: z.object({ - appName: z - .string() - .describe("The app name of the application to get monitoring data for."), - }), - annotations: { - title: "Read Application Monitoring", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.get( - `/application.readAppMonitoring?appName=${input.appName}` - ); - - if (!response?.data) { - return ResponseFormatter.error( - "Failed to fetch application monitoring data", - `No monitoring data found for application "${input.appName}"` - ); - } - - return ResponseFormatter.success( - `Successfully fetched monitoring data for application "${input.appName}"`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/application/applicationReadTraefikConfig.ts b/src/mcp/tools/application/applicationReadTraefikConfig.ts deleted file mode 100644 index 3546153..0000000 --- a/src/mcp/tools/application/applicationReadTraefikConfig.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const applicationReadTraefikConfig = createTool({ - name: "application-readTraefikConfig", - description: "Reads Traefik configuration for an application in Dokploy.", - schema: z.object({ - applicationId: z - .string() - .describe("The ID of the application to get Traefik config for."), - }), - annotations: { - title: "Read Application Traefik Config", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.get( - `/application.readTraefikConfig?applicationId=${input.applicationId}` - ); - - if (!response?.data) { - return ResponseFormatter.error( - "Failed to fetch application Traefik configuration", - `No Traefik configuration found for application "${input.applicationId}"` - ); - } - - return ResponseFormatter.success( - `Successfully fetched Traefik configuration for application "${input.applicationId}"`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/application/applicationRedeploy.ts b/src/mcp/tools/application/applicationRedeploy.ts deleted file mode 100644 index c22ce3b..0000000 --- a/src/mcp/tools/application/applicationRedeploy.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const applicationRedeploy = createTool({ - name: "application-redeploy", - description: "Redeploys an application in Dokploy.", - schema: z.object({ - applicationId: z - .string() - .min(1) - .describe("The ID of the application to redeploy."), - title: z - .string() - .optional() - .describe("Optional title for the redeployment."), - description: z - .string() - .optional() - .describe("Optional description for the redeployment."), - }), - annotations: { - title: "Redeploy Application", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/application.redeploy", input); - - return ResponseFormatter.success( - `Application "${input.applicationId}" redeployment started successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/application/applicationRefreshToken.ts b/src/mcp/tools/application/applicationRefreshToken.ts deleted file mode 100644 index 6ec8942..0000000 --- a/src/mcp/tools/application/applicationRefreshToken.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const applicationRefreshToken = createTool({ - name: "application-refreshToken", - description: "Refreshes the token for an application in Dokploy.", - schema: z.object({ - applicationId: z - .string() - .describe("The ID of the application to refresh token for."), - }), - annotations: { - title: "Refresh Application Token", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/application.refreshToken", input); - - return ResponseFormatter.success( - `Token for application "${input.applicationId}" refreshed successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/application/applicationReload.ts b/src/mcp/tools/application/applicationReload.ts deleted file mode 100644 index 3302bd8..0000000 --- a/src/mcp/tools/application/applicationReload.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const applicationReload = createTool({ - name: "application-reload", - description: "Reloads an application in Dokploy.", - schema: z.object({ - applicationId: z.string().describe("The ID of the application to reload."), - appName: z.string().describe("The app name of the application to reload."), - }), - annotations: { - title: "Reload Application", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/application.reload", input); - - return ResponseFormatter.success( - `Application "${input.applicationId}" reloaded successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/application/applicationSaveBitbucketProvider.ts b/src/mcp/tools/application/applicationSaveBitbucketProvider.ts deleted file mode 100644 index 4528d8b..0000000 --- a/src/mcp/tools/application/applicationSaveBitbucketProvider.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const applicationSaveBitbucketProvider = createTool({ - name: "application-saveBitbucketProvider", - description: - "Saves Bitbucket provider configuration for an application in Dokploy.", - schema: z.object({ - applicationId: z - .string() - .describe("The ID of the application to save Bitbucket provider for."), - bitbucketRepository: z - .string() - .nullable() - .describe("The Bitbucket repository URL or name."), - bitbucketOwner: z - .string() - .nullable() - .describe("The Bitbucket repository owner."), - bitbucketBranch: z - .string() - .nullable() - .describe("The branch to use from the repository."), - bitbucketBuildPath: z - .string() - .nullable() - .describe("The path within the repository to build from."), - bitbucketId: z - .string() - .nullable() - .describe("The Bitbucket integration ID."), - watchPaths: z - .array(z.string()) - .nullable() - .optional() - .describe("Array of paths to watch for changes."), - enableSubmodules: z.boolean().describe("Whether to enable submodules."), - }), - annotations: { - title: "Save Application Bitbucket Provider", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post( - "/application.saveBitbucketProvider", - input - ); - - return ResponseFormatter.success( - `Bitbucket provider for application "${input.applicationId}" saved successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/application/applicationSaveBuildType.ts b/src/mcp/tools/application/applicationSaveBuildType.ts deleted file mode 100644 index a1604bf..0000000 --- a/src/mcp/tools/application/applicationSaveBuildType.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const applicationSaveBuildType = createTool({ - name: "application-saveBuildType", - description: "Saves build type configuration for an application in Dokploy.", - schema: z.object({ - applicationId: z - .string() - .describe("The ID of the application to save build type for."), - buildType: z - .enum([ - "dockerfile", - "heroku_buildpacks", - "paketo_buildpacks", - "nixpacks", - "static", - "railpack", - ]) - .describe("The build type for the application."), - dockerContextPath: z - .string() - .nullable() - .describe("Docker context path (required field)."), - dockerBuildStage: z - .string() - .nullable() - .describe("Docker build stage (required field)."), - dockerfile: z - .string() - .nullable() - .optional() - .describe("Dockerfile content or path."), - herokuVersion: z - .string() - .nullable() - .optional() - .describe("Heroku version for heroku_buildpacks build type."), - railpackVersion: z - .string() - .nullable() - .optional() - .describe("Railpack version for railpack build type."), - publishDirectory: z - .string() - .nullable() - .optional() - .describe("Directory to publish the built application."), - isStaticSpa: z - .boolean() - .nullable() - .optional() - .describe("Whether the application is a static SPA."), - }), - annotations: { - title: "Save Application Build Type", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/application.saveBuildType", input); - - return ResponseFormatter.success( - `Build type for application "${input.applicationId}" saved successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/application/applicationSaveDockerProvider.ts b/src/mcp/tools/application/applicationSaveDockerProvider.ts deleted file mode 100644 index e4e9bce..0000000 --- a/src/mcp/tools/application/applicationSaveDockerProvider.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const applicationSaveDockerProvider = createTool({ - name: "application-saveDockerProvider", - description: - "Saves Docker provider configuration for an application in Dokploy.", - schema: z.object({ - applicationId: z - .string() - .describe("The ID of the application to save Docker provider for."), - dockerImage: z - .string() - .nullable() - .optional() - .describe("The Docker image to use for the application."), - username: z - .string() - .nullable() - .optional() - .describe("Username for Docker registry authentication."), - password: z - .string() - .nullable() - .optional() - .describe("Password for Docker registry authentication."), - registryUrl: z - .string() - .nullable() - .optional() - .describe("The Docker registry URL."), - }), - annotations: { - title: "Save Application Docker Provider", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post( - "/application.saveDockerProvider", - input - ); - - return ResponseFormatter.success( - `Docker provider for application "${input.applicationId}" saved successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/application/applicationSaveEnvironment.ts b/src/mcp/tools/application/applicationSaveEnvironment.ts deleted file mode 100644 index ccb58d5..0000000 --- a/src/mcp/tools/application/applicationSaveEnvironment.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const applicationSaveEnvironment = createTool({ - name: "application-saveEnvironment", - description: "Saves environment variables for an application in Dokploy.", - schema: z.object({ - applicationId: z - .string() - .describe("The ID of the application to save environment for."), - env: z - .string() - .nullable() - .optional() - .describe("Environment variables to save for the application."), - buildArgs: z - .string() - .nullable() - .optional() - .describe("Build arguments for the application."), - }), - annotations: { - title: "Save Application Environment", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post( - "/application.saveEnvironment", - input - ); - - return ResponseFormatter.success( - `Environment variables for application "${input.applicationId}" saved successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/application/applicationSaveGitProvider.ts b/src/mcp/tools/application/applicationSaveGitProvider.ts deleted file mode 100644 index 720e144..0000000 --- a/src/mcp/tools/application/applicationSaveGitProvider.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const applicationSaveGitProvider = createTool({ - name: "application-saveGitProvider", - description: - "Saves Git provider configuration for an application in Dokploy.", - schema: z.object({ - applicationId: z - .string() - .describe("The ID of the application to save Git provider for."), - customGitUrl: z - .string() - .nullable() - .optional() - .describe("The custom Git repository URL."), - customGitBranch: z - .string() - .nullable() - .optional() - .describe("The branch to use from the repository."), - customGitBuildPath: z - .string() - .nullable() - .optional() - .describe("The path within the repository to build from."), - customGitSSHKeyId: z - .string() - .nullable() - .optional() - .describe("The SSH key ID for Git authentication."), - watchPaths: z - .array(z.string()) - .nullable() - .optional() - .describe("Array of paths to watch for changes."), - enableSubmodules: z.boolean().describe("Whether to enable submodules."), - }), - annotations: { - title: "Save Application Git Provider", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post( - "/application.saveGitProvider", - input - ); - - return ResponseFormatter.success( - `Git provider for application "${input.applicationId}" saved successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/application/applicationSaveGiteaProvider.ts b/src/mcp/tools/application/applicationSaveGiteaProvider.ts deleted file mode 100644 index c4c4dde..0000000 --- a/src/mcp/tools/application/applicationSaveGiteaProvider.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const applicationSaveGiteaProvider = createTool({ - name: "application-saveGiteaProvider", - description: - "Saves Gitea provider configuration for an application in Dokploy.", - schema: z.object({ - applicationId: z - .string() - .describe("The ID of the application to save Gitea provider for."), - giteaRepository: z - .string() - .nullable() - .describe("The Gitea repository URL or name."), - giteaOwner: z.string().nullable().describe("The Gitea repository owner."), - giteaBranch: z - .string() - .nullable() - .describe("The branch to use from the repository."), - giteaBuildPath: z - .string() - .nullable() - .describe("The path within the repository to build from."), - giteaId: z.string().nullable().describe("The Gitea integration ID."), - watchPaths: z - .array(z.string()) - .nullable() - .optional() - .describe("Array of paths to watch for changes."), - enableSubmodules: z.boolean().describe("Whether to enable submodules."), - }), - annotations: { - title: "Save Application Gitea Provider", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post( - "/application.saveGiteaProvider", - input - ); - - return ResponseFormatter.success( - `Gitea provider for application "${input.applicationId}" saved successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/application/applicationSaveGithubProvider.ts b/src/mcp/tools/application/applicationSaveGithubProvider.ts deleted file mode 100644 index dd4ff58..0000000 --- a/src/mcp/tools/application/applicationSaveGithubProvider.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const applicationSaveGithubProvider = createTool({ - name: "application-saveGithubProvider", - description: - "Saves GitHub provider configuration for an application in Dokploy.", - schema: z.object({ - applicationId: z - .string() - .describe("The ID of the application to save GitHub provider for."), - repository: z - .string() - .nullable() - .optional() - .describe("The GitHub repository URL or name."), - branch: z - .string() - .nullable() - .optional() - .describe("The branch to use from the repository."), - owner: z.string().nullable().describe("The GitHub repository owner."), - buildPath: z - .string() - .nullable() - .optional() - .describe("The path within the repository to build from."), - githubId: z.string().nullable().describe("The GitHub integration ID."), - watchPaths: z - .array(z.string()) - .nullable() - .optional() - .describe("Paths to watch for changes."), - enableSubmodules: z.boolean().describe("Whether to enable git submodules."), - triggerType: z - .enum(["push", "tag"]) - .optional() - .default("push") - .describe("The trigger type for deployments."), - }), - annotations: { - title: "Save Application GitHub Provider", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post( - "/application.saveGithubProvider", - input - ); - - return ResponseFormatter.success( - `GitHub provider for application "${input.applicationId}" saved successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/application/applicationSaveGitlabProvider.ts b/src/mcp/tools/application/applicationSaveGitlabProvider.ts deleted file mode 100644 index c2a365f..0000000 --- a/src/mcp/tools/application/applicationSaveGitlabProvider.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const applicationSaveGitlabProvider = createTool({ - name: "application-saveGitlabProvider", - description: - "Saves GitLab provider configuration for an application in Dokploy.", - schema: z.object({ - applicationId: z - .string() - .describe("The ID of the application to save GitLab provider for."), - gitlabBranch: z - .string() - .nullable() - .describe("The branch to use from the repository."), - gitlabBuildPath: z - .string() - .nullable() - .describe("The path within the repository to build from."), - gitlabOwner: z.string().nullable().describe("The GitLab repository owner."), - gitlabRepository: z - .string() - .nullable() - .describe("The GitLab repository URL or name."), - gitlabId: z.string().nullable().describe("The GitLab integration ID."), - gitlabProjectId: z.number().nullable().describe("The GitLab project ID."), - gitlabPathNamespace: z - .string() - .nullable() - .describe("The GitLab path namespace."), - watchPaths: z - .array(z.string()) - .nullable() - .optional() - .describe("Paths to watch for changes."), - enableSubmodules: z.boolean().describe("Whether to enable git submodules."), - }), - annotations: { - title: "Save Application GitLab Provider", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post( - "/application.saveGitlabProvider", - input - ); - - return ResponseFormatter.success( - `GitLab provider for application "${input.applicationId}" saved successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/application/applicationStart.ts b/src/mcp/tools/application/applicationStart.ts deleted file mode 100644 index 4ad3b32..0000000 --- a/src/mcp/tools/application/applicationStart.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const applicationStart = createTool({ - name: "application-start", - description: "Starts an application in Dokploy.", - schema: z.object({ - applicationId: z.string().describe("The ID of the application to start."), - }), - annotations: { - title: "Start Application", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/application.start", input); - - return ResponseFormatter.success( - `Application "${input.applicationId}" started successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/application/applicationStop.ts b/src/mcp/tools/application/applicationStop.ts deleted file mode 100644 index d146063..0000000 --- a/src/mcp/tools/application/applicationStop.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const applicationStop = createTool({ - name: "application-stop", - description: "Stops an application in Dokploy.", - schema: z.object({ - applicationId: z.string().describe("The ID of the application to stop."), - }), - annotations: { - title: "Stop Application", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/application.stop", input); - - return ResponseFormatter.success( - `Application "${input.applicationId}" stopped successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/application/applicationUpdate.ts b/src/mcp/tools/application/applicationUpdate.ts deleted file mode 100644 index addbb8c..0000000 --- a/src/mcp/tools/application/applicationUpdate.ts +++ /dev/null @@ -1,457 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const applicationUpdate = createTool({ - name: "application-update", - description: "Updates an existing application in Dokploy.", - schema: z.object({ - applicationId: z.string().describe("The ID of the application to update."), - name: z - .string() - .min(1) - .optional() - .describe("The new name of the application."), - appName: z - .string() - .optional() - .describe("The new app name of the application."), - description: z - .string() - .nullable() - .optional() - .describe("The new description for the application."), - env: z - .string() - .nullable() - .optional() - .describe("Environment variables for the application."), - previewEnv: z - .string() - .nullable() - .optional() - .describe("Preview environment variables."), - watchPaths: z - .array(z.string()) - .nullable() - .optional() - .describe("Paths to watch for changes."), - previewBuildArgs: z - .string() - .nullable() - .optional() - .describe("Preview build arguments."), - previewLabels: z - .array(z.string()) - .nullable() - .optional() - .describe("Preview labels."), - previewWildcard: z - .string() - .nullable() - .optional() - .describe("Preview wildcard configuration."), - previewPort: z - .number() - .nullable() - .optional() - .describe("Preview port number."), - previewHttps: z.boolean().optional().describe("Enable HTTPS for preview."), - previewPath: z.string().nullable().optional().describe("Preview path."), - previewCertificateType: z - .enum(["letsencrypt", "none", "custom"]) - .optional() - .describe("Preview certificate type."), - previewCustomCertResolver: z - .string() - .nullable() - .optional() - .describe("Custom certificate resolver for preview."), - previewLimit: z - .number() - .nullable() - .optional() - .describe("Preview deployment limit."), - isPreviewDeploymentsActive: z - .boolean() - .nullable() - .optional() - .describe("Whether preview deployments are active."), - previewRequireCollaboratorPermissions: z - .boolean() - .nullable() - .optional() - .describe( - "Whether preview deployments require collaborator permissions." - ), - rollbackActive: z - .boolean() - .nullable() - .optional() - .describe("Whether rollback is active."), - buildArgs: z.string().nullable().optional().describe("Build arguments."), - memoryReservation: z - .string() - .nullable() - .optional() - .describe("Memory reservation for the application."), - memoryLimit: z - .string() - .nullable() - .optional() - .describe("Memory limit for the application."), - cpuReservation: z - .string() - .nullable() - .optional() - .describe("CPU reservation for the application."), - cpuLimit: z - .string() - .nullable() - .optional() - .describe("CPU limit for the application."), - title: z.string().nullable().optional().describe("Application title."), - enabled: z - .boolean() - .nullable() - .optional() - .describe("Whether the application is enabled."), - subtitle: z - .string() - .nullable() - .optional() - .describe("Application subtitle."), - command: z - .string() - .nullable() - .optional() - .describe("Command to run the application."), - refreshToken: z - .string() - .nullable() - .optional() - .describe("Refresh token for the application."), - sourceType: z - .enum(["github", "docker", "git", "gitlab", "bitbucket", "gitea", "drop"]) - .optional() - .describe("Source type for the application."), - cleanCache: z - .boolean() - .nullable() - .optional() - .describe("Whether to clean cache on build."), - repository: z.string().nullable().optional().describe("Repository URL."), - owner: z.string().nullable().optional().describe("Repository owner."), - branch: z.string().nullable().optional().describe("Repository branch."), - buildPath: z - .string() - .nullable() - .optional() - .describe("Build path within repository."), - triggerType: z - .enum(["push", "tag"]) - .nullable() - .optional() - .describe("Trigger type for deployments."), - autoDeploy: z - .boolean() - .nullable() - .optional() - .describe("Whether to auto-deploy."), - gitlabProjectId: z - .number() - .nullable() - .optional() - .describe("GitLab project ID."), - gitlabRepository: z - .string() - .nullable() - .optional() - .describe("GitLab repository."), - gitlabOwner: z - .string() - .nullable() - .optional() - .describe("GitLab repository owner."), - gitlabBranch: z.string().nullable().optional().describe("GitLab branch."), - gitlabBuildPath: z - .string() - .nullable() - .optional() - .describe("GitLab build path."), - gitlabPathNamespace: z - .string() - .nullable() - .optional() - .describe("GitLab path namespace."), - giteaRepository: z - .string() - .nullable() - .optional() - .describe("Gitea repository."), - giteaOwner: z - .string() - .nullable() - .optional() - .describe("Gitea repository owner."), - giteaBranch: z.string().nullable().optional().describe("Gitea branch."), - giteaBuildPath: z - .string() - .nullable() - .optional() - .describe("Gitea build path."), - bitbucketRepository: z - .string() - .nullable() - .optional() - .describe("Bitbucket repository."), - bitbucketOwner: z - .string() - .nullable() - .optional() - .describe("Bitbucket repository owner."), - bitbucketBranch: z - .string() - .nullable() - .optional() - .describe("Bitbucket branch."), - bitbucketBuildPath: z - .string() - .nullable() - .optional() - .describe("Bitbucket build path."), - username: z - .string() - .nullable() - .optional() - .describe("Username for authentication."), - password: z - .string() - .nullable() - .optional() - .describe("Password for authentication."), - dockerImage: z.string().nullable().optional().describe("Docker image."), - registryUrl: z - .string() - .nullable() - .optional() - .describe("Docker registry URL."), - customGitUrl: z.string().nullable().optional().describe("Custom Git URL."), - customGitBranch: z - .string() - .nullable() - .optional() - .describe("Custom Git branch."), - customGitBuildPath: z - .string() - .nullable() - .optional() - .describe("Custom Git build path."), - customGitSSHKeyId: z - .string() - .nullable() - .optional() - .describe("Custom Git SSH key ID."), - enableSubmodules: z - .boolean() - .optional() - .describe("Whether to enable Git submodules."), - dockerfile: z - .string() - .nullable() - .optional() - .describe("Dockerfile content or path."), - dockerContextPath: z - .string() - .nullable() - .optional() - .describe("Docker context path."), - dockerBuildStage: z - .string() - .nullable() - .optional() - .describe("Docker build stage."), - dropBuildPath: z - .string() - .nullable() - .optional() - .describe("Drop build path."), - healthCheckSwarm: z - .object({ - Test: z.array(z.string()).optional(), - Interval: z.number().optional(), - Timeout: z.number().optional(), - StartPeriod: z.number().optional(), - Retries: z.number().optional(), - }) - .nullable() - .optional() - .describe("Docker Swarm health check configuration."), - restartPolicySwarm: z - .object({ - Condition: z.string().optional(), - Delay: z.number().optional(), - MaxAttempts: z.number().optional(), - Window: z.number().optional(), - }) - .nullable() - .optional() - .describe("Docker Swarm restart policy configuration."), - placementSwarm: z - .object({ - Constraints: z.array(z.string()).optional(), - Preferences: z - .array( - z.object({ - Spread: z.object({ - SpreadDescriptor: z.string(), - }), - }) - ) - .optional(), - MaxReplicas: z.number().optional(), - Platforms: z - .array( - z.object({ - Architecture: z.string(), - OS: z.string(), - }) - ) - .optional(), - }) - .nullable() - .optional() - .describe("Docker Swarm placement configuration."), - updateConfigSwarm: z - .object({ - Parallelism: z.number(), - Delay: z.number().optional(), - FailureAction: z.string().optional(), - Monitor: z.number().optional(), - MaxFailureRatio: z.number().optional(), - Order: z.string(), - }) - .nullable() - .optional() - .describe("Docker Swarm update configuration."), - rollbackConfigSwarm: z - .object({ - Parallelism: z.number(), - Delay: z.number().optional(), - FailureAction: z.string().optional(), - Monitor: z.number().optional(), - MaxFailureRatio: z.number().optional(), - Order: z.string(), - }) - .nullable() - .optional() - .describe("Docker Swarm rollback configuration."), - modeSwarm: z - .object({ - Replicated: z - .object({ - Replicas: z.number().optional(), - }) - .optional(), - Global: z.object({}).optional(), - ReplicatedJob: z - .object({ - MaxConcurrent: z.number().optional(), - TotalCompletions: z.number().optional(), - }) - .optional(), - GlobalJob: z.object({}).optional(), - }) - .nullable() - .optional() - .describe("Docker Swarm mode configuration."), - labelsSwarm: z - .record(z.string()) - .nullable() - .optional() - .describe("Docker Swarm labels."), - networkSwarm: z - .array( - z.object({ - Target: z.string().optional(), - Aliases: z.array(z.string()).optional(), - DriverOpts: z.object({}).optional(), - }) - ) - .nullable() - .optional() - .describe("Docker Swarm network configuration."), - stopGracePeriodSwarm: z - .number() - .int() - .nullable() - .optional() - .describe("Docker Swarm stop grace period in seconds."), - replicas: z.number().optional().describe("Number of replicas."), - applicationStatus: z - .enum(["idle", "running", "done", "error"]) - .optional() - .describe("Application status."), - buildType: z - .enum([ - "dockerfile", - "heroku_buildpacks", - "paketo_buildpacks", - "nixpacks", - "static", - "railpack", - ]) - .optional() - .describe("Build type."), - railpackVersion: z - .string() - .nullable() - .optional() - .describe("Railpack version."), - herokuVersion: z.string().nullable().optional().describe("Heroku version."), - publishDirectory: z - .string() - .nullable() - .optional() - .describe("Publish directory."), - isStaticSpa: z - .boolean() - .nullable() - .optional() - .describe("Whether the application is a static SPA."), - createdAt: z.string().optional().describe("Creation date."), - registryId: z.string().nullable().optional().describe("Registry ID."), - environmentId: z.string().optional().describe("Environment ID."), - githubId: z - .string() - .nullable() - .optional() - .describe("GitHub integration ID."), - gitlabId: z - .string() - .nullable() - .optional() - .describe("GitLab integration ID."), - giteaId: z.string().nullable().optional().describe("Gitea integration ID."), - bitbucketId: z - .string() - .nullable() - .optional() - .describe("Bitbucket integration ID."), - }), - annotations: { - title: "Update Application", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/application.update", input); - - return ResponseFormatter.success( - `Application "${input.applicationId}" updated successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/application/applicationUpdateTraefikConfig.ts b/src/mcp/tools/application/applicationUpdateTraefikConfig.ts deleted file mode 100644 index 22ba893..0000000 --- a/src/mcp/tools/application/applicationUpdateTraefikConfig.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const applicationUpdateTraefikConfig = createTool({ - name: "application-updateTraefikConfig", - description: "Updates Traefik configuration for an application in Dokploy.", - schema: z.object({ - applicationId: z - .string() - .describe("The ID of the application to update Traefik config for."), - traefikConfig: z - .string() - .describe("The new Traefik configuration content."), - }), - annotations: { - title: "Update Application Traefik Config", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post( - "/application.updateTraefikConfig", - input - ); - - return ResponseFormatter.success( - `Traefik configuration for application "${input.applicationId}" updated successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/application/index.ts b/src/mcp/tools/application/index.ts deleted file mode 100644 index 96a5b64..0000000 --- a/src/mcp/tools/application/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -export { applicationCancelDeployment } from "./applicationCancelDeployment.js"; -export { applicationCleanQueues } from "./applicationCleanQueues.js"; -export { applicationCreate } from "./applicationCreate.js"; -export { applicationDelete } from "./applicationDelete.js"; -export { applicationDeploy } from "./applicationDeploy.js"; -export { applicationDisconnectGitProvider } from "./applicationDisconnectGitProvider.js"; -export { applicationMarkRunning } from "./applicationMarkRunning.js"; -export { applicationMove } from "./applicationMove.js"; -export { applicationOne } from "./applicationOne.js"; -export { applicationReadAppMonitoring } from "./applicationReadAppMonitoring.js"; -export { applicationReadTraefikConfig } from "./applicationReadTraefikConfig.js"; -export { applicationRedeploy } from "./applicationRedeploy.js"; -export { applicationRefreshToken } from "./applicationRefreshToken.js"; -export { applicationReload } from "./applicationReload.js"; -export { applicationSaveBitbucketProvider } from "./applicationSaveBitbucketProvider.js"; -export { applicationSaveBuildType } from "./applicationSaveBuildType.js"; -export { applicationSaveDockerProvider } from "./applicationSaveDockerProvider.js"; -export { applicationSaveEnvironment } from "./applicationSaveEnvironment.js"; -export { applicationSaveGiteaProvider } from "./applicationSaveGiteaProvider.js"; -export { applicationSaveGithubProvider } from "./applicationSaveGithubProvider.js"; -export { applicationSaveGitlabProvider } from "./applicationSaveGitlabProvider.js"; -export { applicationSaveGitProvider } from "./applicationSaveGitProvider.js"; -export { applicationStart } from "./applicationStart.js"; -export { applicationStop } from "./applicationStop.js"; -export { applicationUpdate } from "./applicationUpdate.js"; -export { applicationUpdateTraefikConfig } from "./applicationUpdateTraefikConfig.js"; diff --git a/src/mcp/tools/domain/domainByApplicationId.ts b/src/mcp/tools/domain/domainByApplicationId.ts deleted file mode 100644 index 9b0bb9b..0000000 --- a/src/mcp/tools/domain/domainByApplicationId.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { createTool } from "../toolFactory.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; - -export const domainByApplicationId = createTool({ - name: "domain-byApplicationId", - description: - "Retrieves all domains associated with a specific application in Dokploy. Returns a list of domain configurations including SSL settings, paths, and routing information.", - schema: z.object({ - applicationId: z - .string() - .min(1) - .describe("The ID of the application to retrieve domains for."), - }), - annotations: { - title: "Get Domains by Application ID", - destructiveHint: false, - idempotentHint: true, - openWorldHint: false, - }, - handler: async (input) => { - const response = await apiClient.get("/domain.byApplicationId", { - params: { - applicationId: input.applicationId, - }, - }); - - return ResponseFormatter.success( - `Successfully retrieved domains for application ${input.applicationId}`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/domain/domainByComposeId.ts b/src/mcp/tools/domain/domainByComposeId.ts deleted file mode 100644 index e51fb78..0000000 --- a/src/mcp/tools/domain/domainByComposeId.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { createTool } from "../toolFactory.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; - -export const domainByComposeId = createTool({ - name: "domain-byComposeId", - description: - "Retrieves all domains associated with a specific compose stack/service in Dokploy. Returns a list of domain configurations including SSL settings, paths, and routing information.", - schema: z.object({ - composeId: z - .string() - .min(1) - .describe("The ID of the compose service to retrieve domains for."), - }), - annotations: { - title: "Get Domains by Compose ID", - destructiveHint: false, - idempotentHint: true, - openWorldHint: false, - }, - handler: async (input) => { - const response = await apiClient.get("/domain.byComposeId", { - params: { - composeId: input.composeId, - }, - }); - - return ResponseFormatter.success( - `Successfully retrieved domains for compose ${input.composeId}`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/domain/domainCanGenerateTraefikMeDomains.ts b/src/mcp/tools/domain/domainCanGenerateTraefikMeDomains.ts deleted file mode 100644 index 73353f0..0000000 --- a/src/mcp/tools/domain/domainCanGenerateTraefikMeDomains.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { createTool } from "../toolFactory.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; - -export const domainCanGenerateTraefikMeDomains = createTool({ - name: "domain-canGenerateTraefikMeDomains", - description: - "Checks whether Traefik.me domains can be generated for a specific server in Dokploy.", - schema: z.object({ - serverId: z - .string() - .min(1) - .describe( - "The server ID to verify Traefik.me domain generation support for." - ), - }), - annotations: { - title: "Can Generate Traefik.me Domains", - destructiveHint: false, - idempotentHint: true, - openWorldHint: false, - }, - handler: async (input) => { - const response = await apiClient.get( - "/domain.canGenerateTraefikMeDomains", - { - params: { - serverId: input.serverId, - }, - } - ); - - return ResponseFormatter.success( - `Retrieved Traefik.me domain generation availability for server ${input.serverId}`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/domain/domainCreate.ts b/src/mcp/tools/domain/domainCreate.ts deleted file mode 100644 index 8f97e4f..0000000 --- a/src/mcp/tools/domain/domainCreate.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { createTool } from "../toolFactory.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; - -export const domainCreate = createTool({ - name: "domain-create", - description: - "Creates a new domain configuration in Dokploy. Domains can be configured for applications, compose services, or preview deployments with SSL/TLS certificate options.", - schema: z.object({ - host: z - .string() - .min(1) - .describe( - "The domain host (e.g., example.com or subdomain.example.com)." - ), - path: z - .string() - .min(1) - .nullable() - .optional() - .describe( - "Optional path for the domain (e.g., /api). Used for path-based routing." - ), - port: z - .number() - .min(1) - .max(65535) - .nullable() - .optional() - .describe( - "The port number for the service (1-65535). If not specified, defaults will be used." - ), - https: z.boolean().describe("Whether to enable HTTPS for this domain."), - applicationId: z - .string() - .nullable() - .optional() - .describe( - "The ID of the application to associate this domain with. Required if domainType is 'application'." - ), - certificateType: z - .enum(["letsencrypt", "none", "custom"]) - .describe( - "The type of SSL certificate: 'letsencrypt' for automatic Let's Encrypt certificates, 'none' for no SSL, or 'custom' for custom certificates." - ), - customCertResolver: z - .string() - .nullable() - .optional() - .describe( - "Custom certificate resolver name. Required when certificateType is 'custom'." - ), - composeId: z - .string() - .nullable() - .optional() - .describe( - "The ID of the compose service to associate this domain with. Required if domainType is 'compose'." - ), - serviceName: z - .string() - .nullable() - .optional() - .describe( - "The name of the service within the compose stack. Used with composeId." - ), - domainType: z - .enum(["compose", "application", "preview"]) - .nullable() - .optional() - .describe( - "The type of domain: 'application' for app domains, 'compose' for compose services, or 'preview' for preview deployments." - ), - previewDeploymentId: z - .string() - .nullable() - .optional() - .describe( - "The ID of the preview deployment. Required if domainType is 'preview'." - ), - internalPath: z - .string() - .nullable() - .optional() - .describe("Internal path for routing within the container/service."), - stripPath: z - .boolean() - .describe( - "Whether to strip the path prefix when forwarding requests to the backend service." - ), - }), - annotations: { - title: "Create Domain", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/domain.create", input); - - return ResponseFormatter.success( - `Domain "${input.host}" created successfully with ${input.certificateType} certificate type`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/domain/domainDelete.ts b/src/mcp/tools/domain/domainDelete.ts deleted file mode 100644 index 633fa80..0000000 --- a/src/mcp/tools/domain/domainDelete.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { createTool } from "../toolFactory.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; - -export const domainDelete = createTool({ - name: "domain-delete", - description: "Deletes an existing domain configuration in Dokploy by its ID.", - schema: z.object({ - domainId: z.string().min(1).describe("The ID of the domain to delete."), - }), - annotations: { - title: "Delete Domain", - destructiveHint: true, - idempotentHint: false, - openWorldHint: false, - }, - handler: async (input) => { - const response = await apiClient.post("/domain.delete", { - domainId: input.domainId, - }); - - return ResponseFormatter.success( - `Domain ${input.domainId} deleted successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/domain/domainGenerateDomain.ts b/src/mcp/tools/domain/domainGenerateDomain.ts deleted file mode 100644 index d64d094..0000000 --- a/src/mcp/tools/domain/domainGenerateDomain.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { createTool } from "../toolFactory.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; - -export const domainGenerateDomain = createTool({ - name: "domain-generateDomain", - description: - "Generates a suggested domain for a given application name, optionally scoped to a server.", - schema: z.object({ - appName: z - .string() - .min(1) - .describe("The application name to generate a domain for."), - serverId: z - .string() - .optional() - .describe("Optional server ID to use when generating the domain."), - }), - annotations: { - title: "Generate Domain", - destructiveHint: false, - idempotentHint: true, - openWorldHint: false, - }, - handler: async (input) => { - const response = await apiClient.post("/domain.generateDomain", input); - - return ResponseFormatter.success( - `Successfully generated domain for app \"${input.appName}\"`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/domain/domainOne.ts b/src/mcp/tools/domain/domainOne.ts deleted file mode 100644 index 78e52f1..0000000 --- a/src/mcp/tools/domain/domainOne.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { createTool } from "../toolFactory.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; - -export const domainOne = createTool({ - name: "domain-one", - description: - "Retrieves a specific domain configuration by its ID in Dokploy.", - schema: z.object({ - domainId: z.string().min(1).describe("The ID of the domain to retrieve."), - }), - annotations: { - title: "Get Domain by ID", - destructiveHint: false, - idempotentHint: true, - openWorldHint: false, - }, - handler: async (input) => { - const response = await apiClient.get("/domain.one", { - params: { domainId: input.domainId }, - }); - - return ResponseFormatter.success( - `Successfully retrieved domain ${input.domainId}`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/domain/domainUpdate.ts b/src/mcp/tools/domain/domainUpdate.ts deleted file mode 100644 index 23ca829..0000000 --- a/src/mcp/tools/domain/domainUpdate.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { createTool } from "../toolFactory.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; - -export const domainUpdate = createTool({ - name: "domain-update", - description: - "Updates an existing domain configuration in Dokploy. Allows modifying domain settings including host, SSL configuration, routing options, and service associations.", - schema: z.object({ - host: z - .string() - .min(1) - .describe( - "The domain host (e.g., example.com or subdomain.example.com)." - ), - path: z - .string() - .min(1) - .nullable() - .optional() - .describe( - "Optional path for the domain (e.g., /api). Used for path-based routing." - ), - port: z - .number() - .min(1) - .max(65535) - .nullable() - .optional() - .describe( - "The port number for the service (1-65535). If not specified, defaults will be used." - ), - https: z.boolean().describe("Whether to enable HTTPS for this domain."), - certificateType: z - .enum(["letsencrypt", "none", "custom"]) - .describe( - "The type of SSL certificate: 'letsencrypt' for automatic Let's Encrypt certificates, 'none' for no SSL, or 'custom' for custom certificates." - ), - customCertResolver: z - .string() - .nullable() - .optional() - .describe( - "Custom certificate resolver name. Required when certificateType is 'custom'." - ), - serviceName: z - .string() - .nullable() - .optional() - .describe( - "The name of the service within the compose stack. Used with composeId." - ), - domainType: z - .enum(["compose", "application", "preview"]) - .nullable() - .optional() - .describe( - "The type of domain: 'application' for app domains, 'compose' for compose services, or 'preview' for preview deployments." - ), - internalPath: z - .string() - .nullable() - .optional() - .describe("Internal path for routing within the container/service."), - stripPath: z - .boolean() - .describe( - "Whether to strip the path prefix when forwarding requests to the backend service." - ), - domainId: z.string().describe("The ID of the domain to update."), - }), - annotations: { - title: "Update Domain", - destructiveHint: false, - idempotentHint: true, - openWorldHint: false, - }, - handler: async (input) => { - const response = await apiClient.post("/domain.update", input); - - return ResponseFormatter.success( - `Domain "${input.host}" updated successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/domain/domainValidateDomain.ts b/src/mcp/tools/domain/domainValidateDomain.ts deleted file mode 100644 index 6a51fa2..0000000 --- a/src/mcp/tools/domain/domainValidateDomain.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { createTool } from "../toolFactory.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; - -export const domainValidateDomain = createTool({ - name: "domain-validateDomain", - description: - "Validates if a domain is correctly configured, optionally against a specific server IP.", - schema: z.object({ - domain: z - .string() - .min(1) - .describe("The domain name to validate (e.g., example.com)."), - serverIp: z - .string() - .optional() - .describe("Optional server IP to validate DNS resolution against."), - }), - annotations: { - title: "Validate Domain", - destructiveHint: false, - idempotentHint: true, - openWorldHint: false, - }, - handler: async (input) => { - const response = await apiClient.post("/domain.validateDomain", input); - - return ResponseFormatter.success( - `Validation result for domain "${input.domain}":`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/domain/index.ts b/src/mcp/tools/domain/index.ts deleted file mode 100644 index 35ed09b..0000000 --- a/src/mcp/tools/domain/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -export { domainCreate } from "./domainCreate.js"; -export { domainByApplicationId } from "./domainByApplicationId.js"; -export { domainByComposeId } from "./domainByComposeId.js"; -export { domainGenerateDomain } from "./domainGenerateDomain.js"; -export { domainCanGenerateTraefikMeDomains } from "./domainCanGenerateTraefikMeDomains.js"; -export { domainUpdate } from "./domainUpdate.js"; -export { domainOne } from "./domainOne.js"; -export { domainDelete } from "./domainDelete.js"; -export { domainValidateDomain } from "./domainValidateDomain.js"; diff --git a/src/mcp/tools/index.ts b/src/mcp/tools/index.ts index 5a9e1ac..5881e83 100644 --- a/src/mcp/tools/index.ts +++ b/src/mcp/tools/index.ts @@ -1,13 +1,2 @@ -import * as applicationTools from "./application/index.js"; -import * as domainTools from "./domain/index.js"; -import * as mysqlTools from "./mysql/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(domainTools), - ...Object.values(mysqlTools), - ...Object.values(postgresTools), -]; +export * as api from "./api.js"; +export * as apiSchema from "./apiSchema.js"; diff --git a/src/mcp/tools/mysql/index.ts b/src/mcp/tools/mysql/index.ts deleted file mode 100644 index 36cff69..0000000 --- a/src/mcp/tools/mysql/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -export { mysqlChangeStatus } from "./mysqlChangeStatus.js"; -export { mysqlCreate } from "./mysqlCreate.js"; -export { mysqlDeploy } from "./mysqlDeploy.js"; -export { mysqlMove } from "./mysqlMove.js"; -export { mysqlOne } from "./mysqlOne.js"; -export { mysqlRebuild } from "./mysqlRebuild.js"; -export { mysqlReload } from "./mysqlReload.js"; -export { mysqlRemove } from "./mysqlRemove.js"; -export { mysqlSaveEnvironment } from "./mysqlSaveEnvironment.js"; -export { mysqlSaveExternalPort } from "./mysqlSaveExternalPort.js"; -export { mysqlStart } from "./mysqlStart.js"; -export { mysqlStop } from "./mysqlStop.js"; -export { mysqlUpdate } from "./mysqlUpdate.js"; diff --git a/src/mcp/tools/mysql/mysqlChangeStatus.ts b/src/mcp/tools/mysql/mysqlChangeStatus.ts deleted file mode 100644 index 0143e4a..0000000 --- a/src/mcp/tools/mysql/mysqlChangeStatus.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const mysqlChangeStatus = createTool({ - name: "mysql-changeStatus", - description: "Changes the status of a MySQL database in Dokploy.", - schema: z.object({ - mysqlId: z.string().describe("The ID of the MySQL database to update."), - applicationStatus: z - .enum(["idle", "running", "done", "error"]) - .describe("The new status for the MySQL database."), - }), - annotations: { - title: "Change MySQL Database Status", - destructiveHint: false, - idempotentHint: true, - openWorldHint: false, - }, - handler: async (input) => { - const response = await apiClient.post("/mysql.changeStatus", input); - - return ResponseFormatter.success( - `MySQL database status changed to "${input.applicationStatus}" successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/mysql/mysqlCreate.ts b/src/mcp/tools/mysql/mysqlCreate.ts deleted file mode 100644 index a06ea34..0000000 --- a/src/mcp/tools/mysql/mysqlCreate.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const mysqlCreate = createTool({ - name: "mysql-create", - description: "Creates a new MySQL database in Dokploy.", - schema: z.object({ - name: z.string().min(1).describe("The name of the MySQL database."), - appName: z.string().min(1).describe("The app name for the MySQL database."), - databaseName: z - .string() - .min(1) - .describe("The name of the database to create."), - databaseUser: z - .string() - .min(1) - .describe("The username for database access."), - databasePassword: z - .string() - .regex( - /^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, - "Password contains invalid characters" - ) - .describe("The password for database access."), - databaseRootPassword: z - .string() - .regex( - /^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, - "Root password contains invalid characters" - ) - .describe("The root password for MySQL."), - environmentId: z - .string() - .describe( - "The ID of the environment where the database will be created." - ), - dockerImage: z - .string() - .optional() - .default("mysql:8") - .describe("Docker image to use for MySQL."), - description: z - .string() - .nullable() - .optional() - .describe("An optional description for the database."), - serverId: z - .string() - .nullable() - .optional() - .describe("The ID of the server where the database will be deployed."), - }), - annotations: { - title: "Create MySQL Database", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/mysql.create", input); - - return ResponseFormatter.success( - `MySQL database "${input.name}" created successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/mysql/mysqlDeploy.ts b/src/mcp/tools/mysql/mysqlDeploy.ts deleted file mode 100644 index dffd1fc..0000000 --- a/src/mcp/tools/mysql/mysqlDeploy.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const mysqlDeploy = createTool({ - name: "mysql-deploy", - description: "Deploys a MySQL database in Dokploy.", - schema: z.object({ - mysqlId: z.string().describe("The ID of the MySQL database to deploy."), - }), - annotations: { - title: "Deploy MySQL Database", - destructiveHint: false, - idempotentHint: false, - openWorldHint: false, - }, - handler: async (input) => { - const response = await apiClient.post("/mysql.deploy", input); - - return ResponseFormatter.success( - "MySQL database deployed successfully", - response.data - ); - }, -}); diff --git a/src/mcp/tools/mysql/mysqlMove.ts b/src/mcp/tools/mysql/mysqlMove.ts deleted file mode 100644 index 6bc84a8..0000000 --- a/src/mcp/tools/mysql/mysqlMove.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const mysqlMove = createTool({ - name: "mysql-move", - description: "Moves a MySQL database to a different environment in Dokploy.", - schema: z.object({ - mysqlId: z.string().describe("The ID of the MySQL database to move."), - targetEnvironmentId: z - .string() - .describe("The ID of the target environment to move the database to."), - }), - annotations: { - title: "Move MySQL Database", - destructiveHint: false, - idempotentHint: true, - openWorldHint: false, - }, - handler: async (input) => { - const response = await apiClient.post("/mysql.move", input); - - return ResponseFormatter.success( - "MySQL database moved successfully", - response.data - ); - }, -}); diff --git a/src/mcp/tools/mysql/mysqlOne.ts b/src/mcp/tools/mysql/mysqlOne.ts deleted file mode 100644 index 74ca76e..0000000 --- a/src/mcp/tools/mysql/mysqlOne.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const mysqlOne = createTool({ - name: "mysql-one", - description: "Gets a specific MySQL database by its ID in Dokploy.", - schema: z.object({ - mysqlId: z.string().describe("The ID of the MySQL database to retrieve."), - }), - annotations: { - title: "Get MySQL Database", - destructiveHint: false, - idempotentHint: true, - openWorldHint: false, - }, - handler: async (input) => { - const response = await apiClient.get("/mysql.one", { - params: { mysqlId: input.mysqlId }, - }); - - return ResponseFormatter.success( - "MySQL database retrieved successfully", - response.data - ); - }, -}); diff --git a/src/mcp/tools/mysql/mysqlRebuild.ts b/src/mcp/tools/mysql/mysqlRebuild.ts deleted file mode 100644 index c1b8932..0000000 --- a/src/mcp/tools/mysql/mysqlRebuild.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const mysqlRebuild = createTool({ - name: "mysql-rebuild", - description: "Rebuilds a MySQL database in Dokploy.", - schema: z.object({ - mysqlId: z.string().describe("The ID of the MySQL database to rebuild."), - }), - annotations: { - title: "Rebuild MySQL Database", - destructiveHint: false, - idempotentHint: false, - openWorldHint: false, - }, - handler: async (input) => { - const response = await apiClient.post("/mysql.rebuild", input); - - return ResponseFormatter.success( - "MySQL database rebuild initiated successfully", - response.data - ); - }, -}); diff --git a/src/mcp/tools/mysql/mysqlReload.ts b/src/mcp/tools/mysql/mysqlReload.ts deleted file mode 100644 index 7027169..0000000 --- a/src/mcp/tools/mysql/mysqlReload.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const mysqlReload = createTool({ - name: "mysql-reload", - description: "Reloads a MySQL database configuration in Dokploy.", - schema: z.object({ - mysqlId: z.string().describe("The ID of the MySQL database to reload."), - appName: z.string().min(1).describe("The app name for the MySQL database."), - }), - annotations: { - title: "Reload MySQL Database", - destructiveHint: false, - idempotentHint: true, - openWorldHint: false, - }, - handler: async (input) => { - const response = await apiClient.post("/mysql.reload", input); - - return ResponseFormatter.success( - "MySQL database reloaded successfully", - response.data - ); - }, -}); diff --git a/src/mcp/tools/mysql/mysqlRemove.ts b/src/mcp/tools/mysql/mysqlRemove.ts deleted file mode 100644 index fd6fd79..0000000 --- a/src/mcp/tools/mysql/mysqlRemove.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const mysqlRemove = createTool({ - name: "mysql-remove", - description: "Removes a MySQL database from Dokploy.", - schema: z.object({ - mysqlId: z.string().describe("The ID of the MySQL database to remove."), - }), - annotations: { - title: "Remove MySQL Database", - destructiveHint: true, - idempotentHint: false, - openWorldHint: false, - }, - handler: async (input) => { - const response = await apiClient.post("/mysql.remove", input); - - return ResponseFormatter.success( - "MySQL database removed successfully", - response.data - ); - }, -}); diff --git a/src/mcp/tools/mysql/mysqlSaveEnvironment.ts b/src/mcp/tools/mysql/mysqlSaveEnvironment.ts deleted file mode 100644 index c2974f7..0000000 --- a/src/mcp/tools/mysql/mysqlSaveEnvironment.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const mysqlSaveEnvironment = createTool({ - name: "mysql-saveEnvironment", - description: "Saves environment variables for a MySQL database in Dokploy.", - schema: z.object({ - mysqlId: z.string().describe("The ID of the MySQL database to configure."), - env: z - .string() - .nullable() - .optional() - .describe("Environment variables to set for the MySQL database."), - }), - annotations: { - title: "Save MySQL Environment Variables", - destructiveHint: false, - idempotentHint: true, - openWorldHint: false, - }, - handler: async (input) => { - const response = await apiClient.post("/mysql.saveEnvironment", input); - - return ResponseFormatter.success( - "MySQL environment variables saved successfully", - response.data - ); - }, -}); diff --git a/src/mcp/tools/mysql/mysqlSaveExternalPort.ts b/src/mcp/tools/mysql/mysqlSaveExternalPort.ts deleted file mode 100644 index de5fbc9..0000000 --- a/src/mcp/tools/mysql/mysqlSaveExternalPort.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const mysqlSaveExternalPort = createTool({ - name: "mysql-saveExternalPort", - description: - "Saves external port configuration for a MySQL database in Dokploy.", - schema: z.object({ - mysqlId: z.string().describe("The ID of the MySQL database to configure."), - externalPort: z - .number() - .nullable() - .describe("The external port number to expose the database on."), - }), - annotations: { - title: "Save MySQL External Port", - destructiveHint: false, - idempotentHint: true, - openWorldHint: false, - }, - handler: async (input) => { - const response = await apiClient.post("/mysql.saveExternalPort", input); - - return ResponseFormatter.success( - "MySQL external port configuration saved successfully", - response.data - ); - }, -}); diff --git a/src/mcp/tools/mysql/mysqlStart.ts b/src/mcp/tools/mysql/mysqlStart.ts deleted file mode 100644 index 28ad762..0000000 --- a/src/mcp/tools/mysql/mysqlStart.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const mysqlStart = createTool({ - name: "mysql-start", - description: "Starts a MySQL database in Dokploy.", - schema: z.object({ - mysqlId: z.string().describe("The ID of the MySQL database to start."), - }), - annotations: { - title: "Start MySQL Database", - destructiveHint: false, - idempotentHint: true, - openWorldHint: false, - }, - handler: async (input) => { - const response = await apiClient.post("/mysql.start", input); - - return ResponseFormatter.success( - "MySQL database started successfully", - response.data - ); - }, -}); diff --git a/src/mcp/tools/mysql/mysqlStop.ts b/src/mcp/tools/mysql/mysqlStop.ts deleted file mode 100644 index fb2be78..0000000 --- a/src/mcp/tools/mysql/mysqlStop.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const mysqlStop = createTool({ - name: "mysql-stop", - description: "Stops a MySQL database in Dokploy.", - schema: z.object({ - mysqlId: z.string().describe("The ID of the MySQL database to stop."), - }), - annotations: { - title: "Stop MySQL Database", - destructiveHint: false, - idempotentHint: true, - openWorldHint: false, - }, - handler: async (input) => { - const response = await apiClient.post("/mysql.stop", input); - - return ResponseFormatter.success( - "MySQL database stopped successfully", - response.data - ); - }, -}); diff --git a/src/mcp/tools/mysql/mysqlUpdate.ts b/src/mcp/tools/mysql/mysqlUpdate.ts deleted file mode 100644 index e74ca19..0000000 --- a/src/mcp/tools/mysql/mysqlUpdate.ts +++ /dev/null @@ -1,228 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const mysqlUpdate = createTool({ - name: "mysql-update", - description: "Updates a MySQL database configuration in Dokploy.", - schema: z.object({ - mysqlId: z - .string() - .min(1) - .describe("The ID of the MySQL database to update."), - name: z - .string() - .min(1) - .optional() - .describe("The name of the MySQL database."), - appName: z - .string() - .min(1) - .optional() - .describe("The app name for the MySQL database."), - description: z - .string() - .nullable() - .optional() - .describe("An optional description for the database."), - databaseName: z - .string() - .min(1) - .optional() - .describe("The name of the database."), - databaseUser: z - .string() - .min(1) - .optional() - .describe("The username for database access."), - databasePassword: z - .string() - .regex( - /^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, - "Password contains invalid characters" - ) - .optional() - .describe("The password for database access."), - databaseRootPassword: z - .string() - .regex( - /^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/, - "Root password contains invalid characters" - ) - .optional() - .describe("The root password for MySQL."), - dockerImage: z - .string() - .optional() - .default("mysql:8") - .describe("Docker image to use for MySQL."), - command: z - .string() - .nullable() - .optional() - .describe("Custom command to run in the container."), - env: z - .string() - .nullable() - .optional() - .describe("Environment variables for the database."), - memoryReservation: z - .string() - .nullable() - .optional() - .describe("Memory reservation for the container."), - memoryLimit: z - .string() - .nullable() - .optional() - .describe("Memory limit for the container."), - cpuReservation: z - .string() - .nullable() - .optional() - .describe("CPU reservation for the container."), - cpuLimit: z - .string() - .nullable() - .optional() - .describe("CPU limit for the container."), - externalPort: z - .number() - .nullable() - .optional() - .describe("External port to expose the database on."), - applicationStatus: z - .enum(["idle", "running", "done", "error"]) - .optional() - .describe("The status of the MySQL database."), - healthCheckSwarm: z - .object({ - Test: z.array(z.string()).optional(), - Interval: z.number().optional(), - Timeout: z.number().optional(), - StartPeriod: z.number().optional(), - Retries: z.number().optional(), - }) - .nullable() - .optional() - .describe("Docker Swarm health check configuration."), - restartPolicySwarm: z - .object({ - Condition: z.string().optional(), - Delay: z.number().optional(), - MaxAttempts: z.number().optional(), - Window: z.number().optional(), - }) - .nullable() - .optional() - .describe("Docker Swarm restart policy configuration."), - placementSwarm: z - .object({ - Constraints: z.array(z.string()).optional(), - Preferences: z - .array( - z.object({ - Spread: z.object({ - SpreadDescriptor: z.string(), - }), - }) - ) - .optional(), - Platforms: z - .array( - z.object({ - Architecture: z.string(), - OS: z.string(), - }) - ) - .optional(), - MaxReplicas: z.number().optional(), - }) - .nullable() - .optional() - .describe("Docker Swarm placement configuration."), - updateConfigSwarm: z - .object({ - Parallelism: z.number(), - Delay: z.number().optional(), - FailureAction: z.string().optional(), - Monitor: z.number().optional(), - MaxFailureRatio: z.number().optional(), - Order: z.string(), - }) - .nullable() - .optional() - .describe("Docker Swarm update configuration."), - rollbackConfigSwarm: z - .object({ - Parallelism: z.number(), - Delay: z.number().optional(), - FailureAction: z.string().optional(), - Monitor: z.number().optional(), - MaxFailureRatio: z.number().optional(), - Order: z.string(), - }) - .nullable() - .optional() - .describe("Docker Swarm rollback configuration."), - modeSwarm: z - .object({ - Replicated: z - .object({ - Replicas: z.number(), - }) - .optional(), - Global: z.object({}).optional(), - ReplicatedJob: z - .object({ - MaxConcurrent: z.number().optional(), - TotalCompletions: z.number().optional(), - }) - .optional(), - GlobalJob: z.object({}).optional(), - }) - .nullable() - .optional() - .describe("Docker Swarm mode configuration."), - labelsSwarm: z - .record(z.string()) - .nullable() - .optional() - .describe("Docker Swarm labels."), - networkSwarm: z - .array( - z.object({ - Target: z.string().optional(), - Aliases: z.array(z.string()).optional(), - DriverOpts: z.object({}).optional(), - }) - ) - .nullable() - .optional() - .describe("Docker Swarm network configuration."), - stopGracePeriodSwarm: z - .number() - .int() - .nullable() - .optional() - .describe("Docker Swarm stop grace period in seconds."), - replicas: z.number().optional().describe("Number of replicas."), - createdAt: z.string().optional().describe("Creation timestamp."), - environmentId: z.string().optional().describe("The ID of the environment."), - }), - annotations: { - title: "Update MySQL Database", - destructiveHint: false, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/mysql.update", input); - - return ResponseFormatter.success( - "MySQL database updated successfully", - response.data - ); - }, -}); diff --git a/src/mcp/tools/postgres/index.ts b/src/mcp/tools/postgres/index.ts deleted file mode 100644 index 9fa6b87..0000000 --- a/src/mcp/tools/postgres/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -export { postgresChangeStatus } from "./postgresChangeStatus.js"; -export { postgresCreate } from "./postgresCreate.js"; -export { postgresDeploy } from "./postgresDeploy.js"; -export { postgresMove } from "./postgresMove.js"; -export { postgresOne } from "./postgresOne.js"; -export { postgresRebuild } from "./postgresRebuild.js"; -export { postgresReload } from "./postgresReload.js"; -export { postgresRemove } from "./postgresRemove.js"; -export { postgresSaveEnvironment } from "./postgresSaveEnvironment.js"; -export { postgresSaveExternalPort } from "./postgresSaveExternalPort.js"; -export { postgresStart } from "./postgresStart.js"; -export { postgresStop } from "./postgresStop.js"; -export { postgresUpdate } from "./postgresUpdate.js"; diff --git a/src/mcp/tools/postgres/postgresChangeStatus.ts b/src/mcp/tools/postgres/postgresChangeStatus.ts deleted file mode 100644 index 2eab3e3..0000000 --- a/src/mcp/tools/postgres/postgresChangeStatus.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const postgresChangeStatus = createTool({ - name: "postgres-changeStatus", - description: "Changes the status of a PostgreSQL database in Dokploy.", - schema: z.object({ - postgresId: z - .string() - .describe("The ID of the PostgreSQL database to change status for."), - applicationStatus: z - .enum(["idle", "running", "done", "error"]) - .describe("The new status for the PostgreSQL database."), - }), - annotations: { - title: "Change PostgreSQL Database Status", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/postgres.changeStatus", input); - - return ResponseFormatter.success( - `PostgreSQL database "${input.postgresId}" status changed to "${input.applicationStatus}" successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/postgres/postgresCreate.ts b/src/mcp/tools/postgres/postgresCreate.ts deleted file mode 100644 index 19cbc06..0000000 --- a/src/mcp/tools/postgres/postgresCreate.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const postgresCreate = createTool({ - name: "postgres-create", - description: "Creates a new PostgreSQL database in Dokploy.", - schema: z.object({ - name: z.string().min(1).describe("The name of the PostgreSQL database."), - appName: z.string().describe("The app name for the PostgreSQL database."), - databaseName: z - .string() - .min(1) - .describe("The name of the database to create."), - databaseUser: z - .string() - .min(1) - .describe("The username for database access."), - databasePassword: z - .string() - .regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/) - .describe("The password for database access."), - dockerImage: z - .string() - .default("postgres:15") - .optional() - .describe("Docker image to use for PostgreSQL."), - environmentId: z - .string() - .describe( - "The ID of the environment where the database will be created." - ), - description: z - .string() - .nullable() - .optional() - .describe("An optional description for the database."), - serverId: z - .string() - .nullable() - .optional() - .describe("The ID of the server where the database will be deployed."), - }), - annotations: { - title: "Create PostgreSQL Database", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/postgres.create", input); - - return ResponseFormatter.success( - `PostgreSQL database "${input.name}" created successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/postgres/postgresDeploy.ts b/src/mcp/tools/postgres/postgresDeploy.ts deleted file mode 100644 index d4fd768..0000000 --- a/src/mcp/tools/postgres/postgresDeploy.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const postgresDeploy = createTool({ - name: "postgres-deploy", - description: "Deploys a PostgreSQL database in Dokploy.", - schema: z.object({ - postgresId: z - .string() - .describe("The ID of the PostgreSQL database to deploy."), - }), - annotations: { - title: "Deploy PostgreSQL Database", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/postgres.deploy", input); - - return ResponseFormatter.success( - `PostgreSQL database "${input.postgresId}" deployment started successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/postgres/postgresMove.ts b/src/mcp/tools/postgres/postgresMove.ts deleted file mode 100644 index ff357d2..0000000 --- a/src/mcp/tools/postgres/postgresMove.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const postgresMove = createTool({ - name: "postgres-move", - description: - "Moves a PostgreSQL database to a different environment in Dokploy.", - schema: z.object({ - postgresId: z - .string() - .describe("The ID of the PostgreSQL database to move."), - targetEnvironmentId: z - .string() - .describe("The ID of the target environment to move the database to."), - }), - annotations: { - title: "Move PostgreSQL Database", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/postgres.move", input); - - return ResponseFormatter.success( - `PostgreSQL database "${input.postgresId}" moved to environment "${input.targetEnvironmentId}" successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/postgres/postgresOne.ts b/src/mcp/tools/postgres/postgresOne.ts deleted file mode 100644 index 6db4f73..0000000 --- a/src/mcp/tools/postgres/postgresOne.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const postgresOne = createTool({ - name: "postgres-one", - description: "Gets a specific PostgreSQL database by its ID in Dokploy.", - schema: z.object({ - postgresId: z - .string() - .describe("The ID of the PostgreSQL database to retrieve."), - }), - annotations: { - title: "Get PostgreSQL Database Details", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const postgres = await apiClient.get( - `/postgres.one?postgresId=${input.postgresId}` - ); - - if (!postgres?.data) { - return ResponseFormatter.error( - "Failed to fetch PostgreSQL database", - `PostgreSQL database with ID "${input.postgresId}" not found` - ); - } - - return ResponseFormatter.success( - `Successfully fetched PostgreSQL database "${input.postgresId}"`, - postgres.data - ); - }, -}); diff --git a/src/mcp/tools/postgres/postgresRebuild.ts b/src/mcp/tools/postgres/postgresRebuild.ts deleted file mode 100644 index abe78d2..0000000 --- a/src/mcp/tools/postgres/postgresRebuild.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const postgresRebuild = createTool({ - name: "postgres-rebuild", - description: "Rebuilds a PostgreSQL database in Dokploy.", - schema: z.object({ - postgresId: z - .string() - .describe("The ID of the PostgreSQL database to rebuild."), - }), - annotations: { - title: "Rebuild PostgreSQL Database", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/postgres.rebuild", input); - - return ResponseFormatter.success( - `PostgreSQL database "${input.postgresId}" rebuild started successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/postgres/postgresReload.ts b/src/mcp/tools/postgres/postgresReload.ts deleted file mode 100644 index 6d2db72..0000000 --- a/src/mcp/tools/postgres/postgresReload.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const postgresReload = createTool({ - name: "postgres-reload", - description: "Reloads a PostgreSQL database in Dokploy.", - schema: z.object({ - postgresId: z - .string() - .describe("The ID of the PostgreSQL database to reload."), - appName: z - .string() - .describe("The app name of the PostgreSQL database to reload."), - }), - annotations: { - title: "Reload PostgreSQL Database", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/postgres.reload", input); - - return ResponseFormatter.success( - `PostgreSQL database "${input.postgresId}" reloaded successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/postgres/postgresRemove.ts b/src/mcp/tools/postgres/postgresRemove.ts deleted file mode 100644 index 299b6e4..0000000 --- a/src/mcp/tools/postgres/postgresRemove.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const postgresRemove = createTool({ - name: "postgres-remove", - description: "Removes/deletes a PostgreSQL database from Dokploy.", - schema: z.object({ - postgresId: z - .string() - .describe("The ID of the PostgreSQL database to remove."), - }), - annotations: { - title: "Remove PostgreSQL Database", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/postgres.remove", input); - - return ResponseFormatter.success( - `PostgreSQL database "${input.postgresId}" removed successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/postgres/postgresSaveEnvironment.ts b/src/mcp/tools/postgres/postgresSaveEnvironment.ts deleted file mode 100644 index f4e2b96..0000000 --- a/src/mcp/tools/postgres/postgresSaveEnvironment.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const postgresSaveEnvironment = createTool({ - name: "postgres-saveEnvironment", - description: - "Saves environment variables for a PostgreSQL database in Dokploy.", - schema: z.object({ - postgresId: z - .string() - .describe("The ID of the PostgreSQL database to save environment for."), - env: z - .string() - .nullable() - .optional() - .describe("Environment variables to save for the PostgreSQL database."), - }), - annotations: { - title: "Save PostgreSQL Environment", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/postgres.saveEnvironment", input); - - return ResponseFormatter.success( - `Environment variables for PostgreSQL database "${input.postgresId}" saved successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/postgres/postgresSaveExternalPort.ts b/src/mcp/tools/postgres/postgresSaveExternalPort.ts deleted file mode 100644 index f5747a9..0000000 --- a/src/mcp/tools/postgres/postgresSaveExternalPort.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const postgresSaveExternalPort = createTool({ - name: "postgres-saveExternalPort", - description: - "Saves external port configuration for a PostgreSQL database in Dokploy.", - schema: z.object({ - postgresId: z - .string() - .describe("The ID of the PostgreSQL database to configure."), - externalPort: z - .number() - .nullable() - .describe("The external port number to expose the database on."), - }), - annotations: { - title: "Save PostgreSQL External Port", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/postgres.saveExternalPort", input); - - return ResponseFormatter.success( - `External port for PostgreSQL database "${input.postgresId}" saved successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/postgres/postgresStart.ts b/src/mcp/tools/postgres/postgresStart.ts deleted file mode 100644 index d5d0ea9..0000000 --- a/src/mcp/tools/postgres/postgresStart.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const postgresStart = createTool({ - name: "postgres-start", - description: "Starts a PostgreSQL database in Dokploy.", - schema: z.object({ - postgresId: z - .string() - .describe("The ID of the PostgreSQL database to start."), - }), - annotations: { - title: "Start PostgreSQL Database", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/postgres.start", input); - - return ResponseFormatter.success( - `PostgreSQL database "${input.postgresId}" started successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/postgres/postgresStop.ts b/src/mcp/tools/postgres/postgresStop.ts deleted file mode 100644 index 25c8246..0000000 --- a/src/mcp/tools/postgres/postgresStop.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const postgresStop = createTool({ - name: "postgres-stop", - description: "Stops a PostgreSQL database in Dokploy.", - schema: z.object({ - postgresId: z - .string() - .describe("The ID of the PostgreSQL database to stop."), - }), - annotations: { - title: "Stop PostgreSQL Database", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/postgres.stop", input); - - return ResponseFormatter.success( - `PostgreSQL database "${input.postgresId}" stopped successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/postgres/postgresUpdate.ts b/src/mcp/tools/postgres/postgresUpdate.ts deleted file mode 100644 index 516057f..0000000 --- a/src/mcp/tools/postgres/postgresUpdate.ts +++ /dev/null @@ -1,212 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const postgresUpdate = createTool({ - name: "postgres-update", - description: "Updates an existing PostgreSQL database in Dokploy.", - schema: z.object({ - postgresId: z - .string() - .min(1) - .describe("The ID of the PostgreSQL database to update."), - name: z - .string() - .min(1) - .optional() - .describe("The new name of the PostgreSQL database."), - appName: z - .string() - .optional() - .describe("The new app name of the PostgreSQL database."), - databaseName: z - .string() - .min(1) - .optional() - .describe("The new database name."), - databaseUser: z - .string() - .min(1) - .optional() - .describe("The new database username."), - databasePassword: z - .string() - .regex(/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/) - .optional() - .describe("The new database password."), - description: z - .string() - .nullable() - .optional() - .describe("The new description for the PostgreSQL database."), - dockerImage: z - .string() - .default("postgres:15") - .optional() - .describe("The new Docker image for PostgreSQL."), - command: z - .string() - .nullable() - .optional() - .describe("Custom command to run the PostgreSQL database."), - env: z - .string() - .nullable() - .optional() - .describe("Environment variables for the PostgreSQL database."), - memoryReservation: z - .string() - .nullable() - .optional() - .describe("Memory reservation for the PostgreSQL database."), - externalPort: z - .number() - .nullable() - .optional() - .describe("External port for the PostgreSQL database."), - memoryLimit: z - .string() - .nullable() - .optional() - .describe("Memory limit for the PostgreSQL database."), - cpuReservation: z - .string() - .nullable() - .optional() - .describe("CPU reservation for the PostgreSQL database."), - cpuLimit: z - .string() - .nullable() - .optional() - .describe("CPU limit for the PostgreSQL database."), - applicationStatus: z - .enum(["idle", "running", "done", "error"]) - .optional() - .describe("Application status."), - healthCheckSwarm: z - .object({ - Test: z.array(z.string()), - Interval: z.number(), - Timeout: z.number(), - StartPeriod: z.number(), - Retries: z.number(), - }) - .nullable() - .optional() - .describe("Docker Swarm health check configuration."), - restartPolicySwarm: z - .object({ - Condition: z.string(), - Delay: z.number(), - MaxAttempts: z.number(), - Window: z.number(), - }) - .nullable() - .optional() - .describe("Docker Swarm restart policy configuration."), - placementSwarm: z - .object({ - Constraints: z.array(z.string()), - Preferences: z.array( - z.object({ - Spread: z.object({ - SpreadDescriptor: z.string(), - }), - }) - ), - MaxReplicas: z.number(), - Platforms: z.array( - z.object({ - Architecture: z.string(), - OS: z.string(), - }) - ), - }) - .nullable() - .optional() - .describe("Docker Swarm placement configuration."), - updateConfigSwarm: z - .object({ - Parallelism: z.number(), - Delay: z.number(), - FailureAction: z.string(), - Monitor: z.number(), - MaxFailureRatio: z.number(), - Order: z.string(), - }) - .nullable() - .optional() - .describe("Docker Swarm update configuration."), - rollbackConfigSwarm: z - .object({ - Parallelism: z.number(), - Delay: z.number(), - FailureAction: z.string(), - Monitor: z.number(), - MaxFailureRatio: z.number(), - Order: z.string(), - }) - .nullable() - .optional() - .describe("Docker Swarm rollback configuration."), - modeSwarm: z - .object({ - Replicated: z - .object({ - Replicas: z.number(), - }) - .optional(), - Global: z.object({}).optional(), - ReplicatedJob: z - .object({ - MaxConcurrent: z.number(), - TotalCompletions: z.number(), - }) - .optional(), - GlobalJob: z.object({}).optional(), - }) - .nullable() - .optional() - .describe("Docker Swarm mode configuration."), - labelsSwarm: z - .record(z.string()) - .nullable() - .optional() - .describe("Docker Swarm labels."), - networkSwarm: z - .array( - z.object({ - Target: z.string(), - Aliases: z.array(z.string()), - DriverOpts: z.object({}), - }) - ) - .nullable() - .optional() - .describe("Docker Swarm network configuration."), - stopGracePeriodSwarm: z - .number() - .int() - .nullable() - .optional() - .describe("Docker Swarm stop grace period in seconds."), - replicas: z.number().optional().describe("Number of replicas."), - createdAt: z.string().optional().describe("Creation date."), - environmentId: z.string().optional().describe("Environment ID."), - }), - annotations: { - title: "Update PostgreSQL Database", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/postgres.update", input); - - return ResponseFormatter.success( - `PostgreSQL database "${input.postgresId}" updated successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/project/index.ts b/src/mcp/tools/project/index.ts deleted file mode 100644 index cf008c6..0000000 --- a/src/mcp/tools/project/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { projectAll } from "./projectAll.js"; -export { projectOne } from "./projectOne.js"; -export { projectCreate } from "./projectCreate.js"; -export { projectUpdate } from "./projectUpdate.js"; -export { projectDuplicate } from "./projectDuplicate.js"; -export { projectRemove } from "./projectRemove.js"; diff --git a/src/mcp/tools/project/projectAll.ts b/src/mcp/tools/project/projectAll.ts deleted file mode 100644 index 6c090cc..0000000 --- a/src/mcp/tools/project/projectAll.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { z } from "zod"; -import type { DokployProject } from "../../../types/dokploy.js"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -export const projectAll = createTool({ - name: "project-all", - description: - "Lists all projects in Dokploy with optimized response size suitable for LLM consumption. Returns summary data including project info, environment counts, and service counts per environment. Excludes large fields like env vars and compose files.", - schema: z.object({}), - annotations: { - title: "List All Projects", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.get("/project.all"); - - if (!response?.data) { - return ResponseFormatter.error( - "Failed to fetch projects", - "No response data received" - ); - } - - const projects = response.data as DokployProject[]; - - // Return optimized summary data with environment structure - const optimizedProjects = projects.map((project) => { - // Calculate total services across all environments - const totalCounts = { - applications: 0, - postgres: 0, - mysql: 0, - mariadb: 0, - mongo: 0, - redis: 0, - compose: 0, - }; - - const environments = - project.environments?.map((env) => { - const envCounts = { - applications: env.applications?.length || 0, - postgres: env.postgres?.length || 0, - mysql: env.mysql?.length || 0, - mariadb: env.mariadb?.length || 0, - mongo: env.mongo?.length || 0, - redis: env.redis?.length || 0, - compose: env.compose?.length || 0, - }; - - // Accumulate totals - totalCounts.applications += envCounts.applications; - totalCounts.postgres += envCounts.postgres; - totalCounts.mysql += envCounts.mysql; - totalCounts.mariadb += envCounts.mariadb; - totalCounts.mongo += envCounts.mongo; - totalCounts.redis += envCounts.redis; - totalCounts.compose += envCounts.compose; - - return { - environmentId: env.environmentId, - name: env.name, - description: env.description, - createdAt: env.createdAt, - - // Service counts for this environment - serviceCounts: envCounts, - - // Basic service details (only essential info, no env vars or large files) - services: { - applications: - env.applications?.map((app) => ({ - applicationId: app.applicationId, - name: app.name, - appName: app.appName, - applicationStatus: app.applicationStatus, - sourceType: app.sourceType, - buildType: app.buildType, - repository: app.repository, - branch: app.branch, - domainCount: app.domains?.length || 0, - })) || [], - - postgres: - env.postgres?.map((db) => ({ - postgresId: db.postgresId, - name: db.name, - appName: db.appName, - applicationStatus: db.applicationStatus, - databaseName: db.databaseName, - })) || [], - - mysql: - env.mysql?.map((db) => ({ - mysqlId: db.mysqlId, - name: db.name, - appName: db.appName, - applicationStatus: db.applicationStatus, - databaseName: db.databaseName, - })) || [], - - mariadb: - env.mariadb?.map((db) => ({ - mariadbId: db.mariadbId, - name: db.name, - appName: db.appName, - applicationStatus: db.applicationStatus, - databaseName: db.databaseName, - })) || [], - - mongo: - env.mongo?.map((db) => ({ - mongoId: db.mongoId, - name: db.name, - appName: db.appName, - applicationStatus: db.applicationStatus, - databaseName: db.databaseName, - })) || [], - - redis: - env.redis?.map((db) => ({ - redisId: db.redisId, - name: db.name, - appName: db.appName, - applicationStatus: db.applicationStatus, - })) || [], - - compose: - env.compose?.map((comp) => ({ - composeId: comp.composeId, - name: comp.name, - appName: comp.appName, - composeStatus: comp.composeStatus, - sourceType: comp.sourceType, - repository: comp.repository, - branch: comp.branch, - domainCount: comp.domains?.length || 0, - })) || [], - }, - }; - }) || []; - - return { - projectId: project.projectId, - name: project.name, - description: project.description, - createdAt: project.createdAt, - organizationId: project.organizationId, - - // Total service counts across all environments - totalServiceCounts: totalCounts, - - // Environment details - environmentCount: environments.length, - environments, - }; - }); - - return ResponseFormatter.success( - `Successfully fetched ${optimizedProjects.length} project(s) with environment details`, - optimizedProjects - ); - }, -}); diff --git a/src/mcp/tools/project/projectCreate.ts b/src/mcp/tools/project/projectCreate.ts deleted file mode 100644 index 484897d..0000000 --- a/src/mcp/tools/project/projectCreate.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { createTool } from "../toolFactory.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; - -export const projectCreate = createTool({ - name: "project-create", - description: "Creates a new project in Dokploy.", - schema: z.object({ - name: z.string().min(1).describe("The name of the project."), - description: z - .string() - .nullable() - .optional() - .describe("An optional description for the project."), - env: z - .string() - .optional() - .describe("Optional environment variables for the project."), - }), - annotations: { - title: "Create Project", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/project.create", input); - - return ResponseFormatter.success( - `Project "${input.name}" created successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/project/projectDuplicate.ts b/src/mcp/tools/project/projectDuplicate.ts deleted file mode 100644 index 336f16d..0000000 --- a/src/mcp/tools/project/projectDuplicate.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -const serviceSchema = z.object({ - id: z.string().describe("The ID of the service."), - type: z - .enum([ - "application", - "postgres", - "mariadb", - "mongo", - "mysql", - "redis", - "compose", - ]) - .describe("The type of the service."), -}); - -export const projectDuplicate = createTool({ - name: "project-duplicate", - description: - "Duplicates an existing environment in Dokploy with optional service selection. Creates a new environment with the same configuration and optionally the same services.", - schema: z.object({ - sourceEnvironmentId: z - .string() - .min(1) - .describe("The ID of the source environment to duplicate."), - name: z - .string() - .min(1) - .describe("The name for the new duplicated environment."), - description: z - .string() - .optional() - .describe("An optional description for the duplicated environment."), - includeServices: z - .boolean() - .default(true) - .describe( - "Whether to include services in the duplication. Defaults to true." - ), - selectedServices: z - .array(serviceSchema) - .optional() - .describe( - "Array of specific services to include. When includeServices is true and this is not provided, you MUST first retrieve all services from the source environment and include ALL of them in this array. Services are not automatically included - you must explicitly list each service with its ID and type." - ), - duplicateInSameProject: z - .boolean() - .default(false) - .describe( - "Whether to duplicate the environment within the same project. Defaults to false." - ), - }), - annotations: { - title: "Duplicate Environment", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/project.duplicate", input); - - return ResponseFormatter.success( - `Environment "${input.name}" duplicated successfully from source environment "${input.sourceEnvironmentId}"`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/project/projectOne.ts b/src/mcp/tools/project/projectOne.ts deleted file mode 100644 index 5636f4b..0000000 --- a/src/mcp/tools/project/projectOne.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { createTool } from "../toolFactory.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; - -export const projectOne = createTool({ - name: "project-one", - description: "Gets a specific project by its ID in Dokploy.", - schema: z.object({ - projectId: z.string().describe("The ID of the project to retrieve."), - }), - annotations: { - title: "Get Project Details", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const project = await apiClient.get( - `/project.one?projectId=${input.projectId}` - ); - - if (!project?.data) { - return ResponseFormatter.error( - "Failed to fetch project", - `Project with ID "${input.projectId}" not found` - ); - } - - return ResponseFormatter.success( - `Successfully fetched project "${input.projectId}"`, - project.data - ); - }, -}); diff --git a/src/mcp/tools/project/projectRemove.ts b/src/mcp/tools/project/projectRemove.ts deleted file mode 100644 index e554c20..0000000 --- a/src/mcp/tools/project/projectRemove.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { createTool } from "../toolFactory.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; - -export const projectRemove = createTool({ - name: "project-remove", - description: "Removes/deletes an existing project in Dokploy.", - schema: z.object({ - projectId: z.string().min(1).describe("The ID of the project to remove."), - }), - annotations: { - title: "Remove Project", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/project.remove", input); - - return ResponseFormatter.success( - `Project "${input.projectId}" removed successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/project/projectUpdate.ts b/src/mcp/tools/project/projectUpdate.ts deleted file mode 100644 index 723bcc5..0000000 --- a/src/mcp/tools/project/projectUpdate.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { createTool } from "../toolFactory.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; - -export const projectUpdate = createTool({ - name: "project-update", - description: - "Updates an existing project in Dokploy. Only provide the fields you want to update. System fields like createdAt and organizationId are typically not modified.", - schema: z.object({ - projectId: z.string().min(1).describe("The ID of the project to update."), - name: z.string().min(1).optional().describe("The new name of the project."), - description: z - .string() - .nullable() - .optional() - .describe("The new description for the project."), - createdAt: z - .string() - .optional() - .describe("The creation date of the project."), - organizationId: z - .string() - .optional() - .describe("The organization ID of the project."), - env: z - .string() - .optional() - .describe("Environment variables for the project."), - }), - annotations: { - title: "Update Project", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/project.update", input); - - return ResponseFormatter.success( - `Project "${input.projectId}" updated successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/toolFactory.ts b/src/mcp/tools/toolFactory.ts deleted file mode 100644 index ceec7f0..0000000 --- a/src/mcp/tools/toolFactory.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { z, ZodObject, ZodRawShape } from "zod"; -import apiClient from "../../utils/apiClient.js"; -import { createLogger } from "../../utils/logger.js"; -import { ResponseFormatter } from "../../utils/responseFormatter.js"; - -// Updated to match MCP SDK response format -export type ToolHandler = (input: T) => Promise<{ - content: { type: "text"; text: string }[]; - isError?: boolean; -}>; - -// Defines the structure for a tool. -// TShape is the ZodRawShape (the object passed to z.object()). -export interface ToolDefinition { - name: string; - description: string; - schema: ZodObject; // The schema must be a ZodObject - handler: ToolHandler>>; // Handler input is inferred from the ZodObject - annotations?: { - title?: string; - readOnlyHint?: boolean; - destructiveHint?: boolean; - idempotentHint?: boolean; - openWorldHint?: boolean; - }; -} - -interface ToolContext { - apiClient: typeof apiClient; - logger: ReturnType; -} - -const logger = createLogger("ToolFactory"); - -export function createToolContext(): ToolContext { - return { - apiClient, - logger, - }; -} - -export function createTool( - definition: ToolDefinition -): ToolDefinition { - return { - ...definition, - handler: async (input) => { - const context = createToolContext(); - - try { - // Validate input against schema - const validationResult = definition.schema.safeParse(input); - if (!validationResult.success) { - context.logger.warn( - `Input validation failed for tool: ${definition.name}`, - { - errors: validationResult.error.errors, - input, - } - ); - - const errorMessages = validationResult.error.errors - .map((err) => `${err.path.join(".")}: ${err.message}`) - .join(", "); - - return ResponseFormatter.error( - `Invalid input for tool: ${definition.name}`, - `Validation errors: ${errorMessages}` - ); - } - - context.logger.info(`Executing tool: ${definition.name}`, { - input: validationResult.data, - }); - const result = await definition.handler(validationResult.data); - context.logger.info(`Tool executed successfully: ${definition.name}`); - return result; - } catch (error) { - context.logger.error(`Tool execution failed: ${definition.name}`, { - error: error instanceof Error ? error.message : "Unknown error", - input, - }); - - // More specific error handling based on error types - if (error instanceof Error) { - if ( - error.message.includes("401") || - error.message.includes("Unauthorized") - ) { - return ResponseFormatter.error( - `Authentication failed for tool: ${definition.name}`, - "Please check your DOKPLOY_API_KEY configuration" - ); - } - - if ( - error.message.includes("404") || - error.message.includes("Not Found") - ) { - return ResponseFormatter.error( - `Resource not found`, - `The requested resource for ${definition.name} could not be found` - ); - } - - if ( - error.message.includes("500") || - error.message.includes("Internal Server Error") - ) { - return ResponseFormatter.error( - `Server error occurred`, - `Dokploy server encountered an internal error while processing ${definition.name}` - ); - } - } - - return ResponseFormatter.error( - `Failed to execute tool: ${definition.name}`, - `Error: ${error instanceof Error ? error.message : "Unknown error occurred"}` - ); - } - }, - }; -} diff --git a/src/server.ts b/src/server.ts index 1aa357b..3696598 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,5 +1,6 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { allTools } from "./mcp/tools/index.js"; +import * as api from "./mcp/tools/api.js"; +import * as apiSchema from "./mcp/tools/apiSchema.js"; export function createServer() { const server = new McpServer({ @@ -7,9 +8,21 @@ export function createServer() { version: "1.0.0", }); - for (const tool of allTools) { - server.tool(tool.name, tool.description, tool.schema.shape, tool.handler); - } + server.tool( + api.name, + api.description, + api.schema, + api.annotations, + api.handler + ); + + server.tool( + apiSchema.name, + apiSchema.description, + apiSchema.schema, + apiSchema.annotations, + apiSchema.handler + ); return server; } diff --git a/src/utils/openApiSpec.ts b/src/utils/openApiSpec.ts new file mode 100644 index 0000000..5118240 --- /dev/null +++ b/src/utils/openApiSpec.ts @@ -0,0 +1,75 @@ +import apiClient from "./apiClient.js"; +import { createLogger } from "./logger.js"; + +const logger = createLogger("OpenApiSpec"); + +let cachedSpec: Record | null = null; +let cachedGetOps: Set | null = null; +let cachedOpsList: string | null = null; + +/** + * Fetches and caches the OpenAPI spec from the Dokploy server. + */ +export async function getOpenApiSpec(): Promise> { + if (cachedSpec) return cachedSpec; + + logger.info("Fetching OpenAPI spec from Dokploy server"); + const response = await apiClient.get("/settings.getOpenApiDocument"); + cachedSpec = response.data; + return cachedSpec!; +} + +/** + * Derives the set of GET operations from the OpenAPI spec. + * Any path with a "get" method is a GET operation; everything else is POST. + */ +export async function getGetOperations(): Promise> { + if (cachedGetOps) return cachedGetOps; + + const spec = await getOpenApiSpec(); + const paths = (spec as any).paths || {}; + const getOps = new Set(); + + for (const [path, methods] of Object.entries(paths)) { + const operationName = path.replace(/^\//, ""); + const methodsObj = methods as Record; + if ("get" in methodsObj) { + getOps.add(operationName); + } + } + + cachedGetOps = getOps; + logger.info(`Derived ${getOps.size} GET operations from OpenAPI spec`); + return cachedGetOps; +} + +/** + * Builds the operations list string for the tool description. + * Groups operations by category (the part before the dot). + */ +export async function getOperationsList(): Promise { + if (cachedOpsList) return cachedOpsList; + + const spec = await getOpenApiSpec(); + const paths = (spec as any).paths || {}; + const categories: Record = {}; + + for (const path of Object.keys(paths)) { + const operationName = path.replace(/^\//, ""); + const dotIndex = operationName.indexOf("."); + if (dotIndex <= 0) continue; + const category = operationName.substring(0, dotIndex); + const action = operationName.substring(dotIndex + 1); + if (!categories[category]) categories[category] = []; + categories[category].push(action); + } + + const lines = Object.entries(categories) + .sort(([a], [b]) => a.localeCompare(b)) + .map(([cat, actions]) => `${cat}: ${actions.sort().join(", ")}`); + + cachedOpsList = + "Available operations (use dokploy-api-schema for parameter details):\n" + + lines.join("\n"); + return cachedOpsList; +} diff --git a/src/utils/responseFormatter.ts b/src/utils/responseFormatter.ts index a03dc93..c815fef 100644 --- a/src/utils/responseFormatter.ts +++ b/src/utils/responseFormatter.ts @@ -1,4 +1,5 @@ export interface FormattedResponse { + [key: string]: unknown; content: { type: "text"; text: string }[]; isError?: boolean; } diff --git a/tsconfig.json b/tsconfig.json index 13724a4..a0f8277 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,7 +11,7 @@ "forceConsistentCasingInFileNames": true, "noUnusedLocals": true, "noUnusedParameters": true, - "exactOptionalPropertyTypes": true, + "exactOptionalPropertyTypes": false, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "noUncheckedIndexedAccess": true