Plugin-Based Architecture
Relevant source files
The following files were used as context for generating this wiki page:
The dokku-mcp server utilizes a plugin-based architecture to modularize its functionality. Each plugin encapsulates a specific set of Dokku features, such as application management (apps), core server functions (core), or domain handling (domain). This design allows the server to be extensible and to dynamically expose capabilities based on the plugins installed and enabled on the target Dokku host.
The core of this system is a set of interfaces that define what a plugin is and what capabilities it can provide. A central registry discovers and manages the lifecycle of these plugins, activating or deactivating them in response to the state of the Dokku environment. This ensures that the MCP server only presents tools and resources that are actually available on the managed Dokku instance.
Sources: README.md, internal/server-plugin/domain/server-plugin.go
Core Concepts and Interfaces
The plugin system is defined by a small set of interfaces that each plugin must implement. These interfaces dictate the plugin's identity and the types of capabilities it can offer.
Sources: internal/server-plugin/domain/server-plugin.go:8-30
Plugin Interfaces
The fundamental building block is the ServerPlugin interface. Other interfaces extend this to provide specific capabilities like Tools, Resources, or Prompts.
| Interface | Description |
|---|---|
ServerPlugin | The base interface for all plugins. Provides identity metadata like ID, Name, Description, Version, and DokkuPluginName. |
ResourceProvider | Implemented by plugins that provide MCP Resources (e.g., dokku://apps/list). Requires a GetResources method. |
ToolProvider | Implemented by plugins that provide MCP Tools (e.g., create_app). Requires a GetTools method. |
PromptProvider | Implemented by plugins that provide MCP Prompts (e.g., app_doctor). Requires a GetPrompts method. |
Sources: internal/server-plugin/domain/server-plugin.go:8-30
The following diagram illustrates the relationship between these interfaces.
This diagram shows that ResourceProvider, ToolProvider, and PromptProvider are specialized types of ServerPlugin.
Sources: internal/server-plugin/domain/server-plugin.go:17-30
The DokkuPluginName Method
A key part of the ServerPlugin interface is the DokkuPluginName() method. This method links a dokku-mcp server plugin to a specific plugin on the Dokku host.
- If it returns a non-empty string (e.g.,
"postgres"), the server plugin will only be activated if the corresponding Dokku plugin is enabled on the Dokku host. - If it returns an empty string (
""), the plugin is considered a "core" plugin and is always active, regardless of the Dokku plugins installed.
Sources: internal/server-plugin/domain/server-plugin.go:14, internal/server-plugin/application/registry.go:173-175, internal/server-plugins/core/plugin.go:53-55
Plugin Registration and Lifecycle
Plugins are managed by the DynamicServerPluginRegistry, which handles their registration and lifecycle within the application.
Sources: internal/server-plugin/application/registry.go:73-86
Dependency Injection
Plugins are discovered and collected at startup using the go.uber.org/fx dependency injection framework. Any struct that implements domain.ServerPlugin and is provided to the DI container with the server_plugins group tag will be automatically collected by the registry.
// internal/server-plugins/app/plugin.go:490-497
var Module = fx.Module("app",
fx.Provide(
// ...
// Provide the main plugin
fx.Annotate(
NewAppsServerPlugin,
fx.As(new(domain.ServerPlugin)),
fx.ResultTags(`group:"server_plugins"`),
),
),
)
Sources: internal/server-plugins/app/plugin.go:490-497, internal/server-plugin/application/registry.go:85-88
Lifecycle Management
The DynamicServerPluginRegistry hooks into the application's lifecycle using fx.Lifecycle.
- OnStart: When the application starts, the registry's
RegisterHooksmethod is called. - Initial Registration: It iterates through all plugins collected from the DI container and registers them in a simple
ServerPluginRegistry. - Sync Loop: If enabled, it starts a background goroutine (
runSyncLoop) that periodically synchronizes the active plugins with the state of the Dokku host.
The following diagram shows the startup sequence.
This diagram shows the process from the OnStart hook to the initiation of the plugin synchronization loop.
Sources: internal/server-plugin/application/registry.go:91-120
Dynamic Plugin Activation
A core feature of the architecture is the ability to activate and deactivate plugins dynamically based on the Dokku host's configuration. This is handled by the syncServerPlugins method within the DynamicServerPluginRegistry.
Sources: internal/server-plugin/application/registry.go:141-200
Plugin Discovery Service
The ServerPluginDiscoveryService interface defines the contract for discovering which Dokku plugins are enabled. The default implementation, srvPluginDiscoveryService, executes the dokku plugin:list command via SSH and parses the output to get a list of enabled plugins.
Sources: internal/server-plugin/domain/server-plugin.go:47-49, internal/server-plugin/infrastructure/plugin_discovery.go:26-58
Synchronization Logic
The synchronization process runs periodically (configured by plugin_discovery.sync_interval) and performs the following steps for each registered server plugin:
- Get Enabled Dokku Plugins: It calls
GetEnabledDokkuPlugins()on the discovery service to fetch the current list of enabled plugins from the Dokku host. - Check
DokkuPluginName(): It retrieves the required Dokku plugin name from the server plugin'sDokkuPluginName()method. - Determine Activation State:
- If
DokkuPluginName()is empty, the plugin should be active (it's a core plugin). - If
DokkuPluginName()is not empty, it checks if the name exists in the list of enabled Dokku plugins.
- If
- Activate/Deactivate: It compares the desired state with the current state and updates the active status of the plugin accordingly.
This logic ensures that the MCP server's capabilities accurately reflect the features available on the managed Dokku instance.
This flowchart illustrates the decision-making process for activating or deactivating a server plugin during a synchronization cycle.
Sources: internal/server-plugin/application/registry.go:141-200
Plugin Implementation Example: AppsServerPlugin
The AppsServerPlugin is a concrete example of a plugin that provides comprehensive functionality for managing Dokku applications.
Sources: internal/server-plugins/app/plugin.go
Interface Implementation
The plugin implements ResourceProvider, ToolProvider, and PromptProvider to expose its features.
ID(): Returns"apps".Name(): Returns"Dokku Applications".DokkuPluginName(): Returns"", identifying it as a core plugin that is always active.
Sources: internal/server-plugins/app/plugin.go:34-42
Provided Capabilities
The AppsServerPlugin exposes the following capabilities:
| Type | Name/URI | Description |
|---|---|---|
| Resource | dokku://apps/list | Provides a JSON list of all Dokku applications with status. |
| Tool | create_app | Creates a new Dokku application. |
| Tool | deploy_app | Deploys an application from a Git repository. |
| Tool | scale_app | Scales application processes. |
| Tool | configure_app | Sets environment variables for an application. |
| Tool | get_app_status | Gets comprehensive status for a single application. |
| Prompt | app_doctor | Provides a prompt for diagnosing application health issues. |
Sources: internal/server-plugins/app/plugin.go:45-103
Each capability is implemented via a dedicated handler function within the plugin file, which in turn calls the appropriate use case in the application layer. For example, the handleCreateApp tool handler calls applicationUseCase.CreateApplication.
Sources: internal/server-plugins/app/plugin.go:250-264
Conclusion
The plugin-based architecture is a cornerstone of the dokku-mcp server, providing significant modularity and flexibility. By separating features into distinct plugins and dynamically managing their lifecycle based on the remote Dokku host's state, the system ensures that it only exposes relevant and functional capabilities to the MCP client. This design makes the server easy to extend with new features by simply creating a new plugin that implements the required interfaces and provides its logic to the dependency injection container.
Sources: README.md, internal/server-plugin/application/registry.go