Portainer MCP Client and Model Usage Guide
This document clarifies the different client implementations and model structures used within the portainer-mcp project to prevent confusion and aid development.
Overview
The project interacts with the Portainer API using two main client layers and involves two primary sets of data models:
- Raw Client & Models: Provided by the
portainer/client-api-golibrary. - Wrapper Client & Local Models: Defined within
portainer-mcp/pkg/portainer/. - Raw HTTP Client (Local Stacks): Direct HTTP requests for APIs not covered by the SDK.
Understanding the distinction and interaction between these layers is crucial.
Clients
1. Raw Client (portainer/client-api-go/v2)
- Package:
github.com/portainer/client-api-go/v2 - Role: This is the underlying library that directly communicates with the Portainer API.
- Usage: It’s instantiated within the Wrapper Client. It’s also often used directly within integration tests (
tests/integration/) to fetch the ground-truth state from Portainer for comparison against the MCP handler’s output. - Models Used: Interacts primarily with the Raw Models defined in
github.com/portainer/client-api-go/v2/pkg/models.
2. Wrapper Client (portainer-mcp/pkg/portainer/client)
- Package:
github.com/Malaccamaxgit/portainer-mcp-safe/pkg/portainer/client - Role: This client acts as an abstraction layer on top of the Raw Client. Its primary purposes are:
- To simplify the interface exposed to the rest of the
portainer-mcpapplication (specifically the MCP server handlers ininternal/mcp/). - To perform necessary data transformations, converting Raw Models from the API into the simpler, tailored Local Models.
- To encapsulate common logic or error handling related to Portainer API interactions.
- To simplify the interface exposed to the rest of the
- Usage: This is the client used by the MCP server handlers (
internal/mcp/server.goinstantiates it and passes it to handlers). - Models Used: Takes Raw Models as input from the Raw Client but typically returns Local Models (
portainer-mcp/pkg/portainer/models) after performing conversions.
3. Raw HTTP Client (Local Stacks) (portainer-mcp/pkg/portainer/client/local_stack.go)
- Role: Provides direct HTTP access to Portainer REST API endpoints that are not exposed by the SDK (
client-api-go). Currently used for regular/standalone Docker Compose stacks (as opposed to Edge Stacks). - Why: The official
portainer/client-api-goSDK only contains Edge Stack methods (edge_stack.go). Regular stack endpoints (/api/stacks/*) are not available in the SDK, so direct HTTP requests are necessary. - Implementation:
- Uses
apiRequest()helper method onPortainerClientthat constructs HTTP requests with theX-API-Keyauthentication header. - The
rawHTTPClientstruct (embedded inPortainerClientasrawCli) storesserverURL,token, andhttpClifields for this purpose. - URL scheme normalization ensures
https://is used by default when no scheme is provided.
- Uses
- Models Used: Defines its own
RawLocalStack/LocalStacktypes inpkg/portainer/models/stack.gowith aConvertRawLocalStackToLocalStack()conversion function. - Testing: Uses
httptest.NewServerto mock the Portainer REST API at the HTTP level, rather than mocking the SDK interface.
Models
1. Raw Models (portainer/client-api-go/v2/pkg/models)
- Package:
github.com/portainer/client-api-go/v2/pkg/models - Role: These structs directly map to the data structures returned by the Portainer API.
- Characteristics: Can be complex, may contain fields not relevant to MCP, and might use types (like numeric enums) that are less convenient for MCP’s purposes.
- Examples:
models.PortainereeSettings,models.PortainereeEndpoint. - Usage: Returned by the Raw Client, used as input to the conversion functions within the Wrapper Client / Local Models package.
- Naming Convention: To improve clarity, variables holding instances of these Raw Models are typically prefixed with
raw(e.g.,rawSettings,rawEndpoint).
2. Local Models (portainer-mcp/pkg/portainer/models)
- Package:
github.com/Malaccamaxgit/portainer-mcp-safe/pkg/portainer/models - Role: These are simplified, tailored structs designed specifically for use within the
portainer-mcpapplication and for exposure via the MCP tools. - Characteristics: Simpler structure, contain only relevant fields, often use more convenient types (like string enums).
- Examples:
models.PortainerSettings,models.Environment,models.EnvironmentTag. - Usage: Returned by the Wrapper Client, used within MCP server handlers, and ultimately determine the structure of data returned by MCP tools.
3. Conversion Functions
- Location: Typically reside within
portainer-mcp/pkg/portainer/models. - Role: Bridge the gap between Raw Models and Local Models.
- Examples:
ConvertSettingsToPortainerSettings,ConvertEndpointToEnvironment. - Usage: Called by the Wrapper Client methods to transform data before returning it. The function parameters accepting Raw Models typically follow the
rawprefix naming convention (e.g.,func ConvertSettingsToPortainerSettings(rawSettings *apimodels.PortainereeSettings)).
Typical Workflow Example (GetSettings)
- MCP Handler (
internal/mcp/settings.go): Receives a tool call. - Calls
s.cli.GetSettings(). Here,s.cliis an instance of the Wrapper Client (PortainerClient). - Wrapper Client (
pkg/portainer/client/settings.go): ItsGetSettingsmethod is executed. - Calls the Raw Client’s
GetSettingsmethod (e.g.,c.cli.GetSettings()). - Raw Client interacts with the Portainer API and returns a Raw Model (
*portainermodels.PortainereeSettings). - Wrapper Client calls the Conversion Function (
models.ConvertSettingsToPortainerSettings) with the Raw Model. - Conversion Function returns a Local Model (
models.PortainerSettings). - Wrapper Client returns the Local Model to the MCP Handler.
- MCP Handler marshals the Local Model (
models.PortainerSettings) into JSON and returns it as the tool result.
Typical Workflow Example (Local Stacks — GetLocalStacks)
Unlike the SDK-based workflow above, local stack operations use direct HTTP requests:
- MCP Handler (
internal/mcp/local_stack.go): Receives a tool call forlistLocalStacks. - Calls
s.cli.GetLocalStacks(). Here,s.cliis the Wrapper Client (PortainerClient). - Wrapper Client (
pkg/portainer/client/local_stack.go): ItsGetLocalStacksmethod usesapiRequest()to make a directGET /api/stacksHTTP request. - The Portainer REST API returns a JSON array of raw stack objects.
- The method decodes the JSON into
[]models.RawLocalStackand callsmodels.ConvertRawLocalStackToLocalStack()for each entry. - Returns
[]models.LocalStack(Local Models) to the MCP Handler. - MCP Handler marshals the Local Models into JSON and returns them as the tool result.
Import Conventions
To improve clarity, especially in files where both model types might appear (like tests), consider using consistent import aliases. Leaving the local portainer-mcp/pkg/portainer/models package as the default models and aliasing the external library is recommended:
import (
"github.com/Malaccamaxgit/portainer-mcp-safe/pkg/portainer/models" // Default: models (Local MCP Models)
apimodels "github.com/portainer/client-api-go/v2/pkg/models" // Alias: apimodels (Raw Client-API-Go Models)
)
This approach keeps code cleaner for the more frequently used local models while clearly indicating when the raw API models are involved.
Testing Implications
- Unit Tests (like
pkg/portainer/client/settings_test.go): Should mock the Raw Client interface and verify that the Wrapper Client correctly calls the Raw Client and performs the necessary conversions, returning the expected Local Model. - Unit Tests for Local Stacks (like
pkg/portainer/client/local_stack_test.go): Usehttptest.NewServerto mock the Portainer REST API at the HTTP level, since these methods bypass the SDK. Create a test client withserverURLpointing to the test server. - Integration Tests (like
tests/integration/settings_test.go):- Call the MCP handler, which uses the Wrapper Client internally and returns JSON representing a Local Model.
- Often need to also call the Raw Client directly to get the ground-truth state from the live Portainer instance (variables holding this state should follow the
rawprefix convention, e.g.,rawEndpoint). - May need to manually apply the same Conversion Function to the Raw Model obtained from the Raw Client to create an expected Local Model for comparison against the handler’s result.
By understanding these distinct layers and their interactions, development and testing within portainer-mcp should be clearer.