Request Lifecycle
Relevant source files
The following files were used as context for generating this wiki page:
The request lifecycle in the Dokku MCP server describes the end-to-end process of handling a Model Context Protocol (MCP) request, from its reception to the execution of a Dokku command and the return of a response. The system is built on a plugin-based architecture that follows Domain-Driven Design (DDD) principles, separating concerns into application, domain, and infrastructure layers.
This lifecycle distinguishes between simple synchronous requests (e.g., listing apps) and complex asynchronous operations like application deployments, which are handled in the background to provide an immediate response to the client. The core of the lifecycle is the secure execution of Dokku commands over SSH.
High-Level Request Flow
All interactions begin with an MCP client sending a request to the server, which can operate in stdio or sse transport mode. The server identifies the appropriate plugin to handle the request, which then orchestrates the necessary business logic and infrastructure calls.
The following diagram illustrates the general flow of a request through the system's layers.
This diagram shows how a request flows from the transport layer to a specific plugin, through the use case and infrastructure layers, down to the Dokku API client which executes the command on the Dokku host.
Sources: README.md, internal/server-plugins/app/plugin.go:28-34
Synchronous Request Handling
Synchronous requests, such as get_app_status or apps:list, follow a direct path of execution and block until the Dokku command completes.
1. Plugin Routing and Handling
When an MCP request arrives, it is routed to the corresponding plugin and handler. For example, a CallToolRequest for create_app is directed to the handleCreateApp function within the app plugin.
The plugin defines the tool's interface, including its parameters and description, using an MCP builder.
Sources: internal/server-plugins/app/plugin.go:70-74, internal/server-plugins/app/plugin.go:160-170
2. Command Validation and Execution
The core of the request handling is the DokkuClient, which is responsible for executing commands against the Dokku instance.
The sequence for executing a synchronous command is as follows:
- The handler calls a use case, which in turn calls an infrastructure component (e.g., a repository).
- The infrastructure component invokes
dokku-api.client.ExecuteCommand. - Validation:
ValidateCommandis called first. It checks the command name and arguments against a configurable blacklist and a static list of dangerous characters to prevent command injection. - SSH Preparation:
sshConnManager.PrepareSSHCommandconstructs the fullsshcommand, including arguments for PTY allocation (-t), port, identity file, and the Dokku command to be executed. - Execution:
exec.CommandContextruns the preparedsshcommand. - Response: The
stdoutandstderrfrom the command are captured and returned. Errors are wrapped with context, with special handling for "not found" errors.
The following diagram details the synchronous command execution flow.
This sequence shows the steps within the DokkuClient to validate, prepare, and execute a command over SSH, and return the result.
Sources: internal/dokku-api/client.go:102-111, internal/dokku-api/ssh_integration_test.go:111-120
Security Validation
To prevent command injection, every command and its arguments are sanitized. The ValidateCommand function performs two key checks:
- Blacklist Check: It compares the command against a list of blacklisted patterns (e.g.,
destroy,uninstall). - Dangerous Character Check: It scans the command and arguments for characters that could be used for shell injection.
| Blocked Character | Description |
|---|---|
; | Command separator |
& | Background process |
| ` | ` |
` | Command substitution |
$ | Variable/Command substitution |
( ) | Subshell execution |
{ } | Command grouping |
< > | I/O redirection |
\n \r | Newline / Carriage Return |
Sources: internal/dokku-api/client.go:21-40, internal/dokku-api/client_blacklist_test.go:61-90
Asynchronous Request Handling: Deployments
Operations that are expected to be long-running, such as application deployments, are handled asynchronously to avoid blocking the MCP client. The server immediately returns a deploymentID while tracking the actual status in the background.
Deployment Lifecycle Flow
The asynchronous deployment process was specifically designed to replace a "fire-and-forget" approach where SSH timeouts were incorrectly treated as successful deployments. The new flow ensures accurate status tracking.
- Initiation: A client calls the
deploy_apptool. - Locking: The
deploymentInfrastructurecomponent acquires a mutex for the application to prevent concurrent deployments. - Git Sync: The
git:synccommand is executed synchronously. This part is relatively fast. - Async Rebuild: If
git:syncsucceeds,performAsyncRebuildis called. - Polling Start: A
DeploymentPolleris started in a new goroutine. It will periodically call aDeploymentStatusCheckerto query the app's real status from Dokku (apps:report,ps:report). - Rebuild Trigger: The
ps:rebuildcommand is executed in a separate goroutine. This SSH command is expected to time out, but the build process continues on the Dokku server. - Immediate Response: The initial
deploy_apphandler returns immediately to the client with the deployment entity, including its unique ID. - Background Tracking: The poller continues to check the status until it reaches a terminal state (
succeededorfailed) or a timeout is reached (default 30 minutes).
The following sequence diagram illustrates this asynchronous flow.
This diagram shows how a deployment request returns immediately while the actual build and status checking happen in the background.
Sources: PHASE1_IMPLEMENTATION.md, internal/server-plugins/deployment/infrastructure/deployment_infrastructure.go:79-136
Deployment Status
The status of a deployment is tracked through a simple state machine. The DeploymentStatusChecker determines the state by inspecting reports from Dokku.
| Status | Description |
|---|---|
pending | The deployment has been initiated but is not yet running. |
running | The build is in progress, but no processes are running yet. |
succeeded | The build is complete, and application processes are running. |
failed | The deployment failed due to a build error, timeout, or locked application. |
Sources: PHASE1_IMPLEMENTATION.md, internal/server-plugins/deployment/infrastructure/deployment_status_checker.go:37-77
The transitions between these states are managed by the DeploymentPoller and DeploymentTracker.
This diagram shows the possible states of a deployment during its lifecycle.
Sources: PHASE1_IMPLEMENTATION.md
Conclusion
The Dokku MCP server's request lifecycle is designed for robustness and clarity. It employs a layered architecture to separate concerns, with a strong emphasis on security through command validation. The distinction between synchronous and asynchronous handling, particularly for deployments, provides a responsive user experience while ensuring the correctness of long-running operations by tracking their true status on the Dokku host. This architecture allows for clear, maintainable, and extensible management of Dokku resources.