Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ linters:
- exhaustruct
- paralleltest
- testpackage
- noinlineerr
issues:
max-issues-per-linter: 0
max-same-issues: 0
75 changes: 75 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,84 @@ make build

Run the server:
```bash
# With configuration file
./stackrox-mcp --config=examples/config-read-only.yaml

# Or using environment variables only
export STACKROX_MCP__CENTRAL__URL=central.stackrox:8443
export STACKROX_MCP__TOOLS__VULNERABILITY__ENABLED=true
./stackrox-mcp
```

## Configuration

The StackRox MCP server supports configuration through both YAML files and environment variables. Environment variables take precedence over YAML configuration.

### Configuration File

Specify a configuration file using the `--config` flag:

```bash
./stackrox-mcp --config=/path/to/config.yaml
```

See [examples/config-read-only.yaml](examples/config-read-only.yaml) for a complete configuration example.

### Environment Variables

All configuration options can be set via environment variables using the naming convention:

```
STACKROX_MCP__SECTION__KEY
```

Note the double underscore (`__`) separator between sections and keys.

#### Examples

```bash
export STACKROX_MCP__CENTRAL__URL=central.stackrox:8443
export STACKROX_MCP__GLOBAL__READ_ONLY_TOOLS=true
export STACKROX_MCP__TOOLS__CONFIG_MANAGER__ENABLED=true
```

### Configuration Options

#### Central Configuration

Configuration for connecting to StackRox Central.

| Option | Environment Variable | Type | Required | Default | Description |
|--------|---------------------|------|----------|---------|-------------|
| `central.url` | `STACKROX_MCP__CENTRAL__URL` | string | Yes | central.stackrox:8443 | URL of StackRox Central instance |
| `central.insecure` | `STACKROX_MCP__CENTRAL__INSECURE` | bool | No | `false` | Skip TLS certificate verification |
| `central.force_http1` | `STACKROX_MCP__CENTRAL__FORCE_HTTP1` | bool | No | `false` | Force HTTP/1.1 instead of HTTP/2 |

#### Global Configuration

Global MCP server settings.

| Option | Environment Variable | Type | Required | Default | Description |
|--------|---------------------|------|----------|---------|-------------|
| `global.read_only_tools` | `STACKROX_MCP__GLOBAL__READ_ONLY_TOOLS` | bool | No | `true` | Only allow read-only tools |

#### Tools Configuration

Enable or disable individual MCP tools. At least one tool has to be enabled.

| Option | Environment Variable | Type | Required | Default | Description |
|--------|---------------------|------|----------|---------|-------------|
| `tools.vulnerability.enabled` | `STACKROX_MCP__TOOLS__VULNERABILITY__ENABLED` | bool | No | `false` | Enable vulnerability management tools |
| `tools.config_manager.enabled` | `STACKROX_MCP__TOOLS__CONFIG_MANAGER__ENABLED` | bool | No | `false` | Enable configuration management tools |

### Configuration Precedence

Configuration values are loaded in the following order (later sources override earlier ones):

1. Default values
2. YAML configuration file (if provided via `--config`)
3. Environment variables (highest precedence)

## Development

For detailed development guidelines, testing standards, and contribution workflows, see [CONTRIBUTING.md](.github/CONTRIBUTING.md).
Expand Down
15 changes: 15 additions & 0 deletions cmd/stackrox-mcp/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,28 @@
package main

import (
"flag"
"log/slog"
"os"

"github.com/stackrox/stackrox-mcp/internal/config"
"github.com/stackrox/stackrox-mcp/internal/logging"
)

func main() {
logging.SetupLogging()

configPath := flag.String("config", "", "Path to configuration file (optional)")

flag.Parse()

cfg, err := config.LoadConfig(*configPath)
if err != nil {
slog.Error("Failed to load configuration", "error", err)
os.Exit(1)
}

slog.Info("Configuration loaded successfully", "config", cfg)

slog.Info("Starting Stackrox MCP server")
}
50 changes: 50 additions & 0 deletions examples/config-read-only.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# StackRox MCP Server Configuration
#
# This is an example configuration file for the StackRox MCP server.
# Copy this file and modify it according to your environment.
#
# Environment Variable Mapping:
# All configuration options can be overridden using environment variables.
# Environment variables take precedence over YAML configuration.
#
# Naming convention: STACKROX_MCP__SECTION__KEY
# Example:
# central:
# url: central.stackrox:8443
#
# Can be overridden with:
# STACKROX_MCP__CENTRAL__URL=central.stackrox:8443

# Central connection configuration
central:
# Central URL (required, default: central.stackrox:8443)
# The URL of your StackRox Central instance
url: central.stackrox:8443

# Allow insecure TLS connection (optional, default: false)
# Set to true to skip TLS certificate verification
insecure: false

# Force HTTP1 (optional, default: false)
# Force HTTP/1.1 instead of HTTP/2
force_http1: false

# Global MCP server configuration
global:
# Allow only read-only MCP tools (optional, default: true)
# When true, only tools that perform read operations are available
# When false, both read and write tools may be available (if implemented)
read_only_tools: true

# Configuration of MCP tools
# Each tool has an enable/disable flag. At least one tool has to be enabled.
tools:
# Vulnerability management tools
vulnerability:
# Enable vulnerability management tools (optional, default: false)
enabled: true

# Configuration management tools
config_manager:
# Enable configuration management tools (optional, default: false)
enabled: true
18 changes: 17 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,26 @@ module github.com/stackrox/stackrox-mcp

go 1.24

require github.com/stretchr/testify v1.11.1
require (
github.com/pkg/errors v0.9.1
github.com/spf13/viper v1.21.0
github.com/stretchr/testify v1.11.1
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.10.0 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.28.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
41 changes: 40 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
@@ -1,10 +1,49 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
110 changes: 110 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Package config provides configuration handling for StackRox MCP server.
package config

import (
"strings"

"github.com/pkg/errors"
"github.com/spf13/viper"
)

// Config represents the complete application configuration.
type Config struct {
Central CentralConfig `mapstructure:"central"`
Global GlobalConfig `mapstructure:"global"`
Tools ToolsConfig `mapstructure:"tools"`
}

// CentralConfig contains StackRox Central connection configuration.
type CentralConfig struct {
URL string `mapstructure:"url"`
Insecure bool `mapstructure:"insecure"`
ForceHTTP1 bool `mapstructure:"force_http1"`
}

// GlobalConfig contains global MCP server configuration.
type GlobalConfig struct {
ReadOnlyTools bool `mapstructure:"read_only_tools"`
}

// ToolsConfig contains configuration for individual MCP tools.
type ToolsConfig struct {
Vulnerability ToolsetVulnerabilityConfig `mapstructure:"vulnerability"`
ConfigManager ToolConfigManagerConfig `mapstructure:"config_manager"`
}

// ToolsetVulnerabilityConfig contains configuration for vulnerability management tools.
type ToolsetVulnerabilityConfig struct {
Enabled bool `mapstructure:"enabled"`
}

// ToolConfigManagerConfig contains configuration for config management tools.
type ToolConfigManagerConfig struct {
Enabled bool `mapstructure:"enabled"`
}

// LoadConfig loads configuration from YAML file and environment variables.
// Environment variables take precedence over YAML configuration.
// Env var naming convention: STACKROX_MCP__SECTION__KEY (double underscore as separator).
// configPath: optional path to YAML configuration file (can be empty).
func LoadConfig(configPath string) (*Config, error) {
viperInstance := viper.New()

setDefaults(viperInstance)

// Set up environment variable support.
// Note: SetEnvPrefix adds a single underscore, so "STACKROX_MCP_" becomes the prefix.
// We want double underscores between sections, so we use "__" in the replacer.
viperInstance.SetEnvPrefix("STACKROX_MCP_")
viperInstance.SetEnvKeyReplacer(strings.NewReplacer(".", "__"))
viperInstance.AutomaticEnv()

if configPath != "" {
viperInstance.SetConfigFile(configPath)

if err := viperInstance.ReadInConfig(); err != nil {
return nil, errors.Wrap(err, "failed to read config file")
}
}

var cfg Config
if err := viperInstance.Unmarshal(&cfg); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal config")
}

if err := cfg.Validate(); err != nil {
return nil, errors.Wrap(err, "invalid configuration")
}

return &cfg, nil
}

// setDefaults sets default values for configuration.
func setDefaults(viper *viper.Viper) {
viper.SetDefault("central.url", "central.stackrox:8443")
viper.SetDefault("central.insecure", false)
viper.SetDefault("central.force_http1", false)

viper.SetDefault("global.read_only_tools", true)

viper.SetDefault("tools.vulnerability.enabled", false)
viper.SetDefault("tools.config_manager.enabled", false)
}

var (
errURLRequired = errors.New("central.url is required")
errAtLeastOneTool = errors.New("at least one tool has to be enabled")
)

// Validate validates the configuration.
func (c *Config) Validate() error {
if c.Central.URL == "" {
return errURLRequired
}

if !c.Tools.Vulnerability.Enabled && !c.Tools.ConfigManager.Enabled {
return errAtLeastOneTool
}

return nil
}
Loading