Architecture
import { Aside } from ‘@astrojs/starlight/components’;
System Overview
Section titled “System Overview”┌─────────────────────┐ MCP Protocol ┌─────────────────────┐ HTTPS ┌───────────────┐│ AI Assistant │ ◄──── (stdio/JSON-RPC) ──►│ Portainer MCP │ ◄──────────────► │ Portainer ││ Claude / Copilot │ │ Server │ │ API ││ Cursor / etc. │ │ (Go binary) │ │ v2.39.1 │└─────────────────────┘ └─────────────────────┘ └───────────────┘The server acts as a bridge between MCP-compatible AI assistants and the Portainer management platform. It translates MCP tool calls into Portainer API requests and returns structured results.
Package Structure
Section titled “Package Structure”portainer-mcp/├── cmd/portainer-mcp-enhanced/ # Entry point, CLI flag parsing│ └── mcp.go # main(), flag definitions, server bootstrap├── internal/│ ├── mcp/ # MCP server implementation│ │ ├── server.go # Server struct, PortainerClient interface, options│ │ ├── client_interfaces.go # 18 domain-specific client sub-interfaces│ │ ├── metatool_registry.go # 15 meta-tool definitions│ │ ├── metatool_handler.go # Meta-tool routing logic│ │ ├── schema.go # Tool constants, HTTP validation│ │ └── *.go # Domain handlers (docker, kubernetes, helm, etc.)│ └── k8sutil/ # Kubernetes response metadata stripping├── pkg/│ ├── portainer/│ │ ├── client/ # Wrapper client over raw SDK│ │ │ ├── adapter.go # HTTP transport adapter│ │ │ └── adapter_*.go # 16 domain adapter files (environments, stacks, docker, etc.)│ │ └── models/ # Local model definitions + converters│ └── toolgen/ # YAML tool definition loader + parameter extraction├── tools.yaml # Embedded tool definitions (98 tools)├── tests/integration/ # Integration test suite└── docs/ # Documentation site (Starlight)Key Components
Section titled “Key Components”Entry Point (cmd/portainer-mcp-enhanced/mcp.go)
Section titled “Entry Point (cmd/portainer-mcp-enhanced/mcp.go)”Parses CLI flags, creates the Portainer client, and starts the MCP server. Handles:
- Flag validation (required
-serverand-token) - Client construction with functional options
- Server creation with
WithClient(),WithReadOnly(),WithGranularTools() - Portainer version compatibility check
- Tool registration (meta or granular)
ServeStdio()with graceful signal handling
MCP Server (internal/mcp/server.go)
Section titled “MCP Server (internal/mcp/server.go)”The core Server struct holds:
PortainerClientinterface — abstraction over the Portainer API- Configuration flags (read-only, granular tools)
- All tool handler registrations
The PortainerClient interface composes 18 domain-specific sub-interfaces defined in client_interfaces.go (e.g., TagClient, EnvironmentClient, StackClient, DockerClient, HelmClient). This composition enables fine-grained mocking in tests and follows the Interface Segregation Principle.
Meta-Tool System
Section titled “Meta-Tool System”Registry (metatool_registry.go): Defines 15 MetaToolDef structures, each containing:
- Tool name and description
- List of
MetaActionentries (action name → handler function → read-only flag) - Parameter definitions for each action
Handler (metatool_handler.go): Provides RegisterMetaTools() which:
- Iterates over all meta-tool definitions
- Filters actions based on read-only mode
- Builds the
actionenum from available actions - Merges parameters from all actions (deduplicating shared params like
id) - Creates a routing handler that dispatches by action name
Tool Definitions (tools.yaml)
Section titled “Tool Definitions (tools.yaml)”A YAML file embedded in the binary at build time. Each tool definition includes:
- Name, description, parameter schemas
- MCP annotations (readOnlyHint, destructiveHint, idempotentHint, openWorldHint)
- Required/optional parameter flags
The pkg/toolgen package loads and validates these definitions, converting them into MCP-compatible tool schemas.
Wrapper Client (pkg/portainer/client/)
Section titled “Wrapper Client (pkg/portainer/client/)”An abstraction layer over the raw Portainer SDK client (portainer/client-api-go/v2). It:
- Simplifies the SDK’s interface for MCP use
- Handles data transformation between raw API models and local models
- Configures HTTP transport (TLS, timeouts, scheme detection)
Uses the functional options pattern for configuration:
client.NewAdapter( client.WithHost("portainer.example.com:9443"), client.WithToken("ptr_abc123"), client.WithSkipTLSVerify(false),)Local Models (pkg/portainer/models/)
Section titled “Local Models (pkg/portainer/models/)”Simplified data structures tailored for MCP responses. Each model:
- Contains only relevant fields (not the full API response)
- Uses convenient Go types
- Includes a
FromXxx()conversion function from the raw API model - Is fully documented with godoc comments
Request Flow
Section titled “Request Flow”- AI assistant sends an MCP
tools/callrequest via stdin - MCP framework routes to the registered handler
- Handler extracts and validates parameters
- Wrapper client translates to a Portainer API call
- Portainer API processes the request and returns data
- Local model conversion transforms the response
- Handler serializes the model to JSON
- MCP framework returns the response via stdout
Error Handling
Section titled “Error Handling”Errors follow a consistent pattern:
result, err := s.client.GetEnvironment(id)if err != nil { return nil, fmt.Errorf("failed to get environment: %w", err)}- All errors include descriptive context
- Errors are wrapped with
%wfor chain inspection - Parameter validation happens before API calls
- Invalid parameters return clear error messages
Graceful Shutdown
Section titled “Graceful Shutdown”The server handles SIGINT and SIGTERM signals:
- Signal received → context cancelled
- Server stops accepting new requests
- In-flight requests complete
- Server exits cleanly
Testing Strategy
Section titled “Testing Strategy”| Level | Location | Purpose |
|---|---|---|
| Unit tests | internal/mcp/*_test.go | Handler logic, parameter validation, meta-tool routing |
| Model tests | pkg/portainer/models/*_test.go | Model conversion, nil safety, edge cases |
| Integration tests | tests/integration/ | End-to-end with real Portainer (Docker) |
Integration tests use Docker containers to spin up a Portainer instance, then compare MCP handler results against direct API calls for accuracy.