From 09589ccb68d8c8619133f39aefddfe45b89a5fd5 Mon Sep 17 00:00:00 2001 From: Limehawk <128890849+limehawk@users.noreply.github.com> Date: Tue, 27 Jan 2026 15:13:20 -0500 Subject: [PATCH 01/10] Add comprehensive tool coverage for Dokploy API This PR adds 250+ new MCP tools covering the complete Dokploy API, synced with the official OpenAPI specification. New tool categories: - admin: Server monitoring setup - ai: AI assistant management (create, deploy, suggest) - backup: Database and volume backup management - certificates: SSL certificate management - cluster: Swarm cluster node management - compose: Docker Compose project lifecycle - database: Unified database tools (postgres, mysql, mongo, mariadb, redis) - deployment: Deployment history and process management - destination: Backup destination configuration - docker: Container and config management - environment: Project environment management - git: Git provider and repository integration - mounts: File, bind, and volume mount management - notification: Multi-provider notification setup - organization: Organization and invitation management - port: Port mapping configuration - previewDeployment: Preview deployment management - redirects: URL redirect configuration - registry: Docker registry management - rollback: Deployment rollback functionality - schedule: Scheduled task management - security: Security rule configuration - server: Remote server management - settings: System settings and Traefik configuration - sshKey: SSH key management - stripe: Billing integration - swarm: Docker Swarm node information - user: User and permission management All tools include: - Zod schema validation matching OpenAPI spec - Detailed parameter descriptions for LLM usability - Proper error handling and response formatting --- .gitignore | 7 + src/mcp/tools/admin/adminSetupMonitoring.ts | 85 ++++ src/mcp/tools/admin/index.ts | 1 + src/mcp/tools/ai/aiCreate.ts | 37 ++ src/mcp/tools/ai/aiDelete.ts | 29 ++ src/mcp/tools/ai/aiDeploy.ts | 82 ++++ src/mcp/tools/ai/aiGet.ts | 33 ++ src/mcp/tools/ai/aiGetAll.ts | 24 + src/mcp/tools/ai/aiGetModels.ts | 31 ++ src/mcp/tools/ai/aiOne.ts | 33 ++ src/mcp/tools/ai/aiSuggest.ts | 41 ++ src/mcp/tools/ai/aiUpdate.ts | 59 +++ src/mcp/tools/ai/index.ts | 9 + src/mcp/tools/backup/backupCreate.ts | 99 ++++ src/mcp/tools/backup/backupListFiles.ts | 48 ++ src/mcp/tools/backup/backupOne.ts | 38 ++ src/mcp/tools/backup/backupRemove.ts | 29 ++ src/mcp/tools/backup/backupRunManual.ts | 64 +++ src/mcp/tools/backup/backupUpdate.ts | 67 +++ src/mcp/tools/backup/index.ts | 15 + src/mcp/tools/backup/volumeBackupCreate.ts | 115 +++++ src/mcp/tools/backup/volumeBackupList.ts | 50 ++ src/mcp/tools/backup/volumeBackupOne.ts | 39 ++ src/mcp/tools/backup/volumeBackupRemove.ts | 30 ++ src/mcp/tools/backup/volumeBackupRunManual.ts | 30 ++ src/mcp/tools/backup/volumeBackupUpdate.ts | 119 +++++ src/mcp/tools/certificates/certificatesAll.ts | 31 ++ .../tools/certificates/certificatesCreate.ts | 60 +++ src/mcp/tools/certificates/certificatesOne.ts | 38 ++ .../tools/certificates/certificatesRemove.ts | 29 ++ src/mcp/tools/certificates/index.ts | 4 + src/mcp/tools/cluster/clusterAddManager.ts | 38 ++ src/mcp/tools/cluster/clusterAddWorker.ts | 38 ++ src/mcp/tools/cluster/clusterGetNodes.ts | 37 ++ src/mcp/tools/cluster/clusterRemoveWorker.ts | 36 ++ src/mcp/tools/cluster/index.ts | 4 + .../tools/compose/composeCancelDeployment.ts | 30 ++ src/mcp/tools/compose/composeCleanQueues.ts | 29 ++ src/mcp/tools/compose/composeCreate.ts | 53 +++ src/mcp/tools/compose/composeDelete.ts | 32 ++ src/mcp/tools/compose/composeDeploy.ts | 34 ++ .../tools/compose/composeDeployTemplate.ts | 37 ++ .../compose/composeDisconnectGitProvider.ts | 34 ++ .../tools/compose/composeFetchSourceType.ts | 30 ++ .../compose/composeGetConvertedCompose.ts | 36 ++ .../tools/compose/composeGetDefaultCommand.ts | 35 ++ src/mcp/tools/compose/composeGetTags.ts | 46 ++ src/mcp/tools/compose/composeImport.ts | 29 ++ .../compose/composeIsolatedDeployment.ts | 33 ++ src/mcp/tools/compose/composeKillBuild.ts | 29 ++ .../compose/composeLoadMountsByService.ts | 44 ++ src/mcp/tools/compose/composeLoadServices.ts | 51 +++ src/mcp/tools/compose/composeMove.ts | 31 ++ src/mcp/tools/compose/composeOne.ts | 38 ++ .../tools/compose/composeProcessTemplate.ts | 27 ++ .../tools/compose/composeRandomizeCompose.ts | 34 ++ src/mcp/tools/compose/composeRedeploy.ts | 37 ++ src/mcp/tools/compose/composeRefreshToken.ts | 29 ++ src/mcp/tools/compose/composeStart.ts | 29 ++ src/mcp/tools/compose/composeStop.ts | 29 ++ src/mcp/tools/compose/composeTemplates.ts | 46 ++ src/mcp/tools/compose/composeUpdate.ts | 182 ++++++++ src/mcp/tools/compose/index.ts | 26 ++ .../tools/database/databaseChangeStatus.ts | 62 +++ src/mcp/tools/database/databaseCreate.ts | 146 ++++++ src/mcp/tools/database/databaseDeploy.ts | 56 +++ src/mcp/tools/database/databaseMove.ts | 61 +++ src/mcp/tools/database/databaseOne.ts | 63 +++ src/mcp/tools/database/databaseRebuild.ts | 56 +++ src/mcp/tools/database/databaseReload.ts | 61 +++ src/mcp/tools/database/databaseRemove.ts | 56 +++ .../tools/database/databaseSaveEnvironment.ts | 62 +++ .../database/databaseSaveExternalPort.ts | 66 +++ src/mcp/tools/database/databaseStart.ts | 56 +++ src/mcp/tools/database/databaseStop.ts | 56 +++ src/mcp/tools/database/databaseUpdate.ts | 391 ++++++++++++++++ src/mcp/tools/database/index.ts | 13 + src/mcp/tools/deployment/deploymentAll.ts | 31 ++ .../deployment/deploymentAllByCompose.ts | 31 ++ .../tools/deployment/deploymentAllByServer.ts | 31 ++ .../tools/deployment/deploymentAllByType.ts | 42 ++ .../tools/deployment/deploymentKillProcess.ts | 29 ++ src/mcp/tools/deployment/index.ts | 5 + src/mcp/tools/destination/destinationAll.ts | 24 + .../tools/destination/destinationCreate.ts | 57 +++ src/mcp/tools/destination/destinationOne.ts | 37 ++ .../tools/destination/destinationRemove.ts | 28 ++ .../destination/destinationTestConnection.ts | 56 +++ .../tools/destination/destinationUpdate.ts | 59 +++ src/mcp/tools/destination/index.ts | 6 + src/mcp/tools/docker/dockerGetConfig.ts | 42 ++ src/mcp/tools/docker/dockerGetContainers.ts | 37 ++ .../docker/dockerGetContainersByAppLabel.ts | 46 ++ .../dockerGetContainersByAppNameMatch.ts | 50 ++ .../dockerGetServiceContainersByAppName.ts | 42 ++ .../dockerGetStackContainersByAppName.ts | 42 ++ .../tools/docker/dockerRestartContainer.ts | 33 ++ src/mcp/tools/docker/index.ts | 7 + .../environment/environmentByProjectId.ts | 30 ++ .../tools/environment/environmentCreate.ts | 37 ++ .../tools/environment/environmentDuplicate.ts | 38 ++ src/mcp/tools/environment/environmentOne.ts | 38 ++ .../tools/environment/environmentRemove.ts | 29 ++ .../tools/environment/environmentUpdate.ts | 51 +++ src/mcp/tools/environment/index.ts | 6 + src/mcp/tools/git/gitBranches.ts | 94 ++++ src/mcp/tools/git/gitProviderCreate.ts | 263 +++++++++++ src/mcp/tools/git/gitProviderGetAll.ts | 32 ++ src/mcp/tools/git/gitProviderGetUrl.ts | 41 ++ src/mcp/tools/git/gitProviderOne.ts | 72 +++ src/mcp/tools/git/gitProviderRemove.ts | 34 ++ src/mcp/tools/git/gitProviderUpdate.ts | 321 +++++++++++++ src/mcp/tools/git/gitProviders.ts | 59 +++ src/mcp/tools/git/gitRepositories.ts | 72 +++ src/mcp/tools/git/gitTestConnection.ts | 138 ++++++ src/mcp/tools/git/index.ts | 10 + src/mcp/tools/index.ts | 58 ++- src/mcp/tools/mounts/index.ts | 5 + .../tools/mounts/mountsAllByApplicationId.ts | 80 ++++ src/mcp/tools/mounts/mountsCreate.ts | 106 +++++ src/mcp/tools/mounts/mountsOne.ts | 39 ++ src/mcp/tools/mounts/mountsRemove.ts | 30 ++ src/mcp/tools/mounts/mountsUpdate.ts | 102 +++++ src/mcp/tools/notification/index.ts | 8 + src/mcp/tools/notification/notificationAll.ts | 24 + .../tools/notification/notificationCreate.ts | 324 +++++++++++++ .../notificationGetEmailProviders.ts | 24 + src/mcp/tools/notification/notificationOne.ts | 38 ++ .../notificationReceiveNotification.ts | 44 ++ .../tools/notification/notificationRemove.ts | 28 ++ .../notificationTestConnection.ts | 254 ++++++++++ .../tools/notification/notificationUpdate.ts | 433 ++++++++++++++++++ src/mcp/tools/organization/index.ts | 8 + src/mcp/tools/organization/organizationAll.ts | 24 + .../organizationAllInvitations.ts | 24 + .../tools/organization/organizationCreate.ts | 30 ++ .../tools/organization/organizationDelete.ts | 28 ++ src/mcp/tools/organization/organizationOne.ts | 37 ++ .../organizationRemoveInvitation.ts | 29 ++ .../organization/organizationSetDefault.ts | 29 ++ .../tools/organization/organizationUpdate.ts | 37 ++ src/mcp/tools/port/index.ts | 4 + src/mcp/tools/port/portCreate.ts | 47 ++ src/mcp/tools/port/portDelete.ts | 29 ++ src/mcp/tools/port/portOne.ts | 36 ++ src/mcp/tools/port/portUpdate.ts | 44 ++ src/mcp/tools/previewDeployment/index.ts | 3 + .../previewDeployment/previewDeploymentAll.ts | 31 ++ .../previewDeploymentDelete.ts | 31 ++ .../previewDeployment/previewDeploymentOne.ts | 37 ++ src/mcp/tools/redirects/index.ts | 4 + src/mcp/tools/redirects/redirectsCreate.ts | 45 ++ src/mcp/tools/redirects/redirectsDelete.ts | 29 ++ src/mcp/tools/redirects/redirectsOne.ts | 38 ++ src/mcp/tools/redirects/redirectsUpdate.ts | 46 ++ src/mcp/tools/registry/index.ts | 6 + src/mcp/tools/registry/registryAll.ts | 31 ++ src/mcp/tools/registry/registryCreate.ts | 44 ++ src/mcp/tools/registry/registryOne.ts | 38 ++ src/mcp/tools/registry/registryRemove.ts | 26 ++ .../tools/registry/registryTestRegistry.ts | 45 ++ src/mcp/tools/registry/registryUpdate.ts | 64 +++ src/mcp/tools/rollback/index.ts | 2 + src/mcp/tools/rollback/rollbackDelete.ts | 29 ++ src/mcp/tools/rollback/rollbackRollback.ts | 32 ++ src/mcp/tools/schedule/index.ts | 6 + src/mcp/tools/schedule/scheduleCreate.ts | 71 +++ src/mcp/tools/schedule/scheduleDelete.ts | 26 ++ src/mcp/tools/schedule/scheduleList.ts | 35 ++ src/mcp/tools/schedule/scheduleOne.ts | 35 ++ src/mcp/tools/schedule/scheduleRunManually.ts | 29 ++ src/mcp/tools/schedule/scheduleUpdate.ts | 71 +++ src/mcp/tools/security/index.ts | 4 + src/mcp/tools/security/securityCreate.ts | 43 ++ src/mcp/tools/security/securityDelete.ts | 32 ++ src/mcp/tools/security/securityOne.ts | 41 ++ src/mcp/tools/security/securityUpdate.ts | 44 ++ src/mcp/tools/server/index.ts | 16 + src/mcp/tools/server/serverAll.ts | 24 + src/mcp/tools/server/serverBuildServers.ts | 24 + src/mcp/tools/server/serverCount.ts | 24 + src/mcp/tools/server/serverCreate.ts | 41 ++ .../tools/server/serverGetDefaultCommand.ts | 28 ++ .../tools/server/serverGetServerMetrics.ts | 30 ++ src/mcp/tools/server/serverGetServerTime.ts | 24 + src/mcp/tools/server/serverOne.ts | 35 ++ src/mcp/tools/server/serverPublicIp.ts | 24 + src/mcp/tools/server/serverRemove.ts | 26 ++ src/mcp/tools/server/serverSecurity.ts | 28 ++ src/mcp/tools/server/serverSetup.ts | 26 ++ src/mcp/tools/server/serverSetupMonitoring.ts | 57 +++ src/mcp/tools/server/serverUpdate.ts | 46 ++ src/mcp/tools/server/serverValidate.ts | 28 ++ src/mcp/tools/server/serverWithSSHKey.ts | 24 + src/mcp/tools/settings/index.ts | 46 ++ .../settings/settingsAssignDomainServer.ts | 50 ++ .../tools/settings/settingsCheckGPUStatus.ts | 30 ++ src/mcp/tools/settings/settingsCleanAll.ts | 32 ++ .../settings/settingsCleanDockerBuilder.ts | 34 ++ .../settings/settingsCleanDockerPrune.ts | 29 ++ .../tools/settings/settingsCleanMonitoring.ts | 24 + src/mcp/tools/settings/settingsCleanRedis.ts | 24 + .../settings/settingsCleanSSHPrivateKey.ts | 24 + .../settingsCleanStoppedContainers.ts | 32 ++ .../settings/settingsCleanUnusedImages.ts | 29 ++ .../settings/settingsCleanUnusedVolumes.ts | 32 ++ .../settings/settingsGetDokployCloudIps.ts | 24 + .../settings/settingsGetDokployVersion.ts | 24 + src/mcp/tools/settings/settingsGetIp.ts | 24 + .../settings/settingsGetLogCleanupStatus.ts | 24 + .../settings/settingsGetOpenApiDocument.ts | 24 + .../tools/settings/settingsGetReleaseTag.ts | 24 + .../tools/settings/settingsGetTraefikPorts.ts | 30 ++ .../tools/settings/settingsGetUpdateData.ts | 24 + .../settings/settingsHaveActivateRequests.ts | 24 + ...settingsHaveTraefikDashboardPortEnabled.ts | 32 ++ src/mcp/tools/settings/settingsHealth.ts | 24 + src/mcp/tools/settings/settingsIsCloud.ts | 24 + .../settings/settingsIsUserSubscribed.ts | 24 + .../tools/settings/settingsReadDirectories.ts | 32 ++ .../settingsReadMiddlewareTraefikConfig.ts | 26 ++ .../settings/settingsReadTraefikConfig.ts | 24 + .../tools/settings/settingsReadTraefikEnv.ts | 30 ++ .../tools/settings/settingsReadTraefikFile.ts | 43 ++ .../settingsReadWebServerTraefikConfig.ts | 26 ++ src/mcp/tools/settings/settingsReloadRedis.ts | 24 + .../tools/settings/settingsReloadServer.ts | 24 + .../tools/settings/settingsReloadTraefik.ts | 29 ++ .../settings/settingsSaveSSHPrivateKey.ts | 31 ++ src/mcp/tools/settings/settingsSetupGPU.ts | 29 ++ .../tools/settings/settingsToggleDashboard.ts | 33 ++ .../tools/settings/settingsToggleRequests.ts | 28 ++ .../settings/settingsUpdateDockerCleanup.ts | 37 ++ .../settings/settingsUpdateLogCleanup.ts | 31 ++ .../settingsUpdateMiddlewareTraefikConfig.ts | 34 ++ .../tools/settings/settingsUpdateServer.ts | 24 + .../settings/settingsUpdateTraefikConfig.ts | 34 ++ .../settings/settingsUpdateTraefikFile.ts | 43 ++ .../settings/settingsUpdateTraefikPorts.ts | 47 ++ .../settingsUpdateWebServerTraefikConfig.ts | 34 ++ .../tools/settings/settingsWriteTraefikEnv.ts | 36 ++ src/mcp/tools/sshKey/index.ts | 6 + src/mcp/tools/sshKey/sshKeyAll.ts | 24 + src/mcp/tools/sshKey/sshKeyCreate.ts | 34 ++ src/mcp/tools/sshKey/sshKeyGenerate.ts | 29 ++ src/mcp/tools/sshKey/sshKeyOne.ts | 35 ++ src/mcp/tools/sshKey/sshKeyRemove.ts | 26 ++ src/mcp/tools/sshKey/sshKeyUpdate.ts | 37 ++ src/mcp/tools/stripe/index.ts | 4 + .../stripe/stripeCanCreateMoreServers.ts | 25 + .../stripe/stripeCreateCheckoutSession.ts | 36 ++ .../stripeCreateCustomerPortalSession.ts | 28 ++ src/mcp/tools/stripe/stripeGetProducts.ts | 24 + src/mcp/tools/swarm/index.ts | 3 + src/mcp/tools/swarm/swarmGetNodeApps.ts | 37 ++ src/mcp/tools/swarm/swarmGetNodeInfo.ts | 39 ++ src/mcp/tools/swarm/swarmGetNodes.ts | 37 ++ src/mcp/tools/user/index.ts | 18 + src/mcp/tools/user/userAll.ts | 24 + src/mcp/tools/user/userAssignPermissions.ts | 88 ++++ src/mcp/tools/user/userCheckOrganizations.ts | 30 ++ src/mcp/tools/user/userCreateApiKey.ts | 65 +++ src/mcp/tools/user/userDeleteApiKey.ts | 26 ++ src/mcp/tools/user/userGenerateToken.ts | 24 + src/mcp/tools/user/userGet.ts | 24 + src/mcp/tools/user/userGetBackups.ts | 24 + src/mcp/tools/user/userGetByToken.ts | 35 ++ src/mcp/tools/user/userGetContainerMetrics.ts | 37 ++ src/mcp/tools/user/userGetInvitations.ts | 24 + src/mcp/tools/user/userGetMetricsToken.ts | 24 + src/mcp/tools/user/userGetServerMetrics.ts | 24 + src/mcp/tools/user/userHaveRootAccess.ts | 24 + src/mcp/tools/user/userOne.ts | 33 ++ src/mcp/tools/user/userRemove.ts | 26 ++ src/mcp/tools/user/userSendInvitation.ts | 30 ++ src/mcp/tools/user/userUpdate.ts | 181 ++++++++ tsconfig.json | 2 +- 277 files changed, 12046 insertions(+), 2 deletions(-) create mode 100644 src/mcp/tools/admin/adminSetupMonitoring.ts create mode 100644 src/mcp/tools/admin/index.ts create mode 100644 src/mcp/tools/ai/aiCreate.ts create mode 100644 src/mcp/tools/ai/aiDelete.ts create mode 100644 src/mcp/tools/ai/aiDeploy.ts create mode 100644 src/mcp/tools/ai/aiGet.ts create mode 100644 src/mcp/tools/ai/aiGetAll.ts create mode 100644 src/mcp/tools/ai/aiGetModels.ts create mode 100644 src/mcp/tools/ai/aiOne.ts create mode 100644 src/mcp/tools/ai/aiSuggest.ts create mode 100644 src/mcp/tools/ai/aiUpdate.ts create mode 100644 src/mcp/tools/ai/index.ts create mode 100644 src/mcp/tools/backup/backupCreate.ts create mode 100644 src/mcp/tools/backup/backupListFiles.ts create mode 100644 src/mcp/tools/backup/backupOne.ts create mode 100644 src/mcp/tools/backup/backupRemove.ts create mode 100644 src/mcp/tools/backup/backupRunManual.ts create mode 100644 src/mcp/tools/backup/backupUpdate.ts create mode 100644 src/mcp/tools/backup/index.ts create mode 100644 src/mcp/tools/backup/volumeBackupCreate.ts create mode 100644 src/mcp/tools/backup/volumeBackupList.ts create mode 100644 src/mcp/tools/backup/volumeBackupOne.ts create mode 100644 src/mcp/tools/backup/volumeBackupRemove.ts create mode 100644 src/mcp/tools/backup/volumeBackupRunManual.ts create mode 100644 src/mcp/tools/backup/volumeBackupUpdate.ts create mode 100644 src/mcp/tools/certificates/certificatesAll.ts create mode 100644 src/mcp/tools/certificates/certificatesCreate.ts create mode 100644 src/mcp/tools/certificates/certificatesOne.ts create mode 100644 src/mcp/tools/certificates/certificatesRemove.ts create mode 100644 src/mcp/tools/certificates/index.ts create mode 100644 src/mcp/tools/cluster/clusterAddManager.ts create mode 100644 src/mcp/tools/cluster/clusterAddWorker.ts create mode 100644 src/mcp/tools/cluster/clusterGetNodes.ts create mode 100644 src/mcp/tools/cluster/clusterRemoveWorker.ts create mode 100644 src/mcp/tools/cluster/index.ts create mode 100644 src/mcp/tools/compose/composeCancelDeployment.ts create mode 100644 src/mcp/tools/compose/composeCleanQueues.ts create mode 100644 src/mcp/tools/compose/composeCreate.ts create mode 100644 src/mcp/tools/compose/composeDelete.ts create mode 100644 src/mcp/tools/compose/composeDeploy.ts create mode 100644 src/mcp/tools/compose/composeDeployTemplate.ts create mode 100644 src/mcp/tools/compose/composeDisconnectGitProvider.ts create mode 100644 src/mcp/tools/compose/composeFetchSourceType.ts create mode 100644 src/mcp/tools/compose/composeGetConvertedCompose.ts create mode 100644 src/mcp/tools/compose/composeGetDefaultCommand.ts create mode 100644 src/mcp/tools/compose/composeGetTags.ts create mode 100644 src/mcp/tools/compose/composeImport.ts create mode 100644 src/mcp/tools/compose/composeIsolatedDeployment.ts create mode 100644 src/mcp/tools/compose/composeKillBuild.ts create mode 100644 src/mcp/tools/compose/composeLoadMountsByService.ts create mode 100644 src/mcp/tools/compose/composeLoadServices.ts create mode 100644 src/mcp/tools/compose/composeMove.ts create mode 100644 src/mcp/tools/compose/composeOne.ts create mode 100644 src/mcp/tools/compose/composeProcessTemplate.ts create mode 100644 src/mcp/tools/compose/composeRandomizeCompose.ts create mode 100644 src/mcp/tools/compose/composeRedeploy.ts create mode 100644 src/mcp/tools/compose/composeRefreshToken.ts create mode 100644 src/mcp/tools/compose/composeStart.ts create mode 100644 src/mcp/tools/compose/composeStop.ts create mode 100644 src/mcp/tools/compose/composeTemplates.ts create mode 100644 src/mcp/tools/compose/composeUpdate.ts create mode 100644 src/mcp/tools/compose/index.ts create mode 100644 src/mcp/tools/database/databaseChangeStatus.ts create mode 100644 src/mcp/tools/database/databaseCreate.ts create mode 100644 src/mcp/tools/database/databaseDeploy.ts create mode 100644 src/mcp/tools/database/databaseMove.ts create mode 100644 src/mcp/tools/database/databaseOne.ts create mode 100644 src/mcp/tools/database/databaseRebuild.ts create mode 100644 src/mcp/tools/database/databaseReload.ts create mode 100644 src/mcp/tools/database/databaseRemove.ts create mode 100644 src/mcp/tools/database/databaseSaveEnvironment.ts create mode 100644 src/mcp/tools/database/databaseSaveExternalPort.ts create mode 100644 src/mcp/tools/database/databaseStart.ts create mode 100644 src/mcp/tools/database/databaseStop.ts create mode 100644 src/mcp/tools/database/databaseUpdate.ts create mode 100644 src/mcp/tools/database/index.ts create mode 100644 src/mcp/tools/deployment/deploymentAll.ts create mode 100644 src/mcp/tools/deployment/deploymentAllByCompose.ts create mode 100644 src/mcp/tools/deployment/deploymentAllByServer.ts create mode 100644 src/mcp/tools/deployment/deploymentAllByType.ts create mode 100644 src/mcp/tools/deployment/deploymentKillProcess.ts create mode 100644 src/mcp/tools/deployment/index.ts create mode 100644 src/mcp/tools/destination/destinationAll.ts create mode 100644 src/mcp/tools/destination/destinationCreate.ts create mode 100644 src/mcp/tools/destination/destinationOne.ts create mode 100644 src/mcp/tools/destination/destinationRemove.ts create mode 100644 src/mcp/tools/destination/destinationTestConnection.ts create mode 100644 src/mcp/tools/destination/destinationUpdate.ts create mode 100644 src/mcp/tools/destination/index.ts create mode 100644 src/mcp/tools/docker/dockerGetConfig.ts create mode 100644 src/mcp/tools/docker/dockerGetContainers.ts create mode 100644 src/mcp/tools/docker/dockerGetContainersByAppLabel.ts create mode 100644 src/mcp/tools/docker/dockerGetContainersByAppNameMatch.ts create mode 100644 src/mcp/tools/docker/dockerGetServiceContainersByAppName.ts create mode 100644 src/mcp/tools/docker/dockerGetStackContainersByAppName.ts create mode 100644 src/mcp/tools/docker/dockerRestartContainer.ts create mode 100644 src/mcp/tools/docker/index.ts create mode 100644 src/mcp/tools/environment/environmentByProjectId.ts create mode 100644 src/mcp/tools/environment/environmentCreate.ts create mode 100644 src/mcp/tools/environment/environmentDuplicate.ts create mode 100644 src/mcp/tools/environment/environmentOne.ts create mode 100644 src/mcp/tools/environment/environmentRemove.ts create mode 100644 src/mcp/tools/environment/environmentUpdate.ts create mode 100644 src/mcp/tools/environment/index.ts create mode 100644 src/mcp/tools/git/gitBranches.ts create mode 100644 src/mcp/tools/git/gitProviderCreate.ts create mode 100644 src/mcp/tools/git/gitProviderGetAll.ts create mode 100644 src/mcp/tools/git/gitProviderGetUrl.ts create mode 100644 src/mcp/tools/git/gitProviderOne.ts create mode 100644 src/mcp/tools/git/gitProviderRemove.ts create mode 100644 src/mcp/tools/git/gitProviderUpdate.ts create mode 100644 src/mcp/tools/git/gitProviders.ts create mode 100644 src/mcp/tools/git/gitRepositories.ts create mode 100644 src/mcp/tools/git/gitTestConnection.ts create mode 100644 src/mcp/tools/git/index.ts create mode 100644 src/mcp/tools/mounts/index.ts create mode 100644 src/mcp/tools/mounts/mountsAllByApplicationId.ts create mode 100644 src/mcp/tools/mounts/mountsCreate.ts create mode 100644 src/mcp/tools/mounts/mountsOne.ts create mode 100644 src/mcp/tools/mounts/mountsRemove.ts create mode 100644 src/mcp/tools/mounts/mountsUpdate.ts create mode 100644 src/mcp/tools/notification/index.ts create mode 100644 src/mcp/tools/notification/notificationAll.ts create mode 100644 src/mcp/tools/notification/notificationCreate.ts create mode 100644 src/mcp/tools/notification/notificationGetEmailProviders.ts create mode 100644 src/mcp/tools/notification/notificationOne.ts create mode 100644 src/mcp/tools/notification/notificationReceiveNotification.ts create mode 100644 src/mcp/tools/notification/notificationRemove.ts create mode 100644 src/mcp/tools/notification/notificationTestConnection.ts create mode 100644 src/mcp/tools/notification/notificationUpdate.ts create mode 100644 src/mcp/tools/organization/index.ts create mode 100644 src/mcp/tools/organization/organizationAll.ts create mode 100644 src/mcp/tools/organization/organizationAllInvitations.ts create mode 100644 src/mcp/tools/organization/organizationCreate.ts create mode 100644 src/mcp/tools/organization/organizationDelete.ts create mode 100644 src/mcp/tools/organization/organizationOne.ts create mode 100644 src/mcp/tools/organization/organizationRemoveInvitation.ts create mode 100644 src/mcp/tools/organization/organizationSetDefault.ts create mode 100644 src/mcp/tools/organization/organizationUpdate.ts create mode 100644 src/mcp/tools/port/index.ts create mode 100644 src/mcp/tools/port/portCreate.ts create mode 100644 src/mcp/tools/port/portDelete.ts create mode 100644 src/mcp/tools/port/portOne.ts create mode 100644 src/mcp/tools/port/portUpdate.ts create mode 100644 src/mcp/tools/previewDeployment/index.ts create mode 100644 src/mcp/tools/previewDeployment/previewDeploymentAll.ts create mode 100644 src/mcp/tools/previewDeployment/previewDeploymentDelete.ts create mode 100644 src/mcp/tools/previewDeployment/previewDeploymentOne.ts create mode 100644 src/mcp/tools/redirects/index.ts create mode 100644 src/mcp/tools/redirects/redirectsCreate.ts create mode 100644 src/mcp/tools/redirects/redirectsDelete.ts create mode 100644 src/mcp/tools/redirects/redirectsOne.ts create mode 100644 src/mcp/tools/redirects/redirectsUpdate.ts create mode 100644 src/mcp/tools/registry/index.ts create mode 100644 src/mcp/tools/registry/registryAll.ts create mode 100644 src/mcp/tools/registry/registryCreate.ts create mode 100644 src/mcp/tools/registry/registryOne.ts create mode 100644 src/mcp/tools/registry/registryRemove.ts create mode 100644 src/mcp/tools/registry/registryTestRegistry.ts create mode 100644 src/mcp/tools/registry/registryUpdate.ts create mode 100644 src/mcp/tools/rollback/index.ts create mode 100644 src/mcp/tools/rollback/rollbackDelete.ts create mode 100644 src/mcp/tools/rollback/rollbackRollback.ts create mode 100644 src/mcp/tools/schedule/index.ts create mode 100644 src/mcp/tools/schedule/scheduleCreate.ts create mode 100644 src/mcp/tools/schedule/scheduleDelete.ts create mode 100644 src/mcp/tools/schedule/scheduleList.ts create mode 100644 src/mcp/tools/schedule/scheduleOne.ts create mode 100644 src/mcp/tools/schedule/scheduleRunManually.ts create mode 100644 src/mcp/tools/schedule/scheduleUpdate.ts create mode 100644 src/mcp/tools/security/index.ts create mode 100644 src/mcp/tools/security/securityCreate.ts create mode 100644 src/mcp/tools/security/securityDelete.ts create mode 100644 src/mcp/tools/security/securityOne.ts create mode 100644 src/mcp/tools/security/securityUpdate.ts create mode 100644 src/mcp/tools/server/index.ts create mode 100644 src/mcp/tools/server/serverAll.ts create mode 100644 src/mcp/tools/server/serverBuildServers.ts create mode 100644 src/mcp/tools/server/serverCount.ts create mode 100644 src/mcp/tools/server/serverCreate.ts create mode 100644 src/mcp/tools/server/serverGetDefaultCommand.ts create mode 100644 src/mcp/tools/server/serverGetServerMetrics.ts create mode 100644 src/mcp/tools/server/serverGetServerTime.ts create mode 100644 src/mcp/tools/server/serverOne.ts create mode 100644 src/mcp/tools/server/serverPublicIp.ts create mode 100644 src/mcp/tools/server/serverRemove.ts create mode 100644 src/mcp/tools/server/serverSecurity.ts create mode 100644 src/mcp/tools/server/serverSetup.ts create mode 100644 src/mcp/tools/server/serverSetupMonitoring.ts create mode 100644 src/mcp/tools/server/serverUpdate.ts create mode 100644 src/mcp/tools/server/serverValidate.ts create mode 100644 src/mcp/tools/server/serverWithSSHKey.ts create mode 100644 src/mcp/tools/settings/index.ts create mode 100644 src/mcp/tools/settings/settingsAssignDomainServer.ts create mode 100644 src/mcp/tools/settings/settingsCheckGPUStatus.ts create mode 100644 src/mcp/tools/settings/settingsCleanAll.ts create mode 100644 src/mcp/tools/settings/settingsCleanDockerBuilder.ts create mode 100644 src/mcp/tools/settings/settingsCleanDockerPrune.ts create mode 100644 src/mcp/tools/settings/settingsCleanMonitoring.ts create mode 100644 src/mcp/tools/settings/settingsCleanRedis.ts create mode 100644 src/mcp/tools/settings/settingsCleanSSHPrivateKey.ts create mode 100644 src/mcp/tools/settings/settingsCleanStoppedContainers.ts create mode 100644 src/mcp/tools/settings/settingsCleanUnusedImages.ts create mode 100644 src/mcp/tools/settings/settingsCleanUnusedVolumes.ts create mode 100644 src/mcp/tools/settings/settingsGetDokployCloudIps.ts create mode 100644 src/mcp/tools/settings/settingsGetDokployVersion.ts create mode 100644 src/mcp/tools/settings/settingsGetIp.ts create mode 100644 src/mcp/tools/settings/settingsGetLogCleanupStatus.ts create mode 100644 src/mcp/tools/settings/settingsGetOpenApiDocument.ts create mode 100644 src/mcp/tools/settings/settingsGetReleaseTag.ts create mode 100644 src/mcp/tools/settings/settingsGetTraefikPorts.ts create mode 100644 src/mcp/tools/settings/settingsGetUpdateData.ts create mode 100644 src/mcp/tools/settings/settingsHaveActivateRequests.ts create mode 100644 src/mcp/tools/settings/settingsHaveTraefikDashboardPortEnabled.ts create mode 100644 src/mcp/tools/settings/settingsHealth.ts create mode 100644 src/mcp/tools/settings/settingsIsCloud.ts create mode 100644 src/mcp/tools/settings/settingsIsUserSubscribed.ts create mode 100644 src/mcp/tools/settings/settingsReadDirectories.ts create mode 100644 src/mcp/tools/settings/settingsReadMiddlewareTraefikConfig.ts create mode 100644 src/mcp/tools/settings/settingsReadTraefikConfig.ts create mode 100644 src/mcp/tools/settings/settingsReadTraefikEnv.ts create mode 100644 src/mcp/tools/settings/settingsReadTraefikFile.ts create mode 100644 src/mcp/tools/settings/settingsReadWebServerTraefikConfig.ts create mode 100644 src/mcp/tools/settings/settingsReloadRedis.ts create mode 100644 src/mcp/tools/settings/settingsReloadServer.ts create mode 100644 src/mcp/tools/settings/settingsReloadTraefik.ts create mode 100644 src/mcp/tools/settings/settingsSaveSSHPrivateKey.ts create mode 100644 src/mcp/tools/settings/settingsSetupGPU.ts create mode 100644 src/mcp/tools/settings/settingsToggleDashboard.ts create mode 100644 src/mcp/tools/settings/settingsToggleRequests.ts create mode 100644 src/mcp/tools/settings/settingsUpdateDockerCleanup.ts create mode 100644 src/mcp/tools/settings/settingsUpdateLogCleanup.ts create mode 100644 src/mcp/tools/settings/settingsUpdateMiddlewareTraefikConfig.ts create mode 100644 src/mcp/tools/settings/settingsUpdateServer.ts create mode 100644 src/mcp/tools/settings/settingsUpdateTraefikConfig.ts create mode 100644 src/mcp/tools/settings/settingsUpdateTraefikFile.ts create mode 100644 src/mcp/tools/settings/settingsUpdateTraefikPorts.ts create mode 100644 src/mcp/tools/settings/settingsUpdateWebServerTraefikConfig.ts create mode 100644 src/mcp/tools/settings/settingsWriteTraefikEnv.ts create mode 100644 src/mcp/tools/sshKey/index.ts create mode 100644 src/mcp/tools/sshKey/sshKeyAll.ts create mode 100644 src/mcp/tools/sshKey/sshKeyCreate.ts create mode 100644 src/mcp/tools/sshKey/sshKeyGenerate.ts create mode 100644 src/mcp/tools/sshKey/sshKeyOne.ts create mode 100644 src/mcp/tools/sshKey/sshKeyRemove.ts create mode 100644 src/mcp/tools/sshKey/sshKeyUpdate.ts create mode 100644 src/mcp/tools/stripe/index.ts create mode 100644 src/mcp/tools/stripe/stripeCanCreateMoreServers.ts create mode 100644 src/mcp/tools/stripe/stripeCreateCheckoutSession.ts create mode 100644 src/mcp/tools/stripe/stripeCreateCustomerPortalSession.ts create mode 100644 src/mcp/tools/stripe/stripeGetProducts.ts create mode 100644 src/mcp/tools/swarm/index.ts create mode 100644 src/mcp/tools/swarm/swarmGetNodeApps.ts create mode 100644 src/mcp/tools/swarm/swarmGetNodeInfo.ts create mode 100644 src/mcp/tools/swarm/swarmGetNodes.ts create mode 100644 src/mcp/tools/user/index.ts create mode 100644 src/mcp/tools/user/userAll.ts create mode 100644 src/mcp/tools/user/userAssignPermissions.ts create mode 100644 src/mcp/tools/user/userCheckOrganizations.ts create mode 100644 src/mcp/tools/user/userCreateApiKey.ts create mode 100644 src/mcp/tools/user/userDeleteApiKey.ts create mode 100644 src/mcp/tools/user/userGenerateToken.ts create mode 100644 src/mcp/tools/user/userGet.ts create mode 100644 src/mcp/tools/user/userGetBackups.ts create mode 100644 src/mcp/tools/user/userGetByToken.ts create mode 100644 src/mcp/tools/user/userGetContainerMetrics.ts create mode 100644 src/mcp/tools/user/userGetInvitations.ts create mode 100644 src/mcp/tools/user/userGetMetricsToken.ts create mode 100644 src/mcp/tools/user/userGetServerMetrics.ts create mode 100644 src/mcp/tools/user/userHaveRootAccess.ts create mode 100644 src/mcp/tools/user/userOne.ts create mode 100644 src/mcp/tools/user/userRemove.ts create mode 100644 src/mcp/tools/user/userSendInvitation.ts create mode 100644 src/mcp/tools/user/userUpdate.ts diff --git a/.gitignore b/.gitignore index af474db..aa938ad 100644 --- a/.gitignore +++ b/.gitignore @@ -131,3 +131,10 @@ junit.xml # Misc *.tgz *.tar.gz + +# Local MCP configuration (contains credentials) +.mcp.json +mcp.json + +# Bun lockfile +bun.lock diff --git a/src/mcp/tools/admin/adminSetupMonitoring.ts b/src/mcp/tools/admin/adminSetupMonitoring.ts new file mode 100644 index 0000000..1e51e2f --- /dev/null +++ b/src/mcp/tools/admin/adminSetupMonitoring.ts @@ -0,0 +1,85 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const adminSetupMonitoring = createTool({ + name: "admin-setup-monitoring", + description: "Sets up monitoring configuration for the Dokploy instance.", + schema: z.object({ + metricsConfig: z + .object({ + server: z + .object({ + refreshRate: z + .number() + .min(2) + .describe("Server metrics refresh rate in seconds. Minimum: 2."), + port: z.number().min(1).describe("Port number for metrics server."), + token: z.string().describe("Authentication token for metrics."), + urlCallback: z + .string() + .url() + .describe("Callback URL for metrics notifications."), + retentionDays: z + .number() + .min(1) + .describe("Number of days to retain metrics data. Minimum: 1."), + cronJob: z + .string() + .min(1) + .describe("Cron expression for metrics cleanup schedule."), + thresholds: z + .object({ + cpu: z + .number() + .min(0) + .describe("CPU usage threshold percentage for alerts."), + memory: z + .number() + .min(0) + .describe("Memory usage threshold percentage for alerts."), + }) + .describe("Alert thresholds for server resources."), + }) + .describe("Server monitoring configuration."), + containers: z + .object({ + refreshRate: z + .number() + .min(2) + .describe( + "Container metrics refresh rate in seconds. Minimum: 2.", + ), + services: z + .object({ + include: z + .array(z.string()) + .optional() + .describe("List of service names to include in monitoring."), + exclude: z + .array(z.string()) + .optional() + .describe("List of service names to exclude from monitoring."), + }) + .describe("Services filter configuration."), + }) + .describe("Container monitoring configuration."), + }) + .describe("Metrics configuration for server and container monitoring."), + }), + annotations: { + title: "Setup Monitoring", + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/admin.setupMonitoring", input); + + return ResponseFormatter.success( + "Monitoring setup configured successfully", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/admin/index.ts b/src/mcp/tools/admin/index.ts new file mode 100644 index 0000000..f34e839 --- /dev/null +++ b/src/mcp/tools/admin/index.ts @@ -0,0 +1 @@ +export { adminSetupMonitoring } from "./adminSetupMonitoring.js"; diff --git a/src/mcp/tools/ai/aiCreate.ts b/src/mcp/tools/ai/aiCreate.ts new file mode 100644 index 0000000..d481c44 --- /dev/null +++ b/src/mcp/tools/ai/aiCreate.ts @@ -0,0 +1,37 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const aiCreate = createTool({ + name: "ai-create", + description: "Creates a new AI configuration in Dokploy.", + schema: z.object({ + name: z.string().min(1).describe("The name of the AI configuration."), + apiUrl: z.string().url().describe("The API URL for the AI service."), + apiKey: z.string().describe("The API key for authentication."), + model: z.string().min(1).describe("The model to use."), + isEnabled: z.boolean().describe("Whether the AI configuration is enabled."), + }), + annotations: { + title: "Create AI Configuration", + readOnlyHint: false, + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/ai.create", { + name: input.name, + apiUrl: input.apiUrl, + apiKey: input.apiKey, + model: input.model, + isEnabled: input.isEnabled, + }); + + return ResponseFormatter.success( + `Successfully created AI configuration "${input.name}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/ai/aiDelete.ts b/src/mcp/tools/ai/aiDelete.ts new file mode 100644 index 0000000..9bb4b94 --- /dev/null +++ b/src/mcp/tools/ai/aiDelete.ts @@ -0,0 +1,29 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const aiDelete = createTool({ + name: "ai-delete", + description: "Deletes an AI configuration in Dokploy.", + schema: z.object({ + aiId: z.string().describe("The ID of the AI configuration to delete."), + }), + annotations: { + title: "Delete AI Configuration", + readOnlyHint: false, + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/ai.delete", { + aiId: input.aiId, + }); + + return ResponseFormatter.success( + `Successfully deleted AI configuration "${input.aiId}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/ai/aiDeploy.ts b/src/mcp/tools/ai/aiDeploy.ts new file mode 100644 index 0000000..a30135c --- /dev/null +++ b/src/mcp/tools/ai/aiDeploy.ts @@ -0,0 +1,82 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +const domainSchema = z.object({ + host: z.string().min(1).describe("The host domain for the service."), + port: z.number().min(1).describe("The port number for the service."), + serviceName: z.string().min(1).describe("The name of the service."), +}); + +const configFileSchema = z.object({ + filePath: z.string().min(1).describe("The path where the config file will be stored."), + content: z.string().min(1).describe("The content of the config file."), +}); + +export const aiDeploy = createTool({ + name: "ai-deploy", + description: "Deploys an AI-generated application configuration in Dokploy.", + schema: z.object({ + environmentId: z + .string() + .min(1) + .describe("The ID of the environment to deploy to. Required."), + id: z.string().min(1).describe("The unique ID for the deployment. Required."), + dockerCompose: z + .string() + .min(1) + .describe("The Docker Compose configuration content. Required."), + envVariables: z + .string() + .describe("Environment variables for the deployment in KEY=value format. Required."), + serverId: z + .string() + .optional() + .describe("The ID of the server to deploy to. Optional."), + name: z.string().min(1).describe("The name of the deployment. Required."), + description: z.string().describe("A description of the deployment. Required."), + domains: z + .array(domainSchema) + .optional() + .describe("Domain configurations for the deployment. Each domain requires host, port, and serviceName."), + configFiles: z + .array(configFileSchema) + .optional() + .describe("Configuration files for the deployment. Each file requires filePath and content."), + }), + annotations: { + title: "Deploy AI Configuration", + readOnlyHint: false, + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const body: Record = { + environmentId: input.environmentId, + id: input.id, + dockerCompose: input.dockerCompose, + envVariables: input.envVariables, + name: input.name, + description: input.description, + }; + + if (input.serverId) { + body.serverId = input.serverId; + } + if (input.domains) { + body.domains = input.domains; + } + if (input.configFiles) { + body.configFiles = input.configFiles; + } + + const response = await apiClient.post("/ai.deploy", body); + + return ResponseFormatter.success( + `Successfully deployed AI configuration "${input.name}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/ai/aiGet.ts b/src/mcp/tools/ai/aiGet.ts new file mode 100644 index 0000000..a5cfb24 --- /dev/null +++ b/src/mcp/tools/ai/aiGet.ts @@ -0,0 +1,33 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const aiGet = createTool({ + name: "ai-get", + description: "Gets a specific AI configuration by its ID in Dokploy.", + schema: z.object({ + aiId: z.string().describe("The ID of the AI configuration to retrieve."), + }), + annotations: { + title: "Get AI Configuration by ID", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.get(`/ai.get?aiId=${input.aiId}`); + + if (!response?.data) { + return ResponseFormatter.error( + "Failed to fetch AI configuration", + `AI configuration with ID "${input.aiId}" not found`, + ); + } + + return ResponseFormatter.success( + `Successfully fetched AI configuration "${input.aiId}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/ai/aiGetAll.ts b/src/mcp/tools/ai/aiGetAll.ts new file mode 100644 index 0000000..398178b --- /dev/null +++ b/src/mcp/tools/ai/aiGetAll.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const aiGetAll = createTool({ + name: "ai-get-all", + description: "Gets all AI configurations in Dokploy.", + schema: z.object({}), + annotations: { + title: "Get All AI Configurations", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const response = await apiClient.get("/ai.getAll"); + + return ResponseFormatter.success( + "Successfully fetched all AI configurations", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/ai/aiGetModels.ts b/src/mcp/tools/ai/aiGetModels.ts new file mode 100644 index 0000000..0bbb647 --- /dev/null +++ b/src/mcp/tools/ai/aiGetModels.ts @@ -0,0 +1,31 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const aiGetModels = createTool({ + name: "ai-get-models", + description: "Gets available AI models from an API endpoint in Dokploy.", + schema: z.object({ + apiUrl: z.string().min(1).describe("The API URL to fetch models from."), + apiKey: z.string().describe("The API key for authentication."), + }), + annotations: { + title: "Get AI Models", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const params = new URLSearchParams(); + params.append("apiUrl", input.apiUrl); + params.append("apiKey", input.apiKey); + + const response = await apiClient.get(`/ai.getModels?${params.toString()}`); + + return ResponseFormatter.success( + "Successfully fetched AI models", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/ai/aiOne.ts b/src/mcp/tools/ai/aiOne.ts new file mode 100644 index 0000000..4cc27b9 --- /dev/null +++ b/src/mcp/tools/ai/aiOne.ts @@ -0,0 +1,33 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const aiOne = createTool({ + name: "ai-one", + description: "Gets a specific AI configuration by its ID in Dokploy.", + schema: z.object({ + aiId: z.string().describe("The ID of the AI configuration to retrieve."), + }), + annotations: { + title: "Get AI Configuration", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.get(`/ai.one?aiId=${input.aiId}`); + + if (!response?.data) { + return ResponseFormatter.error( + "Failed to fetch AI configuration", + `AI configuration with ID "${input.aiId}" not found`, + ); + } + + return ResponseFormatter.success( + `Successfully fetched AI configuration "${input.aiId}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/ai/aiSuggest.ts b/src/mcp/tools/ai/aiSuggest.ts new file mode 100644 index 0000000..230a567 --- /dev/null +++ b/src/mcp/tools/ai/aiSuggest.ts @@ -0,0 +1,41 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const aiSuggest = createTool({ + name: "ai-suggest", + description: "Gets AI suggestions based on input in Dokploy.", + schema: z.object({ + aiId: z.string().describe("The ID of the AI configuration to use."), + input: z.string().describe("The input to get suggestions for."), + serverId: z + .string() + .optional() + .describe("The ID of the server to get suggestions for."), + }), + annotations: { + title: "Get AI Suggestions", + readOnlyHint: false, + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const body: Record = { + aiId: input.aiId, + input: input.input, + }; + + if (input.serverId) { + body.serverId = input.serverId; + } + + const response = await apiClient.post("/ai.suggest", body); + + return ResponseFormatter.success( + "Successfully retrieved AI suggestions", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/ai/aiUpdate.ts b/src/mcp/tools/ai/aiUpdate.ts new file mode 100644 index 0000000..bd12dd4 --- /dev/null +++ b/src/mcp/tools/ai/aiUpdate.ts @@ -0,0 +1,59 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const aiUpdate = createTool({ + name: "ai-update", + description: "Updates an existing AI configuration in Dokploy.", + schema: z.object({ + aiId: z + .string() + .min(1) + .describe("The ID of the AI configuration to update."), + name: z + .string() + .min(1) + .optional() + .describe("The new name for the AI configuration."), + apiUrl: z + .string() + .url() + .optional() + .describe("The new API URL for the AI service."), + apiKey: z + .string() + .optional() + .describe("The new API key for authentication."), + model: z.string().min(1).optional().describe("The new model to use."), + isEnabled: z + .boolean() + .optional() + .describe("Whether the AI configuration is enabled."), + createdAt: z.string().optional().describe("The creation timestamp."), + }), + annotations: { + title: "Update AI Configuration", + readOnlyHint: false, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const body: Record = { aiId: input.aiId }; + + if (input.name !== undefined) body.name = input.name; + if (input.apiUrl !== undefined) body.apiUrl = input.apiUrl; + if (input.apiKey !== undefined) body.apiKey = input.apiKey; + if (input.model !== undefined) body.model = input.model; + if (input.isEnabled !== undefined) body.isEnabled = input.isEnabled; + if (input.createdAt !== undefined) body.createdAt = input.createdAt; + + const response = await apiClient.post("/ai.update", body); + + return ResponseFormatter.success( + `Successfully updated AI configuration "${input.aiId}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/ai/index.ts b/src/mcp/tools/ai/index.ts new file mode 100644 index 0000000..b069e78 --- /dev/null +++ b/src/mcp/tools/ai/index.ts @@ -0,0 +1,9 @@ +export { aiOne } from "./aiOne.js"; +export { aiGetModels } from "./aiGetModels.js"; +export { aiCreate } from "./aiCreate.js"; +export { aiUpdate } from "./aiUpdate.js"; +export { aiGetAll } from "./aiGetAll.js"; +export { aiGet } from "./aiGet.js"; +export { aiDelete } from "./aiDelete.js"; +export { aiSuggest } from "./aiSuggest.js"; +export { aiDeploy } from "./aiDeploy.js"; diff --git a/src/mcp/tools/backup/backupCreate.ts b/src/mcp/tools/backup/backupCreate.ts new file mode 100644 index 0000000..a39f60e --- /dev/null +++ b/src/mcp/tools/backup/backupCreate.ts @@ -0,0 +1,99 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const backupCreate = createTool({ + name: "backup-create", + description: + "Creates a new backup configuration for a database or compose service in Dokploy. Supports PostgreSQL, MySQL, MariaDB, MongoDB, and web server backups.", + schema: z.object({ + schedule: z + .string() + .describe("Cron schedule expression for automated backups (e.g., '0 2 * * *' for daily at 2 AM)."), + enabled: z + .boolean() + .nullable() + .optional() + .describe("Whether the backup schedule is enabled. Defaults to true if not specified."), + prefix: z + .string() + .min(1) + .describe("Prefix for backup file names. Used to identify and organize backup files."), + destinationId: z + .string() + .describe("ID of the backup destination (S3, local storage, etc.) where backups will be stored."), + keepLatestCount: z + .number() + .nullable() + .optional() + .describe("Number of most recent backups to retain. Older backups are automatically deleted."), + database: z + .string() + .min(1) + .describe("Name of the database to backup. Must match the database name in the service."), + mariadbId: z + .string() + .nullable() + .optional() + .describe("ID of the MariaDB service. Required when databaseType is 'mariadb'."), + mysqlId: z + .string() + .nullable() + .optional() + .describe("ID of the MySQL service. Required when databaseType is 'mysql'."), + postgresId: z + .string() + .nullable() + .optional() + .describe("ID of the PostgreSQL service. Required when databaseType is 'postgres'."), + mongoId: z + .string() + .nullable() + .optional() + .describe("ID of the MongoDB service. Required when databaseType is 'mongo'."), + databaseType: z + .enum(["postgres", "mariadb", "mysql", "mongo", "web-server"]) + .describe( + "Type of database to backup: 'postgres', 'mariadb', 'mysql', 'mongo', or 'web-server'.", + ), + userId: z + .string() + .nullable() + .optional() + .describe("ID of the user who owns this backup configuration."), + backupType: z + .enum(["database", "compose"]) + .optional() + .describe("Type of backup: 'database' for standalone databases, 'compose' for compose service databases."), + composeId: z + .string() + .nullable() + .optional() + .describe("ID of the compose deployment. Required when backupType is 'compose'."), + serviceName: z + .string() + .nullable() + .optional() + .describe("Name of the service within the compose deployment to backup."), + metadata: z + .unknown() + .nullable() + .optional() + .describe("Additional metadata to associate with the backup configuration."), + }), + annotations: { + title: "Create Backup", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/backup.create", input); + + return ResponseFormatter.success( + `Backup configuration created successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/backup/backupListFiles.ts b/src/mcp/tools/backup/backupListFiles.ts new file mode 100644 index 0000000..8b794fd --- /dev/null +++ b/src/mcp/tools/backup/backupListFiles.ts @@ -0,0 +1,48 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const backupListFiles = createTool({ + name: "backup-list-files", + description: + "Lists backup files stored at a specific destination. Allows searching and filtering backup files by name or prefix.", + schema: z.object({ + destinationId: z + .string() + .describe("The ID of the backup destination to list files from."), + search: z + .string() + .describe("Search string to filter backup files by name or prefix."), + serverId: z + .string() + .optional() + .describe("Server ID to filter backups for multi-server deployments."), + }), + annotations: { + title: "List Backup Files", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + let url = `/backup.listBackupFiles?destinationId=${encodeURIComponent(input.destinationId)}&search=${encodeURIComponent(input.search)}`; + if (input.serverId) { + url += `&serverId=${encodeURIComponent(input.serverId)}`; + } + + const backups = await apiClient.get(url); + + if (!backups?.data) { + return ResponseFormatter.error( + "Failed to fetch backup files", + "No backup files found", + ); + } + + return ResponseFormatter.success( + `Successfully fetched backup files`, + backups.data, + ); + }, +}); diff --git a/src/mcp/tools/backup/backupOne.ts b/src/mcp/tools/backup/backupOne.ts new file mode 100644 index 0000000..e6b514e --- /dev/null +++ b/src/mcp/tools/backup/backupOne.ts @@ -0,0 +1,38 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const backupOne = createTool({ + name: "backup-one", + description: + "Retrieves details of a specific backup configuration by its ID. Returns backup schedule, destination, database settings, and status.", + schema: z.object({ + backupId: z + .string() + .describe("The unique ID of the backup configuration to retrieve."), + }), + annotations: { + title: "Get Backup Details", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const backup = await apiClient.get( + `/backup.one?backupId=${input.backupId}`, + ); + + if (!backup?.data) { + return ResponseFormatter.error( + "Failed to fetch backup", + `Backup with ID "${input.backupId}" not found`, + ); + } + + return ResponseFormatter.success( + `Successfully fetched backup "${input.backupId}"`, + backup.data, + ); + }, +}); diff --git a/src/mcp/tools/backup/backupRemove.ts b/src/mcp/tools/backup/backupRemove.ts new file mode 100644 index 0000000..d4ee7b4 --- /dev/null +++ b/src/mcp/tools/backup/backupRemove.ts @@ -0,0 +1,29 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const backupRemove = createTool({ + name: "backup-remove", + description: + "Permanently deletes a backup configuration from Dokploy. This stops scheduled backups but does not delete existing backup files.", + schema: z.object({ + backupId: z + .string() + .describe("The unique ID of the backup configuration to delete."), + }), + annotations: { + title: "Remove Backup", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/backup.remove", input); + + return ResponseFormatter.success( + `Backup "${input.backupId}" removed successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/backup/backupRunManual.ts b/src/mcp/tools/backup/backupRunManual.ts new file mode 100644 index 0000000..b8bdd7d --- /dev/null +++ b/src/mcp/tools/backup/backupRunManual.ts @@ -0,0 +1,64 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +const backupTypeEnum = z.enum([ + "postgres", + "mysql", + "mariadb", + "mongo", + "compose", + "webServer", +]); + +type BackupType = z.infer; + +const endpointMap: Record = { + postgres: "/backup.manualBackupPostgres", + mysql: "/backup.manualBackupMySql", + mariadb: "/backup.manualBackupMariadb", + mongo: "/backup.manualBackupMongo", + compose: "/backup.manualBackupCompose", + webServer: "/backup.manualBackupWebServer", +}; + +const typeLabels: Record = { + postgres: "PostgreSQL", + mysql: "MySQL", + mariadb: "MariaDB", + mongo: "MongoDB", + compose: "Compose", + webServer: "Web Server", +}; + +export const backupRunManual = createTool({ + name: "backup-run-manual", + description: + "Triggers an immediate manual backup using an existing backup configuration. Runs the backup outside of the scheduled cron time. Supports PostgreSQL, MySQL, MariaDB, MongoDB, Compose, and Web Server backups.", + schema: z.object({ + backupId: z + .string() + .describe("The unique ID of the backup configuration to execute."), + type: backupTypeEnum.describe( + "The database type for the backup. Must match the backup configuration type: 'postgres', 'mysql', 'mariadb', 'mongo', 'compose', or 'webServer'.", + ), + }), + annotations: { + title: "Run Manual Backup", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const endpoint = endpointMap[input.type]; + const label = typeLabels[input.type]; + + const response = await apiClient.post(endpoint, { backupId: input.backupId }); + + return ResponseFormatter.success( + `Manual ${label} backup triggered successfully for backup "${input.backupId}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/backup/backupUpdate.ts b/src/mcp/tools/backup/backupUpdate.ts new file mode 100644 index 0000000..10a7546 --- /dev/null +++ b/src/mcp/tools/backup/backupUpdate.ts @@ -0,0 +1,67 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const backupUpdate = createTool({ + name: "backup-update", + description: + "Updates an existing backup configuration in Dokploy. Allows modifying schedule, destination, retention settings, and other backup parameters.", + schema: z.object({ + backupId: z + .string() + .describe("The unique ID of the backup configuration to update."), + schedule: z + .string() + .describe("Cron schedule expression for automated backups (e.g., '0 2 * * *' for daily at 2 AM)."), + enabled: z + .boolean() + .nullable() + .optional() + .describe("Whether the backup schedule is enabled."), + prefix: z + .string() + .min(1) + .describe("Prefix for backup file names. Used to identify and organize backup files."), + destinationId: z + .string() + .describe("ID of the backup destination (S3, local storage, etc.) where backups will be stored."), + database: z + .string() + .min(1) + .describe("Name of the database to backup. Must match the database name in the service."), + keepLatestCount: z + .number() + .nullable() + .optional() + .describe("Number of most recent backups to retain. Older backups are automatically deleted."), + serviceName: z + .string() + .nullable() + .describe("Name of the service within the compose deployment. Required for compose backups."), + metadata: z + .unknown() + .nullable() + .optional() + .describe("Additional metadata to associate with the backup configuration."), + databaseType: z + .enum(["postgres", "mariadb", "mysql", "mongo", "web-server"]) + .describe( + "Type of database to backup: 'postgres', 'mariadb', 'mysql', 'mongo', or 'web-server'.", + ), + }), + annotations: { + title: "Update Backup", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/backup.update", input); + + return ResponseFormatter.success( + `Backup "${input.backupId}" updated successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/backup/index.ts b/src/mcp/tools/backup/index.ts new file mode 100644 index 0000000..6ef9fb5 --- /dev/null +++ b/src/mcp/tools/backup/index.ts @@ -0,0 +1,15 @@ +// Database backup tools +export { backupCreate } from "./backupCreate.js"; +export { backupOne } from "./backupOne.js"; +export { backupUpdate } from "./backupUpdate.js"; +export { backupRemove } from "./backupRemove.js"; +export { backupListFiles } from "./backupListFiles.js"; +export { backupRunManual } from "./backupRunManual.js"; + +// Volume backup tools +export { volumeBackupCreate } from "./volumeBackupCreate.js"; +export { volumeBackupOne } from "./volumeBackupOne.js"; +export { volumeBackupUpdate } from "./volumeBackupUpdate.js"; +export { volumeBackupRemove } from "./volumeBackupRemove.js"; +export { volumeBackupList } from "./volumeBackupList.js"; +export { volumeBackupRunManual } from "./volumeBackupRunManual.js"; diff --git a/src/mcp/tools/backup/volumeBackupCreate.ts b/src/mcp/tools/backup/volumeBackupCreate.ts new file mode 100644 index 0000000..6117831 --- /dev/null +++ b/src/mcp/tools/backup/volumeBackupCreate.ts @@ -0,0 +1,115 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const volumeBackupCreate = createTool({ + name: "volume-backup-create", + description: + "Creates a new volume backup configuration in Dokploy. Volume backups allow backing up Docker volumes used by applications, databases, or compose services.", + schema: z.object({ + name: z + .string() + .describe("Descriptive name for the volume backup configuration."), + volumeName: z + .string() + .describe("Name of the Docker volume to backup."), + prefix: z + .string() + .describe("Prefix for backup file names. Used to identify and organize backup files."), + cronExpression: z + .string() + .describe("Cron expression for scheduling backups (e.g., '0 2 * * *' for daily at 2 AM)."), + destinationId: z + .string() + .describe("ID of the backup destination (S3, local storage, etc.) where backups will be stored."), + serviceType: z + .enum([ + "application", + "postgres", + "mysql", + "mariadb", + "mongo", + "redis", + "compose", + ]) + .optional() + .describe("Type of the service that owns the volume."), + appName: z + .string() + .optional() + .describe("Name of the application (used for labeling and identification)."), + serviceName: z + .string() + .nullable() + .optional() + .describe("Name of the service within a compose deployment."), + turnOff: z + .boolean() + .optional() + .describe("Whether to stop the service during backup to ensure data consistency."), + keepLatestCount: z + .number() + .nullable() + .optional() + .describe("Number of most recent backups to retain. Older backups are automatically deleted."), + enabled: z + .boolean() + .nullable() + .optional() + .describe("Whether the backup schedule is enabled. Defaults to true if not specified."), + applicationId: z + .string() + .nullable() + .optional() + .describe("ID of the application. Required when serviceType is 'application'."), + postgresId: z + .string() + .nullable() + .optional() + .describe("ID of the PostgreSQL service. Required when serviceType is 'postgres'."), + mariadbId: z + .string() + .nullable() + .optional() + .describe("ID of the MariaDB service. Required when serviceType is 'mariadb'."), + mongoId: z + .string() + .nullable() + .optional() + .describe("ID of the MongoDB service. Required when serviceType is 'mongo'."), + mysqlId: z + .string() + .nullable() + .optional() + .describe("ID of the MySQL service. Required when serviceType is 'mysql'."), + redisId: z + .string() + .nullable() + .optional() + .describe("ID of the Redis service. Required when serviceType is 'redis'."), + composeId: z + .string() + .nullable() + .optional() + .describe("ID of the compose deployment. Required when serviceType is 'compose'."), + createdAt: z + .string() + .optional() + .describe("Creation timestamp. Automatically set if not provided."), + }), + annotations: { + title: "Create Volume Backup", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/volumeBackups.create", input); + + return ResponseFormatter.success( + `Volume backup configuration "${input.name}" created successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/backup/volumeBackupList.ts b/src/mcp/tools/backup/volumeBackupList.ts new file mode 100644 index 0000000..09a21b1 --- /dev/null +++ b/src/mcp/tools/backup/volumeBackupList.ts @@ -0,0 +1,50 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const volumeBackupList = createTool({ + name: "volume-backup-list", + description: + "Lists all volume backup configurations for a specific resource in Dokploy. Filter by resource ID and type.", + schema: z.object({ + id: z + .string() + .min(1) + .describe("The ID of the resource (application, database, or compose deployment) to list volume backups for."), + volumeBackupType: z + .enum([ + "application", + "postgres", + "mysql", + "mariadb", + "mongo", + "redis", + "compose", + ]) + .describe("The type of resource: 'application', 'postgres', 'mysql', 'mariadb', 'mongo', 'redis', or 'compose'."), + }), + annotations: { + title: "List Volume Backups", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const volumeBackups = await apiClient.get( + `/volumeBackups.list?id=${encodeURIComponent(input.id)}&volumeBackupType=${encodeURIComponent(input.volumeBackupType)}`, + ); + + if (!volumeBackups?.data) { + return ResponseFormatter.error( + "Failed to fetch volume backups", + "No volume backups found", + ); + } + + return ResponseFormatter.success( + `Successfully fetched volume backups`, + volumeBackups.data, + ); + }, +}); diff --git a/src/mcp/tools/backup/volumeBackupOne.ts b/src/mcp/tools/backup/volumeBackupOne.ts new file mode 100644 index 0000000..8f47b05 --- /dev/null +++ b/src/mcp/tools/backup/volumeBackupOne.ts @@ -0,0 +1,39 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const volumeBackupOne = createTool({ + name: "volume-backup-one", + description: + "Retrieves details of a specific volume backup configuration by its ID. Returns backup schedule, volume name, destination, and status.", + schema: z.object({ + volumeBackupId: z + .string() + .min(1) + .describe("The unique ID of the volume backup configuration to retrieve."), + }), + annotations: { + title: "Get Volume Backup Details", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const volumeBackup = await apiClient.get( + `/volumeBackups.one?volumeBackupId=${encodeURIComponent(input.volumeBackupId)}`, + ); + + if (!volumeBackup?.data) { + return ResponseFormatter.error( + "Failed to fetch volume backup", + `Volume backup with ID "${input.volumeBackupId}" not found`, + ); + } + + return ResponseFormatter.success( + `Successfully fetched volume backup "${input.volumeBackupId}"`, + volumeBackup.data, + ); + }, +}); diff --git a/src/mcp/tools/backup/volumeBackupRemove.ts b/src/mcp/tools/backup/volumeBackupRemove.ts new file mode 100644 index 0000000..18b9851 --- /dev/null +++ b/src/mcp/tools/backup/volumeBackupRemove.ts @@ -0,0 +1,30 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const volumeBackupRemove = createTool({ + name: "volume-backup-remove", + description: + "Permanently deletes a volume backup configuration from Dokploy. This stops scheduled backups but does not delete existing backup files.", + schema: z.object({ + volumeBackupId: z + .string() + .min(1) + .describe("The unique ID of the volume backup configuration to delete."), + }), + annotations: { + title: "Remove Volume Backup", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/volumeBackups.delete", input); + + return ResponseFormatter.success( + `Volume backup "${input.volumeBackupId}" deleted successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/backup/volumeBackupRunManual.ts b/src/mcp/tools/backup/volumeBackupRunManual.ts new file mode 100644 index 0000000..e31ade5 --- /dev/null +++ b/src/mcp/tools/backup/volumeBackupRunManual.ts @@ -0,0 +1,30 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const volumeBackupRunManual = createTool({ + name: "volume-backup-run-manual", + description: + "Triggers an immediate manual volume backup using an existing backup configuration. Runs the backup outside of the scheduled cron time.", + schema: z.object({ + volumeBackupId: z + .string() + .min(1) + .describe("The unique ID of the volume backup configuration to execute."), + }), + annotations: { + title: "Run Volume Backup Manually", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/volumeBackups.runManually", input); + + return ResponseFormatter.success( + `Manual volume backup triggered successfully for "${input.volumeBackupId}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/backup/volumeBackupUpdate.ts b/src/mcp/tools/backup/volumeBackupUpdate.ts new file mode 100644 index 0000000..3dfb8de --- /dev/null +++ b/src/mcp/tools/backup/volumeBackupUpdate.ts @@ -0,0 +1,119 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const volumeBackupUpdate = createTool({ + name: "volume-backup-update", + description: + "Updates an existing volume backup configuration in Dokploy. Allows modifying schedule, destination, retention settings, and other backup parameters.", + schema: z.object({ + volumeBackupId: z + .string() + .min(1) + .describe("The unique ID of the volume backup configuration to update."), + name: z + .string() + .describe("Descriptive name for the volume backup configuration."), + volumeName: z + .string() + .describe("Name of the Docker volume to backup."), + prefix: z + .string() + .describe("Prefix for backup file names. Used to identify and organize backup files."), + cronExpression: z + .string() + .describe("Cron expression for scheduling backups (e.g., '0 2 * * *' for daily at 2 AM)."), + destinationId: z + .string() + .describe("ID of the backup destination (S3, local storage, etc.) where backups will be stored."), + serviceType: z + .enum([ + "application", + "postgres", + "mysql", + "mariadb", + "mongo", + "redis", + "compose", + ]) + .optional() + .describe("Type of the service that owns the volume."), + appName: z + .string() + .optional() + .describe("Name of the application (used for labeling and identification)."), + serviceName: z + .string() + .nullable() + .optional() + .describe("Name of the service within a compose deployment."), + turnOff: z + .boolean() + .optional() + .describe("Whether to stop the service during backup to ensure data consistency."), + keepLatestCount: z + .number() + .nullable() + .optional() + .describe("Number of most recent backups to retain. Older backups are automatically deleted."), + enabled: z + .boolean() + .nullable() + .optional() + .describe("Whether the backup schedule is enabled."), + applicationId: z + .string() + .nullable() + .optional() + .describe("ID of the application. Required when serviceType is 'application'."), + postgresId: z + .string() + .nullable() + .optional() + .describe("ID of the PostgreSQL service. Required when serviceType is 'postgres'."), + mariadbId: z + .string() + .nullable() + .optional() + .describe("ID of the MariaDB service. Required when serviceType is 'mariadb'."), + mongoId: z + .string() + .nullable() + .optional() + .describe("ID of the MongoDB service. Required when serviceType is 'mongo'."), + mysqlId: z + .string() + .nullable() + .optional() + .describe("ID of the MySQL service. Required when serviceType is 'mysql'."), + redisId: z + .string() + .nullable() + .optional() + .describe("ID of the Redis service. Required when serviceType is 'redis'."), + composeId: z + .string() + .nullable() + .optional() + .describe("ID of the compose deployment. Required when serviceType is 'compose'."), + createdAt: z + .string() + .optional() + .describe("Creation timestamp."), + }), + annotations: { + title: "Update Volume Backup", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/volumeBackups.update", input); + + return ResponseFormatter.success( + `Volume backup "${input.volumeBackupId}" updated successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/certificates/certificatesAll.ts b/src/mcp/tools/certificates/certificatesAll.ts new file mode 100644 index 0000000..c8d08ae --- /dev/null +++ b/src/mcp/tools/certificates/certificatesAll.ts @@ -0,0 +1,31 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const certificatesAll = createTool({ + name: "certificates-all", + description: "Lists all SSL/TLS certificates in Dokploy.", + schema: z.object({}), + annotations: { + title: "List All Certificates", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const certificates = await apiClient.get("/certificates.all"); + + if (!certificates?.data) { + return ResponseFormatter.error( + "Failed to fetch certificates", + "No certificates found", + ); + } + + return ResponseFormatter.success( + `Successfully fetched all certificates`, + certificates.data, + ); + }, +}); diff --git a/src/mcp/tools/certificates/certificatesCreate.ts b/src/mcp/tools/certificates/certificatesCreate.ts new file mode 100644 index 0000000..9f782d1 --- /dev/null +++ b/src/mcp/tools/certificates/certificatesCreate.ts @@ -0,0 +1,60 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const certificatesCreate = createTool({ + name: "certificates-create", + description: "Creates a new SSL/TLS certificate in Dokploy.", + schema: z.object({ + name: z + .string() + .min(1) + .describe("Display name for the certificate. Required."), + certificateData: z + .string() + .min(1) + .describe( + "The SSL/TLS certificate in PEM format. Include the full chain if applicable. Required." + ), + privateKey: z + .string() + .min(1) + .describe("The private key in PEM format. Must match the certificate. Required."), + organizationId: z + .string() + .describe("The organization ID to associate this certificate with. Required."), + certificateId: z + .string() + .optional() + .describe("Custom ID for the certificate. Auto-generated if not provided."), + certificatePath: z + .string() + .optional() + .describe("File path where the certificate will be stored on the server."), + autoRenew: z + .boolean() + .nullable() + .optional() + .describe("Whether to enable automatic certificate renewal."), + serverId: z + .string() + .nullable() + .optional() + .describe("Server ID to deploy this certificate to. Deploys to all servers if not specified."), + }), + annotations: { + title: "Create Certificate", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/certificates.create", input); + + return ResponseFormatter.success( + `Certificate "${input.name}" created successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/certificates/certificatesOne.ts b/src/mcp/tools/certificates/certificatesOne.ts new file mode 100644 index 0000000..4dd2a22 --- /dev/null +++ b/src/mcp/tools/certificates/certificatesOne.ts @@ -0,0 +1,38 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const certificatesOne = createTool({ + name: "certificates-one", + description: "Gets a specific SSL/TLS certificate by its ID in Dokploy.", + schema: z.object({ + certificateId: z + .string() + .min(1) + .describe("The unique identifier of the certificate to retrieve. Required."), + }), + annotations: { + title: "Get Certificate Details", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const certificate = await apiClient.get( + `/certificates.one?certificateId=${encodeURIComponent(input.certificateId)}`, + ); + + if (!certificate?.data) { + return ResponseFormatter.error( + "Failed to fetch certificate", + `Certificate with ID "${input.certificateId}" not found`, + ); + } + + return ResponseFormatter.success( + `Successfully fetched certificate "${input.certificateId}"`, + certificate.data, + ); + }, +}); diff --git a/src/mcp/tools/certificates/certificatesRemove.ts b/src/mcp/tools/certificates/certificatesRemove.ts new file mode 100644 index 0000000..70475cd --- /dev/null +++ b/src/mcp/tools/certificates/certificatesRemove.ts @@ -0,0 +1,29 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const certificatesRemove = createTool({ + name: "certificates-remove", + description: "Removes/deletes an SSL/TLS certificate from Dokploy.", + schema: z.object({ + certificateId: z + .string() + .min(1) + .describe("The unique identifier of the certificate to remove. Required."), + }), + annotations: { + title: "Remove Certificate", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/certificates.remove", input); + + return ResponseFormatter.success( + `Certificate "${input.certificateId}" removed successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/certificates/index.ts b/src/mcp/tools/certificates/index.ts new file mode 100644 index 0000000..679a602 --- /dev/null +++ b/src/mcp/tools/certificates/index.ts @@ -0,0 +1,4 @@ +export { certificatesCreate } from "./certificatesCreate.js"; +export { certificatesOne } from "./certificatesOne.js"; +export { certificatesRemove } from "./certificatesRemove.js"; +export { certificatesAll } from "./certificatesAll.js"; diff --git a/src/mcp/tools/cluster/clusterAddManager.ts b/src/mcp/tools/cluster/clusterAddManager.ts new file mode 100644 index 0000000..68a9fd4 --- /dev/null +++ b/src/mcp/tools/cluster/clusterAddManager.ts @@ -0,0 +1,38 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const clusterAddManager = createTool({ + name: "cluster-add-manager", + description: + "Gets the command to add a manager node to the cluster in Dokploy.", + schema: z.object({ + serverId: z + .string() + .optional() + .describe("The ID of the server to add the manager to."), + }), + annotations: { + title: "Add Cluster Manager", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const params = new URLSearchParams(); + if (input.serverId) { + params.append("serverId", input.serverId); + } + + const queryString = params.toString(); + const url = `/cluster.addManager${queryString ? `?${queryString}` : ""}`; + + const response = await apiClient.get(url); + + return ResponseFormatter.success( + "Successfully retrieved add manager command", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/cluster/clusterAddWorker.ts b/src/mcp/tools/cluster/clusterAddWorker.ts new file mode 100644 index 0000000..414bf13 --- /dev/null +++ b/src/mcp/tools/cluster/clusterAddWorker.ts @@ -0,0 +1,38 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const clusterAddWorker = createTool({ + name: "cluster-add-worker", + description: + "Gets the command to add a worker node to the cluster in Dokploy.", + schema: z.object({ + serverId: z + .string() + .optional() + .describe("The ID of the server to add the worker to."), + }), + annotations: { + title: "Add Cluster Worker", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const params = new URLSearchParams(); + if (input.serverId) { + params.append("serverId", input.serverId); + } + + const queryString = params.toString(); + const url = `/cluster.addWorker${queryString ? `?${queryString}` : ""}`; + + const response = await apiClient.get(url); + + return ResponseFormatter.success( + "Successfully retrieved add worker command", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/cluster/clusterGetNodes.ts b/src/mcp/tools/cluster/clusterGetNodes.ts new file mode 100644 index 0000000..10b059e --- /dev/null +++ b/src/mcp/tools/cluster/clusterGetNodes.ts @@ -0,0 +1,37 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const clusterGetNodes = createTool({ + name: "cluster-get-nodes", + description: "Gets all nodes in the cluster from Dokploy.", + schema: z.object({ + serverId: z + .string() + .optional() + .describe("The ID of the server to get cluster nodes from."), + }), + annotations: { + title: "Get Cluster Nodes", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const params = new URLSearchParams(); + if (input.serverId) { + params.append("serverId", input.serverId); + } + + const queryString = params.toString(); + const url = `/cluster.getNodes${queryString ? `?${queryString}` : ""}`; + + const response = await apiClient.get(url); + + return ResponseFormatter.success( + "Successfully fetched cluster nodes", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/cluster/clusterRemoveWorker.ts b/src/mcp/tools/cluster/clusterRemoveWorker.ts new file mode 100644 index 0000000..c5ea1ff --- /dev/null +++ b/src/mcp/tools/cluster/clusterRemoveWorker.ts @@ -0,0 +1,36 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const clusterRemoveWorker = createTool({ + name: "cluster-remove-worker", + description: "Removes a worker node from the cluster in Dokploy.", + schema: z.object({ + nodeId: z.string().describe("The ID of the worker node to remove."), + serverId: z + .string() + .optional() + .describe("The ID of the server to remove the worker from."), + }), + annotations: { + title: "Remove Cluster Worker", + readOnlyHint: false, + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const body: Record = { nodeId: input.nodeId }; + if (input.serverId) { + body.serverId = input.serverId; + } + + const response = await apiClient.post("/cluster.removeWorker", body); + + return ResponseFormatter.success( + `Successfully removed worker node "${input.nodeId}" from cluster`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/cluster/index.ts b/src/mcp/tools/cluster/index.ts new file mode 100644 index 0000000..468bf51 --- /dev/null +++ b/src/mcp/tools/cluster/index.ts @@ -0,0 +1,4 @@ +export { clusterGetNodes } from "./clusterGetNodes.js"; +export { clusterRemoveWorker } from "./clusterRemoveWorker.js"; +export { clusterAddWorker } from "./clusterAddWorker.js"; +export { clusterAddManager } from "./clusterAddManager.js"; diff --git a/src/mcp/tools/compose/composeCancelDeployment.ts b/src/mcp/tools/compose/composeCancelDeployment.ts new file mode 100644 index 0000000..2dcba64 --- /dev/null +++ b/src/mcp/tools/compose/composeCancelDeployment.ts @@ -0,0 +1,30 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const composeCancelDeployment = createTool({ + name: "compose-cancelDeployment", + description: + "Cancels an ongoing deployment for a Docker Compose project in Dokploy.", + schema: z.object({ + composeId: z + .string() + .min(1) + .describe("The ID of the compose project to cancel deployment for."), + }), + annotations: { + title: "Cancel Compose Deployment", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/compose.cancelDeployment", input); + + return ResponseFormatter.success( + `Deployment for compose project "${input.composeId}" cancelled successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/compose/composeCleanQueues.ts b/src/mcp/tools/compose/composeCleanQueues.ts new file mode 100644 index 0000000..f025c07 --- /dev/null +++ b/src/mcp/tools/compose/composeCleanQueues.ts @@ -0,0 +1,29 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const composeCleanQueues = createTool({ + name: "compose-cleanQueues", + description: "Cleans the deployment queues for a compose project.", + schema: z.object({ + composeId: z + .string() + .min(1) + .describe("The ID of the compose project to clean queues for."), + }), + annotations: { + title: "Clean Compose Queues", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/compose.cleanQueues", input); + + return ResponseFormatter.success( + `Queues cleaned successfully for compose project "${input.composeId}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/compose/composeCreate.ts b/src/mcp/tools/compose/composeCreate.ts new file mode 100644 index 0000000..ffba53b --- /dev/null +++ b/src/mcp/tools/compose/composeCreate.ts @@ -0,0 +1,53 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { createTool } from "../toolFactory.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; + +export const composeCreate = createTool({ + name: "compose-create", + description: "Creates a new Docker Compose project in Dokploy.", + schema: z.object({ + name: z.string().min(1).describe("The name of the compose project."), + description: z + .string() + .nullable() + .optional() + .describe("An optional description for the compose project."), + environmentId: z + .string() + .describe( + "The ID of the environment where the compose project will be created.", + ), + composeType: z + .enum(["docker-compose", "stack"]) + .optional() + .describe("The type of compose deployment (docker-compose or stack)."), + appName: z + .string() + .optional() + .describe("The app name for the compose project."), + serverId: z + .string() + .nullable() + .optional() + .describe("The ID of the server where the compose will be deployed."), + composeFile: z + .string() + .optional() + .describe("The initial compose file content."), + }), + annotations: { + title: "Create Compose Project", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/compose.create", input); + + return ResponseFormatter.success( + `Compose project "${input.name}" created successfully in environment "${input.environmentId}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/compose/composeDelete.ts b/src/mcp/tools/compose/composeDelete.ts new file mode 100644 index 0000000..d4db373 --- /dev/null +++ b/src/mcp/tools/compose/composeDelete.ts @@ -0,0 +1,32 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const composeDelete = createTool({ + name: "compose-delete", + description: "Deletes a Docker Compose project in Dokploy.", + schema: z.object({ + composeId: z + .string() + .min(1) + .describe("The ID of the compose project to delete."), + deleteVolumes: z + .boolean() + .describe("Whether to delete associated volumes."), + }), + annotations: { + title: "Delete Compose Project", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/compose.delete", input); + + return ResponseFormatter.success( + `Compose project "${input.composeId}" deleted successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/compose/composeDeploy.ts b/src/mcp/tools/compose/composeDeploy.ts new file mode 100644 index 0000000..7e857db --- /dev/null +++ b/src/mcp/tools/compose/composeDeploy.ts @@ -0,0 +1,34 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const composeDeploy = createTool({ + name: "compose-deploy", + description: "Deploys a Docker Compose project in Dokploy.", + schema: z.object({ + composeId: z + .string() + .min(1) + .describe("The ID of the compose project 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 Compose Project", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/compose.deploy", input); + + return ResponseFormatter.success( + `Compose project "${input.composeId}" deployment started successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/compose/composeDeployTemplate.ts b/src/mcp/tools/compose/composeDeployTemplate.ts new file mode 100644 index 0000000..8f9d14c --- /dev/null +++ b/src/mcp/tools/compose/composeDeployTemplate.ts @@ -0,0 +1,37 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const composeDeployTemplate = createTool({ + name: "compose-deployTemplate", + description: "Deploys a compose project from a template.", + schema: z.object({ + environmentId: z + .string() + .describe("The ID of the environment to deploy the template to."), + id: z.string().describe("The ID of the template to deploy."), + serverId: z + .string() + .optional() + .describe("The ID of the server to deploy to."), + baseUrl: z + .string() + .optional() + .describe("Base URL for template repository."), + }), + annotations: { + title: "Deploy Compose Template", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/compose.deployTemplate", input); + + return ResponseFormatter.success( + `Template "${input.id}" deployed successfully to environment "${input.environmentId}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/compose/composeDisconnectGitProvider.ts b/src/mcp/tools/compose/composeDisconnectGitProvider.ts new file mode 100644 index 0000000..62bfff9 --- /dev/null +++ b/src/mcp/tools/compose/composeDisconnectGitProvider.ts @@ -0,0 +1,34 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const composeDisconnectGitProvider = createTool({ + name: "compose-disconnectGitProvider", + description: "Disconnects the Git provider from a compose project.", + schema: z.object({ + composeId: z + .string() + .min(1) + .describe( + "The ID of the compose project to disconnect Git provider from.", + ), + }), + annotations: { + title: "Disconnect Compose Git Provider", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post( + "/compose.disconnectGitProvider", + input, + ); + + return ResponseFormatter.success( + `Git provider disconnected successfully for compose project "${input.composeId}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/compose/composeFetchSourceType.ts b/src/mcp/tools/compose/composeFetchSourceType.ts new file mode 100644 index 0000000..8bd1aaf --- /dev/null +++ b/src/mcp/tools/compose/composeFetchSourceType.ts @@ -0,0 +1,30 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const composeFetchSourceType = createTool({ + name: "compose-fetchSourceType", + description: + "Fetches and updates the source type for a compose project based on its configuration.", + schema: z.object({ + composeId: z + .string() + .min(1) + .describe("The ID of the compose project to fetch source type for."), + }), + annotations: { + title: "Fetch Compose Source Type", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/compose.fetchSourceType", input); + + return ResponseFormatter.success( + `Source type fetched successfully for compose project "${input.composeId}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/compose/composeGetConvertedCompose.ts b/src/mcp/tools/compose/composeGetConvertedCompose.ts new file mode 100644 index 0000000..627036d --- /dev/null +++ b/src/mcp/tools/compose/composeGetConvertedCompose.ts @@ -0,0 +1,36 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { createTool } from "../toolFactory.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; + +export const composeGetConvertedCompose = createTool({ + name: "compose-getConvertedCompose", + description: + "Gets the converted/processed compose file with environment variables substituted.", + schema: z.object({ + composeId: z.string().min(1).describe("The ID of the compose project."), + }), + annotations: { + title: "Get Converted Compose File", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const converted = await apiClient.get( + `/compose.getConvertedCompose?composeId=${input.composeId}`, + ); + + if (!converted?.data) { + return ResponseFormatter.error( + "Failed to get converted compose", + `Could not get converted compose file for project "${input.composeId}"`, + ); + } + + return ResponseFormatter.success( + `Successfully retrieved converted compose file for project "${input.composeId}"`, + converted.data, + ); + }, +}); diff --git a/src/mcp/tools/compose/composeGetDefaultCommand.ts b/src/mcp/tools/compose/composeGetDefaultCommand.ts new file mode 100644 index 0000000..041ce15 --- /dev/null +++ b/src/mcp/tools/compose/composeGetDefaultCommand.ts @@ -0,0 +1,35 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { createTool } from "../toolFactory.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; + +export const composeGetDefaultCommand = createTool({ + name: "compose-getDefaultCommand", + description: "Gets the default docker-compose command for a compose project.", + schema: z.object({ + composeId: z.string().min(1).describe("The ID of the compose project."), + }), + annotations: { + title: "Get Default Compose Command", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const command = await apiClient.get( + `/compose.getDefaultCommand?composeId=${input.composeId}`, + ); + + if (!command?.data) { + return ResponseFormatter.error( + "Failed to get default command", + `Could not get default command for compose project "${input.composeId}"`, + ); + } + + return ResponseFormatter.success( + `Successfully retrieved default command for compose project "${input.composeId}"`, + command.data, + ); + }, +}); diff --git a/src/mcp/tools/compose/composeGetTags.ts b/src/mcp/tools/compose/composeGetTags.ts new file mode 100644 index 0000000..f96f1e4 --- /dev/null +++ b/src/mcp/tools/compose/composeGetTags.ts @@ -0,0 +1,46 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { createTool } from "../toolFactory.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; + +export const composeGetTags = createTool({ + name: "compose-getTags", + description: "Gets the list of available tags for compose templates.", + schema: z.object({ + baseUrl: z + .string() + .optional() + .describe("Optional base URL for template repository."), + }), + annotations: { + title: "Get Compose Template Tags", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const params = new URLSearchParams(); + if (input.baseUrl) { + params.append("baseUrl", input.baseUrl); + } + + const queryString = params.toString(); + const url = queryString + ? `/compose.getTags?${queryString}` + : "/compose.getTags"; + + const tags = await apiClient.get(url); + + if (!tags?.data) { + return ResponseFormatter.error( + "Failed to fetch tags", + "Could not retrieve compose template tags", + ); + } + + return ResponseFormatter.success( + "Successfully retrieved compose template tags", + tags.data, + ); + }, +}); diff --git a/src/mcp/tools/compose/composeImport.ts b/src/mcp/tools/compose/composeImport.ts new file mode 100644 index 0000000..927d733 --- /dev/null +++ b/src/mcp/tools/compose/composeImport.ts @@ -0,0 +1,29 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const composeImport = createTool({ + name: "compose-import", + description: "Imports a compose file from base64-encoded content.", + schema: z.object({ + composeId: z.string().min(1).describe("The ID of the compose project."), + base64: z + .string() + .describe("Base64-encoded compose file content to import."), + }), + annotations: { + title: "Import Compose File", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/compose.import", input); + + return ResponseFormatter.success( + `Compose file imported successfully for project "${input.composeId}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/compose/composeIsolatedDeployment.ts b/src/mcp/tools/compose/composeIsolatedDeployment.ts new file mode 100644 index 0000000..303c55f --- /dev/null +++ b/src/mcp/tools/compose/composeIsolatedDeployment.ts @@ -0,0 +1,33 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const composeIsolatedDeployment = createTool({ + name: "compose-isolatedDeployment", + description: "Creates an isolated deployment of a compose project.", + schema: z.object({ + composeId: z + .string() + .min(1) + .describe("The ID of the compose project for isolated deployment."), + suffix: z + .string() + .optional() + .describe("Optional suffix for the isolated deployment."), + }), + annotations: { + title: "Isolated Compose Deployment", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/compose.isolatedDeployment", input); + + return ResponseFormatter.success( + `Isolated deployment started for compose project "${input.composeId}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/compose/composeKillBuild.ts b/src/mcp/tools/compose/composeKillBuild.ts new file mode 100644 index 0000000..1acae44 --- /dev/null +++ b/src/mcp/tools/compose/composeKillBuild.ts @@ -0,0 +1,29 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const composeKillBuild = createTool({ + name: "compose-killBuild", + description: "Kills an ongoing build process for a compose project.", + schema: z.object({ + composeId: z + .string() + .min(1) + .describe("The ID of the compose project to kill build for."), + }), + annotations: { + title: "Kill Compose Build", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/compose.killBuild", input); + + return ResponseFormatter.success( + `Build killed successfully for compose project "${input.composeId}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/compose/composeLoadMountsByService.ts b/src/mcp/tools/compose/composeLoadMountsByService.ts new file mode 100644 index 0000000..e944fbb --- /dev/null +++ b/src/mcp/tools/compose/composeLoadMountsByService.ts @@ -0,0 +1,44 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { createTool } from "../toolFactory.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; + +export const composeLoadMountsByService = createTool({ + name: "compose-loadMountsByService", + description: + "Loads the mounts/volumes for a specific service in a Docker Compose project.", + schema: z.object({ + composeId: z.string().min(1).describe("The ID of the compose project."), + serviceName: z + .string() + .min(1) + .describe("The name of the service to load mounts for."), + }), + annotations: { + title: "Load Compose Service Mounts", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const params = new URLSearchParams(); + params.append("composeId", input.composeId); + params.append("serviceName", input.serviceName); + + const mounts = await apiClient.get( + `/compose.loadMountsByService?${params.toString()}`, + ); + + if (!mounts?.data) { + return ResponseFormatter.error( + "Failed to load mounts", + `Could not load mounts for service "${input.serviceName}" in compose project "${input.composeId}"`, + ); + } + + return ResponseFormatter.success( + `Successfully loaded mounts for service "${input.serviceName}" in compose project "${input.composeId}"`, + mounts.data, + ); + }, +}); diff --git a/src/mcp/tools/compose/composeLoadServices.ts b/src/mcp/tools/compose/composeLoadServices.ts new file mode 100644 index 0000000..4de880f --- /dev/null +++ b/src/mcp/tools/compose/composeLoadServices.ts @@ -0,0 +1,51 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { createTool } from "../toolFactory.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; + +export const composeLoadServices = createTool({ + name: "compose-loadServices", + description: + "Loads and lists the services defined in a Docker Compose project.", + schema: z.object({ + composeId: z + .string() + .min(1) + .describe("The ID of the compose project to load services from."), + type: z + .enum(["fetch", "cache"]) + .optional() + .describe( + "Whether to fetch fresh data or use cached data. Defaults to 'cache'.", + ), + }), + annotations: { + title: "Load Compose Services", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const params = new URLSearchParams(); + params.append("composeId", input.composeId); + if (input.type) { + params.append("type", input.type); + } + + const services = await apiClient.get( + `/compose.loadServices?${params.toString()}`, + ); + + if (!services?.data) { + return ResponseFormatter.error( + "Failed to load services", + `Could not load services for compose project "${input.composeId}"`, + ); + } + + return ResponseFormatter.success( + `Successfully loaded services for compose project "${input.composeId}"`, + services.data, + ); + }, +}); diff --git a/src/mcp/tools/compose/composeMove.ts b/src/mcp/tools/compose/composeMove.ts new file mode 100644 index 0000000..d139b9e --- /dev/null +++ b/src/mcp/tools/compose/composeMove.ts @@ -0,0 +1,31 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const composeMove = createTool({ + name: "compose-move", + description: "Moves a compose project to a different environment.", + schema: z.object({ + composeId: z + .string() + .describe("The ID of the compose project to move."), + targetEnvironmentId: z + .string() + .describe("The ID of the destination environment."), + }), + annotations: { + title: "Move Compose Project", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/compose.move", input); + + return ResponseFormatter.success( + `Compose project "${input.composeId}" moved to environment "${input.targetEnvironmentId}" successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/compose/composeOne.ts b/src/mcp/tools/compose/composeOne.ts new file mode 100644 index 0000000..69172fe --- /dev/null +++ b/src/mcp/tools/compose/composeOne.ts @@ -0,0 +1,38 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { createTool } from "../toolFactory.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; + +export const composeOne = createTool({ + name: "compose-one", + description: "Gets a specific Docker Compose project by its ID in Dokploy.", + schema: z.object({ + composeId: z + .string() + .min(1) + .describe("The ID of the compose project to retrieve."), + }), + annotations: { + title: "Get Compose Project Details", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const compose = await apiClient.get( + `/compose.one?composeId=${input.composeId}`, + ); + + if (!compose?.data) { + return ResponseFormatter.error( + "Failed to fetch compose project", + `Compose project with ID "${input.composeId}" not found`, + ); + } + + return ResponseFormatter.success( + `Successfully fetched compose project "${input.composeId}"`, + compose.data, + ); + }, +}); diff --git a/src/mcp/tools/compose/composeProcessTemplate.ts b/src/mcp/tools/compose/composeProcessTemplate.ts new file mode 100644 index 0000000..bb198b0 --- /dev/null +++ b/src/mcp/tools/compose/composeProcessTemplate.ts @@ -0,0 +1,27 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const composeProcessTemplate = createTool({ + name: "compose-processTemplate", + description: "Processes a compose template with base64-encoded content.", + schema: z.object({ + composeId: z.string().min(1).describe("The ID of the compose project."), + base64: z.string().describe("Base64-encoded compose template content."), + }), + annotations: { + title: "Process Compose Template", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/compose.processTemplate", input); + + return ResponseFormatter.success( + `Template processed successfully for compose project "${input.composeId}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/compose/composeRandomizeCompose.ts b/src/mcp/tools/compose/composeRandomizeCompose.ts new file mode 100644 index 0000000..075cfa0 --- /dev/null +++ b/src/mcp/tools/compose/composeRandomizeCompose.ts @@ -0,0 +1,34 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const composeRandomizeCompose = createTool({ + name: "compose-randomizeCompose", + description: + "Randomizes the compose project names and identifiers to avoid conflicts.", + schema: z.object({ + composeId: z + .string() + .min(1) + .describe("The ID of the compose project to randomize."), + suffix: z + .string() + .optional() + .describe("Optional suffix to append to randomized names."), + }), + annotations: { + title: "Randomize Compose Names", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/compose.randomizeCompose", input); + + return ResponseFormatter.success( + `Compose project "${input.composeId}" names randomized successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/compose/composeRedeploy.ts b/src/mcp/tools/compose/composeRedeploy.ts new file mode 100644 index 0000000..4dee548 --- /dev/null +++ b/src/mcp/tools/compose/composeRedeploy.ts @@ -0,0 +1,37 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const composeRedeploy = createTool({ + name: "compose-redeploy", + description: "Redeploys a Docker Compose project in Dokploy.", + schema: z.object({ + composeId: z + .string() + .min(1) + .describe("The ID of the compose project 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 Compose Project", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/compose.redeploy", input); + + return ResponseFormatter.success( + `Compose project "${input.composeId}" redeployment started successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/compose/composeRefreshToken.ts b/src/mcp/tools/compose/composeRefreshToken.ts new file mode 100644 index 0000000..f75627f --- /dev/null +++ b/src/mcp/tools/compose/composeRefreshToken.ts @@ -0,0 +1,29 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const composeRefreshToken = createTool({ + name: "compose-refreshToken", + description: "Refreshes the token for a compose project.", + schema: z.object({ + composeId: z + .string() + .min(1) + .describe("The ID of the compose project to refresh token for."), + }), + annotations: { + title: "Refresh Compose Token", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/compose.refreshToken", input); + + return ResponseFormatter.success( + `Token refreshed successfully for compose project "${input.composeId}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/compose/composeStart.ts b/src/mcp/tools/compose/composeStart.ts new file mode 100644 index 0000000..6364fce --- /dev/null +++ b/src/mcp/tools/compose/composeStart.ts @@ -0,0 +1,29 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const composeStart = createTool({ + name: "compose-start", + description: "Starts a Docker Compose project in Dokploy.", + schema: z.object({ + composeId: z + .string() + .min(1) + .describe("The ID of the compose project to start."), + }), + annotations: { + title: "Start Compose Project", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/compose.start", input); + + return ResponseFormatter.success( + `Compose project "${input.composeId}" started successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/compose/composeStop.ts b/src/mcp/tools/compose/composeStop.ts new file mode 100644 index 0000000..9d285b0 --- /dev/null +++ b/src/mcp/tools/compose/composeStop.ts @@ -0,0 +1,29 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const composeStop = createTool({ + name: "compose-stop", + description: "Stops a Docker Compose project in Dokploy.", + schema: z.object({ + composeId: z + .string() + .min(1) + .describe("The ID of the compose project to stop."), + }), + annotations: { + title: "Stop Compose Project", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/compose.stop", input); + + return ResponseFormatter.success( + `Compose project "${input.composeId}" stopped successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/compose/composeTemplates.ts b/src/mcp/tools/compose/composeTemplates.ts new file mode 100644 index 0000000..36f3572 --- /dev/null +++ b/src/mcp/tools/compose/composeTemplates.ts @@ -0,0 +1,46 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { createTool } from "../toolFactory.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; + +export const composeTemplates = createTool({ + name: "compose-templates", + description: "Gets the list of available compose templates.", + schema: z.object({ + baseUrl: z + .string() + .optional() + .describe("Optional base URL for template repository."), + }), + annotations: { + title: "List Compose Templates", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const params = new URLSearchParams(); + if (input.baseUrl) { + params.append("baseUrl", input.baseUrl); + } + + const queryString = params.toString(); + const url = queryString + ? `/compose.templates?${queryString}` + : "/compose.templates"; + + const templates = await apiClient.get(url); + + if (!templates?.data) { + return ResponseFormatter.error( + "Failed to fetch templates", + "Could not retrieve compose templates", + ); + } + + return ResponseFormatter.success( + "Successfully retrieved compose templates", + templates.data, + ); + }, +}); diff --git a/src/mcp/tools/compose/composeUpdate.ts b/src/mcp/tools/compose/composeUpdate.ts new file mode 100644 index 0000000..0016203 --- /dev/null +++ b/src/mcp/tools/compose/composeUpdate.ts @@ -0,0 +1,182 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const composeUpdate = createTool({ + name: "compose-update", + description: "Updates an existing Docker Compose project in Dokploy.", + schema: z.object({ + composeId: z + .string() + .describe("The ID of the compose project to update."), + name: z + .string() + .min(1) + .optional() + .describe("The new name of the compose project."), + appName: z + .string() + .optional() + .describe("The new app name of the compose project."), + description: z + .string() + .nullable() + .optional() + .describe("The new description for the compose project."), + env: z + .string() + .nullable() + .optional() + .describe("Environment variables for the compose project."), + composeFile: z.string().optional().describe("The compose file content."), + refreshToken: z + .string() + .nullable() + .optional() + .describe("Refresh token for the compose project."), + sourceType: z + .enum(["git", "github", "gitlab", "bitbucket", "gitea", "raw"]) + .optional() + .describe("Source type for the compose project."), + composeType: z + .enum(["docker-compose", "stack"]) + .optional() + .describe("The type of compose deployment."), + repository: z.string().nullable().optional().describe("Repository URL."), + owner: z.string().nullable().optional().describe("Repository owner."), + branch: z.string().nullable().optional().describe("Repository branch."), + autoDeploy: z + .boolean() + .nullable() + .optional() + .describe("Whether to auto-deploy on changes."), + 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."), + gitlabPathNamespace: z + .string() + .nullable() + .optional() + .describe("GitLab path namespace."), + 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."), + 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."), + customGitUrl: z.string().nullable().optional().describe("Custom Git URL."), + customGitBranch: z + .string() + .nullable() + .optional() + .describe("Custom Git branch."), + customGitSSHKeyId: z + .string() + .nullable() + .optional() + .describe("Custom Git SSH key ID."), + command: z.string().optional().describe("Command to run."), + enableSubmodules: z + .boolean() + .optional() + .describe("Whether to enable Git submodules."), + composePath: z + .string() + .min(1) + .optional() + .describe("Path to the compose file."), + suffix: z.string().optional().describe("Suffix for the compose project."), + randomize: z + .boolean() + .optional() + .describe("Whether to randomize the compose project names."), + isolatedDeployment: z + .boolean() + .optional() + .describe("Whether to use isolated deployment."), + isolatedDeploymentsVolume: z + .boolean() + .optional() + .describe("Whether to isolate deployment volumes."), + triggerType: z + .enum(["push", "tag"]) + .nullable() + .optional() + .describe("Trigger type for deployments."), + composeStatus: z + .enum(["idle", "running", "done", "error"]) + .optional() + .describe("Compose project status."), + environmentId: z.string().optional().describe("Environment ID."), + createdAt: z.string().optional().describe("Creation date."), + watchPaths: z + .array(z.string()) + .nullable() + .optional() + .describe("Paths to watch for changes."), + githubId: z + .string() + .nullable() + .optional() + .describe("GitHub integration ID."), + gitlabId: z + .string() + .nullable() + .optional() + .describe("GitLab integration ID."), + bitbucketId: z + .string() + .nullable() + .optional() + .describe("Bitbucket integration ID."), + giteaId: z.string().nullable().optional().describe("Gitea integration ID."), + }), + annotations: { + title: "Update Compose Project", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/compose.update", input); + + return ResponseFormatter.success( + `Compose project "${input.composeId}" updated successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/compose/index.ts b/src/mcp/tools/compose/index.ts new file mode 100644 index 0000000..07d4874 --- /dev/null +++ b/src/mcp/tools/compose/index.ts @@ -0,0 +1,26 @@ +export { composeCancelDeployment } from "./composeCancelDeployment.js"; +export { composeCleanQueues } from "./composeCleanQueues.js"; +export { composeCreate } from "./composeCreate.js"; +export { composeDelete } from "./composeDelete.js"; +export { composeDeploy } from "./composeDeploy.js"; +export { composeDeployTemplate } from "./composeDeployTemplate.js"; +export { composeDisconnectGitProvider } from "./composeDisconnectGitProvider.js"; +export { composeFetchSourceType } from "./composeFetchSourceType.js"; +export { composeGetConvertedCompose } from "./composeGetConvertedCompose.js"; +export { composeGetDefaultCommand } from "./composeGetDefaultCommand.js"; +export { composeGetTags } from "./composeGetTags.js"; +export { composeImport } from "./composeImport.js"; +export { composeIsolatedDeployment } from "./composeIsolatedDeployment.js"; +export { composeKillBuild } from "./composeKillBuild.js"; +export { composeLoadMountsByService } from "./composeLoadMountsByService.js"; +export { composeLoadServices } from "./composeLoadServices.js"; +export { composeMove } from "./composeMove.js"; +export { composeOne } from "./composeOne.js"; +export { composeProcessTemplate } from "./composeProcessTemplate.js"; +export { composeRandomizeCompose } from "./composeRandomizeCompose.js"; +export { composeRedeploy } from "./composeRedeploy.js"; +export { composeRefreshToken } from "./composeRefreshToken.js"; +export { composeStart } from "./composeStart.js"; +export { composeStop } from "./composeStop.js"; +export { composeTemplates } from "./composeTemplates.js"; +export { composeUpdate } from "./composeUpdate.js"; diff --git a/src/mcp/tools/database/databaseChangeStatus.ts b/src/mcp/tools/database/databaseChangeStatus.ts new file mode 100644 index 0000000..e961a44 --- /dev/null +++ b/src/mcp/tools/database/databaseChangeStatus.ts @@ -0,0 +1,62 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +const dbTypes = ["postgres", "mysql", "mongo", "mariadb", "redis"] as const; +type DbType = (typeof dbTypes)[number]; + +const dbTypeLabels: Record = { + postgres: "PostgreSQL", + mysql: "MySQL", + mongo: "MongoDB", + mariadb: "MariaDB", + redis: "Redis", +}; + +const idParamNames: Record = { + postgres: "postgresId", + mysql: "mysqlId", + mongo: "mongoId", + mariadb: "mariadbId", + redis: "redisId", +}; + +const applicationStatuses = ["idle", "running", "done", "error"] as const; + +export const databaseChangeStatus = createTool({ + name: "database-changeStatus", + description: + "Changes the application status of a database in Dokploy. This updates the status metadata without affecting the actual container state. Supports PostgreSQL, MySQL, MongoDB, MariaDB, and Redis.", + schema: z.object({ + dbType: z.enum(dbTypes).describe("The database type"), + id: z + .string() + .min(1) + .describe("The unique database ID to change status for"), + applicationStatus: z + .enum(applicationStatuses) + .describe("The new application status: idle (not running), running (actively processing), done (completed), or error (failed state)"), + }), + annotations: { + title: "Change Database Status", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const { dbType, id, applicationStatus } = input; + const label = dbTypeLabels[dbType]; + const idParam = idParamNames[dbType]; + + const response = await apiClient.post(`/${dbType}.changeStatus`, { + [idParam]: id, + applicationStatus, + }); + + return ResponseFormatter.success( + `${label} database "${id}" status changed to "${applicationStatus}" successfully`, + response.data + ); + }, +}); diff --git a/src/mcp/tools/database/databaseCreate.ts b/src/mcp/tools/database/databaseCreate.ts new file mode 100644 index 0000000..f471220 --- /dev/null +++ b/src/mcp/tools/database/databaseCreate.ts @@ -0,0 +1,146 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +const dbTypes = ["postgres", "mysql", "mongo", "mariadb", "redis"] as const; +type DbType = (typeof dbTypes)[number]; + +const dbTypeLabels: Record = { + postgres: "PostgreSQL", + mysql: "MySQL", + mongo: "MongoDB", + mariadb: "MariaDB", + redis: "Redis", +}; + +const defaultImages: Record = { + postgres: "postgres:15", + mysql: "mysql:8", + mongo: "mongo:15", + mariadb: "mariadb:6", + redis: "redis:8", +}; + +const passwordRegex = /^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/; + +export const databaseCreate = createTool({ + name: "database-create", + description: + "Creates a new database in Dokploy. Supports PostgreSQL, MySQL, MongoDB, MariaDB, and Redis. Each database type has different required fields - see parameter descriptions for details.", + schema: z.object({ + dbType: z.enum(dbTypes).describe("The database type to create"), + name: z + .string() + .min(1) + .describe("Display name for the database in Dokploy"), + appName: z + .string() + .min(1) + .describe("Application identifier used for container naming"), + environmentId: z + .string() + .describe("ID of the environment where the database will be created"), + // Required for postgres, mysql, mariadb - not used for mongo or redis + databaseName: z + .string() + .min(1) + .optional() + .describe( + "Name of the database to create inside the instance (required for postgres, mysql, mariadb)" + ), + // Required for postgres, mysql, mongo, mariadb - not used for redis + databaseUser: z + .string() + .min(1) + .optional() + .describe( + "Username for database access (required for postgres, mysql, mongo, mariadb)" + ), + // Required for all database types + databasePassword: z + .string() + .regex(passwordRegex, "Password contains invalid characters") + .describe( + "Password for database access (required for all database types)" + ), + // Required for mysql, mariadb only + databaseRootPassword: z + .string() + .regex(passwordRegex, "Root password contains invalid characters") + .optional() + .describe("Root password for administrative access (required for mysql, mariadb)"), + // MongoDB specific - optional with default false + replicaSets: z + .boolean() + .nullable() + .optional() + .default(false) + .describe("Enable MongoDB replica sets for high availability (MongoDB only)"), + dockerImage: z + .string() + .optional() + .describe("Docker image to use (defaults vary by database type)"), + description: z + .string() + .nullable() + .optional() + .describe("Optional description for the database"), + serverId: z + .string() + .nullable() + .optional() + .describe("ID of the server where the database will be deployed (null for default server)"), + }), + annotations: { + title: "Create Database", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const { dbType, ...rest } = input; + const label = dbTypeLabels[dbType]; + + // Build the request payload based on database type + const payload: Record = { + name: rest.name, + appName: rest.appName, + environmentId: rest.environmentId, + dockerImage: rest.dockerImage || defaultImages[dbType], + description: rest.description, + serverId: rest.serverId, + }; + + // Add type-specific fields + if (dbType === "postgres") { + payload.databaseName = rest.databaseName; + payload.databaseUser = rest.databaseUser; + payload.databasePassword = rest.databasePassword; + } + + if (dbType === "mysql" || dbType === "mariadb") { + payload.databaseName = rest.databaseName; + payload.databaseUser = rest.databaseUser; + payload.databasePassword = rest.databasePassword; + payload.databaseRootPassword = rest.databaseRootPassword; + } + + if (dbType === "mongo") { + payload.databaseUser = rest.databaseUser; + payload.databasePassword = rest.databasePassword; + payload.replicaSets = rest.replicaSets; + } + + if (dbType === "redis") { + payload.databasePassword = rest.databasePassword; + } + + const response = await apiClient.post(`/${dbType}.create`, payload); + + return ResponseFormatter.success( + `${label} database "${input.name}" created successfully`, + response.data + ); + }, +}); diff --git a/src/mcp/tools/database/databaseDeploy.ts b/src/mcp/tools/database/databaseDeploy.ts new file mode 100644 index 0000000..7a31afa --- /dev/null +++ b/src/mcp/tools/database/databaseDeploy.ts @@ -0,0 +1,56 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +const dbTypes = ["postgres", "mysql", "mongo", "mariadb", "redis"] as const; +type DbType = (typeof dbTypes)[number]; + +const dbTypeLabels: Record = { + postgres: "PostgreSQL", + mysql: "MySQL", + mongo: "MongoDB", + mariadb: "MariaDB", + redis: "Redis", +}; + +const idParamNames: Record = { + postgres: "postgresId", + mysql: "mysqlId", + mongo: "mongoId", + mariadb: "mariadbId", + redis: "redisId", +}; + +export const databaseDeploy = createTool({ + name: "database-deploy", + description: + "Deploys a database container in Dokploy. This pulls the Docker image and starts the container with the configured settings. Supports PostgreSQL, MySQL, MongoDB, MariaDB, and Redis.", + schema: z.object({ + dbType: z.enum(dbTypes).describe("The database type to deploy"), + id: z + .string() + .min(1) + .describe("The unique database ID to deploy"), + }), + annotations: { + title: "Deploy Database", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const { dbType, id } = input; + const label = dbTypeLabels[dbType]; + const idParam = idParamNames[dbType]; + + const response = await apiClient.post(`/${dbType}.deploy`, { + [idParam]: id, + }); + + return ResponseFormatter.success( + `${label} database "${id}" deployment started successfully`, + response.data + ); + }, +}); diff --git a/src/mcp/tools/database/databaseMove.ts b/src/mcp/tools/database/databaseMove.ts new file mode 100644 index 0000000..888fce8 --- /dev/null +++ b/src/mcp/tools/database/databaseMove.ts @@ -0,0 +1,61 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +const dbTypes = ["postgres", "mysql", "mongo", "mariadb", "redis"] as const; +type DbType = (typeof dbTypes)[number]; + +const dbTypeLabels: Record = { + postgres: "PostgreSQL", + mysql: "MySQL", + mongo: "MongoDB", + mariadb: "MariaDB", + redis: "Redis", +}; + +const idParamNames: Record = { + postgres: "postgresId", + mysql: "mysqlId", + mongo: "mongoId", + mariadb: "mariadbId", + redis: "redisId", +}; + +export const databaseMove = createTool({ + name: "database-move", + description: + "Moves a database to a different environment in Dokploy. This changes the environment association without affecting the running container. Supports PostgreSQL, MySQL, MongoDB, MariaDB, and Redis.", + schema: z.object({ + dbType: z.enum(dbTypes).describe("The database type to move"), + id: z + .string() + .min(1) + .describe("The unique database ID to move"), + targetEnvironmentId: z + .string() + .min(1) + .describe("The ID of the target environment to move the database to"), + }), + annotations: { + title: "Move Database", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const { dbType, id, targetEnvironmentId } = input; + const label = dbTypeLabels[dbType]; + const idParam = idParamNames[dbType]; + + const response = await apiClient.post(`/${dbType}.move`, { + [idParam]: id, + targetEnvironmentId, + }); + + return ResponseFormatter.success( + `${label} database "${id}" moved to environment "${targetEnvironmentId}" successfully`, + response.data + ); + }, +}); diff --git a/src/mcp/tools/database/databaseOne.ts b/src/mcp/tools/database/databaseOne.ts new file mode 100644 index 0000000..4baf570 --- /dev/null +++ b/src/mcp/tools/database/databaseOne.ts @@ -0,0 +1,63 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +const dbTypes = ["postgres", "mysql", "mongo", "mariadb", "redis"] as const; +type DbType = (typeof dbTypes)[number]; + +const dbTypeLabels: Record = { + postgres: "PostgreSQL", + mysql: "MySQL", + mongo: "MongoDB", + mariadb: "MariaDB", + redis: "Redis", +}; + +const idParamNames: Record = { + postgres: "postgresId", + mysql: "mysqlId", + mongo: "mongoId", + mariadb: "mariadbId", + redis: "redisId", +}; + +export const databaseOne = createTool({ + name: "database-one", + description: + "Retrieves detailed information about a specific database by ID. Returns configuration, status, and metadata for PostgreSQL, MySQL, MongoDB, MariaDB, or Redis databases.", + schema: z.object({ + dbType: z.enum(dbTypes).describe("The database type to retrieve"), + id: z + .string() + .min(1) + .describe("The unique database ID (e.g., postgresId, mysqlId, mongoId, mariadbId, or redisId)"), + }), + annotations: { + title: "Get Database", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const { dbType, id } = input; + const label = dbTypeLabels[dbType]; + const idParam = idParamNames[dbType]; + + const response = await apiClient.get(`/${dbType}.one`, { + params: { [idParam]: id }, + }); + + if (!response?.data) { + return ResponseFormatter.error( + `Failed to fetch ${label} database`, + `${label} database with ID "${id}" not found` + ); + } + + return ResponseFormatter.success( + `Successfully fetched ${label} database "${id}"`, + response.data + ); + }, +}); diff --git a/src/mcp/tools/database/databaseRebuild.ts b/src/mcp/tools/database/databaseRebuild.ts new file mode 100644 index 0000000..5e41b91 --- /dev/null +++ b/src/mcp/tools/database/databaseRebuild.ts @@ -0,0 +1,56 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +const dbTypes = ["postgres", "mysql", "mongo", "mariadb", "redis"] as const; +type DbType = (typeof dbTypes)[number]; + +const dbTypeLabels: Record = { + postgres: "PostgreSQL", + mysql: "MySQL", + mongo: "MongoDB", + mariadb: "MariaDB", + redis: "Redis", +}; + +const idParamNames: Record = { + postgres: "postgresId", + mysql: "mysqlId", + mongo: "mongoId", + mariadb: "mariadbId", + redis: "redisId", +}; + +export const databaseRebuild = createTool({ + name: "database-rebuild", + description: + "Rebuilds a database container in Dokploy. This stops the container, pulls the latest image, and recreates it with current configuration. Data in volumes is preserved. Supports PostgreSQL, MySQL, MongoDB, MariaDB, and Redis.", + schema: z.object({ + dbType: z.enum(dbTypes).describe("The database type to rebuild"), + id: z + .string() + .min(1) + .describe("The unique database ID to rebuild"), + }), + annotations: { + title: "Rebuild Database", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const { dbType, id } = input; + const label = dbTypeLabels[dbType]; + const idParam = idParamNames[dbType]; + + const response = await apiClient.post(`/${dbType}.rebuild`, { + [idParam]: id, + }); + + return ResponseFormatter.success( + `${label} database "${id}" rebuild started successfully`, + response.data + ); + }, +}); diff --git a/src/mcp/tools/database/databaseReload.ts b/src/mcp/tools/database/databaseReload.ts new file mode 100644 index 0000000..1bf8881 --- /dev/null +++ b/src/mcp/tools/database/databaseReload.ts @@ -0,0 +1,61 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +const dbTypes = ["postgres", "mysql", "mongo", "mariadb", "redis"] as const; +type DbType = (typeof dbTypes)[number]; + +const dbTypeLabels: Record = { + postgres: "PostgreSQL", + mysql: "MySQL", + mongo: "MongoDB", + mariadb: "MariaDB", + redis: "Redis", +}; + +const idParamNames: Record = { + postgres: "postgresId", + mysql: "mysqlId", + mongo: "mongoId", + mariadb: "mariadbId", + redis: "redisId", +}; + +export const databaseReload = createTool({ + name: "database-reload", + description: + "Reloads a database container in Dokploy by restarting the Docker service. This applies configuration changes without a full rebuild. Supports PostgreSQL, MySQL, MongoDB, MariaDB, and Redis.", + schema: z.object({ + dbType: z.enum(dbTypes).describe("The database type to reload"), + id: z + .string() + .min(1) + .describe("The unique database ID to reload"), + appName: z + .string() + .min(1) + .describe("The application name of the database (container identifier)"), + }), + annotations: { + title: "Reload Database", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const { dbType, id, appName } = input; + const label = dbTypeLabels[dbType]; + const idParam = idParamNames[dbType]; + + const response = await apiClient.post(`/${dbType}.reload`, { + [idParam]: id, + appName, + }); + + return ResponseFormatter.success( + `${label} database "${id}" reloaded successfully`, + response.data + ); + }, +}); diff --git a/src/mcp/tools/database/databaseRemove.ts b/src/mcp/tools/database/databaseRemove.ts new file mode 100644 index 0000000..ae5964f --- /dev/null +++ b/src/mcp/tools/database/databaseRemove.ts @@ -0,0 +1,56 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +const dbTypes = ["postgres", "mysql", "mongo", "mariadb", "redis"] as const; +type DbType = (typeof dbTypes)[number]; + +const dbTypeLabels: Record = { + postgres: "PostgreSQL", + mysql: "MySQL", + mongo: "MongoDB", + mariadb: "MariaDB", + redis: "Redis", +}; + +const idParamNames: Record = { + postgres: "postgresId", + mysql: "mysqlId", + mongo: "mongoId", + mariadb: "mariadbId", + redis: "redisId", +}; + +export const databaseRemove = createTool({ + name: "database-remove", + description: + "Permanently deletes a database from Dokploy. This action cannot be undone and will remove the database container and all associated data. Supports PostgreSQL, MySQL, MongoDB, MariaDB, and Redis.", + schema: z.object({ + dbType: z.enum(dbTypes).describe("The database type to remove"), + id: z + .string() + .min(1) + .describe("The unique database ID to remove"), + }), + annotations: { + title: "Remove Database", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const { dbType, id } = input; + const label = dbTypeLabels[dbType]; + const idParam = idParamNames[dbType]; + + const response = await apiClient.post(`/${dbType}.remove`, { + [idParam]: id, + }); + + return ResponseFormatter.success( + `${label} database "${id}" removed successfully`, + response.data + ); + }, +}); diff --git a/src/mcp/tools/database/databaseSaveEnvironment.ts b/src/mcp/tools/database/databaseSaveEnvironment.ts new file mode 100644 index 0000000..52e8b0d --- /dev/null +++ b/src/mcp/tools/database/databaseSaveEnvironment.ts @@ -0,0 +1,62 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +const dbTypes = ["postgres", "mysql", "mongo", "mariadb", "redis"] as const; +type DbType = (typeof dbTypes)[number]; + +const dbTypeLabels: Record = { + postgres: "PostgreSQL", + mysql: "MySQL", + mongo: "MongoDB", + mariadb: "MariaDB", + redis: "Redis", +}; + +const idParamNames: Record = { + postgres: "postgresId", + mysql: "mysqlId", + mongo: "mongoId", + mariadb: "mariadbId", + redis: "redisId", +}; + +export const databaseSaveEnvironment = createTool({ + name: "database-saveEnvironment", + description: + "Saves environment variables for a database in Dokploy. Environment variables are passed to the container at runtime. Set to null to clear all environment variables. Supports PostgreSQL, MySQL, MongoDB, MariaDB, and Redis.", + schema: z.object({ + dbType: z.enum(dbTypes).describe("The database type"), + id: z + .string() + .min(1) + .describe("The unique database ID to configure"), + env: z + .string() + .nullable() + .optional() + .describe("Environment variables as a string (KEY=value format, one per line), or null to clear"), + }), + annotations: { + title: "Save Database Environment", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const { dbType, id, env } = input; + const label = dbTypeLabels[dbType]; + const idParam = idParamNames[dbType]; + + const response = await apiClient.post(`/${dbType}.saveEnvironment`, { + [idParam]: id, + env, + }); + + return ResponseFormatter.success( + `Environment variables for ${label} database "${id}" saved successfully`, + response.data + ); + }, +}); diff --git a/src/mcp/tools/database/databaseSaveExternalPort.ts b/src/mcp/tools/database/databaseSaveExternalPort.ts new file mode 100644 index 0000000..c719319 --- /dev/null +++ b/src/mcp/tools/database/databaseSaveExternalPort.ts @@ -0,0 +1,66 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +const dbTypes = ["postgres", "mysql", "mongo", "mariadb", "redis"] as const; +type DbType = (typeof dbTypes)[number]; + +const dbTypeLabels: Record = { + postgres: "PostgreSQL", + mysql: "MySQL", + mongo: "MongoDB", + mariadb: "MariaDB", + redis: "Redis", +}; + +const idParamNames: Record = { + postgres: "postgresId", + mysql: "mysqlId", + mongo: "mongoId", + mariadb: "mariadbId", + redis: "redisId", +}; + +export const databaseSaveExternalPort = createTool({ + name: "database-saveExternalPort", + description: + "Saves the external port configuration for a database in Dokploy. This exposes the database on the specified port on the host machine. Set to null to disable external access. Supports PostgreSQL, MySQL, MongoDB, MariaDB, and Redis.", + schema: z.object({ + dbType: z.enum(dbTypes).describe("The database type"), + id: z + .string() + .min(1) + .describe("The unique database ID to configure"), + externalPort: z + .number() + .nullable() + .describe("The external port number to expose the database on (1-65535), or null to disable external access"), + }), + annotations: { + title: "Save Database External Port", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const { dbType, id, externalPort } = input; + const label = dbTypeLabels[dbType]; + const idParam = idParamNames[dbType]; + + const response = await apiClient.post(`/${dbType}.saveExternalPort`, { + [idParam]: id, + externalPort, + }); + + const portMessage = + externalPort !== null + ? `set to port ${externalPort}` + : "disabled (set to null)"; + + return ResponseFormatter.success( + `External port for ${label} database "${id}" ${portMessage} successfully`, + response.data + ); + }, +}); diff --git a/src/mcp/tools/database/databaseStart.ts b/src/mcp/tools/database/databaseStart.ts new file mode 100644 index 0000000..08dc6e2 --- /dev/null +++ b/src/mcp/tools/database/databaseStart.ts @@ -0,0 +1,56 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +const dbTypes = ["postgres", "mysql", "mongo", "mariadb", "redis"] as const; +type DbType = (typeof dbTypes)[number]; + +const dbTypeLabels: Record = { + postgres: "PostgreSQL", + mysql: "MySQL", + mongo: "MongoDB", + mariadb: "MariaDB", + redis: "Redis", +}; + +const idParamNames: Record = { + postgres: "postgresId", + mysql: "mysqlId", + mongo: "mongoId", + mariadb: "mariadbId", + redis: "redisId", +}; + +export const databaseStart = createTool({ + name: "database-start", + description: + "Starts a stopped database container in Dokploy. The database must already be deployed. Supports PostgreSQL, MySQL, MongoDB, MariaDB, and Redis.", + schema: z.object({ + dbType: z.enum(dbTypes).describe("The database type to start"), + id: z + .string() + .min(1) + .describe("The unique database ID to start"), + }), + annotations: { + title: "Start Database", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const { dbType, id } = input; + const label = dbTypeLabels[dbType]; + const idParam = idParamNames[dbType]; + + const response = await apiClient.post(`/${dbType}.start`, { + [idParam]: id, + }); + + return ResponseFormatter.success( + `${label} database "${id}" started successfully`, + response.data + ); + }, +}); diff --git a/src/mcp/tools/database/databaseStop.ts b/src/mcp/tools/database/databaseStop.ts new file mode 100644 index 0000000..023e30d --- /dev/null +++ b/src/mcp/tools/database/databaseStop.ts @@ -0,0 +1,56 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +const dbTypes = ["postgres", "mysql", "mongo", "mariadb", "redis"] as const; +type DbType = (typeof dbTypes)[number]; + +const dbTypeLabels: Record = { + postgres: "PostgreSQL", + mysql: "MySQL", + mongo: "MongoDB", + mariadb: "MariaDB", + redis: "Redis", +}; + +const idParamNames: Record = { + postgres: "postgresId", + mysql: "mysqlId", + mongo: "mongoId", + mariadb: "mariadbId", + redis: "redisId", +}; + +export const databaseStop = createTool({ + name: "database-stop", + description: + "Stops a running database container in Dokploy. The container will be stopped but not removed. Use database-start to restart it. Supports PostgreSQL, MySQL, MongoDB, MariaDB, and Redis.", + schema: z.object({ + dbType: z.enum(dbTypes).describe("The database type to stop"), + id: z + .string() + .min(1) + .describe("The unique database ID to stop"), + }), + annotations: { + title: "Stop Database", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const { dbType, id } = input; + const label = dbTypeLabels[dbType]; + const idParam = idParamNames[dbType]; + + const response = await apiClient.post(`/${dbType}.stop`, { + [idParam]: id, + }); + + return ResponseFormatter.success( + `${label} database "${id}" stopped successfully`, + response.data + ); + }, +}); diff --git a/src/mcp/tools/database/databaseUpdate.ts b/src/mcp/tools/database/databaseUpdate.ts new file mode 100644 index 0000000..e9e9463 --- /dev/null +++ b/src/mcp/tools/database/databaseUpdate.ts @@ -0,0 +1,391 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +const dbTypes = ["postgres", "mysql", "mongo", "mariadb", "redis"] as const; +type DbType = (typeof dbTypes)[number]; + +const dbTypeLabels: Record = { + postgres: "PostgreSQL", + mysql: "MySQL", + mongo: "MongoDB", + mariadb: "MariaDB", + redis: "Redis", +}; + +const idParamNames: Record = { + postgres: "postgresId", + mysql: "mysqlId", + mongo: "mongoId", + mariadb: "mariadbId", + redis: "redisId", +}; + +const defaultImages: Record = { + postgres: "postgres:15", + mysql: "mysql:8", + mongo: "mongo:15", + mariadb: "mariadb:6", + redis: "redis:8", +}; + +const passwordRegex = /^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/; + +// Docker Swarm configuration schemas matching OpenAPI spec +const healthCheckSwarmSchema = 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"); + +const restartPolicySwarmSchema = 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"); + +const placementSwarmSchema = 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 constraints and preferences"); + +const updateConfigSwarmSchema = 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 rolling update configuration"); + +const rollbackConfigSwarmSchema = 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"); + +const modeSwarmSchema = 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 service mode (Replicated, Global, ReplicatedJob, GlobalJob)"); + +const labelsSwarmSchema = z + .record(z.string()) + .nullable() + .optional() + .describe("Docker Swarm service labels as key-value pairs"); + +const networkSwarmSchema = z + .array( + z.object({ + Target: z.string().optional(), + Aliases: z.array(z.string()).optional(), + DriverOpts: z.object({}).optional(), + }) + ) + .nullable() + .optional() + .describe("Docker Swarm network attachment configuration"); + +const endpointSpecSwarmSchema = z + .object({ + Mode: z.string().optional(), + Ports: z + .array( + z.object({ + Protocol: z.string().optional(), + TargetPort: z.number().optional(), + PublishedPort: z.number().optional(), + PublishMode: z.string().optional(), + }) + ) + .optional(), + }) + .nullable() + .optional() + .describe("Docker Swarm endpoint specification for port publishing"); + +export const databaseUpdate = createTool({ + name: "database-update", + description: + "Updates an existing database in Dokploy. Supports PostgreSQL, MySQL, MongoDB, MariaDB, and Redis. Only the database ID is required - all other fields are optional.", + schema: z.object({ + dbType: z.enum(dbTypes).describe("The database type"), + id: z.string().min(1).describe("The database ID to update"), + // Common fields across all database types + name: z + .string() + .min(1) + .optional() + .describe("Display name for the database in Dokploy"), + appName: z + .string() + .min(1) + .optional() + .describe("Application identifier used for container naming"), + description: z + .string() + .nullable() + .optional() + .describe("Description for the database"), + dockerImage: z + .string() + .optional() + .describe("Docker image to use for the database"), + command: z + .string() + .nullable() + .optional() + .describe("Custom command to run the database container"), + env: z + .string() + .nullable() + .optional() + .describe("Environment variables as a string (KEY=value format, one per line)"), + memoryReservation: z + .string() + .nullable() + .optional() + .describe("Memory reservation for the container (e.g., '256m', '1g')"), + memoryLimit: z + .string() + .nullable() + .optional() + .describe("Memory limit for the container (e.g., '512m', '2g')"), + cpuReservation: z + .string() + .nullable() + .optional() + .describe("CPU reservation for the container (e.g., '0.5', '1')"), + cpuLimit: z + .string() + .nullable() + .optional() + .describe("CPU limit for the container (e.g., '1', '2')"), + externalPort: z + .number() + .nullable() + .optional() + .describe("External port to expose the database on the host"), + applicationStatus: z + .enum(["idle", "running", "done", "error"]) + .optional() + .describe("Current application status"), + replicas: z + .number() + .optional() + .describe("Number of container replicas to run"), + createdAt: z + .string() + .optional() + .describe("Creation timestamp (ISO 8601 format)"), + environmentId: z + .string() + .optional() + .describe("ID of the environment the database belongs to"), + // Type-specific fields for postgres, mysql, mariadb + databaseName: z + .string() + .min(1) + .optional() + .describe("Database name inside the instance (postgres, mysql, mariadb)"), + // Type-specific fields for postgres, mysql, mongo, mariadb + databaseUser: z + .string() + .min(1) + .optional() + .describe("Database username (postgres, mysql, mongo, mariadb)"), + databasePassword: z + .string() + .regex(passwordRegex, "Password contains invalid characters") + .optional() + .describe("Database password"), + // Type-specific fields for mysql, mariadb + databaseRootPassword: z + .string() + .regex(passwordRegex, "Root password contains invalid characters") + .optional() + .describe("Root password (mysql, mariadb only)"), + // MongoDB specific + replicaSets: z + .boolean() + .nullable() + .optional() + .describe("Enable replica sets for high availability (MongoDB only)"), + // Docker Swarm configurations + healthCheckSwarm: healthCheckSwarmSchema, + restartPolicySwarm: restartPolicySwarmSchema, + placementSwarm: placementSwarmSchema, + updateConfigSwarm: updateConfigSwarmSchema, + rollbackConfigSwarm: rollbackConfigSwarmSchema, + modeSwarm: modeSwarmSchema, + labelsSwarm: labelsSwarmSchema, + networkSwarm: networkSwarmSchema, + stopGracePeriodSwarm: z + .number() + .int() + .nullable() + .optional() + .describe("Docker Swarm stop grace period in nanoseconds"), + endpointSpecSwarm: endpointSpecSwarmSchema, + }), + annotations: { + title: "Update Database", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const { dbType, id, ...rest } = input; + const label = dbTypeLabels[dbType]; + const idParam = idParamNames[dbType]; + + // Build payload with the correct ID parameter name + const payload: Record = { + [idParam]: id, + }; + + // Add common fields if provided + if (rest.name !== undefined) payload.name = rest.name; + if (rest.appName !== undefined) payload.appName = rest.appName; + if (rest.description !== undefined) payload.description = rest.description; + if (rest.dockerImage !== undefined) + payload.dockerImage = rest.dockerImage || defaultImages[dbType]; + if (rest.command !== undefined) payload.command = rest.command; + if (rest.env !== undefined) payload.env = rest.env; + if (rest.memoryReservation !== undefined) + payload.memoryReservation = rest.memoryReservation; + if (rest.memoryLimit !== undefined) payload.memoryLimit = rest.memoryLimit; + if (rest.cpuReservation !== undefined) + payload.cpuReservation = rest.cpuReservation; + if (rest.cpuLimit !== undefined) payload.cpuLimit = rest.cpuLimit; + if (rest.externalPort !== undefined) payload.externalPort = rest.externalPort; + if (rest.applicationStatus !== undefined) + payload.applicationStatus = rest.applicationStatus; + if (rest.replicas !== undefined) payload.replicas = rest.replicas; + if (rest.createdAt !== undefined) payload.createdAt = rest.createdAt; + if (rest.environmentId !== undefined) + payload.environmentId = rest.environmentId; + + // Add type-specific fields + if ( + dbType === "postgres" || + dbType === "mysql" || + dbType === "mariadb" + ) { + if (rest.databaseName !== undefined) + payload.databaseName = rest.databaseName; + } + + if ( + dbType === "postgres" || + dbType === "mysql" || + dbType === "mongo" || + dbType === "mariadb" + ) { + if (rest.databaseUser !== undefined) + payload.databaseUser = rest.databaseUser; + if (rest.databasePassword !== undefined) + payload.databasePassword = rest.databasePassword; + } + + if (dbType === "mysql" || dbType === "mariadb") { + if (rest.databaseRootPassword !== undefined) + payload.databaseRootPassword = rest.databaseRootPassword; + } + + if (dbType === "mongo") { + if (rest.replicaSets !== undefined) payload.replicaSets = rest.replicaSets; + } + + if (dbType === "redis") { + if (rest.databasePassword !== undefined) + payload.databasePassword = rest.databasePassword; + } + + // Add Swarm configurations if provided + if (rest.healthCheckSwarm !== undefined) + payload.healthCheckSwarm = rest.healthCheckSwarm; + if (rest.restartPolicySwarm !== undefined) + payload.restartPolicySwarm = rest.restartPolicySwarm; + if (rest.placementSwarm !== undefined) + payload.placementSwarm = rest.placementSwarm; + if (rest.updateConfigSwarm !== undefined) + payload.updateConfigSwarm = rest.updateConfigSwarm; + if (rest.rollbackConfigSwarm !== undefined) + payload.rollbackConfigSwarm = rest.rollbackConfigSwarm; + if (rest.modeSwarm !== undefined) payload.modeSwarm = rest.modeSwarm; + if (rest.labelsSwarm !== undefined) payload.labelsSwarm = rest.labelsSwarm; + if (rest.networkSwarm !== undefined) payload.networkSwarm = rest.networkSwarm; + if (rest.stopGracePeriodSwarm !== undefined) + payload.stopGracePeriodSwarm = rest.stopGracePeriodSwarm; + if (rest.endpointSpecSwarm !== undefined) + payload.endpointSpecSwarm = rest.endpointSpecSwarm; + + const response = await apiClient.post(`/${dbType}.update`, payload); + + return ResponseFormatter.success( + `${label} database "${id}" updated successfully`, + response.data + ); + }, +}); diff --git a/src/mcp/tools/database/index.ts b/src/mcp/tools/database/index.ts new file mode 100644 index 0000000..82318d8 --- /dev/null +++ b/src/mcp/tools/database/index.ts @@ -0,0 +1,13 @@ +export { databaseCreate } from "./databaseCreate.js"; +export { databaseOne } from "./databaseOne.js"; +export { databaseUpdate } from "./databaseUpdate.js"; +export { databaseRemove } from "./databaseRemove.js"; +export { databaseStart } from "./databaseStart.js"; +export { databaseStop } from "./databaseStop.js"; +export { databaseDeploy } from "./databaseDeploy.js"; +export { databaseReload } from "./databaseReload.js"; +export { databaseRebuild } from "./databaseRebuild.js"; +export { databaseMove } from "./databaseMove.js"; +export { databaseChangeStatus } from "./databaseChangeStatus.js"; +export { databaseSaveExternalPort } from "./databaseSaveExternalPort.js"; +export { databaseSaveEnvironment } from "./databaseSaveEnvironment.js"; diff --git a/src/mcp/tools/deployment/deploymentAll.ts b/src/mcp/tools/deployment/deploymentAll.ts new file mode 100644 index 0000000..a8404e0 --- /dev/null +++ b/src/mcp/tools/deployment/deploymentAll.ts @@ -0,0 +1,31 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const deploymentAll = createTool({ + name: "deployment-all", + description: "Gets all deployments for a specific application in Dokploy.", + schema: z.object({ + applicationId: z + .string() + .min(1) + .describe("The ID of the application to get deployments for."), + }), + annotations: { + title: "List Application Deployments", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.get( + `/deployment.all?applicationId=${input.applicationId}` + ); + + return ResponseFormatter.success( + `Successfully fetched deployments for application "${input.applicationId}"`, + response.data + ); + }, +}); diff --git a/src/mcp/tools/deployment/deploymentAllByCompose.ts b/src/mcp/tools/deployment/deploymentAllByCompose.ts new file mode 100644 index 0000000..2c5bb22 --- /dev/null +++ b/src/mcp/tools/deployment/deploymentAllByCompose.ts @@ -0,0 +1,31 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const deploymentAllByCompose = createTool({ + name: "deployment-all-by-compose", + description: "Gets all deployments for a specific compose project in Dokploy.", + schema: z.object({ + composeId: z + .string() + .min(1) + .describe("The ID of the compose project to get deployments for."), + }), + annotations: { + title: "List Compose Deployments", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.get( + `/deployment.allByCompose?composeId=${input.composeId}` + ); + + return ResponseFormatter.success( + `Successfully fetched deployments for compose "${input.composeId}"`, + response.data + ); + }, +}); diff --git a/src/mcp/tools/deployment/deploymentAllByServer.ts b/src/mcp/tools/deployment/deploymentAllByServer.ts new file mode 100644 index 0000000..99bc360 --- /dev/null +++ b/src/mcp/tools/deployment/deploymentAllByServer.ts @@ -0,0 +1,31 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const deploymentAllByServer = createTool({ + name: "deployment-all-by-server", + description: "Gets all deployments for a specific server in Dokploy.", + schema: z.object({ + serverId: z + .string() + .min(1) + .describe("The ID of the server to get deployments for."), + }), + annotations: { + title: "List Server Deployments", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.get( + `/deployment.allByServer?serverId=${input.serverId}` + ); + + return ResponseFormatter.success( + `Successfully fetched deployments for server "${input.serverId}"`, + response.data + ); + }, +}); diff --git a/src/mcp/tools/deployment/deploymentAllByType.ts b/src/mcp/tools/deployment/deploymentAllByType.ts new file mode 100644 index 0000000..c62ce18 --- /dev/null +++ b/src/mcp/tools/deployment/deploymentAllByType.ts @@ -0,0 +1,42 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const deploymentAllByType = createTool({ + name: "deployment-all-by-type", + description: "Gets all deployments filtered by type in Dokploy.", + schema: z.object({ + id: z + .string() + .min(1) + .describe("The ID of the resource to get deployments for."), + type: z + .enum([ + "application", + "compose", + "server", + "schedule", + "previewDeployment", + "backup", + "volumeBackup", + ]) + .describe("The type of deployments to retrieve."), + }), + annotations: { + title: "List Deployments By Type", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.get( + `/deployment.allByType?id=${input.id}&type=${input.type}` + ); + + return ResponseFormatter.success( + `Successfully fetched ${input.type} deployments for ID "${input.id}"`, + response.data + ); + }, +}); diff --git a/src/mcp/tools/deployment/deploymentKillProcess.ts b/src/mcp/tools/deployment/deploymentKillProcess.ts new file mode 100644 index 0000000..e041ac6 --- /dev/null +++ b/src/mcp/tools/deployment/deploymentKillProcess.ts @@ -0,0 +1,29 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const deploymentKillProcess = createTool({ + name: "deployment-kill-process", + description: "Kills/cancels a running deployment process in Dokploy.", + schema: z.object({ + deploymentId: z + .string() + .min(1) + .describe("The ID of the deployment to kill."), + }), + annotations: { + title: "Kill Deployment Process", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/deployment.killProcess", input); + + return ResponseFormatter.success( + `Deployment "${input.deploymentId}" process killed successfully`, + response.data + ); + }, +}); diff --git a/src/mcp/tools/deployment/index.ts b/src/mcp/tools/deployment/index.ts new file mode 100644 index 0000000..f797979 --- /dev/null +++ b/src/mcp/tools/deployment/index.ts @@ -0,0 +1,5 @@ +export { deploymentAll } from "./deploymentAll.js"; +export { deploymentAllByCompose } from "./deploymentAllByCompose.js"; +export { deploymentAllByServer } from "./deploymentAllByServer.js"; +export { deploymentAllByType } from "./deploymentAllByType.js"; +export { deploymentKillProcess } from "./deploymentKillProcess.js"; diff --git a/src/mcp/tools/destination/destinationAll.ts b/src/mcp/tools/destination/destinationAll.ts new file mode 100644 index 0000000..7a15aa0 --- /dev/null +++ b/src/mcp/tools/destination/destinationAll.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const destinationAll = createTool({ + name: "destination-all", + description: "Gets all backup destinations in Dokploy.", + schema: z.object({}), + annotations: { + title: "List All Destinations", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const destinations = await apiClient.get("/destination.all"); + + return ResponseFormatter.success( + "Successfully fetched all destinations", + destinations.data, + ); + }, +}); diff --git a/src/mcp/tools/destination/destinationCreate.ts b/src/mcp/tools/destination/destinationCreate.ts new file mode 100644 index 0000000..5ea7a46 --- /dev/null +++ b/src/mcp/tools/destination/destinationCreate.ts @@ -0,0 +1,57 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const destinationCreate = createTool({ + name: "destination-create", + description: + "Creates a new backup destination (S3-compatible storage) in Dokploy.", + schema: z.object({ + name: z + .string() + .min(1) + .describe("Display name for the backup destination. Required."), + provider: z + .string() + .nullable() + .describe( + "Cloud provider name (e.g., 'aws', 'minio', 'backblaze', 'cloudflare'). Can be null for generic S3. Required." + ), + accessKey: z + .string() + .describe("S3-compatible access key ID for authentication. Required."), + secretAccessKey: z + .string() + .describe("S3-compatible secret access key for authentication. Required."), + bucket: z + .string() + .describe("S3 bucket name where backups will be stored. Required."), + region: z + .string() + .describe("S3 region (e.g., 'us-east-1', 'eu-west-1'). Required."), + endpoint: z + .string() + .describe( + "S3-compatible endpoint URL (e.g., 'https://s3.amazonaws.com' or 'https://minio.example.com'). Required." + ), + serverId: z + .string() + .optional() + .describe("Server ID for remote server destinations. Uses local server if not specified."), + }), + annotations: { + title: "Create Backup Destination", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/destination.create", input); + + return ResponseFormatter.success( + `Backup destination "${input.name}" created successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/destination/destinationOne.ts b/src/mcp/tools/destination/destinationOne.ts new file mode 100644 index 0000000..8e2bd1a --- /dev/null +++ b/src/mcp/tools/destination/destinationOne.ts @@ -0,0 +1,37 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const destinationOne = createTool({ + name: "destination-one", + description: "Gets a specific backup destination by its ID in Dokploy.", + schema: z.object({ + destinationId: z + .string() + .describe("The unique identifier of the backup destination to retrieve. Required."), + }), + annotations: { + title: "Get Destination Details", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const destination = await apiClient.get( + `/destination.one?destinationId=${input.destinationId}`, + ); + + if (!destination?.data) { + return ResponseFormatter.error( + "Failed to fetch destination", + `Destination with ID "${input.destinationId}" not found`, + ); + } + + return ResponseFormatter.success( + `Successfully fetched destination "${input.destinationId}"`, + destination.data, + ); + }, +}); diff --git a/src/mcp/tools/destination/destinationRemove.ts b/src/mcp/tools/destination/destinationRemove.ts new file mode 100644 index 0000000..c6939e0 --- /dev/null +++ b/src/mcp/tools/destination/destinationRemove.ts @@ -0,0 +1,28 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const destinationRemove = createTool({ + name: "destination-remove", + description: "Removes/deletes a backup destination from Dokploy.", + schema: z.object({ + destinationId: z + .string() + .describe("The unique identifier of the backup destination to remove. Required."), + }), + annotations: { + title: "Remove Destination", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/destination.remove", input); + + return ResponseFormatter.success( + `Destination "${input.destinationId}" removed successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/destination/destinationTestConnection.ts b/src/mcp/tools/destination/destinationTestConnection.ts new file mode 100644 index 0000000..d03e9c1 --- /dev/null +++ b/src/mcp/tools/destination/destinationTestConnection.ts @@ -0,0 +1,56 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const destinationTestConnection = createTool({ + name: "destination-test-connection", + description: "Tests the connection to a backup destination in Dokploy.", + schema: z.object({ + name: z + .string() + .min(1) + .describe("Display name for the backup destination. Required."), + provider: z + .string() + .nullable() + .describe( + "Cloud provider name (e.g., 'aws', 'minio', 'backblaze', 'cloudflare'). Can be null for generic S3. Required." + ), + accessKey: z + .string() + .describe("S3-compatible access key ID for authentication. Required."), + secretAccessKey: z + .string() + .describe("S3-compatible secret access key for authentication. Required."), + bucket: z + .string() + .describe("S3 bucket name to test connectivity to. Required."), + region: z + .string() + .describe("S3 region (e.g., 'us-east-1', 'eu-west-1'). Required."), + endpoint: z + .string() + .describe( + "S3-compatible endpoint URL to connect to (e.g., 'https://s3.amazonaws.com'). Required." + ), + serverId: z + .string() + .optional() + .describe("Server ID for remote server destinations. Uses local server if not specified."), + }), + annotations: { + title: "Test Destination Connection", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/destination.testConnection", input); + + return ResponseFormatter.success( + "Destination connection test completed successfully", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/destination/destinationUpdate.ts b/src/mcp/tools/destination/destinationUpdate.ts new file mode 100644 index 0000000..e692f05 --- /dev/null +++ b/src/mcp/tools/destination/destinationUpdate.ts @@ -0,0 +1,59 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const destinationUpdate = createTool({ + name: "destination-update", + description: "Updates an existing backup destination in Dokploy.", + schema: z.object({ + destinationId: z + .string() + .describe("The ID of the destination to update. Required."), + name: z + .string() + .min(1) + .describe("Display name for the backup destination. Required."), + provider: z + .string() + .nullable() + .describe( + "Cloud provider name (e.g., 'aws', 'minio', 'backblaze', 'cloudflare'). Can be null for generic S3. Required." + ), + accessKey: z + .string() + .describe("S3-compatible access key ID for authentication. Required."), + secretAccessKey: z + .string() + .describe("S3-compatible secret access key for authentication. Required."), + bucket: z + .string() + .describe("S3 bucket name where backups will be stored. Required."), + region: z + .string() + .describe("S3 region (e.g., 'us-east-1', 'eu-west-1'). Required."), + endpoint: z + .string() + .describe( + "S3-compatible endpoint URL (e.g., 'https://s3.amazonaws.com' or 'https://minio.example.com'). Required." + ), + serverId: z + .string() + .optional() + .describe("Server ID for remote server destinations. Uses local server if not specified."), + }), + annotations: { + title: "Update Destination", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/destination.update", input); + + return ResponseFormatter.success( + `Destination "${input.destinationId}" updated successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/destination/index.ts b/src/mcp/tools/destination/index.ts new file mode 100644 index 0000000..f6f2dcb --- /dev/null +++ b/src/mcp/tools/destination/index.ts @@ -0,0 +1,6 @@ +export { destinationCreate } from "./destinationCreate.js"; +export { destinationTestConnection } from "./destinationTestConnection.js"; +export { destinationOne } from "./destinationOne.js"; +export { destinationAll } from "./destinationAll.js"; +export { destinationRemove } from "./destinationRemove.js"; +export { destinationUpdate } from "./destinationUpdate.js"; diff --git a/src/mcp/tools/docker/dockerGetConfig.ts b/src/mcp/tools/docker/dockerGetConfig.ts new file mode 100644 index 0000000..25bd124 --- /dev/null +++ b/src/mcp/tools/docker/dockerGetConfig.ts @@ -0,0 +1,42 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const dockerGetConfig = createTool({ + name: "docker-get-config", + description: "Gets the configuration of a Docker container in Dokploy.", + schema: z.object({ + containerId: z + .string() + .min(1) + .regex(/^[a-zA-Z0-9.\-_]+$/) + .describe("The ID of the container to get configuration for."), + serverId: z + .string() + .optional() + .describe("The ID of the server where the container is running."), + }), + annotations: { + title: "Get Docker Container Config", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const params = new URLSearchParams(); + params.append("containerId", input.containerId); + if (input.serverId) { + params.append("serverId", input.serverId); + } + + const response = await apiClient.get( + `/docker.getConfig?${params.toString()}`, + ); + + return ResponseFormatter.success( + `Successfully fetched config for container "${input.containerId}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/docker/dockerGetContainers.ts b/src/mcp/tools/docker/dockerGetContainers.ts new file mode 100644 index 0000000..1624506 --- /dev/null +++ b/src/mcp/tools/docker/dockerGetContainers.ts @@ -0,0 +1,37 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const dockerGetContainers = createTool({ + name: "docker-get-containers", + description: "Gets all Docker containers from Dokploy.", + schema: z.object({ + serverId: z + .string() + .optional() + .describe("The ID of the server to get containers from."), + }), + annotations: { + title: "Get Docker Containers", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const params = new URLSearchParams(); + if (input.serverId) { + params.append("serverId", input.serverId); + } + + const queryString = params.toString(); + const url = `/docker.getContainers${queryString ? `?${queryString}` : ""}`; + + const response = await apiClient.get(url); + + return ResponseFormatter.success( + "Successfully fetched Docker containers", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/docker/dockerGetContainersByAppLabel.ts b/src/mcp/tools/docker/dockerGetContainersByAppLabel.ts new file mode 100644 index 0000000..0e35b01 --- /dev/null +++ b/src/mcp/tools/docker/dockerGetContainersByAppLabel.ts @@ -0,0 +1,46 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const dockerGetContainersByAppLabel = createTool({ + name: "docker-get-containers-by-app-label", + description: "Gets Docker containers by app label in Dokploy.", + schema: z.object({ + appName: z + .string() + .min(1) + .regex(/^[a-zA-Z0-9.\-_]+$/) + .describe("The app name to search for."), + type: z + .enum(["standalone", "swarm"]) + .describe("The deployment type (standalone or swarm)."), + serverId: z + .string() + .optional() + .describe("The ID of the server to search on."), + }), + annotations: { + title: "Get Docker Containers by App Label", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const params = new URLSearchParams(); + params.append("appName", input.appName); + params.append("type", input.type); + if (input.serverId) { + params.append("serverId", input.serverId); + } + + const response = await apiClient.get( + `/docker.getContainersByAppLabel?${params.toString()}`, + ); + + return ResponseFormatter.success( + `Successfully fetched containers for app "${input.appName}" with type "${input.type}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/docker/dockerGetContainersByAppNameMatch.ts b/src/mcp/tools/docker/dockerGetContainersByAppNameMatch.ts new file mode 100644 index 0000000..45b2116 --- /dev/null +++ b/src/mcp/tools/docker/dockerGetContainersByAppNameMatch.ts @@ -0,0 +1,50 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const dockerGetContainersByAppNameMatch = createTool({ + name: "docker-get-containers-by-app-name-match", + description: + "Gets Docker containers matching an app name pattern in Dokploy.", + schema: z.object({ + appName: z + .string() + .min(1) + .regex(/^[a-zA-Z0-9.\-_]+$/) + .describe("The app name pattern to match containers against."), + appType: z + .enum(["stack", "docker-compose"]) + .optional() + .describe("The type of app to filter by ('stack' for Docker Swarm stacks or 'docker-compose' for Compose projects)."), + serverId: z + .string() + .optional() + .describe("The ID of the server to search on."), + }), + annotations: { + title: "Get Docker Containers by App Name Match", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const params = new URLSearchParams(); + params.append("appName", input.appName); + if (input.appType) { + params.append("appType", input.appType); + } + if (input.serverId) { + params.append("serverId", input.serverId); + } + + const response = await apiClient.get( + `/docker.getContainersByAppNameMatch?${params.toString()}`, + ); + + return ResponseFormatter.success( + `Successfully fetched containers matching app name "${input.appName}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/docker/dockerGetServiceContainersByAppName.ts b/src/mcp/tools/docker/dockerGetServiceContainersByAppName.ts new file mode 100644 index 0000000..f02ffc1 --- /dev/null +++ b/src/mcp/tools/docker/dockerGetServiceContainersByAppName.ts @@ -0,0 +1,42 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const dockerGetServiceContainersByAppName = createTool({ + name: "docker-get-service-containers-by-app-name", + description: "Gets Docker service containers by app name in Dokploy.", + schema: z.object({ + appName: z + .string() + .min(1) + .regex(/^[a-zA-Z0-9.\-_]+$/) + .describe("The app name to get service containers for."), + serverId: z + .string() + .optional() + .describe("The ID of the server to search on."), + }), + annotations: { + title: "Get Docker Service Containers by App Name", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const params = new URLSearchParams(); + params.append("appName", input.appName); + if (input.serverId) { + params.append("serverId", input.serverId); + } + + const response = await apiClient.get( + `/docker.getServiceContainersByAppName?${params.toString()}`, + ); + + return ResponseFormatter.success( + `Successfully fetched service containers for app "${input.appName}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/docker/dockerGetStackContainersByAppName.ts b/src/mcp/tools/docker/dockerGetStackContainersByAppName.ts new file mode 100644 index 0000000..17634f0 --- /dev/null +++ b/src/mcp/tools/docker/dockerGetStackContainersByAppName.ts @@ -0,0 +1,42 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const dockerGetStackContainersByAppName = createTool({ + name: "docker-get-stack-containers-by-app-name", + description: "Gets Docker stack containers by app name in Dokploy.", + schema: z.object({ + appName: z + .string() + .min(1) + .regex(/^[a-zA-Z0-9.\-_]+$/) + .describe("The app name to get stack containers for."), + serverId: z + .string() + .optional() + .describe("The ID of the server to search on."), + }), + annotations: { + title: "Get Docker Stack Containers by App Name", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const params = new URLSearchParams(); + params.append("appName", input.appName); + if (input.serverId) { + params.append("serverId", input.serverId); + } + + const response = await apiClient.get( + `/docker.getStackContainersByAppName?${params.toString()}`, + ); + + return ResponseFormatter.success( + `Successfully fetched stack containers for app "${input.appName}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/docker/dockerRestartContainer.ts b/src/mcp/tools/docker/dockerRestartContainer.ts new file mode 100644 index 0000000..cd8ff81 --- /dev/null +++ b/src/mcp/tools/docker/dockerRestartContainer.ts @@ -0,0 +1,33 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const dockerRestartContainer = createTool({ + name: "docker-restart-container", + description: "Restarts a Docker container in Dokploy.", + schema: z.object({ + containerId: z + .string() + .min(1) + .regex(/^[a-zA-Z0-9.\-_]+$/) + .describe("The ID of the container to restart."), + }), + annotations: { + title: "Restart Docker Container", + readOnlyHint: false, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/docker.restartContainer", { + containerId: input.containerId, + }); + + return ResponseFormatter.success( + `Successfully restarted container "${input.containerId}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/docker/index.ts b/src/mcp/tools/docker/index.ts new file mode 100644 index 0000000..1e9658c --- /dev/null +++ b/src/mcp/tools/docker/index.ts @@ -0,0 +1,7 @@ +export { dockerGetContainers } from "./dockerGetContainers.js"; +export { dockerRestartContainer } from "./dockerRestartContainer.js"; +export { dockerGetConfig } from "./dockerGetConfig.js"; +export { dockerGetContainersByAppNameMatch } from "./dockerGetContainersByAppNameMatch.js"; +export { dockerGetContainersByAppLabel } from "./dockerGetContainersByAppLabel.js"; +export { dockerGetStackContainersByAppName } from "./dockerGetStackContainersByAppName.js"; +export { dockerGetServiceContainersByAppName } from "./dockerGetServiceContainersByAppName.js"; diff --git a/src/mcp/tools/environment/environmentByProjectId.ts b/src/mcp/tools/environment/environmentByProjectId.ts new file mode 100644 index 0000000..6a3878c --- /dev/null +++ b/src/mcp/tools/environment/environmentByProjectId.ts @@ -0,0 +1,30 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const environmentByProjectId = createTool({ + name: "environment-by-project-id", + description: "Gets all environments for a specific project in Dokploy.", + schema: z.object({ + projectId: z + .string() + .describe("The project ID to list environments for. Required."), + }), + annotations: { + title: "List Environments by Project", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const environments = await apiClient.get( + `/environment.byProjectId?projectId=${input.projectId}`, + ); + + return ResponseFormatter.success( + `Successfully fetched environments for project "${input.projectId}"`, + environments.data, + ); + }, +}); diff --git a/src/mcp/tools/environment/environmentCreate.ts b/src/mcp/tools/environment/environmentCreate.ts new file mode 100644 index 0000000..b9c4e14 --- /dev/null +++ b/src/mcp/tools/environment/environmentCreate.ts @@ -0,0 +1,37 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const environmentCreate = createTool({ + name: "environment-create", + description: "Creates a new environment within a project in Dokploy.", + schema: z.object({ + name: z + .string() + .min(1) + .describe("Name for the environment (e.g., 'production', 'staging', 'development'). Required."), + projectId: z + .string() + .describe("The ID of the project to create the environment in. Required."), + description: z + .string() + .nullable() + .optional() + .describe("Description of the environment's purpose or configuration."), + }), + annotations: { + title: "Create Environment", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/environment.create", input); + + return ResponseFormatter.success( + `Environment "${input.name}" created successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/environment/environmentDuplicate.ts b/src/mcp/tools/environment/environmentDuplicate.ts new file mode 100644 index 0000000..3071182 --- /dev/null +++ b/src/mcp/tools/environment/environmentDuplicate.ts @@ -0,0 +1,38 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const environmentDuplicate = createTool({ + name: "environment-duplicate", + description: "Duplicates an existing environment in Dokploy.", + schema: z.object({ + environmentId: z + .string() + .min(1) + .describe("The ID of the source environment to duplicate. Required."), + name: z + .string() + .min(1) + .describe("Name for the new duplicated environment. Required."), + description: z + .string() + .nullable() + .optional() + .describe("Description for the new duplicated environment. Inherited from source if not specified."), + }), + annotations: { + title: "Duplicate Environment", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/environment.duplicate", input); + + return ResponseFormatter.success( + `Environment "${input.environmentId}" duplicated successfully as "${input.name}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/environment/environmentOne.ts b/src/mcp/tools/environment/environmentOne.ts new file mode 100644 index 0000000..6743d06 --- /dev/null +++ b/src/mcp/tools/environment/environmentOne.ts @@ -0,0 +1,38 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const environmentOne = createTool({ + name: "environment-one", + description: "Gets a specific environment by its ID in Dokploy.", + schema: z.object({ + environmentId: z + .string() + .min(1) + .describe("The unique identifier of the environment to retrieve. Required."), + }), + annotations: { + title: "Get Environment Details", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const environment = await apiClient.get( + `/environment.one?environmentId=${input.environmentId}`, + ); + + if (!environment?.data) { + return ResponseFormatter.error( + "Failed to fetch environment", + `Environment with ID "${input.environmentId}" not found`, + ); + } + + return ResponseFormatter.success( + `Successfully fetched environment "${input.environmentId}"`, + environment.data, + ); + }, +}); diff --git a/src/mcp/tools/environment/environmentRemove.ts b/src/mcp/tools/environment/environmentRemove.ts new file mode 100644 index 0000000..89988a4 --- /dev/null +++ b/src/mcp/tools/environment/environmentRemove.ts @@ -0,0 +1,29 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const environmentRemove = createTool({ + name: "environment-remove", + description: "Removes/deletes an environment from Dokploy.", + schema: z.object({ + environmentId: z + .string() + .min(1) + .describe("The unique identifier of the environment to remove. Required."), + }), + annotations: { + title: "Remove Environment", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/environment.remove", input); + + return ResponseFormatter.success( + `Environment "${input.environmentId}" removed successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/environment/environmentUpdate.ts b/src/mcp/tools/environment/environmentUpdate.ts new file mode 100644 index 0000000..6614d4f --- /dev/null +++ b/src/mcp/tools/environment/environmentUpdate.ts @@ -0,0 +1,51 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const environmentUpdate = createTool({ + name: "environment-update", + description: "Updates an existing environment in Dokploy.", + schema: z.object({ + environmentId: z + .string() + .min(1) + .describe("The ID of the environment to update. Required."), + name: z + .string() + .min(1) + .optional() + .describe("New name for the environment."), + description: z + .string() + .nullable() + .optional() + .describe("New description for the environment."), + createdAt: z + .string() + .optional() + .describe("Creation timestamp (ISO 8601 format)."), + env: z + .string() + .optional() + .describe("Environment variables in KEY=value format, one per line."), + projectId: z + .string() + .optional() + .describe("Project ID to move the environment to."), + }), + annotations: { + title: "Update Environment", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/environment.update", input); + + return ResponseFormatter.success( + `Environment "${input.environmentId}" updated successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/environment/index.ts b/src/mcp/tools/environment/index.ts new file mode 100644 index 0000000..5c055cd --- /dev/null +++ b/src/mcp/tools/environment/index.ts @@ -0,0 +1,6 @@ +export { environmentCreate } from "./environmentCreate.js"; +export { environmentOne } from "./environmentOne.js"; +export { environmentByProjectId } from "./environmentByProjectId.js"; +export { environmentRemove } from "./environmentRemove.js"; +export { environmentUpdate } from "./environmentUpdate.js"; +export { environmentDuplicate } from "./environmentDuplicate.js"; diff --git a/src/mcp/tools/git/gitBranches.ts b/src/mcp/tools/git/gitBranches.ts new file mode 100644 index 0000000..07f9bd2 --- /dev/null +++ b/src/mcp/tools/git/gitBranches.ts @@ -0,0 +1,94 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +const providerSchema = z.enum(["github", "gitlab", "gitea", "bitbucket"]); + +export const gitBranches = createTool({ + name: "git-branches", + description: + "Lists all branches for a repository from a git provider (GitHub, GitLab, Gitea, or Bitbucket) in Dokploy.", + schema: z.object({ + provider: providerSchema.describe( + "The git provider type: github, gitlab, gitea, or bitbucket.", + ), + owner: z + .string() + .min(1) + .describe("The repository owner (username or organization)."), + repo: z + .string() + .min(1) + .describe( + "The repository name. For Gitea, this maps to 'repositoryName' in the API.", + ), + providerId: z + .string() + .optional() + .describe( + "The provider-specific ID (githubId, gitlabId, giteaId, or bitbucketId). Optional for all providers.", + ), + // GitLab specific + id: z + .number() + .optional() + .describe("The GitLab project ID (optional, GitLab only)."), + }), + annotations: { + title: "List Git Branches", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const { provider, owner, repo, providerId, id } = input; + const providerLabel = provider.charAt(0).toUpperCase() + provider.slice(1); + + // Build endpoint and params based on provider + let endpoint: string; + const params = new URLSearchParams(); + + switch (provider) { + case "github": + endpoint = "/github.getGithubBranches"; + params.append("repo", repo); + params.append("owner", owner); + if (providerId) params.append("githubId", providerId); + break; + case "gitlab": + endpoint = "/gitlab.getGitlabBranches"; + params.append("repo", repo); + params.append("owner", owner); + if (providerId) params.append("gitlabId", providerId); + if (id !== undefined) params.append("id", id.toString()); + break; + case "gitea": + endpoint = "/gitea.getGiteaBranches"; + params.append("repositoryName", repo); + params.append("owner", owner); + if (providerId) params.append("giteaId", providerId); + break; + case "bitbucket": + endpoint = "/bitbucket.getBitbucketBranches"; + params.append("repo", repo); + params.append("owner", owner); + if (providerId) params.append("bitbucketId", providerId); + break; + } + + const response = await apiClient.get(`${endpoint}?${params.toString()}`); + + if (!response?.data) { + return ResponseFormatter.error( + `Failed to fetch ${providerLabel} branches`, + `Unable to retrieve branches for repository "${owner}/${repo}"`, + ); + } + + return ResponseFormatter.success( + `Successfully fetched branches for repository "${owner}/${repo}" from ${providerLabel}`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/git/gitProviderCreate.ts b/src/mcp/tools/git/gitProviderCreate.ts new file mode 100644 index 0000000..bac5550 --- /dev/null +++ b/src/mcp/tools/git/gitProviderCreate.ts @@ -0,0 +1,263 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +// Note: GitHub uses app installation, not direct creation like the others +const providerSchema = z.enum(["gitlab", "gitea", "bitbucket"]); + +export const gitProviderCreate = createTool({ + name: "git-provider-create", + description: + "Creates a new git provider (GitLab, Gitea, or Bitbucket) in Dokploy. Note: GitHub providers are created via app installation, not this endpoint.", + schema: z.object({ + provider: providerSchema.describe( + "The git provider type to create: gitlab, gitea, or bitbucket. GitHub uses app installation instead.", + ), + name: z + .string() + .min(1) + .describe( + "Display name for the git provider. Required for all providers.", + ), + // GitLab specific required fields + gitlabUrl: z + .string() + .min(1) + .optional() + .describe( + "The GitLab instance URL (e.g., https://gitlab.com). Required for GitLab.", + ), + authId: z + .string() + .min(1) + .optional() + .describe( + "The authentication ID for OAuth. Required for GitLab and Bitbucket.", + ), + // GitLab optional fields + gitlabId: z + .string() + .optional() + .describe("The GitLab provider ID (optional, GitLab only)."), + applicationId: z + .string() + .optional() + .describe("The OAuth application ID (optional, GitLab only)."), + redirectUri: z + .string() + .optional() + .describe("The OAuth redirect URI (optional, GitLab/Gitea)."), + secret: z + .string() + .optional() + .describe("The OAuth client secret (optional, GitLab only)."), + groupName: z + .string() + .optional() + .describe( + "The GitLab group name to filter repositories (optional, GitLab only).", + ), + // Gitea specific required fields + giteaUrl: z + .string() + .min(1) + .optional() + .describe( + "The Gitea instance URL (e.g., https://gitea.example.com). Required for Gitea.", + ), + // Gitea optional fields + giteaId: z + .string() + .optional() + .describe("The Gitea provider ID (optional, Gitea only)."), + clientId: z + .string() + .optional() + .describe("The OAuth client ID (optional, Gitea only)."), + clientSecret: z + .string() + .optional() + .describe("The OAuth client secret (optional, Gitea only)."), + scopes: z + .string() + .optional() + .describe("OAuth scopes to request (optional, Gitea only)."), + lastAuthenticatedAt: z + .number() + .optional() + .describe( + "Last authentication timestamp in milliseconds (optional, Gitea only).", + ), + giteaUsername: z + .string() + .optional() + .describe("The Gitea username (optional, Gitea only)."), + organizationName: z + .string() + .optional() + .describe( + "The Gitea organization name to filter repositories (optional, Gitea only).", + ), + // Bitbucket optional fields + bitbucketId: z + .string() + .optional() + .describe("The Bitbucket provider ID (optional, Bitbucket only)."), + bitbucketUsername: z + .string() + .optional() + .describe("The Bitbucket username (optional, Bitbucket only)."), + bitbucketEmail: z + .string() + .email() + .optional() + .describe("The Bitbucket email address (optional, Bitbucket only)."), + appPassword: z + .string() + .optional() + .describe( + "The Bitbucket app password for authentication (optional, Bitbucket only).", + ), + apiToken: z + .string() + .optional() + .describe("The Bitbucket API token (optional, Bitbucket only)."), + bitbucketWorkspaceName: z + .string() + .optional() + .describe("The Bitbucket workspace name (optional, Bitbucket only)."), + // Common optional fields + gitProviderId: z + .string() + .optional() + .describe("The parent git provider ID (optional)."), + accessToken: z + .string() + .nullable() + .optional() + .describe( + "The OAuth access token (optional, GitLab only supports null).", + ), + refreshToken: z + .string() + .nullable() + .optional() + .describe( + "The OAuth refresh token (optional, GitLab only supports null).", + ), + expiresAt: z + .number() + .nullable() + .optional() + .describe( + "Token expiration timestamp in milliseconds (optional, GitLab only supports null).", + ), + }), + annotations: { + title: "Create Git Provider", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const { provider, name } = input; + const providerLabel = provider.charAt(0).toUpperCase() + provider.slice(1); + + // Build endpoint and payload based on provider + let endpoint: string; + let payload: Record; + + switch (provider) { + case "gitlab": + // GitLab requires: gitlabUrl (min 1), authId (min 1), name (min 1) + if (!input.gitlabUrl || input.gitlabUrl.length < 1) { + return ResponseFormatter.error( + "Missing required parameter", + "GitLab requires gitlabUrl with at least 1 character", + ); + } + if (!input.authId || input.authId.length < 1) { + return ResponseFormatter.error( + "Missing required parameter", + "GitLab requires authId with at least 1 character", + ); + } + endpoint = "/gitlab.create"; + payload = { + name, + gitlabUrl: input.gitlabUrl, + authId: input.authId, + gitlabId: input.gitlabId, + applicationId: input.applicationId, + redirectUri: input.redirectUri, + secret: input.secret, + accessToken: input.accessToken, + refreshToken: input.refreshToken, + groupName: input.groupName, + expiresAt: input.expiresAt, + gitProviderId: input.gitProviderId, + }; + break; + case "gitea": + // Gitea requires: giteaUrl (min 1), name (min 1) + if (!input.giteaUrl || input.giteaUrl.length < 1) { + return ResponseFormatter.error( + "Missing required parameter", + "Gitea requires giteaUrl with at least 1 character", + ); + } + endpoint = "/gitea.create"; + payload = { + name, + giteaUrl: input.giteaUrl, + giteaId: input.giteaId, + redirectUri: input.redirectUri, + clientId: input.clientId, + clientSecret: input.clientSecret, + gitProviderId: input.gitProviderId, + accessToken: input.accessToken, + refreshToken: input.refreshToken, + expiresAt: input.expiresAt, + scopes: input.scopes, + lastAuthenticatedAt: input.lastAuthenticatedAt, + giteaUsername: input.giteaUsername, + organizationName: input.organizationName, + }; + break; + case "bitbucket": + // Bitbucket requires: authId (min 1), name (min 1) + if (!input.authId || input.authId.length < 1) { + return ResponseFormatter.error( + "Missing required parameter", + "Bitbucket requires authId with at least 1 character", + ); + } + endpoint = "/bitbucket.create"; + payload = { + name, + authId: input.authId, + bitbucketId: input.bitbucketId, + bitbucketUsername: input.bitbucketUsername, + bitbucketEmail: input.bitbucketEmail, + appPassword: input.appPassword, + apiToken: input.apiToken, + bitbucketWorkspaceName: input.bitbucketWorkspaceName, + gitProviderId: input.gitProviderId, + }; + break; + } + + // Remove undefined values + payload = Object.fromEntries( + Object.entries(payload).filter(([, v]) => v !== undefined), + ); + + const response = await apiClient.post(endpoint, payload); + + return ResponseFormatter.success( + `Successfully created ${providerLabel} provider "${name}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/git/gitProviderGetAll.ts b/src/mcp/tools/git/gitProviderGetAll.ts new file mode 100644 index 0000000..51c6045 --- /dev/null +++ b/src/mcp/tools/git/gitProviderGetAll.ts @@ -0,0 +1,32 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const gitProviderGetAll = createTool({ + name: "git-provider-get-all", + description: + "Lists all git providers configured in Dokploy across all provider types (GitHub, GitLab, Gitea, Bitbucket). Returns a combined list of all provider configurations.", + schema: z.object({}), + annotations: { + title: "List All Git Providers", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const response = await apiClient.get("/gitProvider.getAll"); + + if (!response?.data) { + return ResponseFormatter.error( + "Failed to fetch git providers", + "No git providers found or unable to retrieve the list", + ); + } + + return ResponseFormatter.success( + "Successfully fetched all git providers", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/git/gitProviderGetUrl.ts b/src/mcp/tools/git/gitProviderGetUrl.ts new file mode 100644 index 0000000..2d846bc --- /dev/null +++ b/src/mcp/tools/git/gitProviderGetUrl.ts @@ -0,0 +1,41 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const gitProviderGetUrl = createTool({ + name: "git-provider-get-url", + description: + "Gets the instance URL for a Gitea provider in Dokploy. This is useful for retrieving the base URL of a self-hosted Gitea instance.", + schema: z.object({ + giteaId: z + .string() + .min(1) + .describe( + "The Gitea provider ID. Required. Used to identify which Gitea provider's URL to retrieve.", + ), + }), + annotations: { + title: "Get Gitea URL", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.get( + `/gitea.getGiteaUrl?giteaId=${encodeURIComponent(input.giteaId)}`, + ); + + if (!response?.data) { + return ResponseFormatter.error( + "Failed to fetch Gitea URL", + `Unable to retrieve URL for Gitea provider "${input.giteaId}"`, + ); + } + + return ResponseFormatter.success( + `Successfully fetched URL for Gitea provider "${input.giteaId}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/git/gitProviderOne.ts b/src/mcp/tools/git/gitProviderOne.ts new file mode 100644 index 0000000..0d1ef82 --- /dev/null +++ b/src/mcp/tools/git/gitProviderOne.ts @@ -0,0 +1,72 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +const providerSchema = z.enum(["github", "gitlab", "gitea", "bitbucket"]); + +export const gitProviderOne = createTool({ + name: "git-provider-one", + description: + "Gets a specific git provider configuration by its ID (GitHub, GitLab, Gitea, or Bitbucket) in Dokploy.", + schema: z.object({ + provider: providerSchema.describe( + "The git provider type: github, gitlab, gitea, or bitbucket.", + ), + providerId: z + .string() + .min(1) + .describe( + "The provider-specific ID. Required. Maps to githubId, gitlabId, giteaId, or bitbucketId based on provider.", + ), + }), + annotations: { + title: "Get Git Provider Details", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const { provider, providerId } = input; + const providerLabel = provider.charAt(0).toUpperCase() + provider.slice(1); + + // Build endpoint based on provider + let endpoint: string; + let paramName: string; + + switch (provider) { + case "github": + endpoint = "/github.one"; + paramName = "githubId"; + break; + case "gitlab": + endpoint = "/gitlab.one"; + paramName = "gitlabId"; + break; + case "gitea": + endpoint = "/gitea.one"; + paramName = "giteaId"; + break; + case "bitbucket": + endpoint = "/bitbucket.one"; + paramName = "bitbucketId"; + break; + } + + const response = await apiClient.get( + `${endpoint}?${paramName}=${encodeURIComponent(providerId)}`, + ); + + if (!response?.data) { + return ResponseFormatter.error( + `Failed to fetch ${providerLabel} provider`, + `${providerLabel} provider with ID "${providerId}" not found`, + ); + } + + return ResponseFormatter.success( + `Successfully fetched ${providerLabel} provider "${providerId}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/git/gitProviderRemove.ts b/src/mcp/tools/git/gitProviderRemove.ts new file mode 100644 index 0000000..56103df --- /dev/null +++ b/src/mcp/tools/git/gitProviderRemove.ts @@ -0,0 +1,34 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const gitProviderRemove = createTool({ + name: "git-provider-remove", + description: + "Removes a git provider configuration from Dokploy. This deletes the provider regardless of type (GitHub, GitLab, Gitea, or Bitbucket).", + schema: z.object({ + gitProviderId: z + .string() + .min(1) + .describe( + "The ID of the git provider to remove. Required. This is the parent gitProviderId, not the provider-specific ID.", + ), + }), + annotations: { + title: "Remove Git Provider", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/gitProvider.remove", { + gitProviderId: input.gitProviderId, + }); + + return ResponseFormatter.success( + `Successfully removed git provider "${input.gitProviderId}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/git/gitProviderUpdate.ts b/src/mcp/tools/git/gitProviderUpdate.ts new file mode 100644 index 0000000..7327e5e --- /dev/null +++ b/src/mcp/tools/git/gitProviderUpdate.ts @@ -0,0 +1,321 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +const providerSchema = z.enum(["github", "gitlab", "gitea", "bitbucket"]); + +export const gitProviderUpdate = createTool({ + name: "git-provider-update", + description: + "Updates a git provider configuration (GitHub, GitLab, Gitea, or Bitbucket) in Dokploy.", + schema: z.object({ + provider: providerSchema.describe( + "The git provider type to update: github, gitlab, gitea, or bitbucket.", + ), + providerId: z + .string() + .min(1) + .describe( + "The provider-specific ID. Required. Maps to githubId, gitlabId, giteaId, or bitbucketId based on provider.", + ), + gitProviderId: z + .string() + .describe( + "The parent git provider ID. Required for all providers. GitHub requires min 1 char.", + ), + name: z + .string() + .min(1) + .describe( + "Display name for the git provider. Required for all providers.", + ), + // GitHub specific + githubAppName: z + .string() + .min(1) + .optional() + .describe("The GitHub App name. Required for GitHub updates."), + githubAppId: z + .number() + .nullable() + .optional() + .describe("The GitHub App numeric ID (optional, GitHub only)."), + githubClientId: z + .string() + .nullable() + .optional() + .describe("The GitHub OAuth Client ID (optional, GitHub only)."), + githubClientSecret: z + .string() + .nullable() + .optional() + .describe("The GitHub OAuth Client Secret (optional, GitHub only)."), + githubInstallationId: z + .string() + .nullable() + .optional() + .describe("The GitHub App Installation ID (optional, GitHub only)."), + githubPrivateKey: z + .string() + .nullable() + .optional() + .describe("The GitHub App Private Key (optional, GitHub only)."), + githubWebhookSecret: z + .string() + .nullable() + .optional() + .describe("The GitHub Webhook Secret (optional, GitHub only)."), + // GitLab specific + gitlabUrl: z + .string() + .min(1) + .optional() + .describe( + "The GitLab instance URL (e.g., https://gitlab.com). Required for GitLab updates.", + ), + applicationId: z + .string() + .optional() + .describe("The OAuth application ID (optional, GitLab only)."), + redirectUri: z + .string() + .optional() + .describe("The OAuth redirect URI (optional, GitLab/Gitea)."), + secret: z + .string() + .optional() + .describe("The OAuth client secret (optional, GitLab only)."), + groupName: z + .string() + .optional() + .describe( + "The GitLab group name to filter repositories (optional, GitLab only).", + ), + // Gitea specific + giteaUrl: z + .string() + .min(1) + .optional() + .describe( + "The Gitea instance URL (e.g., https://gitea.example.com). Required for Gitea updates.", + ), + clientId: z + .string() + .optional() + .describe("The OAuth client ID (optional, Gitea only)."), + clientSecret: z + .string() + .optional() + .describe("The OAuth client secret (optional, Gitea only)."), + scopes: z + .string() + .optional() + .describe("OAuth scopes to request (optional, Gitea only)."), + lastAuthenticatedAt: z + .number() + .optional() + .describe( + "Last authentication timestamp in milliseconds (optional, Gitea only).", + ), + giteaUsername: z + .string() + .optional() + .describe("The Gitea username (optional, Gitea only)."), + organizationName: z + .string() + .optional() + .describe( + "The organization name to filter repositories (optional, Gitea only).", + ), + // Bitbucket specific + bitbucketUsername: z + .string() + .optional() + .describe("The Bitbucket username (optional, Bitbucket only)."), + bitbucketEmail: z + .string() + .email() + .optional() + .describe("The Bitbucket email address (optional, Bitbucket only)."), + appPassword: z + .string() + .optional() + .describe( + "The Bitbucket app password for authentication (optional, Bitbucket only).", + ), + apiToken: z + .string() + .optional() + .describe("The Bitbucket API token (optional, Bitbucket only)."), + bitbucketWorkspaceName: z + .string() + .optional() + .describe("The Bitbucket workspace name (optional, Bitbucket only)."), + organizationId: z + .string() + .optional() + .describe("The organization ID (optional, Bitbucket only)."), + // Common token fields (GitLab only) + accessToken: z + .string() + .nullable() + .optional() + .describe( + "The OAuth access token (optional, GitLab only supports null).", + ), + refreshToken: z + .string() + .nullable() + .optional() + .describe( + "The OAuth refresh token (optional, GitLab only supports null).", + ), + expiresAt: z + .number() + .nullable() + .optional() + .describe( + "Token expiration timestamp in milliseconds (optional, GitLab only supports null).", + ), + }), + annotations: { + title: "Update Git Provider", + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const { provider, providerId, gitProviderId, name } = input; + const providerLabel = provider.charAt(0).toUpperCase() + provider.slice(1); + + // Build endpoint and payload based on provider + let endpoint: string; + let payload: Record; + + switch (provider) { + case "github": + // GitHub requires: githubId (min 1), githubAppName (min 1), gitProviderId (min 1), name (min 1) + if (!input.githubAppName || input.githubAppName.length < 1) { + return ResponseFormatter.error( + "Missing required parameter", + "GitHub requires githubAppName with at least 1 character", + ); + } + if (!gitProviderId || gitProviderId.length < 1) { + return ResponseFormatter.error( + "Missing required parameter", + "GitHub requires gitProviderId with at least 1 character", + ); + } + endpoint = "/github.update"; + payload = { + githubId: providerId, + gitProviderId, + name, + githubAppName: input.githubAppName, + githubAppId: input.githubAppId, + githubClientId: input.githubClientId, + githubClientSecret: input.githubClientSecret, + githubInstallationId: input.githubInstallationId, + githubPrivateKey: input.githubPrivateKey, + githubWebhookSecret: input.githubWebhookSecret, + }; + break; + case "gitlab": + // GitLab requires: gitlabId (min 1), gitlabUrl (min 1), gitProviderId (required), name (min 1) + if (!input.gitlabUrl || input.gitlabUrl.length < 1) { + return ResponseFormatter.error( + "Missing required parameter", + "GitLab requires gitlabUrl with at least 1 character", + ); + } + if (gitProviderId === undefined) { + return ResponseFormatter.error( + "Missing required parameter", + "GitLab requires gitProviderId", + ); + } + endpoint = "/gitlab.update"; + payload = { + gitlabId: providerId, + gitProviderId, + name, + gitlabUrl: input.gitlabUrl, + applicationId: input.applicationId, + redirectUri: input.redirectUri, + secret: input.secret, + accessToken: input.accessToken, + refreshToken: input.refreshToken, + groupName: input.groupName, + expiresAt: input.expiresAt, + }; + break; + case "gitea": + // Gitea requires: giteaId (min 1), giteaUrl (min 1), gitProviderId (required), name (min 1) + if (!input.giteaUrl || input.giteaUrl.length < 1) { + return ResponseFormatter.error( + "Missing required parameter", + "Gitea requires giteaUrl with at least 1 character", + ); + } + if (gitProviderId === undefined) { + return ResponseFormatter.error( + "Missing required parameter", + "Gitea requires gitProviderId", + ); + } + endpoint = "/gitea.update"; + payload = { + giteaId: providerId, + gitProviderId, + name, + giteaUrl: input.giteaUrl, + redirectUri: input.redirectUri, + clientId: input.clientId, + clientSecret: input.clientSecret, + accessToken: input.accessToken, + refreshToken: input.refreshToken, + expiresAt: input.expiresAt, + scopes: input.scopes, + lastAuthenticatedAt: input.lastAuthenticatedAt, + giteaUsername: input.giteaUsername, + organizationName: input.organizationName, + }; + break; + case "bitbucket": + // Bitbucket requires: bitbucketId (min 1), gitProviderId (required), name (min 1) + if (gitProviderId === undefined) { + return ResponseFormatter.error( + "Missing required parameter", + "Bitbucket requires gitProviderId", + ); + } + endpoint = "/bitbucket.update"; + payload = { + bitbucketId: providerId, + gitProviderId, + name, + bitbucketUsername: input.bitbucketUsername, + bitbucketEmail: input.bitbucketEmail, + appPassword: input.appPassword, + apiToken: input.apiToken, + bitbucketWorkspaceName: input.bitbucketWorkspaceName, + organizationId: input.organizationId, + }; + break; + } + + // Remove undefined values + payload = Object.fromEntries( + Object.entries(payload).filter(([, v]) => v !== undefined), + ); + + const response = await apiClient.post(endpoint, payload); + + return ResponseFormatter.success( + `Successfully updated ${providerLabel} provider "${name}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/git/gitProviders.ts b/src/mcp/tools/git/gitProviders.ts new file mode 100644 index 0000000..1b78131 --- /dev/null +++ b/src/mcp/tools/git/gitProviders.ts @@ -0,0 +1,59 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +const providerSchema = z.enum(["github", "gitlab", "gitea", "bitbucket"]); + +export const gitProviders = createTool({ + name: "git-providers", + description: + "Lists all configured git providers of a specific type (GitHub, GitLab, Gitea, or Bitbucket) in Dokploy. Returns all provider configurations for the specified type.", + schema: z.object({ + provider: providerSchema.describe( + "The git provider type to list: github, gitlab, gitea, or bitbucket.", + ), + }), + annotations: { + title: "List Git Providers by Type", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const { provider } = input; + const providerLabel = provider.charAt(0).toUpperCase() + provider.slice(1); + + // Build endpoint based on provider + let endpoint: string; + + switch (provider) { + case "github": + endpoint = "/github.githubProviders"; + break; + case "gitlab": + endpoint = "/gitlab.gitlabProviders"; + break; + case "gitea": + endpoint = "/gitea.giteaProviders"; + break; + case "bitbucket": + endpoint = "/bitbucket.bitbucketProviders"; + break; + } + + const response = await apiClient.get(endpoint); + + if (!response?.data) { + return ResponseFormatter.error( + `Failed to fetch ${providerLabel} providers`, + `No ${providerLabel} providers found or unable to retrieve the list`, + ); + } + + return ResponseFormatter.success( + `Successfully fetched ${providerLabel} providers`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/git/gitRepositories.ts b/src/mcp/tools/git/gitRepositories.ts new file mode 100644 index 0000000..ef178d9 --- /dev/null +++ b/src/mcp/tools/git/gitRepositories.ts @@ -0,0 +1,72 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +const providerSchema = z.enum(["github", "gitlab", "gitea", "bitbucket"]); + +export const gitRepositories = createTool({ + name: "git-repositories", + description: + "Lists all repositories available from a git provider (GitHub, GitLab, Gitea, or Bitbucket) in Dokploy.", + schema: z.object({ + provider: providerSchema.describe( + "The git provider type: github, gitlab, gitea, or bitbucket.", + ), + providerId: z + .string() + .min(1) + .describe( + "The provider-specific ID. Required. Maps to githubId, gitlabId, giteaId, or bitbucketId based on provider.", + ), + }), + annotations: { + title: "List Git Repositories", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const { provider, providerId } = input; + const providerLabel = provider.charAt(0).toUpperCase() + provider.slice(1); + + // Build endpoint based on provider + let endpoint: string; + let paramName: string; + + switch (provider) { + case "github": + endpoint = "/github.getGithubRepositories"; + paramName = "githubId"; + break; + case "gitlab": + endpoint = "/gitlab.getGitlabRepositories"; + paramName = "gitlabId"; + break; + case "gitea": + endpoint = "/gitea.getGiteaRepositories"; + paramName = "giteaId"; + break; + case "bitbucket": + endpoint = "/bitbucket.getBitbucketRepositories"; + paramName = "bitbucketId"; + break; + } + + const response = await apiClient.get( + `${endpoint}?${paramName}=${encodeURIComponent(providerId)}`, + ); + + if (!response?.data) { + return ResponseFormatter.error( + `Failed to fetch ${providerLabel} repositories`, + `Unable to retrieve repositories for ${providerLabel} provider "${providerId}"`, + ); + } + + return ResponseFormatter.success( + `Successfully fetched repositories for ${providerLabel} provider "${providerId}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/git/gitTestConnection.ts b/src/mcp/tools/git/gitTestConnection.ts new file mode 100644 index 0000000..b5d1fa9 --- /dev/null +++ b/src/mcp/tools/git/gitTestConnection.ts @@ -0,0 +1,138 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +const providerSchema = z.enum(["github", "gitlab", "gitea", "bitbucket"]); + +export const gitTestConnection = createTool({ + name: "git-test-connection", + description: + "Tests the connection to a git provider (GitHub, GitLab, Gitea, or Bitbucket) in Dokploy.", + schema: z.object({ + provider: providerSchema.describe( + "The git provider type: github, gitlab, gitea, or bitbucket.", + ), + // GitHub requires providerId with min 1 char, others have it optional + providerId: z + .string() + .optional() + .describe( + "The provider-specific ID. Required for GitHub (min 1 char) and Bitbucket (min 1 char). Optional for GitLab and Gitea.", + ), + // GitLab specific + groupName: z + .string() + .optional() + .describe("The GitLab group name to filter repositories (GitLab only)."), + // Gitea specific + organizationName: z + .string() + .optional() + .describe( + "The Gitea organization name to filter repositories (Gitea only).", + ), + // Bitbucket specific + bitbucketUsername: z + .string() + .optional() + .describe("The Bitbucket username for authentication (Bitbucket only)."), + bitbucketEmail: z + .string() + .email() + .optional() + .describe( + "The Bitbucket email address for authentication (Bitbucket only).", + ), + workspaceName: z + .string() + .optional() + .describe("The Bitbucket workspace name (Bitbucket only)."), + apiToken: z + .string() + .optional() + .describe("The Bitbucket API token for authentication (Bitbucket only)."), + appPassword: z + .string() + .optional() + .describe( + "The Bitbucket app password for authentication (Bitbucket only).", + ), + }), + annotations: { + title: "Test Git Connection", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const { + provider, + providerId, + groupName, + organizationName, + bitbucketUsername, + bitbucketEmail, + workspaceName, + apiToken, + appPassword, + } = input; + const providerLabel = provider.charAt(0).toUpperCase() + provider.slice(1); + + // Build endpoint and payload based on provider + let endpoint: string; + let payload: Record; + + switch (provider) { + case "github": + // GitHub requires githubId with minLength 1 + if (!providerId || providerId.length < 1) { + return ResponseFormatter.error( + "Missing required parameter", + "GitHub requires providerId (githubId) with at least 1 character", + ); + } + endpoint = "/github.testConnection"; + payload = { githubId: providerId }; + break; + case "gitlab": + // GitLab: gitlabId and groupName are both optional + endpoint = "/gitlab.testConnection"; + payload = {}; + if (providerId) payload.gitlabId = providerId; + if (groupName) payload.groupName = groupName; + break; + case "gitea": + // Gitea: giteaId and organizationName are both optional + endpoint = "/gitea.testConnection"; + payload = {}; + if (providerId) payload.giteaId = providerId; + if (organizationName) payload.organizationName = organizationName; + break; + case "bitbucket": + // Bitbucket requires bitbucketId with minLength 1 + if (!providerId || providerId.length < 1) { + return ResponseFormatter.error( + "Missing required parameter", + "Bitbucket requires providerId (bitbucketId) with at least 1 character", + ); + } + endpoint = "/bitbucket.testConnection"; + payload = { bitbucketId: providerId }; + if (bitbucketUsername) payload.bitbucketUsername = bitbucketUsername; + if (bitbucketEmail) payload.bitbucketEmail = bitbucketEmail; + if (workspaceName) payload.workspaceName = workspaceName; + if (apiToken) payload.apiToken = apiToken; + if (appPassword) payload.appPassword = appPassword; + break; + } + + const response = await apiClient.post(endpoint, payload); + + const providerIdDisplay = providerId || "(default)"; + return ResponseFormatter.success( + `Successfully tested connection for ${providerLabel} provider "${providerIdDisplay}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/git/index.ts b/src/mcp/tools/git/index.ts new file mode 100644 index 0000000..862a7d3 --- /dev/null +++ b/src/mcp/tools/git/index.ts @@ -0,0 +1,10 @@ +export { gitBranches } from "./gitBranches.js"; +export { gitRepositories } from "./gitRepositories.js"; +export { gitTestConnection } from "./gitTestConnection.js"; +export { gitProviderOne } from "./gitProviderOne.js"; +export { gitProviders } from "./gitProviders.js"; +export { gitProviderCreate } from "./gitProviderCreate.js"; +export { gitProviderUpdate } from "./gitProviderUpdate.js"; +export { gitProviderGetAll } from "./gitProviderGetAll.js"; +export { gitProviderRemove } from "./gitProviderRemove.js"; +export { gitProviderGetUrl } from "./gitProviderGetUrl.js"; diff --git a/src/mcp/tools/index.ts b/src/mcp/tools/index.ts index 5a9e1ac..4ea2e38 100644 --- a/src/mcp/tools/index.ts +++ b/src/mcp/tools/index.ts @@ -1,13 +1,69 @@ +import * as adminTools from "./admin/index.js"; +import * as aiTools from "./ai/index.js"; import * as applicationTools from "./application/index.js"; +import * as backupTools from "./backup/index.js"; +import * as certificatesTools from "./certificates/index.js"; +import * as clusterTools from "./cluster/index.js"; +import * as composeTools from "./compose/index.js"; +import * as databaseTools from "./database/index.js"; +import * as deploymentTools from "./deployment/index.js"; +import * as destinationTools from "./destination/index.js"; +import * as dockerTools from "./docker/index.js"; import * as domainTools from "./domain/index.js"; +import * as environmentTools from "./environment/index.js"; +import * as gitTools from "./git/index.js"; +import * as mountsTools from "./mounts/index.js"; import * as mysqlTools from "./mysql/index.js"; +import * as notificationTools from "./notification/index.js"; +import * as organizationTools from "./organization/index.js"; +import * as portTools from "./port/index.js"; import * as postgresTools from "./postgres/index.js"; +import * as previewDeploymentTools from "./previewDeployment/index.js"; import * as projectTools from "./project/index.js"; +import * as redirectsTools from "./redirects/index.js"; +import * as registryTools from "./registry/index.js"; +import * as rollbackTools from "./rollback/index.js"; +import * as scheduleTools from "./schedule/index.js"; +import * as securityTools from "./security/index.js"; +import * as serverTools from "./server/index.js"; +import * as settingsTools from "./settings/index.js"; +import * as sshKeyTools from "./sshKey/index.js"; +import * as stripeTools from "./stripe/index.js"; +import * as swarmTools from "./swarm/index.js"; +import * as userTools from "./user/index.js"; export const allTools = [ - ...Object.values(projectTools), + ...Object.values(adminTools), + ...Object.values(aiTools), ...Object.values(applicationTools), + ...Object.values(backupTools), + ...Object.values(certificatesTools), + ...Object.values(clusterTools), + ...Object.values(composeTools), + ...Object.values(databaseTools), + ...Object.values(deploymentTools), + ...Object.values(destinationTools), + ...Object.values(dockerTools), ...Object.values(domainTools), + ...Object.values(environmentTools), + ...Object.values(gitTools), + ...Object.values(mountsTools), ...Object.values(mysqlTools), + ...Object.values(notificationTools), + ...Object.values(organizationTools), + ...Object.values(portTools), ...Object.values(postgresTools), + ...Object.values(previewDeploymentTools), + ...Object.values(projectTools), + ...Object.values(redirectsTools), + ...Object.values(registryTools), + ...Object.values(rollbackTools), + ...Object.values(scheduleTools), + ...Object.values(securityTools), + ...Object.values(serverTools), + ...Object.values(settingsTools), + ...Object.values(sshKeyTools), + ...Object.values(stripeTools), + ...Object.values(swarmTools), + ...Object.values(userTools), ]; diff --git a/src/mcp/tools/mounts/index.ts b/src/mcp/tools/mounts/index.ts new file mode 100644 index 0000000..3cfb08f --- /dev/null +++ b/src/mcp/tools/mounts/index.ts @@ -0,0 +1,5 @@ +export { mountsCreate } from "./mountsCreate.js"; +export { mountsOne } from "./mountsOne.js"; +export { mountsUpdate } from "./mountsUpdate.js"; +export { mountsRemove } from "./mountsRemove.js"; +export { mountsAllByApplicationId } from "./mountsAllByApplicationId.js"; diff --git a/src/mcp/tools/mounts/mountsAllByApplicationId.ts b/src/mcp/tools/mounts/mountsAllByApplicationId.ts new file mode 100644 index 0000000..66dcc25 --- /dev/null +++ b/src/mcp/tools/mounts/mountsAllByApplicationId.ts @@ -0,0 +1,80 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +interface Mount { + mountId: string; + type: "file" | "bind" | "volume"; + hostPath: string | null; + volumeName: string | null; + filePath: string | null; + content: string | null; + serviceType: string; + mountPath: string; + applicationId: string | null; + postgresId: string | null; + mariadbId: string | null; + mongoId: string | null; + mysqlId: string | null; + redisId: string | null; + composeId: string | null; +} + +export const mountsAllByApplicationId = createTool({ + name: "mounts-allByApplicationId", + description: + "Retrieves all mounts associated with a specific application. Returns a list of all file mounts, bind mounts, and volume mounts configured for the application. Useful for viewing what files and volumes are attached to an app.", + schema: z.object({ + applicationId: z + .string() + .min(1) + .describe("The application ID to list mounts for. Required."), + }), + annotations: { + title: "List Application Mounts", + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + // Mounts are embedded in the application.one response + const response = await apiClient.get( + `/application.one?applicationId=${input.applicationId}` + ); + + if (!response?.data) { + return ResponseFormatter.error( + "Failed to fetch application", + `Application with ID "${input.applicationId}" not found` + ); + } + + const mounts: Mount[] = response.data.mounts ?? []; + + // Group by type for easier reading + const grouped = { + file: mounts.filter((m) => m.type === "file"), + bind: mounts.filter((m) => m.type === "bind"), + volume: mounts.filter((m) => m.type === "volume"), + }; + + // For file mounts, truncate content in summary but include full in data + const summary = mounts.map((m) => ({ + mountId: m.mountId, + type: m.type, + mountPath: m.mountPath, + ...(m.type === "file" && m.content + ? { contentPreview: m.content.slice(0, 100) + (m.content.length > 100 ? "..." : "") } + : {}), + ...(m.type === "volume" ? { volumeName: m.volumeName } : {}), + ...(m.type === "bind" ? { hostPath: m.hostPath } : {}), + })); + + return ResponseFormatter.success( + `Found ${mounts.length} mount(s): ${grouped.file.length} file, ${grouped.bind.length} bind, ${grouped.volume.length} volume`, + { total: mounts.length, grouped, summary, mounts } + ); + }, +}); diff --git a/src/mcp/tools/mounts/mountsCreate.ts b/src/mcp/tools/mounts/mountsCreate.ts new file mode 100644 index 0000000..58f89a7 --- /dev/null +++ b/src/mcp/tools/mounts/mountsCreate.ts @@ -0,0 +1,106 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const mountsCreate = createTool({ + name: "mounts-create", + description: `Creates a new mount for a service in Dokploy. Supports three mount types: + +- **file**: Creates a file with inline content. Requires: filePath (filename only), mountPath (FULL path INCLUDING filename), content (file content). IMPORTANT: For file mounts, mountPath must be the complete path including the filename (e.g., '/app/config.json') because Docker mounts files to files, not files to directories. +- **bind**: Maps a host directory to container. Requires: hostPath (host path), mountPath (container path) +- **volume**: Uses a Docker volume. Requires: volumeName, mountPath (container path)`, + schema: z.object({ + type: z + .enum(["bind", "volume", "file"]) + .describe( + "Mount type. 'file': inline file content, 'bind': host path mapping, 'volume': Docker volume. Required." + ), + mountPath: z + .string() + .min(1) + .describe( + "Destination path inside the container. For file mounts: MUST include the filename (e.g., '/app/config.json'). For bind/volume: the directory path. Required." + ), + serviceId: z + .string() + .min(1) + .describe( + "ID of the service (application, database, or compose) to attach this mount to. Required." + ), + serviceType: z + .enum(["application", "postgres", "mysql", "mariadb", "mongo", "redis", "compose"]) + .default("application") + .optional() + .describe( + "Type of service this mount belongs to. Defaults to 'application'." + ), + filePath: z + .string() + .nullable() + .optional() + .describe( + "For 'file' type: The filename (e.g., 'config.json'). This is just the name, not a path." + ), + content: z + .string() + .nullable() + .optional() + .describe("For 'file' type: The actual file content to be written."), + hostPath: z + .string() + .nullable() + .optional() + .describe("For 'bind' type: Absolute path on the host server to mount from."), + volumeName: z + .string() + .nullable() + .optional() + .describe("For 'volume' type: Name of the Docker volume to mount."), + }), + annotations: { + title: "Create Mount", + readOnlyHint: false, + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + // Validate type-specific required fields + if (input.type === "file") { + if (!input.filePath) { + return ResponseFormatter.error( + "Missing required field", + "filePath is required for 'file' type mounts" + ); + } + if (!input.content) { + return ResponseFormatter.error( + "Missing required field", + "content is required for 'file' type mounts" + ); + } + } else if (input.type === "bind") { + if (!input.hostPath) { + return ResponseFormatter.error( + "Missing required field", + "hostPath is required for 'bind' type mounts" + ); + } + } else if (input.type === "volume") { + if (!input.volumeName) { + return ResponseFormatter.error( + "Missing required field", + "volumeName is required for 'volume' type mounts" + ); + } + } + + const response = await apiClient.post("/mounts.create", input); + + return ResponseFormatter.success( + `Successfully created ${input.type} mount at ${input.mountPath}`, + response?.data ?? { created: true } + ); + }, +}); diff --git a/src/mcp/tools/mounts/mountsOne.ts b/src/mcp/tools/mounts/mountsOne.ts new file mode 100644 index 0000000..1b2b24d --- /dev/null +++ b/src/mcp/tools/mounts/mountsOne.ts @@ -0,0 +1,39 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const mountsOne = createTool({ + name: "mounts-one", + description: + "Retrieves detailed information about a specific mount by its ID. Returns the mount type, paths, content (for file mounts), and associated service information.", + schema: z.object({ + mountId: z + .string() + .describe("The unique identifier of the mount to retrieve. Required."), + }), + annotations: { + title: "Get Mount", + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.get( + `/mounts.one?mountId=${input.mountId}` + ); + + if (!response?.data) { + return ResponseFormatter.error( + "Failed to fetch mount", + `Mount with ID "${input.mountId}" not found` + ); + } + + return ResponseFormatter.success( + `Successfully retrieved mount ${input.mountId}`, + response.data + ); + }, +}); diff --git a/src/mcp/tools/mounts/mountsRemove.ts b/src/mcp/tools/mounts/mountsRemove.ts new file mode 100644 index 0000000..45cd558 --- /dev/null +++ b/src/mcp/tools/mounts/mountsRemove.ts @@ -0,0 +1,30 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const mountsRemove = createTool({ + name: "mounts-remove", + description: + "Removes/deletes a mount from a service in Dokploy. This will detach the mount from the service. For file mounts, the file will no longer be available inside the container after the next deployment.", + schema: z.object({ + mountId: z + .string() + .describe("The unique identifier of the mount to remove. Required."), + }), + annotations: { + title: "Remove Mount", + readOnlyHint: false, + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/mounts.remove", input); + + return ResponseFormatter.success( + `Successfully removed mount ${input.mountId}`, + response?.data ?? { removed: true } + ); + }, +}); diff --git a/src/mcp/tools/mounts/mountsUpdate.ts b/src/mcp/tools/mounts/mountsUpdate.ts new file mode 100644 index 0000000..b8c8af8 --- /dev/null +++ b/src/mcp/tools/mounts/mountsUpdate.ts @@ -0,0 +1,102 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const mountsUpdate = createTool({ + name: "mounts-update", + description: + "Updates an existing mount configuration in Dokploy. Can modify the mount type, paths, and content. For file mounts, you can update the file content directly. Use this to edit configuration files, scripts, or any mounted file content. IMPORTANT: For file mounts, mountPath must include the filename.", + schema: z.object({ + mountId: z + .string() + .min(1) + .describe("The unique identifier of the mount to update. Required."), + type: z + .enum(["bind", "volume", "file"]) + .optional() + .describe("New mount type: 'file', 'bind', or 'volume'."), + mountPath: z + .string() + .min(1) + .optional() + .describe( + "New destination path inside the container. For file mounts: MUST include the filename." + ), + hostPath: z + .string() + .nullable() + .optional() + .describe("New host path for bind mounts."), + volumeName: z + .string() + .nullable() + .optional() + .describe("New volume name for volume mounts."), + content: z + .string() + .nullable() + .optional() + .describe("New file content for file mounts. Use this to edit the file contents."), + filePath: z + .string() + .nullable() + .optional() + .describe("New filename reference for file mounts."), + serviceType: z + .enum(["application", "postgres", "mysql", "mariadb", "mongo", "redis", "compose"]) + .default("application") + .optional() + .describe("Service type this mount belongs to."), + applicationId: z + .string() + .nullable() + .optional() + .describe("Application ID if reassigning to a different application."), + postgresId: z + .string() + .nullable() + .optional() + .describe("PostgreSQL database ID if reassigning to a different database."), + mariadbId: z + .string() + .nullable() + .optional() + .describe("MariaDB database ID if reassigning to a different database."), + mongoId: z + .string() + .nullable() + .optional() + .describe("MongoDB database ID if reassigning to a different database."), + mysqlId: z + .string() + .nullable() + .optional() + .describe("MySQL database ID if reassigning to a different database."), + redisId: z + .string() + .nullable() + .optional() + .describe("Redis database ID if reassigning to a different database."), + composeId: z + .string() + .nullable() + .optional() + .describe("Compose stack ID if reassigning to a different compose stack."), + }), + annotations: { + title: "Update Mount", + readOnlyHint: false, + destructiveHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/mounts.update", input); + + return ResponseFormatter.success( + `Successfully updated mount ${input.mountId}`, + response?.data ?? { updated: true } + ); + }, +}); diff --git a/src/mcp/tools/notification/index.ts b/src/mcp/tools/notification/index.ts new file mode 100644 index 0000000..420c8e6 --- /dev/null +++ b/src/mcp/tools/notification/index.ts @@ -0,0 +1,8 @@ +export { notificationCreate } from "./notificationCreate.js"; +export { notificationUpdate } from "./notificationUpdate.js"; +export { notificationTestConnection } from "./notificationTestConnection.js"; +export { notificationRemove } from "./notificationRemove.js"; +export { notificationOne } from "./notificationOne.js"; +export { notificationAll } from "./notificationAll.js"; +export { notificationReceiveNotification } from "./notificationReceiveNotification.js"; +export { notificationGetEmailProviders } from "./notificationGetEmailProviders.js"; diff --git a/src/mcp/tools/notification/notificationAll.ts b/src/mcp/tools/notification/notificationAll.ts new file mode 100644 index 0000000..8b5755f --- /dev/null +++ b/src/mcp/tools/notification/notificationAll.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const notificationAll = createTool({ + name: "notification-all", + description: "Gets all notification configurations in Dokploy.", + schema: z.object({}), + annotations: { + title: "List All Notifications", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const notifications = await apiClient.get("/notification.all"); + + return ResponseFormatter.success( + "Successfully fetched all notifications", + notifications.data, + ); + }, +}); diff --git a/src/mcp/tools/notification/notificationCreate.ts b/src/mcp/tools/notification/notificationCreate.ts new file mode 100644 index 0000000..8f3a8e0 --- /dev/null +++ b/src/mcp/tools/notification/notificationCreate.ts @@ -0,0 +1,324 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +// Common notification settings shared by all providers (based on OpenAPI spec) +const commonNotificationSettings = { + name: z.string().describe("The name of the notification."), + appBuildError: z.boolean().describe("Notify on application build errors."), + databaseBackup: z.boolean().describe("Notify on database backup events."), + dokployRestart: z.boolean().describe("Notify on Dokploy restart events."), + appDeploy: z.boolean().describe("Notify on application deployment events."), + dockerCleanup: z.boolean().describe("Notify on Docker cleanup events."), +}; + +// Provider-specific configurations (matching OpenAPI spec exactly) +const slackConfig = z.object({ + provider: z.literal("slack"), + ...commonNotificationSettings, + serverThreshold: z + .boolean() + .describe("Notify when server thresholds are exceeded."), + webhookUrl: z + .string() + .min(1) + .describe("The Slack webhook URL for sending notifications."), + channel: z.string().describe("The Slack channel to send notifications to."), +}); + +const discordConfig = z.object({ + provider: z.literal("discord"), + ...commonNotificationSettings, + serverThreshold: z + .boolean() + .describe("Notify when server thresholds are exceeded."), + webhookUrl: z + .string() + .min(1) + .describe("The Discord webhook URL for sending notifications."), + decoration: z + .boolean() + .describe("Whether to use rich embed formatting for messages."), +}); + +const telegramConfig = z.object({ + provider: z.literal("telegram"), + ...commonNotificationSettings, + serverThreshold: z + .boolean() + .describe("Notify when server thresholds are exceeded."), + botToken: z + .string() + .min(1) + .describe("The Telegram bot token for sending notifications."), + chatId: z + .string() + .min(1) + .describe("The Telegram chat ID to send notifications to."), + messageThreadId: z + .string() + .describe( + "The message thread ID for topic-based chats. Required but can be empty string.", + ), +}); + +const emailConfig = z.object({ + provider: z.literal("email"), + ...commonNotificationSettings, + serverThreshold: z + .boolean() + .describe("Notify when server thresholds are exceeded."), + smtpServer: z + .string() + .min(1) + .describe("The SMTP server hostname for sending emails."), + smtpPort: z.number().min(1).describe("The SMTP server port."), + username: z.string().min(1).describe("The SMTP authentication username."), + password: z.string().min(1).describe("The SMTP authentication password."), + fromAddress: z + .string() + .min(1) + .describe("The email address to send notifications from."), + toAddresses: z + .array(z.string()) + .min(1) + .describe("The email addresses to send notifications to."), +}); + +// Note: Gotify does NOT have serverThreshold according to OpenAPI spec +const gotifyConfig = z.object({ + provider: z.literal("gotify"), + ...commonNotificationSettings, + serverUrl: z + .string() + .min(1) + .describe("The Gotify server URL for sending notifications."), + appToken: z + .string() + .min(1) + .describe("The Gotify application token for authentication."), + priority: z + .number() + .min(1) + .describe("The priority level for Gotify messages (minimum 1)."), + decoration: z + .boolean() + .describe("Whether to use rich formatting for messages."), +}); + +// Note: Ntfy does NOT have serverThreshold according to OpenAPI spec +const ntfyConfig = z.object({ + provider: z.literal("ntfy"), + ...commonNotificationSettings, + serverUrl: z + .string() + .min(1) + .describe("The Ntfy server URL for sending notifications."), + topic: z.string().min(1).describe("The Ntfy topic to send notifications to."), + accessToken: z + .string() + .min(1) + .describe("The Ntfy access token for authentication."), + priority: z + .number() + .min(1) + .describe("The priority level for Ntfy messages (minimum 1)."), +}); + +const larkConfig = z.object({ + provider: z.literal("lark"), + ...commonNotificationSettings, + serverThreshold: z + .boolean() + .describe("Notify when server thresholds are exceeded."), + webhookUrl: z + .string() + .min(1) + .describe("The Lark webhook URL for sending notifications."), +}); + +// Discriminated union of all provider configs +const notificationCreateSchema = z.discriminatedUnion("provider", [ + slackConfig, + discordConfig, + telegramConfig, + emailConfig, + gotifyConfig, + ntfyConfig, + larkConfig, +]); + +type NotificationCreateInput = z.infer; + +// Map provider to API endpoint suffix +const providerEndpointMap: Record = + { + slack: "Slack", + discord: "Discord", + telegram: "Telegram", + email: "Email", + gotify: "Gotify", + ntfy: "Ntfy", + lark: "Lark", + }; + +export const notificationCreate = createTool({ + name: "notification-create", + description: `Creates a new notification configuration in Dokploy. Supports multiple providers: slack, discord, telegram, email, gotify, ntfy, lark. Each provider requires different configuration fields. + +Common required fields for all providers: +- name, appBuildError, databaseBackup, dokployRestart, appDeploy, dockerCleanup + +Provider-specific required fields: +- slack: webhookUrl, channel, serverThreshold +- discord: webhookUrl, decoration, serverThreshold +- telegram: botToken, chatId, messageThreadId (can be empty string), serverThreshold +- email: smtpServer, smtpPort, username, password, fromAddress, toAddresses, serverThreshold +- gotify: serverUrl, appToken, priority, decoration (NO serverThreshold) +- ntfy: serverUrl, topic, accessToken, priority (NO serverThreshold) +- lark: webhookUrl, serverThreshold`, + schema: z.object({ + provider: z + .enum(["slack", "discord", "telegram", "email", "gotify", "ntfy", "lark"]) + .describe("The notification provider type."), + name: z.string().describe("The name of the notification."), + appBuildError: z.boolean().describe("Notify on application build errors."), + databaseBackup: z.boolean().describe("Notify on database backup events."), + dokployRestart: z.boolean().describe("Notify on Dokploy restart events."), + appDeploy: z + .boolean() + .describe("Notify on application deployment events."), + dockerCleanup: z.boolean().describe("Notify on Docker cleanup events."), + // Provider-specific fields (all optional at schema level, validated in handler) + webhookUrl: z + .string() + .min(1) + .optional() + .describe( + "Webhook URL (required for slack, discord, lark providers).", + ), + channel: z + .string() + .optional() + .describe("Slack channel (required for slack provider)."), + decoration: z + .boolean() + .optional() + .describe( + "Rich formatting (required for discord, gotify providers).", + ), + serverThreshold: z + .boolean() + .optional() + .describe( + "Notify on server threshold (required for slack, discord, telegram, email, lark). NOT used by gotify or ntfy.", + ), + botToken: z + .string() + .min(1) + .optional() + .describe("Telegram bot token (required for telegram provider)."), + chatId: z + .string() + .min(1) + .optional() + .describe("Telegram chat ID (required for telegram provider)."), + messageThreadId: z + .string() + .optional() + .describe( + "Telegram message thread ID (required for telegram provider, can be empty string).", + ), + smtpServer: z + .string() + .min(1) + .optional() + .describe("SMTP server hostname (required for email provider)."), + smtpPort: z + .number() + .min(1) + .optional() + .describe("SMTP port (required for email provider, minimum 1)."), + username: z + .string() + .min(1) + .optional() + .describe("SMTP username (required for email provider)."), + password: z + .string() + .min(1) + .optional() + .describe("SMTP password (required for email provider)."), + fromAddress: z + .string() + .min(1) + .optional() + .describe("From email address (required for email provider)."), + toAddresses: z + .array(z.string()) + .min(1) + .optional() + .describe("To email addresses array (required for email provider)."), + serverUrl: z + .string() + .min(1) + .optional() + .describe("Server URL (required for gotify, ntfy providers)."), + appToken: z + .string() + .min(1) + .optional() + .describe("Application token (required for gotify provider)."), + priority: z + .number() + .min(1) + .optional() + .describe("Priority level, minimum 1 (required for gotify, ntfy providers)."), + topic: z + .string() + .min(1) + .optional() + .describe("Ntfy topic (required for ntfy provider)."), + accessToken: z + .string() + .min(1) + .optional() + .describe("Access token (required for ntfy provider)."), + }), + annotations: { + title: "Create Notification", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + // Validate with discriminated union schema for better type safety + const validationResult = notificationCreateSchema.safeParse(input); + if (!validationResult.success) { + const errorMessages = validationResult.error.errors + .map((err) => `${err.path.join(".")}: ${err.message}`) + .join(", "); + return ResponseFormatter.error( + `Invalid input for ${input.provider} notification`, + `Validation errors: ${errorMessages}`, + ); + } + + const validatedInput = validationResult.data; + const endpointSuffix = providerEndpointMap[validatedInput.provider]; + + // Remove 'provider' from the payload sent to API + const { provider, ...apiPayload } = validatedInput; + + const response = await apiClient.post( + `/notification.create${endpointSuffix}`, + apiPayload, + ); + + return ResponseFormatter.success( + `${provider.charAt(0).toUpperCase() + provider.slice(1)} notification "${input.name}" created successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/notification/notificationGetEmailProviders.ts b/src/mcp/tools/notification/notificationGetEmailProviders.ts new file mode 100644 index 0000000..93e7745 --- /dev/null +++ b/src/mcp/tools/notification/notificationGetEmailProviders.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const notificationGetEmailProviders = createTool({ + name: "notification-get-email-providers", + description: "Gets the list of available email providers for notifications.", + schema: z.object({}), + annotations: { + title: "Get Email Providers", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const providers = await apiClient.get("/notification.getEmailProviders"); + + return ResponseFormatter.success( + "Successfully fetched email providers", + providers.data, + ); + }, +}); diff --git a/src/mcp/tools/notification/notificationOne.ts b/src/mcp/tools/notification/notificationOne.ts new file mode 100644 index 0000000..9397d21 --- /dev/null +++ b/src/mcp/tools/notification/notificationOne.ts @@ -0,0 +1,38 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const notificationOne = createTool({ + name: "notification-one", + description: + "Gets a specific notification configuration by its ID in Dokploy.", + schema: z.object({ + notificationId: z + .string() + .describe("The ID of the notification to retrieve."), + }), + annotations: { + title: "Get Notification Details", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const notification = await apiClient.get( + `/notification.one?notificationId=${input.notificationId}`, + ); + + if (!notification?.data) { + return ResponseFormatter.error( + "Failed to fetch notification", + `Notification with ID "${input.notificationId}" not found`, + ); + } + + return ResponseFormatter.success( + `Successfully fetched notification "${input.notificationId}"`, + notification.data, + ); + }, +}); diff --git a/src/mcp/tools/notification/notificationReceiveNotification.ts b/src/mcp/tools/notification/notificationReceiveNotification.ts new file mode 100644 index 0000000..17df83f --- /dev/null +++ b/src/mcp/tools/notification/notificationReceiveNotification.ts @@ -0,0 +1,44 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const notificationReceiveNotification = createTool({ + name: "notification-receive-notification", + description: + "Receives and processes a notification event (typically from server monitoring).", + schema: z.object({ + ServerType: z + .enum(["Dokploy", "Remote"]) + .default("Dokploy") + .optional() + .describe("The type of server sending the notification."), + Type: z + .enum(["Memory", "CPU"]) + .describe("The type of threshold notification."), + Value: z.number().describe("The current value that triggered the alert."), + Threshold: z.number().describe("The threshold that was exceeded."), + Message: z.string().describe("The notification message."), + Timestamp: z.string().describe("The timestamp of the event."), + Token: z + .string() + .describe("The authentication token for the notification."), + }), + annotations: { + title: "Receive Notification", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post( + "/notification.receiveNotification", + input, + ); + + return ResponseFormatter.success( + "Notification received and processed successfully", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/notification/notificationRemove.ts b/src/mcp/tools/notification/notificationRemove.ts new file mode 100644 index 0000000..0ed1d4a --- /dev/null +++ b/src/mcp/tools/notification/notificationRemove.ts @@ -0,0 +1,28 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const notificationRemove = createTool({ + name: "notification-remove", + description: "Removes/deletes a notification configuration from Dokploy.", + schema: z.object({ + notificationId: z + .string() + .describe("The ID of the notification to remove."), + }), + annotations: { + title: "Remove Notification", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/notification.remove", input); + + return ResponseFormatter.success( + `Notification "${input.notificationId}" removed successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/notification/notificationTestConnection.ts b/src/mcp/tools/notification/notificationTestConnection.ts new file mode 100644 index 0000000..7dd1de4 --- /dev/null +++ b/src/mcp/tools/notification/notificationTestConnection.ts @@ -0,0 +1,254 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +// Provider-specific test connection configurations (matching OpenAPI spec exactly) +const slackTestConfig = z.object({ + provider: z.literal("slack"), + webhookUrl: z.string().min(1).describe("The Slack webhook URL to test."), + channel: z.string().describe("The Slack channel to test sending to."), +}); + +const discordTestConfig = z.object({ + provider: z.literal("discord"), + webhookUrl: z.string().min(1).describe("The Discord webhook URL to test."), + decoration: z + .boolean() + .optional() + .describe("Whether to use rich embed formatting for the test message."), +}); + +const telegramTestConfig = z.object({ + provider: z.literal("telegram"), + botToken: z.string().min(1).describe("The Telegram bot token to test."), + chatId: z + .string() + .min(1) + .describe("The Telegram chat ID to test sending to."), + messageThreadId: z + .string() + .describe( + "The message thread ID for topic-based chats. Required but can be empty string.", + ), +}); + +const emailTestConfig = z.object({ + provider: z.literal("email"), + smtpServer: z.string().min(1).describe("The SMTP server hostname to test."), + smtpPort: z.number().min(1).describe("The SMTP server port (minimum 1)."), + username: z.string().min(1).describe("The SMTP authentication username."), + password: z.string().min(1).describe("The SMTP authentication password."), + toAddresses: z + .array(z.string()) + .min(1) + .describe("The email addresses to send the test to."), + fromAddress: z + .string() + .min(1) + .describe("The email address to send the test from."), +}); + +const gotifyTestConfig = z.object({ + provider: z.literal("gotify"), + serverUrl: z.string().min(1).describe("The Gotify server URL to test."), + appToken: z + .string() + .min(1) + .describe("The Gotify application token to test."), + priority: z + .number() + .min(1) + .describe("The priority level for the test message (minimum 1)."), + decoration: z + .boolean() + .optional() + .describe("Whether to use rich formatting for the test message."), +}); + +const ntfyTestConfig = z.object({ + provider: z.literal("ntfy"), + serverUrl: z.string().min(1).describe("The Ntfy server URL to test."), + topic: z.string().min(1).describe("The Ntfy topic to test sending to."), + accessToken: z + .string() + .min(1) + .describe("The Ntfy access token for authentication."), + priority: z + .number() + .min(1) + .describe("The priority level for the test message (minimum 1)."), +}); + +const larkTestConfig = z.object({ + provider: z.literal("lark"), + webhookUrl: z.string().min(1).describe("The Lark webhook URL to test."), +}); + +// Discriminated union of all provider test configs +const notificationTestConnectionSchema = z.discriminatedUnion("provider", [ + slackTestConfig, + discordTestConfig, + telegramTestConfig, + emailTestConfig, + gotifyTestConfig, + ntfyTestConfig, + larkTestConfig, +]); + +type NotificationTestConnectionInput = z.infer< + typeof notificationTestConnectionSchema +>; + +// Map provider to API endpoint suffix +const providerEndpointMap: Record< + NotificationTestConnectionInput["provider"], + string +> = { + slack: "Slack", + discord: "Discord", + telegram: "Telegram", + email: "Email", + gotify: "Gotify", + ntfy: "Ntfy", + lark: "Lark", +}; + +export const notificationTestConnection = createTool({ + name: "notification-test-connection", + description: `Tests a notification provider connection in Dokploy by sending a test message. Supports multiple providers: slack, discord, telegram, email, gotify, ntfy, lark. Each provider requires different configuration fields. + +Provider-specific required fields: +- slack: webhookUrl, channel +- discord: webhookUrl (decoration optional) +- telegram: botToken, chatId, messageThreadId (can be empty string) +- email: smtpServer, smtpPort, username, password, fromAddress, toAddresses +- gotify: serverUrl, appToken, priority (decoration optional) +- ntfy: serverUrl, topic, accessToken, priority +- lark: webhookUrl`, + schema: z.object({ + provider: z + .enum(["slack", "discord", "telegram", "email", "gotify", "ntfy", "lark"]) + .describe("The notification provider type."), + // Provider-specific fields (all optional at schema level, validated in handler) + webhookUrl: z + .string() + .min(1) + .optional() + .describe("Webhook URL (required for slack, discord, lark providers)."), + channel: z + .string() + .optional() + .describe("Slack channel (required for slack provider)."), + decoration: z + .boolean() + .optional() + .describe("Rich formatting (optional for discord, gotify providers)."), + botToken: z + .string() + .min(1) + .optional() + .describe("Telegram bot token (required for telegram provider)."), + chatId: z + .string() + .min(1) + .optional() + .describe("Telegram chat ID (required for telegram provider)."), + messageThreadId: z + .string() + .optional() + .describe( + "Telegram message thread ID (required for telegram provider, can be empty string).", + ), + smtpServer: z + .string() + .min(1) + .optional() + .describe("SMTP server hostname (required for email provider)."), + smtpPort: z + .number() + .min(1) + .optional() + .describe("SMTP port, minimum 1 (required for email provider)."), + username: z + .string() + .min(1) + .optional() + .describe("SMTP username (required for email provider)."), + password: z + .string() + .min(1) + .optional() + .describe("SMTP password (required for email provider)."), + fromAddress: z + .string() + .min(1) + .optional() + .describe("From email address (required for email provider)."), + toAddresses: z + .array(z.string()) + .min(1) + .optional() + .describe("To email addresses array (required for email provider)."), + serverUrl: z + .string() + .min(1) + .optional() + .describe("Server URL (required for gotify, ntfy providers)."), + appToken: z + .string() + .min(1) + .optional() + .describe("Application token (required for gotify provider)."), + priority: z + .number() + .min(1) + .optional() + .describe("Priority level, minimum 1 (required for gotify, ntfy providers)."), + topic: z + .string() + .min(1) + .optional() + .describe("Ntfy topic (required for ntfy provider)."), + accessToken: z + .string() + .min(1) + .optional() + .describe("Access token (required for ntfy provider)."), + }), + annotations: { + title: "Test Notification Connection", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + // Validate with discriminated union schema for better type safety + const validationResult = notificationTestConnectionSchema.safeParse(input); + if (!validationResult.success) { + const errorMessages = validationResult.error.errors + .map((err) => `${err.path.join(".")}: ${err.message}`) + .join(", "); + return ResponseFormatter.error( + `Invalid input for ${input.provider} connection test`, + `Validation errors: ${errorMessages}`, + ); + } + + const validatedInput = validationResult.data; + const endpointSuffix = providerEndpointMap[validatedInput.provider]; + + // Remove 'provider' from the payload sent to API + const { provider, ...apiPayload } = validatedInput; + + const response = await apiClient.post( + `/notification.test${endpointSuffix}Connection`, + apiPayload, + ); + + return ResponseFormatter.success( + `${provider.charAt(0).toUpperCase() + provider.slice(1)} connection test completed successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/notification/notificationUpdate.ts b/src/mcp/tools/notification/notificationUpdate.ts new file mode 100644 index 0000000..5ac1297 --- /dev/null +++ b/src/mcp/tools/notification/notificationUpdate.ts @@ -0,0 +1,433 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +// Common notification settings shared by all providers (all optional for updates, based on OpenAPI spec) +const commonUpdateSettings = { + notificationId: z + .string() + .min(1) + .describe("The ID of the notification to update."), + name: z.string().optional().describe("The name of the notification."), + appBuildError: z + .boolean() + .optional() + .describe("Notify on application build errors."), + databaseBackup: z + .boolean() + .optional() + .describe("Notify on database backup events."), + dokployRestart: z + .boolean() + .optional() + .describe("Notify on Dokploy restart events."), + appDeploy: z + .boolean() + .optional() + .describe("Notify on application deployment events."), + dockerCleanup: z + .boolean() + .optional() + .describe("Notify on Docker cleanup events."), + organizationId: z.string().optional().describe("The organization ID."), +}; + +// Provider-specific configurations for updates (matching OpenAPI spec exactly) +// Note: slackId has no minLength in OpenAPI spec (can be empty string) +const slackUpdateConfig = z.object({ + provider: z.literal("slack"), + ...commonUpdateSettings, + slackId: z.string().describe("The Slack configuration ID."), + serverThreshold: z + .boolean() + .optional() + .describe("Notify when server thresholds are exceeded."), + webhookUrl: z + .string() + .min(1) + .optional() + .describe("The Slack webhook URL for sending notifications."), + channel: z + .string() + .optional() + .describe("The Slack channel to send notifications to."), +}); + +const discordUpdateConfig = z.object({ + provider: z.literal("discord"), + ...commonUpdateSettings, + discordId: z.string().min(1).describe("The Discord configuration ID."), + serverThreshold: z + .boolean() + .optional() + .describe("Notify when server thresholds are exceeded."), + webhookUrl: z + .string() + .min(1) + .optional() + .describe("The Discord webhook URL for sending notifications."), + decoration: z + .boolean() + .optional() + .describe("Whether to use rich embed formatting for messages."), +}); + +const telegramUpdateConfig = z.object({ + provider: z.literal("telegram"), + ...commonUpdateSettings, + telegramId: z.string().min(1).describe("The Telegram configuration ID."), + serverThreshold: z + .boolean() + .optional() + .describe("Notify when server thresholds are exceeded."), + botToken: z + .string() + .min(1) + .optional() + .describe("The Telegram bot token for sending notifications."), + chatId: z + .string() + .min(1) + .optional() + .describe("The Telegram chat ID to send notifications to."), + messageThreadId: z + .string() + .optional() + .describe("The message thread ID for topic-based chats."), +}); + +const emailUpdateConfig = z.object({ + provider: z.literal("email"), + ...commonUpdateSettings, + emailId: z.string().min(1).describe("The Email configuration ID."), + serverThreshold: z + .boolean() + .optional() + .describe("Notify when server thresholds are exceeded."), + smtpServer: z + .string() + .min(1) + .optional() + .describe("The SMTP server hostname for sending emails."), + smtpPort: z + .number() + .min(1) + .optional() + .describe("The SMTP server port (minimum 1)."), + username: z + .string() + .min(1) + .optional() + .describe("The SMTP authentication username."), + password: z + .string() + .min(1) + .optional() + .describe("The SMTP authentication password."), + fromAddress: z + .string() + .min(1) + .optional() + .describe("The email address to send notifications from."), + toAddresses: z + .array(z.string()) + .min(1) + .optional() + .describe("The email addresses to send notifications to."), +}); + +// Note: Gotify does NOT have serverThreshold according to OpenAPI spec +const gotifyUpdateConfig = z.object({ + provider: z.literal("gotify"), + ...commonUpdateSettings, + gotifyId: z.string().min(1).describe("The Gotify configuration ID."), + serverUrl: z + .string() + .min(1) + .optional() + .describe("The Gotify server URL for sending notifications."), + appToken: z + .string() + .min(1) + .optional() + .describe("The Gotify application token for authentication."), + priority: z + .number() + .min(1) + .optional() + .describe("The priority level for Gotify messages (minimum 1)."), + decoration: z + .boolean() + .optional() + .describe("Whether to use rich formatting for messages."), +}); + +// Note: Ntfy does NOT have serverThreshold according to OpenAPI spec +const ntfyUpdateConfig = z.object({ + provider: z.literal("ntfy"), + ...commonUpdateSettings, + ntfyId: z.string().min(1).describe("The Ntfy configuration ID."), + serverUrl: z + .string() + .min(1) + .optional() + .describe("The Ntfy server URL for sending notifications."), + topic: z + .string() + .min(1) + .optional() + .describe("The Ntfy topic to send notifications to."), + accessToken: z + .string() + .min(1) + .optional() + .describe("The Ntfy access token for authentication."), + priority: z + .number() + .min(1) + .optional() + .describe("The priority level for Ntfy messages (minimum 1)."), +}); + +const larkUpdateConfig = z.object({ + provider: z.literal("lark"), + ...commonUpdateSettings, + larkId: z.string().min(1).describe("The Lark configuration ID."), + serverThreshold: z + .boolean() + .optional() + .describe("Notify when server thresholds are exceeded."), + webhookUrl: z + .string() + .min(1) + .optional() + .describe("The Lark webhook URL for sending notifications."), +}); + +// Discriminated union of all provider update configs +const notificationUpdateSchema = z.discriminatedUnion("provider", [ + slackUpdateConfig, + discordUpdateConfig, + telegramUpdateConfig, + emailUpdateConfig, + gotifyUpdateConfig, + ntfyUpdateConfig, + larkUpdateConfig, +]); + +type NotificationUpdateInput = z.infer; + +// Map provider to API endpoint suffix +const providerEndpointMap: Record = + { + slack: "Slack", + discord: "Discord", + telegram: "Telegram", + email: "Email", + gotify: "Gotify", + ntfy: "Ntfy", + lark: "Lark", + }; + +export const notificationUpdate = createTool({ + name: "notification-update", + description: `Updates an existing notification configuration in Dokploy. Supports multiple providers: slack, discord, telegram, email, gotify, ntfy, lark. Each provider requires notificationId and a provider-specific ID field. + +Required fields for all providers: +- notificationId: The notification ID to update +- provider-specific ID (slackId, discordId, telegramId, emailId, gotifyId, ntfyId, larkId) + +Note: serverThreshold is available for slack, discord, telegram, email, lark but NOT for gotify or ntfy. + +All other fields are optional and only the provided fields will be updated.`, + schema: z.object({ + provider: z + .enum(["slack", "discord", "telegram", "email", "gotify", "ntfy", "lark"]) + .describe("The notification provider type."), + notificationId: z + .string() + .min(1) + .describe("The ID of the notification to update."), + // Provider-specific ID fields (all optional at schema level, validated in handler) + // Note: slackId has no minLength in OpenAPI spec + slackId: z + .string() + .optional() + .describe("The Slack configuration ID (required for slack provider)."), + discordId: z + .string() + .min(1) + .optional() + .describe( + "The Discord configuration ID (required for discord provider).", + ), + telegramId: z + .string() + .min(1) + .optional() + .describe( + "The Telegram configuration ID (required for telegram provider).", + ), + emailId: z + .string() + .min(1) + .optional() + .describe("The Email configuration ID (required for email provider)."), + gotifyId: z + .string() + .min(1) + .optional() + .describe("The Gotify configuration ID (required for gotify provider)."), + ntfyId: z + .string() + .min(1) + .optional() + .describe("The Ntfy configuration ID (required for ntfy provider)."), + larkId: z + .string() + .min(1) + .optional() + .describe("The Lark configuration ID (required for lark provider)."), + // Common optional fields + name: z.string().optional().describe("The name of the notification."), + appBuildError: z + .boolean() + .optional() + .describe("Notify on application build errors."), + databaseBackup: z + .boolean() + .optional() + .describe("Notify on database backup events."), + dokployRestart: z + .boolean() + .optional() + .describe("Notify on Dokploy restart events."), + appDeploy: z + .boolean() + .optional() + .describe("Notify on application deployment events."), + dockerCleanup: z + .boolean() + .optional() + .describe("Notify on Docker cleanup events."), + organizationId: z.string().optional().describe("The organization ID."), + // Provider-specific optional fields + webhookUrl: z + .string() + .min(1) + .optional() + .describe("Webhook URL (for slack, discord, lark providers)."), + channel: z.string().optional().describe("Slack channel (for slack)."), + decoration: z + .boolean() + .optional() + .describe("Rich formatting (for discord, gotify)."), + serverThreshold: z + .boolean() + .optional() + .describe( + "Notify on server threshold (for slack, discord, telegram, email, lark). NOT available for gotify or ntfy.", + ), + botToken: z + .string() + .min(1) + .optional() + .describe("Telegram bot token (for telegram)."), + chatId: z + .string() + .min(1) + .optional() + .describe("Telegram chat ID (for telegram)."), + messageThreadId: z + .string() + .optional() + .describe("Telegram message thread ID (for telegram)."), + smtpServer: z + .string() + .min(1) + .optional() + .describe("SMTP server hostname (for email)."), + smtpPort: z + .number() + .min(1) + .optional() + .describe("SMTP port, minimum 1 (for email)."), + username: z + .string() + .min(1) + .optional() + .describe("SMTP username (for email)."), + password: z + .string() + .min(1) + .optional() + .describe("SMTP password (for email)."), + fromAddress: z + .string() + .min(1) + .optional() + .describe("From email address (for email)."), + toAddresses: z + .array(z.string()) + .min(1) + .optional() + .describe("To email addresses array (for email)."), + serverUrl: z + .string() + .min(1) + .optional() + .describe("Server URL (for gotify, ntfy)."), + appToken: z + .string() + .min(1) + .optional() + .describe("Application token (for gotify)."), + priority: z + .number() + .min(1) + .optional() + .describe("Priority level, minimum 1 (for gotify, ntfy)."), + topic: z.string().min(1).optional().describe("Ntfy topic (for ntfy)."), + accessToken: z + .string() + .min(1) + .optional() + .describe("Access token (for ntfy)."), + }), + annotations: { + title: "Update Notification", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + // Validate with discriminated union schema for better type safety + const validationResult = notificationUpdateSchema.safeParse(input); + if (!validationResult.success) { + const errorMessages = validationResult.error.errors + .map((err) => `${err.path.join(".")}: ${err.message}`) + .join(", "); + return ResponseFormatter.error( + `Invalid input for ${input.provider} notification update`, + `Validation errors: ${errorMessages}`, + ); + } + + const validatedInput = validationResult.data; + const endpointSuffix = providerEndpointMap[validatedInput.provider]; + + // Remove 'provider' from the payload sent to API + const { provider, ...apiPayload } = validatedInput; + + const response = await apiClient.post( + `/notification.update${endpointSuffix}`, + apiPayload, + ); + + return ResponseFormatter.success( + `${provider.charAt(0).toUpperCase() + provider.slice(1)} notification "${input.notificationId}" updated successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/organization/index.ts b/src/mcp/tools/organization/index.ts new file mode 100644 index 0000000..42844a0 --- /dev/null +++ b/src/mcp/tools/organization/index.ts @@ -0,0 +1,8 @@ +export { organizationAll } from "./organizationAll.js"; +export { organizationAllInvitations } from "./organizationAllInvitations.js"; +export { organizationCreate } from "./organizationCreate.js"; +export { organizationDelete } from "./organizationDelete.js"; +export { organizationOne } from "./organizationOne.js"; +export { organizationRemoveInvitation } from "./organizationRemoveInvitation.js"; +export { organizationSetDefault } from "./organizationSetDefault.js"; +export { organizationUpdate } from "./organizationUpdate.js"; diff --git a/src/mcp/tools/organization/organizationAll.ts b/src/mcp/tools/organization/organizationAll.ts new file mode 100644 index 0000000..27e4988 --- /dev/null +++ b/src/mcp/tools/organization/organizationAll.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const organizationAll = createTool({ + name: "organization-all", + description: "Gets all organizations in Dokploy.", + schema: z.object({}), + annotations: { + title: "Get All Organizations", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const response = await apiClient.get("/organization.all"); + + return ResponseFormatter.success( + "Successfully fetched all organizations", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/organization/organizationAllInvitations.ts b/src/mcp/tools/organization/organizationAllInvitations.ts new file mode 100644 index 0000000..c83ee70 --- /dev/null +++ b/src/mcp/tools/organization/organizationAllInvitations.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const organizationAllInvitations = createTool({ + name: "organization-all-invitations", + description: "Gets all invitations for organizations in Dokploy.", + schema: z.object({}), + annotations: { + title: "Get All Organization Invitations", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const response = await apiClient.get("/organization.allInvitations"); + + return ResponseFormatter.success( + "Successfully fetched all organization invitations", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/organization/organizationCreate.ts b/src/mcp/tools/organization/organizationCreate.ts new file mode 100644 index 0000000..7827543 --- /dev/null +++ b/src/mcp/tools/organization/organizationCreate.ts @@ -0,0 +1,30 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const organizationCreate = createTool({ + name: "organization-create", + description: "Creates a new organization in Dokploy.", + schema: z.object({ + name: z.string().describe("The name of the organization. Required field."), + logo: z + .string() + .optional() + .describe("Optional logo URL or base64-encoded image for the organization."), + }), + annotations: { + title: "Create Organization", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/organization.create", input); + + return ResponseFormatter.success( + `Organization "${input.name}" created successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/organization/organizationDelete.ts b/src/mcp/tools/organization/organizationDelete.ts new file mode 100644 index 0000000..da3adca --- /dev/null +++ b/src/mcp/tools/organization/organizationDelete.ts @@ -0,0 +1,28 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const organizationDelete = createTool({ + name: "organization-delete", + description: "Deletes an organization from Dokploy.", + schema: z.object({ + organizationId: z + .string() + .describe("The ID of the organization to delete."), + }), + annotations: { + title: "Delete Organization", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/organization.delete", input); + + return ResponseFormatter.success( + `Organization "${input.organizationId}" deleted successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/organization/organizationOne.ts b/src/mcp/tools/organization/organizationOne.ts new file mode 100644 index 0000000..af665a4 --- /dev/null +++ b/src/mcp/tools/organization/organizationOne.ts @@ -0,0 +1,37 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const organizationOne = createTool({ + name: "organization-one", + description: "Gets a specific organization by its ID in Dokploy.", + schema: z.object({ + organizationId: z + .string() + .describe("The ID of the organization to retrieve."), + }), + annotations: { + title: "Get Organization Details", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.get( + `/organization.one?organizationId=${input.organizationId}`, + ); + + if (!response?.data) { + return ResponseFormatter.error( + "Failed to fetch organization", + `Organization with ID "${input.organizationId}" not found`, + ); + } + + return ResponseFormatter.success( + `Successfully fetched organization "${input.organizationId}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/organization/organizationRemoveInvitation.ts b/src/mcp/tools/organization/organizationRemoveInvitation.ts new file mode 100644 index 0000000..481bcdb --- /dev/null +++ b/src/mcp/tools/organization/organizationRemoveInvitation.ts @@ -0,0 +1,29 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const organizationRemoveInvitation = createTool({ + name: "organization-remove-invitation", + description: "Removes an invitation from an organization in Dokploy.", + schema: z.object({ + invitationId: z.string().describe("The ID of the invitation to remove."), + }), + annotations: { + title: "Remove Organization Invitation", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post( + "/organization.removeInvitation", + input, + ); + + return ResponseFormatter.success( + `Invitation "${input.invitationId}" removed successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/organization/organizationSetDefault.ts b/src/mcp/tools/organization/organizationSetDefault.ts new file mode 100644 index 0000000..ecd15b4 --- /dev/null +++ b/src/mcp/tools/organization/organizationSetDefault.ts @@ -0,0 +1,29 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const organizationSetDefault = createTool({ + name: "organization-set-default", + description: "Sets the default organization for the current user in Dokploy.", + schema: z.object({ + organizationId: z + .string() + .min(1) + .describe("The ID of the organization to set as default."), + }), + annotations: { + title: "Set Default Organization", + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/organization.setDefault", input); + + return ResponseFormatter.success( + `Organization "${input.organizationId}" set as default successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/organization/organizationUpdate.ts b/src/mcp/tools/organization/organizationUpdate.ts new file mode 100644 index 0000000..2edf4fb --- /dev/null +++ b/src/mcp/tools/organization/organizationUpdate.ts @@ -0,0 +1,37 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const organizationUpdate = createTool({ + name: "organization-update", + description: "Updates an existing organization in Dokploy.", + schema: z.object({ + organizationId: z + .string() + .describe("The ID of the organization to update. Required field."), + name: z + .string() + .describe("The new name for the organization. Required field."), + logo: z + .string() + .optional() + .describe( + "Optional new logo URL or base64-encoded image for the organization.", + ), + }), + annotations: { + title: "Update Organization", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/organization.update", input); + + return ResponseFormatter.success( + `Organization "${input.organizationId}" updated successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/port/index.ts b/src/mcp/tools/port/index.ts new file mode 100644 index 0000000..33f8ca7 --- /dev/null +++ b/src/mcp/tools/port/index.ts @@ -0,0 +1,4 @@ +export { portCreate } from "./portCreate.js"; +export { portOne } from "./portOne.js"; +export { portDelete } from "./portDelete.js"; +export { portUpdate } from "./portUpdate.js"; diff --git a/src/mcp/tools/port/portCreate.ts b/src/mcp/tools/port/portCreate.ts new file mode 100644 index 0000000..9d1de81 --- /dev/null +++ b/src/mcp/tools/port/portCreate.ts @@ -0,0 +1,47 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const portCreate = createTool({ + name: "port-create", + description: "Creates a new port mapping for an application in Dokploy.", + schema: z.object({ + applicationId: z + .string() + .min(1) + .describe("The ID of the application to add the port mapping to. Required."), + publishedPort: z + .number() + .describe("The external port to expose on the host machine. Required."), + targetPort: z + .number() + .describe("The internal port the container is listening on. Required."), + publishMode: z + .enum(["ingress", "host"]) + .default("ingress") + .optional() + .describe( + "Port publish mode: 'ingress' for load-balanced routing across swarm nodes, 'host' for direct host port binding. Defaults to 'ingress'." + ), + protocol: z + .enum(["tcp", "udp"]) + .default("tcp") + .optional() + .describe("Network protocol for the port mapping: 'tcp' or 'udp'. Defaults to 'tcp'."), + }), + annotations: { + title: "Create Port Mapping", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/port.create", input); + + return ResponseFormatter.success( + `Port mapping ${input.publishedPort}:${input.targetPort} created successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/port/portDelete.ts b/src/mcp/tools/port/portDelete.ts new file mode 100644 index 0000000..fd2239c --- /dev/null +++ b/src/mcp/tools/port/portDelete.ts @@ -0,0 +1,29 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const portDelete = createTool({ + name: "port-delete", + description: "Deletes a port mapping from Dokploy.", + schema: z.object({ + portId: z + .string() + .min(1) + .describe("The unique identifier of the port mapping to delete. Required."), + }), + annotations: { + title: "Delete Port Mapping", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/port.delete", input); + + return ResponseFormatter.success( + `Port mapping "${input.portId}" deleted successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/port/portOne.ts b/src/mcp/tools/port/portOne.ts new file mode 100644 index 0000000..b3d38e3 --- /dev/null +++ b/src/mcp/tools/port/portOne.ts @@ -0,0 +1,36 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const portOne = createTool({ + name: "port-one", + description: "Gets a specific port mapping by its ID in Dokploy.", + schema: z.object({ + portId: z + .string() + .min(1) + .describe("The unique identifier of the port mapping to retrieve. Required."), + }), + annotations: { + title: "Get Port Mapping Details", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const port = await apiClient.get(`/port.one?portId=${input.portId}`); + + if (!port?.data) { + return ResponseFormatter.error( + "Failed to fetch port mapping", + `Port mapping with ID "${input.portId}" not found`, + ); + } + + return ResponseFormatter.success( + `Successfully fetched port mapping "${input.portId}"`, + port.data, + ); + }, +}); diff --git a/src/mcp/tools/port/portUpdate.ts b/src/mcp/tools/port/portUpdate.ts new file mode 100644 index 0000000..1312946 --- /dev/null +++ b/src/mcp/tools/port/portUpdate.ts @@ -0,0 +1,44 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const portUpdate = createTool({ + name: "port-update", + description: "Updates an existing port mapping in Dokploy.", + schema: z.object({ + portId: z.string().min(1).describe("The ID of the port mapping to update. Required."), + publishedPort: z + .number() + .describe("The external port to expose on the host machine. Required."), + targetPort: z + .number() + .describe("The internal port the container is listening on. Required."), + publishMode: z + .enum(["ingress", "host"]) + .default("ingress") + .optional() + .describe( + "Port publish mode: 'ingress' for load-balanced routing across swarm nodes, 'host' for direct host port binding. Defaults to 'ingress'." + ), + protocol: z + .enum(["tcp", "udp"]) + .default("tcp") + .optional() + .describe("Network protocol for the port mapping: 'tcp' or 'udp'. Defaults to 'tcp'."), + }), + annotations: { + title: "Update Port Mapping", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/port.update", input); + + return ResponseFormatter.success( + `Port mapping "${input.portId}" updated successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/previewDeployment/index.ts b/src/mcp/tools/previewDeployment/index.ts new file mode 100644 index 0000000..7119a39 --- /dev/null +++ b/src/mcp/tools/previewDeployment/index.ts @@ -0,0 +1,3 @@ +export { previewDeploymentAll } from "./previewDeploymentAll.js"; +export { previewDeploymentDelete } from "./previewDeploymentDelete.js"; +export { previewDeploymentOne } from "./previewDeploymentOne.js"; diff --git a/src/mcp/tools/previewDeployment/previewDeploymentAll.ts b/src/mcp/tools/previewDeployment/previewDeploymentAll.ts new file mode 100644 index 0000000..23888d9 --- /dev/null +++ b/src/mcp/tools/previewDeployment/previewDeploymentAll.ts @@ -0,0 +1,31 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const previewDeploymentAll = createTool({ + name: "preview-deployment-all", + description: "Gets all preview deployments for an application in Dokploy.", + schema: z.object({ + applicationId: z + .string() + .min(1) + .describe("The unique identifier of the application to get preview deployments for. Required."), + }), + annotations: { + title: "Get All Preview Deployments", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.get( + `/previewDeployment.all?applicationId=${input.applicationId}`, + ); + + return ResponseFormatter.success( + `Successfully fetched preview deployments for application "${input.applicationId}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/previewDeployment/previewDeploymentDelete.ts b/src/mcp/tools/previewDeployment/previewDeploymentDelete.ts new file mode 100644 index 0000000..dd6d76a --- /dev/null +++ b/src/mcp/tools/previewDeployment/previewDeploymentDelete.ts @@ -0,0 +1,31 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const previewDeploymentDelete = createTool({ + name: "preview-deployment-delete", + description: "Deletes a preview deployment in Dokploy.", + schema: z.object({ + previewDeploymentId: z + .string() + .describe("The unique identifier of the preview deployment to delete. Required."), + }), + annotations: { + title: "Delete Preview Deployment", + readOnlyHint: false, + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/previewDeployment.delete", { + previewDeploymentId: input.previewDeploymentId, + }); + + return ResponseFormatter.success( + `Successfully deleted preview deployment "${input.previewDeploymentId}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/previewDeployment/previewDeploymentOne.ts b/src/mcp/tools/previewDeployment/previewDeploymentOne.ts new file mode 100644 index 0000000..4c2bbac --- /dev/null +++ b/src/mcp/tools/previewDeployment/previewDeploymentOne.ts @@ -0,0 +1,37 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const previewDeploymentOne = createTool({ + name: "preview-deployment-one", + description: "Gets a specific preview deployment by its ID in Dokploy.", + schema: z.object({ + previewDeploymentId: z + .string() + .describe("The unique identifier of the preview deployment to retrieve. Required."), + }), + annotations: { + title: "Get Preview Deployment Details", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.get( + `/previewDeployment.one?previewDeploymentId=${input.previewDeploymentId}`, + ); + + if (!response?.data) { + return ResponseFormatter.error( + "Failed to fetch preview deployment", + `Preview deployment with ID "${input.previewDeploymentId}" not found`, + ); + } + + return ResponseFormatter.success( + `Successfully fetched preview deployment "${input.previewDeploymentId}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/redirects/index.ts b/src/mcp/tools/redirects/index.ts new file mode 100644 index 0000000..e05caa5 --- /dev/null +++ b/src/mcp/tools/redirects/index.ts @@ -0,0 +1,4 @@ +export { redirectsCreate } from "./redirectsCreate.js"; +export { redirectsOne } from "./redirectsOne.js"; +export { redirectsDelete } from "./redirectsDelete.js"; +export { redirectsUpdate } from "./redirectsUpdate.js"; diff --git a/src/mcp/tools/redirects/redirectsCreate.ts b/src/mcp/tools/redirects/redirectsCreate.ts new file mode 100644 index 0000000..4c6e6da --- /dev/null +++ b/src/mcp/tools/redirects/redirectsCreate.ts @@ -0,0 +1,45 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const redirectsCreate = createTool({ + name: "redirects-create", + description: "Creates a new redirect rule for an application in Dokploy.", + schema: z.object({ + applicationId: z + .string() + .describe("The ID of the application to add the redirect to. Required."), + regex: z + .string() + .min(1) + .describe( + "Regex pattern to match incoming request URLs. Uses Traefik regex syntax. Required." + ), + replacement: z + .string() + .min(1) + .describe( + "Replacement URL or path for matched requests. Can use capture groups from regex (e.g., $1). Required." + ), + permanent: z + .boolean() + .describe( + "Whether this is a permanent redirect (HTTP 301) or temporary (HTTP 302). Required." + ), + }), + annotations: { + title: "Create Redirect", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/redirects.create", input); + + return ResponseFormatter.success( + `Redirect rule created successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/redirects/redirectsDelete.ts b/src/mcp/tools/redirects/redirectsDelete.ts new file mode 100644 index 0000000..3c1e9c4 --- /dev/null +++ b/src/mcp/tools/redirects/redirectsDelete.ts @@ -0,0 +1,29 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const redirectsDelete = createTool({ + name: "redirects-delete", + description: "Deletes a redirect rule from Dokploy.", + schema: z.object({ + redirectId: z + .string() + .min(1) + .describe("The unique identifier of the redirect rule to delete. Required."), + }), + annotations: { + title: "Delete Redirect", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/redirects.delete", input); + + return ResponseFormatter.success( + `Redirect "${input.redirectId}" deleted successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/redirects/redirectsOne.ts b/src/mcp/tools/redirects/redirectsOne.ts new file mode 100644 index 0000000..d756568 --- /dev/null +++ b/src/mcp/tools/redirects/redirectsOne.ts @@ -0,0 +1,38 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const redirectsOne = createTool({ + name: "redirects-one", + description: "Gets a specific redirect rule by its ID in Dokploy.", + schema: z.object({ + redirectId: z + .string() + .min(1) + .describe("The unique identifier of the redirect rule to retrieve. Required."), + }), + annotations: { + title: "Get Redirect Details", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const redirect = await apiClient.get( + `/redirects.one?redirectId=${input.redirectId}`, + ); + + if (!redirect?.data) { + return ResponseFormatter.error( + "Failed to fetch redirect", + `Redirect with ID "${input.redirectId}" not found`, + ); + } + + return ResponseFormatter.success( + `Successfully fetched redirect "${input.redirectId}"`, + redirect.data, + ); + }, +}); diff --git a/src/mcp/tools/redirects/redirectsUpdate.ts b/src/mcp/tools/redirects/redirectsUpdate.ts new file mode 100644 index 0000000..cbd76f6 --- /dev/null +++ b/src/mcp/tools/redirects/redirectsUpdate.ts @@ -0,0 +1,46 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const redirectsUpdate = createTool({ + name: "redirects-update", + description: "Updates an existing redirect rule in Dokploy.", + schema: z.object({ + redirectId: z + .string() + .min(1) + .describe("The ID of the redirect to update. Required."), + regex: z + .string() + .min(1) + .describe( + "Regex pattern to match incoming request URLs. Uses Traefik regex syntax. Required." + ), + replacement: z + .string() + .min(1) + .describe( + "Replacement URL or path for matched requests. Can use capture groups from regex (e.g., $1). Required." + ), + permanent: z + .boolean() + .describe( + "Whether this is a permanent redirect (HTTP 301) or temporary (HTTP 302). Required." + ), + }), + annotations: { + title: "Update Redirect", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/redirects.update", input); + + return ResponseFormatter.success( + `Redirect "${input.redirectId}" updated successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/registry/index.ts b/src/mcp/tools/registry/index.ts new file mode 100644 index 0000000..28f1431 --- /dev/null +++ b/src/mcp/tools/registry/index.ts @@ -0,0 +1,6 @@ +export { registryCreate } from "./registryCreate.js"; +export { registryRemove } from "./registryRemove.js"; +export { registryUpdate } from "./registryUpdate.js"; +export { registryAll } from "./registryAll.js"; +export { registryOne } from "./registryOne.js"; +export { registryTestRegistry } from "./registryTestRegistry.js"; diff --git a/src/mcp/tools/registry/registryAll.ts b/src/mcp/tools/registry/registryAll.ts new file mode 100644 index 0000000..5cfb981 --- /dev/null +++ b/src/mcp/tools/registry/registryAll.ts @@ -0,0 +1,31 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const registryAll = createTool({ + name: "registry-all", + description: "Lists all container registries in Dokploy.", + schema: z.object({}), + annotations: { + title: "List All Registries", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const registries = await apiClient.get("/registry.all"); + + if (!registries?.data) { + return ResponseFormatter.error( + "Failed to fetch registries", + "No registries found", + ); + } + + return ResponseFormatter.success( + `Successfully fetched all registries`, + registries.data, + ); + }, +}); diff --git a/src/mcp/tools/registry/registryCreate.ts b/src/mcp/tools/registry/registryCreate.ts new file mode 100644 index 0000000..e96bf0f --- /dev/null +++ b/src/mcp/tools/registry/registryCreate.ts @@ -0,0 +1,44 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const registryCreate = createTool({ + name: "registry-create", + description: "Creates a new container registry in Dokploy.", + schema: z.object({ + registryName: z.string().min(1).describe("A friendly name for the registry. Required."), + username: z + .string() + .min(1) + .describe("Username for registry authentication. Required."), + password: z + .string() + .min(1) + .describe("Password or access token for registry authentication. Required."), + registryUrl: z.string().describe("URL of the container registry (e.g., 'docker.io', 'ghcr.io', 'registry.example.com'). Required."), + registryType: z.enum(["cloud"]).describe("Type of the registry. Currently only 'cloud' is supported. Required."), + imagePrefix: z + .string() + .nullable() + .describe("Prefix for images in this registry (e.g., 'myorg' for 'myorg/image:tag'). Required, can be null."), + serverId: z + .string() + .optional() + .describe("Server ID to associate the registry with. Optional."), + }), + annotations: { + title: "Create Registry", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/registry.create", input); + + return ResponseFormatter.success( + `Registry "${input.registryName}" created successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/registry/registryOne.ts b/src/mcp/tools/registry/registryOne.ts new file mode 100644 index 0000000..17ffe15 --- /dev/null +++ b/src/mcp/tools/registry/registryOne.ts @@ -0,0 +1,38 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const registryOne = createTool({ + name: "registry-one", + description: "Gets a specific container registry by its ID in Dokploy.", + schema: z.object({ + registryId: z + .string() + .min(1) + .describe("The unique identifier of the registry to retrieve. Required."), + }), + annotations: { + title: "Get Registry Details", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const registry = await apiClient.get( + `/registry.one?registryId=${encodeURIComponent(input.registryId)}`, + ); + + if (!registry?.data) { + return ResponseFormatter.error( + "Failed to fetch registry", + `Registry with ID "${input.registryId}" not found`, + ); + } + + return ResponseFormatter.success( + `Successfully fetched registry "${input.registryId}"`, + registry.data, + ); + }, +}); diff --git a/src/mcp/tools/registry/registryRemove.ts b/src/mcp/tools/registry/registryRemove.ts new file mode 100644 index 0000000..bfcf36c --- /dev/null +++ b/src/mcp/tools/registry/registryRemove.ts @@ -0,0 +1,26 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const registryRemove = createTool({ + name: "registry-remove", + description: "Removes/deletes a container registry from Dokploy.", + schema: z.object({ + registryId: z.string().min(1).describe("The unique identifier of the registry to remove. Required."), + }), + annotations: { + title: "Remove Registry", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/registry.remove", input); + + return ResponseFormatter.success( + `Registry "${input.registryId}" removed successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/registry/registryTestRegistry.ts b/src/mcp/tools/registry/registryTestRegistry.ts new file mode 100644 index 0000000..e24dbce --- /dev/null +++ b/src/mcp/tools/registry/registryTestRegistry.ts @@ -0,0 +1,45 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const registryTestRegistry = createTool({ + name: "registry-test", + description: "Tests connection to a container registry in Dokploy.", + schema: z.object({ + registryName: z.string().optional().describe("Name for the registry. Optional."), + username: z + .string() + .min(1) + .describe("Username for registry authentication. Required."), + password: z + .string() + .min(1) + .describe("Password or access token for registry authentication. Required."), + registryUrl: z.string().describe("URL of the container registry to test (e.g., 'docker.io', 'ghcr.io'). Required."), + registryType: z.enum(["cloud"]).describe("Type of the registry. Currently only 'cloud' is supported. Required."), + imagePrefix: z + .string() + .nullable() + .optional() + .describe("Prefix for images in this registry. Optional."), + serverId: z + .string() + .optional() + .describe("Server ID to run the test from. Optional."), + }), + annotations: { + title: "Test Registry Connection", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/registry.testRegistry", input); + + return ResponseFormatter.success( + `Registry connection test completed successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/registry/registryUpdate.ts b/src/mcp/tools/registry/registryUpdate.ts new file mode 100644 index 0000000..a49484d --- /dev/null +++ b/src/mcp/tools/registry/registryUpdate.ts @@ -0,0 +1,64 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const registryUpdate = createTool({ + name: "registry-update", + description: "Updates an existing container registry in Dokploy.", + schema: z.object({ + registryId: z.string().min(1).describe("The unique identifier of the registry to update. Required."), + registryName: z + .string() + .min(1) + .optional() + .describe("New friendly name for the registry. Optional."), + imagePrefix: z + .string() + .nullable() + .optional() + .describe("New prefix for images in this registry (e.g., 'myorg' for 'myorg/image:tag'). Optional, can be null."), + username: z + .string() + .min(1) + .optional() + .describe("New username for registry authentication. Optional."), + password: z + .string() + .min(1) + .optional() + .describe("New password or access token for registry authentication. Optional."), + registryUrl: z + .string() + .optional() + .describe("New URL of the container registry. Optional."), + createdAt: z.string().optional().describe("Creation timestamp. Usually not modified. Optional."), + registryType: z + .enum(["cloud"]) + .optional() + .describe("Type of the registry. Currently only 'cloud' is supported. Optional."), + organizationId: z + .string() + .min(1) + .optional() + .describe("Organization ID for the registry. Optional."), + serverId: z + .string() + .optional() + .describe("Server ID to associate the registry with. Optional."), + }), + annotations: { + title: "Update Registry", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/registry.update", input); + + return ResponseFormatter.success( + `Registry "${input.registryId}" updated successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/rollback/index.ts b/src/mcp/tools/rollback/index.ts new file mode 100644 index 0000000..a784c07 --- /dev/null +++ b/src/mcp/tools/rollback/index.ts @@ -0,0 +1,2 @@ +export { rollbackDelete } from "./rollbackDelete.js"; +export { rollbackRollback } from "./rollbackRollback.js"; diff --git a/src/mcp/tools/rollback/rollbackDelete.ts b/src/mcp/tools/rollback/rollbackDelete.ts new file mode 100644 index 0000000..65f8506 --- /dev/null +++ b/src/mcp/tools/rollback/rollbackDelete.ts @@ -0,0 +1,29 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const rollbackDelete = createTool({ + name: "rollback-delete", + description: "Deletes a rollback entry in Dokploy.", + schema: z.object({ + rollbackId: z.string().min(1).describe("The unique identifier of the rollback entry to delete. Required."), + }), + annotations: { + title: "Delete Rollback", + readOnlyHint: false, + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/rollback.delete", { + rollbackId: input.rollbackId, + }); + + return ResponseFormatter.success( + `Successfully deleted rollback "${input.rollbackId}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/rollback/rollbackRollback.ts b/src/mcp/tools/rollback/rollbackRollback.ts new file mode 100644 index 0000000..efb1306 --- /dev/null +++ b/src/mcp/tools/rollback/rollbackRollback.ts @@ -0,0 +1,32 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const rollbackRollback = createTool({ + name: "rollback-rollback", + description: "Performs a rollback to a previous deployment in Dokploy.", + schema: z.object({ + rollbackId: z + .string() + .min(1) + .describe("The unique identifier of the rollback to execute. This will restore the application to the state captured in this rollback. Required."), + }), + annotations: { + title: "Execute Rollback", + readOnlyHint: false, + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/rollback.rollback", { + rollbackId: input.rollbackId, + }); + + return ResponseFormatter.success( + `Successfully executed rollback "${input.rollbackId}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/schedule/index.ts b/src/mcp/tools/schedule/index.ts new file mode 100644 index 0000000..b8d83a7 --- /dev/null +++ b/src/mcp/tools/schedule/index.ts @@ -0,0 +1,6 @@ +export { scheduleCreate } from "./scheduleCreate.js"; +export { scheduleUpdate } from "./scheduleUpdate.js"; +export { scheduleDelete } from "./scheduleDelete.js"; +export { scheduleList } from "./scheduleList.js"; +export { scheduleOne } from "./scheduleOne.js"; +export { scheduleRunManually } from "./scheduleRunManually.js"; diff --git a/src/mcp/tools/schedule/scheduleCreate.ts b/src/mcp/tools/schedule/scheduleCreate.ts new file mode 100644 index 0000000..d3731dc --- /dev/null +++ b/src/mcp/tools/schedule/scheduleCreate.ts @@ -0,0 +1,71 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const scheduleCreate = createTool({ + name: "schedule-create", + description: "Creates a new scheduled task in Dokploy.", + schema: z.object({ + name: z.string().describe("The name of the scheduled task. Required."), + cronExpression: z + .string() + .describe("The cron expression defining when the task runs (e.g., '0 0 * * *' for daily at midnight). Required."), + command: z.string().describe("The command to execute when the schedule runs. Required."), + scheduleId: z.string().optional().describe("Optional schedule ID. If not provided, one will be generated."), + appName: z.string().optional().describe("The application name for the scheduled task."), + serviceName: z + .string() + .nullable() + .optional() + .describe("The service name for compose applications. Only applicable when scheduleType is 'compose'."), + shellType: z + .enum(["bash", "sh"]) + .optional() + .describe("The shell type to use for command execution. Either 'bash' or 'sh'."), + scheduleType: z + .enum(["application", "compose", "server", "dokploy-server"]) + .optional() + .describe("The type of schedule: 'application' for app-level, 'compose' for compose services, 'server' for remote servers, or 'dokploy-server' for the Dokploy server itself."), + script: z + .string() + .nullable() + .optional() + .describe("The script content to execute. Can be used instead of or in addition to command."), + applicationId: z + .string() + .nullable() + .optional() + .describe("The application ID. Required when scheduleType is 'application'."), + composeId: z + .string() + .nullable() + .optional() + .describe("The compose ID. Required when scheduleType is 'compose'."), + serverId: z + .string() + .nullable() + .optional() + .describe("The server ID. Required when scheduleType is 'server'."), + userId: z.string().nullable().optional().describe("The user ID who owns this schedule."), + enabled: z + .boolean() + .optional() + .describe("Whether the schedule is enabled and will run. Defaults to true."), + createdAt: z.string().optional().describe("The creation timestamp. Usually auto-generated."), + }), + annotations: { + title: "Create Schedule", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/schedule.create", input); + + return ResponseFormatter.success( + `Schedule "${input.name}" created successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/schedule/scheduleDelete.ts b/src/mcp/tools/schedule/scheduleDelete.ts new file mode 100644 index 0000000..56984ee --- /dev/null +++ b/src/mcp/tools/schedule/scheduleDelete.ts @@ -0,0 +1,26 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const scheduleDelete = createTool({ + name: "schedule-delete", + description: "Deletes a scheduled task from Dokploy.", + schema: z.object({ + scheduleId: z.string().describe("The unique identifier of the schedule to delete. Required."), + }), + annotations: { + title: "Delete Schedule", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/schedule.delete", input); + + return ResponseFormatter.success( + `Schedule "${input.scheduleId}" deleted successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/schedule/scheduleList.ts b/src/mcp/tools/schedule/scheduleList.ts new file mode 100644 index 0000000..ad77aa3 --- /dev/null +++ b/src/mcp/tools/schedule/scheduleList.ts @@ -0,0 +1,35 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const scheduleList = createTool({ + name: "schedule-list", + description: "Lists all scheduled tasks for a specific resource in Dokploy.", + schema: z.object({ + id: z + .string() + .describe( + "The ID of the resource (applicationId, composeId, or serverId) to list schedules for. Required.", + ), + scheduleType: z + .enum(["application", "compose", "server", "dokploy-server"]) + .describe("The type of schedules to list: 'application', 'compose', 'server', or 'dokploy-server'. Required."), + }), + annotations: { + title: "List Schedules", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const schedules = await apiClient.get( + `/schedule.list?id=${input.id}&scheduleType=${input.scheduleType}`, + ); + + return ResponseFormatter.success( + `Successfully fetched schedules for ${input.scheduleType} "${input.id}"`, + schedules.data, + ); + }, +}); diff --git a/src/mcp/tools/schedule/scheduleOne.ts b/src/mcp/tools/schedule/scheduleOne.ts new file mode 100644 index 0000000..ed71f01 --- /dev/null +++ b/src/mcp/tools/schedule/scheduleOne.ts @@ -0,0 +1,35 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const scheduleOne = createTool({ + name: "schedule-one", + description: "Gets a specific scheduled task by its ID in Dokploy.", + schema: z.object({ + scheduleId: z.string().describe("The unique identifier of the schedule to retrieve. Required."), + }), + annotations: { + title: "Get Schedule Details", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const schedule = await apiClient.get( + `/schedule.one?scheduleId=${input.scheduleId}`, + ); + + if (!schedule?.data) { + return ResponseFormatter.error( + "Failed to fetch schedule", + `Schedule with ID "${input.scheduleId}" not found`, + ); + } + + return ResponseFormatter.success( + `Successfully fetched schedule "${input.scheduleId}"`, + schedule.data, + ); + }, +}); diff --git a/src/mcp/tools/schedule/scheduleRunManually.ts b/src/mcp/tools/schedule/scheduleRunManually.ts new file mode 100644 index 0000000..3935ff8 --- /dev/null +++ b/src/mcp/tools/schedule/scheduleRunManually.ts @@ -0,0 +1,29 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const scheduleRunManually = createTool({ + name: "schedule-run-manually", + description: "Manually triggers a scheduled task to run immediately.", + schema: z.object({ + scheduleId: z + .string() + .min(1) + .describe("The unique identifier of the schedule to trigger immediately. Required."), + }), + annotations: { + title: "Run Schedule Manually", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/schedule.runManually", input); + + return ResponseFormatter.success( + `Schedule "${input.scheduleId}" triggered successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/schedule/scheduleUpdate.ts b/src/mcp/tools/schedule/scheduleUpdate.ts new file mode 100644 index 0000000..87a9a06 --- /dev/null +++ b/src/mcp/tools/schedule/scheduleUpdate.ts @@ -0,0 +1,71 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const scheduleUpdate = createTool({ + name: "schedule-update", + description: "Updates an existing scheduled task in Dokploy.", + schema: z.object({ + scheduleId: z.string().min(1).describe("The ID of the schedule to update. Required."), + name: z.string().describe("The name of the scheduled task. Required."), + cronExpression: z + .string() + .describe("The cron expression defining when the task runs (e.g., '0 0 * * *' for daily at midnight). Required."), + command: z.string().describe("The command to execute when the schedule runs. Required."), + appName: z.string().optional().describe("The application name for the scheduled task."), + serviceName: z + .string() + .nullable() + .optional() + .describe("The service name for compose applications. Only applicable when scheduleType is 'compose'."), + shellType: z + .enum(["bash", "sh"]) + .optional() + .describe("The shell type to use for command execution. Either 'bash' or 'sh'."), + scheduleType: z + .enum(["application", "compose", "server", "dokploy-server"]) + .optional() + .describe("The type of schedule: 'application' for app-level, 'compose' for compose services, 'server' for remote servers, or 'dokploy-server' for the Dokploy server itself."), + script: z + .string() + .nullable() + .optional() + .describe("The script content to execute. Can be used instead of or in addition to command."), + applicationId: z + .string() + .nullable() + .optional() + .describe("The application ID. Required when scheduleType is 'application'."), + composeId: z + .string() + .nullable() + .optional() + .describe("The compose ID. Required when scheduleType is 'compose'."), + serverId: z + .string() + .nullable() + .optional() + .describe("The server ID. Required when scheduleType is 'server'."), + userId: z.string().nullable().optional().describe("The user ID who owns this schedule."), + enabled: z + .boolean() + .optional() + .describe("Whether the schedule is enabled and will run."), + createdAt: z.string().optional().describe("The creation timestamp."), + }), + annotations: { + title: "Update Schedule", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/schedule.update", input); + + return ResponseFormatter.success( + `Schedule "${input.scheduleId}" updated successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/security/index.ts b/src/mcp/tools/security/index.ts new file mode 100644 index 0000000..7cc1346 --- /dev/null +++ b/src/mcp/tools/security/index.ts @@ -0,0 +1,4 @@ +export { securityCreate } from "./securityCreate.js"; +export { securityDelete } from "./securityDelete.js"; +export { securityOne } from "./securityOne.js"; +export { securityUpdate } from "./securityUpdate.js"; diff --git a/src/mcp/tools/security/securityCreate.ts b/src/mcp/tools/security/securityCreate.ts new file mode 100644 index 0000000..5fe423e --- /dev/null +++ b/src/mcp/tools/security/securityCreate.ts @@ -0,0 +1,43 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const securityCreate = createTool({ + name: "security-create", + description: + "Creates a new security entry (HTTP basic auth) for an application in Dokploy.", + schema: z.object({ + applicationId: z + .string() + .describe( + "The ID of the application to add HTTP basic authentication to.", + ), + username: z + .string() + .min(1) + .describe( + "The username for HTTP basic auth. Must be at least 1 character.", + ), + password: z + .string() + .min(1) + .describe( + "The password for HTTP basic auth. Must be at least 1 character.", + ), + }), + annotations: { + title: "Create Security Entry", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/security.create", input); + + return ResponseFormatter.success( + `Security entry created for application "${input.applicationId}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/security/securityDelete.ts b/src/mcp/tools/security/securityDelete.ts new file mode 100644 index 0000000..369cbd9 --- /dev/null +++ b/src/mcp/tools/security/securityDelete.ts @@ -0,0 +1,32 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const securityDelete = createTool({ + name: "security-delete", + description: + "Deletes a security entry (HTTP basic auth configuration) from Dokploy.", + schema: z.object({ + securityId: z + .string() + .min(1) + .describe( + "The ID of the security entry to delete. Must be at least 1 character.", + ), + }), + annotations: { + title: "Delete Security Entry", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/security.delete", input); + + return ResponseFormatter.success( + `Security entry "${input.securityId}" deleted successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/security/securityOne.ts b/src/mcp/tools/security/securityOne.ts new file mode 100644 index 0000000..b2d2dec --- /dev/null +++ b/src/mcp/tools/security/securityOne.ts @@ -0,0 +1,41 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const securityOne = createTool({ + name: "security-one", + description: + "Gets a specific security entry (HTTP basic auth configuration) by its ID in Dokploy.", + schema: z.object({ + securityId: z + .string() + .min(1) + .describe( + "The ID of the security entry to retrieve. Must be at least 1 character.", + ), + }), + annotations: { + title: "Get Security Entry Details", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.get( + `/security.one?securityId=${input.securityId}`, + ); + + if (!response?.data) { + return ResponseFormatter.error( + "Failed to fetch security entry", + `Security entry with ID "${input.securityId}" not found`, + ); + } + + return ResponseFormatter.success( + `Successfully fetched security entry "${input.securityId}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/security/securityUpdate.ts b/src/mcp/tools/security/securityUpdate.ts new file mode 100644 index 0000000..3940a75 --- /dev/null +++ b/src/mcp/tools/security/securityUpdate.ts @@ -0,0 +1,44 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const securityUpdate = createTool({ + name: "security-update", + description: + "Updates an existing security entry (HTTP basic auth configuration) in Dokploy.", + schema: z.object({ + securityId: z + .string() + .min(1) + .describe( + "The ID of the security entry to update. Must be at least 1 character.", + ), + username: z + .string() + .min(1) + .describe( + "The new username for HTTP basic auth. Must be at least 1 character.", + ), + password: z + .string() + .min(1) + .describe( + "The new password for HTTP basic auth. Must be at least 1 character.", + ), + }), + annotations: { + title: "Update Security Entry", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/security.update", input); + + return ResponseFormatter.success( + `Security entry "${input.securityId}" updated successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/server/index.ts b/src/mcp/tools/server/index.ts new file mode 100644 index 0000000..9b4015c --- /dev/null +++ b/src/mcp/tools/server/index.ts @@ -0,0 +1,16 @@ +export { serverAll } from "./serverAll.js"; +export { serverBuildServers } from "./serverBuildServers.js"; +export { serverCount } from "./serverCount.js"; +export { serverCreate } from "./serverCreate.js"; +export { serverGetDefaultCommand } from "./serverGetDefaultCommand.js"; +export { serverGetServerMetrics } from "./serverGetServerMetrics.js"; +export { serverGetServerTime } from "./serverGetServerTime.js"; +export { serverOne } from "./serverOne.js"; +export { serverPublicIp } from "./serverPublicIp.js"; +export { serverRemove } from "./serverRemove.js"; +export { serverSecurity } from "./serverSecurity.js"; +export { serverSetup } from "./serverSetup.js"; +export { serverSetupMonitoring } from "./serverSetupMonitoring.js"; +export { serverUpdate } from "./serverUpdate.js"; +export { serverValidate } from "./serverValidate.js"; +export { serverWithSSHKey } from "./serverWithSSHKey.js"; diff --git a/src/mcp/tools/server/serverAll.ts b/src/mcp/tools/server/serverAll.ts new file mode 100644 index 0000000..ab8ce09 --- /dev/null +++ b/src/mcp/tools/server/serverAll.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const serverAll = createTool({ + name: "server-all", + description: "Gets all servers in Dokploy.", + schema: z.object({}), + annotations: { + title: "List All Servers", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const response = await apiClient.get("/server.all"); + + return ResponseFormatter.success( + "Successfully fetched all servers", + response.data + ); + }, +}); diff --git a/src/mcp/tools/server/serverBuildServers.ts b/src/mcp/tools/server/serverBuildServers.ts new file mode 100644 index 0000000..a76dc77 --- /dev/null +++ b/src/mcp/tools/server/serverBuildServers.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const serverBuildServers = createTool({ + name: "server-build-servers", + description: "Gets all build servers in Dokploy.", + schema: z.object({}), + annotations: { + title: "List Build Servers", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const response = await apiClient.get("/server.buildServers"); + + return ResponseFormatter.success( + "Successfully fetched build servers", + response.data + ); + }, +}); diff --git a/src/mcp/tools/server/serverCount.ts b/src/mcp/tools/server/serverCount.ts new file mode 100644 index 0000000..5303dd6 --- /dev/null +++ b/src/mcp/tools/server/serverCount.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const serverCount = createTool({ + name: "server-count", + description: "Gets the count of servers in Dokploy.", + schema: z.object({}), + annotations: { + title: "Get Server Count", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const response = await apiClient.get("/server.count"); + + return ResponseFormatter.success( + "Successfully fetched server count", + response.data + ); + }, +}); diff --git a/src/mcp/tools/server/serverCreate.ts b/src/mcp/tools/server/serverCreate.ts new file mode 100644 index 0000000..5284c1f --- /dev/null +++ b/src/mcp/tools/server/serverCreate.ts @@ -0,0 +1,41 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const serverCreate = createTool({ + name: "server-create", + description: "Creates a new server in Dokploy.", + schema: z.object({ + name: z.string().min(1).describe("The name of the server."), + description: z + .string() + .nullable() + .optional() + .describe("An optional description for the server."), + ipAddress: z.string().describe("The IP address of the server."), + port: z.number().describe("The SSH port of the server."), + username: z.string().describe("The SSH username for the server."), + sshKeyId: z + .string() + .nullable() + .describe("The ID of the SSH key to use for authentication."), + serverType: z + .enum(["deploy", "build"]) + .describe("The type of server - 'deploy' for deployment or 'build' for building."), + }), + annotations: { + title: "Create Server", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/server.create", input); + + return ResponseFormatter.success( + `Server "${input.name}" created successfully`, + response.data + ); + }, +}); diff --git a/src/mcp/tools/server/serverGetDefaultCommand.ts b/src/mcp/tools/server/serverGetDefaultCommand.ts new file mode 100644 index 0000000..6ac6972 --- /dev/null +++ b/src/mcp/tools/server/serverGetDefaultCommand.ts @@ -0,0 +1,28 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const serverGetDefaultCommand = createTool({ + name: "server-get-default-command", + description: "Gets the default command for a specific server in Dokploy.", + schema: z.object({ + serverId: z.string().min(1).describe("The ID of the server to get the default command for."), + }), + annotations: { + title: "Get Server Default Command", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.get( + `/server.getDefaultCommand?serverId=${input.serverId}` + ); + + return ResponseFormatter.success( + `Successfully fetched default command for server "${input.serverId}"`, + response.data + ); + }, +}); diff --git a/src/mcp/tools/server/serverGetServerMetrics.ts b/src/mcp/tools/server/serverGetServerMetrics.ts new file mode 100644 index 0000000..2201c4d --- /dev/null +++ b/src/mcp/tools/server/serverGetServerMetrics.ts @@ -0,0 +1,30 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const serverGetServerMetrics = createTool({ + name: "server-get-server-metrics", + description: "Gets server metrics from the Dokploy monitoring endpoint.", + schema: z.object({ + url: z.string().describe("The URL of the metrics endpoint."), + token: z.string().describe("The authentication token for the metrics endpoint."), + dataPoints: z.string().describe("The number of data points to retrieve."), + }), + annotations: { + title: "Get Server Metrics", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.get( + `/server.getServerMetrics?url=${encodeURIComponent(input.url)}&token=${encodeURIComponent(input.token)}&dataPoints=${encodeURIComponent(input.dataPoints)}` + ); + + return ResponseFormatter.success( + "Successfully fetched server metrics", + response.data + ); + }, +}); diff --git a/src/mcp/tools/server/serverGetServerTime.ts b/src/mcp/tools/server/serverGetServerTime.ts new file mode 100644 index 0000000..3c5fc7c --- /dev/null +++ b/src/mcp/tools/server/serverGetServerTime.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const serverGetServerTime = createTool({ + name: "server-get-server-time", + description: "Gets the current time of the Dokploy server.", + schema: z.object({}), + annotations: { + title: "Get Server Time", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const response = await apiClient.get("/server.getServerTime"); + + return ResponseFormatter.success( + "Successfully fetched server time", + response.data + ); + }, +}); diff --git a/src/mcp/tools/server/serverOne.ts b/src/mcp/tools/server/serverOne.ts new file mode 100644 index 0000000..58547ec --- /dev/null +++ b/src/mcp/tools/server/serverOne.ts @@ -0,0 +1,35 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const serverOne = createTool({ + name: "server-one", + description: "Gets a specific server by its ID in Dokploy.", + schema: z.object({ + serverId: z.string().min(1).describe("The ID of the server to retrieve."), + }), + annotations: { + title: "Get Server Details", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.get( + `/server.one?serverId=${input.serverId}` + ); + + if (!response?.data) { + return ResponseFormatter.error( + "Failed to fetch server", + `Server with ID "${input.serverId}" not found` + ); + } + + return ResponseFormatter.success( + `Successfully fetched server "${input.serverId}"`, + response.data + ); + }, +}); diff --git a/src/mcp/tools/server/serverPublicIp.ts b/src/mcp/tools/server/serverPublicIp.ts new file mode 100644 index 0000000..4e264a3 --- /dev/null +++ b/src/mcp/tools/server/serverPublicIp.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const serverPublicIp = createTool({ + name: "server-public-ip", + description: "Gets the public IP address of the Dokploy server.", + schema: z.object({}), + annotations: { + title: "Get Server Public IP", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const response = await apiClient.get("/server.publicIp"); + + return ResponseFormatter.success( + "Successfully fetched server public IP", + response.data + ); + }, +}); diff --git a/src/mcp/tools/server/serverRemove.ts b/src/mcp/tools/server/serverRemove.ts new file mode 100644 index 0000000..d0807af --- /dev/null +++ b/src/mcp/tools/server/serverRemove.ts @@ -0,0 +1,26 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const serverRemove = createTool({ + name: "server-remove", + description: "Removes/deletes a server from Dokploy.", + schema: z.object({ + serverId: z.string().min(1).describe("The ID of the server to remove."), + }), + annotations: { + title: "Remove Server", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/server.remove", input); + + return ResponseFormatter.success( + `Server "${input.serverId}" removed successfully`, + response.data + ); + }, +}); diff --git a/src/mcp/tools/server/serverSecurity.ts b/src/mcp/tools/server/serverSecurity.ts new file mode 100644 index 0000000..60bd582 --- /dev/null +++ b/src/mcp/tools/server/serverSecurity.ts @@ -0,0 +1,28 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const serverSecurity = createTool({ + name: "server-security", + description: "Gets security information for a specific server in Dokploy.", + schema: z.object({ + serverId: z.string().min(1).describe("The ID of the server to get security info for."), + }), + annotations: { + title: "Get Server Security Info", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.get( + `/server.security?serverId=${input.serverId}` + ); + + return ResponseFormatter.success( + `Successfully fetched security info for server "${input.serverId}"`, + response.data + ); + }, +}); diff --git a/src/mcp/tools/server/serverSetup.ts b/src/mcp/tools/server/serverSetup.ts new file mode 100644 index 0000000..c98801e --- /dev/null +++ b/src/mcp/tools/server/serverSetup.ts @@ -0,0 +1,26 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const serverSetup = createTool({ + name: "server-setup", + description: "Sets up a server in Dokploy (installs required dependencies and configures the server).", + schema: z.object({ + serverId: z.string().min(1).describe("The ID of the server to set up."), + }), + annotations: { + title: "Setup Server", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/server.setup", input); + + return ResponseFormatter.success( + `Server "${input.serverId}" setup initiated successfully`, + response.data + ); + }, +}); diff --git a/src/mcp/tools/server/serverSetupMonitoring.ts b/src/mcp/tools/server/serverSetupMonitoring.ts new file mode 100644 index 0000000..88be1b2 --- /dev/null +++ b/src/mcp/tools/server/serverSetupMonitoring.ts @@ -0,0 +1,57 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +const thresholdsSchema = z.object({ + cpu: z.number().min(0).describe("CPU threshold percentage (minimum 0)."), + memory: z.number().min(0).describe("Memory threshold percentage (minimum 0)."), +}); + +const servicesSchema = z.object({ + include: z.array(z.string()).optional().describe("List of services to include in monitoring."), + exclude: z.array(z.string()).optional().describe("List of services to exclude from monitoring."), +}); + +const serverMetricsSchema = z.object({ + refreshRate: z.number().min(2).describe("Refresh rate in seconds (minimum 2)."), + port: z.number().min(1).describe("Port for metrics collection (minimum 1)."), + token: z.string().describe("Token for authentication."), + urlCallback: z.string().url().describe("Callback URL for metrics data."), + retentionDays: z.number().min(1).describe("Number of days to retain metrics data (minimum 1)."), + cronJob: z.string().min(1).describe("Cron job schedule expression."), + thresholds: thresholdsSchema.describe("Threshold configuration for alerts."), +}); + +const containersMetricsSchema = z.object({ + refreshRate: z.number().min(2).describe("Refresh rate in seconds for container metrics (minimum 2)."), + services: servicesSchema.describe("Services configuration for container monitoring."), +}); + +const metricsConfigSchema = z.object({ + server: serverMetricsSchema.describe("Server metrics configuration."), + containers: containersMetricsSchema.describe("Containers metrics configuration."), +}); + +export const serverSetupMonitoring = createTool({ + name: "server-setup-monitoring", + description: "Sets up monitoring for a server in Dokploy.", + schema: z.object({ + serverId: z.string().min(1).describe("The ID of the server to set up monitoring for."), + metricsConfig: metricsConfigSchema.describe("The metrics configuration object."), + }), + annotations: { + title: "Setup Server Monitoring", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/server.setupMonitoring", input); + + return ResponseFormatter.success( + `Monitoring setup initiated for server "${input.serverId}"`, + response.data + ); + }, +}); diff --git a/src/mcp/tools/server/serverUpdate.ts b/src/mcp/tools/server/serverUpdate.ts new file mode 100644 index 0000000..680c383 --- /dev/null +++ b/src/mcp/tools/server/serverUpdate.ts @@ -0,0 +1,46 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const serverUpdate = createTool({ + name: "server-update", + description: "Updates an existing server in Dokploy.", + schema: z.object({ + serverId: z.string().min(1).describe("The ID of the server to update."), + name: z.string().min(1).describe("The name of the server."), + description: z + .string() + .nullable() + .optional() + .describe("An optional description for the server."), + ipAddress: z.string().describe("The IP address of the server."), + port: z.number().describe("The SSH port of the server."), + username: z.string().describe("The SSH username for the server."), + sshKeyId: z + .string() + .nullable() + .describe("The ID of the SSH key to use for authentication."), + serverType: z + .enum(["deploy", "build"]) + .describe("The type of server - 'deploy' for deployment or 'build' for building."), + command: z + .string() + .optional() + .describe("Custom command for the server."), + }), + annotations: { + title: "Update Server", + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/server.update", input); + + return ResponseFormatter.success( + `Server "${input.serverId}" updated successfully`, + response.data + ); + }, +}); diff --git a/src/mcp/tools/server/serverValidate.ts b/src/mcp/tools/server/serverValidate.ts new file mode 100644 index 0000000..aab2077 --- /dev/null +++ b/src/mcp/tools/server/serverValidate.ts @@ -0,0 +1,28 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const serverValidate = createTool({ + name: "server-validate", + description: "Validates a server connection in Dokploy.", + schema: z.object({ + serverId: z.string().min(1).describe("The ID of the server to validate."), + }), + annotations: { + title: "Validate Server", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.get( + `/server.validate?serverId=${input.serverId}` + ); + + return ResponseFormatter.success( + `Server "${input.serverId}" validated successfully`, + response.data + ); + }, +}); diff --git a/src/mcp/tools/server/serverWithSSHKey.ts b/src/mcp/tools/server/serverWithSSHKey.ts new file mode 100644 index 0000000..738331e --- /dev/null +++ b/src/mcp/tools/server/serverWithSSHKey.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const serverWithSSHKey = createTool({ + name: "server-with-ssh-key", + description: "Gets all servers that have SSH keys configured in Dokploy.", + schema: z.object({}), + annotations: { + title: "List Servers With SSH Keys", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const response = await apiClient.get("/server.withSSHKey"); + + return ResponseFormatter.success( + "Successfully fetched servers with SSH keys", + response.data + ); + }, +}); diff --git a/src/mcp/tools/settings/index.ts b/src/mcp/tools/settings/index.ts new file mode 100644 index 0000000..6ad87f5 --- /dev/null +++ b/src/mcp/tools/settings/index.ts @@ -0,0 +1,46 @@ +export { settingsAssignDomainServer } from "./settingsAssignDomainServer.js"; +export { settingsCheckGPUStatus } from "./settingsCheckGPUStatus.js"; +export { settingsCleanAll } from "./settingsCleanAll.js"; +export { settingsCleanDockerBuilder } from "./settingsCleanDockerBuilder.js"; +export { settingsCleanDockerPrune } from "./settingsCleanDockerPrune.js"; +export { settingsCleanMonitoring } from "./settingsCleanMonitoring.js"; +export { settingsCleanRedis } from "./settingsCleanRedis.js"; +export { settingsCleanSSHPrivateKey } from "./settingsCleanSSHPrivateKey.js"; +export { settingsCleanStoppedContainers } from "./settingsCleanStoppedContainers.js"; +export { settingsCleanUnusedImages } from "./settingsCleanUnusedImages.js"; +export { settingsCleanUnusedVolumes } from "./settingsCleanUnusedVolumes.js"; +export { settingsGetDokployCloudIps } from "./settingsGetDokployCloudIps.js"; +export { settingsGetDokployVersion } from "./settingsGetDokployVersion.js"; +export { settingsGetIp } from "./settingsGetIp.js"; +export { settingsGetLogCleanupStatus } from "./settingsGetLogCleanupStatus.js"; +export { settingsGetOpenApiDocument } from "./settingsGetOpenApiDocument.js"; +export { settingsGetReleaseTag } from "./settingsGetReleaseTag.js"; +export { settingsGetTraefikPorts } from "./settingsGetTraefikPorts.js"; +export { settingsGetUpdateData } from "./settingsGetUpdateData.js"; +export { settingsHaveActivateRequests } from "./settingsHaveActivateRequests.js"; +export { settingsHaveTraefikDashboardPortEnabled } from "./settingsHaveTraefikDashboardPortEnabled.js"; +export { settingsHealth } from "./settingsHealth.js"; +export { settingsIsCloud } from "./settingsIsCloud.js"; +export { settingsIsUserSubscribed } from "./settingsIsUserSubscribed.js"; +export { settingsReadDirectories } from "./settingsReadDirectories.js"; +export { settingsReadMiddlewareTraefikConfig } from "./settingsReadMiddlewareTraefikConfig.js"; +export { settingsReadTraefikConfig } from "./settingsReadTraefikConfig.js"; +export { settingsReadTraefikEnv } from "./settingsReadTraefikEnv.js"; +export { settingsReadTraefikFile } from "./settingsReadTraefikFile.js"; +export { settingsReadWebServerTraefikConfig } from "./settingsReadWebServerTraefikConfig.js"; +export { settingsReloadRedis } from "./settingsReloadRedis.js"; +export { settingsReloadServer } from "./settingsReloadServer.js"; +export { settingsReloadTraefik } from "./settingsReloadTraefik.js"; +export { settingsSaveSSHPrivateKey } from "./settingsSaveSSHPrivateKey.js"; +export { settingsSetupGPU } from "./settingsSetupGPU.js"; +export { settingsToggleDashboard } from "./settingsToggleDashboard.js"; +export { settingsToggleRequests } from "./settingsToggleRequests.js"; +export { settingsUpdateDockerCleanup } from "./settingsUpdateDockerCleanup.js"; +export { settingsUpdateLogCleanup } from "./settingsUpdateLogCleanup.js"; +export { settingsUpdateMiddlewareTraefikConfig } from "./settingsUpdateMiddlewareTraefikConfig.js"; +export { settingsUpdateServer } from "./settingsUpdateServer.js"; +export { settingsUpdateTraefikConfig } from "./settingsUpdateTraefikConfig.js"; +export { settingsUpdateTraefikFile } from "./settingsUpdateTraefikFile.js"; +export { settingsUpdateTraefikPorts } from "./settingsUpdateTraefikPorts.js"; +export { settingsUpdateWebServerTraefikConfig } from "./settingsUpdateWebServerTraefikConfig.js"; +export { settingsWriteTraefikEnv } from "./settingsWriteTraefikEnv.js"; diff --git a/src/mcp/tools/settings/settingsAssignDomainServer.ts b/src/mcp/tools/settings/settingsAssignDomainServer.ts new file mode 100644 index 0000000..ca12706 --- /dev/null +++ b/src/mcp/tools/settings/settingsAssignDomainServer.ts @@ -0,0 +1,50 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsAssignDomainServer = createTool({ + name: "settings-assign-domain-server", + description: "Assigns a domain to the Dokploy server.", + schema: z.object({ + host: z + .string() + .nullable() + .describe( + "The domain host to assign. Set to null to remove the domain assignment.", + ), + certificateType: z + .enum(["letsencrypt", "none", "custom"]) + .describe( + "The type of SSL certificate to use: letsencrypt for automatic SSL, none for no SSL, custom for user-provided certificates.", + ), + letsEncryptEmail: z + .string() + .nullable() + .optional() + .describe( + "Email address for Let's Encrypt certificate notifications. Required when certificateType is letsencrypt.", + ), + https: z + .boolean() + .optional() + .describe("Whether to enable HTTPS for the domain."), + }), + annotations: { + title: "Assign Domain to Server", + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post( + "/settings.assignDomainServer", + input, + ); + + return ResponseFormatter.success( + `Domain "${input.host}" assigned successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsCheckGPUStatus.ts b/src/mcp/tools/settings/settingsCheckGPUStatus.ts new file mode 100644 index 0000000..c2a41be --- /dev/null +++ b/src/mcp/tools/settings/settingsCheckGPUStatus.ts @@ -0,0 +1,30 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsCheckGPUStatus = createTool({ + name: "settings-check-gpu-status", + description: "Checks the GPU status in Dokploy.", + schema: z.object({ + serverId: z + .string() + .optional() + .describe("Optional server ID to check GPU status on a specific server."), + }), + annotations: { + title: "Check GPU Status", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const params = input.serverId ? `?serverId=${input.serverId}` : ""; + const response = await apiClient.get(`/settings.checkGPUStatus${params}`); + + return ResponseFormatter.success( + "Successfully fetched GPU status", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsCleanAll.ts b/src/mcp/tools/settings/settingsCleanAll.ts new file mode 100644 index 0000000..fec4296 --- /dev/null +++ b/src/mcp/tools/settings/settingsCleanAll.ts @@ -0,0 +1,32 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsCleanAll = createTool({ + name: "settings-clean-all", + description: + "Cleans all Docker resources (images, volumes, containers, builder cache) in Dokploy.", + schema: z.object({ + serverId: z + .string() + .optional() + .describe( + "Optional server ID to clean all resources on a specific server.", + ), + }), + annotations: { + title: "Clean All Docker Resources", + destructiveHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/settings.cleanAll", input); + + return ResponseFormatter.success( + "All Docker resources cleaned successfully", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsCleanDockerBuilder.ts b/src/mcp/tools/settings/settingsCleanDockerBuilder.ts new file mode 100644 index 0000000..596ea3e --- /dev/null +++ b/src/mcp/tools/settings/settingsCleanDockerBuilder.ts @@ -0,0 +1,34 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsCleanDockerBuilder = createTool({ + name: "settings-clean-docker-builder", + description: "Cleans Docker builder cache in Dokploy.", + schema: z.object({ + serverId: z + .string() + .optional() + .describe( + "Optional server ID to clean builder cache on a specific server.", + ), + }), + annotations: { + title: "Clean Docker Builder", + destructiveHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post( + "/settings.cleanDockerBuilder", + input, + ); + + return ResponseFormatter.success( + "Docker builder cache cleaned successfully", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsCleanDockerPrune.ts b/src/mcp/tools/settings/settingsCleanDockerPrune.ts new file mode 100644 index 0000000..a38db82 --- /dev/null +++ b/src/mcp/tools/settings/settingsCleanDockerPrune.ts @@ -0,0 +1,29 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsCleanDockerPrune = createTool({ + name: "settings-clean-docker-prune", + description: "Runs Docker system prune in Dokploy.", + schema: z.object({ + serverId: z + .string() + .optional() + .describe("Optional server ID to run prune on a specific server."), + }), + annotations: { + title: "Docker Prune", + destructiveHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/settings.cleanDockerPrune", input); + + return ResponseFormatter.success( + "Docker prune completed successfully", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsCleanMonitoring.ts b/src/mcp/tools/settings/settingsCleanMonitoring.ts new file mode 100644 index 0000000..e8bf866 --- /dev/null +++ b/src/mcp/tools/settings/settingsCleanMonitoring.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsCleanMonitoring = createTool({ + name: "settings-clean-monitoring", + description: "Cleans monitoring data in Dokploy.", + schema: z.object({}), + annotations: { + title: "Clean Monitoring Data", + destructiveHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const response = await apiClient.post("/settings.cleanMonitoring", {}); + + return ResponseFormatter.success( + "Monitoring data cleaned successfully", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsCleanRedis.ts b/src/mcp/tools/settings/settingsCleanRedis.ts new file mode 100644 index 0000000..9cf1638 --- /dev/null +++ b/src/mcp/tools/settings/settingsCleanRedis.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsCleanRedis = createTool({ + name: "settings-clean-redis", + description: "Cleans Redis cache in Dokploy.", + schema: z.object({}), + annotations: { + title: "Clean Redis", + destructiveHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const response = await apiClient.post("/settings.cleanRedis", {}); + + return ResponseFormatter.success( + "Redis cleaned successfully", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsCleanSSHPrivateKey.ts b/src/mcp/tools/settings/settingsCleanSSHPrivateKey.ts new file mode 100644 index 0000000..4d2e9f0 --- /dev/null +++ b/src/mcp/tools/settings/settingsCleanSSHPrivateKey.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsCleanSSHPrivateKey = createTool({ + name: "settings-clean-ssh-private-key", + description: "Removes the SSH private key from Dokploy.", + schema: z.object({}), + annotations: { + title: "Clean SSH Private Key", + destructiveHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const response = await apiClient.post("/settings.cleanSSHPrivateKey", {}); + + return ResponseFormatter.success( + "SSH private key removed successfully", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsCleanStoppedContainers.ts b/src/mcp/tools/settings/settingsCleanStoppedContainers.ts new file mode 100644 index 0000000..328cea4 --- /dev/null +++ b/src/mcp/tools/settings/settingsCleanStoppedContainers.ts @@ -0,0 +1,32 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsCleanStoppedContainers = createTool({ + name: "settings-clean-stopped-containers", + description: "Cleans stopped Docker containers in Dokploy.", + schema: z.object({ + serverId: z + .string() + .optional() + .describe("Optional server ID to clean containers on a specific server."), + }), + annotations: { + title: "Clean Stopped Containers", + destructiveHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post( + "/settings.cleanStoppedContainers", + input, + ); + + return ResponseFormatter.success( + "Stopped containers cleaned successfully", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsCleanUnusedImages.ts b/src/mcp/tools/settings/settingsCleanUnusedImages.ts new file mode 100644 index 0000000..2f4ffac --- /dev/null +++ b/src/mcp/tools/settings/settingsCleanUnusedImages.ts @@ -0,0 +1,29 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsCleanUnusedImages = createTool({ + name: "settings-clean-unused-images", + description: "Cleans unused Docker images in Dokploy.", + schema: z.object({ + serverId: z + .string() + .optional() + .describe("Optional server ID to clean images on a specific server."), + }), + annotations: { + title: "Clean Unused Images", + destructiveHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/settings.cleanUnusedImages", input); + + return ResponseFormatter.success( + "Unused images cleaned successfully", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsCleanUnusedVolumes.ts b/src/mcp/tools/settings/settingsCleanUnusedVolumes.ts new file mode 100644 index 0000000..2b6a8da --- /dev/null +++ b/src/mcp/tools/settings/settingsCleanUnusedVolumes.ts @@ -0,0 +1,32 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsCleanUnusedVolumes = createTool({ + name: "settings-clean-unused-volumes", + description: "Cleans unused Docker volumes in Dokploy.", + schema: z.object({ + serverId: z + .string() + .optional() + .describe("Optional server ID to clean volumes on a specific server."), + }), + annotations: { + title: "Clean Unused Volumes", + destructiveHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post( + "/settings.cleanUnusedVolumes", + input, + ); + + return ResponseFormatter.success( + "Unused volumes cleaned successfully", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsGetDokployCloudIps.ts b/src/mcp/tools/settings/settingsGetDokployCloudIps.ts new file mode 100644 index 0000000..9986e9b --- /dev/null +++ b/src/mcp/tools/settings/settingsGetDokployCloudIps.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsGetDokployCloudIps = createTool({ + name: "settings-get-dokploy-cloud-ips", + description: "Gets the Dokploy Cloud IP addresses.", + schema: z.object({}), + annotations: { + title: "Get Dokploy Cloud IPs", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const response = await apiClient.get("/settings.getDokployCloudIps"); + + return ResponseFormatter.success( + "Successfully fetched Dokploy Cloud IPs", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsGetDokployVersion.ts b/src/mcp/tools/settings/settingsGetDokployVersion.ts new file mode 100644 index 0000000..943333e --- /dev/null +++ b/src/mcp/tools/settings/settingsGetDokployVersion.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsGetDokployVersion = createTool({ + name: "settings-get-dokploy-version", + description: "Gets the current Dokploy version.", + schema: z.object({}), + annotations: { + title: "Get Dokploy Version", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const response = await apiClient.get("/settings.getDokployVersion"); + + return ResponseFormatter.success( + "Successfully fetched Dokploy version", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsGetIp.ts b/src/mcp/tools/settings/settingsGetIp.ts new file mode 100644 index 0000000..327dfbb --- /dev/null +++ b/src/mcp/tools/settings/settingsGetIp.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsGetIp = createTool({ + name: "settings-get-ip", + description: "Gets the IP address of the Dokploy server.", + schema: z.object({}), + annotations: { + title: "Get Server IP", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const response = await apiClient.get("/settings.getIp"); + + return ResponseFormatter.success( + "Successfully fetched server IP", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsGetLogCleanupStatus.ts b/src/mcp/tools/settings/settingsGetLogCleanupStatus.ts new file mode 100644 index 0000000..ae06be3 --- /dev/null +++ b/src/mcp/tools/settings/settingsGetLogCleanupStatus.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsGetLogCleanupStatus = createTool({ + name: "settings-get-log-cleanup-status", + description: "Gets the log cleanup status in Dokploy.", + schema: z.object({}), + annotations: { + title: "Get Log Cleanup Status", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const response = await apiClient.get("/settings.getLogCleanupStatus"); + + return ResponseFormatter.success( + "Successfully fetched log cleanup status", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsGetOpenApiDocument.ts b/src/mcp/tools/settings/settingsGetOpenApiDocument.ts new file mode 100644 index 0000000..ab3cdd9 --- /dev/null +++ b/src/mcp/tools/settings/settingsGetOpenApiDocument.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsGetOpenApiDocument = createTool({ + name: "settings-get-openapi-document", + description: "Gets the OpenAPI documentation for Dokploy.", + schema: z.object({}), + annotations: { + title: "Get OpenAPI Document", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const response = await apiClient.get("/settings.getOpenApiDocument"); + + return ResponseFormatter.success( + "Successfully fetched OpenAPI document", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsGetReleaseTag.ts b/src/mcp/tools/settings/settingsGetReleaseTag.ts new file mode 100644 index 0000000..b88b3f8 --- /dev/null +++ b/src/mcp/tools/settings/settingsGetReleaseTag.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsGetReleaseTag = createTool({ + name: "settings-get-release-tag", + description: "Gets the current release tag for Dokploy.", + schema: z.object({}), + annotations: { + title: "Get Release Tag", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const response = await apiClient.get("/settings.getReleaseTag"); + + return ResponseFormatter.success( + "Successfully fetched release tag", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsGetTraefikPorts.ts b/src/mcp/tools/settings/settingsGetTraefikPorts.ts new file mode 100644 index 0000000..20e1b3c --- /dev/null +++ b/src/mcp/tools/settings/settingsGetTraefikPorts.ts @@ -0,0 +1,30 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsGetTraefikPorts = createTool({ + name: "settings-get-traefik-ports", + description: "Gets the Traefik ports configuration in Dokploy.", + schema: z.object({ + serverId: z + .string() + .optional() + .describe("Optional server ID to get ports from a specific server."), + }), + annotations: { + title: "Get Traefik Ports", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const params = input.serverId ? `?serverId=${input.serverId}` : ""; + const response = await apiClient.get(`/settings.getTraefikPorts${params}`); + + return ResponseFormatter.success( + "Successfully fetched Traefik ports", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsGetUpdateData.ts b/src/mcp/tools/settings/settingsGetUpdateData.ts new file mode 100644 index 0000000..5c14740 --- /dev/null +++ b/src/mcp/tools/settings/settingsGetUpdateData.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsGetUpdateData = createTool({ + name: "settings-get-update-data", + description: "Gets update data for Dokploy.", + schema: z.object({}), + annotations: { + title: "Get Update Data", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const response = await apiClient.post("/settings.getUpdateData", {}); + + return ResponseFormatter.success( + "Successfully fetched update data", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsHaveActivateRequests.ts b/src/mcp/tools/settings/settingsHaveActivateRequests.ts new file mode 100644 index 0000000..356b6ad --- /dev/null +++ b/src/mcp/tools/settings/settingsHaveActivateRequests.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsHaveActivateRequests = createTool({ + name: "settings-have-activate-requests", + description: "Checks if activate requests feature is enabled in Dokploy.", + schema: z.object({}), + annotations: { + title: "Check Activate Requests", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const response = await apiClient.get("/settings.haveActivateRequests"); + + return ResponseFormatter.success( + "Successfully checked activate requests status", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsHaveTraefikDashboardPortEnabled.ts b/src/mcp/tools/settings/settingsHaveTraefikDashboardPortEnabled.ts new file mode 100644 index 0000000..df3b853 --- /dev/null +++ b/src/mcp/tools/settings/settingsHaveTraefikDashboardPortEnabled.ts @@ -0,0 +1,32 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsHaveTraefikDashboardPortEnabled = createTool({ + name: "settings-have-traefik-dashboard-port-enabled", + description: "Checks if the Traefik dashboard port is enabled in Dokploy.", + schema: z.object({ + serverId: z + .string() + .optional() + .describe("Optional server ID to check on a specific server."), + }), + annotations: { + title: "Check Traefik Dashboard Port", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const params = input.serverId ? `?serverId=${input.serverId}` : ""; + const response = await apiClient.get( + `/settings.haveTraefikDashboardPortEnabled${params}`, + ); + + return ResponseFormatter.success( + "Successfully checked Traefik dashboard port status", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsHealth.ts b/src/mcp/tools/settings/settingsHealth.ts new file mode 100644 index 0000000..08518ff --- /dev/null +++ b/src/mcp/tools/settings/settingsHealth.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsHealth = createTool({ + name: "settings-health", + description: "Gets the health status of the Dokploy server.", + schema: z.object({}), + annotations: { + title: "Check Health", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const response = await apiClient.get("/settings.health"); + + return ResponseFormatter.success( + "Successfully fetched health status", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsIsCloud.ts b/src/mcp/tools/settings/settingsIsCloud.ts new file mode 100644 index 0000000..15387c0 --- /dev/null +++ b/src/mcp/tools/settings/settingsIsCloud.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsIsCloud = createTool({ + name: "settings-is-cloud", + description: "Checks if the Dokploy instance is running in cloud mode.", + schema: z.object({}), + annotations: { + title: "Check Cloud Mode", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const response = await apiClient.get("/settings.isCloud"); + + return ResponseFormatter.success( + "Successfully checked cloud mode status", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsIsUserSubscribed.ts b/src/mcp/tools/settings/settingsIsUserSubscribed.ts new file mode 100644 index 0000000..39d551f --- /dev/null +++ b/src/mcp/tools/settings/settingsIsUserSubscribed.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsIsUserSubscribed = createTool({ + name: "settings-is-user-subscribed", + description: "Checks if the user is subscribed to Dokploy.", + schema: z.object({}), + annotations: { + title: "Check User Subscription", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const response = await apiClient.get("/settings.isUserSubscribed"); + + return ResponseFormatter.success( + "Successfully checked subscription status", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsReadDirectories.ts b/src/mcp/tools/settings/settingsReadDirectories.ts new file mode 100644 index 0000000..adc5554 --- /dev/null +++ b/src/mcp/tools/settings/settingsReadDirectories.ts @@ -0,0 +1,32 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsReadDirectories = createTool({ + name: "settings-read-directories", + description: "Reads Traefik configuration directories in Dokploy.", + schema: z.object({ + serverId: z + .string() + .optional() + .describe( + "Optional server ID to read directories from a specific server.", + ), + }), + annotations: { + title: "Read Directories", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const params = input.serverId ? `?serverId=${input.serverId}` : ""; + const response = await apiClient.get(`/settings.readDirectories${params}`); + + return ResponseFormatter.success( + "Successfully fetched directories", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsReadMiddlewareTraefikConfig.ts b/src/mcp/tools/settings/settingsReadMiddlewareTraefikConfig.ts new file mode 100644 index 0000000..d9e6efb --- /dev/null +++ b/src/mcp/tools/settings/settingsReadMiddlewareTraefikConfig.ts @@ -0,0 +1,26 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsReadMiddlewareTraefikConfig = createTool({ + name: "settings-read-middleware-traefik-config", + description: "Reads the middleware Traefik configuration in Dokploy.", + schema: z.object({}), + annotations: { + title: "Read Middleware Traefik Config", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const response = await apiClient.get( + "/settings.readMiddlewareTraefikConfig", + ); + + return ResponseFormatter.success( + "Successfully fetched middleware Traefik configuration", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsReadTraefikConfig.ts b/src/mcp/tools/settings/settingsReadTraefikConfig.ts new file mode 100644 index 0000000..c5e51f9 --- /dev/null +++ b/src/mcp/tools/settings/settingsReadTraefikConfig.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsReadTraefikConfig = createTool({ + name: "settings-read-traefik-config", + description: "Reads the Traefik configuration in Dokploy.", + schema: z.object({}), + annotations: { + title: "Read Traefik Config", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const response = await apiClient.get("/settings.readTraefikConfig"); + + return ResponseFormatter.success( + "Successfully fetched Traefik configuration", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsReadTraefikEnv.ts b/src/mcp/tools/settings/settingsReadTraefikEnv.ts new file mode 100644 index 0000000..04892e3 --- /dev/null +++ b/src/mcp/tools/settings/settingsReadTraefikEnv.ts @@ -0,0 +1,30 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsReadTraefikEnv = createTool({ + name: "settings-read-traefik-env", + description: "Reads the Traefik environment variables in Dokploy.", + schema: z.object({ + serverId: z + .string() + .optional() + .describe("Optional server ID to read env from a specific server."), + }), + annotations: { + title: "Read Traefik Env", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const params = input.serverId ? `?serverId=${input.serverId}` : ""; + const response = await apiClient.get(`/settings.readTraefikEnv${params}`); + + return ResponseFormatter.success( + "Successfully fetched Traefik environment", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsReadTraefikFile.ts b/src/mcp/tools/settings/settingsReadTraefikFile.ts new file mode 100644 index 0000000..5213ab1 --- /dev/null +++ b/src/mcp/tools/settings/settingsReadTraefikFile.ts @@ -0,0 +1,43 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsReadTraefikFile = createTool({ + name: "settings-read-traefik-file", + description: "Reads a specific Traefik configuration file in Dokploy.", + schema: z.object({ + path: z + .string() + .min(1) + .describe( + "The path of the Traefik file to read. Must be at least 1 character.", + ), + serverId: z + .string() + .optional() + .describe( + "Optional server ID to read the file from a specific remote server.", + ), + }), + annotations: { + title: "Read Traefik File", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const params = new URLSearchParams({ path: input.path }); + if (input.serverId) { + params.append("serverId", input.serverId); + } + const response = await apiClient.get( + `/settings.readTraefikFile?${params.toString()}`, + ); + + return ResponseFormatter.success( + `Successfully fetched Traefik file "${input.path}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsReadWebServerTraefikConfig.ts b/src/mcp/tools/settings/settingsReadWebServerTraefikConfig.ts new file mode 100644 index 0000000..73f699d --- /dev/null +++ b/src/mcp/tools/settings/settingsReadWebServerTraefikConfig.ts @@ -0,0 +1,26 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsReadWebServerTraefikConfig = createTool({ + name: "settings-read-web-server-traefik-config", + description: "Reads the web server Traefik configuration in Dokploy.", + schema: z.object({}), + annotations: { + title: "Read Web Server Traefik Config", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const response = await apiClient.get( + "/settings.readWebServerTraefikConfig", + ); + + return ResponseFormatter.success( + "Successfully fetched web server Traefik configuration", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsReloadRedis.ts b/src/mcp/tools/settings/settingsReloadRedis.ts new file mode 100644 index 0000000..9797ac0 --- /dev/null +++ b/src/mcp/tools/settings/settingsReloadRedis.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsReloadRedis = createTool({ + name: "settings-reload-redis", + description: "Reloads Redis in Dokploy.", + schema: z.object({}), + annotations: { + title: "Reload Redis", + destructiveHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const response = await apiClient.post("/settings.reloadRedis", {}); + + return ResponseFormatter.success( + "Redis reloaded successfully", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsReloadServer.ts b/src/mcp/tools/settings/settingsReloadServer.ts new file mode 100644 index 0000000..1be6a9d --- /dev/null +++ b/src/mcp/tools/settings/settingsReloadServer.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsReloadServer = createTool({ + name: "settings-reload-server", + description: "Reloads the Dokploy server.", + schema: z.object({}), + annotations: { + title: "Reload Server", + destructiveHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const response = await apiClient.post("/settings.reloadServer", {}); + + return ResponseFormatter.success( + "Server reloaded successfully", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsReloadTraefik.ts b/src/mcp/tools/settings/settingsReloadTraefik.ts new file mode 100644 index 0000000..fdf6f87 --- /dev/null +++ b/src/mcp/tools/settings/settingsReloadTraefik.ts @@ -0,0 +1,29 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsReloadTraefik = createTool({ + name: "settings-reload-traefik", + description: "Reloads Traefik in Dokploy.", + schema: z.object({ + serverId: z + .string() + .optional() + .describe("Optional server ID to reload Traefik on a specific server."), + }), + annotations: { + title: "Reload Traefik", + destructiveHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/settings.reloadTraefik", input); + + return ResponseFormatter.success( + "Traefik reloaded successfully", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsSaveSSHPrivateKey.ts b/src/mcp/tools/settings/settingsSaveSSHPrivateKey.ts new file mode 100644 index 0000000..4b38be4 --- /dev/null +++ b/src/mcp/tools/settings/settingsSaveSSHPrivateKey.ts @@ -0,0 +1,31 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsSaveSSHPrivateKey = createTool({ + name: "settings-save-ssh-private-key", + description: "Saves an SSH private key in Dokploy.", + schema: z.object({ + sshPrivateKey: z + .string() + .nullable() + .describe( + "The SSH private key to save. Set to null to clear the existing key.", + ), + }), + annotations: { + title: "Save SSH Private Key", + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/settings.saveSSHPrivateKey", input); + + return ResponseFormatter.success( + "SSH private key saved successfully", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsSetupGPU.ts b/src/mcp/tools/settings/settingsSetupGPU.ts new file mode 100644 index 0000000..f838208 --- /dev/null +++ b/src/mcp/tools/settings/settingsSetupGPU.ts @@ -0,0 +1,29 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsSetupGPU = createTool({ + name: "settings-setup-gpu", + description: "Sets up GPU support in Dokploy.", + schema: z.object({ + serverId: z + .string() + .optional() + .describe("Optional server ID to setup GPU on a specific server."), + }), + annotations: { + title: "Setup GPU", + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/settings.setupGPU", input); + + return ResponseFormatter.success( + "GPU setup completed successfully", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsToggleDashboard.ts b/src/mcp/tools/settings/settingsToggleDashboard.ts new file mode 100644 index 0000000..55599d4 --- /dev/null +++ b/src/mcp/tools/settings/settingsToggleDashboard.ts @@ -0,0 +1,33 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsToggleDashboard = createTool({ + name: "settings-toggle-dashboard", + description: "Toggles the Traefik dashboard in Dokploy.", + schema: z.object({ + enableDashboard: z + .boolean() + .optional() + .describe("Whether to enable or disable the dashboard."), + serverId: z + .string() + .optional() + .describe("Optional server ID to toggle dashboard on a specific server."), + }), + annotations: { + title: "Toggle Dashboard", + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/settings.toggleDashboard", input); + + return ResponseFormatter.success( + "Dashboard toggled successfully", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsToggleRequests.ts b/src/mcp/tools/settings/settingsToggleRequests.ts new file mode 100644 index 0000000..e9790aa --- /dev/null +++ b/src/mcp/tools/settings/settingsToggleRequests.ts @@ -0,0 +1,28 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsToggleRequests = createTool({ + name: "settings-toggle-requests", + description: "Toggles the activate requests feature in Dokploy.", + schema: z.object({ + enable: z + .boolean() + .describe("Whether to enable or disable activate requests."), + }), + annotations: { + title: "Toggle Requests", + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/settings.toggleRequests", input); + + return ResponseFormatter.success( + `Activate requests ${input.enable ? "enabled" : "disabled"} successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsUpdateDockerCleanup.ts b/src/mcp/tools/settings/settingsUpdateDockerCleanup.ts new file mode 100644 index 0000000..ff95147 --- /dev/null +++ b/src/mcp/tools/settings/settingsUpdateDockerCleanup.ts @@ -0,0 +1,37 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsUpdateDockerCleanup = createTool({ + name: "settings-update-docker-cleanup", + description: "Updates Docker cleanup settings in Dokploy.", + schema: z.object({ + enableDockerCleanup: z + .boolean() + .describe("Whether to enable automatic Docker cleanup."), + serverId: z + .string() + .optional() + .describe( + "Optional server ID to update cleanup settings on a specific server.", + ), + }), + annotations: { + title: "Update Docker Cleanup Settings", + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post( + "/settings.updateDockerCleanup", + input, + ); + + return ResponseFormatter.success( + "Docker cleanup settings updated successfully", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsUpdateLogCleanup.ts b/src/mcp/tools/settings/settingsUpdateLogCleanup.ts new file mode 100644 index 0000000..69c4c86 --- /dev/null +++ b/src/mcp/tools/settings/settingsUpdateLogCleanup.ts @@ -0,0 +1,31 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsUpdateLogCleanup = createTool({ + name: "settings-update-log-cleanup", + description: "Updates log cleanup settings in Dokploy.", + schema: z.object({ + cronExpression: z + .string() + .nullable() + .describe( + "Cron expression for log cleanup schedule. Set to null to disable.", + ), + }), + annotations: { + title: "Update Log Cleanup", + destructiveHint: false, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/settings.updateLogCleanup", input); + + return ResponseFormatter.success( + "Log cleanup settings updated successfully", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsUpdateMiddlewareTraefikConfig.ts b/src/mcp/tools/settings/settingsUpdateMiddlewareTraefikConfig.ts new file mode 100644 index 0000000..a179a65 --- /dev/null +++ b/src/mcp/tools/settings/settingsUpdateMiddlewareTraefikConfig.ts @@ -0,0 +1,34 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsUpdateMiddlewareTraefikConfig = createTool({ + name: "settings-update-middleware-traefik-config", + description: "Updates the middleware Traefik configuration in Dokploy.", + schema: z.object({ + traefikConfig: z + .string() + .min(1) + .describe( + "The new middleware Traefik configuration content. Must be at least 1 character.", + ), + }), + annotations: { + title: "Update Middleware Traefik Config", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post( + "/settings.updateMiddlewareTraefikConfig", + input, + ); + + return ResponseFormatter.success( + "Middleware Traefik configuration updated successfully", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsUpdateServer.ts b/src/mcp/tools/settings/settingsUpdateServer.ts new file mode 100644 index 0000000..6f2fa48 --- /dev/null +++ b/src/mcp/tools/settings/settingsUpdateServer.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsUpdateServer = createTool({ + name: "settings-update-server", + description: "Updates the Dokploy server to the latest version.", + schema: z.object({}), + annotations: { + title: "Update Server", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async () => { + const response = await apiClient.post("/settings.updateServer", {}); + + return ResponseFormatter.success( + "Server update initiated successfully", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsUpdateTraefikConfig.ts b/src/mcp/tools/settings/settingsUpdateTraefikConfig.ts new file mode 100644 index 0000000..21240d3 --- /dev/null +++ b/src/mcp/tools/settings/settingsUpdateTraefikConfig.ts @@ -0,0 +1,34 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsUpdateTraefikConfig = createTool({ + name: "settings-update-traefik-config", + description: "Updates the main Traefik configuration in Dokploy.", + schema: z.object({ + traefikConfig: z + .string() + .min(1) + .describe( + "The new Traefik configuration content. Must be at least 1 character.", + ), + }), + annotations: { + title: "Update Traefik Config", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post( + "/settings.updateTraefikConfig", + input, + ); + + return ResponseFormatter.success( + "Traefik configuration updated successfully", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsUpdateTraefikFile.ts b/src/mcp/tools/settings/settingsUpdateTraefikFile.ts new file mode 100644 index 0000000..c335cd2 --- /dev/null +++ b/src/mcp/tools/settings/settingsUpdateTraefikFile.ts @@ -0,0 +1,43 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsUpdateTraefikFile = createTool({ + name: "settings-update-traefik-file", + description: "Updates a specific Traefik configuration file in Dokploy.", + schema: z.object({ + path: z + .string() + .min(1) + .describe( + "The path of the Traefik file to update. Must be at least 1 character.", + ), + traefikConfig: z + .string() + .min(1) + .describe( + "The new Traefik configuration content. Must be at least 1 character.", + ), + serverId: z + .string() + .optional() + .describe( + "Optional server ID to update the file on a specific remote server.", + ), + }), + annotations: { + title: "Update Traefik File", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/settings.updateTraefikFile", input); + + return ResponseFormatter.success( + `Traefik file "${input.path}" updated successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsUpdateTraefikPorts.ts b/src/mcp/tools/settings/settingsUpdateTraefikPorts.ts new file mode 100644 index 0000000..0ede9bf --- /dev/null +++ b/src/mcp/tools/settings/settingsUpdateTraefikPorts.ts @@ -0,0 +1,47 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsUpdateTraefikPorts = createTool({ + name: "settings-update-traefik-ports", + description: "Updates Traefik ports configuration in Dokploy.", + schema: z.object({ + serverId: z + .string() + .optional() + .describe("Optional server ID to update ports on a specific server."), + additionalPorts: z + .array( + z.object({ + targetPort: z + .number() + .describe("The target port inside the container."), + publishedPort: z + .number() + .describe("The published port exposed on the host."), + protocol: z + .enum(["tcp", "udp", "sctp"]) + .describe("The protocol for the port mapping: tcp, udp, or sctp."), + }), + ) + .describe("List of additional ports to configure for Traefik."), + }), + annotations: { + title: "Update Traefik Ports", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post( + "/settings.updateTraefikPorts", + input, + ); + + return ResponseFormatter.success( + "Traefik ports updated successfully", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsUpdateWebServerTraefikConfig.ts b/src/mcp/tools/settings/settingsUpdateWebServerTraefikConfig.ts new file mode 100644 index 0000000..fe53dde --- /dev/null +++ b/src/mcp/tools/settings/settingsUpdateWebServerTraefikConfig.ts @@ -0,0 +1,34 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsUpdateWebServerTraefikConfig = createTool({ + name: "settings-update-web-server-traefik-config", + description: "Updates the web server Traefik configuration in Dokploy.", + schema: z.object({ + traefikConfig: z + .string() + .min(1) + .describe( + "The new web server Traefik configuration content. Must be at least 1 character.", + ), + }), + annotations: { + title: "Update Web Server Traefik Config", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post( + "/settings.updateWebServerTraefikConfig", + input, + ); + + return ResponseFormatter.success( + "Web server Traefik configuration updated successfully", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/settings/settingsWriteTraefikEnv.ts b/src/mcp/tools/settings/settingsWriteTraefikEnv.ts new file mode 100644 index 0000000..1e4f40c --- /dev/null +++ b/src/mcp/tools/settings/settingsWriteTraefikEnv.ts @@ -0,0 +1,36 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const settingsWriteTraefikEnv = createTool({ + name: "settings-write-traefik-env", + description: "Writes the Traefik environment variables in Dokploy.", + schema: z.object({ + env: z + .string() + .describe( + "The Traefik environment variables content in KEY=value format. Required field.", + ), + serverId: z + .string() + .optional() + .describe( + "Optional server ID to write environment variables on a specific server.", + ), + }), + annotations: { + title: "Write Traefik Env", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/settings.writeTraefikEnv", input); + + return ResponseFormatter.success( + "Traefik environment updated successfully", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/sshKey/index.ts b/src/mcp/tools/sshKey/index.ts new file mode 100644 index 0000000..d2d74c4 --- /dev/null +++ b/src/mcp/tools/sshKey/index.ts @@ -0,0 +1,6 @@ +export { sshKeyCreate } from "./sshKeyCreate.js"; +export { sshKeyRemove } from "./sshKeyRemove.js"; +export { sshKeyOne } from "./sshKeyOne.js"; +export { sshKeyAll } from "./sshKeyAll.js"; +export { sshKeyGenerate } from "./sshKeyGenerate.js"; +export { sshKeyUpdate } from "./sshKeyUpdate.js"; diff --git a/src/mcp/tools/sshKey/sshKeyAll.ts b/src/mcp/tools/sshKey/sshKeyAll.ts new file mode 100644 index 0000000..d2d50cd --- /dev/null +++ b/src/mcp/tools/sshKey/sshKeyAll.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const sshKeyAll = createTool({ + name: "ssh-key-all", + description: "Gets all SSH keys in Dokploy.", + schema: z.object({}), + annotations: { + title: "List All SSH Keys", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const sshKeys = await apiClient.get("/sshKey.all"); + + return ResponseFormatter.success( + "Successfully fetched all SSH keys", + sshKeys.data, + ); + }, +}); diff --git a/src/mcp/tools/sshKey/sshKeyCreate.ts b/src/mcp/tools/sshKey/sshKeyCreate.ts new file mode 100644 index 0000000..dbb6b6c --- /dev/null +++ b/src/mcp/tools/sshKey/sshKeyCreate.ts @@ -0,0 +1,34 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const sshKeyCreate = createTool({ + name: "ssh-key-create", + description: "Creates a new SSH key in Dokploy.", + schema: z.object({ + name: z.string().min(1).describe("The name of the SSH key. Required."), + privateKey: z.string().describe("The private SSH key content (PEM format). Required."), + publicKey: z.string().describe("The public SSH key content. Required."), + organizationId: z.string().describe("The organization ID to associate this key with. Required."), + description: z + .string() + .nullable() + .optional() + .describe("An optional description for the SSH key."), + }), + annotations: { + title: "Create SSH Key", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/sshKey.create", input); + + return ResponseFormatter.success( + `SSH key "${input.name}" created successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/sshKey/sshKeyGenerate.ts b/src/mcp/tools/sshKey/sshKeyGenerate.ts new file mode 100644 index 0000000..f572ca1 --- /dev/null +++ b/src/mcp/tools/sshKey/sshKeyGenerate.ts @@ -0,0 +1,29 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const sshKeyGenerate = createTool({ + name: "ssh-key-generate", + description: "Generates a new SSH key pair in Dokploy.", + schema: z.object({ + type: z + .enum(["rsa", "ed25519"]) + .optional() + .describe("The type of SSH key to generate. Either 'rsa' (2048-bit) or 'ed25519' (more secure, recommended). Optional."), + }), + annotations: { + title: "Generate SSH Key", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/sshKey.generate", input); + + return ResponseFormatter.success( + "SSH key pair generated successfully", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/sshKey/sshKeyOne.ts b/src/mcp/tools/sshKey/sshKeyOne.ts new file mode 100644 index 0000000..40caec5 --- /dev/null +++ b/src/mcp/tools/sshKey/sshKeyOne.ts @@ -0,0 +1,35 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const sshKeyOne = createTool({ + name: "ssh-key-one", + description: "Gets a specific SSH key by its ID in Dokploy.", + schema: z.object({ + sshKeyId: z.string().describe("The unique identifier of the SSH key to retrieve. Required."), + }), + annotations: { + title: "Get SSH Key Details", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const sshKey = await apiClient.get( + `/sshKey.one?sshKeyId=${input.sshKeyId}`, + ); + + if (!sshKey?.data) { + return ResponseFormatter.error( + "Failed to fetch SSH key", + `SSH key with ID "${input.sshKeyId}" not found`, + ); + } + + return ResponseFormatter.success( + `Successfully fetched SSH key "${input.sshKeyId}"`, + sshKey.data, + ); + }, +}); diff --git a/src/mcp/tools/sshKey/sshKeyRemove.ts b/src/mcp/tools/sshKey/sshKeyRemove.ts new file mode 100644 index 0000000..ccd18fc --- /dev/null +++ b/src/mcp/tools/sshKey/sshKeyRemove.ts @@ -0,0 +1,26 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const sshKeyRemove = createTool({ + name: "ssh-key-remove", + description: "Removes/deletes an SSH key from Dokploy.", + schema: z.object({ + sshKeyId: z.string().describe("The unique identifier of the SSH key to remove. Required."), + }), + annotations: { + title: "Remove SSH Key", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/sshKey.remove", input); + + return ResponseFormatter.success( + `SSH key "${input.sshKeyId}" removed successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/sshKey/sshKeyUpdate.ts b/src/mcp/tools/sshKey/sshKeyUpdate.ts new file mode 100644 index 0000000..27c03c6 --- /dev/null +++ b/src/mcp/tools/sshKey/sshKeyUpdate.ts @@ -0,0 +1,37 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const sshKeyUpdate = createTool({ + name: "ssh-key-update", + description: "Updates an existing SSH key in Dokploy.", + schema: z.object({ + sshKeyId: z.string().describe("The unique identifier of the SSH key to update. Required."), + name: z.string().min(1).optional().describe("The new name of the SSH key. Optional."), + description: z + .string() + .nullable() + .optional() + .describe("The new description for the SSH key. Optional, can be set to null to remove."), + lastUsedAt: z + .string() + .nullable() + .optional() + .describe("The last used timestamp in ISO format. Optional, can be set to null."), + }), + annotations: { + title: "Update SSH Key", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/sshKey.update", input); + + return ResponseFormatter.success( + `SSH key "${input.sshKeyId}" updated successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/stripe/index.ts b/src/mcp/tools/stripe/index.ts new file mode 100644 index 0000000..43de025 --- /dev/null +++ b/src/mcp/tools/stripe/index.ts @@ -0,0 +1,4 @@ +export { stripeGetProducts } from "./stripeGetProducts.js"; +export { stripeCreateCheckoutSession } from "./stripeCreateCheckoutSession.js"; +export { stripeCreateCustomerPortalSession } from "./stripeCreateCustomerPortalSession.js"; +export { stripeCanCreateMoreServers } from "./stripeCanCreateMoreServers.js"; diff --git a/src/mcp/tools/stripe/stripeCanCreateMoreServers.ts b/src/mcp/tools/stripe/stripeCanCreateMoreServers.ts new file mode 100644 index 0000000..1afc957 --- /dev/null +++ b/src/mcp/tools/stripe/stripeCanCreateMoreServers.ts @@ -0,0 +1,25 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const stripeCanCreateMoreServers = createTool({ + name: "stripe-can-create-more-servers", + description: + "Checks if the user can create more servers based on their Stripe subscription in Dokploy.", + schema: z.object({}), + annotations: { + title: "Check Server Creation Limit", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const response = await apiClient.get("/stripe.canCreateMoreServers"); + + return ResponseFormatter.success( + "Successfully checked server creation limit", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/stripe/stripeCreateCheckoutSession.ts b/src/mcp/tools/stripe/stripeCreateCheckoutSession.ts new file mode 100644 index 0000000..d0c2c31 --- /dev/null +++ b/src/mcp/tools/stripe/stripeCreateCheckoutSession.ts @@ -0,0 +1,36 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const stripeCreateCheckoutSession = createTool({ + name: "stripe-create-checkout-session", + description: "Creates a Stripe checkout session in Dokploy.", + schema: z.object({ + productId: z.string().describe("The Stripe product ID to purchase. Required."), + serverQuantity: z + .number() + .min(1) + .describe("The number of servers to purchase. Must be at least 1. Required."), + isAnnual: z.boolean().describe("Whether this is an annual subscription (true) or monthly (false). Required."), + }), + annotations: { + title: "Create Stripe Checkout Session", + readOnlyHint: false, + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/stripe.createCheckoutSession", { + productId: input.productId, + serverQuantity: input.serverQuantity, + isAnnual: input.isAnnual, + }); + + return ResponseFormatter.success( + "Successfully created Stripe checkout session", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/stripe/stripeCreateCustomerPortalSession.ts b/src/mcp/tools/stripe/stripeCreateCustomerPortalSession.ts new file mode 100644 index 0000000..e591eb1 --- /dev/null +++ b/src/mcp/tools/stripe/stripeCreateCustomerPortalSession.ts @@ -0,0 +1,28 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const stripeCreateCustomerPortalSession = createTool({ + name: "stripe-create-customer-portal-session", + description: "Creates a Stripe customer portal session in Dokploy.", + schema: z.object({}), + annotations: { + title: "Create Stripe Customer Portal Session", + readOnlyHint: false, + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async () => { + const response = await apiClient.post( + "/stripe.createCustomerPortalSession", + {}, + ); + + return ResponseFormatter.success( + "Successfully created Stripe customer portal session", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/stripe/stripeGetProducts.ts b/src/mcp/tools/stripe/stripeGetProducts.ts new file mode 100644 index 0000000..faee827 --- /dev/null +++ b/src/mcp/tools/stripe/stripeGetProducts.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const stripeGetProducts = createTool({ + name: "stripe-get-products", + description: "Gets all Stripe products available in Dokploy.", + schema: z.object({}), + annotations: { + title: "Get Stripe Products", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const response = await apiClient.get("/stripe.getProducts"); + + return ResponseFormatter.success( + "Successfully fetched Stripe products", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/swarm/index.ts b/src/mcp/tools/swarm/index.ts new file mode 100644 index 0000000..8ccf0af --- /dev/null +++ b/src/mcp/tools/swarm/index.ts @@ -0,0 +1,3 @@ +export { swarmGetNodes } from "./swarmGetNodes.js"; +export { swarmGetNodeInfo } from "./swarmGetNodeInfo.js"; +export { swarmGetNodeApps } from "./swarmGetNodeApps.js"; diff --git a/src/mcp/tools/swarm/swarmGetNodeApps.ts b/src/mcp/tools/swarm/swarmGetNodeApps.ts new file mode 100644 index 0000000..c111b26 --- /dev/null +++ b/src/mcp/tools/swarm/swarmGetNodeApps.ts @@ -0,0 +1,37 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const swarmGetNodeApps = createTool({ + name: "swarm-get-node-apps", + description: "Gets all applications deployed on swarm nodes in Dokploy.", + schema: z.object({ + serverId: z + .string() + .optional() + .describe("The ID of the server to get node apps from."), + }), + annotations: { + title: "Get Swarm Node Apps", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const params = new URLSearchParams(); + if (input.serverId) { + params.append("serverId", input.serverId); + } + + const queryString = params.toString(); + const url = `/swarm.getNodeApps${queryString ? `?${queryString}` : ""}`; + + const response = await apiClient.get(url); + + return ResponseFormatter.success( + "Successfully fetched swarm node apps", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/swarm/swarmGetNodeInfo.ts b/src/mcp/tools/swarm/swarmGetNodeInfo.ts new file mode 100644 index 0000000..ed20356 --- /dev/null +++ b/src/mcp/tools/swarm/swarmGetNodeInfo.ts @@ -0,0 +1,39 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const swarmGetNodeInfo = createTool({ + name: "swarm-get-node-info", + description: + "Gets detailed information about a specific swarm node in Dokploy.", + schema: z.object({ + nodeId: z.string().describe("The ID of the node to get information for."), + serverId: z + .string() + .optional() + .describe("The ID of the server to get node info from."), + }), + annotations: { + title: "Get Swarm Node Info", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const params = new URLSearchParams(); + params.append("nodeId", input.nodeId); + if (input.serverId) { + params.append("serverId", input.serverId); + } + + const response = await apiClient.get( + `/swarm.getNodeInfo?${params.toString()}`, + ); + + return ResponseFormatter.success( + `Successfully fetched info for swarm node "${input.nodeId}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/swarm/swarmGetNodes.ts b/src/mcp/tools/swarm/swarmGetNodes.ts new file mode 100644 index 0000000..964f525 --- /dev/null +++ b/src/mcp/tools/swarm/swarmGetNodes.ts @@ -0,0 +1,37 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const swarmGetNodes = createTool({ + name: "swarm-get-nodes", + description: "Gets all nodes in the Docker Swarm from Dokploy.", + schema: z.object({ + serverId: z + .string() + .optional() + .describe("The ID of the server to get swarm nodes from."), + }), + annotations: { + title: "Get Swarm Nodes", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const params = new URLSearchParams(); + if (input.serverId) { + params.append("serverId", input.serverId); + } + + const queryString = params.toString(); + const url = `/swarm.getNodes${queryString ? `?${queryString}` : ""}`; + + const response = await apiClient.get(url); + + return ResponseFormatter.success( + "Successfully fetched swarm nodes", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/user/index.ts b/src/mcp/tools/user/index.ts new file mode 100644 index 0000000..501639e --- /dev/null +++ b/src/mcp/tools/user/index.ts @@ -0,0 +1,18 @@ +export { userAll } from "./userAll.js"; +export { userAssignPermissions } from "./userAssignPermissions.js"; +export { userCheckOrganizations } from "./userCheckOrganizations.js"; +export { userCreateApiKey } from "./userCreateApiKey.js"; +export { userDeleteApiKey } from "./userDeleteApiKey.js"; +export { userGenerateToken } from "./userGenerateToken.js"; +export { userGet } from "./userGet.js"; +export { userGetBackups } from "./userGetBackups.js"; +export { userGetByToken } from "./userGetByToken.js"; +export { userGetContainerMetrics } from "./userGetContainerMetrics.js"; +export { userGetInvitations } from "./userGetInvitations.js"; +export { userGetMetricsToken } from "./userGetMetricsToken.js"; +export { userGetServerMetrics } from "./userGetServerMetrics.js"; +export { userHaveRootAccess } from "./userHaveRootAccess.js"; +export { userOne } from "./userOne.js"; +export { userRemove } from "./userRemove.js"; +export { userSendInvitation } from "./userSendInvitation.js"; +export { userUpdate } from "./userUpdate.js"; diff --git a/src/mcp/tools/user/userAll.ts b/src/mcp/tools/user/userAll.ts new file mode 100644 index 0000000..c072faa --- /dev/null +++ b/src/mcp/tools/user/userAll.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const userAll = createTool({ + name: "user-all", + description: "Gets all users in the Dokploy instance.", + schema: z.object({}), + annotations: { + title: "Get All Users", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const response = await apiClient.get("/user.all"); + + return ResponseFormatter.success( + "Successfully fetched all users", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/user/userAssignPermissions.ts b/src/mcp/tools/user/userAssignPermissions.ts new file mode 100644 index 0000000..a70daec --- /dev/null +++ b/src/mcp/tools/user/userAssignPermissions.ts @@ -0,0 +1,88 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const userAssignPermissions = createTool({ + name: "user-assign-permissions", + description: + "Assigns permissions to a user in Dokploy. All fields are required.", + schema: z.object({ + id: z + .string() + .min(1) + .describe( + "The ID of the user to assign permissions. Must be at least 1 character.", + ), + accessedProjects: z + .array(z.string()) + .describe( + "List of project IDs the user can access. Required, can be empty array.", + ), + accessedEnvironments: z + .array(z.string()) + .describe( + "List of environment IDs the user can access. Required, can be empty array.", + ), + accessedServices: z + .array(z.string()) + .describe( + "List of service IDs the user can access. Required, can be empty array.", + ), + canCreateProjects: z + .boolean() + .describe("Whether the user can create new projects. Required."), + canCreateServices: z + .boolean() + .describe( + "Whether the user can create new services within projects. Required.", + ), + canDeleteProjects: z + .boolean() + .describe("Whether the user can delete projects. Required."), + canDeleteServices: z + .boolean() + .describe("Whether the user can delete services. Required."), + canAccessToDocker: z + .boolean() + .describe( + "Whether the user can access Docker management features. Required.", + ), + canAccessToTraefikFiles: z + .boolean() + .describe( + "Whether the user can access and modify Traefik configuration files. Required.", + ), + canAccessToAPI: z + .boolean() + .describe("Whether the user can access the Dokploy API. Required."), + canAccessToSSHKeys: z + .boolean() + .describe("Whether the user can access and manage SSH keys. Required."), + canAccessToGitProviders: z + .boolean() + .describe( + "Whether the user can access and configure Git providers. Required.", + ), + canDeleteEnvironments: z + .boolean() + .describe("Whether the user can delete environments. Required."), + canCreateEnvironments: z + .boolean() + .describe("Whether the user can create new environments. Required."), + }), + annotations: { + title: "Assign User Permissions", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/user.assignPermissions", input); + + return ResponseFormatter.success( + `Permissions assigned to user "${input.id}" successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/user/userCheckOrganizations.ts b/src/mcp/tools/user/userCheckOrganizations.ts new file mode 100644 index 0000000..a04494e --- /dev/null +++ b/src/mcp/tools/user/userCheckOrganizations.ts @@ -0,0 +1,30 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const userCheckOrganizations = createTool({ + name: "user-check-organizations", + description: "Checks the organizations a user belongs to in Dokploy.", + schema: z.object({ + userId: z + .string() + .describe("The ID of the user to check organizations for."), + }), + annotations: { + title: "Check User Organizations", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.get( + `/user.checkUserOrganizations?userId=${input.userId}`, + ); + + return ResponseFormatter.success( + `Successfully fetched organizations for user "${input.userId}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/user/userCreateApiKey.ts b/src/mcp/tools/user/userCreateApiKey.ts new file mode 100644 index 0000000..a57f474 --- /dev/null +++ b/src/mcp/tools/user/userCreateApiKey.ts @@ -0,0 +1,65 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const userCreateApiKey = createTool({ + name: "user-create-api-key", + description: "Creates a new API key for the current user in Dokploy.", + schema: z.object({ + name: z + .string() + .min(1) + .describe("The name for the API key. Required field."), + prefix: z.string().optional().describe("Optional prefix for the API key."), + expiresIn: z + .number() + .optional() + .describe("Expiration time in seconds from now."), + metadata: z + .object({ + organizationId: z + .string() + .describe("Organization ID to associate with the key. Required."), + }) + .describe("Metadata for the API key. Must include organizationId."), + rateLimitEnabled: z + .boolean() + .optional() + .describe("Whether rate limiting is enabled for this key."), + rateLimitTimeWindow: z + .number() + .optional() + .describe("Rate limit time window in milliseconds."), + rateLimitMax: z + .number() + .optional() + .describe("Maximum requests allowed within the time window."), + remaining: z + .number() + .optional() + .describe("Remaining request count for the current window."), + refillAmount: z + .number() + .optional() + .describe("Amount to refill the rate limit bucket."), + refillInterval: z + .number() + .optional() + .describe("Refill interval in milliseconds."), + }), + annotations: { + title: "Create API Key", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/user.createApiKey", input); + + return ResponseFormatter.success( + `API key "${input.name}" created successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/user/userDeleteApiKey.ts b/src/mcp/tools/user/userDeleteApiKey.ts new file mode 100644 index 0000000..683e0dc --- /dev/null +++ b/src/mcp/tools/user/userDeleteApiKey.ts @@ -0,0 +1,26 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const userDeleteApiKey = createTool({ + name: "user-delete-api-key", + description: "Deletes an API key for the current user in Dokploy.", + schema: z.object({ + apiKeyId: z.string().describe("The ID of the API key to delete."), + }), + annotations: { + title: "Delete API Key", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/user.deleteApiKey", input); + + return ResponseFormatter.success( + `API key "${input.apiKeyId}" deleted successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/user/userGenerateToken.ts b/src/mcp/tools/user/userGenerateToken.ts new file mode 100644 index 0000000..4953f9a --- /dev/null +++ b/src/mcp/tools/user/userGenerateToken.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const userGenerateToken = createTool({ + name: "user-generate-token", + description: "Generates a new token for the current user in Dokploy.", + schema: z.object({}), + annotations: { + title: "Generate User Token", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async () => { + const response = await apiClient.post("/user.generateToken", {}); + + return ResponseFormatter.success( + "Token generated successfully", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/user/userGet.ts b/src/mcp/tools/user/userGet.ts new file mode 100644 index 0000000..3da9a18 --- /dev/null +++ b/src/mcp/tools/user/userGet.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const userGet = createTool({ + name: "user-get", + description: "Gets the current authenticated user information in Dokploy.", + schema: z.object({}), + annotations: { + title: "Get Current User", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const response = await apiClient.get("/user.get"); + + return ResponseFormatter.success( + "Successfully fetched current user", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/user/userGetBackups.ts b/src/mcp/tools/user/userGetBackups.ts new file mode 100644 index 0000000..92137de --- /dev/null +++ b/src/mcp/tools/user/userGetBackups.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const userGetBackups = createTool({ + name: "user-get-backups", + description: "Gets the backups for the current user in Dokploy.", + schema: z.object({}), + annotations: { + title: "Get User Backups", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const response = await apiClient.get("/user.getBackups"); + + return ResponseFormatter.success( + "Successfully fetched user backups", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/user/userGetByToken.ts b/src/mcp/tools/user/userGetByToken.ts new file mode 100644 index 0000000..a8a3fc3 --- /dev/null +++ b/src/mcp/tools/user/userGetByToken.ts @@ -0,0 +1,35 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const userGetByToken = createTool({ + name: "user-get-by-token", + description: "Gets a user by their token in Dokploy.", + schema: z.object({ + token: z.string().min(1).describe("The token to look up the user."), + }), + annotations: { + title: "Get User By Token", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.get( + `/user.getUserByToken?token=${input.token}`, + ); + + if (!response?.data) { + return ResponseFormatter.error( + "Failed to fetch user", + "User with the provided token not found", + ); + } + + return ResponseFormatter.success( + "Successfully fetched user by token", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/user/userGetContainerMetrics.ts b/src/mcp/tools/user/userGetContainerMetrics.ts new file mode 100644 index 0000000..d838a76 --- /dev/null +++ b/src/mcp/tools/user/userGetContainerMetrics.ts @@ -0,0 +1,37 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const userGetContainerMetrics = createTool({ + name: "user-get-container-metrics", + description: "Gets container metrics for a specific application in Dokploy.", + schema: z.object({ + url: z.string().describe("The metrics URL."), + token: z.string().describe("The metrics token."), + appName: z.string().describe("The application name to get metrics for."), + dataPoints: z.string().describe("The number of data points to retrieve."), + }), + annotations: { + title: "Get Container Metrics", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const params = new URLSearchParams({ + url: input.url, + token: input.token, + appName: input.appName, + dataPoints: input.dataPoints, + }); + const response = await apiClient.get( + `/user.getContainerMetrics?${params.toString()}`, + ); + + return ResponseFormatter.success( + `Successfully fetched container metrics for "${input.appName}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/user/userGetInvitations.ts b/src/mcp/tools/user/userGetInvitations.ts new file mode 100644 index 0000000..1dd02bc --- /dev/null +++ b/src/mcp/tools/user/userGetInvitations.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const userGetInvitations = createTool({ + name: "user-get-invitations", + description: "Gets pending invitations for the current user in Dokploy.", + schema: z.object({}), + annotations: { + title: "Get User Invitations", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const response = await apiClient.get("/user.getInvitations"); + + return ResponseFormatter.success( + "Successfully fetched user invitations", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/user/userGetMetricsToken.ts b/src/mcp/tools/user/userGetMetricsToken.ts new file mode 100644 index 0000000..9b4a3f4 --- /dev/null +++ b/src/mcp/tools/user/userGetMetricsToken.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const userGetMetricsToken = createTool({ + name: "user-get-metrics-token", + description: "Gets the metrics token for the current user in Dokploy.", + schema: z.object({}), + annotations: { + title: "Get Metrics Token", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const response = await apiClient.get("/user.getMetricsToken"); + + return ResponseFormatter.success( + "Successfully fetched metrics token", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/user/userGetServerMetrics.ts b/src/mcp/tools/user/userGetServerMetrics.ts new file mode 100644 index 0000000..baf7377 --- /dev/null +++ b/src/mcp/tools/user/userGetServerMetrics.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const userGetServerMetrics = createTool({ + name: "user-get-server-metrics", + description: "Gets server metrics for the current user in Dokploy.", + schema: z.object({}), + annotations: { + title: "Get Server Metrics", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const response = await apiClient.get("/user.getServerMetrics"); + + return ResponseFormatter.success( + "Successfully fetched server metrics", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/user/userHaveRootAccess.ts b/src/mcp/tools/user/userHaveRootAccess.ts new file mode 100644 index 0000000..fa00ba5 --- /dev/null +++ b/src/mcp/tools/user/userHaveRootAccess.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const userHaveRootAccess = createTool({ + name: "user-have-root-access", + description: "Checks if the current user has root access in Dokploy.", + schema: z.object({}), + annotations: { + title: "Check Root Access", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async () => { + const response = await apiClient.get("/user.haveRootAccess"); + + return ResponseFormatter.success( + "Successfully checked root access status", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/user/userOne.ts b/src/mcp/tools/user/userOne.ts new file mode 100644 index 0000000..59f3e85 --- /dev/null +++ b/src/mcp/tools/user/userOne.ts @@ -0,0 +1,33 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const userOne = createTool({ + name: "user-one", + description: "Gets a specific user by their ID in Dokploy.", + schema: z.object({ + userId: z.string().describe("The ID of the user to retrieve."), + }), + annotations: { + title: "Get User Details", + readOnlyHint: true, + idempotentHint: true, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.get(`/user.one?userId=${input.userId}`); + + if (!response?.data) { + return ResponseFormatter.error( + "Failed to fetch user", + `User with ID "${input.userId}" not found`, + ); + } + + return ResponseFormatter.success( + `Successfully fetched user "${input.userId}"`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/user/userRemove.ts b/src/mcp/tools/user/userRemove.ts new file mode 100644 index 0000000..f8e8e61 --- /dev/null +++ b/src/mcp/tools/user/userRemove.ts @@ -0,0 +1,26 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const userRemove = createTool({ + name: "user-remove", + description: "Removes a user from Dokploy.", + schema: z.object({ + userId: z.string().describe("The ID of the user to remove."), + }), + annotations: { + title: "Remove User", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/user.remove", input); + + return ResponseFormatter.success( + `User "${input.userId}" removed successfully`, + response.data, + ); + }, +}); diff --git a/src/mcp/tools/user/userSendInvitation.ts b/src/mcp/tools/user/userSendInvitation.ts new file mode 100644 index 0000000..c3a7a6c --- /dev/null +++ b/src/mcp/tools/user/userSendInvitation.ts @@ -0,0 +1,30 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const userSendInvitation = createTool({ + name: "user-send-invitation", + description: "Sends an invitation to a user in Dokploy.", + schema: z.object({ + invitationId: z.string().min(1).describe("The ID of the invitation."), + notificationId: z + .string() + .min(1) + .describe("The ID of the notification to use for sending."), + }), + annotations: { + title: "Send Invitation", + destructiveHint: false, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/user.sendInvitation", input); + + return ResponseFormatter.success( + "Invitation sent successfully", + response.data, + ); + }, +}); diff --git a/src/mcp/tools/user/userUpdate.ts b/src/mcp/tools/user/userUpdate.ts new file mode 100644 index 0000000..1756623 --- /dev/null +++ b/src/mcp/tools/user/userUpdate.ts @@ -0,0 +1,181 @@ +import { z } from "zod"; +import apiClient from "../../../utils/apiClient.js"; +import { ResponseFormatter } from "../../../utils/responseFormatter.js"; +import { createTool } from "../toolFactory.js"; + +export const userUpdate = createTool({ + name: "user-update", + description: "Updates user information in Dokploy.", + schema: z.object({ + id: z.string().min(1).optional().describe("The ID of the user to update."), + name: z.string().optional().describe("Display name of the user."), + isRegistered: z + .boolean() + .optional() + .describe("Whether the user is registered."), + expirationDate: z.string().optional().describe("User expiration date."), + createdAt2: z.string().optional().describe("Alternative creation timestamp."), + createdAt: z + .string() + .datetime() + .nullable() + .optional() + .describe("Creation timestamp in ISO 8601 format."), + twoFactorEnabled: z + .boolean() + .nullable() + .optional() + .describe("Whether two-factor authentication is enabled."), + email: z + .string() + .email() + .min(1) + .optional() + .describe("User email address."), + emailVerified: z + .boolean() + .optional() + .describe("Whether email is verified."), + image: z.string().nullable().optional().describe("User profile image URL."), + banned: z + .boolean() + .nullable() + .optional() + .describe("Whether the user is banned."), + banReason: z + .string() + .nullable() + .optional() + .describe("Reason for ban if banned."), + banExpires: z + .string() + .datetime() + .nullable() + .optional() + .describe("Ban expiration timestamp in ISO 8601 format."), + updatedAt: z + .string() + .datetime() + .optional() + .describe("Update timestamp in ISO 8601 format."), + serverIp: z + .string() + .nullable() + .optional() + .describe("Server IP address associated with the user."), + certificateType: z + .enum(["letsencrypt", "none", "custom"]) + .optional() + .describe("Type of SSL certificate to use."), + https: z.boolean().optional().describe("Whether HTTPS is enabled."), + host: z.string().nullable().optional().describe("Host domain."), + letsEncryptEmail: z + .string() + .nullable() + .optional() + .describe("Email for Let's Encrypt certificate."), + sshPrivateKey: z + .string() + .nullable() + .optional() + .describe("SSH private key for server access."), + enableDockerCleanup: z + .boolean() + .optional() + .describe("Whether automatic Docker cleanup is enabled."), + logCleanupCron: z + .string() + .nullable() + .optional() + .describe("Cron expression for log cleanup schedule."), + enablePaidFeatures: z + .boolean() + .optional() + .describe("Whether paid features are enabled."), + allowImpersonation: z + .boolean() + .optional() + .describe("Whether impersonation is allowed."), + metricsConfig: z + .object({ + server: z + .object({ + type: z + .enum(["Dokploy", "Remote"]) + .describe("Metrics server type."), + refreshRate: z.number().describe("Refresh rate in seconds."), + port: z.number().describe("Port number."), + token: z.string().describe("Authentication token."), + urlCallback: z.string().describe("Callback URL."), + retentionDays: z.number().describe("Data retention in days."), + cronJob: z.string().describe("Cron expression for cleanup."), + thresholds: z + .object({ + cpu: z.number().describe("CPU threshold percentage."), + memory: z.number().describe("Memory threshold percentage."), + }) + .describe("Alert thresholds."), + }) + .describe("Server metrics configuration."), + containers: z + .object({ + refreshRate: z.number().describe("Refresh rate in seconds."), + services: z + .object({ + include: z + .array(z.string()) + .describe("Services to include."), + exclude: z + .array(z.string()) + .describe("Services to exclude."), + }) + .describe("Service filters."), + }) + .describe("Container metrics configuration."), + }) + .optional() + .describe("Metrics configuration for monitoring."), + cleanupCacheApplications: z + .boolean() + .optional() + .describe("Whether to cleanup cache for applications."), + cleanupCacheOnPreviews: z + .boolean() + .optional() + .describe("Whether to cleanup cache on previews."), + cleanupCacheOnCompose: z + .boolean() + .optional() + .describe("Whether to cleanup cache on compose deployments."), + stripeCustomerId: z + .string() + .nullable() + .optional() + .describe("Stripe customer ID."), + stripeSubscriptionId: z + .string() + .nullable() + .optional() + .describe("Stripe subscription ID."), + serversQuantity: z.number().optional().describe("Number of servers."), + password: z.string().optional().describe("New password."), + currentPassword: z + .string() + .optional() + .describe("Current password for verification."), + }), + annotations: { + title: "Update User", + destructiveHint: true, + idempotentHint: false, + openWorldHint: true, + }, + handler: async (input) => { + const response = await apiClient.post("/user.update", input); + + return ResponseFormatter.success( + "User updated successfully", + response.data, + ); + }, +}); 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 From 1c2ce1d62e03550157387cf23fa699cbf131f3f0 Mon Sep 17 00:00:00 2001 From: limehawk <128890849+limehawk@users.noreply.github.com> Date: Wed, 11 Mar 2026 00:29:53 -0400 Subject: [PATCH 02/10] Add design spec for single api tool consolidation Replaces 312 individual MCP tools with two: dokploy-api (executes any operation) and dokploy-api-schema (discovers operations and params from OpenAPI spec). --- .../2026-03-11-single-api-tool-design.md | 134 ++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 docs/superpowers/specs/2026-03-11-single-api-tool-design.md diff --git a/docs/superpowers/specs/2026-03-11-single-api-tool-design.md b/docs/superpowers/specs/2026-03-11-single-api-tool-design.md new file mode 100644 index 0000000..667d119 --- /dev/null +++ b/docs/superpowers/specs/2026-03-11-single-api-tool-design.md @@ -0,0 +1,134 @@ +# Single API Tool Design + +Replace 312 individual MCP tools with two: `dokploy-api` and `dokploy-api-schema`. + +## Context + +The Dokploy MCP server currently registers 312 tools across 33 categories. Every tool is a thin wrapper around a single HTTP call to the Dokploy API using RPC-style endpoints (`/resource.action`). Only GET and POST methods are used. This creates massive tool-list bloat for consuming LLMs with no functional benefit — every tool does the same thing: validate input, call endpoint, format response. + +## Architecture + +### Tool 1: `dokploy-api` + +Executes any Dokploy API operation. + +**Parameters:** +- `method` (required): `"GET"` or `"POST"` +- `operation` (required): The API operation path, e.g. `"application.create"`, `"server.one"` +- `params` (optional): Object — forwarded as JSON body (POST) or query string (GET) + +**Behavior:** +- POST: `apiClient.post(/${operation}, params)` +- GET: `apiClient.get(/${operation}, { params })` +- Error handling preserved from current `toolFactory.ts` (401/403/404/422/500 mapping) +- Response formatted via `ResponseFormatter.success/error` + +**Tool description** includes a categorized list of all available operation names (no param details) so the LLM knows what's available without a round-trip. + +**Annotations:** +- `readOnlyHint: false` (tool can mutate) +- `openWorldHint: true` (connects to external API) + +### Tool 2: `dokploy-api-schema` + +Returns parameter details for API operations, sourced from the Dokploy OpenAPI spec. + +**Parameters:** +- `category` (optional): Filter by category, e.g. `"application"`, `"server"` +- `operation` (optional): Get details for a specific operation, e.g. `"application.create"` +- If neither provided, returns a summary of all categories with operation counts + +**Behavior:** +- On first call, fetches OpenAPI spec from `GET /settings.getOpenApiDocument` and caches in module-level variable +- Parses the spec to extract operation details: parameters, types, required fields, descriptions +- Maps OpenAPI paths to operation names (strip leading `/`, e.g. `/application.create` -> `application.create`) +- Returns filtered results based on input + +**Annotations:** +- `readOnlyHint: true` +- `idempotentHint: true` + +### Operation Name List (for `dokploy-api` tool description) + +Grouped by category, derived from current tool inventory: + +``` +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 +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 +gitProvider: getAll, remove +github: githubProviders, getGithubBranches, getGithubRepositories +gitlab: getGitlabBranches, getGitlabRepositories +gitea: getGiteaBranches, getGiteaRepositories, getGiteaUrl +bitbucket: getBitbucketBranches, getBitbucketRepositories +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 +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 +``` + +## Files Changed + +### Deleted +- `src/mcp/tools/toolFactory.ts` +- All 33 category directories under `src/mcp/tools/` (admin/, ai/, application/, backup/, certificates/, cluster/, compose/, database/, deployment/, destination/, docker/, domain/, environment/, git/, mounts/, mysql/, notification/, organization/, port/, postgres/, previewDeployment/, project/, redirects/, registry/, rollback/, schedule/, security/, server/, settings/, sshKey/, stripe/, swarm/, user/) + +### Created +- `src/mcp/tools/api.ts` — the `dokploy-api` tool +- `src/mcp/tools/apiSchema.ts` — the `dokploy-api-schema` tool + +### Modified +- `src/mcp/tools/index.ts` — exports only the two new tools +- `src/server.ts` — registers two tools with proper annotations support + +### Unchanged +- `src/utils/apiClient.ts` +- `src/utils/responseFormatter.ts` +- `src/utils/logger.ts` +- `src/utils/clientConfig.ts` +- `src/index.ts` +- `src/http-server.ts` + +## Error Handling + +Preserved from `toolFactory.ts`: +- Input validation: operation must be a non-empty string, method must be GET or POST +- 401/Unauthorized -> auth error message +- 403 -> permissions error +- 404 -> resource not found +- 422 -> validation error (forward API error details) +- 500 -> server error +- Network errors -> connectivity message +- All errors formatted via `ResponseFormatter.error()` + +## Edge Cases + +- **No params**: Both GET and POST work fine with no params (empty body / no query string) +- **OpenAPI spec unavailable**: `dokploy-api-schema` returns an error message if the spec fetch fails, does not break `dokploy-api` +- **Unknown operation**: The Dokploy API will return 404, which gets handled by standard error flow +- **Large spec**: Cached after first fetch, no repeated network calls From dc76d401b19df6c5ee74391876272307a167916d Mon Sep 17 00:00:00 2001 From: limehawk <128890849+limehawk@users.noreply.github.com> Date: Wed, 11 Mar 2026 00:33:10 -0400 Subject: [PATCH 03/10] Fix spec: add missing db types, git ops, clarify error handling Addresses review findings: adds mongo/mariadb/redis categories, missing gitProvider operations, specifies api-schema output format, corrects error handling as new vs preserved, documents polymorphic endpoint flattening. --- .../2026-03-11-single-api-tool-design.md | 70 ++++++++++++++----- 1 file changed, 51 insertions(+), 19 deletions(-) diff --git a/docs/superpowers/specs/2026-03-11-single-api-tool-design.md b/docs/superpowers/specs/2026-03-11-single-api-tool-design.md index 667d119..753ce87 100644 --- a/docs/superpowers/specs/2026-03-11-single-api-tool-design.md +++ b/docs/superpowers/specs/2026-03-11-single-api-tool-design.md @@ -1,10 +1,10 @@ # Single API Tool Design -Replace 312 individual MCP tools with two: `dokploy-api` and `dokploy-api-schema`. +Replace 313 individual MCP tools with two: `dokploy-api` and `dokploy-api-schema`. ## Context -The Dokploy MCP server currently registers 312 tools across 33 categories. Every tool is a thin wrapper around a single HTTP call to the Dokploy API using RPC-style endpoints (`/resource.action`). Only GET and POST methods are used. This creates massive tool-list bloat for consuming LLMs with no functional benefit — every tool does the same thing: validate input, call endpoint, format response. +The Dokploy MCP server currently registers 313 tools across 33 categories. Every tool is a thin wrapper around a single HTTP call to the Dokploy API using RPC-style endpoints (`/resource.action`). Only GET and POST methods are used. This creates massive tool-list bloat — consuming ~70k tokens on launch (see PR #17) — with no functional benefit, since every tool does the same thing: validate input, call endpoint, format response. ## Architecture @@ -18,12 +18,11 @@ Executes any Dokploy API operation. - `params` (optional): Object — forwarded as JSON body (POST) or query string (GET) **Behavior:** -- POST: `apiClient.post(/${operation}, params)` -- GET: `apiClient.get(/${operation}, { params })` -- Error handling preserved from current `toolFactory.ts` (401/403/404/422/500 mapping) +- POST: `apiClient.post(`/${operation}`, params)` +- GET: `apiClient.get(`/${operation}`, { params })` — uses axios params serialization (differs from old tools which manually built query strings, but functionally equivalent for flat key-value params) - Response formatted via `ResponseFormatter.success/error` -**Tool description** includes a categorized list of all available operation names (no param details) so the LLM knows what's available without a round-trip. +**Tool description** includes a categorized list of all available operation names (no param details) so the LLM knows what's available without a round-trip. Format is one line per category: `category: op1, op2, op3`. Use `dokploy-api-schema` to discover parameters for any operation. **Annotations:** - `readOnlyHint: false` (tool can mutate) @@ -44,19 +43,47 @@ Returns parameter details for API operations, sourced from the Dokploy OpenAPI s - Maps OpenAPI paths to operation names (strip leading `/`, e.g. `/application.create` -> `application.create`) - Returns filtered results based on input +**Example output for `operation: "application.create"`:** +```json +{ + "operation": "application.create", + "method": "POST", + "description": "Create a new application", + "parameters": { + "name": { "type": "string", "required": true, "description": "The name of the application" }, + "appName": { "type": "string", "required": false }, + "environmentId": { "type": "string", "required": true, "description": "The environment ID" }, + "serverId": { "type": "string", "required": false, "nullable": true } + } +} +``` + +**Example output for `category: "application"` (abbreviated):** +```json +{ + "category": "application", + "operations": [ + { "name": "application.create", "method": "POST", "description": "..." }, + { "name": "application.one", "method": "GET", "description": "..." }, + ... + ] +} +``` + **Annotations:** - `readOnlyHint: true` - `idempotentHint: true` ### Operation Name List (for `dokploy-api` tool description) -Grouped by category, derived from current tool inventory: +Grouped by category, derived from actual Dokploy API endpoints: ``` 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 @@ -65,11 +92,12 @@ 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 -gitProvider: getAll, remove -github: githubProviders, getGithubBranches, getGithubRepositories -gitlab: getGitlabBranches, getGitlabRepositories gitea: getGiteaBranches, getGiteaRepositories, getGiteaUrl -bitbucket: getBitbucketBranches, getBitbucketRepositories +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 @@ -79,6 +107,7 @@ postgres: changeStatus, create, deploy, move, one, rebuild, reload, remove, save 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 @@ -92,6 +121,8 @@ user: all, assignPermissions, checkUserOrganizations, createApiKey, deleteApiKey volumeBackups: create, delete, list, one, runManually, update ``` +Note: The old `database/` tools used polymorphic dispatch (accepting a `dbType` param to route to `postgres.*`, `mysql.*`, `mongo.*`, `mariadb.*`, `redis.*`). With the single-tool design, callers use the actual API endpoint directly (e.g., `mongo.create` instead of `database.create` with `dbType: "mongo"`). Same for git provider tools. + ## Files Changed ### Deleted @@ -104,7 +135,7 @@ volumeBackups: create, delete, list, one, runManually, update ### Modified - `src/mcp/tools/index.ts` — exports only the two new tools -- `src/server.ts` — registers two tools with proper annotations support +- `src/server.ts` — registers two tools with annotations support (new — old code used 4-arg overload without annotations) ### Unchanged - `src/utils/apiClient.ts` @@ -116,13 +147,13 @@ volumeBackups: create, delete, list, one, runManually, update ## Error Handling -Preserved from `toolFactory.ts`: +Carried forward from `toolFactory.ts` with additions: - Input validation: operation must be a non-empty string, method must be GET or POST -- 401/Unauthorized -> auth error message -- 403 -> permissions error -- 404 -> resource not found -- 422 -> validation error (forward API error details) -- 500 -> server error +- 401/Unauthorized -> auth error message (existing) +- 404 -> resource not found (existing) +- 500 -> server error (existing) +- 403 -> permissions error (new — was only logged in apiClient interceptor) +- 422 -> validation error, forward API error details (new — was only logged in apiClient interceptor) - Network errors -> connectivity message - All errors formatted via `ResponseFormatter.error()` @@ -131,4 +162,5 @@ Preserved from `toolFactory.ts`: - **No params**: Both GET and POST work fine with no params (empty body / no query string) - **OpenAPI spec unavailable**: `dokploy-api-schema` returns an error message if the spec fetch fails, does not break `dokploy-api` - **Unknown operation**: The Dokploy API will return 404, which gets handled by standard error flow -- **Large spec**: Cached after first fetch, no repeated network calls +- **Large spec**: Cached after first fetch, no repeated network calls. For long-running servers, cache persists for the process lifetime (acceptable — spec changes require server restart anyway) +- **Polymorphic endpoints**: Old `database.*` and `git.*` tools that dispatched based on a type parameter are replaced by direct endpoint calls (e.g., `mongo.create`, `github.getGithubBranches`) From de8498f1d4a05fb8d7a80b21193f4f8884df0af2 Mon Sep 17 00:00:00 2001 From: limehawk <128890849+limehawk@users.noreply.github.com> Date: Wed, 11 Mar 2026 00:38:31 -0400 Subject: [PATCH 04/10] Add implementation plan for single api tool 6 tasks: create api.ts, apiSchema.ts, rewire server, delete old tools, build/verify, update docs. Fixes from review: proper AxiosError status checking, recursive $ref/allOf schema resolution, depth guard against circular refs. --- .../plans/2026-03-11-single-api-tool.md | 582 ++++++++++++++++++ 1 file changed, 582 insertions(+) create mode 100644 docs/superpowers/plans/2026-03-11-single-api-tool.md diff --git a/docs/superpowers/plans/2026-03-11-single-api-tool.md b/docs/superpowers/plans/2026-03-11-single-api-tool.md new file mode 100644 index 0000000..761e960 --- /dev/null +++ b/docs/superpowers/plans/2026-03-11-single-api-tool.md @@ -0,0 +1,582 @@ +# Single API Tool Implementation Plan + +> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Replace 313 individual MCP tools with two (`dokploy-api` and `dokploy-api-schema`) to reduce launch token usage from ~70k to under 2k. + +**Architecture:** A single `dokploy-api` tool forwards any operation to the Dokploy API as a generic HTTP call. A companion `dokploy-api-schema` tool lazily fetches and caches the OpenAPI spec to provide parameter details on demand. All 33 category directories are deleted. + +**Tech Stack:** TypeScript, Zod, axios, @modelcontextprotocol/sdk 1.12.0 + +**Spec:** `docs/superpowers/specs/2026-03-11-single-api-tool-design.md` + +--- + +## File Structure + +| File | Action | Responsibility | +|------|--------|---------------| +| `src/mcp/tools/api.ts` | Create | `dokploy-api` tool — generic HTTP dispatch | +| `src/mcp/tools/apiSchema.ts` | Create | `dokploy-api-schema` tool — OpenAPI spec lookup | +| `src/mcp/tools/index.ts` | Rewrite | Export only the two new tools | +| `src/server.ts` | Rewrite | Register tools with annotations support | +| `src/mcp/tools/toolFactory.ts` | Delete | No longer needed | +| `src/mcp/tools/{33 dirs}/*` | Delete | All individual tool files | + +Unchanged: `src/utils/apiClient.ts`, `src/utils/responseFormatter.ts`, `src/utils/logger.ts`, `src/utils/clientConfig.ts`, `src/index.ts`, `src/http-server.ts` + +--- + +## Task 1: Create `dokploy-api` tool + +**Files:** +- Create: `src/mcp/tools/api.ts` + +- [ ] **Step 1: Create `src/mcp/tools/api.ts`** + +```typescript +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"; + +const logger = createLogger("DokployApi"); + +const OPERATIONS_LIST = `Available operations (use dokploy-api-schema for 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`; + +export const schema = { + method: z.enum(["GET", "POST"]).describe("HTTP method"), + 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. POST: sent as JSON body. GET: sent as query string." + ), +}; + +export const name = "dokploy-api"; + +export const description = `Execute any Dokploy API operation. Use dokploy-api-schema to discover parameters for an operation. + +${OPERATIONS_LIST}`; + +export const annotations = { + title: "Dokploy API", + readOnlyHint: false, + openWorldHint: true, +}; + +export async function handler(input: { + method: "GET" | "POST"; + operation: string; + params?: Record; +}) { + const { method, operation, params } = input; + 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 === 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: ${method} ${operation}`, + `Error: ${error instanceof Error ? error.message : "Unknown error"}` + ); + } +} +``` + +- [ ] **Step 2: Verify the file compiles** + +Run: `cd /home/limehawk/dev/dokploy-mcp && bunx tsc --noEmit src/mcp/tools/api.ts 2>&1 | head -20` + +Note: This will fail until index.ts is updated. That's fine — we're just checking for syntax errors in this file. If import errors are the only errors, the file is correct. + +- [ ] **Step 3: Commit** + +```bash +git add src/mcp/tools/api.ts +git commit -m "feat: add dokploy-api generic tool" +``` + +--- + +## Task 2: Create `dokploy-api-schema` tool + +**Files:** +- Create: `src/mcp/tools/apiSchema.ts` + +- [ ] **Step 1: Create `src/mcp/tools/apiSchema.ts`** + +```typescript +import { z } from "zod"; +import apiClient from "../../utils/apiClient.js"; +import { createLogger } from "../../utils/logger.js"; +import { ResponseFormatter } from "../../utils/responseFormatter.js"; + +const logger = createLogger("DokployApiSchema"); + +let cachedSpec: Record | null = null; + +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!; +} + +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"}` + ); + } +} +``` + +- [ ] **Step 2: Commit** + +```bash +git add src/mcp/tools/apiSchema.ts +git commit -m "feat: add dokploy-api-schema tool for operation discovery" +``` + +--- + +## Task 3: Rewrite `index.ts` and `server.ts` + +**Files:** +- Rewrite: `src/mcp/tools/index.ts` +- Rewrite: `src/server.ts` + +- [ ] **Step 1: Rewrite `src/mcp/tools/index.ts`** + +```typescript +export * as api from "./api.js"; +export * as apiSchema from "./apiSchema.js"; +``` + +- [ ] **Step 2: Rewrite `src/server.ts`** + +```typescript +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import * as api from "./mcp/tools/api.js"; +import * as apiSchema from "./mcp/tools/apiSchema.js"; + +export function createServer() { + const server = new McpServer({ + name: "dokploy", + version: "1.0.0", + }); + + 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; +} +``` + +- [ ] **Step 3: Commit** + +```bash +git add src/mcp/tools/index.ts src/server.ts +git commit -m "feat: wire up two-tool registration in server" +``` + +--- + +## Task 4: Delete old tool files + +**Files:** +- Delete: `src/mcp/tools/toolFactory.ts` +- Delete: all 33 category directories under `src/mcp/tools/` + +- [ ] **Step 1: Delete `toolFactory.ts`** + +```bash +cd /home/limehawk/dev/dokploy-mcp +gio trash src/mcp/tools/toolFactory.ts +``` + +- [ ] **Step 2: Delete all 33 category directories** + +```bash +cd /home/limehawk/dev/dokploy-mcp/src/mcp/tools +gio trash admin ai application backup certificates cluster compose database deployment destination docker domain environment git mounts mysql notification organization port postgres previewDeployment project redirects registry rollback schedule security server settings sshKey stripe swarm user +``` + +- [ ] **Step 3: Commit** + +```bash +git add -A src/mcp/tools/ +git commit -m "chore: remove 313 individual tool files + +Replaced by dokploy-api and dokploy-api-schema tools." +``` + +--- + +## Task 5: Build and verify + +- [ ] **Step 1: Type-check** + +Run: `cd /home/limehawk/dev/dokploy-mcp && bun run type-check` +Expected: 0 errors + +- [ ] **Step 2: Build** + +Run: `cd /home/limehawk/dev/dokploy-mcp && bun run build` +Expected: Clean build, `build/` directory populated + +- [ ] **Step 3: Verify tool registration by starting stdio mode briefly** + +Run: `cd /home/limehawk/dev/dokploy-mcp && echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | timeout 5 node build/index.js 2>/dev/null || true` +Expected: JSON response showing exactly 2 tools: `dokploy-api` and `dokploy-api-schema` + +- [ ] **Step 4: Commit build (if build output is tracked)** + +Check if `build/` is in `.gitignore`. If not tracked, skip this step. + +--- + +## Task 6: Update documentation + +**Files:** +- Modify: `TOOLS.md` (if it exists and documents individual tools) + +- [ ] **Step 1: Replace TOOLS.md content** + +Replace the entire file with documentation for the two new tools: usage examples, parameter descriptions, and the operation list. Keep it concise — the tool descriptions themselves are comprehensive. + +- [ ] **Step 2: Commit** + +```bash +git add TOOLS.md +git commit -m "docs: update TOOLS.md for single api tool architecture" +``` From 9a8562e5a6f15ac8d052b54241f4f497988c59a8 Mon Sep 17 00:00:00 2001 From: limehawk <128890849+limehawk@users.noreply.github.com> Date: Wed, 11 Mar 2026 00:42:13 -0400 Subject: [PATCH 05/10] Drop method param from dokploy-api tool Auto-detect GET vs POST via static set of known GET operations. LLM only needs to provide operation and params. --- .../plans/2026-03-11-single-api-tool.md | 143 +++++++++++++++++- .../2026-03-11-single-api-tool-design.md | 2 +- 2 files changed, 138 insertions(+), 7 deletions(-) diff --git a/docs/superpowers/plans/2026-03-11-single-api-tool.md b/docs/superpowers/plans/2026-03-11-single-api-tool.md index 761e960..eb94215 100644 --- a/docs/superpowers/plans/2026-03-11-single-api-tool.md +++ b/docs/superpowers/plans/2026-03-11-single-api-tool.md @@ -85,8 +85,139 @@ 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`; +// Operations that use GET. Everything else uses POST. +const GET_OPERATIONS = new Set([ + "ai.get", + "ai.getAll", + "ai.getModels", + "ai.one", + "application.one", + "application.readAppMonitoring", + "application.readTraefikConfig", + "backup.listBackupFiles", + "backup.one", + "bitbucket.getBitbucketBranches", + "bitbucket.getBitbucketRepositories", + "certificates.all", + "certificates.one", + "cluster.addManager", + "cluster.addWorker", + "cluster.getNodes", + "compose.getConvertedCompose", + "compose.getDefaultCommand", + "compose.getTags", + "compose.loadMountsByService", + "compose.loadServices", + "compose.one", + "compose.templates", + "deployment.all", + "deployment.allByCompose", + "deployment.allByServer", + "deployment.allByType", + "destination.all", + "destination.one", + "docker.getConfig", + "docker.getContainers", + "docker.getContainersByAppLabel", + "docker.getContainersByAppNameMatch", + "docker.getServiceContainersByAppName", + "docker.getStackContainersByAppName", + "domain.byApplicationId", + "domain.byComposeId", + "domain.canGenerateTraefikMeDomains", + "domain.one", + "environment.byProjectId", + "environment.one", + "gitea.getGiteaBranches", + "gitea.getGiteaRepositories", + "gitea.getGiteaUrl", + "github.getGithubBranches", + "github.getGithubRepositories", + "github.githubProviders", + "gitlab.getGitlabBranches", + "gitlab.getGitlabRepositories", + "gitProvider.getAll", + "gitProvider.one", + "mariadb.one", + "mongo.one", + "mounts.one", + "mysql.one", + "notification.all", + "notification.getEmailProviders", + "notification.one", + "organization.all", + "organization.allInvitations", + "organization.one", + "port.one", + "postgres.one", + "previewDeployment.all", + "previewDeployment.one", + "project.all", + "project.one", + "redirects.one", + "redis.one", + "registry.all", + "registry.one", + "schedule.list", + "schedule.one", + "security.one", + "server.all", + "server.buildServers", + "server.count", + "server.getDefaultCommand", + "server.getServerMetrics", + "server.getServerTime", + "server.one", + "server.publicIp", + "server.security", + "server.validate", + "server.withSSHKey", + "settings.checkGPUStatus", + "settings.getDokployCloudIps", + "settings.getDokployVersion", + "settings.getIp", + "settings.getLogCleanupStatus", + "settings.getOpenApiDocument", + "settings.getReleaseTag", + "settings.getTraefikPorts", + "settings.haveActivateRequests", + "settings.haveTraefikDashboardPortEnabled", + "settings.health", + "settings.isCloud", + "settings.isUserSubscribed", + "settings.readDirectories", + "settings.readMiddlewareTraefikConfig", + "settings.readTraefikConfig", + "settings.readTraefikEnv", + "settings.readTraefikFile", + "settings.readWebServerTraefikConfig", + "sshKey.all", + "sshKey.one", + "stripe.canCreateMoreServers", + "stripe.getProducts", + "swarm.getNodeApps", + "swarm.getNodeInfo", + "swarm.getNodes", + "user.all", + "user.checkUserOrganizations", + "user.get", + "user.getBackups", + "user.getUserByToken", + "user.getContainerMetrics", + "user.getInvitations", + "user.getMetricsToken", + "user.getServerMetrics", + "user.haveRootAccess", + "user.one", + "volumeBackups.list", + "volumeBackups.one", +]); + +function getMethod(operation: string): "GET" | "POST" { + return GET_OPERATIONS.has(operation) ? "GET" : "POST"; +} + export const schema = { - method: z.enum(["GET", "POST"]).describe("HTTP method"), operation: z .string() .min(1) @@ -97,13 +228,13 @@ export const schema = { .record(z.any()) .optional() .describe( - "Parameters object. POST: sent as JSON body. GET: sent as query string." + "Parameters object. Sent as JSON body for mutations, query string for reads." ), }; export const name = "dokploy-api"; -export const description = `Execute any Dokploy API operation. Use dokploy-api-schema to discover parameters for an operation. +export const description = `Execute any Dokploy API operation. HTTP method is auto-detected. Use dokploy-api-schema to discover parameters for an operation. ${OPERATIONS_LIST}`; @@ -114,11 +245,11 @@ export const annotations = { }; export async function handler(input: { - method: "GET" | "POST"; operation: string; params?: Record; }) { - const { method, operation, params } = input; + const { operation, params } = input; + const method = getMethod(operation); const endpoint = `/${operation}`; logger.info(`Executing ${method} ${endpoint}`, { hasParams: !!params }); @@ -177,7 +308,7 @@ export async function handler(input: { } return ResponseFormatter.error( - `Failed: ${method} ${operation}`, + `Failed: ${operation}`, `Error: ${error instanceof Error ? error.message : "Unknown error"}` ); } diff --git a/docs/superpowers/specs/2026-03-11-single-api-tool-design.md b/docs/superpowers/specs/2026-03-11-single-api-tool-design.md index 753ce87..b07a046 100644 --- a/docs/superpowers/specs/2026-03-11-single-api-tool-design.md +++ b/docs/superpowers/specs/2026-03-11-single-api-tool-design.md @@ -13,11 +13,11 @@ The Dokploy MCP server currently registers 313 tools across 33 categories. Every Executes any Dokploy API operation. **Parameters:** -- `method` (required): `"GET"` or `"POST"` - `operation` (required): The API operation path, e.g. `"application.create"`, `"server.one"` - `params` (optional): Object — forwarded as JSON body (POST) or query string (GET) **Behavior:** +- HTTP method is auto-detected: a static set of known GET operations is hardcoded (derived from inventory). Everything else defaults to POST. - POST: `apiClient.post(`/${operation}`, params)` - GET: `apiClient.get(`/${operation}`, { params })` — uses axios params serialization (differs from old tools which manually built query strings, but functionally equivalent for flat key-value params) - Response formatted via `ResponseFormatter.success/error` From 25abf2a89ea4289ec091c30ed6435bf5ab6601db Mon Sep 17 00:00:00 2001 From: limehawk <128890849+limehawk@users.noreply.github.com> Date: Wed, 11 Mar 2026 00:47:30 -0400 Subject: [PATCH 06/10] Replace 313 individual tools with generic dokploy-api + dokploy-api-schema - dokploy-api: executes any Dokploy API operation with auto-detected HTTP method (GET/POST) via static operations set - dokploy-api-schema: discovers operation parameters from OpenAPI spec with lazy fetch and in-memory caching - Removes all 33 category directories and toolFactory.ts - Adds index signature to FormattedResponse for MCP SDK compatibility - Reduces tool registration from ~70k tokens to ~2k on launch --- src/mcp/tools/admin/adminSetupMonitoring.ts | 85 ---- src/mcp/tools/admin/index.ts | 1 - src/mcp/tools/ai/aiCreate.ts | 37 -- src/mcp/tools/ai/aiDelete.ts | 29 -- src/mcp/tools/ai/aiDeploy.ts | 82 ---- src/mcp/tools/ai/aiGet.ts | 33 -- src/mcp/tools/ai/aiGetAll.ts | 24 - src/mcp/tools/ai/aiGetModels.ts | 31 -- src/mcp/tools/ai/aiOne.ts | 33 -- src/mcp/tools/ai/aiSuggest.ts | 41 -- src/mcp/tools/ai/aiUpdate.ts | 59 --- src/mcp/tools/ai/index.ts | 9 - src/mcp/tools/api.ts | 278 +++++++++++ src/mcp/tools/apiSchema.ts | 238 +++++++++ .../applicationCancelDeployment.ts | 31 -- .../application/applicationCleanQueues.ts | 28 -- .../tools/application/applicationCreate.ts | 46 -- .../tools/application/applicationDelete.ts | 26 - .../tools/application/applicationDeploy.ts | 34 -- .../applicationDisconnectGitProvider.ts | 32 -- .../application/applicationMarkRunning.ts | 28 -- src/mcp/tools/application/applicationMove.ts | 29 -- src/mcp/tools/application/applicationOne.ts | 37 -- .../applicationReadAppMonitoring.ts | 37 -- .../applicationReadTraefikConfig.ts | 37 -- .../tools/application/applicationRedeploy.ts | 37 -- .../application/applicationRefreshToken.ts | 28 -- .../tools/application/applicationReload.ts | 27 -- .../applicationSaveBitbucketProvider.ts | 58 --- .../application/applicationSaveBuildType.ts | 71 --- .../applicationSaveDockerProvider.ts | 52 -- .../application/applicationSaveEnvironment.ts | 41 -- .../application/applicationSaveGitProvider.ts | 58 --- .../applicationSaveGiteaProvider.ts | 52 -- .../applicationSaveGithubProvider.ts | 60 --- .../applicationSaveGitlabProvider.ts | 57 --- src/mcp/tools/application/applicationStart.ts | 26 - src/mcp/tools/application/applicationStop.ts | 26 - .../tools/application/applicationUpdate.ts | 457 ------------------ .../applicationUpdateTraefikConfig.ts | 34 -- src/mcp/tools/application/index.ts | 26 - src/mcp/tools/backup/backupCreate.ts | 99 ---- src/mcp/tools/backup/backupListFiles.ts | 48 -- src/mcp/tools/backup/backupOne.ts | 38 -- src/mcp/tools/backup/backupRemove.ts | 29 -- src/mcp/tools/backup/backupRunManual.ts | 64 --- src/mcp/tools/backup/backupUpdate.ts | 67 --- src/mcp/tools/backup/index.ts | 15 - src/mcp/tools/backup/volumeBackupCreate.ts | 115 ----- src/mcp/tools/backup/volumeBackupList.ts | 50 -- src/mcp/tools/backup/volumeBackupOne.ts | 39 -- src/mcp/tools/backup/volumeBackupRemove.ts | 30 -- src/mcp/tools/backup/volumeBackupRunManual.ts | 30 -- src/mcp/tools/backup/volumeBackupUpdate.ts | 119 ----- src/mcp/tools/certificates/certificatesAll.ts | 31 -- .../tools/certificates/certificatesCreate.ts | 60 --- src/mcp/tools/certificates/certificatesOne.ts | 38 -- .../tools/certificates/certificatesRemove.ts | 29 -- src/mcp/tools/certificates/index.ts | 4 - src/mcp/tools/cluster/clusterAddManager.ts | 38 -- src/mcp/tools/cluster/clusterAddWorker.ts | 38 -- src/mcp/tools/cluster/clusterGetNodes.ts | 37 -- src/mcp/tools/cluster/clusterRemoveWorker.ts | 36 -- src/mcp/tools/cluster/index.ts | 4 - .../tools/compose/composeCancelDeployment.ts | 30 -- src/mcp/tools/compose/composeCleanQueues.ts | 29 -- src/mcp/tools/compose/composeCreate.ts | 53 -- src/mcp/tools/compose/composeDelete.ts | 32 -- src/mcp/tools/compose/composeDeploy.ts | 34 -- .../tools/compose/composeDeployTemplate.ts | 37 -- .../compose/composeDisconnectGitProvider.ts | 34 -- .../tools/compose/composeFetchSourceType.ts | 30 -- .../compose/composeGetConvertedCompose.ts | 36 -- .../tools/compose/composeGetDefaultCommand.ts | 35 -- src/mcp/tools/compose/composeGetTags.ts | 46 -- src/mcp/tools/compose/composeImport.ts | 29 -- .../compose/composeIsolatedDeployment.ts | 33 -- src/mcp/tools/compose/composeKillBuild.ts | 29 -- .../compose/composeLoadMountsByService.ts | 44 -- src/mcp/tools/compose/composeLoadServices.ts | 51 -- src/mcp/tools/compose/composeMove.ts | 31 -- src/mcp/tools/compose/composeOne.ts | 38 -- .../tools/compose/composeProcessTemplate.ts | 27 -- .../tools/compose/composeRandomizeCompose.ts | 34 -- src/mcp/tools/compose/composeRedeploy.ts | 37 -- src/mcp/tools/compose/composeRefreshToken.ts | 29 -- src/mcp/tools/compose/composeStart.ts | 29 -- src/mcp/tools/compose/composeStop.ts | 29 -- src/mcp/tools/compose/composeTemplates.ts | 46 -- src/mcp/tools/compose/composeUpdate.ts | 182 ------- src/mcp/tools/compose/index.ts | 26 - .../tools/database/databaseChangeStatus.ts | 62 --- src/mcp/tools/database/databaseCreate.ts | 146 ------ src/mcp/tools/database/databaseDeploy.ts | 56 --- src/mcp/tools/database/databaseMove.ts | 61 --- src/mcp/tools/database/databaseOne.ts | 63 --- src/mcp/tools/database/databaseRebuild.ts | 56 --- src/mcp/tools/database/databaseReload.ts | 61 --- src/mcp/tools/database/databaseRemove.ts | 56 --- .../tools/database/databaseSaveEnvironment.ts | 62 --- .../database/databaseSaveExternalPort.ts | 66 --- src/mcp/tools/database/databaseStart.ts | 56 --- src/mcp/tools/database/databaseStop.ts | 56 --- src/mcp/tools/database/databaseUpdate.ts | 391 --------------- src/mcp/tools/database/index.ts | 13 - src/mcp/tools/deployment/deploymentAll.ts | 31 -- .../deployment/deploymentAllByCompose.ts | 31 -- .../tools/deployment/deploymentAllByServer.ts | 31 -- .../tools/deployment/deploymentAllByType.ts | 42 -- .../tools/deployment/deploymentKillProcess.ts | 29 -- src/mcp/tools/deployment/index.ts | 5 - src/mcp/tools/destination/destinationAll.ts | 24 - .../tools/destination/destinationCreate.ts | 57 --- src/mcp/tools/destination/destinationOne.ts | 37 -- .../tools/destination/destinationRemove.ts | 28 -- .../destination/destinationTestConnection.ts | 56 --- .../tools/destination/destinationUpdate.ts | 59 --- src/mcp/tools/destination/index.ts | 6 - src/mcp/tools/docker/dockerGetConfig.ts | 42 -- src/mcp/tools/docker/dockerGetContainers.ts | 37 -- .../docker/dockerGetContainersByAppLabel.ts | 46 -- .../dockerGetContainersByAppNameMatch.ts | 50 -- .../dockerGetServiceContainersByAppName.ts | 42 -- .../dockerGetStackContainersByAppName.ts | 42 -- .../tools/docker/dockerRestartContainer.ts | 33 -- src/mcp/tools/docker/index.ts | 7 - src/mcp/tools/domain/domainByApplicationId.ts | 34 -- src/mcp/tools/domain/domainByComposeId.ts | 34 -- .../domainCanGenerateTraefikMeDomains.ts | 39 -- src/mcp/tools/domain/domainCreate.ts | 107 ---- src/mcp/tools/domain/domainDelete.ts | 28 -- src/mcp/tools/domain/domainGenerateDomain.ts | 34 -- src/mcp/tools/domain/domainOne.ts | 29 -- src/mcp/tools/domain/domainUpdate.ts | 87 ---- src/mcp/tools/domain/domainValidateDomain.ts | 34 -- src/mcp/tools/domain/index.ts | 9 - .../environment/environmentByProjectId.ts | 30 -- .../tools/environment/environmentCreate.ts | 37 -- .../tools/environment/environmentDuplicate.ts | 38 -- src/mcp/tools/environment/environmentOne.ts | 38 -- .../tools/environment/environmentRemove.ts | 29 -- .../tools/environment/environmentUpdate.ts | 51 -- src/mcp/tools/environment/index.ts | 6 - src/mcp/tools/git/gitBranches.ts | 94 ---- src/mcp/tools/git/gitProviderCreate.ts | 263 ---------- src/mcp/tools/git/gitProviderGetAll.ts | 32 -- src/mcp/tools/git/gitProviderGetUrl.ts | 41 -- src/mcp/tools/git/gitProviderOne.ts | 72 --- src/mcp/tools/git/gitProviderRemove.ts | 34 -- src/mcp/tools/git/gitProviderUpdate.ts | 321 ------------ src/mcp/tools/git/gitProviders.ts | 59 --- src/mcp/tools/git/gitRepositories.ts | 72 --- src/mcp/tools/git/gitTestConnection.ts | 138 ------ src/mcp/tools/git/index.ts | 10 - src/mcp/tools/index.ts | 71 +-- src/mcp/tools/mounts/index.ts | 5 - .../tools/mounts/mountsAllByApplicationId.ts | 80 --- src/mcp/tools/mounts/mountsCreate.ts | 106 ---- src/mcp/tools/mounts/mountsOne.ts | 39 -- src/mcp/tools/mounts/mountsRemove.ts | 30 -- src/mcp/tools/mounts/mountsUpdate.ts | 102 ---- src/mcp/tools/mysql/index.ts | 13 - src/mcp/tools/mysql/mysqlChangeStatus.ts | 29 -- src/mcp/tools/mysql/mysqlCreate.ts | 69 --- src/mcp/tools/mysql/mysqlDeploy.ts | 26 - src/mcp/tools/mysql/mysqlMove.ts | 29 -- src/mcp/tools/mysql/mysqlOne.ts | 28 -- src/mcp/tools/mysql/mysqlRebuild.ts | 26 - src/mcp/tools/mysql/mysqlReload.ts | 27 -- src/mcp/tools/mysql/mysqlRemove.ts | 26 - src/mcp/tools/mysql/mysqlSaveEnvironment.ts | 31 -- src/mcp/tools/mysql/mysqlSaveExternalPort.ts | 31 -- src/mcp/tools/mysql/mysqlStart.ts | 26 - src/mcp/tools/mysql/mysqlStop.ts | 26 - src/mcp/tools/mysql/mysqlUpdate.ts | 228 --------- src/mcp/tools/notification/index.ts | 8 - src/mcp/tools/notification/notificationAll.ts | 24 - .../tools/notification/notificationCreate.ts | 324 ------------- .../notificationGetEmailProviders.ts | 24 - src/mcp/tools/notification/notificationOne.ts | 38 -- .../notificationReceiveNotification.ts | 44 -- .../tools/notification/notificationRemove.ts | 28 -- .../notificationTestConnection.ts | 254 ---------- .../tools/notification/notificationUpdate.ts | 433 ----------------- src/mcp/tools/organization/index.ts | 8 - src/mcp/tools/organization/organizationAll.ts | 24 - .../organizationAllInvitations.ts | 24 - .../tools/organization/organizationCreate.ts | 30 -- .../tools/organization/organizationDelete.ts | 28 -- src/mcp/tools/organization/organizationOne.ts | 37 -- .../organizationRemoveInvitation.ts | 29 -- .../organization/organizationSetDefault.ts | 29 -- .../tools/organization/organizationUpdate.ts | 37 -- src/mcp/tools/port/index.ts | 4 - src/mcp/tools/port/portCreate.ts | 47 -- src/mcp/tools/port/portDelete.ts | 29 -- src/mcp/tools/port/portOne.ts | 36 -- src/mcp/tools/port/portUpdate.ts | 44 -- src/mcp/tools/postgres/index.ts | 13 - .../tools/postgres/postgresChangeStatus.ts | 31 -- src/mcp/tools/postgres/postgresCreate.ts | 59 --- src/mcp/tools/postgres/postgresDeploy.ts | 28 -- src/mcp/tools/postgres/postgresMove.ts | 32 -- src/mcp/tools/postgres/postgresOne.ts | 37 -- src/mcp/tools/postgres/postgresRebuild.ts | 28 -- src/mcp/tools/postgres/postgresReload.ts | 31 -- src/mcp/tools/postgres/postgresRemove.ts | 28 -- .../tools/postgres/postgresSaveEnvironment.ts | 34 -- .../postgres/postgresSaveExternalPort.ts | 33 -- src/mcp/tools/postgres/postgresStart.ts | 28 -- src/mcp/tools/postgres/postgresStop.ts | 28 -- src/mcp/tools/postgres/postgresUpdate.ts | 212 -------- src/mcp/tools/previewDeployment/index.ts | 3 - .../previewDeployment/previewDeploymentAll.ts | 31 -- .../previewDeploymentDelete.ts | 31 -- .../previewDeployment/previewDeploymentOne.ts | 37 -- src/mcp/tools/project/index.ts | 6 - src/mcp/tools/project/projectAll.ts | 168 ------- src/mcp/tools/project/projectCreate.ts | 35 -- src/mcp/tools/project/projectDuplicate.ts | 71 --- src/mcp/tools/project/projectOne.ts | 35 -- src/mcp/tools/project/projectRemove.ts | 26 - src/mcp/tools/project/projectUpdate.ts | 45 -- src/mcp/tools/redirects/index.ts | 4 - src/mcp/tools/redirects/redirectsCreate.ts | 45 -- src/mcp/tools/redirects/redirectsDelete.ts | 29 -- src/mcp/tools/redirects/redirectsOne.ts | 38 -- src/mcp/tools/redirects/redirectsUpdate.ts | 46 -- src/mcp/tools/registry/index.ts | 6 - src/mcp/tools/registry/registryAll.ts | 31 -- src/mcp/tools/registry/registryCreate.ts | 44 -- src/mcp/tools/registry/registryOne.ts | 38 -- src/mcp/tools/registry/registryRemove.ts | 26 - .../tools/registry/registryTestRegistry.ts | 45 -- src/mcp/tools/registry/registryUpdate.ts | 64 --- src/mcp/tools/rollback/index.ts | 2 - src/mcp/tools/rollback/rollbackDelete.ts | 29 -- src/mcp/tools/rollback/rollbackRollback.ts | 32 -- src/mcp/tools/schedule/index.ts | 6 - src/mcp/tools/schedule/scheduleCreate.ts | 71 --- src/mcp/tools/schedule/scheduleDelete.ts | 26 - src/mcp/tools/schedule/scheduleList.ts | 35 -- src/mcp/tools/schedule/scheduleOne.ts | 35 -- src/mcp/tools/schedule/scheduleRunManually.ts | 29 -- src/mcp/tools/schedule/scheduleUpdate.ts | 71 --- src/mcp/tools/security/index.ts | 4 - src/mcp/tools/security/securityCreate.ts | 43 -- src/mcp/tools/security/securityDelete.ts | 32 -- src/mcp/tools/security/securityOne.ts | 41 -- src/mcp/tools/security/securityUpdate.ts | 44 -- src/mcp/tools/server/index.ts | 16 - src/mcp/tools/server/serverAll.ts | 24 - src/mcp/tools/server/serverBuildServers.ts | 24 - src/mcp/tools/server/serverCount.ts | 24 - src/mcp/tools/server/serverCreate.ts | 41 -- .../tools/server/serverGetDefaultCommand.ts | 28 -- .../tools/server/serverGetServerMetrics.ts | 30 -- src/mcp/tools/server/serverGetServerTime.ts | 24 - src/mcp/tools/server/serverOne.ts | 35 -- src/mcp/tools/server/serverPublicIp.ts | 24 - src/mcp/tools/server/serverRemove.ts | 26 - src/mcp/tools/server/serverSecurity.ts | 28 -- src/mcp/tools/server/serverSetup.ts | 26 - src/mcp/tools/server/serverSetupMonitoring.ts | 57 --- src/mcp/tools/server/serverUpdate.ts | 46 -- src/mcp/tools/server/serverValidate.ts | 28 -- src/mcp/tools/server/serverWithSSHKey.ts | 24 - src/mcp/tools/settings/index.ts | 46 -- .../settings/settingsAssignDomainServer.ts | 50 -- .../tools/settings/settingsCheckGPUStatus.ts | 30 -- src/mcp/tools/settings/settingsCleanAll.ts | 32 -- .../settings/settingsCleanDockerBuilder.ts | 34 -- .../settings/settingsCleanDockerPrune.ts | 29 -- .../tools/settings/settingsCleanMonitoring.ts | 24 - src/mcp/tools/settings/settingsCleanRedis.ts | 24 - .../settings/settingsCleanSSHPrivateKey.ts | 24 - .../settingsCleanStoppedContainers.ts | 32 -- .../settings/settingsCleanUnusedImages.ts | 29 -- .../settings/settingsCleanUnusedVolumes.ts | 32 -- .../settings/settingsGetDokployCloudIps.ts | 24 - .../settings/settingsGetDokployVersion.ts | 24 - src/mcp/tools/settings/settingsGetIp.ts | 24 - .../settings/settingsGetLogCleanupStatus.ts | 24 - .../settings/settingsGetOpenApiDocument.ts | 24 - .../tools/settings/settingsGetReleaseTag.ts | 24 - .../tools/settings/settingsGetTraefikPorts.ts | 30 -- .../tools/settings/settingsGetUpdateData.ts | 24 - .../settings/settingsHaveActivateRequests.ts | 24 - ...settingsHaveTraefikDashboardPortEnabled.ts | 32 -- src/mcp/tools/settings/settingsHealth.ts | 24 - src/mcp/tools/settings/settingsIsCloud.ts | 24 - .../settings/settingsIsUserSubscribed.ts | 24 - .../tools/settings/settingsReadDirectories.ts | 32 -- .../settingsReadMiddlewareTraefikConfig.ts | 26 - .../settings/settingsReadTraefikConfig.ts | 24 - .../tools/settings/settingsReadTraefikEnv.ts | 30 -- .../tools/settings/settingsReadTraefikFile.ts | 43 -- .../settingsReadWebServerTraefikConfig.ts | 26 - src/mcp/tools/settings/settingsReloadRedis.ts | 24 - .../tools/settings/settingsReloadServer.ts | 24 - .../tools/settings/settingsReloadTraefik.ts | 29 -- .../settings/settingsSaveSSHPrivateKey.ts | 31 -- src/mcp/tools/settings/settingsSetupGPU.ts | 29 -- .../tools/settings/settingsToggleDashboard.ts | 33 -- .../tools/settings/settingsToggleRequests.ts | 28 -- .../settings/settingsUpdateDockerCleanup.ts | 37 -- .../settings/settingsUpdateLogCleanup.ts | 31 -- .../settingsUpdateMiddlewareTraefikConfig.ts | 34 -- .../tools/settings/settingsUpdateServer.ts | 24 - .../settings/settingsUpdateTraefikConfig.ts | 34 -- .../settings/settingsUpdateTraefikFile.ts | 43 -- .../settings/settingsUpdateTraefikPorts.ts | 47 -- .../settingsUpdateWebServerTraefikConfig.ts | 34 -- .../tools/settings/settingsWriteTraefikEnv.ts | 36 -- src/mcp/tools/sshKey/index.ts | 6 - src/mcp/tools/sshKey/sshKeyAll.ts | 24 - src/mcp/tools/sshKey/sshKeyCreate.ts | 34 -- src/mcp/tools/sshKey/sshKeyGenerate.ts | 29 -- src/mcp/tools/sshKey/sshKeyOne.ts | 35 -- src/mcp/tools/sshKey/sshKeyRemove.ts | 26 - src/mcp/tools/sshKey/sshKeyUpdate.ts | 37 -- src/mcp/tools/stripe/index.ts | 4 - .../stripe/stripeCanCreateMoreServers.ts | 25 - .../stripe/stripeCreateCheckoutSession.ts | 36 -- .../stripeCreateCustomerPortalSession.ts | 28 -- src/mcp/tools/stripe/stripeGetProducts.ts | 24 - src/mcp/tools/swarm/index.ts | 3 - src/mcp/tools/swarm/swarmGetNodeApps.ts | 37 -- src/mcp/tools/swarm/swarmGetNodeInfo.ts | 39 -- src/mcp/tools/swarm/swarmGetNodes.ts | 37 -- src/mcp/tools/toolFactory.ts | 124 ----- src/mcp/tools/user/index.ts | 18 - src/mcp/tools/user/userAll.ts | 24 - src/mcp/tools/user/userAssignPermissions.ts | 88 ---- src/mcp/tools/user/userCheckOrganizations.ts | 30 -- src/mcp/tools/user/userCreateApiKey.ts | 65 --- src/mcp/tools/user/userDeleteApiKey.ts | 26 - src/mcp/tools/user/userGenerateToken.ts | 24 - src/mcp/tools/user/userGet.ts | 24 - src/mcp/tools/user/userGetBackups.ts | 24 - src/mcp/tools/user/userGetByToken.ts | 35 -- src/mcp/tools/user/userGetContainerMetrics.ts | 37 -- src/mcp/tools/user/userGetInvitations.ts | 24 - src/mcp/tools/user/userGetMetricsToken.ts | 24 - src/mcp/tools/user/userGetServerMetrics.ts | 24 - src/mcp/tools/user/userHaveRootAccess.ts | 24 - src/mcp/tools/user/userOne.ts | 33 -- src/mcp/tools/user/userRemove.ts | 26 - src/mcp/tools/user/userSendInvitation.ts | 30 -- src/mcp/tools/user/userUpdate.ts | 181 ------- src/server.ts | 21 +- src/utils/responseFormatter.ts | 1 + 352 files changed, 536 insertions(+), 15711 deletions(-) delete mode 100644 src/mcp/tools/admin/adminSetupMonitoring.ts delete mode 100644 src/mcp/tools/admin/index.ts delete mode 100644 src/mcp/tools/ai/aiCreate.ts delete mode 100644 src/mcp/tools/ai/aiDelete.ts delete mode 100644 src/mcp/tools/ai/aiDeploy.ts delete mode 100644 src/mcp/tools/ai/aiGet.ts delete mode 100644 src/mcp/tools/ai/aiGetAll.ts delete mode 100644 src/mcp/tools/ai/aiGetModels.ts delete mode 100644 src/mcp/tools/ai/aiOne.ts delete mode 100644 src/mcp/tools/ai/aiSuggest.ts delete mode 100644 src/mcp/tools/ai/aiUpdate.ts delete mode 100644 src/mcp/tools/ai/index.ts create mode 100644 src/mcp/tools/api.ts create mode 100644 src/mcp/tools/apiSchema.ts delete mode 100644 src/mcp/tools/application/applicationCancelDeployment.ts delete mode 100644 src/mcp/tools/application/applicationCleanQueues.ts delete mode 100644 src/mcp/tools/application/applicationCreate.ts delete mode 100644 src/mcp/tools/application/applicationDelete.ts delete mode 100644 src/mcp/tools/application/applicationDeploy.ts delete mode 100644 src/mcp/tools/application/applicationDisconnectGitProvider.ts delete mode 100644 src/mcp/tools/application/applicationMarkRunning.ts delete mode 100644 src/mcp/tools/application/applicationMove.ts delete mode 100644 src/mcp/tools/application/applicationOne.ts delete mode 100644 src/mcp/tools/application/applicationReadAppMonitoring.ts delete mode 100644 src/mcp/tools/application/applicationReadTraefikConfig.ts delete mode 100644 src/mcp/tools/application/applicationRedeploy.ts delete mode 100644 src/mcp/tools/application/applicationRefreshToken.ts delete mode 100644 src/mcp/tools/application/applicationReload.ts delete mode 100644 src/mcp/tools/application/applicationSaveBitbucketProvider.ts delete mode 100644 src/mcp/tools/application/applicationSaveBuildType.ts delete mode 100644 src/mcp/tools/application/applicationSaveDockerProvider.ts delete mode 100644 src/mcp/tools/application/applicationSaveEnvironment.ts delete mode 100644 src/mcp/tools/application/applicationSaveGitProvider.ts delete mode 100644 src/mcp/tools/application/applicationSaveGiteaProvider.ts delete mode 100644 src/mcp/tools/application/applicationSaveGithubProvider.ts delete mode 100644 src/mcp/tools/application/applicationSaveGitlabProvider.ts delete mode 100644 src/mcp/tools/application/applicationStart.ts delete mode 100644 src/mcp/tools/application/applicationStop.ts delete mode 100644 src/mcp/tools/application/applicationUpdate.ts delete mode 100644 src/mcp/tools/application/applicationUpdateTraefikConfig.ts delete mode 100644 src/mcp/tools/application/index.ts delete mode 100644 src/mcp/tools/backup/backupCreate.ts delete mode 100644 src/mcp/tools/backup/backupListFiles.ts delete mode 100644 src/mcp/tools/backup/backupOne.ts delete mode 100644 src/mcp/tools/backup/backupRemove.ts delete mode 100644 src/mcp/tools/backup/backupRunManual.ts delete mode 100644 src/mcp/tools/backup/backupUpdate.ts delete mode 100644 src/mcp/tools/backup/index.ts delete mode 100644 src/mcp/tools/backup/volumeBackupCreate.ts delete mode 100644 src/mcp/tools/backup/volumeBackupList.ts delete mode 100644 src/mcp/tools/backup/volumeBackupOne.ts delete mode 100644 src/mcp/tools/backup/volumeBackupRemove.ts delete mode 100644 src/mcp/tools/backup/volumeBackupRunManual.ts delete mode 100644 src/mcp/tools/backup/volumeBackupUpdate.ts delete mode 100644 src/mcp/tools/certificates/certificatesAll.ts delete mode 100644 src/mcp/tools/certificates/certificatesCreate.ts delete mode 100644 src/mcp/tools/certificates/certificatesOne.ts delete mode 100644 src/mcp/tools/certificates/certificatesRemove.ts delete mode 100644 src/mcp/tools/certificates/index.ts delete mode 100644 src/mcp/tools/cluster/clusterAddManager.ts delete mode 100644 src/mcp/tools/cluster/clusterAddWorker.ts delete mode 100644 src/mcp/tools/cluster/clusterGetNodes.ts delete mode 100644 src/mcp/tools/cluster/clusterRemoveWorker.ts delete mode 100644 src/mcp/tools/cluster/index.ts delete mode 100644 src/mcp/tools/compose/composeCancelDeployment.ts delete mode 100644 src/mcp/tools/compose/composeCleanQueues.ts delete mode 100644 src/mcp/tools/compose/composeCreate.ts delete mode 100644 src/mcp/tools/compose/composeDelete.ts delete mode 100644 src/mcp/tools/compose/composeDeploy.ts delete mode 100644 src/mcp/tools/compose/composeDeployTemplate.ts delete mode 100644 src/mcp/tools/compose/composeDisconnectGitProvider.ts delete mode 100644 src/mcp/tools/compose/composeFetchSourceType.ts delete mode 100644 src/mcp/tools/compose/composeGetConvertedCompose.ts delete mode 100644 src/mcp/tools/compose/composeGetDefaultCommand.ts delete mode 100644 src/mcp/tools/compose/composeGetTags.ts delete mode 100644 src/mcp/tools/compose/composeImport.ts delete mode 100644 src/mcp/tools/compose/composeIsolatedDeployment.ts delete mode 100644 src/mcp/tools/compose/composeKillBuild.ts delete mode 100644 src/mcp/tools/compose/composeLoadMountsByService.ts delete mode 100644 src/mcp/tools/compose/composeLoadServices.ts delete mode 100644 src/mcp/tools/compose/composeMove.ts delete mode 100644 src/mcp/tools/compose/composeOne.ts delete mode 100644 src/mcp/tools/compose/composeProcessTemplate.ts delete mode 100644 src/mcp/tools/compose/composeRandomizeCompose.ts delete mode 100644 src/mcp/tools/compose/composeRedeploy.ts delete mode 100644 src/mcp/tools/compose/composeRefreshToken.ts delete mode 100644 src/mcp/tools/compose/composeStart.ts delete mode 100644 src/mcp/tools/compose/composeStop.ts delete mode 100644 src/mcp/tools/compose/composeTemplates.ts delete mode 100644 src/mcp/tools/compose/composeUpdate.ts delete mode 100644 src/mcp/tools/compose/index.ts delete mode 100644 src/mcp/tools/database/databaseChangeStatus.ts delete mode 100644 src/mcp/tools/database/databaseCreate.ts delete mode 100644 src/mcp/tools/database/databaseDeploy.ts delete mode 100644 src/mcp/tools/database/databaseMove.ts delete mode 100644 src/mcp/tools/database/databaseOne.ts delete mode 100644 src/mcp/tools/database/databaseRebuild.ts delete mode 100644 src/mcp/tools/database/databaseReload.ts delete mode 100644 src/mcp/tools/database/databaseRemove.ts delete mode 100644 src/mcp/tools/database/databaseSaveEnvironment.ts delete mode 100644 src/mcp/tools/database/databaseSaveExternalPort.ts delete mode 100644 src/mcp/tools/database/databaseStart.ts delete mode 100644 src/mcp/tools/database/databaseStop.ts delete mode 100644 src/mcp/tools/database/databaseUpdate.ts delete mode 100644 src/mcp/tools/database/index.ts delete mode 100644 src/mcp/tools/deployment/deploymentAll.ts delete mode 100644 src/mcp/tools/deployment/deploymentAllByCompose.ts delete mode 100644 src/mcp/tools/deployment/deploymentAllByServer.ts delete mode 100644 src/mcp/tools/deployment/deploymentAllByType.ts delete mode 100644 src/mcp/tools/deployment/deploymentKillProcess.ts delete mode 100644 src/mcp/tools/deployment/index.ts delete mode 100644 src/mcp/tools/destination/destinationAll.ts delete mode 100644 src/mcp/tools/destination/destinationCreate.ts delete mode 100644 src/mcp/tools/destination/destinationOne.ts delete mode 100644 src/mcp/tools/destination/destinationRemove.ts delete mode 100644 src/mcp/tools/destination/destinationTestConnection.ts delete mode 100644 src/mcp/tools/destination/destinationUpdate.ts delete mode 100644 src/mcp/tools/destination/index.ts delete mode 100644 src/mcp/tools/docker/dockerGetConfig.ts delete mode 100644 src/mcp/tools/docker/dockerGetContainers.ts delete mode 100644 src/mcp/tools/docker/dockerGetContainersByAppLabel.ts delete mode 100644 src/mcp/tools/docker/dockerGetContainersByAppNameMatch.ts delete mode 100644 src/mcp/tools/docker/dockerGetServiceContainersByAppName.ts delete mode 100644 src/mcp/tools/docker/dockerGetStackContainersByAppName.ts delete mode 100644 src/mcp/tools/docker/dockerRestartContainer.ts delete mode 100644 src/mcp/tools/docker/index.ts delete mode 100644 src/mcp/tools/domain/domainByApplicationId.ts delete mode 100644 src/mcp/tools/domain/domainByComposeId.ts delete mode 100644 src/mcp/tools/domain/domainCanGenerateTraefikMeDomains.ts delete mode 100644 src/mcp/tools/domain/domainCreate.ts delete mode 100644 src/mcp/tools/domain/domainDelete.ts delete mode 100644 src/mcp/tools/domain/domainGenerateDomain.ts delete mode 100644 src/mcp/tools/domain/domainOne.ts delete mode 100644 src/mcp/tools/domain/domainUpdate.ts delete mode 100644 src/mcp/tools/domain/domainValidateDomain.ts delete mode 100644 src/mcp/tools/domain/index.ts delete mode 100644 src/mcp/tools/environment/environmentByProjectId.ts delete mode 100644 src/mcp/tools/environment/environmentCreate.ts delete mode 100644 src/mcp/tools/environment/environmentDuplicate.ts delete mode 100644 src/mcp/tools/environment/environmentOne.ts delete mode 100644 src/mcp/tools/environment/environmentRemove.ts delete mode 100644 src/mcp/tools/environment/environmentUpdate.ts delete mode 100644 src/mcp/tools/environment/index.ts delete mode 100644 src/mcp/tools/git/gitBranches.ts delete mode 100644 src/mcp/tools/git/gitProviderCreate.ts delete mode 100644 src/mcp/tools/git/gitProviderGetAll.ts delete mode 100644 src/mcp/tools/git/gitProviderGetUrl.ts delete mode 100644 src/mcp/tools/git/gitProviderOne.ts delete mode 100644 src/mcp/tools/git/gitProviderRemove.ts delete mode 100644 src/mcp/tools/git/gitProviderUpdate.ts delete mode 100644 src/mcp/tools/git/gitProviders.ts delete mode 100644 src/mcp/tools/git/gitRepositories.ts delete mode 100644 src/mcp/tools/git/gitTestConnection.ts delete mode 100644 src/mcp/tools/git/index.ts delete mode 100644 src/mcp/tools/mounts/index.ts delete mode 100644 src/mcp/tools/mounts/mountsAllByApplicationId.ts delete mode 100644 src/mcp/tools/mounts/mountsCreate.ts delete mode 100644 src/mcp/tools/mounts/mountsOne.ts delete mode 100644 src/mcp/tools/mounts/mountsRemove.ts delete mode 100644 src/mcp/tools/mounts/mountsUpdate.ts delete mode 100644 src/mcp/tools/mysql/index.ts delete mode 100644 src/mcp/tools/mysql/mysqlChangeStatus.ts delete mode 100644 src/mcp/tools/mysql/mysqlCreate.ts delete mode 100644 src/mcp/tools/mysql/mysqlDeploy.ts delete mode 100644 src/mcp/tools/mysql/mysqlMove.ts delete mode 100644 src/mcp/tools/mysql/mysqlOne.ts delete mode 100644 src/mcp/tools/mysql/mysqlRebuild.ts delete mode 100644 src/mcp/tools/mysql/mysqlReload.ts delete mode 100644 src/mcp/tools/mysql/mysqlRemove.ts delete mode 100644 src/mcp/tools/mysql/mysqlSaveEnvironment.ts delete mode 100644 src/mcp/tools/mysql/mysqlSaveExternalPort.ts delete mode 100644 src/mcp/tools/mysql/mysqlStart.ts delete mode 100644 src/mcp/tools/mysql/mysqlStop.ts delete mode 100644 src/mcp/tools/mysql/mysqlUpdate.ts delete mode 100644 src/mcp/tools/notification/index.ts delete mode 100644 src/mcp/tools/notification/notificationAll.ts delete mode 100644 src/mcp/tools/notification/notificationCreate.ts delete mode 100644 src/mcp/tools/notification/notificationGetEmailProviders.ts delete mode 100644 src/mcp/tools/notification/notificationOne.ts delete mode 100644 src/mcp/tools/notification/notificationReceiveNotification.ts delete mode 100644 src/mcp/tools/notification/notificationRemove.ts delete mode 100644 src/mcp/tools/notification/notificationTestConnection.ts delete mode 100644 src/mcp/tools/notification/notificationUpdate.ts delete mode 100644 src/mcp/tools/organization/index.ts delete mode 100644 src/mcp/tools/organization/organizationAll.ts delete mode 100644 src/mcp/tools/organization/organizationAllInvitations.ts delete mode 100644 src/mcp/tools/organization/organizationCreate.ts delete mode 100644 src/mcp/tools/organization/organizationDelete.ts delete mode 100644 src/mcp/tools/organization/organizationOne.ts delete mode 100644 src/mcp/tools/organization/organizationRemoveInvitation.ts delete mode 100644 src/mcp/tools/organization/organizationSetDefault.ts delete mode 100644 src/mcp/tools/organization/organizationUpdate.ts delete mode 100644 src/mcp/tools/port/index.ts delete mode 100644 src/mcp/tools/port/portCreate.ts delete mode 100644 src/mcp/tools/port/portDelete.ts delete mode 100644 src/mcp/tools/port/portOne.ts delete mode 100644 src/mcp/tools/port/portUpdate.ts delete mode 100644 src/mcp/tools/postgres/index.ts delete mode 100644 src/mcp/tools/postgres/postgresChangeStatus.ts delete mode 100644 src/mcp/tools/postgres/postgresCreate.ts delete mode 100644 src/mcp/tools/postgres/postgresDeploy.ts delete mode 100644 src/mcp/tools/postgres/postgresMove.ts delete mode 100644 src/mcp/tools/postgres/postgresOne.ts delete mode 100644 src/mcp/tools/postgres/postgresRebuild.ts delete mode 100644 src/mcp/tools/postgres/postgresReload.ts delete mode 100644 src/mcp/tools/postgres/postgresRemove.ts delete mode 100644 src/mcp/tools/postgres/postgresSaveEnvironment.ts delete mode 100644 src/mcp/tools/postgres/postgresSaveExternalPort.ts delete mode 100644 src/mcp/tools/postgres/postgresStart.ts delete mode 100644 src/mcp/tools/postgres/postgresStop.ts delete mode 100644 src/mcp/tools/postgres/postgresUpdate.ts delete mode 100644 src/mcp/tools/previewDeployment/index.ts delete mode 100644 src/mcp/tools/previewDeployment/previewDeploymentAll.ts delete mode 100644 src/mcp/tools/previewDeployment/previewDeploymentDelete.ts delete mode 100644 src/mcp/tools/previewDeployment/previewDeploymentOne.ts delete mode 100644 src/mcp/tools/project/index.ts delete mode 100644 src/mcp/tools/project/projectAll.ts delete mode 100644 src/mcp/tools/project/projectCreate.ts delete mode 100644 src/mcp/tools/project/projectDuplicate.ts delete mode 100644 src/mcp/tools/project/projectOne.ts delete mode 100644 src/mcp/tools/project/projectRemove.ts delete mode 100644 src/mcp/tools/project/projectUpdate.ts delete mode 100644 src/mcp/tools/redirects/index.ts delete mode 100644 src/mcp/tools/redirects/redirectsCreate.ts delete mode 100644 src/mcp/tools/redirects/redirectsDelete.ts delete mode 100644 src/mcp/tools/redirects/redirectsOne.ts delete mode 100644 src/mcp/tools/redirects/redirectsUpdate.ts delete mode 100644 src/mcp/tools/registry/index.ts delete mode 100644 src/mcp/tools/registry/registryAll.ts delete mode 100644 src/mcp/tools/registry/registryCreate.ts delete mode 100644 src/mcp/tools/registry/registryOne.ts delete mode 100644 src/mcp/tools/registry/registryRemove.ts delete mode 100644 src/mcp/tools/registry/registryTestRegistry.ts delete mode 100644 src/mcp/tools/registry/registryUpdate.ts delete mode 100644 src/mcp/tools/rollback/index.ts delete mode 100644 src/mcp/tools/rollback/rollbackDelete.ts delete mode 100644 src/mcp/tools/rollback/rollbackRollback.ts delete mode 100644 src/mcp/tools/schedule/index.ts delete mode 100644 src/mcp/tools/schedule/scheduleCreate.ts delete mode 100644 src/mcp/tools/schedule/scheduleDelete.ts delete mode 100644 src/mcp/tools/schedule/scheduleList.ts delete mode 100644 src/mcp/tools/schedule/scheduleOne.ts delete mode 100644 src/mcp/tools/schedule/scheduleRunManually.ts delete mode 100644 src/mcp/tools/schedule/scheduleUpdate.ts delete mode 100644 src/mcp/tools/security/index.ts delete mode 100644 src/mcp/tools/security/securityCreate.ts delete mode 100644 src/mcp/tools/security/securityDelete.ts delete mode 100644 src/mcp/tools/security/securityOne.ts delete mode 100644 src/mcp/tools/security/securityUpdate.ts delete mode 100644 src/mcp/tools/server/index.ts delete mode 100644 src/mcp/tools/server/serverAll.ts delete mode 100644 src/mcp/tools/server/serverBuildServers.ts delete mode 100644 src/mcp/tools/server/serverCount.ts delete mode 100644 src/mcp/tools/server/serverCreate.ts delete mode 100644 src/mcp/tools/server/serverGetDefaultCommand.ts delete mode 100644 src/mcp/tools/server/serverGetServerMetrics.ts delete mode 100644 src/mcp/tools/server/serverGetServerTime.ts delete mode 100644 src/mcp/tools/server/serverOne.ts delete mode 100644 src/mcp/tools/server/serverPublicIp.ts delete mode 100644 src/mcp/tools/server/serverRemove.ts delete mode 100644 src/mcp/tools/server/serverSecurity.ts delete mode 100644 src/mcp/tools/server/serverSetup.ts delete mode 100644 src/mcp/tools/server/serverSetupMonitoring.ts delete mode 100644 src/mcp/tools/server/serverUpdate.ts delete mode 100644 src/mcp/tools/server/serverValidate.ts delete mode 100644 src/mcp/tools/server/serverWithSSHKey.ts delete mode 100644 src/mcp/tools/settings/index.ts delete mode 100644 src/mcp/tools/settings/settingsAssignDomainServer.ts delete mode 100644 src/mcp/tools/settings/settingsCheckGPUStatus.ts delete mode 100644 src/mcp/tools/settings/settingsCleanAll.ts delete mode 100644 src/mcp/tools/settings/settingsCleanDockerBuilder.ts delete mode 100644 src/mcp/tools/settings/settingsCleanDockerPrune.ts delete mode 100644 src/mcp/tools/settings/settingsCleanMonitoring.ts delete mode 100644 src/mcp/tools/settings/settingsCleanRedis.ts delete mode 100644 src/mcp/tools/settings/settingsCleanSSHPrivateKey.ts delete mode 100644 src/mcp/tools/settings/settingsCleanStoppedContainers.ts delete mode 100644 src/mcp/tools/settings/settingsCleanUnusedImages.ts delete mode 100644 src/mcp/tools/settings/settingsCleanUnusedVolumes.ts delete mode 100644 src/mcp/tools/settings/settingsGetDokployCloudIps.ts delete mode 100644 src/mcp/tools/settings/settingsGetDokployVersion.ts delete mode 100644 src/mcp/tools/settings/settingsGetIp.ts delete mode 100644 src/mcp/tools/settings/settingsGetLogCleanupStatus.ts delete mode 100644 src/mcp/tools/settings/settingsGetOpenApiDocument.ts delete mode 100644 src/mcp/tools/settings/settingsGetReleaseTag.ts delete mode 100644 src/mcp/tools/settings/settingsGetTraefikPorts.ts delete mode 100644 src/mcp/tools/settings/settingsGetUpdateData.ts delete mode 100644 src/mcp/tools/settings/settingsHaveActivateRequests.ts delete mode 100644 src/mcp/tools/settings/settingsHaveTraefikDashboardPortEnabled.ts delete mode 100644 src/mcp/tools/settings/settingsHealth.ts delete mode 100644 src/mcp/tools/settings/settingsIsCloud.ts delete mode 100644 src/mcp/tools/settings/settingsIsUserSubscribed.ts delete mode 100644 src/mcp/tools/settings/settingsReadDirectories.ts delete mode 100644 src/mcp/tools/settings/settingsReadMiddlewareTraefikConfig.ts delete mode 100644 src/mcp/tools/settings/settingsReadTraefikConfig.ts delete mode 100644 src/mcp/tools/settings/settingsReadTraefikEnv.ts delete mode 100644 src/mcp/tools/settings/settingsReadTraefikFile.ts delete mode 100644 src/mcp/tools/settings/settingsReadWebServerTraefikConfig.ts delete mode 100644 src/mcp/tools/settings/settingsReloadRedis.ts delete mode 100644 src/mcp/tools/settings/settingsReloadServer.ts delete mode 100644 src/mcp/tools/settings/settingsReloadTraefik.ts delete mode 100644 src/mcp/tools/settings/settingsSaveSSHPrivateKey.ts delete mode 100644 src/mcp/tools/settings/settingsSetupGPU.ts delete mode 100644 src/mcp/tools/settings/settingsToggleDashboard.ts delete mode 100644 src/mcp/tools/settings/settingsToggleRequests.ts delete mode 100644 src/mcp/tools/settings/settingsUpdateDockerCleanup.ts delete mode 100644 src/mcp/tools/settings/settingsUpdateLogCleanup.ts delete mode 100644 src/mcp/tools/settings/settingsUpdateMiddlewareTraefikConfig.ts delete mode 100644 src/mcp/tools/settings/settingsUpdateServer.ts delete mode 100644 src/mcp/tools/settings/settingsUpdateTraefikConfig.ts delete mode 100644 src/mcp/tools/settings/settingsUpdateTraefikFile.ts delete mode 100644 src/mcp/tools/settings/settingsUpdateTraefikPorts.ts delete mode 100644 src/mcp/tools/settings/settingsUpdateWebServerTraefikConfig.ts delete mode 100644 src/mcp/tools/settings/settingsWriteTraefikEnv.ts delete mode 100644 src/mcp/tools/sshKey/index.ts delete mode 100644 src/mcp/tools/sshKey/sshKeyAll.ts delete mode 100644 src/mcp/tools/sshKey/sshKeyCreate.ts delete mode 100644 src/mcp/tools/sshKey/sshKeyGenerate.ts delete mode 100644 src/mcp/tools/sshKey/sshKeyOne.ts delete mode 100644 src/mcp/tools/sshKey/sshKeyRemove.ts delete mode 100644 src/mcp/tools/sshKey/sshKeyUpdate.ts delete mode 100644 src/mcp/tools/stripe/index.ts delete mode 100644 src/mcp/tools/stripe/stripeCanCreateMoreServers.ts delete mode 100644 src/mcp/tools/stripe/stripeCreateCheckoutSession.ts delete mode 100644 src/mcp/tools/stripe/stripeCreateCustomerPortalSession.ts delete mode 100644 src/mcp/tools/stripe/stripeGetProducts.ts delete mode 100644 src/mcp/tools/swarm/index.ts delete mode 100644 src/mcp/tools/swarm/swarmGetNodeApps.ts delete mode 100644 src/mcp/tools/swarm/swarmGetNodeInfo.ts delete mode 100644 src/mcp/tools/swarm/swarmGetNodes.ts delete mode 100644 src/mcp/tools/toolFactory.ts delete mode 100644 src/mcp/tools/user/index.ts delete mode 100644 src/mcp/tools/user/userAll.ts delete mode 100644 src/mcp/tools/user/userAssignPermissions.ts delete mode 100644 src/mcp/tools/user/userCheckOrganizations.ts delete mode 100644 src/mcp/tools/user/userCreateApiKey.ts delete mode 100644 src/mcp/tools/user/userDeleteApiKey.ts delete mode 100644 src/mcp/tools/user/userGenerateToken.ts delete mode 100644 src/mcp/tools/user/userGet.ts delete mode 100644 src/mcp/tools/user/userGetBackups.ts delete mode 100644 src/mcp/tools/user/userGetByToken.ts delete mode 100644 src/mcp/tools/user/userGetContainerMetrics.ts delete mode 100644 src/mcp/tools/user/userGetInvitations.ts delete mode 100644 src/mcp/tools/user/userGetMetricsToken.ts delete mode 100644 src/mcp/tools/user/userGetServerMetrics.ts delete mode 100644 src/mcp/tools/user/userHaveRootAccess.ts delete mode 100644 src/mcp/tools/user/userOne.ts delete mode 100644 src/mcp/tools/user/userRemove.ts delete mode 100644 src/mcp/tools/user/userSendInvitation.ts delete mode 100644 src/mcp/tools/user/userUpdate.ts diff --git a/src/mcp/tools/admin/adminSetupMonitoring.ts b/src/mcp/tools/admin/adminSetupMonitoring.ts deleted file mode 100644 index 1e51e2f..0000000 --- a/src/mcp/tools/admin/adminSetupMonitoring.ts +++ /dev/null @@ -1,85 +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 adminSetupMonitoring = createTool({ - name: "admin-setup-monitoring", - description: "Sets up monitoring configuration for the Dokploy instance.", - schema: z.object({ - metricsConfig: z - .object({ - server: z - .object({ - refreshRate: z - .number() - .min(2) - .describe("Server metrics refresh rate in seconds. Minimum: 2."), - port: z.number().min(1).describe("Port number for metrics server."), - token: z.string().describe("Authentication token for metrics."), - urlCallback: z - .string() - .url() - .describe("Callback URL for metrics notifications."), - retentionDays: z - .number() - .min(1) - .describe("Number of days to retain metrics data. Minimum: 1."), - cronJob: z - .string() - .min(1) - .describe("Cron expression for metrics cleanup schedule."), - thresholds: z - .object({ - cpu: z - .number() - .min(0) - .describe("CPU usage threshold percentage for alerts."), - memory: z - .number() - .min(0) - .describe("Memory usage threshold percentage for alerts."), - }) - .describe("Alert thresholds for server resources."), - }) - .describe("Server monitoring configuration."), - containers: z - .object({ - refreshRate: z - .number() - .min(2) - .describe( - "Container metrics refresh rate in seconds. Minimum: 2.", - ), - services: z - .object({ - include: z - .array(z.string()) - .optional() - .describe("List of service names to include in monitoring."), - exclude: z - .array(z.string()) - .optional() - .describe("List of service names to exclude from monitoring."), - }) - .describe("Services filter configuration."), - }) - .describe("Container monitoring configuration."), - }) - .describe("Metrics configuration for server and container monitoring."), - }), - annotations: { - title: "Setup Monitoring", - destructiveHint: false, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/admin.setupMonitoring", input); - - return ResponseFormatter.success( - "Monitoring setup configured successfully", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/admin/index.ts b/src/mcp/tools/admin/index.ts deleted file mode 100644 index f34e839..0000000 --- a/src/mcp/tools/admin/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { adminSetupMonitoring } from "./adminSetupMonitoring.js"; diff --git a/src/mcp/tools/ai/aiCreate.ts b/src/mcp/tools/ai/aiCreate.ts deleted file mode 100644 index d481c44..0000000 --- a/src/mcp/tools/ai/aiCreate.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 aiCreate = createTool({ - name: "ai-create", - description: "Creates a new AI configuration in Dokploy.", - schema: z.object({ - name: z.string().min(1).describe("The name of the AI configuration."), - apiUrl: z.string().url().describe("The API URL for the AI service."), - apiKey: z.string().describe("The API key for authentication."), - model: z.string().min(1).describe("The model to use."), - isEnabled: z.boolean().describe("Whether the AI configuration is enabled."), - }), - annotations: { - title: "Create AI Configuration", - readOnlyHint: false, - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/ai.create", { - name: input.name, - apiUrl: input.apiUrl, - apiKey: input.apiKey, - model: input.model, - isEnabled: input.isEnabled, - }); - - return ResponseFormatter.success( - `Successfully created AI configuration "${input.name}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/ai/aiDelete.ts b/src/mcp/tools/ai/aiDelete.ts deleted file mode 100644 index 9bb4b94..0000000 --- a/src/mcp/tools/ai/aiDelete.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 aiDelete = createTool({ - name: "ai-delete", - description: "Deletes an AI configuration in Dokploy.", - schema: z.object({ - aiId: z.string().describe("The ID of the AI configuration to delete."), - }), - annotations: { - title: "Delete AI Configuration", - readOnlyHint: false, - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/ai.delete", { - aiId: input.aiId, - }); - - return ResponseFormatter.success( - `Successfully deleted AI configuration "${input.aiId}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/ai/aiDeploy.ts b/src/mcp/tools/ai/aiDeploy.ts deleted file mode 100644 index a30135c..0000000 --- a/src/mcp/tools/ai/aiDeploy.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -const domainSchema = z.object({ - host: z.string().min(1).describe("The host domain for the service."), - port: z.number().min(1).describe("The port number for the service."), - serviceName: z.string().min(1).describe("The name of the service."), -}); - -const configFileSchema = z.object({ - filePath: z.string().min(1).describe("The path where the config file will be stored."), - content: z.string().min(1).describe("The content of the config file."), -}); - -export const aiDeploy = createTool({ - name: "ai-deploy", - description: "Deploys an AI-generated application configuration in Dokploy.", - schema: z.object({ - environmentId: z - .string() - .min(1) - .describe("The ID of the environment to deploy to. Required."), - id: z.string().min(1).describe("The unique ID for the deployment. Required."), - dockerCompose: z - .string() - .min(1) - .describe("The Docker Compose configuration content. Required."), - envVariables: z - .string() - .describe("Environment variables for the deployment in KEY=value format. Required."), - serverId: z - .string() - .optional() - .describe("The ID of the server to deploy to. Optional."), - name: z.string().min(1).describe("The name of the deployment. Required."), - description: z.string().describe("A description of the deployment. Required."), - domains: z - .array(domainSchema) - .optional() - .describe("Domain configurations for the deployment. Each domain requires host, port, and serviceName."), - configFiles: z - .array(configFileSchema) - .optional() - .describe("Configuration files for the deployment. Each file requires filePath and content."), - }), - annotations: { - title: "Deploy AI Configuration", - readOnlyHint: false, - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const body: Record = { - environmentId: input.environmentId, - id: input.id, - dockerCompose: input.dockerCompose, - envVariables: input.envVariables, - name: input.name, - description: input.description, - }; - - if (input.serverId) { - body.serverId = input.serverId; - } - if (input.domains) { - body.domains = input.domains; - } - if (input.configFiles) { - body.configFiles = input.configFiles; - } - - const response = await apiClient.post("/ai.deploy", body); - - return ResponseFormatter.success( - `Successfully deployed AI configuration "${input.name}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/ai/aiGet.ts b/src/mcp/tools/ai/aiGet.ts deleted file mode 100644 index a5cfb24..0000000 --- a/src/mcp/tools/ai/aiGet.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 aiGet = createTool({ - name: "ai-get", - description: "Gets a specific AI configuration by its ID in Dokploy.", - schema: z.object({ - aiId: z.string().describe("The ID of the AI configuration to retrieve."), - }), - annotations: { - title: "Get AI Configuration by ID", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.get(`/ai.get?aiId=${input.aiId}`); - - if (!response?.data) { - return ResponseFormatter.error( - "Failed to fetch AI configuration", - `AI configuration with ID "${input.aiId}" not found`, - ); - } - - return ResponseFormatter.success( - `Successfully fetched AI configuration "${input.aiId}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/ai/aiGetAll.ts b/src/mcp/tools/ai/aiGetAll.ts deleted file mode 100644 index 398178b..0000000 --- a/src/mcp/tools/ai/aiGetAll.ts +++ /dev/null @@ -1,24 +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 aiGetAll = createTool({ - name: "ai-get-all", - description: "Gets all AI configurations in Dokploy.", - schema: z.object({}), - annotations: { - title: "Get All AI Configurations", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.get("/ai.getAll"); - - return ResponseFormatter.success( - "Successfully fetched all AI configurations", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/ai/aiGetModels.ts b/src/mcp/tools/ai/aiGetModels.ts deleted file mode 100644 index 0bbb647..0000000 --- a/src/mcp/tools/ai/aiGetModels.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 aiGetModels = createTool({ - name: "ai-get-models", - description: "Gets available AI models from an API endpoint in Dokploy.", - schema: z.object({ - apiUrl: z.string().min(1).describe("The API URL to fetch models from."), - apiKey: z.string().describe("The API key for authentication."), - }), - annotations: { - title: "Get AI Models", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const params = new URLSearchParams(); - params.append("apiUrl", input.apiUrl); - params.append("apiKey", input.apiKey); - - const response = await apiClient.get(`/ai.getModels?${params.toString()}`); - - return ResponseFormatter.success( - "Successfully fetched AI models", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/ai/aiOne.ts b/src/mcp/tools/ai/aiOne.ts deleted file mode 100644 index 4cc27b9..0000000 --- a/src/mcp/tools/ai/aiOne.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 aiOne = createTool({ - name: "ai-one", - description: "Gets a specific AI configuration by its ID in Dokploy.", - schema: z.object({ - aiId: z.string().describe("The ID of the AI configuration to retrieve."), - }), - annotations: { - title: "Get AI Configuration", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.get(`/ai.one?aiId=${input.aiId}`); - - if (!response?.data) { - return ResponseFormatter.error( - "Failed to fetch AI configuration", - `AI configuration with ID "${input.aiId}" not found`, - ); - } - - return ResponseFormatter.success( - `Successfully fetched AI configuration "${input.aiId}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/ai/aiSuggest.ts b/src/mcp/tools/ai/aiSuggest.ts deleted file mode 100644 index 230a567..0000000 --- a/src/mcp/tools/ai/aiSuggest.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 aiSuggest = createTool({ - name: "ai-suggest", - description: "Gets AI suggestions based on input in Dokploy.", - schema: z.object({ - aiId: z.string().describe("The ID of the AI configuration to use."), - input: z.string().describe("The input to get suggestions for."), - serverId: z - .string() - .optional() - .describe("The ID of the server to get suggestions for."), - }), - annotations: { - title: "Get AI Suggestions", - readOnlyHint: false, - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const body: Record = { - aiId: input.aiId, - input: input.input, - }; - - if (input.serverId) { - body.serverId = input.serverId; - } - - const response = await apiClient.post("/ai.suggest", body); - - return ResponseFormatter.success( - "Successfully retrieved AI suggestions", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/ai/aiUpdate.ts b/src/mcp/tools/ai/aiUpdate.ts deleted file mode 100644 index bd12dd4..0000000 --- a/src/mcp/tools/ai/aiUpdate.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 aiUpdate = createTool({ - name: "ai-update", - description: "Updates an existing AI configuration in Dokploy.", - schema: z.object({ - aiId: z - .string() - .min(1) - .describe("The ID of the AI configuration to update."), - name: z - .string() - .min(1) - .optional() - .describe("The new name for the AI configuration."), - apiUrl: z - .string() - .url() - .optional() - .describe("The new API URL for the AI service."), - apiKey: z - .string() - .optional() - .describe("The new API key for authentication."), - model: z.string().min(1).optional().describe("The new model to use."), - isEnabled: z - .boolean() - .optional() - .describe("Whether the AI configuration is enabled."), - createdAt: z.string().optional().describe("The creation timestamp."), - }), - annotations: { - title: "Update AI Configuration", - readOnlyHint: false, - destructiveHint: false, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const body: Record = { aiId: input.aiId }; - - if (input.name !== undefined) body.name = input.name; - if (input.apiUrl !== undefined) body.apiUrl = input.apiUrl; - if (input.apiKey !== undefined) body.apiKey = input.apiKey; - if (input.model !== undefined) body.model = input.model; - if (input.isEnabled !== undefined) body.isEnabled = input.isEnabled; - if (input.createdAt !== undefined) body.createdAt = input.createdAt; - - const response = await apiClient.post("/ai.update", body); - - return ResponseFormatter.success( - `Successfully updated AI configuration "${input.aiId}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/ai/index.ts b/src/mcp/tools/ai/index.ts deleted file mode 100644 index b069e78..0000000 --- a/src/mcp/tools/ai/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -export { aiOne } from "./aiOne.js"; -export { aiGetModels } from "./aiGetModels.js"; -export { aiCreate } from "./aiCreate.js"; -export { aiUpdate } from "./aiUpdate.js"; -export { aiGetAll } from "./aiGetAll.js"; -export { aiGet } from "./aiGet.js"; -export { aiDelete } from "./aiDelete.js"; -export { aiSuggest } from "./aiSuggest.js"; -export { aiDeploy } from "./aiDeploy.js"; diff --git a/src/mcp/tools/api.ts b/src/mcp/tools/api.ts new file mode 100644 index 0000000..6d4328d --- /dev/null +++ b/src/mcp/tools/api.ts @@ -0,0 +1,278 @@ +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"; + +const logger = createLogger("DokployApi"); + +const OPERATIONS_LIST = `Available operations (use dokploy-api-schema for 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`; + +// Operations that use GET. Everything else uses POST. +const GET_OPERATIONS = new Set([ + "ai.get", + "ai.getAll", + "ai.getModels", + "ai.one", + "application.one", + "application.readAppMonitoring", + "application.readTraefikConfig", + "backup.listBackupFiles", + "backup.one", + "bitbucket.getBitbucketBranches", + "bitbucket.getBitbucketRepositories", + "certificates.all", + "certificates.one", + "cluster.addManager", + "cluster.addWorker", + "cluster.getNodes", + "compose.getConvertedCompose", + "compose.getDefaultCommand", + "compose.getTags", + "compose.loadMountsByService", + "compose.loadServices", + "compose.one", + "compose.templates", + "deployment.all", + "deployment.allByCompose", + "deployment.allByServer", + "deployment.allByType", + "destination.all", + "destination.one", + "docker.getConfig", + "docker.getContainers", + "docker.getContainersByAppLabel", + "docker.getContainersByAppNameMatch", + "docker.getServiceContainersByAppName", + "docker.getStackContainersByAppName", + "domain.byApplicationId", + "domain.byComposeId", + "domain.canGenerateTraefikMeDomains", + "domain.one", + "environment.byProjectId", + "environment.one", + "gitea.getGiteaBranches", + "gitea.getGiteaRepositories", + "gitea.getGiteaUrl", + "github.getGithubBranches", + "github.getGithubRepositories", + "github.githubProviders", + "gitlab.getGitlabBranches", + "gitlab.getGitlabRepositories", + "gitProvider.getAll", + "gitProvider.one", + "mariadb.one", + "mongo.one", + "mounts.one", + "mysql.one", + "notification.all", + "notification.getEmailProviders", + "notification.one", + "organization.all", + "organization.allInvitations", + "organization.one", + "port.one", + "postgres.one", + "previewDeployment.all", + "previewDeployment.one", + "project.all", + "project.one", + "redirects.one", + "redis.one", + "registry.all", + "registry.one", + "schedule.list", + "schedule.one", + "security.one", + "server.all", + "server.buildServers", + "server.count", + "server.getDefaultCommand", + "server.getServerMetrics", + "server.getServerTime", + "server.one", + "server.publicIp", + "server.security", + "server.validate", + "server.withSSHKey", + "settings.checkGPUStatus", + "settings.getDokployCloudIps", + "settings.getDokployVersion", + "settings.getIp", + "settings.getLogCleanupStatus", + "settings.getOpenApiDocument", + "settings.getReleaseTag", + "settings.getTraefikPorts", + "settings.haveActivateRequests", + "settings.haveTraefikDashboardPortEnabled", + "settings.health", + "settings.isCloud", + "settings.isUserSubscribed", + "settings.readDirectories", + "settings.readMiddlewareTraefikConfig", + "settings.readTraefikConfig", + "settings.readTraefikEnv", + "settings.readTraefikFile", + "settings.readWebServerTraefikConfig", + "sshKey.all", + "sshKey.one", + "stripe.canCreateMoreServers", + "stripe.getProducts", + "swarm.getNodeApps", + "swarm.getNodeInfo", + "swarm.getNodes", + "user.all", + "user.checkUserOrganizations", + "user.get", + "user.getBackups", + "user.getUserByToken", + "user.getContainerMetrics", + "user.getInvitations", + "user.getMetricsToken", + "user.getServerMetrics", + "user.haveRootAccess", + "user.one", + "volumeBackups.list", + "volumeBackups.one", +]); + +function getMethod(operation: string): "GET" | "POST" { + return GET_OPERATIONS.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"; + +export const description = `Execute any Dokploy API operation. HTTP method is auto-detected. Use dokploy-api-schema to discover parameters for an operation. + +${OPERATIONS_LIST}`; + +export const annotations = { + title: "Dokploy API", + readOnlyHint: false, + openWorldHint: true, +}; + +export async function handler(input: { + operation: string; + params?: Record; +}) { + const { operation, params } = input; + const method = 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 === 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..404afd2 --- /dev/null +++ b/src/mcp/tools/apiSchema.ts @@ -0,0 +1,238 @@ +import { z } from "zod"; +import apiClient from "../../utils/apiClient.js"; +import { createLogger } from "../../utils/logger.js"; +import { ResponseFormatter } from "../../utils/responseFormatter.js"; + +const logger = createLogger("DokployApiSchema"); + +let cachedSpec: Record | null = null; + +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!; +} + +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/backup/backupCreate.ts b/src/mcp/tools/backup/backupCreate.ts deleted file mode 100644 index a39f60e..0000000 --- a/src/mcp/tools/backup/backupCreate.ts +++ /dev/null @@ -1,99 +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 backupCreate = createTool({ - name: "backup-create", - description: - "Creates a new backup configuration for a database or compose service in Dokploy. Supports PostgreSQL, MySQL, MariaDB, MongoDB, and web server backups.", - schema: z.object({ - schedule: z - .string() - .describe("Cron schedule expression for automated backups (e.g., '0 2 * * *' for daily at 2 AM)."), - enabled: z - .boolean() - .nullable() - .optional() - .describe("Whether the backup schedule is enabled. Defaults to true if not specified."), - prefix: z - .string() - .min(1) - .describe("Prefix for backup file names. Used to identify and organize backup files."), - destinationId: z - .string() - .describe("ID of the backup destination (S3, local storage, etc.) where backups will be stored."), - keepLatestCount: z - .number() - .nullable() - .optional() - .describe("Number of most recent backups to retain. Older backups are automatically deleted."), - database: z - .string() - .min(1) - .describe("Name of the database to backup. Must match the database name in the service."), - mariadbId: z - .string() - .nullable() - .optional() - .describe("ID of the MariaDB service. Required when databaseType is 'mariadb'."), - mysqlId: z - .string() - .nullable() - .optional() - .describe("ID of the MySQL service. Required when databaseType is 'mysql'."), - postgresId: z - .string() - .nullable() - .optional() - .describe("ID of the PostgreSQL service. Required when databaseType is 'postgres'."), - mongoId: z - .string() - .nullable() - .optional() - .describe("ID of the MongoDB service. Required when databaseType is 'mongo'."), - databaseType: z - .enum(["postgres", "mariadb", "mysql", "mongo", "web-server"]) - .describe( - "Type of database to backup: 'postgres', 'mariadb', 'mysql', 'mongo', or 'web-server'.", - ), - userId: z - .string() - .nullable() - .optional() - .describe("ID of the user who owns this backup configuration."), - backupType: z - .enum(["database", "compose"]) - .optional() - .describe("Type of backup: 'database' for standalone databases, 'compose' for compose service databases."), - composeId: z - .string() - .nullable() - .optional() - .describe("ID of the compose deployment. Required when backupType is 'compose'."), - serviceName: z - .string() - .nullable() - .optional() - .describe("Name of the service within the compose deployment to backup."), - metadata: z - .unknown() - .nullable() - .optional() - .describe("Additional metadata to associate with the backup configuration."), - }), - annotations: { - title: "Create Backup", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/backup.create", input); - - return ResponseFormatter.success( - `Backup configuration created successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/backup/backupListFiles.ts b/src/mcp/tools/backup/backupListFiles.ts deleted file mode 100644 index 8b794fd..0000000 --- a/src/mcp/tools/backup/backupListFiles.ts +++ /dev/null @@ -1,48 +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 backupListFiles = createTool({ - name: "backup-list-files", - description: - "Lists backup files stored at a specific destination. Allows searching and filtering backup files by name or prefix.", - schema: z.object({ - destinationId: z - .string() - .describe("The ID of the backup destination to list files from."), - search: z - .string() - .describe("Search string to filter backup files by name or prefix."), - serverId: z - .string() - .optional() - .describe("Server ID to filter backups for multi-server deployments."), - }), - annotations: { - title: "List Backup Files", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - let url = `/backup.listBackupFiles?destinationId=${encodeURIComponent(input.destinationId)}&search=${encodeURIComponent(input.search)}`; - if (input.serverId) { - url += `&serverId=${encodeURIComponent(input.serverId)}`; - } - - const backups = await apiClient.get(url); - - if (!backups?.data) { - return ResponseFormatter.error( - "Failed to fetch backup files", - "No backup files found", - ); - } - - return ResponseFormatter.success( - `Successfully fetched backup files`, - backups.data, - ); - }, -}); diff --git a/src/mcp/tools/backup/backupOne.ts b/src/mcp/tools/backup/backupOne.ts deleted file mode 100644 index e6b514e..0000000 --- a/src/mcp/tools/backup/backupOne.ts +++ /dev/null @@ -1,38 +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 backupOne = createTool({ - name: "backup-one", - description: - "Retrieves details of a specific backup configuration by its ID. Returns backup schedule, destination, database settings, and status.", - schema: z.object({ - backupId: z - .string() - .describe("The unique ID of the backup configuration to retrieve."), - }), - annotations: { - title: "Get Backup Details", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const backup = await apiClient.get( - `/backup.one?backupId=${input.backupId}`, - ); - - if (!backup?.data) { - return ResponseFormatter.error( - "Failed to fetch backup", - `Backup with ID "${input.backupId}" not found`, - ); - } - - return ResponseFormatter.success( - `Successfully fetched backup "${input.backupId}"`, - backup.data, - ); - }, -}); diff --git a/src/mcp/tools/backup/backupRemove.ts b/src/mcp/tools/backup/backupRemove.ts deleted file mode 100644 index d4ee7b4..0000000 --- a/src/mcp/tools/backup/backupRemove.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 backupRemove = createTool({ - name: "backup-remove", - description: - "Permanently deletes a backup configuration from Dokploy. This stops scheduled backups but does not delete existing backup files.", - schema: z.object({ - backupId: z - .string() - .describe("The unique ID of the backup configuration to delete."), - }), - annotations: { - title: "Remove Backup", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/backup.remove", input); - - return ResponseFormatter.success( - `Backup "${input.backupId}" removed successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/backup/backupRunManual.ts b/src/mcp/tools/backup/backupRunManual.ts deleted file mode 100644 index b8bdd7d..0000000 --- a/src/mcp/tools/backup/backupRunManual.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -const backupTypeEnum = z.enum([ - "postgres", - "mysql", - "mariadb", - "mongo", - "compose", - "webServer", -]); - -type BackupType = z.infer; - -const endpointMap: Record = { - postgres: "/backup.manualBackupPostgres", - mysql: "/backup.manualBackupMySql", - mariadb: "/backup.manualBackupMariadb", - mongo: "/backup.manualBackupMongo", - compose: "/backup.manualBackupCompose", - webServer: "/backup.manualBackupWebServer", -}; - -const typeLabels: Record = { - postgres: "PostgreSQL", - mysql: "MySQL", - mariadb: "MariaDB", - mongo: "MongoDB", - compose: "Compose", - webServer: "Web Server", -}; - -export const backupRunManual = createTool({ - name: "backup-run-manual", - description: - "Triggers an immediate manual backup using an existing backup configuration. Runs the backup outside of the scheduled cron time. Supports PostgreSQL, MySQL, MariaDB, MongoDB, Compose, and Web Server backups.", - schema: z.object({ - backupId: z - .string() - .describe("The unique ID of the backup configuration to execute."), - type: backupTypeEnum.describe( - "The database type for the backup. Must match the backup configuration type: 'postgres', 'mysql', 'mariadb', 'mongo', 'compose', or 'webServer'.", - ), - }), - annotations: { - title: "Run Manual Backup", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const endpoint = endpointMap[input.type]; - const label = typeLabels[input.type]; - - const response = await apiClient.post(endpoint, { backupId: input.backupId }); - - return ResponseFormatter.success( - `Manual ${label} backup triggered successfully for backup "${input.backupId}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/backup/backupUpdate.ts b/src/mcp/tools/backup/backupUpdate.ts deleted file mode 100644 index 10a7546..0000000 --- a/src/mcp/tools/backup/backupUpdate.ts +++ /dev/null @@ -1,67 +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 backupUpdate = createTool({ - name: "backup-update", - description: - "Updates an existing backup configuration in Dokploy. Allows modifying schedule, destination, retention settings, and other backup parameters.", - schema: z.object({ - backupId: z - .string() - .describe("The unique ID of the backup configuration to update."), - schedule: z - .string() - .describe("Cron schedule expression for automated backups (e.g., '0 2 * * *' for daily at 2 AM)."), - enabled: z - .boolean() - .nullable() - .optional() - .describe("Whether the backup schedule is enabled."), - prefix: z - .string() - .min(1) - .describe("Prefix for backup file names. Used to identify and organize backup files."), - destinationId: z - .string() - .describe("ID of the backup destination (S3, local storage, etc.) where backups will be stored."), - database: z - .string() - .min(1) - .describe("Name of the database to backup. Must match the database name in the service."), - keepLatestCount: z - .number() - .nullable() - .optional() - .describe("Number of most recent backups to retain. Older backups are automatically deleted."), - serviceName: z - .string() - .nullable() - .describe("Name of the service within the compose deployment. Required for compose backups."), - metadata: z - .unknown() - .nullable() - .optional() - .describe("Additional metadata to associate with the backup configuration."), - databaseType: z - .enum(["postgres", "mariadb", "mysql", "mongo", "web-server"]) - .describe( - "Type of database to backup: 'postgres', 'mariadb', 'mysql', 'mongo', or 'web-server'.", - ), - }), - annotations: { - title: "Update Backup", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/backup.update", input); - - return ResponseFormatter.success( - `Backup "${input.backupId}" updated successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/backup/index.ts b/src/mcp/tools/backup/index.ts deleted file mode 100644 index 6ef9fb5..0000000 --- a/src/mcp/tools/backup/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -// Database backup tools -export { backupCreate } from "./backupCreate.js"; -export { backupOne } from "./backupOne.js"; -export { backupUpdate } from "./backupUpdate.js"; -export { backupRemove } from "./backupRemove.js"; -export { backupListFiles } from "./backupListFiles.js"; -export { backupRunManual } from "./backupRunManual.js"; - -// Volume backup tools -export { volumeBackupCreate } from "./volumeBackupCreate.js"; -export { volumeBackupOne } from "./volumeBackupOne.js"; -export { volumeBackupUpdate } from "./volumeBackupUpdate.js"; -export { volumeBackupRemove } from "./volumeBackupRemove.js"; -export { volumeBackupList } from "./volumeBackupList.js"; -export { volumeBackupRunManual } from "./volumeBackupRunManual.js"; diff --git a/src/mcp/tools/backup/volumeBackupCreate.ts b/src/mcp/tools/backup/volumeBackupCreate.ts deleted file mode 100644 index 6117831..0000000 --- a/src/mcp/tools/backup/volumeBackupCreate.ts +++ /dev/null @@ -1,115 +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 volumeBackupCreate = createTool({ - name: "volume-backup-create", - description: - "Creates a new volume backup configuration in Dokploy. Volume backups allow backing up Docker volumes used by applications, databases, or compose services.", - schema: z.object({ - name: z - .string() - .describe("Descriptive name for the volume backup configuration."), - volumeName: z - .string() - .describe("Name of the Docker volume to backup."), - prefix: z - .string() - .describe("Prefix for backup file names. Used to identify and organize backup files."), - cronExpression: z - .string() - .describe("Cron expression for scheduling backups (e.g., '0 2 * * *' for daily at 2 AM)."), - destinationId: z - .string() - .describe("ID of the backup destination (S3, local storage, etc.) where backups will be stored."), - serviceType: z - .enum([ - "application", - "postgres", - "mysql", - "mariadb", - "mongo", - "redis", - "compose", - ]) - .optional() - .describe("Type of the service that owns the volume."), - appName: z - .string() - .optional() - .describe("Name of the application (used for labeling and identification)."), - serviceName: z - .string() - .nullable() - .optional() - .describe("Name of the service within a compose deployment."), - turnOff: z - .boolean() - .optional() - .describe("Whether to stop the service during backup to ensure data consistency."), - keepLatestCount: z - .number() - .nullable() - .optional() - .describe("Number of most recent backups to retain. Older backups are automatically deleted."), - enabled: z - .boolean() - .nullable() - .optional() - .describe("Whether the backup schedule is enabled. Defaults to true if not specified."), - applicationId: z - .string() - .nullable() - .optional() - .describe("ID of the application. Required when serviceType is 'application'."), - postgresId: z - .string() - .nullable() - .optional() - .describe("ID of the PostgreSQL service. Required when serviceType is 'postgres'."), - mariadbId: z - .string() - .nullable() - .optional() - .describe("ID of the MariaDB service. Required when serviceType is 'mariadb'."), - mongoId: z - .string() - .nullable() - .optional() - .describe("ID of the MongoDB service. Required when serviceType is 'mongo'."), - mysqlId: z - .string() - .nullable() - .optional() - .describe("ID of the MySQL service. Required when serviceType is 'mysql'."), - redisId: z - .string() - .nullable() - .optional() - .describe("ID of the Redis service. Required when serviceType is 'redis'."), - composeId: z - .string() - .nullable() - .optional() - .describe("ID of the compose deployment. Required when serviceType is 'compose'."), - createdAt: z - .string() - .optional() - .describe("Creation timestamp. Automatically set if not provided."), - }), - annotations: { - title: "Create Volume Backup", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/volumeBackups.create", input); - - return ResponseFormatter.success( - `Volume backup configuration "${input.name}" created successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/backup/volumeBackupList.ts b/src/mcp/tools/backup/volumeBackupList.ts deleted file mode 100644 index 09a21b1..0000000 --- a/src/mcp/tools/backup/volumeBackupList.ts +++ /dev/null @@ -1,50 +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 volumeBackupList = createTool({ - name: "volume-backup-list", - description: - "Lists all volume backup configurations for a specific resource in Dokploy. Filter by resource ID and type.", - schema: z.object({ - id: z - .string() - .min(1) - .describe("The ID of the resource (application, database, or compose deployment) to list volume backups for."), - volumeBackupType: z - .enum([ - "application", - "postgres", - "mysql", - "mariadb", - "mongo", - "redis", - "compose", - ]) - .describe("The type of resource: 'application', 'postgres', 'mysql', 'mariadb', 'mongo', 'redis', or 'compose'."), - }), - annotations: { - title: "List Volume Backups", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const volumeBackups = await apiClient.get( - `/volumeBackups.list?id=${encodeURIComponent(input.id)}&volumeBackupType=${encodeURIComponent(input.volumeBackupType)}`, - ); - - if (!volumeBackups?.data) { - return ResponseFormatter.error( - "Failed to fetch volume backups", - "No volume backups found", - ); - } - - return ResponseFormatter.success( - `Successfully fetched volume backups`, - volumeBackups.data, - ); - }, -}); diff --git a/src/mcp/tools/backup/volumeBackupOne.ts b/src/mcp/tools/backup/volumeBackupOne.ts deleted file mode 100644 index 8f47b05..0000000 --- a/src/mcp/tools/backup/volumeBackupOne.ts +++ /dev/null @@ -1,39 +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 volumeBackupOne = createTool({ - name: "volume-backup-one", - description: - "Retrieves details of a specific volume backup configuration by its ID. Returns backup schedule, volume name, destination, and status.", - schema: z.object({ - volumeBackupId: z - .string() - .min(1) - .describe("The unique ID of the volume backup configuration to retrieve."), - }), - annotations: { - title: "Get Volume Backup Details", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const volumeBackup = await apiClient.get( - `/volumeBackups.one?volumeBackupId=${encodeURIComponent(input.volumeBackupId)}`, - ); - - if (!volumeBackup?.data) { - return ResponseFormatter.error( - "Failed to fetch volume backup", - `Volume backup with ID "${input.volumeBackupId}" not found`, - ); - } - - return ResponseFormatter.success( - `Successfully fetched volume backup "${input.volumeBackupId}"`, - volumeBackup.data, - ); - }, -}); diff --git a/src/mcp/tools/backup/volumeBackupRemove.ts b/src/mcp/tools/backup/volumeBackupRemove.ts deleted file mode 100644 index 18b9851..0000000 --- a/src/mcp/tools/backup/volumeBackupRemove.ts +++ /dev/null @@ -1,30 +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 volumeBackupRemove = createTool({ - name: "volume-backup-remove", - description: - "Permanently deletes a volume backup configuration from Dokploy. This stops scheduled backups but does not delete existing backup files.", - schema: z.object({ - volumeBackupId: z - .string() - .min(1) - .describe("The unique ID of the volume backup configuration to delete."), - }), - annotations: { - title: "Remove Volume Backup", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/volumeBackups.delete", input); - - return ResponseFormatter.success( - `Volume backup "${input.volumeBackupId}" deleted successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/backup/volumeBackupRunManual.ts b/src/mcp/tools/backup/volumeBackupRunManual.ts deleted file mode 100644 index e31ade5..0000000 --- a/src/mcp/tools/backup/volumeBackupRunManual.ts +++ /dev/null @@ -1,30 +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 volumeBackupRunManual = createTool({ - name: "volume-backup-run-manual", - description: - "Triggers an immediate manual volume backup using an existing backup configuration. Runs the backup outside of the scheduled cron time.", - schema: z.object({ - volumeBackupId: z - .string() - .min(1) - .describe("The unique ID of the volume backup configuration to execute."), - }), - annotations: { - title: "Run Volume Backup Manually", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/volumeBackups.runManually", input); - - return ResponseFormatter.success( - `Manual volume backup triggered successfully for "${input.volumeBackupId}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/backup/volumeBackupUpdate.ts b/src/mcp/tools/backup/volumeBackupUpdate.ts deleted file mode 100644 index 3dfb8de..0000000 --- a/src/mcp/tools/backup/volumeBackupUpdate.ts +++ /dev/null @@ -1,119 +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 volumeBackupUpdate = createTool({ - name: "volume-backup-update", - description: - "Updates an existing volume backup configuration in Dokploy. Allows modifying schedule, destination, retention settings, and other backup parameters.", - schema: z.object({ - volumeBackupId: z - .string() - .min(1) - .describe("The unique ID of the volume backup configuration to update."), - name: z - .string() - .describe("Descriptive name for the volume backup configuration."), - volumeName: z - .string() - .describe("Name of the Docker volume to backup."), - prefix: z - .string() - .describe("Prefix for backup file names. Used to identify and organize backup files."), - cronExpression: z - .string() - .describe("Cron expression for scheduling backups (e.g., '0 2 * * *' for daily at 2 AM)."), - destinationId: z - .string() - .describe("ID of the backup destination (S3, local storage, etc.) where backups will be stored."), - serviceType: z - .enum([ - "application", - "postgres", - "mysql", - "mariadb", - "mongo", - "redis", - "compose", - ]) - .optional() - .describe("Type of the service that owns the volume."), - appName: z - .string() - .optional() - .describe("Name of the application (used for labeling and identification)."), - serviceName: z - .string() - .nullable() - .optional() - .describe("Name of the service within a compose deployment."), - turnOff: z - .boolean() - .optional() - .describe("Whether to stop the service during backup to ensure data consistency."), - keepLatestCount: z - .number() - .nullable() - .optional() - .describe("Number of most recent backups to retain. Older backups are automatically deleted."), - enabled: z - .boolean() - .nullable() - .optional() - .describe("Whether the backup schedule is enabled."), - applicationId: z - .string() - .nullable() - .optional() - .describe("ID of the application. Required when serviceType is 'application'."), - postgresId: z - .string() - .nullable() - .optional() - .describe("ID of the PostgreSQL service. Required when serviceType is 'postgres'."), - mariadbId: z - .string() - .nullable() - .optional() - .describe("ID of the MariaDB service. Required when serviceType is 'mariadb'."), - mongoId: z - .string() - .nullable() - .optional() - .describe("ID of the MongoDB service. Required when serviceType is 'mongo'."), - mysqlId: z - .string() - .nullable() - .optional() - .describe("ID of the MySQL service. Required when serviceType is 'mysql'."), - redisId: z - .string() - .nullable() - .optional() - .describe("ID of the Redis service. Required when serviceType is 'redis'."), - composeId: z - .string() - .nullable() - .optional() - .describe("ID of the compose deployment. Required when serviceType is 'compose'."), - createdAt: z - .string() - .optional() - .describe("Creation timestamp."), - }), - annotations: { - title: "Update Volume Backup", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/volumeBackups.update", input); - - return ResponseFormatter.success( - `Volume backup "${input.volumeBackupId}" updated successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/certificates/certificatesAll.ts b/src/mcp/tools/certificates/certificatesAll.ts deleted file mode 100644 index c8d08ae..0000000 --- a/src/mcp/tools/certificates/certificatesAll.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 certificatesAll = createTool({ - name: "certificates-all", - description: "Lists all SSL/TLS certificates in Dokploy.", - schema: z.object({}), - annotations: { - title: "List All Certificates", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const certificates = await apiClient.get("/certificates.all"); - - if (!certificates?.data) { - return ResponseFormatter.error( - "Failed to fetch certificates", - "No certificates found", - ); - } - - return ResponseFormatter.success( - `Successfully fetched all certificates`, - certificates.data, - ); - }, -}); diff --git a/src/mcp/tools/certificates/certificatesCreate.ts b/src/mcp/tools/certificates/certificatesCreate.ts deleted file mode 100644 index 9f782d1..0000000 --- a/src/mcp/tools/certificates/certificatesCreate.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 certificatesCreate = createTool({ - name: "certificates-create", - description: "Creates a new SSL/TLS certificate in Dokploy.", - schema: z.object({ - name: z - .string() - .min(1) - .describe("Display name for the certificate. Required."), - certificateData: z - .string() - .min(1) - .describe( - "The SSL/TLS certificate in PEM format. Include the full chain if applicable. Required." - ), - privateKey: z - .string() - .min(1) - .describe("The private key in PEM format. Must match the certificate. Required."), - organizationId: z - .string() - .describe("The organization ID to associate this certificate with. Required."), - certificateId: z - .string() - .optional() - .describe("Custom ID for the certificate. Auto-generated if not provided."), - certificatePath: z - .string() - .optional() - .describe("File path where the certificate will be stored on the server."), - autoRenew: z - .boolean() - .nullable() - .optional() - .describe("Whether to enable automatic certificate renewal."), - serverId: z - .string() - .nullable() - .optional() - .describe("Server ID to deploy this certificate to. Deploys to all servers if not specified."), - }), - annotations: { - title: "Create Certificate", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/certificates.create", input); - - return ResponseFormatter.success( - `Certificate "${input.name}" created successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/certificates/certificatesOne.ts b/src/mcp/tools/certificates/certificatesOne.ts deleted file mode 100644 index 4dd2a22..0000000 --- a/src/mcp/tools/certificates/certificatesOne.ts +++ /dev/null @@ -1,38 +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 certificatesOne = createTool({ - name: "certificates-one", - description: "Gets a specific SSL/TLS certificate by its ID in Dokploy.", - schema: z.object({ - certificateId: z - .string() - .min(1) - .describe("The unique identifier of the certificate to retrieve. Required."), - }), - annotations: { - title: "Get Certificate Details", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const certificate = await apiClient.get( - `/certificates.one?certificateId=${encodeURIComponent(input.certificateId)}`, - ); - - if (!certificate?.data) { - return ResponseFormatter.error( - "Failed to fetch certificate", - `Certificate with ID "${input.certificateId}" not found`, - ); - } - - return ResponseFormatter.success( - `Successfully fetched certificate "${input.certificateId}"`, - certificate.data, - ); - }, -}); diff --git a/src/mcp/tools/certificates/certificatesRemove.ts b/src/mcp/tools/certificates/certificatesRemove.ts deleted file mode 100644 index 70475cd..0000000 --- a/src/mcp/tools/certificates/certificatesRemove.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 certificatesRemove = createTool({ - name: "certificates-remove", - description: "Removes/deletes an SSL/TLS certificate from Dokploy.", - schema: z.object({ - certificateId: z - .string() - .min(1) - .describe("The unique identifier of the certificate to remove. Required."), - }), - annotations: { - title: "Remove Certificate", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/certificates.remove", input); - - return ResponseFormatter.success( - `Certificate "${input.certificateId}" removed successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/certificates/index.ts b/src/mcp/tools/certificates/index.ts deleted file mode 100644 index 679a602..0000000 --- a/src/mcp/tools/certificates/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { certificatesCreate } from "./certificatesCreate.js"; -export { certificatesOne } from "./certificatesOne.js"; -export { certificatesRemove } from "./certificatesRemove.js"; -export { certificatesAll } from "./certificatesAll.js"; diff --git a/src/mcp/tools/cluster/clusterAddManager.ts b/src/mcp/tools/cluster/clusterAddManager.ts deleted file mode 100644 index 68a9fd4..0000000 --- a/src/mcp/tools/cluster/clusterAddManager.ts +++ /dev/null @@ -1,38 +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 clusterAddManager = createTool({ - name: "cluster-add-manager", - description: - "Gets the command to add a manager node to the cluster in Dokploy.", - schema: z.object({ - serverId: z - .string() - .optional() - .describe("The ID of the server to add the manager to."), - }), - annotations: { - title: "Add Cluster Manager", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const params = new URLSearchParams(); - if (input.serverId) { - params.append("serverId", input.serverId); - } - - const queryString = params.toString(); - const url = `/cluster.addManager${queryString ? `?${queryString}` : ""}`; - - const response = await apiClient.get(url); - - return ResponseFormatter.success( - "Successfully retrieved add manager command", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/cluster/clusterAddWorker.ts b/src/mcp/tools/cluster/clusterAddWorker.ts deleted file mode 100644 index 414bf13..0000000 --- a/src/mcp/tools/cluster/clusterAddWorker.ts +++ /dev/null @@ -1,38 +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 clusterAddWorker = createTool({ - name: "cluster-add-worker", - description: - "Gets the command to add a worker node to the cluster in Dokploy.", - schema: z.object({ - serverId: z - .string() - .optional() - .describe("The ID of the server to add the worker to."), - }), - annotations: { - title: "Add Cluster Worker", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const params = new URLSearchParams(); - if (input.serverId) { - params.append("serverId", input.serverId); - } - - const queryString = params.toString(); - const url = `/cluster.addWorker${queryString ? `?${queryString}` : ""}`; - - const response = await apiClient.get(url); - - return ResponseFormatter.success( - "Successfully retrieved add worker command", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/cluster/clusterGetNodes.ts b/src/mcp/tools/cluster/clusterGetNodes.ts deleted file mode 100644 index 10b059e..0000000 --- a/src/mcp/tools/cluster/clusterGetNodes.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 clusterGetNodes = createTool({ - name: "cluster-get-nodes", - description: "Gets all nodes in the cluster from Dokploy.", - schema: z.object({ - serverId: z - .string() - .optional() - .describe("The ID of the server to get cluster nodes from."), - }), - annotations: { - title: "Get Cluster Nodes", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const params = new URLSearchParams(); - if (input.serverId) { - params.append("serverId", input.serverId); - } - - const queryString = params.toString(); - const url = `/cluster.getNodes${queryString ? `?${queryString}` : ""}`; - - const response = await apiClient.get(url); - - return ResponseFormatter.success( - "Successfully fetched cluster nodes", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/cluster/clusterRemoveWorker.ts b/src/mcp/tools/cluster/clusterRemoveWorker.ts deleted file mode 100644 index c5ea1ff..0000000 --- a/src/mcp/tools/cluster/clusterRemoveWorker.ts +++ /dev/null @@ -1,36 +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 clusterRemoveWorker = createTool({ - name: "cluster-remove-worker", - description: "Removes a worker node from the cluster in Dokploy.", - schema: z.object({ - nodeId: z.string().describe("The ID of the worker node to remove."), - serverId: z - .string() - .optional() - .describe("The ID of the server to remove the worker from."), - }), - annotations: { - title: "Remove Cluster Worker", - readOnlyHint: false, - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const body: Record = { nodeId: input.nodeId }; - if (input.serverId) { - body.serverId = input.serverId; - } - - const response = await apiClient.post("/cluster.removeWorker", body); - - return ResponseFormatter.success( - `Successfully removed worker node "${input.nodeId}" from cluster`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/cluster/index.ts b/src/mcp/tools/cluster/index.ts deleted file mode 100644 index 468bf51..0000000 --- a/src/mcp/tools/cluster/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { clusterGetNodes } from "./clusterGetNodes.js"; -export { clusterRemoveWorker } from "./clusterRemoveWorker.js"; -export { clusterAddWorker } from "./clusterAddWorker.js"; -export { clusterAddManager } from "./clusterAddManager.js"; diff --git a/src/mcp/tools/compose/composeCancelDeployment.ts b/src/mcp/tools/compose/composeCancelDeployment.ts deleted file mode 100644 index 2dcba64..0000000 --- a/src/mcp/tools/compose/composeCancelDeployment.ts +++ /dev/null @@ -1,30 +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 composeCancelDeployment = createTool({ - name: "compose-cancelDeployment", - description: - "Cancels an ongoing deployment for a Docker Compose project in Dokploy.", - schema: z.object({ - composeId: z - .string() - .min(1) - .describe("The ID of the compose project to cancel deployment for."), - }), - annotations: { - title: "Cancel Compose Deployment", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/compose.cancelDeployment", input); - - return ResponseFormatter.success( - `Deployment for compose project "${input.composeId}" cancelled successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/compose/composeCleanQueues.ts b/src/mcp/tools/compose/composeCleanQueues.ts deleted file mode 100644 index f025c07..0000000 --- a/src/mcp/tools/compose/composeCleanQueues.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 composeCleanQueues = createTool({ - name: "compose-cleanQueues", - description: "Cleans the deployment queues for a compose project.", - schema: z.object({ - composeId: z - .string() - .min(1) - .describe("The ID of the compose project to clean queues for."), - }), - annotations: { - title: "Clean Compose Queues", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/compose.cleanQueues", input); - - return ResponseFormatter.success( - `Queues cleaned successfully for compose project "${input.composeId}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/compose/composeCreate.ts b/src/mcp/tools/compose/composeCreate.ts deleted file mode 100644 index ffba53b..0000000 --- a/src/mcp/tools/compose/composeCreate.ts +++ /dev/null @@ -1,53 +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 composeCreate = createTool({ - name: "compose-create", - description: "Creates a new Docker Compose project in Dokploy.", - schema: z.object({ - name: z.string().min(1).describe("The name of the compose project."), - description: z - .string() - .nullable() - .optional() - .describe("An optional description for the compose project."), - environmentId: z - .string() - .describe( - "The ID of the environment where the compose project will be created.", - ), - composeType: z - .enum(["docker-compose", "stack"]) - .optional() - .describe("The type of compose deployment (docker-compose or stack)."), - appName: z - .string() - .optional() - .describe("The app name for the compose project."), - serverId: z - .string() - .nullable() - .optional() - .describe("The ID of the server where the compose will be deployed."), - composeFile: z - .string() - .optional() - .describe("The initial compose file content."), - }), - annotations: { - title: "Create Compose Project", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/compose.create", input); - - return ResponseFormatter.success( - `Compose project "${input.name}" created successfully in environment "${input.environmentId}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/compose/composeDelete.ts b/src/mcp/tools/compose/composeDelete.ts deleted file mode 100644 index d4db373..0000000 --- a/src/mcp/tools/compose/composeDelete.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 composeDelete = createTool({ - name: "compose-delete", - description: "Deletes a Docker Compose project in Dokploy.", - schema: z.object({ - composeId: z - .string() - .min(1) - .describe("The ID of the compose project to delete."), - deleteVolumes: z - .boolean() - .describe("Whether to delete associated volumes."), - }), - annotations: { - title: "Delete Compose Project", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/compose.delete", input); - - return ResponseFormatter.success( - `Compose project "${input.composeId}" deleted successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/compose/composeDeploy.ts b/src/mcp/tools/compose/composeDeploy.ts deleted file mode 100644 index 7e857db..0000000 --- a/src/mcp/tools/compose/composeDeploy.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 composeDeploy = createTool({ - name: "compose-deploy", - description: "Deploys a Docker Compose project in Dokploy.", - schema: z.object({ - composeId: z - .string() - .min(1) - .describe("The ID of the compose project 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 Compose Project", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/compose.deploy", input); - - return ResponseFormatter.success( - `Compose project "${input.composeId}" deployment started successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/compose/composeDeployTemplate.ts b/src/mcp/tools/compose/composeDeployTemplate.ts deleted file mode 100644 index 8f9d14c..0000000 --- a/src/mcp/tools/compose/composeDeployTemplate.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 composeDeployTemplate = createTool({ - name: "compose-deployTemplate", - description: "Deploys a compose project from a template.", - schema: z.object({ - environmentId: z - .string() - .describe("The ID of the environment to deploy the template to."), - id: z.string().describe("The ID of the template to deploy."), - serverId: z - .string() - .optional() - .describe("The ID of the server to deploy to."), - baseUrl: z - .string() - .optional() - .describe("Base URL for template repository."), - }), - annotations: { - title: "Deploy Compose Template", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/compose.deployTemplate", input); - - return ResponseFormatter.success( - `Template "${input.id}" deployed successfully to environment "${input.environmentId}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/compose/composeDisconnectGitProvider.ts b/src/mcp/tools/compose/composeDisconnectGitProvider.ts deleted file mode 100644 index 62bfff9..0000000 --- a/src/mcp/tools/compose/composeDisconnectGitProvider.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 composeDisconnectGitProvider = createTool({ - name: "compose-disconnectGitProvider", - description: "Disconnects the Git provider from a compose project.", - schema: z.object({ - composeId: z - .string() - .min(1) - .describe( - "The ID of the compose project to disconnect Git provider from.", - ), - }), - annotations: { - title: "Disconnect Compose Git Provider", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post( - "/compose.disconnectGitProvider", - input, - ); - - return ResponseFormatter.success( - `Git provider disconnected successfully for compose project "${input.composeId}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/compose/composeFetchSourceType.ts b/src/mcp/tools/compose/composeFetchSourceType.ts deleted file mode 100644 index 8bd1aaf..0000000 --- a/src/mcp/tools/compose/composeFetchSourceType.ts +++ /dev/null @@ -1,30 +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 composeFetchSourceType = createTool({ - name: "compose-fetchSourceType", - description: - "Fetches and updates the source type for a compose project based on its configuration.", - schema: z.object({ - composeId: z - .string() - .min(1) - .describe("The ID of the compose project to fetch source type for."), - }), - annotations: { - title: "Fetch Compose Source Type", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/compose.fetchSourceType", input); - - return ResponseFormatter.success( - `Source type fetched successfully for compose project "${input.composeId}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/compose/composeGetConvertedCompose.ts b/src/mcp/tools/compose/composeGetConvertedCompose.ts deleted file mode 100644 index 627036d..0000000 --- a/src/mcp/tools/compose/composeGetConvertedCompose.ts +++ /dev/null @@ -1,36 +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 composeGetConvertedCompose = createTool({ - name: "compose-getConvertedCompose", - description: - "Gets the converted/processed compose file with environment variables substituted.", - schema: z.object({ - composeId: z.string().min(1).describe("The ID of the compose project."), - }), - annotations: { - title: "Get Converted Compose File", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const converted = await apiClient.get( - `/compose.getConvertedCompose?composeId=${input.composeId}`, - ); - - if (!converted?.data) { - return ResponseFormatter.error( - "Failed to get converted compose", - `Could not get converted compose file for project "${input.composeId}"`, - ); - } - - return ResponseFormatter.success( - `Successfully retrieved converted compose file for project "${input.composeId}"`, - converted.data, - ); - }, -}); diff --git a/src/mcp/tools/compose/composeGetDefaultCommand.ts b/src/mcp/tools/compose/composeGetDefaultCommand.ts deleted file mode 100644 index 041ce15..0000000 --- a/src/mcp/tools/compose/composeGetDefaultCommand.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 composeGetDefaultCommand = createTool({ - name: "compose-getDefaultCommand", - description: "Gets the default docker-compose command for a compose project.", - schema: z.object({ - composeId: z.string().min(1).describe("The ID of the compose project."), - }), - annotations: { - title: "Get Default Compose Command", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const command = await apiClient.get( - `/compose.getDefaultCommand?composeId=${input.composeId}`, - ); - - if (!command?.data) { - return ResponseFormatter.error( - "Failed to get default command", - `Could not get default command for compose project "${input.composeId}"`, - ); - } - - return ResponseFormatter.success( - `Successfully retrieved default command for compose project "${input.composeId}"`, - command.data, - ); - }, -}); diff --git a/src/mcp/tools/compose/composeGetTags.ts b/src/mcp/tools/compose/composeGetTags.ts deleted file mode 100644 index f96f1e4..0000000 --- a/src/mcp/tools/compose/composeGetTags.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 composeGetTags = createTool({ - name: "compose-getTags", - description: "Gets the list of available tags for compose templates.", - schema: z.object({ - baseUrl: z - .string() - .optional() - .describe("Optional base URL for template repository."), - }), - annotations: { - title: "Get Compose Template Tags", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const params = new URLSearchParams(); - if (input.baseUrl) { - params.append("baseUrl", input.baseUrl); - } - - const queryString = params.toString(); - const url = queryString - ? `/compose.getTags?${queryString}` - : "/compose.getTags"; - - const tags = await apiClient.get(url); - - if (!tags?.data) { - return ResponseFormatter.error( - "Failed to fetch tags", - "Could not retrieve compose template tags", - ); - } - - return ResponseFormatter.success( - "Successfully retrieved compose template tags", - tags.data, - ); - }, -}); diff --git a/src/mcp/tools/compose/composeImport.ts b/src/mcp/tools/compose/composeImport.ts deleted file mode 100644 index 927d733..0000000 --- a/src/mcp/tools/compose/composeImport.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 composeImport = createTool({ - name: "compose-import", - description: "Imports a compose file from base64-encoded content.", - schema: z.object({ - composeId: z.string().min(1).describe("The ID of the compose project."), - base64: z - .string() - .describe("Base64-encoded compose file content to import."), - }), - annotations: { - title: "Import Compose File", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/compose.import", input); - - return ResponseFormatter.success( - `Compose file imported successfully for project "${input.composeId}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/compose/composeIsolatedDeployment.ts b/src/mcp/tools/compose/composeIsolatedDeployment.ts deleted file mode 100644 index 303c55f..0000000 --- a/src/mcp/tools/compose/composeIsolatedDeployment.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 composeIsolatedDeployment = createTool({ - name: "compose-isolatedDeployment", - description: "Creates an isolated deployment of a compose project.", - schema: z.object({ - composeId: z - .string() - .min(1) - .describe("The ID of the compose project for isolated deployment."), - suffix: z - .string() - .optional() - .describe("Optional suffix for the isolated deployment."), - }), - annotations: { - title: "Isolated Compose Deployment", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/compose.isolatedDeployment", input); - - return ResponseFormatter.success( - `Isolated deployment started for compose project "${input.composeId}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/compose/composeKillBuild.ts b/src/mcp/tools/compose/composeKillBuild.ts deleted file mode 100644 index 1acae44..0000000 --- a/src/mcp/tools/compose/composeKillBuild.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 composeKillBuild = createTool({ - name: "compose-killBuild", - description: "Kills an ongoing build process for a compose project.", - schema: z.object({ - composeId: z - .string() - .min(1) - .describe("The ID of the compose project to kill build for."), - }), - annotations: { - title: "Kill Compose Build", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/compose.killBuild", input); - - return ResponseFormatter.success( - `Build killed successfully for compose project "${input.composeId}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/compose/composeLoadMountsByService.ts b/src/mcp/tools/compose/composeLoadMountsByService.ts deleted file mode 100644 index e944fbb..0000000 --- a/src/mcp/tools/compose/composeLoadMountsByService.ts +++ /dev/null @@ -1,44 +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 composeLoadMountsByService = createTool({ - name: "compose-loadMountsByService", - description: - "Loads the mounts/volumes for a specific service in a Docker Compose project.", - schema: z.object({ - composeId: z.string().min(1).describe("The ID of the compose project."), - serviceName: z - .string() - .min(1) - .describe("The name of the service to load mounts for."), - }), - annotations: { - title: "Load Compose Service Mounts", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const params = new URLSearchParams(); - params.append("composeId", input.composeId); - params.append("serviceName", input.serviceName); - - const mounts = await apiClient.get( - `/compose.loadMountsByService?${params.toString()}`, - ); - - if (!mounts?.data) { - return ResponseFormatter.error( - "Failed to load mounts", - `Could not load mounts for service "${input.serviceName}" in compose project "${input.composeId}"`, - ); - } - - return ResponseFormatter.success( - `Successfully loaded mounts for service "${input.serviceName}" in compose project "${input.composeId}"`, - mounts.data, - ); - }, -}); diff --git a/src/mcp/tools/compose/composeLoadServices.ts b/src/mcp/tools/compose/composeLoadServices.ts deleted file mode 100644 index 4de880f..0000000 --- a/src/mcp/tools/compose/composeLoadServices.ts +++ /dev/null @@ -1,51 +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 composeLoadServices = createTool({ - name: "compose-loadServices", - description: - "Loads and lists the services defined in a Docker Compose project.", - schema: z.object({ - composeId: z - .string() - .min(1) - .describe("The ID of the compose project to load services from."), - type: z - .enum(["fetch", "cache"]) - .optional() - .describe( - "Whether to fetch fresh data or use cached data. Defaults to 'cache'.", - ), - }), - annotations: { - title: "Load Compose Services", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const params = new URLSearchParams(); - params.append("composeId", input.composeId); - if (input.type) { - params.append("type", input.type); - } - - const services = await apiClient.get( - `/compose.loadServices?${params.toString()}`, - ); - - if (!services?.data) { - return ResponseFormatter.error( - "Failed to load services", - `Could not load services for compose project "${input.composeId}"`, - ); - } - - return ResponseFormatter.success( - `Successfully loaded services for compose project "${input.composeId}"`, - services.data, - ); - }, -}); diff --git a/src/mcp/tools/compose/composeMove.ts b/src/mcp/tools/compose/composeMove.ts deleted file mode 100644 index d139b9e..0000000 --- a/src/mcp/tools/compose/composeMove.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 composeMove = createTool({ - name: "compose-move", - description: "Moves a compose project to a different environment.", - schema: z.object({ - composeId: z - .string() - .describe("The ID of the compose project to move."), - targetEnvironmentId: z - .string() - .describe("The ID of the destination environment."), - }), - annotations: { - title: "Move Compose Project", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/compose.move", input); - - return ResponseFormatter.success( - `Compose project "${input.composeId}" moved to environment "${input.targetEnvironmentId}" successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/compose/composeOne.ts b/src/mcp/tools/compose/composeOne.ts deleted file mode 100644 index 69172fe..0000000 --- a/src/mcp/tools/compose/composeOne.ts +++ /dev/null @@ -1,38 +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 composeOne = createTool({ - name: "compose-one", - description: "Gets a specific Docker Compose project by its ID in Dokploy.", - schema: z.object({ - composeId: z - .string() - .min(1) - .describe("The ID of the compose project to retrieve."), - }), - annotations: { - title: "Get Compose Project Details", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const compose = await apiClient.get( - `/compose.one?composeId=${input.composeId}`, - ); - - if (!compose?.data) { - return ResponseFormatter.error( - "Failed to fetch compose project", - `Compose project with ID "${input.composeId}" not found`, - ); - } - - return ResponseFormatter.success( - `Successfully fetched compose project "${input.composeId}"`, - compose.data, - ); - }, -}); diff --git a/src/mcp/tools/compose/composeProcessTemplate.ts b/src/mcp/tools/compose/composeProcessTemplate.ts deleted file mode 100644 index bb198b0..0000000 --- a/src/mcp/tools/compose/composeProcessTemplate.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 composeProcessTemplate = createTool({ - name: "compose-processTemplate", - description: "Processes a compose template with base64-encoded content.", - schema: z.object({ - composeId: z.string().min(1).describe("The ID of the compose project."), - base64: z.string().describe("Base64-encoded compose template content."), - }), - annotations: { - title: "Process Compose Template", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/compose.processTemplate", input); - - return ResponseFormatter.success( - `Template processed successfully for compose project "${input.composeId}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/compose/composeRandomizeCompose.ts b/src/mcp/tools/compose/composeRandomizeCompose.ts deleted file mode 100644 index 075cfa0..0000000 --- a/src/mcp/tools/compose/composeRandomizeCompose.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 composeRandomizeCompose = createTool({ - name: "compose-randomizeCompose", - description: - "Randomizes the compose project names and identifiers to avoid conflicts.", - schema: z.object({ - composeId: z - .string() - .min(1) - .describe("The ID of the compose project to randomize."), - suffix: z - .string() - .optional() - .describe("Optional suffix to append to randomized names."), - }), - annotations: { - title: "Randomize Compose Names", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/compose.randomizeCompose", input); - - return ResponseFormatter.success( - `Compose project "${input.composeId}" names randomized successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/compose/composeRedeploy.ts b/src/mcp/tools/compose/composeRedeploy.ts deleted file mode 100644 index 4dee548..0000000 --- a/src/mcp/tools/compose/composeRedeploy.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 composeRedeploy = createTool({ - name: "compose-redeploy", - description: "Redeploys a Docker Compose project in Dokploy.", - schema: z.object({ - composeId: z - .string() - .min(1) - .describe("The ID of the compose project 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 Compose Project", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/compose.redeploy", input); - - return ResponseFormatter.success( - `Compose project "${input.composeId}" redeployment started successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/compose/composeRefreshToken.ts b/src/mcp/tools/compose/composeRefreshToken.ts deleted file mode 100644 index f75627f..0000000 --- a/src/mcp/tools/compose/composeRefreshToken.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 composeRefreshToken = createTool({ - name: "compose-refreshToken", - description: "Refreshes the token for a compose project.", - schema: z.object({ - composeId: z - .string() - .min(1) - .describe("The ID of the compose project to refresh token for."), - }), - annotations: { - title: "Refresh Compose Token", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/compose.refreshToken", input); - - return ResponseFormatter.success( - `Token refreshed successfully for compose project "${input.composeId}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/compose/composeStart.ts b/src/mcp/tools/compose/composeStart.ts deleted file mode 100644 index 6364fce..0000000 --- a/src/mcp/tools/compose/composeStart.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 composeStart = createTool({ - name: "compose-start", - description: "Starts a Docker Compose project in Dokploy.", - schema: z.object({ - composeId: z - .string() - .min(1) - .describe("The ID of the compose project to start."), - }), - annotations: { - title: "Start Compose Project", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/compose.start", input); - - return ResponseFormatter.success( - `Compose project "${input.composeId}" started successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/compose/composeStop.ts b/src/mcp/tools/compose/composeStop.ts deleted file mode 100644 index 9d285b0..0000000 --- a/src/mcp/tools/compose/composeStop.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 composeStop = createTool({ - name: "compose-stop", - description: "Stops a Docker Compose project in Dokploy.", - schema: z.object({ - composeId: z - .string() - .min(1) - .describe("The ID of the compose project to stop."), - }), - annotations: { - title: "Stop Compose Project", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/compose.stop", input); - - return ResponseFormatter.success( - `Compose project "${input.composeId}" stopped successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/compose/composeTemplates.ts b/src/mcp/tools/compose/composeTemplates.ts deleted file mode 100644 index 36f3572..0000000 --- a/src/mcp/tools/compose/composeTemplates.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 composeTemplates = createTool({ - name: "compose-templates", - description: "Gets the list of available compose templates.", - schema: z.object({ - baseUrl: z - .string() - .optional() - .describe("Optional base URL for template repository."), - }), - annotations: { - title: "List Compose Templates", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const params = new URLSearchParams(); - if (input.baseUrl) { - params.append("baseUrl", input.baseUrl); - } - - const queryString = params.toString(); - const url = queryString - ? `/compose.templates?${queryString}` - : "/compose.templates"; - - const templates = await apiClient.get(url); - - if (!templates?.data) { - return ResponseFormatter.error( - "Failed to fetch templates", - "Could not retrieve compose templates", - ); - } - - return ResponseFormatter.success( - "Successfully retrieved compose templates", - templates.data, - ); - }, -}); diff --git a/src/mcp/tools/compose/composeUpdate.ts b/src/mcp/tools/compose/composeUpdate.ts deleted file mode 100644 index 0016203..0000000 --- a/src/mcp/tools/compose/composeUpdate.ts +++ /dev/null @@ -1,182 +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 composeUpdate = createTool({ - name: "compose-update", - description: "Updates an existing Docker Compose project in Dokploy.", - schema: z.object({ - composeId: z - .string() - .describe("The ID of the compose project to update."), - name: z - .string() - .min(1) - .optional() - .describe("The new name of the compose project."), - appName: z - .string() - .optional() - .describe("The new app name of the compose project."), - description: z - .string() - .nullable() - .optional() - .describe("The new description for the compose project."), - env: z - .string() - .nullable() - .optional() - .describe("Environment variables for the compose project."), - composeFile: z.string().optional().describe("The compose file content."), - refreshToken: z - .string() - .nullable() - .optional() - .describe("Refresh token for the compose project."), - sourceType: z - .enum(["git", "github", "gitlab", "bitbucket", "gitea", "raw"]) - .optional() - .describe("Source type for the compose project."), - composeType: z - .enum(["docker-compose", "stack"]) - .optional() - .describe("The type of compose deployment."), - repository: z.string().nullable().optional().describe("Repository URL."), - owner: z.string().nullable().optional().describe("Repository owner."), - branch: z.string().nullable().optional().describe("Repository branch."), - autoDeploy: z - .boolean() - .nullable() - .optional() - .describe("Whether to auto-deploy on changes."), - 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."), - gitlabPathNamespace: z - .string() - .nullable() - .optional() - .describe("GitLab path namespace."), - 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."), - 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."), - customGitUrl: z.string().nullable().optional().describe("Custom Git URL."), - customGitBranch: z - .string() - .nullable() - .optional() - .describe("Custom Git branch."), - customGitSSHKeyId: z - .string() - .nullable() - .optional() - .describe("Custom Git SSH key ID."), - command: z.string().optional().describe("Command to run."), - enableSubmodules: z - .boolean() - .optional() - .describe("Whether to enable Git submodules."), - composePath: z - .string() - .min(1) - .optional() - .describe("Path to the compose file."), - suffix: z.string().optional().describe("Suffix for the compose project."), - randomize: z - .boolean() - .optional() - .describe("Whether to randomize the compose project names."), - isolatedDeployment: z - .boolean() - .optional() - .describe("Whether to use isolated deployment."), - isolatedDeploymentsVolume: z - .boolean() - .optional() - .describe("Whether to isolate deployment volumes."), - triggerType: z - .enum(["push", "tag"]) - .nullable() - .optional() - .describe("Trigger type for deployments."), - composeStatus: z - .enum(["idle", "running", "done", "error"]) - .optional() - .describe("Compose project status."), - environmentId: z.string().optional().describe("Environment ID."), - createdAt: z.string().optional().describe("Creation date."), - watchPaths: z - .array(z.string()) - .nullable() - .optional() - .describe("Paths to watch for changes."), - githubId: z - .string() - .nullable() - .optional() - .describe("GitHub integration ID."), - gitlabId: z - .string() - .nullable() - .optional() - .describe("GitLab integration ID."), - bitbucketId: z - .string() - .nullable() - .optional() - .describe("Bitbucket integration ID."), - giteaId: z.string().nullable().optional().describe("Gitea integration ID."), - }), - annotations: { - title: "Update Compose Project", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/compose.update", input); - - return ResponseFormatter.success( - `Compose project "${input.composeId}" updated successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/compose/index.ts b/src/mcp/tools/compose/index.ts deleted file mode 100644 index 07d4874..0000000 --- a/src/mcp/tools/compose/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -export { composeCancelDeployment } from "./composeCancelDeployment.js"; -export { composeCleanQueues } from "./composeCleanQueues.js"; -export { composeCreate } from "./composeCreate.js"; -export { composeDelete } from "./composeDelete.js"; -export { composeDeploy } from "./composeDeploy.js"; -export { composeDeployTemplate } from "./composeDeployTemplate.js"; -export { composeDisconnectGitProvider } from "./composeDisconnectGitProvider.js"; -export { composeFetchSourceType } from "./composeFetchSourceType.js"; -export { composeGetConvertedCompose } from "./composeGetConvertedCompose.js"; -export { composeGetDefaultCommand } from "./composeGetDefaultCommand.js"; -export { composeGetTags } from "./composeGetTags.js"; -export { composeImport } from "./composeImport.js"; -export { composeIsolatedDeployment } from "./composeIsolatedDeployment.js"; -export { composeKillBuild } from "./composeKillBuild.js"; -export { composeLoadMountsByService } from "./composeLoadMountsByService.js"; -export { composeLoadServices } from "./composeLoadServices.js"; -export { composeMove } from "./composeMove.js"; -export { composeOne } from "./composeOne.js"; -export { composeProcessTemplate } from "./composeProcessTemplate.js"; -export { composeRandomizeCompose } from "./composeRandomizeCompose.js"; -export { composeRedeploy } from "./composeRedeploy.js"; -export { composeRefreshToken } from "./composeRefreshToken.js"; -export { composeStart } from "./composeStart.js"; -export { composeStop } from "./composeStop.js"; -export { composeTemplates } from "./composeTemplates.js"; -export { composeUpdate } from "./composeUpdate.js"; diff --git a/src/mcp/tools/database/databaseChangeStatus.ts b/src/mcp/tools/database/databaseChangeStatus.ts deleted file mode 100644 index e961a44..0000000 --- a/src/mcp/tools/database/databaseChangeStatus.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -const dbTypes = ["postgres", "mysql", "mongo", "mariadb", "redis"] as const; -type DbType = (typeof dbTypes)[number]; - -const dbTypeLabels: Record = { - postgres: "PostgreSQL", - mysql: "MySQL", - mongo: "MongoDB", - mariadb: "MariaDB", - redis: "Redis", -}; - -const idParamNames: Record = { - postgres: "postgresId", - mysql: "mysqlId", - mongo: "mongoId", - mariadb: "mariadbId", - redis: "redisId", -}; - -const applicationStatuses = ["idle", "running", "done", "error"] as const; - -export const databaseChangeStatus = createTool({ - name: "database-changeStatus", - description: - "Changes the application status of a database in Dokploy. This updates the status metadata without affecting the actual container state. Supports PostgreSQL, MySQL, MongoDB, MariaDB, and Redis.", - schema: z.object({ - dbType: z.enum(dbTypes).describe("The database type"), - id: z - .string() - .min(1) - .describe("The unique database ID to change status for"), - applicationStatus: z - .enum(applicationStatuses) - .describe("The new application status: idle (not running), running (actively processing), done (completed), or error (failed state)"), - }), - annotations: { - title: "Change Database Status", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const { dbType, id, applicationStatus } = input; - const label = dbTypeLabels[dbType]; - const idParam = idParamNames[dbType]; - - const response = await apiClient.post(`/${dbType}.changeStatus`, { - [idParam]: id, - applicationStatus, - }); - - return ResponseFormatter.success( - `${label} database "${id}" status changed to "${applicationStatus}" successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/database/databaseCreate.ts b/src/mcp/tools/database/databaseCreate.ts deleted file mode 100644 index f471220..0000000 --- a/src/mcp/tools/database/databaseCreate.ts +++ /dev/null @@ -1,146 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -const dbTypes = ["postgres", "mysql", "mongo", "mariadb", "redis"] as const; -type DbType = (typeof dbTypes)[number]; - -const dbTypeLabels: Record = { - postgres: "PostgreSQL", - mysql: "MySQL", - mongo: "MongoDB", - mariadb: "MariaDB", - redis: "Redis", -}; - -const defaultImages: Record = { - postgres: "postgres:15", - mysql: "mysql:8", - mongo: "mongo:15", - mariadb: "mariadb:6", - redis: "redis:8", -}; - -const passwordRegex = /^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/; - -export const databaseCreate = createTool({ - name: "database-create", - description: - "Creates a new database in Dokploy. Supports PostgreSQL, MySQL, MongoDB, MariaDB, and Redis. Each database type has different required fields - see parameter descriptions for details.", - schema: z.object({ - dbType: z.enum(dbTypes).describe("The database type to create"), - name: z - .string() - .min(1) - .describe("Display name for the database in Dokploy"), - appName: z - .string() - .min(1) - .describe("Application identifier used for container naming"), - environmentId: z - .string() - .describe("ID of the environment where the database will be created"), - // Required for postgres, mysql, mariadb - not used for mongo or redis - databaseName: z - .string() - .min(1) - .optional() - .describe( - "Name of the database to create inside the instance (required for postgres, mysql, mariadb)" - ), - // Required for postgres, mysql, mongo, mariadb - not used for redis - databaseUser: z - .string() - .min(1) - .optional() - .describe( - "Username for database access (required for postgres, mysql, mongo, mariadb)" - ), - // Required for all database types - databasePassword: z - .string() - .regex(passwordRegex, "Password contains invalid characters") - .describe( - "Password for database access (required for all database types)" - ), - // Required for mysql, mariadb only - databaseRootPassword: z - .string() - .regex(passwordRegex, "Root password contains invalid characters") - .optional() - .describe("Root password for administrative access (required for mysql, mariadb)"), - // MongoDB specific - optional with default false - replicaSets: z - .boolean() - .nullable() - .optional() - .default(false) - .describe("Enable MongoDB replica sets for high availability (MongoDB only)"), - dockerImage: z - .string() - .optional() - .describe("Docker image to use (defaults vary by database type)"), - description: z - .string() - .nullable() - .optional() - .describe("Optional description for the database"), - serverId: z - .string() - .nullable() - .optional() - .describe("ID of the server where the database will be deployed (null for default server)"), - }), - annotations: { - title: "Create Database", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const { dbType, ...rest } = input; - const label = dbTypeLabels[dbType]; - - // Build the request payload based on database type - const payload: Record = { - name: rest.name, - appName: rest.appName, - environmentId: rest.environmentId, - dockerImage: rest.dockerImage || defaultImages[dbType], - description: rest.description, - serverId: rest.serverId, - }; - - // Add type-specific fields - if (dbType === "postgres") { - payload.databaseName = rest.databaseName; - payload.databaseUser = rest.databaseUser; - payload.databasePassword = rest.databasePassword; - } - - if (dbType === "mysql" || dbType === "mariadb") { - payload.databaseName = rest.databaseName; - payload.databaseUser = rest.databaseUser; - payload.databasePassword = rest.databasePassword; - payload.databaseRootPassword = rest.databaseRootPassword; - } - - if (dbType === "mongo") { - payload.databaseUser = rest.databaseUser; - payload.databasePassword = rest.databasePassword; - payload.replicaSets = rest.replicaSets; - } - - if (dbType === "redis") { - payload.databasePassword = rest.databasePassword; - } - - const response = await apiClient.post(`/${dbType}.create`, payload); - - return ResponseFormatter.success( - `${label} database "${input.name}" created successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/database/databaseDeploy.ts b/src/mcp/tools/database/databaseDeploy.ts deleted file mode 100644 index 7a31afa..0000000 --- a/src/mcp/tools/database/databaseDeploy.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -const dbTypes = ["postgres", "mysql", "mongo", "mariadb", "redis"] as const; -type DbType = (typeof dbTypes)[number]; - -const dbTypeLabels: Record = { - postgres: "PostgreSQL", - mysql: "MySQL", - mongo: "MongoDB", - mariadb: "MariaDB", - redis: "Redis", -}; - -const idParamNames: Record = { - postgres: "postgresId", - mysql: "mysqlId", - mongo: "mongoId", - mariadb: "mariadbId", - redis: "redisId", -}; - -export const databaseDeploy = createTool({ - name: "database-deploy", - description: - "Deploys a database container in Dokploy. This pulls the Docker image and starts the container with the configured settings. Supports PostgreSQL, MySQL, MongoDB, MariaDB, and Redis.", - schema: z.object({ - dbType: z.enum(dbTypes).describe("The database type to deploy"), - id: z - .string() - .min(1) - .describe("The unique database ID to deploy"), - }), - annotations: { - title: "Deploy Database", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const { dbType, id } = input; - const label = dbTypeLabels[dbType]; - const idParam = idParamNames[dbType]; - - const response = await apiClient.post(`/${dbType}.deploy`, { - [idParam]: id, - }); - - return ResponseFormatter.success( - `${label} database "${id}" deployment started successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/database/databaseMove.ts b/src/mcp/tools/database/databaseMove.ts deleted file mode 100644 index 888fce8..0000000 --- a/src/mcp/tools/database/databaseMove.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -const dbTypes = ["postgres", "mysql", "mongo", "mariadb", "redis"] as const; -type DbType = (typeof dbTypes)[number]; - -const dbTypeLabels: Record = { - postgres: "PostgreSQL", - mysql: "MySQL", - mongo: "MongoDB", - mariadb: "MariaDB", - redis: "Redis", -}; - -const idParamNames: Record = { - postgres: "postgresId", - mysql: "mysqlId", - mongo: "mongoId", - mariadb: "mariadbId", - redis: "redisId", -}; - -export const databaseMove = createTool({ - name: "database-move", - description: - "Moves a database to a different environment in Dokploy. This changes the environment association without affecting the running container. Supports PostgreSQL, MySQL, MongoDB, MariaDB, and Redis.", - schema: z.object({ - dbType: z.enum(dbTypes).describe("The database type to move"), - id: z - .string() - .min(1) - .describe("The unique database ID to move"), - targetEnvironmentId: z - .string() - .min(1) - .describe("The ID of the target environment to move the database to"), - }), - annotations: { - title: "Move Database", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const { dbType, id, targetEnvironmentId } = input; - const label = dbTypeLabels[dbType]; - const idParam = idParamNames[dbType]; - - const response = await apiClient.post(`/${dbType}.move`, { - [idParam]: id, - targetEnvironmentId, - }); - - return ResponseFormatter.success( - `${label} database "${id}" moved to environment "${targetEnvironmentId}" successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/database/databaseOne.ts b/src/mcp/tools/database/databaseOne.ts deleted file mode 100644 index 4baf570..0000000 --- a/src/mcp/tools/database/databaseOne.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -const dbTypes = ["postgres", "mysql", "mongo", "mariadb", "redis"] as const; -type DbType = (typeof dbTypes)[number]; - -const dbTypeLabels: Record = { - postgres: "PostgreSQL", - mysql: "MySQL", - mongo: "MongoDB", - mariadb: "MariaDB", - redis: "Redis", -}; - -const idParamNames: Record = { - postgres: "postgresId", - mysql: "mysqlId", - mongo: "mongoId", - mariadb: "mariadbId", - redis: "redisId", -}; - -export const databaseOne = createTool({ - name: "database-one", - description: - "Retrieves detailed information about a specific database by ID. Returns configuration, status, and metadata for PostgreSQL, MySQL, MongoDB, MariaDB, or Redis databases.", - schema: z.object({ - dbType: z.enum(dbTypes).describe("The database type to retrieve"), - id: z - .string() - .min(1) - .describe("The unique database ID (e.g., postgresId, mysqlId, mongoId, mariadbId, or redisId)"), - }), - annotations: { - title: "Get Database", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const { dbType, id } = input; - const label = dbTypeLabels[dbType]; - const idParam = idParamNames[dbType]; - - const response = await apiClient.get(`/${dbType}.one`, { - params: { [idParam]: id }, - }); - - if (!response?.data) { - return ResponseFormatter.error( - `Failed to fetch ${label} database`, - `${label} database with ID "${id}" not found` - ); - } - - return ResponseFormatter.success( - `Successfully fetched ${label} database "${id}"`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/database/databaseRebuild.ts b/src/mcp/tools/database/databaseRebuild.ts deleted file mode 100644 index 5e41b91..0000000 --- a/src/mcp/tools/database/databaseRebuild.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -const dbTypes = ["postgres", "mysql", "mongo", "mariadb", "redis"] as const; -type DbType = (typeof dbTypes)[number]; - -const dbTypeLabels: Record = { - postgres: "PostgreSQL", - mysql: "MySQL", - mongo: "MongoDB", - mariadb: "MariaDB", - redis: "Redis", -}; - -const idParamNames: Record = { - postgres: "postgresId", - mysql: "mysqlId", - mongo: "mongoId", - mariadb: "mariadbId", - redis: "redisId", -}; - -export const databaseRebuild = createTool({ - name: "database-rebuild", - description: - "Rebuilds a database container in Dokploy. This stops the container, pulls the latest image, and recreates it with current configuration. Data in volumes is preserved. Supports PostgreSQL, MySQL, MongoDB, MariaDB, and Redis.", - schema: z.object({ - dbType: z.enum(dbTypes).describe("The database type to rebuild"), - id: z - .string() - .min(1) - .describe("The unique database ID to rebuild"), - }), - annotations: { - title: "Rebuild Database", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const { dbType, id } = input; - const label = dbTypeLabels[dbType]; - const idParam = idParamNames[dbType]; - - const response = await apiClient.post(`/${dbType}.rebuild`, { - [idParam]: id, - }); - - return ResponseFormatter.success( - `${label} database "${id}" rebuild started successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/database/databaseReload.ts b/src/mcp/tools/database/databaseReload.ts deleted file mode 100644 index 1bf8881..0000000 --- a/src/mcp/tools/database/databaseReload.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -const dbTypes = ["postgres", "mysql", "mongo", "mariadb", "redis"] as const; -type DbType = (typeof dbTypes)[number]; - -const dbTypeLabels: Record = { - postgres: "PostgreSQL", - mysql: "MySQL", - mongo: "MongoDB", - mariadb: "MariaDB", - redis: "Redis", -}; - -const idParamNames: Record = { - postgres: "postgresId", - mysql: "mysqlId", - mongo: "mongoId", - mariadb: "mariadbId", - redis: "redisId", -}; - -export const databaseReload = createTool({ - name: "database-reload", - description: - "Reloads a database container in Dokploy by restarting the Docker service. This applies configuration changes without a full rebuild. Supports PostgreSQL, MySQL, MongoDB, MariaDB, and Redis.", - schema: z.object({ - dbType: z.enum(dbTypes).describe("The database type to reload"), - id: z - .string() - .min(1) - .describe("The unique database ID to reload"), - appName: z - .string() - .min(1) - .describe("The application name of the database (container identifier)"), - }), - annotations: { - title: "Reload Database", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const { dbType, id, appName } = input; - const label = dbTypeLabels[dbType]; - const idParam = idParamNames[dbType]; - - const response = await apiClient.post(`/${dbType}.reload`, { - [idParam]: id, - appName, - }); - - return ResponseFormatter.success( - `${label} database "${id}" reloaded successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/database/databaseRemove.ts b/src/mcp/tools/database/databaseRemove.ts deleted file mode 100644 index ae5964f..0000000 --- a/src/mcp/tools/database/databaseRemove.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -const dbTypes = ["postgres", "mysql", "mongo", "mariadb", "redis"] as const; -type DbType = (typeof dbTypes)[number]; - -const dbTypeLabels: Record = { - postgres: "PostgreSQL", - mysql: "MySQL", - mongo: "MongoDB", - mariadb: "MariaDB", - redis: "Redis", -}; - -const idParamNames: Record = { - postgres: "postgresId", - mysql: "mysqlId", - mongo: "mongoId", - mariadb: "mariadbId", - redis: "redisId", -}; - -export const databaseRemove = createTool({ - name: "database-remove", - description: - "Permanently deletes a database from Dokploy. This action cannot be undone and will remove the database container and all associated data. Supports PostgreSQL, MySQL, MongoDB, MariaDB, and Redis.", - schema: z.object({ - dbType: z.enum(dbTypes).describe("The database type to remove"), - id: z - .string() - .min(1) - .describe("The unique database ID to remove"), - }), - annotations: { - title: "Remove Database", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const { dbType, id } = input; - const label = dbTypeLabels[dbType]; - const idParam = idParamNames[dbType]; - - const response = await apiClient.post(`/${dbType}.remove`, { - [idParam]: id, - }); - - return ResponseFormatter.success( - `${label} database "${id}" removed successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/database/databaseSaveEnvironment.ts b/src/mcp/tools/database/databaseSaveEnvironment.ts deleted file mode 100644 index 52e8b0d..0000000 --- a/src/mcp/tools/database/databaseSaveEnvironment.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -const dbTypes = ["postgres", "mysql", "mongo", "mariadb", "redis"] as const; -type DbType = (typeof dbTypes)[number]; - -const dbTypeLabels: Record = { - postgres: "PostgreSQL", - mysql: "MySQL", - mongo: "MongoDB", - mariadb: "MariaDB", - redis: "Redis", -}; - -const idParamNames: Record = { - postgres: "postgresId", - mysql: "mysqlId", - mongo: "mongoId", - mariadb: "mariadbId", - redis: "redisId", -}; - -export const databaseSaveEnvironment = createTool({ - name: "database-saveEnvironment", - description: - "Saves environment variables for a database in Dokploy. Environment variables are passed to the container at runtime. Set to null to clear all environment variables. Supports PostgreSQL, MySQL, MongoDB, MariaDB, and Redis.", - schema: z.object({ - dbType: z.enum(dbTypes).describe("The database type"), - id: z - .string() - .min(1) - .describe("The unique database ID to configure"), - env: z - .string() - .nullable() - .optional() - .describe("Environment variables as a string (KEY=value format, one per line), or null to clear"), - }), - annotations: { - title: "Save Database Environment", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const { dbType, id, env } = input; - const label = dbTypeLabels[dbType]; - const idParam = idParamNames[dbType]; - - const response = await apiClient.post(`/${dbType}.saveEnvironment`, { - [idParam]: id, - env, - }); - - return ResponseFormatter.success( - `Environment variables for ${label} database "${id}" saved successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/database/databaseSaveExternalPort.ts b/src/mcp/tools/database/databaseSaveExternalPort.ts deleted file mode 100644 index c719319..0000000 --- a/src/mcp/tools/database/databaseSaveExternalPort.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -const dbTypes = ["postgres", "mysql", "mongo", "mariadb", "redis"] as const; -type DbType = (typeof dbTypes)[number]; - -const dbTypeLabels: Record = { - postgres: "PostgreSQL", - mysql: "MySQL", - mongo: "MongoDB", - mariadb: "MariaDB", - redis: "Redis", -}; - -const idParamNames: Record = { - postgres: "postgresId", - mysql: "mysqlId", - mongo: "mongoId", - mariadb: "mariadbId", - redis: "redisId", -}; - -export const databaseSaveExternalPort = createTool({ - name: "database-saveExternalPort", - description: - "Saves the external port configuration for a database in Dokploy. This exposes the database on the specified port on the host machine. Set to null to disable external access. Supports PostgreSQL, MySQL, MongoDB, MariaDB, and Redis.", - schema: z.object({ - dbType: z.enum(dbTypes).describe("The database type"), - id: z - .string() - .min(1) - .describe("The unique database ID to configure"), - externalPort: z - .number() - .nullable() - .describe("The external port number to expose the database on (1-65535), or null to disable external access"), - }), - annotations: { - title: "Save Database External Port", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const { dbType, id, externalPort } = input; - const label = dbTypeLabels[dbType]; - const idParam = idParamNames[dbType]; - - const response = await apiClient.post(`/${dbType}.saveExternalPort`, { - [idParam]: id, - externalPort, - }); - - const portMessage = - externalPort !== null - ? `set to port ${externalPort}` - : "disabled (set to null)"; - - return ResponseFormatter.success( - `External port for ${label} database "${id}" ${portMessage} successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/database/databaseStart.ts b/src/mcp/tools/database/databaseStart.ts deleted file mode 100644 index 08dc6e2..0000000 --- a/src/mcp/tools/database/databaseStart.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -const dbTypes = ["postgres", "mysql", "mongo", "mariadb", "redis"] as const; -type DbType = (typeof dbTypes)[number]; - -const dbTypeLabels: Record = { - postgres: "PostgreSQL", - mysql: "MySQL", - mongo: "MongoDB", - mariadb: "MariaDB", - redis: "Redis", -}; - -const idParamNames: Record = { - postgres: "postgresId", - mysql: "mysqlId", - mongo: "mongoId", - mariadb: "mariadbId", - redis: "redisId", -}; - -export const databaseStart = createTool({ - name: "database-start", - description: - "Starts a stopped database container in Dokploy. The database must already be deployed. Supports PostgreSQL, MySQL, MongoDB, MariaDB, and Redis.", - schema: z.object({ - dbType: z.enum(dbTypes).describe("The database type to start"), - id: z - .string() - .min(1) - .describe("The unique database ID to start"), - }), - annotations: { - title: "Start Database", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const { dbType, id } = input; - const label = dbTypeLabels[dbType]; - const idParam = idParamNames[dbType]; - - const response = await apiClient.post(`/${dbType}.start`, { - [idParam]: id, - }); - - return ResponseFormatter.success( - `${label} database "${id}" started successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/database/databaseStop.ts b/src/mcp/tools/database/databaseStop.ts deleted file mode 100644 index 023e30d..0000000 --- a/src/mcp/tools/database/databaseStop.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -const dbTypes = ["postgres", "mysql", "mongo", "mariadb", "redis"] as const; -type DbType = (typeof dbTypes)[number]; - -const dbTypeLabels: Record = { - postgres: "PostgreSQL", - mysql: "MySQL", - mongo: "MongoDB", - mariadb: "MariaDB", - redis: "Redis", -}; - -const idParamNames: Record = { - postgres: "postgresId", - mysql: "mysqlId", - mongo: "mongoId", - mariadb: "mariadbId", - redis: "redisId", -}; - -export const databaseStop = createTool({ - name: "database-stop", - description: - "Stops a running database container in Dokploy. The container will be stopped but not removed. Use database-start to restart it. Supports PostgreSQL, MySQL, MongoDB, MariaDB, and Redis.", - schema: z.object({ - dbType: z.enum(dbTypes).describe("The database type to stop"), - id: z - .string() - .min(1) - .describe("The unique database ID to stop"), - }), - annotations: { - title: "Stop Database", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const { dbType, id } = input; - const label = dbTypeLabels[dbType]; - const idParam = idParamNames[dbType]; - - const response = await apiClient.post(`/${dbType}.stop`, { - [idParam]: id, - }); - - return ResponseFormatter.success( - `${label} database "${id}" stopped successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/database/databaseUpdate.ts b/src/mcp/tools/database/databaseUpdate.ts deleted file mode 100644 index e9e9463..0000000 --- a/src/mcp/tools/database/databaseUpdate.ts +++ /dev/null @@ -1,391 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -const dbTypes = ["postgres", "mysql", "mongo", "mariadb", "redis"] as const; -type DbType = (typeof dbTypes)[number]; - -const dbTypeLabels: Record = { - postgres: "PostgreSQL", - mysql: "MySQL", - mongo: "MongoDB", - mariadb: "MariaDB", - redis: "Redis", -}; - -const idParamNames: Record = { - postgres: "postgresId", - mysql: "mysqlId", - mongo: "mongoId", - mariadb: "mariadbId", - redis: "redisId", -}; - -const defaultImages: Record = { - postgres: "postgres:15", - mysql: "mysql:8", - mongo: "mongo:15", - mariadb: "mariadb:6", - redis: "redis:8", -}; - -const passwordRegex = /^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/; - -// Docker Swarm configuration schemas matching OpenAPI spec -const healthCheckSwarmSchema = 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"); - -const restartPolicySwarmSchema = 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"); - -const placementSwarmSchema = 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 constraints and preferences"); - -const updateConfigSwarmSchema = 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 rolling update configuration"); - -const rollbackConfigSwarmSchema = 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"); - -const modeSwarmSchema = 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 service mode (Replicated, Global, ReplicatedJob, GlobalJob)"); - -const labelsSwarmSchema = z - .record(z.string()) - .nullable() - .optional() - .describe("Docker Swarm service labels as key-value pairs"); - -const networkSwarmSchema = z - .array( - z.object({ - Target: z.string().optional(), - Aliases: z.array(z.string()).optional(), - DriverOpts: z.object({}).optional(), - }) - ) - .nullable() - .optional() - .describe("Docker Swarm network attachment configuration"); - -const endpointSpecSwarmSchema = z - .object({ - Mode: z.string().optional(), - Ports: z - .array( - z.object({ - Protocol: z.string().optional(), - TargetPort: z.number().optional(), - PublishedPort: z.number().optional(), - PublishMode: z.string().optional(), - }) - ) - .optional(), - }) - .nullable() - .optional() - .describe("Docker Swarm endpoint specification for port publishing"); - -export const databaseUpdate = createTool({ - name: "database-update", - description: - "Updates an existing database in Dokploy. Supports PostgreSQL, MySQL, MongoDB, MariaDB, and Redis. Only the database ID is required - all other fields are optional.", - schema: z.object({ - dbType: z.enum(dbTypes).describe("The database type"), - id: z.string().min(1).describe("The database ID to update"), - // Common fields across all database types - name: z - .string() - .min(1) - .optional() - .describe("Display name for the database in Dokploy"), - appName: z - .string() - .min(1) - .optional() - .describe("Application identifier used for container naming"), - description: z - .string() - .nullable() - .optional() - .describe("Description for the database"), - dockerImage: z - .string() - .optional() - .describe("Docker image to use for the database"), - command: z - .string() - .nullable() - .optional() - .describe("Custom command to run the database container"), - env: z - .string() - .nullable() - .optional() - .describe("Environment variables as a string (KEY=value format, one per line)"), - memoryReservation: z - .string() - .nullable() - .optional() - .describe("Memory reservation for the container (e.g., '256m', '1g')"), - memoryLimit: z - .string() - .nullable() - .optional() - .describe("Memory limit for the container (e.g., '512m', '2g')"), - cpuReservation: z - .string() - .nullable() - .optional() - .describe("CPU reservation for the container (e.g., '0.5', '1')"), - cpuLimit: z - .string() - .nullable() - .optional() - .describe("CPU limit for the container (e.g., '1', '2')"), - externalPort: z - .number() - .nullable() - .optional() - .describe("External port to expose the database on the host"), - applicationStatus: z - .enum(["idle", "running", "done", "error"]) - .optional() - .describe("Current application status"), - replicas: z - .number() - .optional() - .describe("Number of container replicas to run"), - createdAt: z - .string() - .optional() - .describe("Creation timestamp (ISO 8601 format)"), - environmentId: z - .string() - .optional() - .describe("ID of the environment the database belongs to"), - // Type-specific fields for postgres, mysql, mariadb - databaseName: z - .string() - .min(1) - .optional() - .describe("Database name inside the instance (postgres, mysql, mariadb)"), - // Type-specific fields for postgres, mysql, mongo, mariadb - databaseUser: z - .string() - .min(1) - .optional() - .describe("Database username (postgres, mysql, mongo, mariadb)"), - databasePassword: z - .string() - .regex(passwordRegex, "Password contains invalid characters") - .optional() - .describe("Database password"), - // Type-specific fields for mysql, mariadb - databaseRootPassword: z - .string() - .regex(passwordRegex, "Root password contains invalid characters") - .optional() - .describe("Root password (mysql, mariadb only)"), - // MongoDB specific - replicaSets: z - .boolean() - .nullable() - .optional() - .describe("Enable replica sets for high availability (MongoDB only)"), - // Docker Swarm configurations - healthCheckSwarm: healthCheckSwarmSchema, - restartPolicySwarm: restartPolicySwarmSchema, - placementSwarm: placementSwarmSchema, - updateConfigSwarm: updateConfigSwarmSchema, - rollbackConfigSwarm: rollbackConfigSwarmSchema, - modeSwarm: modeSwarmSchema, - labelsSwarm: labelsSwarmSchema, - networkSwarm: networkSwarmSchema, - stopGracePeriodSwarm: z - .number() - .int() - .nullable() - .optional() - .describe("Docker Swarm stop grace period in nanoseconds"), - endpointSpecSwarm: endpointSpecSwarmSchema, - }), - annotations: { - title: "Update Database", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const { dbType, id, ...rest } = input; - const label = dbTypeLabels[dbType]; - const idParam = idParamNames[dbType]; - - // Build payload with the correct ID parameter name - const payload: Record = { - [idParam]: id, - }; - - // Add common fields if provided - if (rest.name !== undefined) payload.name = rest.name; - if (rest.appName !== undefined) payload.appName = rest.appName; - if (rest.description !== undefined) payload.description = rest.description; - if (rest.dockerImage !== undefined) - payload.dockerImage = rest.dockerImage || defaultImages[dbType]; - if (rest.command !== undefined) payload.command = rest.command; - if (rest.env !== undefined) payload.env = rest.env; - if (rest.memoryReservation !== undefined) - payload.memoryReservation = rest.memoryReservation; - if (rest.memoryLimit !== undefined) payload.memoryLimit = rest.memoryLimit; - if (rest.cpuReservation !== undefined) - payload.cpuReservation = rest.cpuReservation; - if (rest.cpuLimit !== undefined) payload.cpuLimit = rest.cpuLimit; - if (rest.externalPort !== undefined) payload.externalPort = rest.externalPort; - if (rest.applicationStatus !== undefined) - payload.applicationStatus = rest.applicationStatus; - if (rest.replicas !== undefined) payload.replicas = rest.replicas; - if (rest.createdAt !== undefined) payload.createdAt = rest.createdAt; - if (rest.environmentId !== undefined) - payload.environmentId = rest.environmentId; - - // Add type-specific fields - if ( - dbType === "postgres" || - dbType === "mysql" || - dbType === "mariadb" - ) { - if (rest.databaseName !== undefined) - payload.databaseName = rest.databaseName; - } - - if ( - dbType === "postgres" || - dbType === "mysql" || - dbType === "mongo" || - dbType === "mariadb" - ) { - if (rest.databaseUser !== undefined) - payload.databaseUser = rest.databaseUser; - if (rest.databasePassword !== undefined) - payload.databasePassword = rest.databasePassword; - } - - if (dbType === "mysql" || dbType === "mariadb") { - if (rest.databaseRootPassword !== undefined) - payload.databaseRootPassword = rest.databaseRootPassword; - } - - if (dbType === "mongo") { - if (rest.replicaSets !== undefined) payload.replicaSets = rest.replicaSets; - } - - if (dbType === "redis") { - if (rest.databasePassword !== undefined) - payload.databasePassword = rest.databasePassword; - } - - // Add Swarm configurations if provided - if (rest.healthCheckSwarm !== undefined) - payload.healthCheckSwarm = rest.healthCheckSwarm; - if (rest.restartPolicySwarm !== undefined) - payload.restartPolicySwarm = rest.restartPolicySwarm; - if (rest.placementSwarm !== undefined) - payload.placementSwarm = rest.placementSwarm; - if (rest.updateConfigSwarm !== undefined) - payload.updateConfigSwarm = rest.updateConfigSwarm; - if (rest.rollbackConfigSwarm !== undefined) - payload.rollbackConfigSwarm = rest.rollbackConfigSwarm; - if (rest.modeSwarm !== undefined) payload.modeSwarm = rest.modeSwarm; - if (rest.labelsSwarm !== undefined) payload.labelsSwarm = rest.labelsSwarm; - if (rest.networkSwarm !== undefined) payload.networkSwarm = rest.networkSwarm; - if (rest.stopGracePeriodSwarm !== undefined) - payload.stopGracePeriodSwarm = rest.stopGracePeriodSwarm; - if (rest.endpointSpecSwarm !== undefined) - payload.endpointSpecSwarm = rest.endpointSpecSwarm; - - const response = await apiClient.post(`/${dbType}.update`, payload); - - return ResponseFormatter.success( - `${label} database "${id}" updated successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/database/index.ts b/src/mcp/tools/database/index.ts deleted file mode 100644 index 82318d8..0000000 --- a/src/mcp/tools/database/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -export { databaseCreate } from "./databaseCreate.js"; -export { databaseOne } from "./databaseOne.js"; -export { databaseUpdate } from "./databaseUpdate.js"; -export { databaseRemove } from "./databaseRemove.js"; -export { databaseStart } from "./databaseStart.js"; -export { databaseStop } from "./databaseStop.js"; -export { databaseDeploy } from "./databaseDeploy.js"; -export { databaseReload } from "./databaseReload.js"; -export { databaseRebuild } from "./databaseRebuild.js"; -export { databaseMove } from "./databaseMove.js"; -export { databaseChangeStatus } from "./databaseChangeStatus.js"; -export { databaseSaveExternalPort } from "./databaseSaveExternalPort.js"; -export { databaseSaveEnvironment } from "./databaseSaveEnvironment.js"; diff --git a/src/mcp/tools/deployment/deploymentAll.ts b/src/mcp/tools/deployment/deploymentAll.ts deleted file mode 100644 index a8404e0..0000000 --- a/src/mcp/tools/deployment/deploymentAll.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 deploymentAll = createTool({ - name: "deployment-all", - description: "Gets all deployments for a specific application in Dokploy.", - schema: z.object({ - applicationId: z - .string() - .min(1) - .describe("The ID of the application to get deployments for."), - }), - annotations: { - title: "List Application Deployments", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.get( - `/deployment.all?applicationId=${input.applicationId}` - ); - - return ResponseFormatter.success( - `Successfully fetched deployments for application "${input.applicationId}"`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/deployment/deploymentAllByCompose.ts b/src/mcp/tools/deployment/deploymentAllByCompose.ts deleted file mode 100644 index 2c5bb22..0000000 --- a/src/mcp/tools/deployment/deploymentAllByCompose.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 deploymentAllByCompose = createTool({ - name: "deployment-all-by-compose", - description: "Gets all deployments for a specific compose project in Dokploy.", - schema: z.object({ - composeId: z - .string() - .min(1) - .describe("The ID of the compose project to get deployments for."), - }), - annotations: { - title: "List Compose Deployments", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.get( - `/deployment.allByCompose?composeId=${input.composeId}` - ); - - return ResponseFormatter.success( - `Successfully fetched deployments for compose "${input.composeId}"`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/deployment/deploymentAllByServer.ts b/src/mcp/tools/deployment/deploymentAllByServer.ts deleted file mode 100644 index 99bc360..0000000 --- a/src/mcp/tools/deployment/deploymentAllByServer.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 deploymentAllByServer = createTool({ - name: "deployment-all-by-server", - description: "Gets all deployments for a specific server in Dokploy.", - schema: z.object({ - serverId: z - .string() - .min(1) - .describe("The ID of the server to get deployments for."), - }), - annotations: { - title: "List Server Deployments", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.get( - `/deployment.allByServer?serverId=${input.serverId}` - ); - - return ResponseFormatter.success( - `Successfully fetched deployments for server "${input.serverId}"`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/deployment/deploymentAllByType.ts b/src/mcp/tools/deployment/deploymentAllByType.ts deleted file mode 100644 index c62ce18..0000000 --- a/src/mcp/tools/deployment/deploymentAllByType.ts +++ /dev/null @@ -1,42 +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 deploymentAllByType = createTool({ - name: "deployment-all-by-type", - description: "Gets all deployments filtered by type in Dokploy.", - schema: z.object({ - id: z - .string() - .min(1) - .describe("The ID of the resource to get deployments for."), - type: z - .enum([ - "application", - "compose", - "server", - "schedule", - "previewDeployment", - "backup", - "volumeBackup", - ]) - .describe("The type of deployments to retrieve."), - }), - annotations: { - title: "List Deployments By Type", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.get( - `/deployment.allByType?id=${input.id}&type=${input.type}` - ); - - return ResponseFormatter.success( - `Successfully fetched ${input.type} deployments for ID "${input.id}"`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/deployment/deploymentKillProcess.ts b/src/mcp/tools/deployment/deploymentKillProcess.ts deleted file mode 100644 index e041ac6..0000000 --- a/src/mcp/tools/deployment/deploymentKillProcess.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 deploymentKillProcess = createTool({ - name: "deployment-kill-process", - description: "Kills/cancels a running deployment process in Dokploy.", - schema: z.object({ - deploymentId: z - .string() - .min(1) - .describe("The ID of the deployment to kill."), - }), - annotations: { - title: "Kill Deployment Process", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/deployment.killProcess", input); - - return ResponseFormatter.success( - `Deployment "${input.deploymentId}" process killed successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/deployment/index.ts b/src/mcp/tools/deployment/index.ts deleted file mode 100644 index f797979..0000000 --- a/src/mcp/tools/deployment/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { deploymentAll } from "./deploymentAll.js"; -export { deploymentAllByCompose } from "./deploymentAllByCompose.js"; -export { deploymentAllByServer } from "./deploymentAllByServer.js"; -export { deploymentAllByType } from "./deploymentAllByType.js"; -export { deploymentKillProcess } from "./deploymentKillProcess.js"; diff --git a/src/mcp/tools/destination/destinationAll.ts b/src/mcp/tools/destination/destinationAll.ts deleted file mode 100644 index 7a15aa0..0000000 --- a/src/mcp/tools/destination/destinationAll.ts +++ /dev/null @@ -1,24 +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 destinationAll = createTool({ - name: "destination-all", - description: "Gets all backup destinations in Dokploy.", - schema: z.object({}), - annotations: { - title: "List All Destinations", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const destinations = await apiClient.get("/destination.all"); - - return ResponseFormatter.success( - "Successfully fetched all destinations", - destinations.data, - ); - }, -}); diff --git a/src/mcp/tools/destination/destinationCreate.ts b/src/mcp/tools/destination/destinationCreate.ts deleted file mode 100644 index 5ea7a46..0000000 --- a/src/mcp/tools/destination/destinationCreate.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 destinationCreate = createTool({ - name: "destination-create", - description: - "Creates a new backup destination (S3-compatible storage) in Dokploy.", - schema: z.object({ - name: z - .string() - .min(1) - .describe("Display name for the backup destination. Required."), - provider: z - .string() - .nullable() - .describe( - "Cloud provider name (e.g., 'aws', 'minio', 'backblaze', 'cloudflare'). Can be null for generic S3. Required." - ), - accessKey: z - .string() - .describe("S3-compatible access key ID for authentication. Required."), - secretAccessKey: z - .string() - .describe("S3-compatible secret access key for authentication. Required."), - bucket: z - .string() - .describe("S3 bucket name where backups will be stored. Required."), - region: z - .string() - .describe("S3 region (e.g., 'us-east-1', 'eu-west-1'). Required."), - endpoint: z - .string() - .describe( - "S3-compatible endpoint URL (e.g., 'https://s3.amazonaws.com' or 'https://minio.example.com'). Required." - ), - serverId: z - .string() - .optional() - .describe("Server ID for remote server destinations. Uses local server if not specified."), - }), - annotations: { - title: "Create Backup Destination", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/destination.create", input); - - return ResponseFormatter.success( - `Backup destination "${input.name}" created successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/destination/destinationOne.ts b/src/mcp/tools/destination/destinationOne.ts deleted file mode 100644 index 8e2bd1a..0000000 --- a/src/mcp/tools/destination/destinationOne.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 destinationOne = createTool({ - name: "destination-one", - description: "Gets a specific backup destination by its ID in Dokploy.", - schema: z.object({ - destinationId: z - .string() - .describe("The unique identifier of the backup destination to retrieve. Required."), - }), - annotations: { - title: "Get Destination Details", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const destination = await apiClient.get( - `/destination.one?destinationId=${input.destinationId}`, - ); - - if (!destination?.data) { - return ResponseFormatter.error( - "Failed to fetch destination", - `Destination with ID "${input.destinationId}" not found`, - ); - } - - return ResponseFormatter.success( - `Successfully fetched destination "${input.destinationId}"`, - destination.data, - ); - }, -}); diff --git a/src/mcp/tools/destination/destinationRemove.ts b/src/mcp/tools/destination/destinationRemove.ts deleted file mode 100644 index c6939e0..0000000 --- a/src/mcp/tools/destination/destinationRemove.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 destinationRemove = createTool({ - name: "destination-remove", - description: "Removes/deletes a backup destination from Dokploy.", - schema: z.object({ - destinationId: z - .string() - .describe("The unique identifier of the backup destination to remove. Required."), - }), - annotations: { - title: "Remove Destination", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/destination.remove", input); - - return ResponseFormatter.success( - `Destination "${input.destinationId}" removed successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/destination/destinationTestConnection.ts b/src/mcp/tools/destination/destinationTestConnection.ts deleted file mode 100644 index d03e9c1..0000000 --- a/src/mcp/tools/destination/destinationTestConnection.ts +++ /dev/null @@ -1,56 +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 destinationTestConnection = createTool({ - name: "destination-test-connection", - description: "Tests the connection to a backup destination in Dokploy.", - schema: z.object({ - name: z - .string() - .min(1) - .describe("Display name for the backup destination. Required."), - provider: z - .string() - .nullable() - .describe( - "Cloud provider name (e.g., 'aws', 'minio', 'backblaze', 'cloudflare'). Can be null for generic S3. Required." - ), - accessKey: z - .string() - .describe("S3-compatible access key ID for authentication. Required."), - secretAccessKey: z - .string() - .describe("S3-compatible secret access key for authentication. Required."), - bucket: z - .string() - .describe("S3 bucket name to test connectivity to. Required."), - region: z - .string() - .describe("S3 region (e.g., 'us-east-1', 'eu-west-1'). Required."), - endpoint: z - .string() - .describe( - "S3-compatible endpoint URL to connect to (e.g., 'https://s3.amazonaws.com'). Required." - ), - serverId: z - .string() - .optional() - .describe("Server ID for remote server destinations. Uses local server if not specified."), - }), - annotations: { - title: "Test Destination Connection", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/destination.testConnection", input); - - return ResponseFormatter.success( - "Destination connection test completed successfully", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/destination/destinationUpdate.ts b/src/mcp/tools/destination/destinationUpdate.ts deleted file mode 100644 index e692f05..0000000 --- a/src/mcp/tools/destination/destinationUpdate.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 destinationUpdate = createTool({ - name: "destination-update", - description: "Updates an existing backup destination in Dokploy.", - schema: z.object({ - destinationId: z - .string() - .describe("The ID of the destination to update. Required."), - name: z - .string() - .min(1) - .describe("Display name for the backup destination. Required."), - provider: z - .string() - .nullable() - .describe( - "Cloud provider name (e.g., 'aws', 'minio', 'backblaze', 'cloudflare'). Can be null for generic S3. Required." - ), - accessKey: z - .string() - .describe("S3-compatible access key ID for authentication. Required."), - secretAccessKey: z - .string() - .describe("S3-compatible secret access key for authentication. Required."), - bucket: z - .string() - .describe("S3 bucket name where backups will be stored. Required."), - region: z - .string() - .describe("S3 region (e.g., 'us-east-1', 'eu-west-1'). Required."), - endpoint: z - .string() - .describe( - "S3-compatible endpoint URL (e.g., 'https://s3.amazonaws.com' or 'https://minio.example.com'). Required." - ), - serverId: z - .string() - .optional() - .describe("Server ID for remote server destinations. Uses local server if not specified."), - }), - annotations: { - title: "Update Destination", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/destination.update", input); - - return ResponseFormatter.success( - `Destination "${input.destinationId}" updated successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/destination/index.ts b/src/mcp/tools/destination/index.ts deleted file mode 100644 index f6f2dcb..0000000 --- a/src/mcp/tools/destination/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { destinationCreate } from "./destinationCreate.js"; -export { destinationTestConnection } from "./destinationTestConnection.js"; -export { destinationOne } from "./destinationOne.js"; -export { destinationAll } from "./destinationAll.js"; -export { destinationRemove } from "./destinationRemove.js"; -export { destinationUpdate } from "./destinationUpdate.js"; diff --git a/src/mcp/tools/docker/dockerGetConfig.ts b/src/mcp/tools/docker/dockerGetConfig.ts deleted file mode 100644 index 25bd124..0000000 --- a/src/mcp/tools/docker/dockerGetConfig.ts +++ /dev/null @@ -1,42 +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 dockerGetConfig = createTool({ - name: "docker-get-config", - description: "Gets the configuration of a Docker container in Dokploy.", - schema: z.object({ - containerId: z - .string() - .min(1) - .regex(/^[a-zA-Z0-9.\-_]+$/) - .describe("The ID of the container to get configuration for."), - serverId: z - .string() - .optional() - .describe("The ID of the server where the container is running."), - }), - annotations: { - title: "Get Docker Container Config", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const params = new URLSearchParams(); - params.append("containerId", input.containerId); - if (input.serverId) { - params.append("serverId", input.serverId); - } - - const response = await apiClient.get( - `/docker.getConfig?${params.toString()}`, - ); - - return ResponseFormatter.success( - `Successfully fetched config for container "${input.containerId}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/docker/dockerGetContainers.ts b/src/mcp/tools/docker/dockerGetContainers.ts deleted file mode 100644 index 1624506..0000000 --- a/src/mcp/tools/docker/dockerGetContainers.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 dockerGetContainers = createTool({ - name: "docker-get-containers", - description: "Gets all Docker containers from Dokploy.", - schema: z.object({ - serverId: z - .string() - .optional() - .describe("The ID of the server to get containers from."), - }), - annotations: { - title: "Get Docker Containers", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const params = new URLSearchParams(); - if (input.serverId) { - params.append("serverId", input.serverId); - } - - const queryString = params.toString(); - const url = `/docker.getContainers${queryString ? `?${queryString}` : ""}`; - - const response = await apiClient.get(url); - - return ResponseFormatter.success( - "Successfully fetched Docker containers", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/docker/dockerGetContainersByAppLabel.ts b/src/mcp/tools/docker/dockerGetContainersByAppLabel.ts deleted file mode 100644 index 0e35b01..0000000 --- a/src/mcp/tools/docker/dockerGetContainersByAppLabel.ts +++ /dev/null @@ -1,46 +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 dockerGetContainersByAppLabel = createTool({ - name: "docker-get-containers-by-app-label", - description: "Gets Docker containers by app label in Dokploy.", - schema: z.object({ - appName: z - .string() - .min(1) - .regex(/^[a-zA-Z0-9.\-_]+$/) - .describe("The app name to search for."), - type: z - .enum(["standalone", "swarm"]) - .describe("The deployment type (standalone or swarm)."), - serverId: z - .string() - .optional() - .describe("The ID of the server to search on."), - }), - annotations: { - title: "Get Docker Containers by App Label", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const params = new URLSearchParams(); - params.append("appName", input.appName); - params.append("type", input.type); - if (input.serverId) { - params.append("serverId", input.serverId); - } - - const response = await apiClient.get( - `/docker.getContainersByAppLabel?${params.toString()}`, - ); - - return ResponseFormatter.success( - `Successfully fetched containers for app "${input.appName}" with type "${input.type}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/docker/dockerGetContainersByAppNameMatch.ts b/src/mcp/tools/docker/dockerGetContainersByAppNameMatch.ts deleted file mode 100644 index 45b2116..0000000 --- a/src/mcp/tools/docker/dockerGetContainersByAppNameMatch.ts +++ /dev/null @@ -1,50 +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 dockerGetContainersByAppNameMatch = createTool({ - name: "docker-get-containers-by-app-name-match", - description: - "Gets Docker containers matching an app name pattern in Dokploy.", - schema: z.object({ - appName: z - .string() - .min(1) - .regex(/^[a-zA-Z0-9.\-_]+$/) - .describe("The app name pattern to match containers against."), - appType: z - .enum(["stack", "docker-compose"]) - .optional() - .describe("The type of app to filter by ('stack' for Docker Swarm stacks or 'docker-compose' for Compose projects)."), - serverId: z - .string() - .optional() - .describe("The ID of the server to search on."), - }), - annotations: { - title: "Get Docker Containers by App Name Match", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const params = new URLSearchParams(); - params.append("appName", input.appName); - if (input.appType) { - params.append("appType", input.appType); - } - if (input.serverId) { - params.append("serverId", input.serverId); - } - - const response = await apiClient.get( - `/docker.getContainersByAppNameMatch?${params.toString()}`, - ); - - return ResponseFormatter.success( - `Successfully fetched containers matching app name "${input.appName}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/docker/dockerGetServiceContainersByAppName.ts b/src/mcp/tools/docker/dockerGetServiceContainersByAppName.ts deleted file mode 100644 index f02ffc1..0000000 --- a/src/mcp/tools/docker/dockerGetServiceContainersByAppName.ts +++ /dev/null @@ -1,42 +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 dockerGetServiceContainersByAppName = createTool({ - name: "docker-get-service-containers-by-app-name", - description: "Gets Docker service containers by app name in Dokploy.", - schema: z.object({ - appName: z - .string() - .min(1) - .regex(/^[a-zA-Z0-9.\-_]+$/) - .describe("The app name to get service containers for."), - serverId: z - .string() - .optional() - .describe("The ID of the server to search on."), - }), - annotations: { - title: "Get Docker Service Containers by App Name", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const params = new URLSearchParams(); - params.append("appName", input.appName); - if (input.serverId) { - params.append("serverId", input.serverId); - } - - const response = await apiClient.get( - `/docker.getServiceContainersByAppName?${params.toString()}`, - ); - - return ResponseFormatter.success( - `Successfully fetched service containers for app "${input.appName}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/docker/dockerGetStackContainersByAppName.ts b/src/mcp/tools/docker/dockerGetStackContainersByAppName.ts deleted file mode 100644 index 17634f0..0000000 --- a/src/mcp/tools/docker/dockerGetStackContainersByAppName.ts +++ /dev/null @@ -1,42 +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 dockerGetStackContainersByAppName = createTool({ - name: "docker-get-stack-containers-by-app-name", - description: "Gets Docker stack containers by app name in Dokploy.", - schema: z.object({ - appName: z - .string() - .min(1) - .regex(/^[a-zA-Z0-9.\-_]+$/) - .describe("The app name to get stack containers for."), - serverId: z - .string() - .optional() - .describe("The ID of the server to search on."), - }), - annotations: { - title: "Get Docker Stack Containers by App Name", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const params = new URLSearchParams(); - params.append("appName", input.appName); - if (input.serverId) { - params.append("serverId", input.serverId); - } - - const response = await apiClient.get( - `/docker.getStackContainersByAppName?${params.toString()}`, - ); - - return ResponseFormatter.success( - `Successfully fetched stack containers for app "${input.appName}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/docker/dockerRestartContainer.ts b/src/mcp/tools/docker/dockerRestartContainer.ts deleted file mode 100644 index cd8ff81..0000000 --- a/src/mcp/tools/docker/dockerRestartContainer.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 dockerRestartContainer = createTool({ - name: "docker-restart-container", - description: "Restarts a Docker container in Dokploy.", - schema: z.object({ - containerId: z - .string() - .min(1) - .regex(/^[a-zA-Z0-9.\-_]+$/) - .describe("The ID of the container to restart."), - }), - annotations: { - title: "Restart Docker Container", - readOnlyHint: false, - destructiveHint: false, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/docker.restartContainer", { - containerId: input.containerId, - }); - - return ResponseFormatter.success( - `Successfully restarted container "${input.containerId}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/docker/index.ts b/src/mcp/tools/docker/index.ts deleted file mode 100644 index 1e9658c..0000000 --- a/src/mcp/tools/docker/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export { dockerGetContainers } from "./dockerGetContainers.js"; -export { dockerRestartContainer } from "./dockerRestartContainer.js"; -export { dockerGetConfig } from "./dockerGetConfig.js"; -export { dockerGetContainersByAppNameMatch } from "./dockerGetContainersByAppNameMatch.js"; -export { dockerGetContainersByAppLabel } from "./dockerGetContainersByAppLabel.js"; -export { dockerGetStackContainersByAppName } from "./dockerGetStackContainersByAppName.js"; -export { dockerGetServiceContainersByAppName } from "./dockerGetServiceContainersByAppName.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/environment/environmentByProjectId.ts b/src/mcp/tools/environment/environmentByProjectId.ts deleted file mode 100644 index 6a3878c..0000000 --- a/src/mcp/tools/environment/environmentByProjectId.ts +++ /dev/null @@ -1,30 +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 environmentByProjectId = createTool({ - name: "environment-by-project-id", - description: "Gets all environments for a specific project in Dokploy.", - schema: z.object({ - projectId: z - .string() - .describe("The project ID to list environments for. Required."), - }), - annotations: { - title: "List Environments by Project", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const environments = await apiClient.get( - `/environment.byProjectId?projectId=${input.projectId}`, - ); - - return ResponseFormatter.success( - `Successfully fetched environments for project "${input.projectId}"`, - environments.data, - ); - }, -}); diff --git a/src/mcp/tools/environment/environmentCreate.ts b/src/mcp/tools/environment/environmentCreate.ts deleted file mode 100644 index b9c4e14..0000000 --- a/src/mcp/tools/environment/environmentCreate.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 environmentCreate = createTool({ - name: "environment-create", - description: "Creates a new environment within a project in Dokploy.", - schema: z.object({ - name: z - .string() - .min(1) - .describe("Name for the environment (e.g., 'production', 'staging', 'development'). Required."), - projectId: z - .string() - .describe("The ID of the project to create the environment in. Required."), - description: z - .string() - .nullable() - .optional() - .describe("Description of the environment's purpose or configuration."), - }), - annotations: { - title: "Create Environment", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/environment.create", input); - - return ResponseFormatter.success( - `Environment "${input.name}" created successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/environment/environmentDuplicate.ts b/src/mcp/tools/environment/environmentDuplicate.ts deleted file mode 100644 index 3071182..0000000 --- a/src/mcp/tools/environment/environmentDuplicate.ts +++ /dev/null @@ -1,38 +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 environmentDuplicate = createTool({ - name: "environment-duplicate", - description: "Duplicates an existing environment in Dokploy.", - schema: z.object({ - environmentId: z - .string() - .min(1) - .describe("The ID of the source environment to duplicate. Required."), - name: z - .string() - .min(1) - .describe("Name for the new duplicated environment. Required."), - description: z - .string() - .nullable() - .optional() - .describe("Description for the new duplicated environment. Inherited from source if not specified."), - }), - annotations: { - title: "Duplicate Environment", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/environment.duplicate", input); - - return ResponseFormatter.success( - `Environment "${input.environmentId}" duplicated successfully as "${input.name}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/environment/environmentOne.ts b/src/mcp/tools/environment/environmentOne.ts deleted file mode 100644 index 6743d06..0000000 --- a/src/mcp/tools/environment/environmentOne.ts +++ /dev/null @@ -1,38 +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 environmentOne = createTool({ - name: "environment-one", - description: "Gets a specific environment by its ID in Dokploy.", - schema: z.object({ - environmentId: z - .string() - .min(1) - .describe("The unique identifier of the environment to retrieve. Required."), - }), - annotations: { - title: "Get Environment Details", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const environment = await apiClient.get( - `/environment.one?environmentId=${input.environmentId}`, - ); - - if (!environment?.data) { - return ResponseFormatter.error( - "Failed to fetch environment", - `Environment with ID "${input.environmentId}" not found`, - ); - } - - return ResponseFormatter.success( - `Successfully fetched environment "${input.environmentId}"`, - environment.data, - ); - }, -}); diff --git a/src/mcp/tools/environment/environmentRemove.ts b/src/mcp/tools/environment/environmentRemove.ts deleted file mode 100644 index 89988a4..0000000 --- a/src/mcp/tools/environment/environmentRemove.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 environmentRemove = createTool({ - name: "environment-remove", - description: "Removes/deletes an environment from Dokploy.", - schema: z.object({ - environmentId: z - .string() - .min(1) - .describe("The unique identifier of the environment to remove. Required."), - }), - annotations: { - title: "Remove Environment", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/environment.remove", input); - - return ResponseFormatter.success( - `Environment "${input.environmentId}" removed successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/environment/environmentUpdate.ts b/src/mcp/tools/environment/environmentUpdate.ts deleted file mode 100644 index 6614d4f..0000000 --- a/src/mcp/tools/environment/environmentUpdate.ts +++ /dev/null @@ -1,51 +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 environmentUpdate = createTool({ - name: "environment-update", - description: "Updates an existing environment in Dokploy.", - schema: z.object({ - environmentId: z - .string() - .min(1) - .describe("The ID of the environment to update. Required."), - name: z - .string() - .min(1) - .optional() - .describe("New name for the environment."), - description: z - .string() - .nullable() - .optional() - .describe("New description for the environment."), - createdAt: z - .string() - .optional() - .describe("Creation timestamp (ISO 8601 format)."), - env: z - .string() - .optional() - .describe("Environment variables in KEY=value format, one per line."), - projectId: z - .string() - .optional() - .describe("Project ID to move the environment to."), - }), - annotations: { - title: "Update Environment", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/environment.update", input); - - return ResponseFormatter.success( - `Environment "${input.environmentId}" updated successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/environment/index.ts b/src/mcp/tools/environment/index.ts deleted file mode 100644 index 5c055cd..0000000 --- a/src/mcp/tools/environment/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { environmentCreate } from "./environmentCreate.js"; -export { environmentOne } from "./environmentOne.js"; -export { environmentByProjectId } from "./environmentByProjectId.js"; -export { environmentRemove } from "./environmentRemove.js"; -export { environmentUpdate } from "./environmentUpdate.js"; -export { environmentDuplicate } from "./environmentDuplicate.js"; diff --git a/src/mcp/tools/git/gitBranches.ts b/src/mcp/tools/git/gitBranches.ts deleted file mode 100644 index 07f9bd2..0000000 --- a/src/mcp/tools/git/gitBranches.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -const providerSchema = z.enum(["github", "gitlab", "gitea", "bitbucket"]); - -export const gitBranches = createTool({ - name: "git-branches", - description: - "Lists all branches for a repository from a git provider (GitHub, GitLab, Gitea, or Bitbucket) in Dokploy.", - schema: z.object({ - provider: providerSchema.describe( - "The git provider type: github, gitlab, gitea, or bitbucket.", - ), - owner: z - .string() - .min(1) - .describe("The repository owner (username or organization)."), - repo: z - .string() - .min(1) - .describe( - "The repository name. For Gitea, this maps to 'repositoryName' in the API.", - ), - providerId: z - .string() - .optional() - .describe( - "The provider-specific ID (githubId, gitlabId, giteaId, or bitbucketId). Optional for all providers.", - ), - // GitLab specific - id: z - .number() - .optional() - .describe("The GitLab project ID (optional, GitLab only)."), - }), - annotations: { - title: "List Git Branches", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const { provider, owner, repo, providerId, id } = input; - const providerLabel = provider.charAt(0).toUpperCase() + provider.slice(1); - - // Build endpoint and params based on provider - let endpoint: string; - const params = new URLSearchParams(); - - switch (provider) { - case "github": - endpoint = "/github.getGithubBranches"; - params.append("repo", repo); - params.append("owner", owner); - if (providerId) params.append("githubId", providerId); - break; - case "gitlab": - endpoint = "/gitlab.getGitlabBranches"; - params.append("repo", repo); - params.append("owner", owner); - if (providerId) params.append("gitlabId", providerId); - if (id !== undefined) params.append("id", id.toString()); - break; - case "gitea": - endpoint = "/gitea.getGiteaBranches"; - params.append("repositoryName", repo); - params.append("owner", owner); - if (providerId) params.append("giteaId", providerId); - break; - case "bitbucket": - endpoint = "/bitbucket.getBitbucketBranches"; - params.append("repo", repo); - params.append("owner", owner); - if (providerId) params.append("bitbucketId", providerId); - break; - } - - const response = await apiClient.get(`${endpoint}?${params.toString()}`); - - if (!response?.data) { - return ResponseFormatter.error( - `Failed to fetch ${providerLabel} branches`, - `Unable to retrieve branches for repository "${owner}/${repo}"`, - ); - } - - return ResponseFormatter.success( - `Successfully fetched branches for repository "${owner}/${repo}" from ${providerLabel}`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/git/gitProviderCreate.ts b/src/mcp/tools/git/gitProviderCreate.ts deleted file mode 100644 index bac5550..0000000 --- a/src/mcp/tools/git/gitProviderCreate.ts +++ /dev/null @@ -1,263 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -// Note: GitHub uses app installation, not direct creation like the others -const providerSchema = z.enum(["gitlab", "gitea", "bitbucket"]); - -export const gitProviderCreate = createTool({ - name: "git-provider-create", - description: - "Creates a new git provider (GitLab, Gitea, or Bitbucket) in Dokploy. Note: GitHub providers are created via app installation, not this endpoint.", - schema: z.object({ - provider: providerSchema.describe( - "The git provider type to create: gitlab, gitea, or bitbucket. GitHub uses app installation instead.", - ), - name: z - .string() - .min(1) - .describe( - "Display name for the git provider. Required for all providers.", - ), - // GitLab specific required fields - gitlabUrl: z - .string() - .min(1) - .optional() - .describe( - "The GitLab instance URL (e.g., https://gitlab.com). Required for GitLab.", - ), - authId: z - .string() - .min(1) - .optional() - .describe( - "The authentication ID for OAuth. Required for GitLab and Bitbucket.", - ), - // GitLab optional fields - gitlabId: z - .string() - .optional() - .describe("The GitLab provider ID (optional, GitLab only)."), - applicationId: z - .string() - .optional() - .describe("The OAuth application ID (optional, GitLab only)."), - redirectUri: z - .string() - .optional() - .describe("The OAuth redirect URI (optional, GitLab/Gitea)."), - secret: z - .string() - .optional() - .describe("The OAuth client secret (optional, GitLab only)."), - groupName: z - .string() - .optional() - .describe( - "The GitLab group name to filter repositories (optional, GitLab only).", - ), - // Gitea specific required fields - giteaUrl: z - .string() - .min(1) - .optional() - .describe( - "The Gitea instance URL (e.g., https://gitea.example.com). Required for Gitea.", - ), - // Gitea optional fields - giteaId: z - .string() - .optional() - .describe("The Gitea provider ID (optional, Gitea only)."), - clientId: z - .string() - .optional() - .describe("The OAuth client ID (optional, Gitea only)."), - clientSecret: z - .string() - .optional() - .describe("The OAuth client secret (optional, Gitea only)."), - scopes: z - .string() - .optional() - .describe("OAuth scopes to request (optional, Gitea only)."), - lastAuthenticatedAt: z - .number() - .optional() - .describe( - "Last authentication timestamp in milliseconds (optional, Gitea only).", - ), - giteaUsername: z - .string() - .optional() - .describe("The Gitea username (optional, Gitea only)."), - organizationName: z - .string() - .optional() - .describe( - "The Gitea organization name to filter repositories (optional, Gitea only).", - ), - // Bitbucket optional fields - bitbucketId: z - .string() - .optional() - .describe("The Bitbucket provider ID (optional, Bitbucket only)."), - bitbucketUsername: z - .string() - .optional() - .describe("The Bitbucket username (optional, Bitbucket only)."), - bitbucketEmail: z - .string() - .email() - .optional() - .describe("The Bitbucket email address (optional, Bitbucket only)."), - appPassword: z - .string() - .optional() - .describe( - "The Bitbucket app password for authentication (optional, Bitbucket only).", - ), - apiToken: z - .string() - .optional() - .describe("The Bitbucket API token (optional, Bitbucket only)."), - bitbucketWorkspaceName: z - .string() - .optional() - .describe("The Bitbucket workspace name (optional, Bitbucket only)."), - // Common optional fields - gitProviderId: z - .string() - .optional() - .describe("The parent git provider ID (optional)."), - accessToken: z - .string() - .nullable() - .optional() - .describe( - "The OAuth access token (optional, GitLab only supports null).", - ), - refreshToken: z - .string() - .nullable() - .optional() - .describe( - "The OAuth refresh token (optional, GitLab only supports null).", - ), - expiresAt: z - .number() - .nullable() - .optional() - .describe( - "Token expiration timestamp in milliseconds (optional, GitLab only supports null).", - ), - }), - annotations: { - title: "Create Git Provider", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const { provider, name } = input; - const providerLabel = provider.charAt(0).toUpperCase() + provider.slice(1); - - // Build endpoint and payload based on provider - let endpoint: string; - let payload: Record; - - switch (provider) { - case "gitlab": - // GitLab requires: gitlabUrl (min 1), authId (min 1), name (min 1) - if (!input.gitlabUrl || input.gitlabUrl.length < 1) { - return ResponseFormatter.error( - "Missing required parameter", - "GitLab requires gitlabUrl with at least 1 character", - ); - } - if (!input.authId || input.authId.length < 1) { - return ResponseFormatter.error( - "Missing required parameter", - "GitLab requires authId with at least 1 character", - ); - } - endpoint = "/gitlab.create"; - payload = { - name, - gitlabUrl: input.gitlabUrl, - authId: input.authId, - gitlabId: input.gitlabId, - applicationId: input.applicationId, - redirectUri: input.redirectUri, - secret: input.secret, - accessToken: input.accessToken, - refreshToken: input.refreshToken, - groupName: input.groupName, - expiresAt: input.expiresAt, - gitProviderId: input.gitProviderId, - }; - break; - case "gitea": - // Gitea requires: giteaUrl (min 1), name (min 1) - if (!input.giteaUrl || input.giteaUrl.length < 1) { - return ResponseFormatter.error( - "Missing required parameter", - "Gitea requires giteaUrl with at least 1 character", - ); - } - endpoint = "/gitea.create"; - payload = { - name, - giteaUrl: input.giteaUrl, - giteaId: input.giteaId, - redirectUri: input.redirectUri, - clientId: input.clientId, - clientSecret: input.clientSecret, - gitProviderId: input.gitProviderId, - accessToken: input.accessToken, - refreshToken: input.refreshToken, - expiresAt: input.expiresAt, - scopes: input.scopes, - lastAuthenticatedAt: input.lastAuthenticatedAt, - giteaUsername: input.giteaUsername, - organizationName: input.organizationName, - }; - break; - case "bitbucket": - // Bitbucket requires: authId (min 1), name (min 1) - if (!input.authId || input.authId.length < 1) { - return ResponseFormatter.error( - "Missing required parameter", - "Bitbucket requires authId with at least 1 character", - ); - } - endpoint = "/bitbucket.create"; - payload = { - name, - authId: input.authId, - bitbucketId: input.bitbucketId, - bitbucketUsername: input.bitbucketUsername, - bitbucketEmail: input.bitbucketEmail, - appPassword: input.appPassword, - apiToken: input.apiToken, - bitbucketWorkspaceName: input.bitbucketWorkspaceName, - gitProviderId: input.gitProviderId, - }; - break; - } - - // Remove undefined values - payload = Object.fromEntries( - Object.entries(payload).filter(([, v]) => v !== undefined), - ); - - const response = await apiClient.post(endpoint, payload); - - return ResponseFormatter.success( - `Successfully created ${providerLabel} provider "${name}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/git/gitProviderGetAll.ts b/src/mcp/tools/git/gitProviderGetAll.ts deleted file mode 100644 index 51c6045..0000000 --- a/src/mcp/tools/git/gitProviderGetAll.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 gitProviderGetAll = createTool({ - name: "git-provider-get-all", - description: - "Lists all git providers configured in Dokploy across all provider types (GitHub, GitLab, Gitea, Bitbucket). Returns a combined list of all provider configurations.", - schema: z.object({}), - annotations: { - title: "List All Git Providers", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.get("/gitProvider.getAll"); - - if (!response?.data) { - return ResponseFormatter.error( - "Failed to fetch git providers", - "No git providers found or unable to retrieve the list", - ); - } - - return ResponseFormatter.success( - "Successfully fetched all git providers", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/git/gitProviderGetUrl.ts b/src/mcp/tools/git/gitProviderGetUrl.ts deleted file mode 100644 index 2d846bc..0000000 --- a/src/mcp/tools/git/gitProviderGetUrl.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 gitProviderGetUrl = createTool({ - name: "git-provider-get-url", - description: - "Gets the instance URL for a Gitea provider in Dokploy. This is useful for retrieving the base URL of a self-hosted Gitea instance.", - schema: z.object({ - giteaId: z - .string() - .min(1) - .describe( - "The Gitea provider ID. Required. Used to identify which Gitea provider's URL to retrieve.", - ), - }), - annotations: { - title: "Get Gitea URL", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.get( - `/gitea.getGiteaUrl?giteaId=${encodeURIComponent(input.giteaId)}`, - ); - - if (!response?.data) { - return ResponseFormatter.error( - "Failed to fetch Gitea URL", - `Unable to retrieve URL for Gitea provider "${input.giteaId}"`, - ); - } - - return ResponseFormatter.success( - `Successfully fetched URL for Gitea provider "${input.giteaId}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/git/gitProviderOne.ts b/src/mcp/tools/git/gitProviderOne.ts deleted file mode 100644 index 0d1ef82..0000000 --- a/src/mcp/tools/git/gitProviderOne.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -const providerSchema = z.enum(["github", "gitlab", "gitea", "bitbucket"]); - -export const gitProviderOne = createTool({ - name: "git-provider-one", - description: - "Gets a specific git provider configuration by its ID (GitHub, GitLab, Gitea, or Bitbucket) in Dokploy.", - schema: z.object({ - provider: providerSchema.describe( - "The git provider type: github, gitlab, gitea, or bitbucket.", - ), - providerId: z - .string() - .min(1) - .describe( - "The provider-specific ID. Required. Maps to githubId, gitlabId, giteaId, or bitbucketId based on provider.", - ), - }), - annotations: { - title: "Get Git Provider Details", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const { provider, providerId } = input; - const providerLabel = provider.charAt(0).toUpperCase() + provider.slice(1); - - // Build endpoint based on provider - let endpoint: string; - let paramName: string; - - switch (provider) { - case "github": - endpoint = "/github.one"; - paramName = "githubId"; - break; - case "gitlab": - endpoint = "/gitlab.one"; - paramName = "gitlabId"; - break; - case "gitea": - endpoint = "/gitea.one"; - paramName = "giteaId"; - break; - case "bitbucket": - endpoint = "/bitbucket.one"; - paramName = "bitbucketId"; - break; - } - - const response = await apiClient.get( - `${endpoint}?${paramName}=${encodeURIComponent(providerId)}`, - ); - - if (!response?.data) { - return ResponseFormatter.error( - `Failed to fetch ${providerLabel} provider`, - `${providerLabel} provider with ID "${providerId}" not found`, - ); - } - - return ResponseFormatter.success( - `Successfully fetched ${providerLabel} provider "${providerId}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/git/gitProviderRemove.ts b/src/mcp/tools/git/gitProviderRemove.ts deleted file mode 100644 index 56103df..0000000 --- a/src/mcp/tools/git/gitProviderRemove.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 gitProviderRemove = createTool({ - name: "git-provider-remove", - description: - "Removes a git provider configuration from Dokploy. This deletes the provider regardless of type (GitHub, GitLab, Gitea, or Bitbucket).", - schema: z.object({ - gitProviderId: z - .string() - .min(1) - .describe( - "The ID of the git provider to remove. Required. This is the parent gitProviderId, not the provider-specific ID.", - ), - }), - annotations: { - title: "Remove Git Provider", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/gitProvider.remove", { - gitProviderId: input.gitProviderId, - }); - - return ResponseFormatter.success( - `Successfully removed git provider "${input.gitProviderId}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/git/gitProviderUpdate.ts b/src/mcp/tools/git/gitProviderUpdate.ts deleted file mode 100644 index 7327e5e..0000000 --- a/src/mcp/tools/git/gitProviderUpdate.ts +++ /dev/null @@ -1,321 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -const providerSchema = z.enum(["github", "gitlab", "gitea", "bitbucket"]); - -export const gitProviderUpdate = createTool({ - name: "git-provider-update", - description: - "Updates a git provider configuration (GitHub, GitLab, Gitea, or Bitbucket) in Dokploy.", - schema: z.object({ - provider: providerSchema.describe( - "The git provider type to update: github, gitlab, gitea, or bitbucket.", - ), - providerId: z - .string() - .min(1) - .describe( - "The provider-specific ID. Required. Maps to githubId, gitlabId, giteaId, or bitbucketId based on provider.", - ), - gitProviderId: z - .string() - .describe( - "The parent git provider ID. Required for all providers. GitHub requires min 1 char.", - ), - name: z - .string() - .min(1) - .describe( - "Display name for the git provider. Required for all providers.", - ), - // GitHub specific - githubAppName: z - .string() - .min(1) - .optional() - .describe("The GitHub App name. Required for GitHub updates."), - githubAppId: z - .number() - .nullable() - .optional() - .describe("The GitHub App numeric ID (optional, GitHub only)."), - githubClientId: z - .string() - .nullable() - .optional() - .describe("The GitHub OAuth Client ID (optional, GitHub only)."), - githubClientSecret: z - .string() - .nullable() - .optional() - .describe("The GitHub OAuth Client Secret (optional, GitHub only)."), - githubInstallationId: z - .string() - .nullable() - .optional() - .describe("The GitHub App Installation ID (optional, GitHub only)."), - githubPrivateKey: z - .string() - .nullable() - .optional() - .describe("The GitHub App Private Key (optional, GitHub only)."), - githubWebhookSecret: z - .string() - .nullable() - .optional() - .describe("The GitHub Webhook Secret (optional, GitHub only)."), - // GitLab specific - gitlabUrl: z - .string() - .min(1) - .optional() - .describe( - "The GitLab instance URL (e.g., https://gitlab.com). Required for GitLab updates.", - ), - applicationId: z - .string() - .optional() - .describe("The OAuth application ID (optional, GitLab only)."), - redirectUri: z - .string() - .optional() - .describe("The OAuth redirect URI (optional, GitLab/Gitea)."), - secret: z - .string() - .optional() - .describe("The OAuth client secret (optional, GitLab only)."), - groupName: z - .string() - .optional() - .describe( - "The GitLab group name to filter repositories (optional, GitLab only).", - ), - // Gitea specific - giteaUrl: z - .string() - .min(1) - .optional() - .describe( - "The Gitea instance URL (e.g., https://gitea.example.com). Required for Gitea updates.", - ), - clientId: z - .string() - .optional() - .describe("The OAuth client ID (optional, Gitea only)."), - clientSecret: z - .string() - .optional() - .describe("The OAuth client secret (optional, Gitea only)."), - scopes: z - .string() - .optional() - .describe("OAuth scopes to request (optional, Gitea only)."), - lastAuthenticatedAt: z - .number() - .optional() - .describe( - "Last authentication timestamp in milliseconds (optional, Gitea only).", - ), - giteaUsername: z - .string() - .optional() - .describe("The Gitea username (optional, Gitea only)."), - organizationName: z - .string() - .optional() - .describe( - "The organization name to filter repositories (optional, Gitea only).", - ), - // Bitbucket specific - bitbucketUsername: z - .string() - .optional() - .describe("The Bitbucket username (optional, Bitbucket only)."), - bitbucketEmail: z - .string() - .email() - .optional() - .describe("The Bitbucket email address (optional, Bitbucket only)."), - appPassword: z - .string() - .optional() - .describe( - "The Bitbucket app password for authentication (optional, Bitbucket only).", - ), - apiToken: z - .string() - .optional() - .describe("The Bitbucket API token (optional, Bitbucket only)."), - bitbucketWorkspaceName: z - .string() - .optional() - .describe("The Bitbucket workspace name (optional, Bitbucket only)."), - organizationId: z - .string() - .optional() - .describe("The organization ID (optional, Bitbucket only)."), - // Common token fields (GitLab only) - accessToken: z - .string() - .nullable() - .optional() - .describe( - "The OAuth access token (optional, GitLab only supports null).", - ), - refreshToken: z - .string() - .nullable() - .optional() - .describe( - "The OAuth refresh token (optional, GitLab only supports null).", - ), - expiresAt: z - .number() - .nullable() - .optional() - .describe( - "Token expiration timestamp in milliseconds (optional, GitLab only supports null).", - ), - }), - annotations: { - title: "Update Git Provider", - destructiveHint: false, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const { provider, providerId, gitProviderId, name } = input; - const providerLabel = provider.charAt(0).toUpperCase() + provider.slice(1); - - // Build endpoint and payload based on provider - let endpoint: string; - let payload: Record; - - switch (provider) { - case "github": - // GitHub requires: githubId (min 1), githubAppName (min 1), gitProviderId (min 1), name (min 1) - if (!input.githubAppName || input.githubAppName.length < 1) { - return ResponseFormatter.error( - "Missing required parameter", - "GitHub requires githubAppName with at least 1 character", - ); - } - if (!gitProviderId || gitProviderId.length < 1) { - return ResponseFormatter.error( - "Missing required parameter", - "GitHub requires gitProviderId with at least 1 character", - ); - } - endpoint = "/github.update"; - payload = { - githubId: providerId, - gitProviderId, - name, - githubAppName: input.githubAppName, - githubAppId: input.githubAppId, - githubClientId: input.githubClientId, - githubClientSecret: input.githubClientSecret, - githubInstallationId: input.githubInstallationId, - githubPrivateKey: input.githubPrivateKey, - githubWebhookSecret: input.githubWebhookSecret, - }; - break; - case "gitlab": - // GitLab requires: gitlabId (min 1), gitlabUrl (min 1), gitProviderId (required), name (min 1) - if (!input.gitlabUrl || input.gitlabUrl.length < 1) { - return ResponseFormatter.error( - "Missing required parameter", - "GitLab requires gitlabUrl with at least 1 character", - ); - } - if (gitProviderId === undefined) { - return ResponseFormatter.error( - "Missing required parameter", - "GitLab requires gitProviderId", - ); - } - endpoint = "/gitlab.update"; - payload = { - gitlabId: providerId, - gitProviderId, - name, - gitlabUrl: input.gitlabUrl, - applicationId: input.applicationId, - redirectUri: input.redirectUri, - secret: input.secret, - accessToken: input.accessToken, - refreshToken: input.refreshToken, - groupName: input.groupName, - expiresAt: input.expiresAt, - }; - break; - case "gitea": - // Gitea requires: giteaId (min 1), giteaUrl (min 1), gitProviderId (required), name (min 1) - if (!input.giteaUrl || input.giteaUrl.length < 1) { - return ResponseFormatter.error( - "Missing required parameter", - "Gitea requires giteaUrl with at least 1 character", - ); - } - if (gitProviderId === undefined) { - return ResponseFormatter.error( - "Missing required parameter", - "Gitea requires gitProviderId", - ); - } - endpoint = "/gitea.update"; - payload = { - giteaId: providerId, - gitProviderId, - name, - giteaUrl: input.giteaUrl, - redirectUri: input.redirectUri, - clientId: input.clientId, - clientSecret: input.clientSecret, - accessToken: input.accessToken, - refreshToken: input.refreshToken, - expiresAt: input.expiresAt, - scopes: input.scopes, - lastAuthenticatedAt: input.lastAuthenticatedAt, - giteaUsername: input.giteaUsername, - organizationName: input.organizationName, - }; - break; - case "bitbucket": - // Bitbucket requires: bitbucketId (min 1), gitProviderId (required), name (min 1) - if (gitProviderId === undefined) { - return ResponseFormatter.error( - "Missing required parameter", - "Bitbucket requires gitProviderId", - ); - } - endpoint = "/bitbucket.update"; - payload = { - bitbucketId: providerId, - gitProviderId, - name, - bitbucketUsername: input.bitbucketUsername, - bitbucketEmail: input.bitbucketEmail, - appPassword: input.appPassword, - apiToken: input.apiToken, - bitbucketWorkspaceName: input.bitbucketWorkspaceName, - organizationId: input.organizationId, - }; - break; - } - - // Remove undefined values - payload = Object.fromEntries( - Object.entries(payload).filter(([, v]) => v !== undefined), - ); - - const response = await apiClient.post(endpoint, payload); - - return ResponseFormatter.success( - `Successfully updated ${providerLabel} provider "${name}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/git/gitProviders.ts b/src/mcp/tools/git/gitProviders.ts deleted file mode 100644 index 1b78131..0000000 --- a/src/mcp/tools/git/gitProviders.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"; - -const providerSchema = z.enum(["github", "gitlab", "gitea", "bitbucket"]); - -export const gitProviders = createTool({ - name: "git-providers", - description: - "Lists all configured git providers of a specific type (GitHub, GitLab, Gitea, or Bitbucket) in Dokploy. Returns all provider configurations for the specified type.", - schema: z.object({ - provider: providerSchema.describe( - "The git provider type to list: github, gitlab, gitea, or bitbucket.", - ), - }), - annotations: { - title: "List Git Providers by Type", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const { provider } = input; - const providerLabel = provider.charAt(0).toUpperCase() + provider.slice(1); - - // Build endpoint based on provider - let endpoint: string; - - switch (provider) { - case "github": - endpoint = "/github.githubProviders"; - break; - case "gitlab": - endpoint = "/gitlab.gitlabProviders"; - break; - case "gitea": - endpoint = "/gitea.giteaProviders"; - break; - case "bitbucket": - endpoint = "/bitbucket.bitbucketProviders"; - break; - } - - const response = await apiClient.get(endpoint); - - if (!response?.data) { - return ResponseFormatter.error( - `Failed to fetch ${providerLabel} providers`, - `No ${providerLabel} providers found or unable to retrieve the list`, - ); - } - - return ResponseFormatter.success( - `Successfully fetched ${providerLabel} providers`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/git/gitRepositories.ts b/src/mcp/tools/git/gitRepositories.ts deleted file mode 100644 index ef178d9..0000000 --- a/src/mcp/tools/git/gitRepositories.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -const providerSchema = z.enum(["github", "gitlab", "gitea", "bitbucket"]); - -export const gitRepositories = createTool({ - name: "git-repositories", - description: - "Lists all repositories available from a git provider (GitHub, GitLab, Gitea, or Bitbucket) in Dokploy.", - schema: z.object({ - provider: providerSchema.describe( - "The git provider type: github, gitlab, gitea, or bitbucket.", - ), - providerId: z - .string() - .min(1) - .describe( - "The provider-specific ID. Required. Maps to githubId, gitlabId, giteaId, or bitbucketId based on provider.", - ), - }), - annotations: { - title: "List Git Repositories", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const { provider, providerId } = input; - const providerLabel = provider.charAt(0).toUpperCase() + provider.slice(1); - - // Build endpoint based on provider - let endpoint: string; - let paramName: string; - - switch (provider) { - case "github": - endpoint = "/github.getGithubRepositories"; - paramName = "githubId"; - break; - case "gitlab": - endpoint = "/gitlab.getGitlabRepositories"; - paramName = "gitlabId"; - break; - case "gitea": - endpoint = "/gitea.getGiteaRepositories"; - paramName = "giteaId"; - break; - case "bitbucket": - endpoint = "/bitbucket.getBitbucketRepositories"; - paramName = "bitbucketId"; - break; - } - - const response = await apiClient.get( - `${endpoint}?${paramName}=${encodeURIComponent(providerId)}`, - ); - - if (!response?.data) { - return ResponseFormatter.error( - `Failed to fetch ${providerLabel} repositories`, - `Unable to retrieve repositories for ${providerLabel} provider "${providerId}"`, - ); - } - - return ResponseFormatter.success( - `Successfully fetched repositories for ${providerLabel} provider "${providerId}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/git/gitTestConnection.ts b/src/mcp/tools/git/gitTestConnection.ts deleted file mode 100644 index b5d1fa9..0000000 --- a/src/mcp/tools/git/gitTestConnection.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -const providerSchema = z.enum(["github", "gitlab", "gitea", "bitbucket"]); - -export const gitTestConnection = createTool({ - name: "git-test-connection", - description: - "Tests the connection to a git provider (GitHub, GitLab, Gitea, or Bitbucket) in Dokploy.", - schema: z.object({ - provider: providerSchema.describe( - "The git provider type: github, gitlab, gitea, or bitbucket.", - ), - // GitHub requires providerId with min 1 char, others have it optional - providerId: z - .string() - .optional() - .describe( - "The provider-specific ID. Required for GitHub (min 1 char) and Bitbucket (min 1 char). Optional for GitLab and Gitea.", - ), - // GitLab specific - groupName: z - .string() - .optional() - .describe("The GitLab group name to filter repositories (GitLab only)."), - // Gitea specific - organizationName: z - .string() - .optional() - .describe( - "The Gitea organization name to filter repositories (Gitea only).", - ), - // Bitbucket specific - bitbucketUsername: z - .string() - .optional() - .describe("The Bitbucket username for authentication (Bitbucket only)."), - bitbucketEmail: z - .string() - .email() - .optional() - .describe( - "The Bitbucket email address for authentication (Bitbucket only).", - ), - workspaceName: z - .string() - .optional() - .describe("The Bitbucket workspace name (Bitbucket only)."), - apiToken: z - .string() - .optional() - .describe("The Bitbucket API token for authentication (Bitbucket only)."), - appPassword: z - .string() - .optional() - .describe( - "The Bitbucket app password for authentication (Bitbucket only).", - ), - }), - annotations: { - title: "Test Git Connection", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const { - provider, - providerId, - groupName, - organizationName, - bitbucketUsername, - bitbucketEmail, - workspaceName, - apiToken, - appPassword, - } = input; - const providerLabel = provider.charAt(0).toUpperCase() + provider.slice(1); - - // Build endpoint and payload based on provider - let endpoint: string; - let payload: Record; - - switch (provider) { - case "github": - // GitHub requires githubId with minLength 1 - if (!providerId || providerId.length < 1) { - return ResponseFormatter.error( - "Missing required parameter", - "GitHub requires providerId (githubId) with at least 1 character", - ); - } - endpoint = "/github.testConnection"; - payload = { githubId: providerId }; - break; - case "gitlab": - // GitLab: gitlabId and groupName are both optional - endpoint = "/gitlab.testConnection"; - payload = {}; - if (providerId) payload.gitlabId = providerId; - if (groupName) payload.groupName = groupName; - break; - case "gitea": - // Gitea: giteaId and organizationName are both optional - endpoint = "/gitea.testConnection"; - payload = {}; - if (providerId) payload.giteaId = providerId; - if (organizationName) payload.organizationName = organizationName; - break; - case "bitbucket": - // Bitbucket requires bitbucketId with minLength 1 - if (!providerId || providerId.length < 1) { - return ResponseFormatter.error( - "Missing required parameter", - "Bitbucket requires providerId (bitbucketId) with at least 1 character", - ); - } - endpoint = "/bitbucket.testConnection"; - payload = { bitbucketId: providerId }; - if (bitbucketUsername) payload.bitbucketUsername = bitbucketUsername; - if (bitbucketEmail) payload.bitbucketEmail = bitbucketEmail; - if (workspaceName) payload.workspaceName = workspaceName; - if (apiToken) payload.apiToken = apiToken; - if (appPassword) payload.appPassword = appPassword; - break; - } - - const response = await apiClient.post(endpoint, payload); - - const providerIdDisplay = providerId || "(default)"; - return ResponseFormatter.success( - `Successfully tested connection for ${providerLabel} provider "${providerIdDisplay}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/git/index.ts b/src/mcp/tools/git/index.ts deleted file mode 100644 index 862a7d3..0000000 --- a/src/mcp/tools/git/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -export { gitBranches } from "./gitBranches.js"; -export { gitRepositories } from "./gitRepositories.js"; -export { gitTestConnection } from "./gitTestConnection.js"; -export { gitProviderOne } from "./gitProviderOne.js"; -export { gitProviders } from "./gitProviders.js"; -export { gitProviderCreate } from "./gitProviderCreate.js"; -export { gitProviderUpdate } from "./gitProviderUpdate.js"; -export { gitProviderGetAll } from "./gitProviderGetAll.js"; -export { gitProviderRemove } from "./gitProviderRemove.js"; -export { gitProviderGetUrl } from "./gitProviderGetUrl.js"; diff --git a/src/mcp/tools/index.ts b/src/mcp/tools/index.ts index 4ea2e38..5881e83 100644 --- a/src/mcp/tools/index.ts +++ b/src/mcp/tools/index.ts @@ -1,69 +1,2 @@ -import * as adminTools from "./admin/index.js"; -import * as aiTools from "./ai/index.js"; -import * as applicationTools from "./application/index.js"; -import * as backupTools from "./backup/index.js"; -import * as certificatesTools from "./certificates/index.js"; -import * as clusterTools from "./cluster/index.js"; -import * as composeTools from "./compose/index.js"; -import * as databaseTools from "./database/index.js"; -import * as deploymentTools from "./deployment/index.js"; -import * as destinationTools from "./destination/index.js"; -import * as dockerTools from "./docker/index.js"; -import * as domainTools from "./domain/index.js"; -import * as environmentTools from "./environment/index.js"; -import * as gitTools from "./git/index.js"; -import * as mountsTools from "./mounts/index.js"; -import * as mysqlTools from "./mysql/index.js"; -import * as notificationTools from "./notification/index.js"; -import * as organizationTools from "./organization/index.js"; -import * as portTools from "./port/index.js"; -import * as postgresTools from "./postgres/index.js"; -import * as previewDeploymentTools from "./previewDeployment/index.js"; -import * as projectTools from "./project/index.js"; -import * as redirectsTools from "./redirects/index.js"; -import * as registryTools from "./registry/index.js"; -import * as rollbackTools from "./rollback/index.js"; -import * as scheduleTools from "./schedule/index.js"; -import * as securityTools from "./security/index.js"; -import * as serverTools from "./server/index.js"; -import * as settingsTools from "./settings/index.js"; -import * as sshKeyTools from "./sshKey/index.js"; -import * as stripeTools from "./stripe/index.js"; -import * as swarmTools from "./swarm/index.js"; -import * as userTools from "./user/index.js"; - -export const allTools = [ - ...Object.values(adminTools), - ...Object.values(aiTools), - ...Object.values(applicationTools), - ...Object.values(backupTools), - ...Object.values(certificatesTools), - ...Object.values(clusterTools), - ...Object.values(composeTools), - ...Object.values(databaseTools), - ...Object.values(deploymentTools), - ...Object.values(destinationTools), - ...Object.values(dockerTools), - ...Object.values(domainTools), - ...Object.values(environmentTools), - ...Object.values(gitTools), - ...Object.values(mountsTools), - ...Object.values(mysqlTools), - ...Object.values(notificationTools), - ...Object.values(organizationTools), - ...Object.values(portTools), - ...Object.values(postgresTools), - ...Object.values(previewDeploymentTools), - ...Object.values(projectTools), - ...Object.values(redirectsTools), - ...Object.values(registryTools), - ...Object.values(rollbackTools), - ...Object.values(scheduleTools), - ...Object.values(securityTools), - ...Object.values(serverTools), - ...Object.values(settingsTools), - ...Object.values(sshKeyTools), - ...Object.values(stripeTools), - ...Object.values(swarmTools), - ...Object.values(userTools), -]; +export * as api from "./api.js"; +export * as apiSchema from "./apiSchema.js"; diff --git a/src/mcp/tools/mounts/index.ts b/src/mcp/tools/mounts/index.ts deleted file mode 100644 index 3cfb08f..0000000 --- a/src/mcp/tools/mounts/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { mountsCreate } from "./mountsCreate.js"; -export { mountsOne } from "./mountsOne.js"; -export { mountsUpdate } from "./mountsUpdate.js"; -export { mountsRemove } from "./mountsRemove.js"; -export { mountsAllByApplicationId } from "./mountsAllByApplicationId.js"; diff --git a/src/mcp/tools/mounts/mountsAllByApplicationId.ts b/src/mcp/tools/mounts/mountsAllByApplicationId.ts deleted file mode 100644 index 66dcc25..0000000 --- a/src/mcp/tools/mounts/mountsAllByApplicationId.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -interface Mount { - mountId: string; - type: "file" | "bind" | "volume"; - hostPath: string | null; - volumeName: string | null; - filePath: string | null; - content: string | null; - serviceType: string; - mountPath: string; - applicationId: string | null; - postgresId: string | null; - mariadbId: string | null; - mongoId: string | null; - mysqlId: string | null; - redisId: string | null; - composeId: string | null; -} - -export const mountsAllByApplicationId = createTool({ - name: "mounts-allByApplicationId", - description: - "Retrieves all mounts associated with a specific application. Returns a list of all file mounts, bind mounts, and volume mounts configured for the application. Useful for viewing what files and volumes are attached to an app.", - schema: z.object({ - applicationId: z - .string() - .min(1) - .describe("The application ID to list mounts for. Required."), - }), - annotations: { - title: "List Application Mounts", - readOnlyHint: true, - destructiveHint: false, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - // Mounts are embedded in the application.one response - const response = await apiClient.get( - `/application.one?applicationId=${input.applicationId}` - ); - - if (!response?.data) { - return ResponseFormatter.error( - "Failed to fetch application", - `Application with ID "${input.applicationId}" not found` - ); - } - - const mounts: Mount[] = response.data.mounts ?? []; - - // Group by type for easier reading - const grouped = { - file: mounts.filter((m) => m.type === "file"), - bind: mounts.filter((m) => m.type === "bind"), - volume: mounts.filter((m) => m.type === "volume"), - }; - - // For file mounts, truncate content in summary but include full in data - const summary = mounts.map((m) => ({ - mountId: m.mountId, - type: m.type, - mountPath: m.mountPath, - ...(m.type === "file" && m.content - ? { contentPreview: m.content.slice(0, 100) + (m.content.length > 100 ? "..." : "") } - : {}), - ...(m.type === "volume" ? { volumeName: m.volumeName } : {}), - ...(m.type === "bind" ? { hostPath: m.hostPath } : {}), - })); - - return ResponseFormatter.success( - `Found ${mounts.length} mount(s): ${grouped.file.length} file, ${grouped.bind.length} bind, ${grouped.volume.length} volume`, - { total: mounts.length, grouped, summary, mounts } - ); - }, -}); diff --git a/src/mcp/tools/mounts/mountsCreate.ts b/src/mcp/tools/mounts/mountsCreate.ts deleted file mode 100644 index 58f89a7..0000000 --- a/src/mcp/tools/mounts/mountsCreate.ts +++ /dev/null @@ -1,106 +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 mountsCreate = createTool({ - name: "mounts-create", - description: `Creates a new mount for a service in Dokploy. Supports three mount types: - -- **file**: Creates a file with inline content. Requires: filePath (filename only), mountPath (FULL path INCLUDING filename), content (file content). IMPORTANT: For file mounts, mountPath must be the complete path including the filename (e.g., '/app/config.json') because Docker mounts files to files, not files to directories. -- **bind**: Maps a host directory to container. Requires: hostPath (host path), mountPath (container path) -- **volume**: Uses a Docker volume. Requires: volumeName, mountPath (container path)`, - schema: z.object({ - type: z - .enum(["bind", "volume", "file"]) - .describe( - "Mount type. 'file': inline file content, 'bind': host path mapping, 'volume': Docker volume. Required." - ), - mountPath: z - .string() - .min(1) - .describe( - "Destination path inside the container. For file mounts: MUST include the filename (e.g., '/app/config.json'). For bind/volume: the directory path. Required." - ), - serviceId: z - .string() - .min(1) - .describe( - "ID of the service (application, database, or compose) to attach this mount to. Required." - ), - serviceType: z - .enum(["application", "postgres", "mysql", "mariadb", "mongo", "redis", "compose"]) - .default("application") - .optional() - .describe( - "Type of service this mount belongs to. Defaults to 'application'." - ), - filePath: z - .string() - .nullable() - .optional() - .describe( - "For 'file' type: The filename (e.g., 'config.json'). This is just the name, not a path." - ), - content: z - .string() - .nullable() - .optional() - .describe("For 'file' type: The actual file content to be written."), - hostPath: z - .string() - .nullable() - .optional() - .describe("For 'bind' type: Absolute path on the host server to mount from."), - volumeName: z - .string() - .nullable() - .optional() - .describe("For 'volume' type: Name of the Docker volume to mount."), - }), - annotations: { - title: "Create Mount", - readOnlyHint: false, - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - // Validate type-specific required fields - if (input.type === "file") { - if (!input.filePath) { - return ResponseFormatter.error( - "Missing required field", - "filePath is required for 'file' type mounts" - ); - } - if (!input.content) { - return ResponseFormatter.error( - "Missing required field", - "content is required for 'file' type mounts" - ); - } - } else if (input.type === "bind") { - if (!input.hostPath) { - return ResponseFormatter.error( - "Missing required field", - "hostPath is required for 'bind' type mounts" - ); - } - } else if (input.type === "volume") { - if (!input.volumeName) { - return ResponseFormatter.error( - "Missing required field", - "volumeName is required for 'volume' type mounts" - ); - } - } - - const response = await apiClient.post("/mounts.create", input); - - return ResponseFormatter.success( - `Successfully created ${input.type} mount at ${input.mountPath}`, - response?.data ?? { created: true } - ); - }, -}); diff --git a/src/mcp/tools/mounts/mountsOne.ts b/src/mcp/tools/mounts/mountsOne.ts deleted file mode 100644 index 1b2b24d..0000000 --- a/src/mcp/tools/mounts/mountsOne.ts +++ /dev/null @@ -1,39 +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 mountsOne = createTool({ - name: "mounts-one", - description: - "Retrieves detailed information about a specific mount by its ID. Returns the mount type, paths, content (for file mounts), and associated service information.", - schema: z.object({ - mountId: z - .string() - .describe("The unique identifier of the mount to retrieve. Required."), - }), - annotations: { - title: "Get Mount", - readOnlyHint: true, - destructiveHint: false, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.get( - `/mounts.one?mountId=${input.mountId}` - ); - - if (!response?.data) { - return ResponseFormatter.error( - "Failed to fetch mount", - `Mount with ID "${input.mountId}" not found` - ); - } - - return ResponseFormatter.success( - `Successfully retrieved mount ${input.mountId}`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/mounts/mountsRemove.ts b/src/mcp/tools/mounts/mountsRemove.ts deleted file mode 100644 index 45cd558..0000000 --- a/src/mcp/tools/mounts/mountsRemove.ts +++ /dev/null @@ -1,30 +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 mountsRemove = createTool({ - name: "mounts-remove", - description: - "Removes/deletes a mount from a service in Dokploy. This will detach the mount from the service. For file mounts, the file will no longer be available inside the container after the next deployment.", - schema: z.object({ - mountId: z - .string() - .describe("The unique identifier of the mount to remove. Required."), - }), - annotations: { - title: "Remove Mount", - readOnlyHint: false, - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/mounts.remove", input); - - return ResponseFormatter.success( - `Successfully removed mount ${input.mountId}`, - response?.data ?? { removed: true } - ); - }, -}); diff --git a/src/mcp/tools/mounts/mountsUpdate.ts b/src/mcp/tools/mounts/mountsUpdate.ts deleted file mode 100644 index b8c8af8..0000000 --- a/src/mcp/tools/mounts/mountsUpdate.ts +++ /dev/null @@ -1,102 +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 mountsUpdate = createTool({ - name: "mounts-update", - description: - "Updates an existing mount configuration in Dokploy. Can modify the mount type, paths, and content. For file mounts, you can update the file content directly. Use this to edit configuration files, scripts, or any mounted file content. IMPORTANT: For file mounts, mountPath must include the filename.", - schema: z.object({ - mountId: z - .string() - .min(1) - .describe("The unique identifier of the mount to update. Required."), - type: z - .enum(["bind", "volume", "file"]) - .optional() - .describe("New mount type: 'file', 'bind', or 'volume'."), - mountPath: z - .string() - .min(1) - .optional() - .describe( - "New destination path inside the container. For file mounts: MUST include the filename." - ), - hostPath: z - .string() - .nullable() - .optional() - .describe("New host path for bind mounts."), - volumeName: z - .string() - .nullable() - .optional() - .describe("New volume name for volume mounts."), - content: z - .string() - .nullable() - .optional() - .describe("New file content for file mounts. Use this to edit the file contents."), - filePath: z - .string() - .nullable() - .optional() - .describe("New filename reference for file mounts."), - serviceType: z - .enum(["application", "postgres", "mysql", "mariadb", "mongo", "redis", "compose"]) - .default("application") - .optional() - .describe("Service type this mount belongs to."), - applicationId: z - .string() - .nullable() - .optional() - .describe("Application ID if reassigning to a different application."), - postgresId: z - .string() - .nullable() - .optional() - .describe("PostgreSQL database ID if reassigning to a different database."), - mariadbId: z - .string() - .nullable() - .optional() - .describe("MariaDB database ID if reassigning to a different database."), - mongoId: z - .string() - .nullable() - .optional() - .describe("MongoDB database ID if reassigning to a different database."), - mysqlId: z - .string() - .nullable() - .optional() - .describe("MySQL database ID if reassigning to a different database."), - redisId: z - .string() - .nullable() - .optional() - .describe("Redis database ID if reassigning to a different database."), - composeId: z - .string() - .nullable() - .optional() - .describe("Compose stack ID if reassigning to a different compose stack."), - }), - annotations: { - title: "Update Mount", - readOnlyHint: false, - destructiveHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/mounts.update", input); - - return ResponseFormatter.success( - `Successfully updated mount ${input.mountId}`, - response?.data ?? { updated: true } - ); - }, -}); 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/notification/index.ts b/src/mcp/tools/notification/index.ts deleted file mode 100644 index 420c8e6..0000000 --- a/src/mcp/tools/notification/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -export { notificationCreate } from "./notificationCreate.js"; -export { notificationUpdate } from "./notificationUpdate.js"; -export { notificationTestConnection } from "./notificationTestConnection.js"; -export { notificationRemove } from "./notificationRemove.js"; -export { notificationOne } from "./notificationOne.js"; -export { notificationAll } from "./notificationAll.js"; -export { notificationReceiveNotification } from "./notificationReceiveNotification.js"; -export { notificationGetEmailProviders } from "./notificationGetEmailProviders.js"; diff --git a/src/mcp/tools/notification/notificationAll.ts b/src/mcp/tools/notification/notificationAll.ts deleted file mode 100644 index 8b5755f..0000000 --- a/src/mcp/tools/notification/notificationAll.ts +++ /dev/null @@ -1,24 +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 notificationAll = createTool({ - name: "notification-all", - description: "Gets all notification configurations in Dokploy.", - schema: z.object({}), - annotations: { - title: "List All Notifications", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const notifications = await apiClient.get("/notification.all"); - - return ResponseFormatter.success( - "Successfully fetched all notifications", - notifications.data, - ); - }, -}); diff --git a/src/mcp/tools/notification/notificationCreate.ts b/src/mcp/tools/notification/notificationCreate.ts deleted file mode 100644 index 8f3a8e0..0000000 --- a/src/mcp/tools/notification/notificationCreate.ts +++ /dev/null @@ -1,324 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -// Common notification settings shared by all providers (based on OpenAPI spec) -const commonNotificationSettings = { - name: z.string().describe("The name of the notification."), - appBuildError: z.boolean().describe("Notify on application build errors."), - databaseBackup: z.boolean().describe("Notify on database backup events."), - dokployRestart: z.boolean().describe("Notify on Dokploy restart events."), - appDeploy: z.boolean().describe("Notify on application deployment events."), - dockerCleanup: z.boolean().describe("Notify on Docker cleanup events."), -}; - -// Provider-specific configurations (matching OpenAPI spec exactly) -const slackConfig = z.object({ - provider: z.literal("slack"), - ...commonNotificationSettings, - serverThreshold: z - .boolean() - .describe("Notify when server thresholds are exceeded."), - webhookUrl: z - .string() - .min(1) - .describe("The Slack webhook URL for sending notifications."), - channel: z.string().describe("The Slack channel to send notifications to."), -}); - -const discordConfig = z.object({ - provider: z.literal("discord"), - ...commonNotificationSettings, - serverThreshold: z - .boolean() - .describe("Notify when server thresholds are exceeded."), - webhookUrl: z - .string() - .min(1) - .describe("The Discord webhook URL for sending notifications."), - decoration: z - .boolean() - .describe("Whether to use rich embed formatting for messages."), -}); - -const telegramConfig = z.object({ - provider: z.literal("telegram"), - ...commonNotificationSettings, - serverThreshold: z - .boolean() - .describe("Notify when server thresholds are exceeded."), - botToken: z - .string() - .min(1) - .describe("The Telegram bot token for sending notifications."), - chatId: z - .string() - .min(1) - .describe("The Telegram chat ID to send notifications to."), - messageThreadId: z - .string() - .describe( - "The message thread ID for topic-based chats. Required but can be empty string.", - ), -}); - -const emailConfig = z.object({ - provider: z.literal("email"), - ...commonNotificationSettings, - serverThreshold: z - .boolean() - .describe("Notify when server thresholds are exceeded."), - smtpServer: z - .string() - .min(1) - .describe("The SMTP server hostname for sending emails."), - smtpPort: z.number().min(1).describe("The SMTP server port."), - username: z.string().min(1).describe("The SMTP authentication username."), - password: z.string().min(1).describe("The SMTP authentication password."), - fromAddress: z - .string() - .min(1) - .describe("The email address to send notifications from."), - toAddresses: z - .array(z.string()) - .min(1) - .describe("The email addresses to send notifications to."), -}); - -// Note: Gotify does NOT have serverThreshold according to OpenAPI spec -const gotifyConfig = z.object({ - provider: z.literal("gotify"), - ...commonNotificationSettings, - serverUrl: z - .string() - .min(1) - .describe("The Gotify server URL for sending notifications."), - appToken: z - .string() - .min(1) - .describe("The Gotify application token for authentication."), - priority: z - .number() - .min(1) - .describe("The priority level for Gotify messages (minimum 1)."), - decoration: z - .boolean() - .describe("Whether to use rich formatting for messages."), -}); - -// Note: Ntfy does NOT have serverThreshold according to OpenAPI spec -const ntfyConfig = z.object({ - provider: z.literal("ntfy"), - ...commonNotificationSettings, - serverUrl: z - .string() - .min(1) - .describe("The Ntfy server URL for sending notifications."), - topic: z.string().min(1).describe("The Ntfy topic to send notifications to."), - accessToken: z - .string() - .min(1) - .describe("The Ntfy access token for authentication."), - priority: z - .number() - .min(1) - .describe("The priority level for Ntfy messages (minimum 1)."), -}); - -const larkConfig = z.object({ - provider: z.literal("lark"), - ...commonNotificationSettings, - serverThreshold: z - .boolean() - .describe("Notify when server thresholds are exceeded."), - webhookUrl: z - .string() - .min(1) - .describe("The Lark webhook URL for sending notifications."), -}); - -// Discriminated union of all provider configs -const notificationCreateSchema = z.discriminatedUnion("provider", [ - slackConfig, - discordConfig, - telegramConfig, - emailConfig, - gotifyConfig, - ntfyConfig, - larkConfig, -]); - -type NotificationCreateInput = z.infer; - -// Map provider to API endpoint suffix -const providerEndpointMap: Record = - { - slack: "Slack", - discord: "Discord", - telegram: "Telegram", - email: "Email", - gotify: "Gotify", - ntfy: "Ntfy", - lark: "Lark", - }; - -export const notificationCreate = createTool({ - name: "notification-create", - description: `Creates a new notification configuration in Dokploy. Supports multiple providers: slack, discord, telegram, email, gotify, ntfy, lark. Each provider requires different configuration fields. - -Common required fields for all providers: -- name, appBuildError, databaseBackup, dokployRestart, appDeploy, dockerCleanup - -Provider-specific required fields: -- slack: webhookUrl, channel, serverThreshold -- discord: webhookUrl, decoration, serverThreshold -- telegram: botToken, chatId, messageThreadId (can be empty string), serverThreshold -- email: smtpServer, smtpPort, username, password, fromAddress, toAddresses, serverThreshold -- gotify: serverUrl, appToken, priority, decoration (NO serverThreshold) -- ntfy: serverUrl, topic, accessToken, priority (NO serverThreshold) -- lark: webhookUrl, serverThreshold`, - schema: z.object({ - provider: z - .enum(["slack", "discord", "telegram", "email", "gotify", "ntfy", "lark"]) - .describe("The notification provider type."), - name: z.string().describe("The name of the notification."), - appBuildError: z.boolean().describe("Notify on application build errors."), - databaseBackup: z.boolean().describe("Notify on database backup events."), - dokployRestart: z.boolean().describe("Notify on Dokploy restart events."), - appDeploy: z - .boolean() - .describe("Notify on application deployment events."), - dockerCleanup: z.boolean().describe("Notify on Docker cleanup events."), - // Provider-specific fields (all optional at schema level, validated in handler) - webhookUrl: z - .string() - .min(1) - .optional() - .describe( - "Webhook URL (required for slack, discord, lark providers).", - ), - channel: z - .string() - .optional() - .describe("Slack channel (required for slack provider)."), - decoration: z - .boolean() - .optional() - .describe( - "Rich formatting (required for discord, gotify providers).", - ), - serverThreshold: z - .boolean() - .optional() - .describe( - "Notify on server threshold (required for slack, discord, telegram, email, lark). NOT used by gotify or ntfy.", - ), - botToken: z - .string() - .min(1) - .optional() - .describe("Telegram bot token (required for telegram provider)."), - chatId: z - .string() - .min(1) - .optional() - .describe("Telegram chat ID (required for telegram provider)."), - messageThreadId: z - .string() - .optional() - .describe( - "Telegram message thread ID (required for telegram provider, can be empty string).", - ), - smtpServer: z - .string() - .min(1) - .optional() - .describe("SMTP server hostname (required for email provider)."), - smtpPort: z - .number() - .min(1) - .optional() - .describe("SMTP port (required for email provider, minimum 1)."), - username: z - .string() - .min(1) - .optional() - .describe("SMTP username (required for email provider)."), - password: z - .string() - .min(1) - .optional() - .describe("SMTP password (required for email provider)."), - fromAddress: z - .string() - .min(1) - .optional() - .describe("From email address (required for email provider)."), - toAddresses: z - .array(z.string()) - .min(1) - .optional() - .describe("To email addresses array (required for email provider)."), - serverUrl: z - .string() - .min(1) - .optional() - .describe("Server URL (required for gotify, ntfy providers)."), - appToken: z - .string() - .min(1) - .optional() - .describe("Application token (required for gotify provider)."), - priority: z - .number() - .min(1) - .optional() - .describe("Priority level, minimum 1 (required for gotify, ntfy providers)."), - topic: z - .string() - .min(1) - .optional() - .describe("Ntfy topic (required for ntfy provider)."), - accessToken: z - .string() - .min(1) - .optional() - .describe("Access token (required for ntfy provider)."), - }), - annotations: { - title: "Create Notification", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - // Validate with discriminated union schema for better type safety - const validationResult = notificationCreateSchema.safeParse(input); - if (!validationResult.success) { - const errorMessages = validationResult.error.errors - .map((err) => `${err.path.join(".")}: ${err.message}`) - .join(", "); - return ResponseFormatter.error( - `Invalid input for ${input.provider} notification`, - `Validation errors: ${errorMessages}`, - ); - } - - const validatedInput = validationResult.data; - const endpointSuffix = providerEndpointMap[validatedInput.provider]; - - // Remove 'provider' from the payload sent to API - const { provider, ...apiPayload } = validatedInput; - - const response = await apiClient.post( - `/notification.create${endpointSuffix}`, - apiPayload, - ); - - return ResponseFormatter.success( - `${provider.charAt(0).toUpperCase() + provider.slice(1)} notification "${input.name}" created successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/notification/notificationGetEmailProviders.ts b/src/mcp/tools/notification/notificationGetEmailProviders.ts deleted file mode 100644 index 93e7745..0000000 --- a/src/mcp/tools/notification/notificationGetEmailProviders.ts +++ /dev/null @@ -1,24 +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 notificationGetEmailProviders = createTool({ - name: "notification-get-email-providers", - description: "Gets the list of available email providers for notifications.", - schema: z.object({}), - annotations: { - title: "Get Email Providers", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const providers = await apiClient.get("/notification.getEmailProviders"); - - return ResponseFormatter.success( - "Successfully fetched email providers", - providers.data, - ); - }, -}); diff --git a/src/mcp/tools/notification/notificationOne.ts b/src/mcp/tools/notification/notificationOne.ts deleted file mode 100644 index 9397d21..0000000 --- a/src/mcp/tools/notification/notificationOne.ts +++ /dev/null @@ -1,38 +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 notificationOne = createTool({ - name: "notification-one", - description: - "Gets a specific notification configuration by its ID in Dokploy.", - schema: z.object({ - notificationId: z - .string() - .describe("The ID of the notification to retrieve."), - }), - annotations: { - title: "Get Notification Details", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const notification = await apiClient.get( - `/notification.one?notificationId=${input.notificationId}`, - ); - - if (!notification?.data) { - return ResponseFormatter.error( - "Failed to fetch notification", - `Notification with ID "${input.notificationId}" not found`, - ); - } - - return ResponseFormatter.success( - `Successfully fetched notification "${input.notificationId}"`, - notification.data, - ); - }, -}); diff --git a/src/mcp/tools/notification/notificationReceiveNotification.ts b/src/mcp/tools/notification/notificationReceiveNotification.ts deleted file mode 100644 index 17df83f..0000000 --- a/src/mcp/tools/notification/notificationReceiveNotification.ts +++ /dev/null @@ -1,44 +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 notificationReceiveNotification = createTool({ - name: "notification-receive-notification", - description: - "Receives and processes a notification event (typically from server monitoring).", - schema: z.object({ - ServerType: z - .enum(["Dokploy", "Remote"]) - .default("Dokploy") - .optional() - .describe("The type of server sending the notification."), - Type: z - .enum(["Memory", "CPU"]) - .describe("The type of threshold notification."), - Value: z.number().describe("The current value that triggered the alert."), - Threshold: z.number().describe("The threshold that was exceeded."), - Message: z.string().describe("The notification message."), - Timestamp: z.string().describe("The timestamp of the event."), - Token: z - .string() - .describe("The authentication token for the notification."), - }), - annotations: { - title: "Receive Notification", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post( - "/notification.receiveNotification", - input, - ); - - return ResponseFormatter.success( - "Notification received and processed successfully", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/notification/notificationRemove.ts b/src/mcp/tools/notification/notificationRemove.ts deleted file mode 100644 index 0ed1d4a..0000000 --- a/src/mcp/tools/notification/notificationRemove.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 notificationRemove = createTool({ - name: "notification-remove", - description: "Removes/deletes a notification configuration from Dokploy.", - schema: z.object({ - notificationId: z - .string() - .describe("The ID of the notification to remove."), - }), - annotations: { - title: "Remove Notification", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/notification.remove", input); - - return ResponseFormatter.success( - `Notification "${input.notificationId}" removed successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/notification/notificationTestConnection.ts b/src/mcp/tools/notification/notificationTestConnection.ts deleted file mode 100644 index 7dd1de4..0000000 --- a/src/mcp/tools/notification/notificationTestConnection.ts +++ /dev/null @@ -1,254 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -// Provider-specific test connection configurations (matching OpenAPI spec exactly) -const slackTestConfig = z.object({ - provider: z.literal("slack"), - webhookUrl: z.string().min(1).describe("The Slack webhook URL to test."), - channel: z.string().describe("The Slack channel to test sending to."), -}); - -const discordTestConfig = z.object({ - provider: z.literal("discord"), - webhookUrl: z.string().min(1).describe("The Discord webhook URL to test."), - decoration: z - .boolean() - .optional() - .describe("Whether to use rich embed formatting for the test message."), -}); - -const telegramTestConfig = z.object({ - provider: z.literal("telegram"), - botToken: z.string().min(1).describe("The Telegram bot token to test."), - chatId: z - .string() - .min(1) - .describe("The Telegram chat ID to test sending to."), - messageThreadId: z - .string() - .describe( - "The message thread ID for topic-based chats. Required but can be empty string.", - ), -}); - -const emailTestConfig = z.object({ - provider: z.literal("email"), - smtpServer: z.string().min(1).describe("The SMTP server hostname to test."), - smtpPort: z.number().min(1).describe("The SMTP server port (minimum 1)."), - username: z.string().min(1).describe("The SMTP authentication username."), - password: z.string().min(1).describe("The SMTP authentication password."), - toAddresses: z - .array(z.string()) - .min(1) - .describe("The email addresses to send the test to."), - fromAddress: z - .string() - .min(1) - .describe("The email address to send the test from."), -}); - -const gotifyTestConfig = z.object({ - provider: z.literal("gotify"), - serverUrl: z.string().min(1).describe("The Gotify server URL to test."), - appToken: z - .string() - .min(1) - .describe("The Gotify application token to test."), - priority: z - .number() - .min(1) - .describe("The priority level for the test message (minimum 1)."), - decoration: z - .boolean() - .optional() - .describe("Whether to use rich formatting for the test message."), -}); - -const ntfyTestConfig = z.object({ - provider: z.literal("ntfy"), - serverUrl: z.string().min(1).describe("The Ntfy server URL to test."), - topic: z.string().min(1).describe("The Ntfy topic to test sending to."), - accessToken: z - .string() - .min(1) - .describe("The Ntfy access token for authentication."), - priority: z - .number() - .min(1) - .describe("The priority level for the test message (minimum 1)."), -}); - -const larkTestConfig = z.object({ - provider: z.literal("lark"), - webhookUrl: z.string().min(1).describe("The Lark webhook URL to test."), -}); - -// Discriminated union of all provider test configs -const notificationTestConnectionSchema = z.discriminatedUnion("provider", [ - slackTestConfig, - discordTestConfig, - telegramTestConfig, - emailTestConfig, - gotifyTestConfig, - ntfyTestConfig, - larkTestConfig, -]); - -type NotificationTestConnectionInput = z.infer< - typeof notificationTestConnectionSchema ->; - -// Map provider to API endpoint suffix -const providerEndpointMap: Record< - NotificationTestConnectionInput["provider"], - string -> = { - slack: "Slack", - discord: "Discord", - telegram: "Telegram", - email: "Email", - gotify: "Gotify", - ntfy: "Ntfy", - lark: "Lark", -}; - -export const notificationTestConnection = createTool({ - name: "notification-test-connection", - description: `Tests a notification provider connection in Dokploy by sending a test message. Supports multiple providers: slack, discord, telegram, email, gotify, ntfy, lark. Each provider requires different configuration fields. - -Provider-specific required fields: -- slack: webhookUrl, channel -- discord: webhookUrl (decoration optional) -- telegram: botToken, chatId, messageThreadId (can be empty string) -- email: smtpServer, smtpPort, username, password, fromAddress, toAddresses -- gotify: serverUrl, appToken, priority (decoration optional) -- ntfy: serverUrl, topic, accessToken, priority -- lark: webhookUrl`, - schema: z.object({ - provider: z - .enum(["slack", "discord", "telegram", "email", "gotify", "ntfy", "lark"]) - .describe("The notification provider type."), - // Provider-specific fields (all optional at schema level, validated in handler) - webhookUrl: z - .string() - .min(1) - .optional() - .describe("Webhook URL (required for slack, discord, lark providers)."), - channel: z - .string() - .optional() - .describe("Slack channel (required for slack provider)."), - decoration: z - .boolean() - .optional() - .describe("Rich formatting (optional for discord, gotify providers)."), - botToken: z - .string() - .min(1) - .optional() - .describe("Telegram bot token (required for telegram provider)."), - chatId: z - .string() - .min(1) - .optional() - .describe("Telegram chat ID (required for telegram provider)."), - messageThreadId: z - .string() - .optional() - .describe( - "Telegram message thread ID (required for telegram provider, can be empty string).", - ), - smtpServer: z - .string() - .min(1) - .optional() - .describe("SMTP server hostname (required for email provider)."), - smtpPort: z - .number() - .min(1) - .optional() - .describe("SMTP port, minimum 1 (required for email provider)."), - username: z - .string() - .min(1) - .optional() - .describe("SMTP username (required for email provider)."), - password: z - .string() - .min(1) - .optional() - .describe("SMTP password (required for email provider)."), - fromAddress: z - .string() - .min(1) - .optional() - .describe("From email address (required for email provider)."), - toAddresses: z - .array(z.string()) - .min(1) - .optional() - .describe("To email addresses array (required for email provider)."), - serverUrl: z - .string() - .min(1) - .optional() - .describe("Server URL (required for gotify, ntfy providers)."), - appToken: z - .string() - .min(1) - .optional() - .describe("Application token (required for gotify provider)."), - priority: z - .number() - .min(1) - .optional() - .describe("Priority level, minimum 1 (required for gotify, ntfy providers)."), - topic: z - .string() - .min(1) - .optional() - .describe("Ntfy topic (required for ntfy provider)."), - accessToken: z - .string() - .min(1) - .optional() - .describe("Access token (required for ntfy provider)."), - }), - annotations: { - title: "Test Notification Connection", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - // Validate with discriminated union schema for better type safety - const validationResult = notificationTestConnectionSchema.safeParse(input); - if (!validationResult.success) { - const errorMessages = validationResult.error.errors - .map((err) => `${err.path.join(".")}: ${err.message}`) - .join(", "); - return ResponseFormatter.error( - `Invalid input for ${input.provider} connection test`, - `Validation errors: ${errorMessages}`, - ); - } - - const validatedInput = validationResult.data; - const endpointSuffix = providerEndpointMap[validatedInput.provider]; - - // Remove 'provider' from the payload sent to API - const { provider, ...apiPayload } = validatedInput; - - const response = await apiClient.post( - `/notification.test${endpointSuffix}Connection`, - apiPayload, - ); - - return ResponseFormatter.success( - `${provider.charAt(0).toUpperCase() + provider.slice(1)} connection test completed successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/notification/notificationUpdate.ts b/src/mcp/tools/notification/notificationUpdate.ts deleted file mode 100644 index 5ac1297..0000000 --- a/src/mcp/tools/notification/notificationUpdate.ts +++ /dev/null @@ -1,433 +0,0 @@ -import { z } from "zod"; -import apiClient from "../../../utils/apiClient.js"; -import { ResponseFormatter } from "../../../utils/responseFormatter.js"; -import { createTool } from "../toolFactory.js"; - -// Common notification settings shared by all providers (all optional for updates, based on OpenAPI spec) -const commonUpdateSettings = { - notificationId: z - .string() - .min(1) - .describe("The ID of the notification to update."), - name: z.string().optional().describe("The name of the notification."), - appBuildError: z - .boolean() - .optional() - .describe("Notify on application build errors."), - databaseBackup: z - .boolean() - .optional() - .describe("Notify on database backup events."), - dokployRestart: z - .boolean() - .optional() - .describe("Notify on Dokploy restart events."), - appDeploy: z - .boolean() - .optional() - .describe("Notify on application deployment events."), - dockerCleanup: z - .boolean() - .optional() - .describe("Notify on Docker cleanup events."), - organizationId: z.string().optional().describe("The organization ID."), -}; - -// Provider-specific configurations for updates (matching OpenAPI spec exactly) -// Note: slackId has no minLength in OpenAPI spec (can be empty string) -const slackUpdateConfig = z.object({ - provider: z.literal("slack"), - ...commonUpdateSettings, - slackId: z.string().describe("The Slack configuration ID."), - serverThreshold: z - .boolean() - .optional() - .describe("Notify when server thresholds are exceeded."), - webhookUrl: z - .string() - .min(1) - .optional() - .describe("The Slack webhook URL for sending notifications."), - channel: z - .string() - .optional() - .describe("The Slack channel to send notifications to."), -}); - -const discordUpdateConfig = z.object({ - provider: z.literal("discord"), - ...commonUpdateSettings, - discordId: z.string().min(1).describe("The Discord configuration ID."), - serverThreshold: z - .boolean() - .optional() - .describe("Notify when server thresholds are exceeded."), - webhookUrl: z - .string() - .min(1) - .optional() - .describe("The Discord webhook URL for sending notifications."), - decoration: z - .boolean() - .optional() - .describe("Whether to use rich embed formatting for messages."), -}); - -const telegramUpdateConfig = z.object({ - provider: z.literal("telegram"), - ...commonUpdateSettings, - telegramId: z.string().min(1).describe("The Telegram configuration ID."), - serverThreshold: z - .boolean() - .optional() - .describe("Notify when server thresholds are exceeded."), - botToken: z - .string() - .min(1) - .optional() - .describe("The Telegram bot token for sending notifications."), - chatId: z - .string() - .min(1) - .optional() - .describe("The Telegram chat ID to send notifications to."), - messageThreadId: z - .string() - .optional() - .describe("The message thread ID for topic-based chats."), -}); - -const emailUpdateConfig = z.object({ - provider: z.literal("email"), - ...commonUpdateSettings, - emailId: z.string().min(1).describe("The Email configuration ID."), - serverThreshold: z - .boolean() - .optional() - .describe("Notify when server thresholds are exceeded."), - smtpServer: z - .string() - .min(1) - .optional() - .describe("The SMTP server hostname for sending emails."), - smtpPort: z - .number() - .min(1) - .optional() - .describe("The SMTP server port (minimum 1)."), - username: z - .string() - .min(1) - .optional() - .describe("The SMTP authentication username."), - password: z - .string() - .min(1) - .optional() - .describe("The SMTP authentication password."), - fromAddress: z - .string() - .min(1) - .optional() - .describe("The email address to send notifications from."), - toAddresses: z - .array(z.string()) - .min(1) - .optional() - .describe("The email addresses to send notifications to."), -}); - -// Note: Gotify does NOT have serverThreshold according to OpenAPI spec -const gotifyUpdateConfig = z.object({ - provider: z.literal("gotify"), - ...commonUpdateSettings, - gotifyId: z.string().min(1).describe("The Gotify configuration ID."), - serverUrl: z - .string() - .min(1) - .optional() - .describe("The Gotify server URL for sending notifications."), - appToken: z - .string() - .min(1) - .optional() - .describe("The Gotify application token for authentication."), - priority: z - .number() - .min(1) - .optional() - .describe("The priority level for Gotify messages (minimum 1)."), - decoration: z - .boolean() - .optional() - .describe("Whether to use rich formatting for messages."), -}); - -// Note: Ntfy does NOT have serverThreshold according to OpenAPI spec -const ntfyUpdateConfig = z.object({ - provider: z.literal("ntfy"), - ...commonUpdateSettings, - ntfyId: z.string().min(1).describe("The Ntfy configuration ID."), - serverUrl: z - .string() - .min(1) - .optional() - .describe("The Ntfy server URL for sending notifications."), - topic: z - .string() - .min(1) - .optional() - .describe("The Ntfy topic to send notifications to."), - accessToken: z - .string() - .min(1) - .optional() - .describe("The Ntfy access token for authentication."), - priority: z - .number() - .min(1) - .optional() - .describe("The priority level for Ntfy messages (minimum 1)."), -}); - -const larkUpdateConfig = z.object({ - provider: z.literal("lark"), - ...commonUpdateSettings, - larkId: z.string().min(1).describe("The Lark configuration ID."), - serverThreshold: z - .boolean() - .optional() - .describe("Notify when server thresholds are exceeded."), - webhookUrl: z - .string() - .min(1) - .optional() - .describe("The Lark webhook URL for sending notifications."), -}); - -// Discriminated union of all provider update configs -const notificationUpdateSchema = z.discriminatedUnion("provider", [ - slackUpdateConfig, - discordUpdateConfig, - telegramUpdateConfig, - emailUpdateConfig, - gotifyUpdateConfig, - ntfyUpdateConfig, - larkUpdateConfig, -]); - -type NotificationUpdateInput = z.infer; - -// Map provider to API endpoint suffix -const providerEndpointMap: Record = - { - slack: "Slack", - discord: "Discord", - telegram: "Telegram", - email: "Email", - gotify: "Gotify", - ntfy: "Ntfy", - lark: "Lark", - }; - -export const notificationUpdate = createTool({ - name: "notification-update", - description: `Updates an existing notification configuration in Dokploy. Supports multiple providers: slack, discord, telegram, email, gotify, ntfy, lark. Each provider requires notificationId and a provider-specific ID field. - -Required fields for all providers: -- notificationId: The notification ID to update -- provider-specific ID (slackId, discordId, telegramId, emailId, gotifyId, ntfyId, larkId) - -Note: serverThreshold is available for slack, discord, telegram, email, lark but NOT for gotify or ntfy. - -All other fields are optional and only the provided fields will be updated.`, - schema: z.object({ - provider: z - .enum(["slack", "discord", "telegram", "email", "gotify", "ntfy", "lark"]) - .describe("The notification provider type."), - notificationId: z - .string() - .min(1) - .describe("The ID of the notification to update."), - // Provider-specific ID fields (all optional at schema level, validated in handler) - // Note: slackId has no minLength in OpenAPI spec - slackId: z - .string() - .optional() - .describe("The Slack configuration ID (required for slack provider)."), - discordId: z - .string() - .min(1) - .optional() - .describe( - "The Discord configuration ID (required for discord provider).", - ), - telegramId: z - .string() - .min(1) - .optional() - .describe( - "The Telegram configuration ID (required for telegram provider).", - ), - emailId: z - .string() - .min(1) - .optional() - .describe("The Email configuration ID (required for email provider)."), - gotifyId: z - .string() - .min(1) - .optional() - .describe("The Gotify configuration ID (required for gotify provider)."), - ntfyId: z - .string() - .min(1) - .optional() - .describe("The Ntfy configuration ID (required for ntfy provider)."), - larkId: z - .string() - .min(1) - .optional() - .describe("The Lark configuration ID (required for lark provider)."), - // Common optional fields - name: z.string().optional().describe("The name of the notification."), - appBuildError: z - .boolean() - .optional() - .describe("Notify on application build errors."), - databaseBackup: z - .boolean() - .optional() - .describe("Notify on database backup events."), - dokployRestart: z - .boolean() - .optional() - .describe("Notify on Dokploy restart events."), - appDeploy: z - .boolean() - .optional() - .describe("Notify on application deployment events."), - dockerCleanup: z - .boolean() - .optional() - .describe("Notify on Docker cleanup events."), - organizationId: z.string().optional().describe("The organization ID."), - // Provider-specific optional fields - webhookUrl: z - .string() - .min(1) - .optional() - .describe("Webhook URL (for slack, discord, lark providers)."), - channel: z.string().optional().describe("Slack channel (for slack)."), - decoration: z - .boolean() - .optional() - .describe("Rich formatting (for discord, gotify)."), - serverThreshold: z - .boolean() - .optional() - .describe( - "Notify on server threshold (for slack, discord, telegram, email, lark). NOT available for gotify or ntfy.", - ), - botToken: z - .string() - .min(1) - .optional() - .describe("Telegram bot token (for telegram)."), - chatId: z - .string() - .min(1) - .optional() - .describe("Telegram chat ID (for telegram)."), - messageThreadId: z - .string() - .optional() - .describe("Telegram message thread ID (for telegram)."), - smtpServer: z - .string() - .min(1) - .optional() - .describe("SMTP server hostname (for email)."), - smtpPort: z - .number() - .min(1) - .optional() - .describe("SMTP port, minimum 1 (for email)."), - username: z - .string() - .min(1) - .optional() - .describe("SMTP username (for email)."), - password: z - .string() - .min(1) - .optional() - .describe("SMTP password (for email)."), - fromAddress: z - .string() - .min(1) - .optional() - .describe("From email address (for email)."), - toAddresses: z - .array(z.string()) - .min(1) - .optional() - .describe("To email addresses array (for email)."), - serverUrl: z - .string() - .min(1) - .optional() - .describe("Server URL (for gotify, ntfy)."), - appToken: z - .string() - .min(1) - .optional() - .describe("Application token (for gotify)."), - priority: z - .number() - .min(1) - .optional() - .describe("Priority level, minimum 1 (for gotify, ntfy)."), - topic: z.string().min(1).optional().describe("Ntfy topic (for ntfy)."), - accessToken: z - .string() - .min(1) - .optional() - .describe("Access token (for ntfy)."), - }), - annotations: { - title: "Update Notification", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - // Validate with discriminated union schema for better type safety - const validationResult = notificationUpdateSchema.safeParse(input); - if (!validationResult.success) { - const errorMessages = validationResult.error.errors - .map((err) => `${err.path.join(".")}: ${err.message}`) - .join(", "); - return ResponseFormatter.error( - `Invalid input for ${input.provider} notification update`, - `Validation errors: ${errorMessages}`, - ); - } - - const validatedInput = validationResult.data; - const endpointSuffix = providerEndpointMap[validatedInput.provider]; - - // Remove 'provider' from the payload sent to API - const { provider, ...apiPayload } = validatedInput; - - const response = await apiClient.post( - `/notification.update${endpointSuffix}`, - apiPayload, - ); - - return ResponseFormatter.success( - `${provider.charAt(0).toUpperCase() + provider.slice(1)} notification "${input.notificationId}" updated successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/organization/index.ts b/src/mcp/tools/organization/index.ts deleted file mode 100644 index 42844a0..0000000 --- a/src/mcp/tools/organization/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -export { organizationAll } from "./organizationAll.js"; -export { organizationAllInvitations } from "./organizationAllInvitations.js"; -export { organizationCreate } from "./organizationCreate.js"; -export { organizationDelete } from "./organizationDelete.js"; -export { organizationOne } from "./organizationOne.js"; -export { organizationRemoveInvitation } from "./organizationRemoveInvitation.js"; -export { organizationSetDefault } from "./organizationSetDefault.js"; -export { organizationUpdate } from "./organizationUpdate.js"; diff --git a/src/mcp/tools/organization/organizationAll.ts b/src/mcp/tools/organization/organizationAll.ts deleted file mode 100644 index 27e4988..0000000 --- a/src/mcp/tools/organization/organizationAll.ts +++ /dev/null @@ -1,24 +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 organizationAll = createTool({ - name: "organization-all", - description: "Gets all organizations in Dokploy.", - schema: z.object({}), - annotations: { - title: "Get All Organizations", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.get("/organization.all"); - - return ResponseFormatter.success( - "Successfully fetched all organizations", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/organization/organizationAllInvitations.ts b/src/mcp/tools/organization/organizationAllInvitations.ts deleted file mode 100644 index c83ee70..0000000 --- a/src/mcp/tools/organization/organizationAllInvitations.ts +++ /dev/null @@ -1,24 +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 organizationAllInvitations = createTool({ - name: "organization-all-invitations", - description: "Gets all invitations for organizations in Dokploy.", - schema: z.object({}), - annotations: { - title: "Get All Organization Invitations", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.get("/organization.allInvitations"); - - return ResponseFormatter.success( - "Successfully fetched all organization invitations", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/organization/organizationCreate.ts b/src/mcp/tools/organization/organizationCreate.ts deleted file mode 100644 index 7827543..0000000 --- a/src/mcp/tools/organization/organizationCreate.ts +++ /dev/null @@ -1,30 +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 organizationCreate = createTool({ - name: "organization-create", - description: "Creates a new organization in Dokploy.", - schema: z.object({ - name: z.string().describe("The name of the organization. Required field."), - logo: z - .string() - .optional() - .describe("Optional logo URL or base64-encoded image for the organization."), - }), - annotations: { - title: "Create Organization", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/organization.create", input); - - return ResponseFormatter.success( - `Organization "${input.name}" created successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/organization/organizationDelete.ts b/src/mcp/tools/organization/organizationDelete.ts deleted file mode 100644 index da3adca..0000000 --- a/src/mcp/tools/organization/organizationDelete.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 organizationDelete = createTool({ - name: "organization-delete", - description: "Deletes an organization from Dokploy.", - schema: z.object({ - organizationId: z - .string() - .describe("The ID of the organization to delete."), - }), - annotations: { - title: "Delete Organization", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/organization.delete", input); - - return ResponseFormatter.success( - `Organization "${input.organizationId}" deleted successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/organization/organizationOne.ts b/src/mcp/tools/organization/organizationOne.ts deleted file mode 100644 index af665a4..0000000 --- a/src/mcp/tools/organization/organizationOne.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 organizationOne = createTool({ - name: "organization-one", - description: "Gets a specific organization by its ID in Dokploy.", - schema: z.object({ - organizationId: z - .string() - .describe("The ID of the organization to retrieve."), - }), - annotations: { - title: "Get Organization Details", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.get( - `/organization.one?organizationId=${input.organizationId}`, - ); - - if (!response?.data) { - return ResponseFormatter.error( - "Failed to fetch organization", - `Organization with ID "${input.organizationId}" not found`, - ); - } - - return ResponseFormatter.success( - `Successfully fetched organization "${input.organizationId}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/organization/organizationRemoveInvitation.ts b/src/mcp/tools/organization/organizationRemoveInvitation.ts deleted file mode 100644 index 481bcdb..0000000 --- a/src/mcp/tools/organization/organizationRemoveInvitation.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 organizationRemoveInvitation = createTool({ - name: "organization-remove-invitation", - description: "Removes an invitation from an organization in Dokploy.", - schema: z.object({ - invitationId: z.string().describe("The ID of the invitation to remove."), - }), - annotations: { - title: "Remove Organization Invitation", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post( - "/organization.removeInvitation", - input, - ); - - return ResponseFormatter.success( - `Invitation "${input.invitationId}" removed successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/organization/organizationSetDefault.ts b/src/mcp/tools/organization/organizationSetDefault.ts deleted file mode 100644 index ecd15b4..0000000 --- a/src/mcp/tools/organization/organizationSetDefault.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 organizationSetDefault = createTool({ - name: "organization-set-default", - description: "Sets the default organization for the current user in Dokploy.", - schema: z.object({ - organizationId: z - .string() - .min(1) - .describe("The ID of the organization to set as default."), - }), - annotations: { - title: "Set Default Organization", - destructiveHint: false, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/organization.setDefault", input); - - return ResponseFormatter.success( - `Organization "${input.organizationId}" set as default successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/organization/organizationUpdate.ts b/src/mcp/tools/organization/organizationUpdate.ts deleted file mode 100644 index 2edf4fb..0000000 --- a/src/mcp/tools/organization/organizationUpdate.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 organizationUpdate = createTool({ - name: "organization-update", - description: "Updates an existing organization in Dokploy.", - schema: z.object({ - organizationId: z - .string() - .describe("The ID of the organization to update. Required field."), - name: z - .string() - .describe("The new name for the organization. Required field."), - logo: z - .string() - .optional() - .describe( - "Optional new logo URL or base64-encoded image for the organization.", - ), - }), - annotations: { - title: "Update Organization", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/organization.update", input); - - return ResponseFormatter.success( - `Organization "${input.organizationId}" updated successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/port/index.ts b/src/mcp/tools/port/index.ts deleted file mode 100644 index 33f8ca7..0000000 --- a/src/mcp/tools/port/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { portCreate } from "./portCreate.js"; -export { portOne } from "./portOne.js"; -export { portDelete } from "./portDelete.js"; -export { portUpdate } from "./portUpdate.js"; diff --git a/src/mcp/tools/port/portCreate.ts b/src/mcp/tools/port/portCreate.ts deleted file mode 100644 index 9d1de81..0000000 --- a/src/mcp/tools/port/portCreate.ts +++ /dev/null @@ -1,47 +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 portCreate = createTool({ - name: "port-create", - description: "Creates a new port mapping for an application in Dokploy.", - schema: z.object({ - applicationId: z - .string() - .min(1) - .describe("The ID of the application to add the port mapping to. Required."), - publishedPort: z - .number() - .describe("The external port to expose on the host machine. Required."), - targetPort: z - .number() - .describe("The internal port the container is listening on. Required."), - publishMode: z - .enum(["ingress", "host"]) - .default("ingress") - .optional() - .describe( - "Port publish mode: 'ingress' for load-balanced routing across swarm nodes, 'host' for direct host port binding. Defaults to 'ingress'." - ), - protocol: z - .enum(["tcp", "udp"]) - .default("tcp") - .optional() - .describe("Network protocol for the port mapping: 'tcp' or 'udp'. Defaults to 'tcp'."), - }), - annotations: { - title: "Create Port Mapping", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/port.create", input); - - return ResponseFormatter.success( - `Port mapping ${input.publishedPort}:${input.targetPort} created successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/port/portDelete.ts b/src/mcp/tools/port/portDelete.ts deleted file mode 100644 index fd2239c..0000000 --- a/src/mcp/tools/port/portDelete.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 portDelete = createTool({ - name: "port-delete", - description: "Deletes a port mapping from Dokploy.", - schema: z.object({ - portId: z - .string() - .min(1) - .describe("The unique identifier of the port mapping to delete. Required."), - }), - annotations: { - title: "Delete Port Mapping", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/port.delete", input); - - return ResponseFormatter.success( - `Port mapping "${input.portId}" deleted successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/port/portOne.ts b/src/mcp/tools/port/portOne.ts deleted file mode 100644 index b3d38e3..0000000 --- a/src/mcp/tools/port/portOne.ts +++ /dev/null @@ -1,36 +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 portOne = createTool({ - name: "port-one", - description: "Gets a specific port mapping by its ID in Dokploy.", - schema: z.object({ - portId: z - .string() - .min(1) - .describe("The unique identifier of the port mapping to retrieve. Required."), - }), - annotations: { - title: "Get Port Mapping Details", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const port = await apiClient.get(`/port.one?portId=${input.portId}`); - - if (!port?.data) { - return ResponseFormatter.error( - "Failed to fetch port mapping", - `Port mapping with ID "${input.portId}" not found`, - ); - } - - return ResponseFormatter.success( - `Successfully fetched port mapping "${input.portId}"`, - port.data, - ); - }, -}); diff --git a/src/mcp/tools/port/portUpdate.ts b/src/mcp/tools/port/portUpdate.ts deleted file mode 100644 index 1312946..0000000 --- a/src/mcp/tools/port/portUpdate.ts +++ /dev/null @@ -1,44 +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 portUpdate = createTool({ - name: "port-update", - description: "Updates an existing port mapping in Dokploy.", - schema: z.object({ - portId: z.string().min(1).describe("The ID of the port mapping to update. Required."), - publishedPort: z - .number() - .describe("The external port to expose on the host machine. Required."), - targetPort: z - .number() - .describe("The internal port the container is listening on. Required."), - publishMode: z - .enum(["ingress", "host"]) - .default("ingress") - .optional() - .describe( - "Port publish mode: 'ingress' for load-balanced routing across swarm nodes, 'host' for direct host port binding. Defaults to 'ingress'." - ), - protocol: z - .enum(["tcp", "udp"]) - .default("tcp") - .optional() - .describe("Network protocol for the port mapping: 'tcp' or 'udp'. Defaults to 'tcp'."), - }), - annotations: { - title: "Update Port Mapping", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/port.update", input); - - return ResponseFormatter.success( - `Port mapping "${input.portId}" 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/previewDeployment/index.ts b/src/mcp/tools/previewDeployment/index.ts deleted file mode 100644 index 7119a39..0000000 --- a/src/mcp/tools/previewDeployment/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { previewDeploymentAll } from "./previewDeploymentAll.js"; -export { previewDeploymentDelete } from "./previewDeploymentDelete.js"; -export { previewDeploymentOne } from "./previewDeploymentOne.js"; diff --git a/src/mcp/tools/previewDeployment/previewDeploymentAll.ts b/src/mcp/tools/previewDeployment/previewDeploymentAll.ts deleted file mode 100644 index 23888d9..0000000 --- a/src/mcp/tools/previewDeployment/previewDeploymentAll.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 previewDeploymentAll = createTool({ - name: "preview-deployment-all", - description: "Gets all preview deployments for an application in Dokploy.", - schema: z.object({ - applicationId: z - .string() - .min(1) - .describe("The unique identifier of the application to get preview deployments for. Required."), - }), - annotations: { - title: "Get All Preview Deployments", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.get( - `/previewDeployment.all?applicationId=${input.applicationId}`, - ); - - return ResponseFormatter.success( - `Successfully fetched preview deployments for application "${input.applicationId}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/previewDeployment/previewDeploymentDelete.ts b/src/mcp/tools/previewDeployment/previewDeploymentDelete.ts deleted file mode 100644 index dd6d76a..0000000 --- a/src/mcp/tools/previewDeployment/previewDeploymentDelete.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 previewDeploymentDelete = createTool({ - name: "preview-deployment-delete", - description: "Deletes a preview deployment in Dokploy.", - schema: z.object({ - previewDeploymentId: z - .string() - .describe("The unique identifier of the preview deployment to delete. Required."), - }), - annotations: { - title: "Delete Preview Deployment", - readOnlyHint: false, - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/previewDeployment.delete", { - previewDeploymentId: input.previewDeploymentId, - }); - - return ResponseFormatter.success( - `Successfully deleted preview deployment "${input.previewDeploymentId}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/previewDeployment/previewDeploymentOne.ts b/src/mcp/tools/previewDeployment/previewDeploymentOne.ts deleted file mode 100644 index 4c2bbac..0000000 --- a/src/mcp/tools/previewDeployment/previewDeploymentOne.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 previewDeploymentOne = createTool({ - name: "preview-deployment-one", - description: "Gets a specific preview deployment by its ID in Dokploy.", - schema: z.object({ - previewDeploymentId: z - .string() - .describe("The unique identifier of the preview deployment to retrieve. Required."), - }), - annotations: { - title: "Get Preview Deployment Details", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.get( - `/previewDeployment.one?previewDeploymentId=${input.previewDeploymentId}`, - ); - - if (!response?.data) { - return ResponseFormatter.error( - "Failed to fetch preview deployment", - `Preview deployment with ID "${input.previewDeploymentId}" not found`, - ); - } - - return ResponseFormatter.success( - `Successfully fetched preview deployment "${input.previewDeploymentId}"`, - 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/redirects/index.ts b/src/mcp/tools/redirects/index.ts deleted file mode 100644 index e05caa5..0000000 --- a/src/mcp/tools/redirects/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { redirectsCreate } from "./redirectsCreate.js"; -export { redirectsOne } from "./redirectsOne.js"; -export { redirectsDelete } from "./redirectsDelete.js"; -export { redirectsUpdate } from "./redirectsUpdate.js"; diff --git a/src/mcp/tools/redirects/redirectsCreate.ts b/src/mcp/tools/redirects/redirectsCreate.ts deleted file mode 100644 index 4c6e6da..0000000 --- a/src/mcp/tools/redirects/redirectsCreate.ts +++ /dev/null @@ -1,45 +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 redirectsCreate = createTool({ - name: "redirects-create", - description: "Creates a new redirect rule for an application in Dokploy.", - schema: z.object({ - applicationId: z - .string() - .describe("The ID of the application to add the redirect to. Required."), - regex: z - .string() - .min(1) - .describe( - "Regex pattern to match incoming request URLs. Uses Traefik regex syntax. Required." - ), - replacement: z - .string() - .min(1) - .describe( - "Replacement URL or path for matched requests. Can use capture groups from regex (e.g., $1). Required." - ), - permanent: z - .boolean() - .describe( - "Whether this is a permanent redirect (HTTP 301) or temporary (HTTP 302). Required." - ), - }), - annotations: { - title: "Create Redirect", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/redirects.create", input); - - return ResponseFormatter.success( - `Redirect rule created successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/redirects/redirectsDelete.ts b/src/mcp/tools/redirects/redirectsDelete.ts deleted file mode 100644 index 3c1e9c4..0000000 --- a/src/mcp/tools/redirects/redirectsDelete.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 redirectsDelete = createTool({ - name: "redirects-delete", - description: "Deletes a redirect rule from Dokploy.", - schema: z.object({ - redirectId: z - .string() - .min(1) - .describe("The unique identifier of the redirect rule to delete. Required."), - }), - annotations: { - title: "Delete Redirect", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/redirects.delete", input); - - return ResponseFormatter.success( - `Redirect "${input.redirectId}" deleted successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/redirects/redirectsOne.ts b/src/mcp/tools/redirects/redirectsOne.ts deleted file mode 100644 index d756568..0000000 --- a/src/mcp/tools/redirects/redirectsOne.ts +++ /dev/null @@ -1,38 +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 redirectsOne = createTool({ - name: "redirects-one", - description: "Gets a specific redirect rule by its ID in Dokploy.", - schema: z.object({ - redirectId: z - .string() - .min(1) - .describe("The unique identifier of the redirect rule to retrieve. Required."), - }), - annotations: { - title: "Get Redirect Details", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const redirect = await apiClient.get( - `/redirects.one?redirectId=${input.redirectId}`, - ); - - if (!redirect?.data) { - return ResponseFormatter.error( - "Failed to fetch redirect", - `Redirect with ID "${input.redirectId}" not found`, - ); - } - - return ResponseFormatter.success( - `Successfully fetched redirect "${input.redirectId}"`, - redirect.data, - ); - }, -}); diff --git a/src/mcp/tools/redirects/redirectsUpdate.ts b/src/mcp/tools/redirects/redirectsUpdate.ts deleted file mode 100644 index cbd76f6..0000000 --- a/src/mcp/tools/redirects/redirectsUpdate.ts +++ /dev/null @@ -1,46 +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 redirectsUpdate = createTool({ - name: "redirects-update", - description: "Updates an existing redirect rule in Dokploy.", - schema: z.object({ - redirectId: z - .string() - .min(1) - .describe("The ID of the redirect to update. Required."), - regex: z - .string() - .min(1) - .describe( - "Regex pattern to match incoming request URLs. Uses Traefik regex syntax. Required." - ), - replacement: z - .string() - .min(1) - .describe( - "Replacement URL or path for matched requests. Can use capture groups from regex (e.g., $1). Required." - ), - permanent: z - .boolean() - .describe( - "Whether this is a permanent redirect (HTTP 301) or temporary (HTTP 302). Required." - ), - }), - annotations: { - title: "Update Redirect", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/redirects.update", input); - - return ResponseFormatter.success( - `Redirect "${input.redirectId}" updated successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/registry/index.ts b/src/mcp/tools/registry/index.ts deleted file mode 100644 index 28f1431..0000000 --- a/src/mcp/tools/registry/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { registryCreate } from "./registryCreate.js"; -export { registryRemove } from "./registryRemove.js"; -export { registryUpdate } from "./registryUpdate.js"; -export { registryAll } from "./registryAll.js"; -export { registryOne } from "./registryOne.js"; -export { registryTestRegistry } from "./registryTestRegistry.js"; diff --git a/src/mcp/tools/registry/registryAll.ts b/src/mcp/tools/registry/registryAll.ts deleted file mode 100644 index 5cfb981..0000000 --- a/src/mcp/tools/registry/registryAll.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 registryAll = createTool({ - name: "registry-all", - description: "Lists all container registries in Dokploy.", - schema: z.object({}), - annotations: { - title: "List All Registries", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const registries = await apiClient.get("/registry.all"); - - if (!registries?.data) { - return ResponseFormatter.error( - "Failed to fetch registries", - "No registries found", - ); - } - - return ResponseFormatter.success( - `Successfully fetched all registries`, - registries.data, - ); - }, -}); diff --git a/src/mcp/tools/registry/registryCreate.ts b/src/mcp/tools/registry/registryCreate.ts deleted file mode 100644 index e96bf0f..0000000 --- a/src/mcp/tools/registry/registryCreate.ts +++ /dev/null @@ -1,44 +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 registryCreate = createTool({ - name: "registry-create", - description: "Creates a new container registry in Dokploy.", - schema: z.object({ - registryName: z.string().min(1).describe("A friendly name for the registry. Required."), - username: z - .string() - .min(1) - .describe("Username for registry authentication. Required."), - password: z - .string() - .min(1) - .describe("Password or access token for registry authentication. Required."), - registryUrl: z.string().describe("URL of the container registry (e.g., 'docker.io', 'ghcr.io', 'registry.example.com'). Required."), - registryType: z.enum(["cloud"]).describe("Type of the registry. Currently only 'cloud' is supported. Required."), - imagePrefix: z - .string() - .nullable() - .describe("Prefix for images in this registry (e.g., 'myorg' for 'myorg/image:tag'). Required, can be null."), - serverId: z - .string() - .optional() - .describe("Server ID to associate the registry with. Optional."), - }), - annotations: { - title: "Create Registry", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/registry.create", input); - - return ResponseFormatter.success( - `Registry "${input.registryName}" created successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/registry/registryOne.ts b/src/mcp/tools/registry/registryOne.ts deleted file mode 100644 index 17ffe15..0000000 --- a/src/mcp/tools/registry/registryOne.ts +++ /dev/null @@ -1,38 +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 registryOne = createTool({ - name: "registry-one", - description: "Gets a specific container registry by its ID in Dokploy.", - schema: z.object({ - registryId: z - .string() - .min(1) - .describe("The unique identifier of the registry to retrieve. Required."), - }), - annotations: { - title: "Get Registry Details", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const registry = await apiClient.get( - `/registry.one?registryId=${encodeURIComponent(input.registryId)}`, - ); - - if (!registry?.data) { - return ResponseFormatter.error( - "Failed to fetch registry", - `Registry with ID "${input.registryId}" not found`, - ); - } - - return ResponseFormatter.success( - `Successfully fetched registry "${input.registryId}"`, - registry.data, - ); - }, -}); diff --git a/src/mcp/tools/registry/registryRemove.ts b/src/mcp/tools/registry/registryRemove.ts deleted file mode 100644 index bfcf36c..0000000 --- a/src/mcp/tools/registry/registryRemove.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 registryRemove = createTool({ - name: "registry-remove", - description: "Removes/deletes a container registry from Dokploy.", - schema: z.object({ - registryId: z.string().min(1).describe("The unique identifier of the registry to remove. Required."), - }), - annotations: { - title: "Remove Registry", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/registry.remove", input); - - return ResponseFormatter.success( - `Registry "${input.registryId}" removed successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/registry/registryTestRegistry.ts b/src/mcp/tools/registry/registryTestRegistry.ts deleted file mode 100644 index e24dbce..0000000 --- a/src/mcp/tools/registry/registryTestRegistry.ts +++ /dev/null @@ -1,45 +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 registryTestRegistry = createTool({ - name: "registry-test", - description: "Tests connection to a container registry in Dokploy.", - schema: z.object({ - registryName: z.string().optional().describe("Name for the registry. Optional."), - username: z - .string() - .min(1) - .describe("Username for registry authentication. Required."), - password: z - .string() - .min(1) - .describe("Password or access token for registry authentication. Required."), - registryUrl: z.string().describe("URL of the container registry to test (e.g., 'docker.io', 'ghcr.io'). Required."), - registryType: z.enum(["cloud"]).describe("Type of the registry. Currently only 'cloud' is supported. Required."), - imagePrefix: z - .string() - .nullable() - .optional() - .describe("Prefix for images in this registry. Optional."), - serverId: z - .string() - .optional() - .describe("Server ID to run the test from. Optional."), - }), - annotations: { - title: "Test Registry Connection", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/registry.testRegistry", input); - - return ResponseFormatter.success( - `Registry connection test completed successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/registry/registryUpdate.ts b/src/mcp/tools/registry/registryUpdate.ts deleted file mode 100644 index a49484d..0000000 --- a/src/mcp/tools/registry/registryUpdate.ts +++ /dev/null @@ -1,64 +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 registryUpdate = createTool({ - name: "registry-update", - description: "Updates an existing container registry in Dokploy.", - schema: z.object({ - registryId: z.string().min(1).describe("The unique identifier of the registry to update. Required."), - registryName: z - .string() - .min(1) - .optional() - .describe("New friendly name for the registry. Optional."), - imagePrefix: z - .string() - .nullable() - .optional() - .describe("New prefix for images in this registry (e.g., 'myorg' for 'myorg/image:tag'). Optional, can be null."), - username: z - .string() - .min(1) - .optional() - .describe("New username for registry authentication. Optional."), - password: z - .string() - .min(1) - .optional() - .describe("New password or access token for registry authentication. Optional."), - registryUrl: z - .string() - .optional() - .describe("New URL of the container registry. Optional."), - createdAt: z.string().optional().describe("Creation timestamp. Usually not modified. Optional."), - registryType: z - .enum(["cloud"]) - .optional() - .describe("Type of the registry. Currently only 'cloud' is supported. Optional."), - organizationId: z - .string() - .min(1) - .optional() - .describe("Organization ID for the registry. Optional."), - serverId: z - .string() - .optional() - .describe("Server ID to associate the registry with. Optional."), - }), - annotations: { - title: "Update Registry", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/registry.update", input); - - return ResponseFormatter.success( - `Registry "${input.registryId}" updated successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/rollback/index.ts b/src/mcp/tools/rollback/index.ts deleted file mode 100644 index a784c07..0000000 --- a/src/mcp/tools/rollback/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { rollbackDelete } from "./rollbackDelete.js"; -export { rollbackRollback } from "./rollbackRollback.js"; diff --git a/src/mcp/tools/rollback/rollbackDelete.ts b/src/mcp/tools/rollback/rollbackDelete.ts deleted file mode 100644 index 65f8506..0000000 --- a/src/mcp/tools/rollback/rollbackDelete.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 rollbackDelete = createTool({ - name: "rollback-delete", - description: "Deletes a rollback entry in Dokploy.", - schema: z.object({ - rollbackId: z.string().min(1).describe("The unique identifier of the rollback entry to delete. Required."), - }), - annotations: { - title: "Delete Rollback", - readOnlyHint: false, - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/rollback.delete", { - rollbackId: input.rollbackId, - }); - - return ResponseFormatter.success( - `Successfully deleted rollback "${input.rollbackId}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/rollback/rollbackRollback.ts b/src/mcp/tools/rollback/rollbackRollback.ts deleted file mode 100644 index efb1306..0000000 --- a/src/mcp/tools/rollback/rollbackRollback.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 rollbackRollback = createTool({ - name: "rollback-rollback", - description: "Performs a rollback to a previous deployment in Dokploy.", - schema: z.object({ - rollbackId: z - .string() - .min(1) - .describe("The unique identifier of the rollback to execute. This will restore the application to the state captured in this rollback. Required."), - }), - annotations: { - title: "Execute Rollback", - readOnlyHint: false, - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/rollback.rollback", { - rollbackId: input.rollbackId, - }); - - return ResponseFormatter.success( - `Successfully executed rollback "${input.rollbackId}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/schedule/index.ts b/src/mcp/tools/schedule/index.ts deleted file mode 100644 index b8d83a7..0000000 --- a/src/mcp/tools/schedule/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { scheduleCreate } from "./scheduleCreate.js"; -export { scheduleUpdate } from "./scheduleUpdate.js"; -export { scheduleDelete } from "./scheduleDelete.js"; -export { scheduleList } from "./scheduleList.js"; -export { scheduleOne } from "./scheduleOne.js"; -export { scheduleRunManually } from "./scheduleRunManually.js"; diff --git a/src/mcp/tools/schedule/scheduleCreate.ts b/src/mcp/tools/schedule/scheduleCreate.ts deleted file mode 100644 index d3731dc..0000000 --- a/src/mcp/tools/schedule/scheduleCreate.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 scheduleCreate = createTool({ - name: "schedule-create", - description: "Creates a new scheduled task in Dokploy.", - schema: z.object({ - name: z.string().describe("The name of the scheduled task. Required."), - cronExpression: z - .string() - .describe("The cron expression defining when the task runs (e.g., '0 0 * * *' for daily at midnight). Required."), - command: z.string().describe("The command to execute when the schedule runs. Required."), - scheduleId: z.string().optional().describe("Optional schedule ID. If not provided, one will be generated."), - appName: z.string().optional().describe("The application name for the scheduled task."), - serviceName: z - .string() - .nullable() - .optional() - .describe("The service name for compose applications. Only applicable when scheduleType is 'compose'."), - shellType: z - .enum(["bash", "sh"]) - .optional() - .describe("The shell type to use for command execution. Either 'bash' or 'sh'."), - scheduleType: z - .enum(["application", "compose", "server", "dokploy-server"]) - .optional() - .describe("The type of schedule: 'application' for app-level, 'compose' for compose services, 'server' for remote servers, or 'dokploy-server' for the Dokploy server itself."), - script: z - .string() - .nullable() - .optional() - .describe("The script content to execute. Can be used instead of or in addition to command."), - applicationId: z - .string() - .nullable() - .optional() - .describe("The application ID. Required when scheduleType is 'application'."), - composeId: z - .string() - .nullable() - .optional() - .describe("The compose ID. Required when scheduleType is 'compose'."), - serverId: z - .string() - .nullable() - .optional() - .describe("The server ID. Required when scheduleType is 'server'."), - userId: z.string().nullable().optional().describe("The user ID who owns this schedule."), - enabled: z - .boolean() - .optional() - .describe("Whether the schedule is enabled and will run. Defaults to true."), - createdAt: z.string().optional().describe("The creation timestamp. Usually auto-generated."), - }), - annotations: { - title: "Create Schedule", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/schedule.create", input); - - return ResponseFormatter.success( - `Schedule "${input.name}" created successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/schedule/scheduleDelete.ts b/src/mcp/tools/schedule/scheduleDelete.ts deleted file mode 100644 index 56984ee..0000000 --- a/src/mcp/tools/schedule/scheduleDelete.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 scheduleDelete = createTool({ - name: "schedule-delete", - description: "Deletes a scheduled task from Dokploy.", - schema: z.object({ - scheduleId: z.string().describe("The unique identifier of the schedule to delete. Required."), - }), - annotations: { - title: "Delete Schedule", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/schedule.delete", input); - - return ResponseFormatter.success( - `Schedule "${input.scheduleId}" deleted successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/schedule/scheduleList.ts b/src/mcp/tools/schedule/scheduleList.ts deleted file mode 100644 index ad77aa3..0000000 --- a/src/mcp/tools/schedule/scheduleList.ts +++ /dev/null @@ -1,35 +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 scheduleList = createTool({ - name: "schedule-list", - description: "Lists all scheduled tasks for a specific resource in Dokploy.", - schema: z.object({ - id: z - .string() - .describe( - "The ID of the resource (applicationId, composeId, or serverId) to list schedules for. Required.", - ), - scheduleType: z - .enum(["application", "compose", "server", "dokploy-server"]) - .describe("The type of schedules to list: 'application', 'compose', 'server', or 'dokploy-server'. Required."), - }), - annotations: { - title: "List Schedules", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const schedules = await apiClient.get( - `/schedule.list?id=${input.id}&scheduleType=${input.scheduleType}`, - ); - - return ResponseFormatter.success( - `Successfully fetched schedules for ${input.scheduleType} "${input.id}"`, - schedules.data, - ); - }, -}); diff --git a/src/mcp/tools/schedule/scheduleOne.ts b/src/mcp/tools/schedule/scheduleOne.ts deleted file mode 100644 index ed71f01..0000000 --- a/src/mcp/tools/schedule/scheduleOne.ts +++ /dev/null @@ -1,35 +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 scheduleOne = createTool({ - name: "schedule-one", - description: "Gets a specific scheduled task by its ID in Dokploy.", - schema: z.object({ - scheduleId: z.string().describe("The unique identifier of the schedule to retrieve. Required."), - }), - annotations: { - title: "Get Schedule Details", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const schedule = await apiClient.get( - `/schedule.one?scheduleId=${input.scheduleId}`, - ); - - if (!schedule?.data) { - return ResponseFormatter.error( - "Failed to fetch schedule", - `Schedule with ID "${input.scheduleId}" not found`, - ); - } - - return ResponseFormatter.success( - `Successfully fetched schedule "${input.scheduleId}"`, - schedule.data, - ); - }, -}); diff --git a/src/mcp/tools/schedule/scheduleRunManually.ts b/src/mcp/tools/schedule/scheduleRunManually.ts deleted file mode 100644 index 3935ff8..0000000 --- a/src/mcp/tools/schedule/scheduleRunManually.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 scheduleRunManually = createTool({ - name: "schedule-run-manually", - description: "Manually triggers a scheduled task to run immediately.", - schema: z.object({ - scheduleId: z - .string() - .min(1) - .describe("The unique identifier of the schedule to trigger immediately. Required."), - }), - annotations: { - title: "Run Schedule Manually", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/schedule.runManually", input); - - return ResponseFormatter.success( - `Schedule "${input.scheduleId}" triggered successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/schedule/scheduleUpdate.ts b/src/mcp/tools/schedule/scheduleUpdate.ts deleted file mode 100644 index 87a9a06..0000000 --- a/src/mcp/tools/schedule/scheduleUpdate.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 scheduleUpdate = createTool({ - name: "schedule-update", - description: "Updates an existing scheduled task in Dokploy.", - schema: z.object({ - scheduleId: z.string().min(1).describe("The ID of the schedule to update. Required."), - name: z.string().describe("The name of the scheduled task. Required."), - cronExpression: z - .string() - .describe("The cron expression defining when the task runs (e.g., '0 0 * * *' for daily at midnight). Required."), - command: z.string().describe("The command to execute when the schedule runs. Required."), - appName: z.string().optional().describe("The application name for the scheduled task."), - serviceName: z - .string() - .nullable() - .optional() - .describe("The service name for compose applications. Only applicable when scheduleType is 'compose'."), - shellType: z - .enum(["bash", "sh"]) - .optional() - .describe("The shell type to use for command execution. Either 'bash' or 'sh'."), - scheduleType: z - .enum(["application", "compose", "server", "dokploy-server"]) - .optional() - .describe("The type of schedule: 'application' for app-level, 'compose' for compose services, 'server' for remote servers, or 'dokploy-server' for the Dokploy server itself."), - script: z - .string() - .nullable() - .optional() - .describe("The script content to execute. Can be used instead of or in addition to command."), - applicationId: z - .string() - .nullable() - .optional() - .describe("The application ID. Required when scheduleType is 'application'."), - composeId: z - .string() - .nullable() - .optional() - .describe("The compose ID. Required when scheduleType is 'compose'."), - serverId: z - .string() - .nullable() - .optional() - .describe("The server ID. Required when scheduleType is 'server'."), - userId: z.string().nullable().optional().describe("The user ID who owns this schedule."), - enabled: z - .boolean() - .optional() - .describe("Whether the schedule is enabled and will run."), - createdAt: z.string().optional().describe("The creation timestamp."), - }), - annotations: { - title: "Update Schedule", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/schedule.update", input); - - return ResponseFormatter.success( - `Schedule "${input.scheduleId}" updated successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/security/index.ts b/src/mcp/tools/security/index.ts deleted file mode 100644 index 7cc1346..0000000 --- a/src/mcp/tools/security/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { securityCreate } from "./securityCreate.js"; -export { securityDelete } from "./securityDelete.js"; -export { securityOne } from "./securityOne.js"; -export { securityUpdate } from "./securityUpdate.js"; diff --git a/src/mcp/tools/security/securityCreate.ts b/src/mcp/tools/security/securityCreate.ts deleted file mode 100644 index 5fe423e..0000000 --- a/src/mcp/tools/security/securityCreate.ts +++ /dev/null @@ -1,43 +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 securityCreate = createTool({ - name: "security-create", - description: - "Creates a new security entry (HTTP basic auth) for an application in Dokploy.", - schema: z.object({ - applicationId: z - .string() - .describe( - "The ID of the application to add HTTP basic authentication to.", - ), - username: z - .string() - .min(1) - .describe( - "The username for HTTP basic auth. Must be at least 1 character.", - ), - password: z - .string() - .min(1) - .describe( - "The password for HTTP basic auth. Must be at least 1 character.", - ), - }), - annotations: { - title: "Create Security Entry", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/security.create", input); - - return ResponseFormatter.success( - `Security entry created for application "${input.applicationId}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/security/securityDelete.ts b/src/mcp/tools/security/securityDelete.ts deleted file mode 100644 index 369cbd9..0000000 --- a/src/mcp/tools/security/securityDelete.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 securityDelete = createTool({ - name: "security-delete", - description: - "Deletes a security entry (HTTP basic auth configuration) from Dokploy.", - schema: z.object({ - securityId: z - .string() - .min(1) - .describe( - "The ID of the security entry to delete. Must be at least 1 character.", - ), - }), - annotations: { - title: "Delete Security Entry", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/security.delete", input); - - return ResponseFormatter.success( - `Security entry "${input.securityId}" deleted successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/security/securityOne.ts b/src/mcp/tools/security/securityOne.ts deleted file mode 100644 index b2d2dec..0000000 --- a/src/mcp/tools/security/securityOne.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 securityOne = createTool({ - name: "security-one", - description: - "Gets a specific security entry (HTTP basic auth configuration) by its ID in Dokploy.", - schema: z.object({ - securityId: z - .string() - .min(1) - .describe( - "The ID of the security entry to retrieve. Must be at least 1 character.", - ), - }), - annotations: { - title: "Get Security Entry Details", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.get( - `/security.one?securityId=${input.securityId}`, - ); - - if (!response?.data) { - return ResponseFormatter.error( - "Failed to fetch security entry", - `Security entry with ID "${input.securityId}" not found`, - ); - } - - return ResponseFormatter.success( - `Successfully fetched security entry "${input.securityId}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/security/securityUpdate.ts b/src/mcp/tools/security/securityUpdate.ts deleted file mode 100644 index 3940a75..0000000 --- a/src/mcp/tools/security/securityUpdate.ts +++ /dev/null @@ -1,44 +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 securityUpdate = createTool({ - name: "security-update", - description: - "Updates an existing security entry (HTTP basic auth configuration) in Dokploy.", - schema: z.object({ - securityId: z - .string() - .min(1) - .describe( - "The ID of the security entry to update. Must be at least 1 character.", - ), - username: z - .string() - .min(1) - .describe( - "The new username for HTTP basic auth. Must be at least 1 character.", - ), - password: z - .string() - .min(1) - .describe( - "The new password for HTTP basic auth. Must be at least 1 character.", - ), - }), - annotations: { - title: "Update Security Entry", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/security.update", input); - - return ResponseFormatter.success( - `Security entry "${input.securityId}" updated successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/server/index.ts b/src/mcp/tools/server/index.ts deleted file mode 100644 index 9b4015c..0000000 --- a/src/mcp/tools/server/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -export { serverAll } from "./serverAll.js"; -export { serverBuildServers } from "./serverBuildServers.js"; -export { serverCount } from "./serverCount.js"; -export { serverCreate } from "./serverCreate.js"; -export { serverGetDefaultCommand } from "./serverGetDefaultCommand.js"; -export { serverGetServerMetrics } from "./serverGetServerMetrics.js"; -export { serverGetServerTime } from "./serverGetServerTime.js"; -export { serverOne } from "./serverOne.js"; -export { serverPublicIp } from "./serverPublicIp.js"; -export { serverRemove } from "./serverRemove.js"; -export { serverSecurity } from "./serverSecurity.js"; -export { serverSetup } from "./serverSetup.js"; -export { serverSetupMonitoring } from "./serverSetupMonitoring.js"; -export { serverUpdate } from "./serverUpdate.js"; -export { serverValidate } from "./serverValidate.js"; -export { serverWithSSHKey } from "./serverWithSSHKey.js"; diff --git a/src/mcp/tools/server/serverAll.ts b/src/mcp/tools/server/serverAll.ts deleted file mode 100644 index ab8ce09..0000000 --- a/src/mcp/tools/server/serverAll.ts +++ /dev/null @@ -1,24 +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 serverAll = createTool({ - name: "server-all", - description: "Gets all servers in Dokploy.", - schema: z.object({}), - annotations: { - title: "List All Servers", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.get("/server.all"); - - return ResponseFormatter.success( - "Successfully fetched all servers", - response.data - ); - }, -}); diff --git a/src/mcp/tools/server/serverBuildServers.ts b/src/mcp/tools/server/serverBuildServers.ts deleted file mode 100644 index a76dc77..0000000 --- a/src/mcp/tools/server/serverBuildServers.ts +++ /dev/null @@ -1,24 +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 serverBuildServers = createTool({ - name: "server-build-servers", - description: "Gets all build servers in Dokploy.", - schema: z.object({}), - annotations: { - title: "List Build Servers", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.get("/server.buildServers"); - - return ResponseFormatter.success( - "Successfully fetched build servers", - response.data - ); - }, -}); diff --git a/src/mcp/tools/server/serverCount.ts b/src/mcp/tools/server/serverCount.ts deleted file mode 100644 index 5303dd6..0000000 --- a/src/mcp/tools/server/serverCount.ts +++ /dev/null @@ -1,24 +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 serverCount = createTool({ - name: "server-count", - description: "Gets the count of servers in Dokploy.", - schema: z.object({}), - annotations: { - title: "Get Server Count", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.get("/server.count"); - - return ResponseFormatter.success( - "Successfully fetched server count", - response.data - ); - }, -}); diff --git a/src/mcp/tools/server/serverCreate.ts b/src/mcp/tools/server/serverCreate.ts deleted file mode 100644 index 5284c1f..0000000 --- a/src/mcp/tools/server/serverCreate.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 serverCreate = createTool({ - name: "server-create", - description: "Creates a new server in Dokploy.", - schema: z.object({ - name: z.string().min(1).describe("The name of the server."), - description: z - .string() - .nullable() - .optional() - .describe("An optional description for the server."), - ipAddress: z.string().describe("The IP address of the server."), - port: z.number().describe("The SSH port of the server."), - username: z.string().describe("The SSH username for the server."), - sshKeyId: z - .string() - .nullable() - .describe("The ID of the SSH key to use for authentication."), - serverType: z - .enum(["deploy", "build"]) - .describe("The type of server - 'deploy' for deployment or 'build' for building."), - }), - annotations: { - title: "Create Server", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/server.create", input); - - return ResponseFormatter.success( - `Server "${input.name}" created successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/server/serverGetDefaultCommand.ts b/src/mcp/tools/server/serverGetDefaultCommand.ts deleted file mode 100644 index 6ac6972..0000000 --- a/src/mcp/tools/server/serverGetDefaultCommand.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 serverGetDefaultCommand = createTool({ - name: "server-get-default-command", - description: "Gets the default command for a specific server in Dokploy.", - schema: z.object({ - serverId: z.string().min(1).describe("The ID of the server to get the default command for."), - }), - annotations: { - title: "Get Server Default Command", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.get( - `/server.getDefaultCommand?serverId=${input.serverId}` - ); - - return ResponseFormatter.success( - `Successfully fetched default command for server "${input.serverId}"`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/server/serverGetServerMetrics.ts b/src/mcp/tools/server/serverGetServerMetrics.ts deleted file mode 100644 index 2201c4d..0000000 --- a/src/mcp/tools/server/serverGetServerMetrics.ts +++ /dev/null @@ -1,30 +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 serverGetServerMetrics = createTool({ - name: "server-get-server-metrics", - description: "Gets server metrics from the Dokploy monitoring endpoint.", - schema: z.object({ - url: z.string().describe("The URL of the metrics endpoint."), - token: z.string().describe("The authentication token for the metrics endpoint."), - dataPoints: z.string().describe("The number of data points to retrieve."), - }), - annotations: { - title: "Get Server Metrics", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.get( - `/server.getServerMetrics?url=${encodeURIComponent(input.url)}&token=${encodeURIComponent(input.token)}&dataPoints=${encodeURIComponent(input.dataPoints)}` - ); - - return ResponseFormatter.success( - "Successfully fetched server metrics", - response.data - ); - }, -}); diff --git a/src/mcp/tools/server/serverGetServerTime.ts b/src/mcp/tools/server/serverGetServerTime.ts deleted file mode 100644 index 3c5fc7c..0000000 --- a/src/mcp/tools/server/serverGetServerTime.ts +++ /dev/null @@ -1,24 +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 serverGetServerTime = createTool({ - name: "server-get-server-time", - description: "Gets the current time of the Dokploy server.", - schema: z.object({}), - annotations: { - title: "Get Server Time", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.get("/server.getServerTime"); - - return ResponseFormatter.success( - "Successfully fetched server time", - response.data - ); - }, -}); diff --git a/src/mcp/tools/server/serverOne.ts b/src/mcp/tools/server/serverOne.ts deleted file mode 100644 index 58547ec..0000000 --- a/src/mcp/tools/server/serverOne.ts +++ /dev/null @@ -1,35 +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 serverOne = createTool({ - name: "server-one", - description: "Gets a specific server by its ID in Dokploy.", - schema: z.object({ - serverId: z.string().min(1).describe("The ID of the server to retrieve."), - }), - annotations: { - title: "Get Server Details", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.get( - `/server.one?serverId=${input.serverId}` - ); - - if (!response?.data) { - return ResponseFormatter.error( - "Failed to fetch server", - `Server with ID "${input.serverId}" not found` - ); - } - - return ResponseFormatter.success( - `Successfully fetched server "${input.serverId}"`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/server/serverPublicIp.ts b/src/mcp/tools/server/serverPublicIp.ts deleted file mode 100644 index 4e264a3..0000000 --- a/src/mcp/tools/server/serverPublicIp.ts +++ /dev/null @@ -1,24 +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 serverPublicIp = createTool({ - name: "server-public-ip", - description: "Gets the public IP address of the Dokploy server.", - schema: z.object({}), - annotations: { - title: "Get Server Public IP", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.get("/server.publicIp"); - - return ResponseFormatter.success( - "Successfully fetched server public IP", - response.data - ); - }, -}); diff --git a/src/mcp/tools/server/serverRemove.ts b/src/mcp/tools/server/serverRemove.ts deleted file mode 100644 index d0807af..0000000 --- a/src/mcp/tools/server/serverRemove.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 serverRemove = createTool({ - name: "server-remove", - description: "Removes/deletes a server from Dokploy.", - schema: z.object({ - serverId: z.string().min(1).describe("The ID of the server to remove."), - }), - annotations: { - title: "Remove Server", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/server.remove", input); - - return ResponseFormatter.success( - `Server "${input.serverId}" removed successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/server/serverSecurity.ts b/src/mcp/tools/server/serverSecurity.ts deleted file mode 100644 index 60bd582..0000000 --- a/src/mcp/tools/server/serverSecurity.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 serverSecurity = createTool({ - name: "server-security", - description: "Gets security information for a specific server in Dokploy.", - schema: z.object({ - serverId: z.string().min(1).describe("The ID of the server to get security info for."), - }), - annotations: { - title: "Get Server Security Info", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.get( - `/server.security?serverId=${input.serverId}` - ); - - return ResponseFormatter.success( - `Successfully fetched security info for server "${input.serverId}"`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/server/serverSetup.ts b/src/mcp/tools/server/serverSetup.ts deleted file mode 100644 index c98801e..0000000 --- a/src/mcp/tools/server/serverSetup.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 serverSetup = createTool({ - name: "server-setup", - description: "Sets up a server in Dokploy (installs required dependencies and configures the server).", - schema: z.object({ - serverId: z.string().min(1).describe("The ID of the server to set up."), - }), - annotations: { - title: "Setup Server", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/server.setup", input); - - return ResponseFormatter.success( - `Server "${input.serverId}" setup initiated successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/server/serverSetupMonitoring.ts b/src/mcp/tools/server/serverSetupMonitoring.ts deleted file mode 100644 index 88be1b2..0000000 --- a/src/mcp/tools/server/serverSetupMonitoring.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"; - -const thresholdsSchema = z.object({ - cpu: z.number().min(0).describe("CPU threshold percentage (minimum 0)."), - memory: z.number().min(0).describe("Memory threshold percentage (minimum 0)."), -}); - -const servicesSchema = z.object({ - include: z.array(z.string()).optional().describe("List of services to include in monitoring."), - exclude: z.array(z.string()).optional().describe("List of services to exclude from monitoring."), -}); - -const serverMetricsSchema = z.object({ - refreshRate: z.number().min(2).describe("Refresh rate in seconds (minimum 2)."), - port: z.number().min(1).describe("Port for metrics collection (minimum 1)."), - token: z.string().describe("Token for authentication."), - urlCallback: z.string().url().describe("Callback URL for metrics data."), - retentionDays: z.number().min(1).describe("Number of days to retain metrics data (minimum 1)."), - cronJob: z.string().min(1).describe("Cron job schedule expression."), - thresholds: thresholdsSchema.describe("Threshold configuration for alerts."), -}); - -const containersMetricsSchema = z.object({ - refreshRate: z.number().min(2).describe("Refresh rate in seconds for container metrics (minimum 2)."), - services: servicesSchema.describe("Services configuration for container monitoring."), -}); - -const metricsConfigSchema = z.object({ - server: serverMetricsSchema.describe("Server metrics configuration."), - containers: containersMetricsSchema.describe("Containers metrics configuration."), -}); - -export const serverSetupMonitoring = createTool({ - name: "server-setup-monitoring", - description: "Sets up monitoring for a server in Dokploy.", - schema: z.object({ - serverId: z.string().min(1).describe("The ID of the server to set up monitoring for."), - metricsConfig: metricsConfigSchema.describe("The metrics configuration object."), - }), - annotations: { - title: "Setup Server Monitoring", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/server.setupMonitoring", input); - - return ResponseFormatter.success( - `Monitoring setup initiated for server "${input.serverId}"`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/server/serverUpdate.ts b/src/mcp/tools/server/serverUpdate.ts deleted file mode 100644 index 680c383..0000000 --- a/src/mcp/tools/server/serverUpdate.ts +++ /dev/null @@ -1,46 +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 serverUpdate = createTool({ - name: "server-update", - description: "Updates an existing server in Dokploy.", - schema: z.object({ - serverId: z.string().min(1).describe("The ID of the server to update."), - name: z.string().min(1).describe("The name of the server."), - description: z - .string() - .nullable() - .optional() - .describe("An optional description for the server."), - ipAddress: z.string().describe("The IP address of the server."), - port: z.number().describe("The SSH port of the server."), - username: z.string().describe("The SSH username for the server."), - sshKeyId: z - .string() - .nullable() - .describe("The ID of the SSH key to use for authentication."), - serverType: z - .enum(["deploy", "build"]) - .describe("The type of server - 'deploy' for deployment or 'build' for building."), - command: z - .string() - .optional() - .describe("Custom command for the server."), - }), - annotations: { - title: "Update Server", - destructiveHint: false, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/server.update", input); - - return ResponseFormatter.success( - `Server "${input.serverId}" updated successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/server/serverValidate.ts b/src/mcp/tools/server/serverValidate.ts deleted file mode 100644 index aab2077..0000000 --- a/src/mcp/tools/server/serverValidate.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 serverValidate = createTool({ - name: "server-validate", - description: "Validates a server connection in Dokploy.", - schema: z.object({ - serverId: z.string().min(1).describe("The ID of the server to validate."), - }), - annotations: { - title: "Validate Server", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.get( - `/server.validate?serverId=${input.serverId}` - ); - - return ResponseFormatter.success( - `Server "${input.serverId}" validated successfully`, - response.data - ); - }, -}); diff --git a/src/mcp/tools/server/serverWithSSHKey.ts b/src/mcp/tools/server/serverWithSSHKey.ts deleted file mode 100644 index 738331e..0000000 --- a/src/mcp/tools/server/serverWithSSHKey.ts +++ /dev/null @@ -1,24 +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 serverWithSSHKey = createTool({ - name: "server-with-ssh-key", - description: "Gets all servers that have SSH keys configured in Dokploy.", - schema: z.object({}), - annotations: { - title: "List Servers With SSH Keys", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.get("/server.withSSHKey"); - - return ResponseFormatter.success( - "Successfully fetched servers with SSH keys", - response.data - ); - }, -}); diff --git a/src/mcp/tools/settings/index.ts b/src/mcp/tools/settings/index.ts deleted file mode 100644 index 6ad87f5..0000000 --- a/src/mcp/tools/settings/index.ts +++ /dev/null @@ -1,46 +0,0 @@ -export { settingsAssignDomainServer } from "./settingsAssignDomainServer.js"; -export { settingsCheckGPUStatus } from "./settingsCheckGPUStatus.js"; -export { settingsCleanAll } from "./settingsCleanAll.js"; -export { settingsCleanDockerBuilder } from "./settingsCleanDockerBuilder.js"; -export { settingsCleanDockerPrune } from "./settingsCleanDockerPrune.js"; -export { settingsCleanMonitoring } from "./settingsCleanMonitoring.js"; -export { settingsCleanRedis } from "./settingsCleanRedis.js"; -export { settingsCleanSSHPrivateKey } from "./settingsCleanSSHPrivateKey.js"; -export { settingsCleanStoppedContainers } from "./settingsCleanStoppedContainers.js"; -export { settingsCleanUnusedImages } from "./settingsCleanUnusedImages.js"; -export { settingsCleanUnusedVolumes } from "./settingsCleanUnusedVolumes.js"; -export { settingsGetDokployCloudIps } from "./settingsGetDokployCloudIps.js"; -export { settingsGetDokployVersion } from "./settingsGetDokployVersion.js"; -export { settingsGetIp } from "./settingsGetIp.js"; -export { settingsGetLogCleanupStatus } from "./settingsGetLogCleanupStatus.js"; -export { settingsGetOpenApiDocument } from "./settingsGetOpenApiDocument.js"; -export { settingsGetReleaseTag } from "./settingsGetReleaseTag.js"; -export { settingsGetTraefikPorts } from "./settingsGetTraefikPorts.js"; -export { settingsGetUpdateData } from "./settingsGetUpdateData.js"; -export { settingsHaveActivateRequests } from "./settingsHaveActivateRequests.js"; -export { settingsHaveTraefikDashboardPortEnabled } from "./settingsHaveTraefikDashboardPortEnabled.js"; -export { settingsHealth } from "./settingsHealth.js"; -export { settingsIsCloud } from "./settingsIsCloud.js"; -export { settingsIsUserSubscribed } from "./settingsIsUserSubscribed.js"; -export { settingsReadDirectories } from "./settingsReadDirectories.js"; -export { settingsReadMiddlewareTraefikConfig } from "./settingsReadMiddlewareTraefikConfig.js"; -export { settingsReadTraefikConfig } from "./settingsReadTraefikConfig.js"; -export { settingsReadTraefikEnv } from "./settingsReadTraefikEnv.js"; -export { settingsReadTraefikFile } from "./settingsReadTraefikFile.js"; -export { settingsReadWebServerTraefikConfig } from "./settingsReadWebServerTraefikConfig.js"; -export { settingsReloadRedis } from "./settingsReloadRedis.js"; -export { settingsReloadServer } from "./settingsReloadServer.js"; -export { settingsReloadTraefik } from "./settingsReloadTraefik.js"; -export { settingsSaveSSHPrivateKey } from "./settingsSaveSSHPrivateKey.js"; -export { settingsSetupGPU } from "./settingsSetupGPU.js"; -export { settingsToggleDashboard } from "./settingsToggleDashboard.js"; -export { settingsToggleRequests } from "./settingsToggleRequests.js"; -export { settingsUpdateDockerCleanup } from "./settingsUpdateDockerCleanup.js"; -export { settingsUpdateLogCleanup } from "./settingsUpdateLogCleanup.js"; -export { settingsUpdateMiddlewareTraefikConfig } from "./settingsUpdateMiddlewareTraefikConfig.js"; -export { settingsUpdateServer } from "./settingsUpdateServer.js"; -export { settingsUpdateTraefikConfig } from "./settingsUpdateTraefikConfig.js"; -export { settingsUpdateTraefikFile } from "./settingsUpdateTraefikFile.js"; -export { settingsUpdateTraefikPorts } from "./settingsUpdateTraefikPorts.js"; -export { settingsUpdateWebServerTraefikConfig } from "./settingsUpdateWebServerTraefikConfig.js"; -export { settingsWriteTraefikEnv } from "./settingsWriteTraefikEnv.js"; diff --git a/src/mcp/tools/settings/settingsAssignDomainServer.ts b/src/mcp/tools/settings/settingsAssignDomainServer.ts deleted file mode 100644 index ca12706..0000000 --- a/src/mcp/tools/settings/settingsAssignDomainServer.ts +++ /dev/null @@ -1,50 +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 settingsAssignDomainServer = createTool({ - name: "settings-assign-domain-server", - description: "Assigns a domain to the Dokploy server.", - schema: z.object({ - host: z - .string() - .nullable() - .describe( - "The domain host to assign. Set to null to remove the domain assignment.", - ), - certificateType: z - .enum(["letsencrypt", "none", "custom"]) - .describe( - "The type of SSL certificate to use: letsencrypt for automatic SSL, none for no SSL, custom for user-provided certificates.", - ), - letsEncryptEmail: z - .string() - .nullable() - .optional() - .describe( - "Email address for Let's Encrypt certificate notifications. Required when certificateType is letsencrypt.", - ), - https: z - .boolean() - .optional() - .describe("Whether to enable HTTPS for the domain."), - }), - annotations: { - title: "Assign Domain to Server", - destructiveHint: false, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post( - "/settings.assignDomainServer", - input, - ); - - return ResponseFormatter.success( - `Domain "${input.host}" assigned successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsCheckGPUStatus.ts b/src/mcp/tools/settings/settingsCheckGPUStatus.ts deleted file mode 100644 index c2a41be..0000000 --- a/src/mcp/tools/settings/settingsCheckGPUStatus.ts +++ /dev/null @@ -1,30 +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 settingsCheckGPUStatus = createTool({ - name: "settings-check-gpu-status", - description: "Checks the GPU status in Dokploy.", - schema: z.object({ - serverId: z - .string() - .optional() - .describe("Optional server ID to check GPU status on a specific server."), - }), - annotations: { - title: "Check GPU Status", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const params = input.serverId ? `?serverId=${input.serverId}` : ""; - const response = await apiClient.get(`/settings.checkGPUStatus${params}`); - - return ResponseFormatter.success( - "Successfully fetched GPU status", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsCleanAll.ts b/src/mcp/tools/settings/settingsCleanAll.ts deleted file mode 100644 index fec4296..0000000 --- a/src/mcp/tools/settings/settingsCleanAll.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 settingsCleanAll = createTool({ - name: "settings-clean-all", - description: - "Cleans all Docker resources (images, volumes, containers, builder cache) in Dokploy.", - schema: z.object({ - serverId: z - .string() - .optional() - .describe( - "Optional server ID to clean all resources on a specific server.", - ), - }), - annotations: { - title: "Clean All Docker Resources", - destructiveHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/settings.cleanAll", input); - - return ResponseFormatter.success( - "All Docker resources cleaned successfully", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsCleanDockerBuilder.ts b/src/mcp/tools/settings/settingsCleanDockerBuilder.ts deleted file mode 100644 index 596ea3e..0000000 --- a/src/mcp/tools/settings/settingsCleanDockerBuilder.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 settingsCleanDockerBuilder = createTool({ - name: "settings-clean-docker-builder", - description: "Cleans Docker builder cache in Dokploy.", - schema: z.object({ - serverId: z - .string() - .optional() - .describe( - "Optional server ID to clean builder cache on a specific server.", - ), - }), - annotations: { - title: "Clean Docker Builder", - destructiveHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post( - "/settings.cleanDockerBuilder", - input, - ); - - return ResponseFormatter.success( - "Docker builder cache cleaned successfully", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsCleanDockerPrune.ts b/src/mcp/tools/settings/settingsCleanDockerPrune.ts deleted file mode 100644 index a38db82..0000000 --- a/src/mcp/tools/settings/settingsCleanDockerPrune.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 settingsCleanDockerPrune = createTool({ - name: "settings-clean-docker-prune", - description: "Runs Docker system prune in Dokploy.", - schema: z.object({ - serverId: z - .string() - .optional() - .describe("Optional server ID to run prune on a specific server."), - }), - annotations: { - title: "Docker Prune", - destructiveHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/settings.cleanDockerPrune", input); - - return ResponseFormatter.success( - "Docker prune completed successfully", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsCleanMonitoring.ts b/src/mcp/tools/settings/settingsCleanMonitoring.ts deleted file mode 100644 index e8bf866..0000000 --- a/src/mcp/tools/settings/settingsCleanMonitoring.ts +++ /dev/null @@ -1,24 +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 settingsCleanMonitoring = createTool({ - name: "settings-clean-monitoring", - description: "Cleans monitoring data in Dokploy.", - schema: z.object({}), - annotations: { - title: "Clean Monitoring Data", - destructiveHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.post("/settings.cleanMonitoring", {}); - - return ResponseFormatter.success( - "Monitoring data cleaned successfully", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsCleanRedis.ts b/src/mcp/tools/settings/settingsCleanRedis.ts deleted file mode 100644 index 9cf1638..0000000 --- a/src/mcp/tools/settings/settingsCleanRedis.ts +++ /dev/null @@ -1,24 +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 settingsCleanRedis = createTool({ - name: "settings-clean-redis", - description: "Cleans Redis cache in Dokploy.", - schema: z.object({}), - annotations: { - title: "Clean Redis", - destructiveHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.post("/settings.cleanRedis", {}); - - return ResponseFormatter.success( - "Redis cleaned successfully", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsCleanSSHPrivateKey.ts b/src/mcp/tools/settings/settingsCleanSSHPrivateKey.ts deleted file mode 100644 index 4d2e9f0..0000000 --- a/src/mcp/tools/settings/settingsCleanSSHPrivateKey.ts +++ /dev/null @@ -1,24 +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 settingsCleanSSHPrivateKey = createTool({ - name: "settings-clean-ssh-private-key", - description: "Removes the SSH private key from Dokploy.", - schema: z.object({}), - annotations: { - title: "Clean SSH Private Key", - destructiveHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.post("/settings.cleanSSHPrivateKey", {}); - - return ResponseFormatter.success( - "SSH private key removed successfully", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsCleanStoppedContainers.ts b/src/mcp/tools/settings/settingsCleanStoppedContainers.ts deleted file mode 100644 index 328cea4..0000000 --- a/src/mcp/tools/settings/settingsCleanStoppedContainers.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 settingsCleanStoppedContainers = createTool({ - name: "settings-clean-stopped-containers", - description: "Cleans stopped Docker containers in Dokploy.", - schema: z.object({ - serverId: z - .string() - .optional() - .describe("Optional server ID to clean containers on a specific server."), - }), - annotations: { - title: "Clean Stopped Containers", - destructiveHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post( - "/settings.cleanStoppedContainers", - input, - ); - - return ResponseFormatter.success( - "Stopped containers cleaned successfully", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsCleanUnusedImages.ts b/src/mcp/tools/settings/settingsCleanUnusedImages.ts deleted file mode 100644 index 2f4ffac..0000000 --- a/src/mcp/tools/settings/settingsCleanUnusedImages.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 settingsCleanUnusedImages = createTool({ - name: "settings-clean-unused-images", - description: "Cleans unused Docker images in Dokploy.", - schema: z.object({ - serverId: z - .string() - .optional() - .describe("Optional server ID to clean images on a specific server."), - }), - annotations: { - title: "Clean Unused Images", - destructiveHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/settings.cleanUnusedImages", input); - - return ResponseFormatter.success( - "Unused images cleaned successfully", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsCleanUnusedVolumes.ts b/src/mcp/tools/settings/settingsCleanUnusedVolumes.ts deleted file mode 100644 index 2b6a8da..0000000 --- a/src/mcp/tools/settings/settingsCleanUnusedVolumes.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 settingsCleanUnusedVolumes = createTool({ - name: "settings-clean-unused-volumes", - description: "Cleans unused Docker volumes in Dokploy.", - schema: z.object({ - serverId: z - .string() - .optional() - .describe("Optional server ID to clean volumes on a specific server."), - }), - annotations: { - title: "Clean Unused Volumes", - destructiveHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post( - "/settings.cleanUnusedVolumes", - input, - ); - - return ResponseFormatter.success( - "Unused volumes cleaned successfully", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsGetDokployCloudIps.ts b/src/mcp/tools/settings/settingsGetDokployCloudIps.ts deleted file mode 100644 index 9986e9b..0000000 --- a/src/mcp/tools/settings/settingsGetDokployCloudIps.ts +++ /dev/null @@ -1,24 +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 settingsGetDokployCloudIps = createTool({ - name: "settings-get-dokploy-cloud-ips", - description: "Gets the Dokploy Cloud IP addresses.", - schema: z.object({}), - annotations: { - title: "Get Dokploy Cloud IPs", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.get("/settings.getDokployCloudIps"); - - return ResponseFormatter.success( - "Successfully fetched Dokploy Cloud IPs", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsGetDokployVersion.ts b/src/mcp/tools/settings/settingsGetDokployVersion.ts deleted file mode 100644 index 943333e..0000000 --- a/src/mcp/tools/settings/settingsGetDokployVersion.ts +++ /dev/null @@ -1,24 +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 settingsGetDokployVersion = createTool({ - name: "settings-get-dokploy-version", - description: "Gets the current Dokploy version.", - schema: z.object({}), - annotations: { - title: "Get Dokploy Version", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.get("/settings.getDokployVersion"); - - return ResponseFormatter.success( - "Successfully fetched Dokploy version", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsGetIp.ts b/src/mcp/tools/settings/settingsGetIp.ts deleted file mode 100644 index 327dfbb..0000000 --- a/src/mcp/tools/settings/settingsGetIp.ts +++ /dev/null @@ -1,24 +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 settingsGetIp = createTool({ - name: "settings-get-ip", - description: "Gets the IP address of the Dokploy server.", - schema: z.object({}), - annotations: { - title: "Get Server IP", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.get("/settings.getIp"); - - return ResponseFormatter.success( - "Successfully fetched server IP", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsGetLogCleanupStatus.ts b/src/mcp/tools/settings/settingsGetLogCleanupStatus.ts deleted file mode 100644 index ae06be3..0000000 --- a/src/mcp/tools/settings/settingsGetLogCleanupStatus.ts +++ /dev/null @@ -1,24 +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 settingsGetLogCleanupStatus = createTool({ - name: "settings-get-log-cleanup-status", - description: "Gets the log cleanup status in Dokploy.", - schema: z.object({}), - annotations: { - title: "Get Log Cleanup Status", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.get("/settings.getLogCleanupStatus"); - - return ResponseFormatter.success( - "Successfully fetched log cleanup status", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsGetOpenApiDocument.ts b/src/mcp/tools/settings/settingsGetOpenApiDocument.ts deleted file mode 100644 index ab3cdd9..0000000 --- a/src/mcp/tools/settings/settingsGetOpenApiDocument.ts +++ /dev/null @@ -1,24 +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 settingsGetOpenApiDocument = createTool({ - name: "settings-get-openapi-document", - description: "Gets the OpenAPI documentation for Dokploy.", - schema: z.object({}), - annotations: { - title: "Get OpenAPI Document", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.get("/settings.getOpenApiDocument"); - - return ResponseFormatter.success( - "Successfully fetched OpenAPI document", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsGetReleaseTag.ts b/src/mcp/tools/settings/settingsGetReleaseTag.ts deleted file mode 100644 index b88b3f8..0000000 --- a/src/mcp/tools/settings/settingsGetReleaseTag.ts +++ /dev/null @@ -1,24 +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 settingsGetReleaseTag = createTool({ - name: "settings-get-release-tag", - description: "Gets the current release tag for Dokploy.", - schema: z.object({}), - annotations: { - title: "Get Release Tag", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.get("/settings.getReleaseTag"); - - return ResponseFormatter.success( - "Successfully fetched release tag", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsGetTraefikPorts.ts b/src/mcp/tools/settings/settingsGetTraefikPorts.ts deleted file mode 100644 index 20e1b3c..0000000 --- a/src/mcp/tools/settings/settingsGetTraefikPorts.ts +++ /dev/null @@ -1,30 +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 settingsGetTraefikPorts = createTool({ - name: "settings-get-traefik-ports", - description: "Gets the Traefik ports configuration in Dokploy.", - schema: z.object({ - serverId: z - .string() - .optional() - .describe("Optional server ID to get ports from a specific server."), - }), - annotations: { - title: "Get Traefik Ports", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const params = input.serverId ? `?serverId=${input.serverId}` : ""; - const response = await apiClient.get(`/settings.getTraefikPorts${params}`); - - return ResponseFormatter.success( - "Successfully fetched Traefik ports", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsGetUpdateData.ts b/src/mcp/tools/settings/settingsGetUpdateData.ts deleted file mode 100644 index 5c14740..0000000 --- a/src/mcp/tools/settings/settingsGetUpdateData.ts +++ /dev/null @@ -1,24 +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 settingsGetUpdateData = createTool({ - name: "settings-get-update-data", - description: "Gets update data for Dokploy.", - schema: z.object({}), - annotations: { - title: "Get Update Data", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.post("/settings.getUpdateData", {}); - - return ResponseFormatter.success( - "Successfully fetched update data", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsHaveActivateRequests.ts b/src/mcp/tools/settings/settingsHaveActivateRequests.ts deleted file mode 100644 index 356b6ad..0000000 --- a/src/mcp/tools/settings/settingsHaveActivateRequests.ts +++ /dev/null @@ -1,24 +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 settingsHaveActivateRequests = createTool({ - name: "settings-have-activate-requests", - description: "Checks if activate requests feature is enabled in Dokploy.", - schema: z.object({}), - annotations: { - title: "Check Activate Requests", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.get("/settings.haveActivateRequests"); - - return ResponseFormatter.success( - "Successfully checked activate requests status", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsHaveTraefikDashboardPortEnabled.ts b/src/mcp/tools/settings/settingsHaveTraefikDashboardPortEnabled.ts deleted file mode 100644 index df3b853..0000000 --- a/src/mcp/tools/settings/settingsHaveTraefikDashboardPortEnabled.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 settingsHaveTraefikDashboardPortEnabled = createTool({ - name: "settings-have-traefik-dashboard-port-enabled", - description: "Checks if the Traefik dashboard port is enabled in Dokploy.", - schema: z.object({ - serverId: z - .string() - .optional() - .describe("Optional server ID to check on a specific server."), - }), - annotations: { - title: "Check Traefik Dashboard Port", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const params = input.serverId ? `?serverId=${input.serverId}` : ""; - const response = await apiClient.get( - `/settings.haveTraefikDashboardPortEnabled${params}`, - ); - - return ResponseFormatter.success( - "Successfully checked Traefik dashboard port status", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsHealth.ts b/src/mcp/tools/settings/settingsHealth.ts deleted file mode 100644 index 08518ff..0000000 --- a/src/mcp/tools/settings/settingsHealth.ts +++ /dev/null @@ -1,24 +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 settingsHealth = createTool({ - name: "settings-health", - description: "Gets the health status of the Dokploy server.", - schema: z.object({}), - annotations: { - title: "Check Health", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.get("/settings.health"); - - return ResponseFormatter.success( - "Successfully fetched health status", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsIsCloud.ts b/src/mcp/tools/settings/settingsIsCloud.ts deleted file mode 100644 index 15387c0..0000000 --- a/src/mcp/tools/settings/settingsIsCloud.ts +++ /dev/null @@ -1,24 +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 settingsIsCloud = createTool({ - name: "settings-is-cloud", - description: "Checks if the Dokploy instance is running in cloud mode.", - schema: z.object({}), - annotations: { - title: "Check Cloud Mode", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.get("/settings.isCloud"); - - return ResponseFormatter.success( - "Successfully checked cloud mode status", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsIsUserSubscribed.ts b/src/mcp/tools/settings/settingsIsUserSubscribed.ts deleted file mode 100644 index 39d551f..0000000 --- a/src/mcp/tools/settings/settingsIsUserSubscribed.ts +++ /dev/null @@ -1,24 +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 settingsIsUserSubscribed = createTool({ - name: "settings-is-user-subscribed", - description: "Checks if the user is subscribed to Dokploy.", - schema: z.object({}), - annotations: { - title: "Check User Subscription", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.get("/settings.isUserSubscribed"); - - return ResponseFormatter.success( - "Successfully checked subscription status", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsReadDirectories.ts b/src/mcp/tools/settings/settingsReadDirectories.ts deleted file mode 100644 index adc5554..0000000 --- a/src/mcp/tools/settings/settingsReadDirectories.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 settingsReadDirectories = createTool({ - name: "settings-read-directories", - description: "Reads Traefik configuration directories in Dokploy.", - schema: z.object({ - serverId: z - .string() - .optional() - .describe( - "Optional server ID to read directories from a specific server.", - ), - }), - annotations: { - title: "Read Directories", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const params = input.serverId ? `?serverId=${input.serverId}` : ""; - const response = await apiClient.get(`/settings.readDirectories${params}`); - - return ResponseFormatter.success( - "Successfully fetched directories", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsReadMiddlewareTraefikConfig.ts b/src/mcp/tools/settings/settingsReadMiddlewareTraefikConfig.ts deleted file mode 100644 index d9e6efb..0000000 --- a/src/mcp/tools/settings/settingsReadMiddlewareTraefikConfig.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 settingsReadMiddlewareTraefikConfig = createTool({ - name: "settings-read-middleware-traefik-config", - description: "Reads the middleware Traefik configuration in Dokploy.", - schema: z.object({}), - annotations: { - title: "Read Middleware Traefik Config", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.get( - "/settings.readMiddlewareTraefikConfig", - ); - - return ResponseFormatter.success( - "Successfully fetched middleware Traefik configuration", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsReadTraefikConfig.ts b/src/mcp/tools/settings/settingsReadTraefikConfig.ts deleted file mode 100644 index c5e51f9..0000000 --- a/src/mcp/tools/settings/settingsReadTraefikConfig.ts +++ /dev/null @@ -1,24 +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 settingsReadTraefikConfig = createTool({ - name: "settings-read-traefik-config", - description: "Reads the Traefik configuration in Dokploy.", - schema: z.object({}), - annotations: { - title: "Read Traefik Config", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.get("/settings.readTraefikConfig"); - - return ResponseFormatter.success( - "Successfully fetched Traefik configuration", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsReadTraefikEnv.ts b/src/mcp/tools/settings/settingsReadTraefikEnv.ts deleted file mode 100644 index 04892e3..0000000 --- a/src/mcp/tools/settings/settingsReadTraefikEnv.ts +++ /dev/null @@ -1,30 +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 settingsReadTraefikEnv = createTool({ - name: "settings-read-traefik-env", - description: "Reads the Traefik environment variables in Dokploy.", - schema: z.object({ - serverId: z - .string() - .optional() - .describe("Optional server ID to read env from a specific server."), - }), - annotations: { - title: "Read Traefik Env", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const params = input.serverId ? `?serverId=${input.serverId}` : ""; - const response = await apiClient.get(`/settings.readTraefikEnv${params}`); - - return ResponseFormatter.success( - "Successfully fetched Traefik environment", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsReadTraefikFile.ts b/src/mcp/tools/settings/settingsReadTraefikFile.ts deleted file mode 100644 index 5213ab1..0000000 --- a/src/mcp/tools/settings/settingsReadTraefikFile.ts +++ /dev/null @@ -1,43 +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 settingsReadTraefikFile = createTool({ - name: "settings-read-traefik-file", - description: "Reads a specific Traefik configuration file in Dokploy.", - schema: z.object({ - path: z - .string() - .min(1) - .describe( - "The path of the Traefik file to read. Must be at least 1 character.", - ), - serverId: z - .string() - .optional() - .describe( - "Optional server ID to read the file from a specific remote server.", - ), - }), - annotations: { - title: "Read Traefik File", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const params = new URLSearchParams({ path: input.path }); - if (input.serverId) { - params.append("serverId", input.serverId); - } - const response = await apiClient.get( - `/settings.readTraefikFile?${params.toString()}`, - ); - - return ResponseFormatter.success( - `Successfully fetched Traefik file "${input.path}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsReadWebServerTraefikConfig.ts b/src/mcp/tools/settings/settingsReadWebServerTraefikConfig.ts deleted file mode 100644 index 73f699d..0000000 --- a/src/mcp/tools/settings/settingsReadWebServerTraefikConfig.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 settingsReadWebServerTraefikConfig = createTool({ - name: "settings-read-web-server-traefik-config", - description: "Reads the web server Traefik configuration in Dokploy.", - schema: z.object({}), - annotations: { - title: "Read Web Server Traefik Config", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.get( - "/settings.readWebServerTraefikConfig", - ); - - return ResponseFormatter.success( - "Successfully fetched web server Traefik configuration", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsReloadRedis.ts b/src/mcp/tools/settings/settingsReloadRedis.ts deleted file mode 100644 index 9797ac0..0000000 --- a/src/mcp/tools/settings/settingsReloadRedis.ts +++ /dev/null @@ -1,24 +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 settingsReloadRedis = createTool({ - name: "settings-reload-redis", - description: "Reloads Redis in Dokploy.", - schema: z.object({}), - annotations: { - title: "Reload Redis", - destructiveHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.post("/settings.reloadRedis", {}); - - return ResponseFormatter.success( - "Redis reloaded successfully", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsReloadServer.ts b/src/mcp/tools/settings/settingsReloadServer.ts deleted file mode 100644 index 1be6a9d..0000000 --- a/src/mcp/tools/settings/settingsReloadServer.ts +++ /dev/null @@ -1,24 +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 settingsReloadServer = createTool({ - name: "settings-reload-server", - description: "Reloads the Dokploy server.", - schema: z.object({}), - annotations: { - title: "Reload Server", - destructiveHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.post("/settings.reloadServer", {}); - - return ResponseFormatter.success( - "Server reloaded successfully", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsReloadTraefik.ts b/src/mcp/tools/settings/settingsReloadTraefik.ts deleted file mode 100644 index fdf6f87..0000000 --- a/src/mcp/tools/settings/settingsReloadTraefik.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 settingsReloadTraefik = createTool({ - name: "settings-reload-traefik", - description: "Reloads Traefik in Dokploy.", - schema: z.object({ - serverId: z - .string() - .optional() - .describe("Optional server ID to reload Traefik on a specific server."), - }), - annotations: { - title: "Reload Traefik", - destructiveHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/settings.reloadTraefik", input); - - return ResponseFormatter.success( - "Traefik reloaded successfully", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsSaveSSHPrivateKey.ts b/src/mcp/tools/settings/settingsSaveSSHPrivateKey.ts deleted file mode 100644 index 4b38be4..0000000 --- a/src/mcp/tools/settings/settingsSaveSSHPrivateKey.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 settingsSaveSSHPrivateKey = createTool({ - name: "settings-save-ssh-private-key", - description: "Saves an SSH private key in Dokploy.", - schema: z.object({ - sshPrivateKey: z - .string() - .nullable() - .describe( - "The SSH private key to save. Set to null to clear the existing key.", - ), - }), - annotations: { - title: "Save SSH Private Key", - destructiveHint: false, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/settings.saveSSHPrivateKey", input); - - return ResponseFormatter.success( - "SSH private key saved successfully", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsSetupGPU.ts b/src/mcp/tools/settings/settingsSetupGPU.ts deleted file mode 100644 index f838208..0000000 --- a/src/mcp/tools/settings/settingsSetupGPU.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 settingsSetupGPU = createTool({ - name: "settings-setup-gpu", - description: "Sets up GPU support in Dokploy.", - schema: z.object({ - serverId: z - .string() - .optional() - .describe("Optional server ID to setup GPU on a specific server."), - }), - annotations: { - title: "Setup GPU", - destructiveHint: false, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/settings.setupGPU", input); - - return ResponseFormatter.success( - "GPU setup completed successfully", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsToggleDashboard.ts b/src/mcp/tools/settings/settingsToggleDashboard.ts deleted file mode 100644 index 55599d4..0000000 --- a/src/mcp/tools/settings/settingsToggleDashboard.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 settingsToggleDashboard = createTool({ - name: "settings-toggle-dashboard", - description: "Toggles the Traefik dashboard in Dokploy.", - schema: z.object({ - enableDashboard: z - .boolean() - .optional() - .describe("Whether to enable or disable the dashboard."), - serverId: z - .string() - .optional() - .describe("Optional server ID to toggle dashboard on a specific server."), - }), - annotations: { - title: "Toggle Dashboard", - destructiveHint: false, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/settings.toggleDashboard", input); - - return ResponseFormatter.success( - "Dashboard toggled successfully", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsToggleRequests.ts b/src/mcp/tools/settings/settingsToggleRequests.ts deleted file mode 100644 index e9790aa..0000000 --- a/src/mcp/tools/settings/settingsToggleRequests.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 settingsToggleRequests = createTool({ - name: "settings-toggle-requests", - description: "Toggles the activate requests feature in Dokploy.", - schema: z.object({ - enable: z - .boolean() - .describe("Whether to enable or disable activate requests."), - }), - annotations: { - title: "Toggle Requests", - destructiveHint: false, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/settings.toggleRequests", input); - - return ResponseFormatter.success( - `Activate requests ${input.enable ? "enabled" : "disabled"} successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsUpdateDockerCleanup.ts b/src/mcp/tools/settings/settingsUpdateDockerCleanup.ts deleted file mode 100644 index ff95147..0000000 --- a/src/mcp/tools/settings/settingsUpdateDockerCleanup.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 settingsUpdateDockerCleanup = createTool({ - name: "settings-update-docker-cleanup", - description: "Updates Docker cleanup settings in Dokploy.", - schema: z.object({ - enableDockerCleanup: z - .boolean() - .describe("Whether to enable automatic Docker cleanup."), - serverId: z - .string() - .optional() - .describe( - "Optional server ID to update cleanup settings on a specific server.", - ), - }), - annotations: { - title: "Update Docker Cleanup Settings", - destructiveHint: false, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post( - "/settings.updateDockerCleanup", - input, - ); - - return ResponseFormatter.success( - "Docker cleanup settings updated successfully", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsUpdateLogCleanup.ts b/src/mcp/tools/settings/settingsUpdateLogCleanup.ts deleted file mode 100644 index 69c4c86..0000000 --- a/src/mcp/tools/settings/settingsUpdateLogCleanup.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 settingsUpdateLogCleanup = createTool({ - name: "settings-update-log-cleanup", - description: "Updates log cleanup settings in Dokploy.", - schema: z.object({ - cronExpression: z - .string() - .nullable() - .describe( - "Cron expression for log cleanup schedule. Set to null to disable.", - ), - }), - annotations: { - title: "Update Log Cleanup", - destructiveHint: false, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/settings.updateLogCleanup", input); - - return ResponseFormatter.success( - "Log cleanup settings updated successfully", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsUpdateMiddlewareTraefikConfig.ts b/src/mcp/tools/settings/settingsUpdateMiddlewareTraefikConfig.ts deleted file mode 100644 index a179a65..0000000 --- a/src/mcp/tools/settings/settingsUpdateMiddlewareTraefikConfig.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 settingsUpdateMiddlewareTraefikConfig = createTool({ - name: "settings-update-middleware-traefik-config", - description: "Updates the middleware Traefik configuration in Dokploy.", - schema: z.object({ - traefikConfig: z - .string() - .min(1) - .describe( - "The new middleware Traefik configuration content. Must be at least 1 character.", - ), - }), - annotations: { - title: "Update Middleware Traefik Config", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post( - "/settings.updateMiddlewareTraefikConfig", - input, - ); - - return ResponseFormatter.success( - "Middleware Traefik configuration updated successfully", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsUpdateServer.ts b/src/mcp/tools/settings/settingsUpdateServer.ts deleted file mode 100644 index 6f2fa48..0000000 --- a/src/mcp/tools/settings/settingsUpdateServer.ts +++ /dev/null @@ -1,24 +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 settingsUpdateServer = createTool({ - name: "settings-update-server", - description: "Updates the Dokploy server to the latest version.", - schema: z.object({}), - annotations: { - title: "Update Server", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.post("/settings.updateServer", {}); - - return ResponseFormatter.success( - "Server update initiated successfully", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsUpdateTraefikConfig.ts b/src/mcp/tools/settings/settingsUpdateTraefikConfig.ts deleted file mode 100644 index 21240d3..0000000 --- a/src/mcp/tools/settings/settingsUpdateTraefikConfig.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 settingsUpdateTraefikConfig = createTool({ - name: "settings-update-traefik-config", - description: "Updates the main Traefik configuration in Dokploy.", - schema: z.object({ - traefikConfig: z - .string() - .min(1) - .describe( - "The new Traefik configuration content. Must be at least 1 character.", - ), - }), - annotations: { - title: "Update Traefik Config", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post( - "/settings.updateTraefikConfig", - input, - ); - - return ResponseFormatter.success( - "Traefik configuration updated successfully", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsUpdateTraefikFile.ts b/src/mcp/tools/settings/settingsUpdateTraefikFile.ts deleted file mode 100644 index c335cd2..0000000 --- a/src/mcp/tools/settings/settingsUpdateTraefikFile.ts +++ /dev/null @@ -1,43 +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 settingsUpdateTraefikFile = createTool({ - name: "settings-update-traefik-file", - description: "Updates a specific Traefik configuration file in Dokploy.", - schema: z.object({ - path: z - .string() - .min(1) - .describe( - "The path of the Traefik file to update. Must be at least 1 character.", - ), - traefikConfig: z - .string() - .min(1) - .describe( - "The new Traefik configuration content. Must be at least 1 character.", - ), - serverId: z - .string() - .optional() - .describe( - "Optional server ID to update the file on a specific remote server.", - ), - }), - annotations: { - title: "Update Traefik File", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/settings.updateTraefikFile", input); - - return ResponseFormatter.success( - `Traefik file "${input.path}" updated successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsUpdateTraefikPorts.ts b/src/mcp/tools/settings/settingsUpdateTraefikPorts.ts deleted file mode 100644 index 0ede9bf..0000000 --- a/src/mcp/tools/settings/settingsUpdateTraefikPorts.ts +++ /dev/null @@ -1,47 +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 settingsUpdateTraefikPorts = createTool({ - name: "settings-update-traefik-ports", - description: "Updates Traefik ports configuration in Dokploy.", - schema: z.object({ - serverId: z - .string() - .optional() - .describe("Optional server ID to update ports on a specific server."), - additionalPorts: z - .array( - z.object({ - targetPort: z - .number() - .describe("The target port inside the container."), - publishedPort: z - .number() - .describe("The published port exposed on the host."), - protocol: z - .enum(["tcp", "udp", "sctp"]) - .describe("The protocol for the port mapping: tcp, udp, or sctp."), - }), - ) - .describe("List of additional ports to configure for Traefik."), - }), - annotations: { - title: "Update Traefik Ports", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post( - "/settings.updateTraefikPorts", - input, - ); - - return ResponseFormatter.success( - "Traefik ports updated successfully", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsUpdateWebServerTraefikConfig.ts b/src/mcp/tools/settings/settingsUpdateWebServerTraefikConfig.ts deleted file mode 100644 index fe53dde..0000000 --- a/src/mcp/tools/settings/settingsUpdateWebServerTraefikConfig.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 settingsUpdateWebServerTraefikConfig = createTool({ - name: "settings-update-web-server-traefik-config", - description: "Updates the web server Traefik configuration in Dokploy.", - schema: z.object({ - traefikConfig: z - .string() - .min(1) - .describe( - "The new web server Traefik configuration content. Must be at least 1 character.", - ), - }), - annotations: { - title: "Update Web Server Traefik Config", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post( - "/settings.updateWebServerTraefikConfig", - input, - ); - - return ResponseFormatter.success( - "Web server Traefik configuration updated successfully", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/settings/settingsWriteTraefikEnv.ts b/src/mcp/tools/settings/settingsWriteTraefikEnv.ts deleted file mode 100644 index 1e4f40c..0000000 --- a/src/mcp/tools/settings/settingsWriteTraefikEnv.ts +++ /dev/null @@ -1,36 +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 settingsWriteTraefikEnv = createTool({ - name: "settings-write-traefik-env", - description: "Writes the Traefik environment variables in Dokploy.", - schema: z.object({ - env: z - .string() - .describe( - "The Traefik environment variables content in KEY=value format. Required field.", - ), - serverId: z - .string() - .optional() - .describe( - "Optional server ID to write environment variables on a specific server.", - ), - }), - annotations: { - title: "Write Traefik Env", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/settings.writeTraefikEnv", input); - - return ResponseFormatter.success( - "Traefik environment updated successfully", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/sshKey/index.ts b/src/mcp/tools/sshKey/index.ts deleted file mode 100644 index d2d74c4..0000000 --- a/src/mcp/tools/sshKey/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { sshKeyCreate } from "./sshKeyCreate.js"; -export { sshKeyRemove } from "./sshKeyRemove.js"; -export { sshKeyOne } from "./sshKeyOne.js"; -export { sshKeyAll } from "./sshKeyAll.js"; -export { sshKeyGenerate } from "./sshKeyGenerate.js"; -export { sshKeyUpdate } from "./sshKeyUpdate.js"; diff --git a/src/mcp/tools/sshKey/sshKeyAll.ts b/src/mcp/tools/sshKey/sshKeyAll.ts deleted file mode 100644 index d2d50cd..0000000 --- a/src/mcp/tools/sshKey/sshKeyAll.ts +++ /dev/null @@ -1,24 +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 sshKeyAll = createTool({ - name: "ssh-key-all", - description: "Gets all SSH keys in Dokploy.", - schema: z.object({}), - annotations: { - title: "List All SSH Keys", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const sshKeys = await apiClient.get("/sshKey.all"); - - return ResponseFormatter.success( - "Successfully fetched all SSH keys", - sshKeys.data, - ); - }, -}); diff --git a/src/mcp/tools/sshKey/sshKeyCreate.ts b/src/mcp/tools/sshKey/sshKeyCreate.ts deleted file mode 100644 index dbb6b6c..0000000 --- a/src/mcp/tools/sshKey/sshKeyCreate.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 sshKeyCreate = createTool({ - name: "ssh-key-create", - description: "Creates a new SSH key in Dokploy.", - schema: z.object({ - name: z.string().min(1).describe("The name of the SSH key. Required."), - privateKey: z.string().describe("The private SSH key content (PEM format). Required."), - publicKey: z.string().describe("The public SSH key content. Required."), - organizationId: z.string().describe("The organization ID to associate this key with. Required."), - description: z - .string() - .nullable() - .optional() - .describe("An optional description for the SSH key."), - }), - annotations: { - title: "Create SSH Key", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/sshKey.create", input); - - return ResponseFormatter.success( - `SSH key "${input.name}" created successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/sshKey/sshKeyGenerate.ts b/src/mcp/tools/sshKey/sshKeyGenerate.ts deleted file mode 100644 index f572ca1..0000000 --- a/src/mcp/tools/sshKey/sshKeyGenerate.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 sshKeyGenerate = createTool({ - name: "ssh-key-generate", - description: "Generates a new SSH key pair in Dokploy.", - schema: z.object({ - type: z - .enum(["rsa", "ed25519"]) - .optional() - .describe("The type of SSH key to generate. Either 'rsa' (2048-bit) or 'ed25519' (more secure, recommended). Optional."), - }), - annotations: { - title: "Generate SSH Key", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/sshKey.generate", input); - - return ResponseFormatter.success( - "SSH key pair generated successfully", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/sshKey/sshKeyOne.ts b/src/mcp/tools/sshKey/sshKeyOne.ts deleted file mode 100644 index 40caec5..0000000 --- a/src/mcp/tools/sshKey/sshKeyOne.ts +++ /dev/null @@ -1,35 +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 sshKeyOne = createTool({ - name: "ssh-key-one", - description: "Gets a specific SSH key by its ID in Dokploy.", - schema: z.object({ - sshKeyId: z.string().describe("The unique identifier of the SSH key to retrieve. Required."), - }), - annotations: { - title: "Get SSH Key Details", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const sshKey = await apiClient.get( - `/sshKey.one?sshKeyId=${input.sshKeyId}`, - ); - - if (!sshKey?.data) { - return ResponseFormatter.error( - "Failed to fetch SSH key", - `SSH key with ID "${input.sshKeyId}" not found`, - ); - } - - return ResponseFormatter.success( - `Successfully fetched SSH key "${input.sshKeyId}"`, - sshKey.data, - ); - }, -}); diff --git a/src/mcp/tools/sshKey/sshKeyRemove.ts b/src/mcp/tools/sshKey/sshKeyRemove.ts deleted file mode 100644 index ccd18fc..0000000 --- a/src/mcp/tools/sshKey/sshKeyRemove.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 sshKeyRemove = createTool({ - name: "ssh-key-remove", - description: "Removes/deletes an SSH key from Dokploy.", - schema: z.object({ - sshKeyId: z.string().describe("The unique identifier of the SSH key to remove. Required."), - }), - annotations: { - title: "Remove SSH Key", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/sshKey.remove", input); - - return ResponseFormatter.success( - `SSH key "${input.sshKeyId}" removed successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/sshKey/sshKeyUpdate.ts b/src/mcp/tools/sshKey/sshKeyUpdate.ts deleted file mode 100644 index 27c03c6..0000000 --- a/src/mcp/tools/sshKey/sshKeyUpdate.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 sshKeyUpdate = createTool({ - name: "ssh-key-update", - description: "Updates an existing SSH key in Dokploy.", - schema: z.object({ - sshKeyId: z.string().describe("The unique identifier of the SSH key to update. Required."), - name: z.string().min(1).optional().describe("The new name of the SSH key. Optional."), - description: z - .string() - .nullable() - .optional() - .describe("The new description for the SSH key. Optional, can be set to null to remove."), - lastUsedAt: z - .string() - .nullable() - .optional() - .describe("The last used timestamp in ISO format. Optional, can be set to null."), - }), - annotations: { - title: "Update SSH Key", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/sshKey.update", input); - - return ResponseFormatter.success( - `SSH key "${input.sshKeyId}" updated successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/stripe/index.ts b/src/mcp/tools/stripe/index.ts deleted file mode 100644 index 43de025..0000000 --- a/src/mcp/tools/stripe/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { stripeGetProducts } from "./stripeGetProducts.js"; -export { stripeCreateCheckoutSession } from "./stripeCreateCheckoutSession.js"; -export { stripeCreateCustomerPortalSession } from "./stripeCreateCustomerPortalSession.js"; -export { stripeCanCreateMoreServers } from "./stripeCanCreateMoreServers.js"; diff --git a/src/mcp/tools/stripe/stripeCanCreateMoreServers.ts b/src/mcp/tools/stripe/stripeCanCreateMoreServers.ts deleted file mode 100644 index 1afc957..0000000 --- a/src/mcp/tools/stripe/stripeCanCreateMoreServers.ts +++ /dev/null @@ -1,25 +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 stripeCanCreateMoreServers = createTool({ - name: "stripe-can-create-more-servers", - description: - "Checks if the user can create more servers based on their Stripe subscription in Dokploy.", - schema: z.object({}), - annotations: { - title: "Check Server Creation Limit", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.get("/stripe.canCreateMoreServers"); - - return ResponseFormatter.success( - "Successfully checked server creation limit", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/stripe/stripeCreateCheckoutSession.ts b/src/mcp/tools/stripe/stripeCreateCheckoutSession.ts deleted file mode 100644 index d0c2c31..0000000 --- a/src/mcp/tools/stripe/stripeCreateCheckoutSession.ts +++ /dev/null @@ -1,36 +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 stripeCreateCheckoutSession = createTool({ - name: "stripe-create-checkout-session", - description: "Creates a Stripe checkout session in Dokploy.", - schema: z.object({ - productId: z.string().describe("The Stripe product ID to purchase. Required."), - serverQuantity: z - .number() - .min(1) - .describe("The number of servers to purchase. Must be at least 1. Required."), - isAnnual: z.boolean().describe("Whether this is an annual subscription (true) or monthly (false). Required."), - }), - annotations: { - title: "Create Stripe Checkout Session", - readOnlyHint: false, - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/stripe.createCheckoutSession", { - productId: input.productId, - serverQuantity: input.serverQuantity, - isAnnual: input.isAnnual, - }); - - return ResponseFormatter.success( - "Successfully created Stripe checkout session", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/stripe/stripeCreateCustomerPortalSession.ts b/src/mcp/tools/stripe/stripeCreateCustomerPortalSession.ts deleted file mode 100644 index e591eb1..0000000 --- a/src/mcp/tools/stripe/stripeCreateCustomerPortalSession.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 stripeCreateCustomerPortalSession = createTool({ - name: "stripe-create-customer-portal-session", - description: "Creates a Stripe customer portal session in Dokploy.", - schema: z.object({}), - annotations: { - title: "Create Stripe Customer Portal Session", - readOnlyHint: false, - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.post( - "/stripe.createCustomerPortalSession", - {}, - ); - - return ResponseFormatter.success( - "Successfully created Stripe customer portal session", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/stripe/stripeGetProducts.ts b/src/mcp/tools/stripe/stripeGetProducts.ts deleted file mode 100644 index faee827..0000000 --- a/src/mcp/tools/stripe/stripeGetProducts.ts +++ /dev/null @@ -1,24 +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 stripeGetProducts = createTool({ - name: "stripe-get-products", - description: "Gets all Stripe products available in Dokploy.", - schema: z.object({}), - annotations: { - title: "Get Stripe Products", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.get("/stripe.getProducts"); - - return ResponseFormatter.success( - "Successfully fetched Stripe products", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/swarm/index.ts b/src/mcp/tools/swarm/index.ts deleted file mode 100644 index 8ccf0af..0000000 --- a/src/mcp/tools/swarm/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { swarmGetNodes } from "./swarmGetNodes.js"; -export { swarmGetNodeInfo } from "./swarmGetNodeInfo.js"; -export { swarmGetNodeApps } from "./swarmGetNodeApps.js"; diff --git a/src/mcp/tools/swarm/swarmGetNodeApps.ts b/src/mcp/tools/swarm/swarmGetNodeApps.ts deleted file mode 100644 index c111b26..0000000 --- a/src/mcp/tools/swarm/swarmGetNodeApps.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 swarmGetNodeApps = createTool({ - name: "swarm-get-node-apps", - description: "Gets all applications deployed on swarm nodes in Dokploy.", - schema: z.object({ - serverId: z - .string() - .optional() - .describe("The ID of the server to get node apps from."), - }), - annotations: { - title: "Get Swarm Node Apps", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const params = new URLSearchParams(); - if (input.serverId) { - params.append("serverId", input.serverId); - } - - const queryString = params.toString(); - const url = `/swarm.getNodeApps${queryString ? `?${queryString}` : ""}`; - - const response = await apiClient.get(url); - - return ResponseFormatter.success( - "Successfully fetched swarm node apps", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/swarm/swarmGetNodeInfo.ts b/src/mcp/tools/swarm/swarmGetNodeInfo.ts deleted file mode 100644 index ed20356..0000000 --- a/src/mcp/tools/swarm/swarmGetNodeInfo.ts +++ /dev/null @@ -1,39 +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 swarmGetNodeInfo = createTool({ - name: "swarm-get-node-info", - description: - "Gets detailed information about a specific swarm node in Dokploy.", - schema: z.object({ - nodeId: z.string().describe("The ID of the node to get information for."), - serverId: z - .string() - .optional() - .describe("The ID of the server to get node info from."), - }), - annotations: { - title: "Get Swarm Node Info", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const params = new URLSearchParams(); - params.append("nodeId", input.nodeId); - if (input.serverId) { - params.append("serverId", input.serverId); - } - - const response = await apiClient.get( - `/swarm.getNodeInfo?${params.toString()}`, - ); - - return ResponseFormatter.success( - `Successfully fetched info for swarm node "${input.nodeId}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/swarm/swarmGetNodes.ts b/src/mcp/tools/swarm/swarmGetNodes.ts deleted file mode 100644 index 964f525..0000000 --- a/src/mcp/tools/swarm/swarmGetNodes.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 swarmGetNodes = createTool({ - name: "swarm-get-nodes", - description: "Gets all nodes in the Docker Swarm from Dokploy.", - schema: z.object({ - serverId: z - .string() - .optional() - .describe("The ID of the server to get swarm nodes from."), - }), - annotations: { - title: "Get Swarm Nodes", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const params = new URLSearchParams(); - if (input.serverId) { - params.append("serverId", input.serverId); - } - - const queryString = params.toString(); - const url = `/swarm.getNodes${queryString ? `?${queryString}` : ""}`; - - const response = await apiClient.get(url); - - return ResponseFormatter.success( - "Successfully fetched swarm nodes", - 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/mcp/tools/user/index.ts b/src/mcp/tools/user/index.ts deleted file mode 100644 index 501639e..0000000 --- a/src/mcp/tools/user/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -export { userAll } from "./userAll.js"; -export { userAssignPermissions } from "./userAssignPermissions.js"; -export { userCheckOrganizations } from "./userCheckOrganizations.js"; -export { userCreateApiKey } from "./userCreateApiKey.js"; -export { userDeleteApiKey } from "./userDeleteApiKey.js"; -export { userGenerateToken } from "./userGenerateToken.js"; -export { userGet } from "./userGet.js"; -export { userGetBackups } from "./userGetBackups.js"; -export { userGetByToken } from "./userGetByToken.js"; -export { userGetContainerMetrics } from "./userGetContainerMetrics.js"; -export { userGetInvitations } from "./userGetInvitations.js"; -export { userGetMetricsToken } from "./userGetMetricsToken.js"; -export { userGetServerMetrics } from "./userGetServerMetrics.js"; -export { userHaveRootAccess } from "./userHaveRootAccess.js"; -export { userOne } from "./userOne.js"; -export { userRemove } from "./userRemove.js"; -export { userSendInvitation } from "./userSendInvitation.js"; -export { userUpdate } from "./userUpdate.js"; diff --git a/src/mcp/tools/user/userAll.ts b/src/mcp/tools/user/userAll.ts deleted file mode 100644 index c072faa..0000000 --- a/src/mcp/tools/user/userAll.ts +++ /dev/null @@ -1,24 +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 userAll = createTool({ - name: "user-all", - description: "Gets all users in the Dokploy instance.", - schema: z.object({}), - annotations: { - title: "Get All Users", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.get("/user.all"); - - return ResponseFormatter.success( - "Successfully fetched all users", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/user/userAssignPermissions.ts b/src/mcp/tools/user/userAssignPermissions.ts deleted file mode 100644 index a70daec..0000000 --- a/src/mcp/tools/user/userAssignPermissions.ts +++ /dev/null @@ -1,88 +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 userAssignPermissions = createTool({ - name: "user-assign-permissions", - description: - "Assigns permissions to a user in Dokploy. All fields are required.", - schema: z.object({ - id: z - .string() - .min(1) - .describe( - "The ID of the user to assign permissions. Must be at least 1 character.", - ), - accessedProjects: z - .array(z.string()) - .describe( - "List of project IDs the user can access. Required, can be empty array.", - ), - accessedEnvironments: z - .array(z.string()) - .describe( - "List of environment IDs the user can access. Required, can be empty array.", - ), - accessedServices: z - .array(z.string()) - .describe( - "List of service IDs the user can access. Required, can be empty array.", - ), - canCreateProjects: z - .boolean() - .describe("Whether the user can create new projects. Required."), - canCreateServices: z - .boolean() - .describe( - "Whether the user can create new services within projects. Required.", - ), - canDeleteProjects: z - .boolean() - .describe("Whether the user can delete projects. Required."), - canDeleteServices: z - .boolean() - .describe("Whether the user can delete services. Required."), - canAccessToDocker: z - .boolean() - .describe( - "Whether the user can access Docker management features. Required.", - ), - canAccessToTraefikFiles: z - .boolean() - .describe( - "Whether the user can access and modify Traefik configuration files. Required.", - ), - canAccessToAPI: z - .boolean() - .describe("Whether the user can access the Dokploy API. Required."), - canAccessToSSHKeys: z - .boolean() - .describe("Whether the user can access and manage SSH keys. Required."), - canAccessToGitProviders: z - .boolean() - .describe( - "Whether the user can access and configure Git providers. Required.", - ), - canDeleteEnvironments: z - .boolean() - .describe("Whether the user can delete environments. Required."), - canCreateEnvironments: z - .boolean() - .describe("Whether the user can create new environments. Required."), - }), - annotations: { - title: "Assign User Permissions", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/user.assignPermissions", input); - - return ResponseFormatter.success( - `Permissions assigned to user "${input.id}" successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/user/userCheckOrganizations.ts b/src/mcp/tools/user/userCheckOrganizations.ts deleted file mode 100644 index a04494e..0000000 --- a/src/mcp/tools/user/userCheckOrganizations.ts +++ /dev/null @@ -1,30 +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 userCheckOrganizations = createTool({ - name: "user-check-organizations", - description: "Checks the organizations a user belongs to in Dokploy.", - schema: z.object({ - userId: z - .string() - .describe("The ID of the user to check organizations for."), - }), - annotations: { - title: "Check User Organizations", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.get( - `/user.checkUserOrganizations?userId=${input.userId}`, - ); - - return ResponseFormatter.success( - `Successfully fetched organizations for user "${input.userId}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/user/userCreateApiKey.ts b/src/mcp/tools/user/userCreateApiKey.ts deleted file mode 100644 index a57f474..0000000 --- a/src/mcp/tools/user/userCreateApiKey.ts +++ /dev/null @@ -1,65 +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 userCreateApiKey = createTool({ - name: "user-create-api-key", - description: "Creates a new API key for the current user in Dokploy.", - schema: z.object({ - name: z - .string() - .min(1) - .describe("The name for the API key. Required field."), - prefix: z.string().optional().describe("Optional prefix for the API key."), - expiresIn: z - .number() - .optional() - .describe("Expiration time in seconds from now."), - metadata: z - .object({ - organizationId: z - .string() - .describe("Organization ID to associate with the key. Required."), - }) - .describe("Metadata for the API key. Must include organizationId."), - rateLimitEnabled: z - .boolean() - .optional() - .describe("Whether rate limiting is enabled for this key."), - rateLimitTimeWindow: z - .number() - .optional() - .describe("Rate limit time window in milliseconds."), - rateLimitMax: z - .number() - .optional() - .describe("Maximum requests allowed within the time window."), - remaining: z - .number() - .optional() - .describe("Remaining request count for the current window."), - refillAmount: z - .number() - .optional() - .describe("Amount to refill the rate limit bucket."), - refillInterval: z - .number() - .optional() - .describe("Refill interval in milliseconds."), - }), - annotations: { - title: "Create API Key", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/user.createApiKey", input); - - return ResponseFormatter.success( - `API key "${input.name}" created successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/user/userDeleteApiKey.ts b/src/mcp/tools/user/userDeleteApiKey.ts deleted file mode 100644 index 683e0dc..0000000 --- a/src/mcp/tools/user/userDeleteApiKey.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 userDeleteApiKey = createTool({ - name: "user-delete-api-key", - description: "Deletes an API key for the current user in Dokploy.", - schema: z.object({ - apiKeyId: z.string().describe("The ID of the API key to delete."), - }), - annotations: { - title: "Delete API Key", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/user.deleteApiKey", input); - - return ResponseFormatter.success( - `API key "${input.apiKeyId}" deleted successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/user/userGenerateToken.ts b/src/mcp/tools/user/userGenerateToken.ts deleted file mode 100644 index 4953f9a..0000000 --- a/src/mcp/tools/user/userGenerateToken.ts +++ /dev/null @@ -1,24 +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 userGenerateToken = createTool({ - name: "user-generate-token", - description: "Generates a new token for the current user in Dokploy.", - schema: z.object({}), - annotations: { - title: "Generate User Token", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.post("/user.generateToken", {}); - - return ResponseFormatter.success( - "Token generated successfully", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/user/userGet.ts b/src/mcp/tools/user/userGet.ts deleted file mode 100644 index 3da9a18..0000000 --- a/src/mcp/tools/user/userGet.ts +++ /dev/null @@ -1,24 +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 userGet = createTool({ - name: "user-get", - description: "Gets the current authenticated user information in Dokploy.", - schema: z.object({}), - annotations: { - title: "Get Current User", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.get("/user.get"); - - return ResponseFormatter.success( - "Successfully fetched current user", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/user/userGetBackups.ts b/src/mcp/tools/user/userGetBackups.ts deleted file mode 100644 index 92137de..0000000 --- a/src/mcp/tools/user/userGetBackups.ts +++ /dev/null @@ -1,24 +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 userGetBackups = createTool({ - name: "user-get-backups", - description: "Gets the backups for the current user in Dokploy.", - schema: z.object({}), - annotations: { - title: "Get User Backups", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.get("/user.getBackups"); - - return ResponseFormatter.success( - "Successfully fetched user backups", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/user/userGetByToken.ts b/src/mcp/tools/user/userGetByToken.ts deleted file mode 100644 index a8a3fc3..0000000 --- a/src/mcp/tools/user/userGetByToken.ts +++ /dev/null @@ -1,35 +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 userGetByToken = createTool({ - name: "user-get-by-token", - description: "Gets a user by their token in Dokploy.", - schema: z.object({ - token: z.string().min(1).describe("The token to look up the user."), - }), - annotations: { - title: "Get User By Token", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.get( - `/user.getUserByToken?token=${input.token}`, - ); - - if (!response?.data) { - return ResponseFormatter.error( - "Failed to fetch user", - "User with the provided token not found", - ); - } - - return ResponseFormatter.success( - "Successfully fetched user by token", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/user/userGetContainerMetrics.ts b/src/mcp/tools/user/userGetContainerMetrics.ts deleted file mode 100644 index d838a76..0000000 --- a/src/mcp/tools/user/userGetContainerMetrics.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 userGetContainerMetrics = createTool({ - name: "user-get-container-metrics", - description: "Gets container metrics for a specific application in Dokploy.", - schema: z.object({ - url: z.string().describe("The metrics URL."), - token: z.string().describe("The metrics token."), - appName: z.string().describe("The application name to get metrics for."), - dataPoints: z.string().describe("The number of data points to retrieve."), - }), - annotations: { - title: "Get Container Metrics", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const params = new URLSearchParams({ - url: input.url, - token: input.token, - appName: input.appName, - dataPoints: input.dataPoints, - }); - const response = await apiClient.get( - `/user.getContainerMetrics?${params.toString()}`, - ); - - return ResponseFormatter.success( - `Successfully fetched container metrics for "${input.appName}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/user/userGetInvitations.ts b/src/mcp/tools/user/userGetInvitations.ts deleted file mode 100644 index 1dd02bc..0000000 --- a/src/mcp/tools/user/userGetInvitations.ts +++ /dev/null @@ -1,24 +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 userGetInvitations = createTool({ - name: "user-get-invitations", - description: "Gets pending invitations for the current user in Dokploy.", - schema: z.object({}), - annotations: { - title: "Get User Invitations", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.get("/user.getInvitations"); - - return ResponseFormatter.success( - "Successfully fetched user invitations", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/user/userGetMetricsToken.ts b/src/mcp/tools/user/userGetMetricsToken.ts deleted file mode 100644 index 9b4a3f4..0000000 --- a/src/mcp/tools/user/userGetMetricsToken.ts +++ /dev/null @@ -1,24 +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 userGetMetricsToken = createTool({ - name: "user-get-metrics-token", - description: "Gets the metrics token for the current user in Dokploy.", - schema: z.object({}), - annotations: { - title: "Get Metrics Token", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.get("/user.getMetricsToken"); - - return ResponseFormatter.success( - "Successfully fetched metrics token", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/user/userGetServerMetrics.ts b/src/mcp/tools/user/userGetServerMetrics.ts deleted file mode 100644 index baf7377..0000000 --- a/src/mcp/tools/user/userGetServerMetrics.ts +++ /dev/null @@ -1,24 +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 userGetServerMetrics = createTool({ - name: "user-get-server-metrics", - description: "Gets server metrics for the current user in Dokploy.", - schema: z.object({}), - annotations: { - title: "Get Server Metrics", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.get("/user.getServerMetrics"); - - return ResponseFormatter.success( - "Successfully fetched server metrics", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/user/userHaveRootAccess.ts b/src/mcp/tools/user/userHaveRootAccess.ts deleted file mode 100644 index fa00ba5..0000000 --- a/src/mcp/tools/user/userHaveRootAccess.ts +++ /dev/null @@ -1,24 +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 userHaveRootAccess = createTool({ - name: "user-have-root-access", - description: "Checks if the current user has root access in Dokploy.", - schema: z.object({}), - annotations: { - title: "Check Root Access", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async () => { - const response = await apiClient.get("/user.haveRootAccess"); - - return ResponseFormatter.success( - "Successfully checked root access status", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/user/userOne.ts b/src/mcp/tools/user/userOne.ts deleted file mode 100644 index 59f3e85..0000000 --- a/src/mcp/tools/user/userOne.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 userOne = createTool({ - name: "user-one", - description: "Gets a specific user by their ID in Dokploy.", - schema: z.object({ - userId: z.string().describe("The ID of the user to retrieve."), - }), - annotations: { - title: "Get User Details", - readOnlyHint: true, - idempotentHint: true, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.get(`/user.one?userId=${input.userId}`); - - if (!response?.data) { - return ResponseFormatter.error( - "Failed to fetch user", - `User with ID "${input.userId}" not found`, - ); - } - - return ResponseFormatter.success( - `Successfully fetched user "${input.userId}"`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/user/userRemove.ts b/src/mcp/tools/user/userRemove.ts deleted file mode 100644 index f8e8e61..0000000 --- a/src/mcp/tools/user/userRemove.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 userRemove = createTool({ - name: "user-remove", - description: "Removes a user from Dokploy.", - schema: z.object({ - userId: z.string().describe("The ID of the user to remove."), - }), - annotations: { - title: "Remove User", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/user.remove", input); - - return ResponseFormatter.success( - `User "${input.userId}" removed successfully`, - response.data, - ); - }, -}); diff --git a/src/mcp/tools/user/userSendInvitation.ts b/src/mcp/tools/user/userSendInvitation.ts deleted file mode 100644 index c3a7a6c..0000000 --- a/src/mcp/tools/user/userSendInvitation.ts +++ /dev/null @@ -1,30 +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 userSendInvitation = createTool({ - name: "user-send-invitation", - description: "Sends an invitation to a user in Dokploy.", - schema: z.object({ - invitationId: z.string().min(1).describe("The ID of the invitation."), - notificationId: z - .string() - .min(1) - .describe("The ID of the notification to use for sending."), - }), - annotations: { - title: "Send Invitation", - destructiveHint: false, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/user.sendInvitation", input); - - return ResponseFormatter.success( - "Invitation sent successfully", - response.data, - ); - }, -}); diff --git a/src/mcp/tools/user/userUpdate.ts b/src/mcp/tools/user/userUpdate.ts deleted file mode 100644 index 1756623..0000000 --- a/src/mcp/tools/user/userUpdate.ts +++ /dev/null @@ -1,181 +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 userUpdate = createTool({ - name: "user-update", - description: "Updates user information in Dokploy.", - schema: z.object({ - id: z.string().min(1).optional().describe("The ID of the user to update."), - name: z.string().optional().describe("Display name of the user."), - isRegistered: z - .boolean() - .optional() - .describe("Whether the user is registered."), - expirationDate: z.string().optional().describe("User expiration date."), - createdAt2: z.string().optional().describe("Alternative creation timestamp."), - createdAt: z - .string() - .datetime() - .nullable() - .optional() - .describe("Creation timestamp in ISO 8601 format."), - twoFactorEnabled: z - .boolean() - .nullable() - .optional() - .describe("Whether two-factor authentication is enabled."), - email: z - .string() - .email() - .min(1) - .optional() - .describe("User email address."), - emailVerified: z - .boolean() - .optional() - .describe("Whether email is verified."), - image: z.string().nullable().optional().describe("User profile image URL."), - banned: z - .boolean() - .nullable() - .optional() - .describe("Whether the user is banned."), - banReason: z - .string() - .nullable() - .optional() - .describe("Reason for ban if banned."), - banExpires: z - .string() - .datetime() - .nullable() - .optional() - .describe("Ban expiration timestamp in ISO 8601 format."), - updatedAt: z - .string() - .datetime() - .optional() - .describe("Update timestamp in ISO 8601 format."), - serverIp: z - .string() - .nullable() - .optional() - .describe("Server IP address associated with the user."), - certificateType: z - .enum(["letsencrypt", "none", "custom"]) - .optional() - .describe("Type of SSL certificate to use."), - https: z.boolean().optional().describe("Whether HTTPS is enabled."), - host: z.string().nullable().optional().describe("Host domain."), - letsEncryptEmail: z - .string() - .nullable() - .optional() - .describe("Email for Let's Encrypt certificate."), - sshPrivateKey: z - .string() - .nullable() - .optional() - .describe("SSH private key for server access."), - enableDockerCleanup: z - .boolean() - .optional() - .describe("Whether automatic Docker cleanup is enabled."), - logCleanupCron: z - .string() - .nullable() - .optional() - .describe("Cron expression for log cleanup schedule."), - enablePaidFeatures: z - .boolean() - .optional() - .describe("Whether paid features are enabled."), - allowImpersonation: z - .boolean() - .optional() - .describe("Whether impersonation is allowed."), - metricsConfig: z - .object({ - server: z - .object({ - type: z - .enum(["Dokploy", "Remote"]) - .describe("Metrics server type."), - refreshRate: z.number().describe("Refresh rate in seconds."), - port: z.number().describe("Port number."), - token: z.string().describe("Authentication token."), - urlCallback: z.string().describe("Callback URL."), - retentionDays: z.number().describe("Data retention in days."), - cronJob: z.string().describe("Cron expression for cleanup."), - thresholds: z - .object({ - cpu: z.number().describe("CPU threshold percentage."), - memory: z.number().describe("Memory threshold percentage."), - }) - .describe("Alert thresholds."), - }) - .describe("Server metrics configuration."), - containers: z - .object({ - refreshRate: z.number().describe("Refresh rate in seconds."), - services: z - .object({ - include: z - .array(z.string()) - .describe("Services to include."), - exclude: z - .array(z.string()) - .describe("Services to exclude."), - }) - .describe("Service filters."), - }) - .describe("Container metrics configuration."), - }) - .optional() - .describe("Metrics configuration for monitoring."), - cleanupCacheApplications: z - .boolean() - .optional() - .describe("Whether to cleanup cache for applications."), - cleanupCacheOnPreviews: z - .boolean() - .optional() - .describe("Whether to cleanup cache on previews."), - cleanupCacheOnCompose: z - .boolean() - .optional() - .describe("Whether to cleanup cache on compose deployments."), - stripeCustomerId: z - .string() - .nullable() - .optional() - .describe("Stripe customer ID."), - stripeSubscriptionId: z - .string() - .nullable() - .optional() - .describe("Stripe subscription ID."), - serversQuantity: z.number().optional().describe("Number of servers."), - password: z.string().optional().describe("New password."), - currentPassword: z - .string() - .optional() - .describe("Current password for verification."), - }), - annotations: { - title: "Update User", - destructiveHint: true, - idempotentHint: false, - openWorldHint: true, - }, - handler: async (input) => { - const response = await apiClient.post("/user.update", input); - - return ResponseFormatter.success( - "User updated successfully", - response.data, - ); - }, -}); 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/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; } From 39b6e758576e825b0d9602e3564a5c2f693459e9 Mon Sep 17 00:00:00 2001 From: limehawk <128890849+limehawk@users.noreply.github.com> Date: Wed, 11 Mar 2026 00:48:11 -0400 Subject: [PATCH 07/10] docs: update TOOLS.md for two-tool architecture --- TOOLS.md | 1095 +++++------------------------------------------------- 1 file changed, 92 insertions(+), 1003 deletions(-) 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" } +``` From 3237c7b74bcfa36aa0adb249b8324ff281f5520f Mon Sep 17 00:00:00 2001 From: limehawk <128890849+limehawk@users.noreply.github.com> Date: Wed, 11 Mar 2026 01:16:54 -0400 Subject: [PATCH 08/10] Derive GET_OPERATIONS and OPERATIONS_LIST from live OpenAPI spec Replace 170 lines of hand-maintained static lists with dynamic derivation from the Dokploy OpenAPI spec at startup. Both tools now share a single cached spec fetch via utils/openApiSpec.ts. Also surfaces Dokploy's actual error message on 400 responses instead of swallowing it. --- package.json | 4 +- src/mcp/tools/api.ts | 210 +++++++------------------------------ src/mcp/tools/apiSchema.ts | 13 +-- src/utils/openApiSpec.ts | 75 +++++++++++++ 4 files changed, 113 insertions(+), 189 deletions(-) create mode 100644 src/utils/openApiSpec.ts 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/mcp/tools/api.ts b/src/mcp/tools/api.ts index 6d4328d..715c154 100644 --- a/src/mcp/tools/api.ts +++ b/src/mcp/tools/api.ts @@ -3,181 +3,16 @@ 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"); -const OPERATIONS_LIST = `Available operations (use dokploy-api-schema for 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`; - -// Operations that use GET. Everything else uses POST. -const GET_OPERATIONS = new Set([ - "ai.get", - "ai.getAll", - "ai.getModels", - "ai.one", - "application.one", - "application.readAppMonitoring", - "application.readTraefikConfig", - "backup.listBackupFiles", - "backup.one", - "bitbucket.getBitbucketBranches", - "bitbucket.getBitbucketRepositories", - "certificates.all", - "certificates.one", - "cluster.addManager", - "cluster.addWorker", - "cluster.getNodes", - "compose.getConvertedCompose", - "compose.getDefaultCommand", - "compose.getTags", - "compose.loadMountsByService", - "compose.loadServices", - "compose.one", - "compose.templates", - "deployment.all", - "deployment.allByCompose", - "deployment.allByServer", - "deployment.allByType", - "destination.all", - "destination.one", - "docker.getConfig", - "docker.getContainers", - "docker.getContainersByAppLabel", - "docker.getContainersByAppNameMatch", - "docker.getServiceContainersByAppName", - "docker.getStackContainersByAppName", - "domain.byApplicationId", - "domain.byComposeId", - "domain.canGenerateTraefikMeDomains", - "domain.one", - "environment.byProjectId", - "environment.one", - "gitea.getGiteaBranches", - "gitea.getGiteaRepositories", - "gitea.getGiteaUrl", - "github.getGithubBranches", - "github.getGithubRepositories", - "github.githubProviders", - "gitlab.getGitlabBranches", - "gitlab.getGitlabRepositories", - "gitProvider.getAll", - "gitProvider.one", - "mariadb.one", - "mongo.one", - "mounts.one", - "mysql.one", - "notification.all", - "notification.getEmailProviders", - "notification.one", - "organization.all", - "organization.allInvitations", - "organization.one", - "port.one", - "postgres.one", - "previewDeployment.all", - "previewDeployment.one", - "project.all", - "project.one", - "redirects.one", - "redis.one", - "registry.all", - "registry.one", - "schedule.list", - "schedule.one", - "security.one", - "server.all", - "server.buildServers", - "server.count", - "server.getDefaultCommand", - "server.getServerMetrics", - "server.getServerTime", - "server.one", - "server.publicIp", - "server.security", - "server.validate", - "server.withSSHKey", - "settings.checkGPUStatus", - "settings.getDokployCloudIps", - "settings.getDokployVersion", - "settings.getIp", - "settings.getLogCleanupStatus", - "settings.getOpenApiDocument", - "settings.getReleaseTag", - "settings.getTraefikPorts", - "settings.haveActivateRequests", - "settings.haveTraefikDashboardPortEnabled", - "settings.health", - "settings.isCloud", - "settings.isUserSubscribed", - "settings.readDirectories", - "settings.readMiddlewareTraefikConfig", - "settings.readTraefikConfig", - "settings.readTraefikEnv", - "settings.readTraefikFile", - "settings.readWebServerTraefikConfig", - "sshKey.all", - "sshKey.one", - "stripe.canCreateMoreServers", - "stripe.getProducts", - "swarm.getNodeApps", - "swarm.getNodeInfo", - "swarm.getNodes", - "user.all", - "user.checkUserOrganizations", - "user.get", - "user.getBackups", - "user.getUserByToken", - "user.getContainerMetrics", - "user.getInvitations", - "user.getMetricsToken", - "user.getServerMetrics", - "user.haveRootAccess", - "user.one", - "volumeBackups.list", - "volumeBackups.one", -]); - -function getMethod(operation: string): "GET" | "POST" { - return GET_OPERATIONS.has(operation) ? "GET" : "POST"; +async function getMethod(operation: string): Promise<"GET" | "POST"> { + const getOps = await getGetOperations(); + return getOps.has(operation) ? "GET" : "POST"; } export const schema = { @@ -197,9 +32,25 @@ export const schema = { export const name = "dokploy-api"; -export const description = `Execute any Dokploy API operation. HTTP method is auto-detected. Use dokploy-api-schema to discover parameters for an operation. +// 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."; -${OPERATIONS_LIST}`; +// 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", @@ -211,8 +62,11 @@ export async function handler(input: { operation: string; params?: Record; }) { + // Ensure description is populated for future registrations + await ensureDescription(); + const { operation, params } = input; - const method = getMethod(operation); + const method = await getMethod(operation); const endpoint = `/${operation}`; logger.info(`Executing ${method} ${endpoint}`, { hasParams: !!params }); @@ -238,6 +92,12 @@ export async function handler(input: { 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", diff --git a/src/mcp/tools/apiSchema.ts b/src/mcp/tools/apiSchema.ts index 404afd2..01687e1 100644 --- a/src/mcp/tools/apiSchema.ts +++ b/src/mcp/tools/apiSchema.ts @@ -1,21 +1,10 @@ import { z } from "zod"; -import apiClient from "../../utils/apiClient.js"; import { createLogger } from "../../utils/logger.js"; import { ResponseFormatter } from "../../utils/responseFormatter.js"; +import { getOpenApiSpec } from "../../utils/openApiSpec.js"; const logger = createLogger("DokployApiSchema"); -let cachedSpec: Record | null = null; - -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!; -} - interface OperationInfo { name: string; method: string; 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; +} From ef2b87d389fd9a0f3a88b5cf26d18190b842800f Mon Sep 17 00:00:00 2001 From: limehawk <128890849+limehawk@users.noreply.github.com> Date: Wed, 11 Mar 2026 01:21:55 -0400 Subject: [PATCH 09/10] docs: update README for two-tool architecture with dynamic OpenAPI spec --- README.md | 92 ++++++++++++++++++++++++------------------------------- 1 file changed, 40 insertions(+), 52 deletions(-) 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: From dd68ff5b026aefc113de6d5cd5385e8f68287a02 Mon Sep 17 00:00:00 2001 From: limehawk <128890849+limehawk@users.noreply.github.com> Date: Wed, 11 Mar 2026 01:22:13 -0400 Subject: [PATCH 10/10] chore: update .talismanrc for current file checksums --- .talismanrc | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .talismanrc diff --git a/.talismanrc b/.talismanrc new file mode 100644 index 0000000..8412474 --- /dev/null +++ b/.talismanrc @@ -0,0 +1,12 @@ +fileignoreconfig: +- filename: src/utils/responseFormatter.ts + checksum: 7a57965c3c892b019d338897aec8e840bdb734838e39e25272155546a0b5fec7 +- filename: TOOLS.md + checksum: a753018f7274e1cd71930d9db29c66dfa670ca2cb6b4d9a0ba35774e630058f7 +- filename: docs/superpowers/specs/2026-03-11-single-api-tool-design.md + checksum: 8d95cc27cd4f944a57299c1dc5b4042b3c5c5d384de3b9c28e1cd6b263feb3c3 +- filename: README.md + checksum: 47ea5aeca35aa2f4987ccdbf193947554a9d80179f66ec03bd5ea13a5916285d +- filename: docs/superpowers/plans/2026-03-11-single-api-tool.md + checksum: 032dd223ad408bc67a8929e647b4d8088b0ff5bc9ebcb1f20b2caf52cd881103 +version: "1.0"