Skip to content

Commit 19a0352

Browse files
committed
Add tools and toolset handling
1 parent c267e67 commit 19a0352

30 files changed

+1502
-67
lines changed

.golangci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ linters:
1919
- paralleltest
2020
- testpackage
2121
- noinlineerr
22+
- ireturn
23+
# requires package-level variables created from errors.New()
24+
- err113
2225
issues:
2326
max-issues-per-linter: 0
2427
max-same-issues: 0

Makefile

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,19 @@
44
# Binary name
55
BINARY_NAME=stackrox-mcp
66

7+
# Version (can be overridden with VERSION=x.y.z make build)
8+
VERSION?=0.1.0
9+
710
# Go parameters
811
GOCMD=go
912
GOBUILD=$(GOCMD) build
1013
GOTEST=$(GOCMD) test
1114
GOFMT=$(GOCMD) fmt
1215
GOCLEAN=$(GOCMD) clean
1316

17+
# Build flags
18+
LDFLAGS=-ldflags "-X github.com/stackrox/stackrox-mcp/internal/server.version=$(VERSION)"
19+
1420
# Coverage files
1521
COVERAGE_OUT=coverage.out
1622

@@ -24,7 +30,7 @@ help: ## Display this help message
2430

2531
.PHONY: build
2632
build: ## Build the binary
27-
$(GOBUILD) -o $(BINARY_NAME) ./cmd/stackrox-mcp
33+
$(GOBUILD) $(LDFLAGS) -o $(BINARY_NAME) ./cmd/stackrox-mcp
2834

2935
.PHONY: test
3036
test: ## Run unit tests with coverage

README.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ export STACKROX_MCP__TOOLS__VULNERABILITY__ENABLED=true
2828
./stackrox-mcp
2929
```
3030

31+
The server will start on `http://localhost:8080` by default. See the [Testing the MCP Server](#testing-the-mcp-server) section for instructions on connecting with Claude Code.
32+
3133
## Configuration
3234

3335
The StackRox MCP server supports configuration through both YAML files and environment variables. Environment variables take precedence over YAML configuration.
@@ -80,6 +82,15 @@ Global MCP server settings.
8082
|--------|---------------------|------|----------|---------|-------------|
8183
| `global.read_only_tools` | `STACKROX_MCP__GLOBAL__READ_ONLY_TOOLS` | bool | No | `true` | Only allow read-only tools |
8284

85+
#### Server Configuration
86+
87+
HTTP server settings for the MCP server.
88+
89+
| Option | Environment Variable | Type | Required | Default | Description |
90+
|--------|---------------------|------|----------|---------|-------------|
91+
| `server.address` | `STACKROX_MCP__SERVER__ADDRESS` | string | No | `localhost` | HTTP server listen address |
92+
| `server.port` | `STACKROX_MCP__SERVER__PORT` | int | No | `8080` | HTTP server listen port (must be 1-65535) |
93+
8394
#### Tools Configuration
8495

8596
Enable or disable individual MCP tools. At least one tool has to be enabled.
@@ -97,6 +108,63 @@ Configuration values are loaded in the following order (later sources override e
97108
2. YAML configuration file (if provided via `--config`)
98109
3. Environment variables (highest precedence)
99110

111+
## Testing the MCP Server
112+
113+
### Starting the Server
114+
115+
Start the server with a configuration file:
116+
117+
```bash
118+
./stackrox-mcp --config examples/config-read-only.yaml
119+
```
120+
121+
Or using environment variables:
122+
123+
```bash
124+
export STACKROX_MCP__CENTRAL__URL="central.example.com:8443"
125+
export STACKROX_MCP__TOOLS__VULNERABILITY__ENABLED="true"
126+
./stackrox-mcp
127+
```
128+
129+
The server will start on `http://localhost:8080` by default (configurable via `server.address` and `server.port`).
130+
131+
### Connecting with Claude Code CLI
132+
133+
Add the MCP server to Claude Code using command-line options:
134+
135+
```bash
136+
claude mcp add stackrox \
137+
--name "StackRox MCP Server" \
138+
--transport http \
139+
--url http://localhost:8080
140+
```
141+
142+
### Verifying Connection
143+
144+
List configured MCP servers:
145+
146+
```bash
147+
claude mcp list
148+
```
149+
150+
Get details for a specific server:
151+
152+
```bash
153+
claude mcp get stackrox
154+
```
155+
156+
Within a Claude Code session, use the `/mcp` command to view available tools from connected servers.
157+
158+
### Example Usage
159+
160+
Once connected, interact with the tools using natural language:
161+
162+
**List all clusters:**
163+
```
164+
You: "Can you list all the clusters from StackRox?"
165+
Claude: [Uses list_clusters tool to retrieve cluster information]
166+
```
167+
100168
## Development
101169

102170
For detailed development guidelines, testing standards, and contribution workflows, see [CONTRIBUTING.md](.github/CONTRIBUTING.md).

cmd/stackrox-mcp/main.go

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,63 @@
22
package main
33

44
import (
5-
"flag"
5+
"context"
66
"log/slog"
77
"os"
8+
"os/signal"
9+
"syscall"
10+
11+
"github.com/spf13/pflag"
812

913
"github.com/stackrox/stackrox-mcp/internal/config"
1014
"github.com/stackrox/stackrox-mcp/internal/logging"
15+
"github.com/stackrox/stackrox-mcp/internal/server"
16+
"github.com/stackrox/stackrox-mcp/internal/toolsets"
17+
toolsetConfig "github.com/stackrox/stackrox-mcp/internal/toolsets/config"
18+
toolsetVulnerability "github.com/stackrox/stackrox-mcp/internal/toolsets/vulnerability"
1119
)
1220

21+
// getToolsets initializes and returns all available toolsets.
22+
func getToolsets(cfg *config.Config) []toolsets.Toolset {
23+
return []toolsets.Toolset{
24+
toolsetConfig.NewToolset(cfg),
25+
toolsetVulnerability.NewToolset(cfg),
26+
}
27+
}
28+
1329
func main() {
1430
logging.SetupLogging()
1531

16-
configPath := flag.String("config", "", "Path to configuration file (optional)")
32+
configPath := pflag.String("config", "", "Path to configuration file (optional)")
1733

18-
flag.Parse()
34+
pflag.Parse()
1935

2036
cfg, err := config.LoadConfig(*configPath)
2137
if err != nil {
22-
slog.Error("Failed to load configuration", "error", err)
23-
os.Exit(1)
38+
logging.Fatal("Failed to load configuration", err)
2439
}
2540

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

43+
registry := toolsets.NewRegistry(cfg, getToolsets(cfg))
44+
srv := server.NewServer(cfg, registry)
45+
46+
// Set up context with signal handling for graceful shutdown.
47+
ctx, cancel := context.WithCancel(context.Background())
48+
defer cancel()
49+
50+
sigChan := make(chan os.Signal, 1)
51+
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
52+
53+
go func() {
54+
<-sigChan
55+
slog.Info("Received shutdown signal")
56+
cancel()
57+
}()
58+
2859
slog.Info("Starting Stackrox MCP server")
60+
61+
if err := srv.Start(ctx); err != nil {
62+
logging.Fatal("Server error", err)
63+
}
2964
}

cmd/stackrox-mcp/main_test.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"errors"
6+
"net"
7+
"net/http"
8+
"strconv"
9+
"testing"
10+
"time"
11+
12+
"github.com/stackrox/stackrox-mcp/internal/config"
13+
"github.com/stackrox/stackrox-mcp/internal/server"
14+
"github.com/stackrox/stackrox-mcp/internal/testutil"
15+
"github.com/stackrox/stackrox-mcp/internal/toolsets"
16+
"github.com/stretchr/testify/assert"
17+
"github.com/stretchr/testify/require"
18+
)
19+
20+
func getDefaultConfig() *config.Config {
21+
return &config.Config{
22+
Global: config.GlobalConfig{
23+
ReadOnlyTools: false,
24+
},
25+
Central: config.CentralConfig{
26+
URL: "central.example.com:8443",
27+
},
28+
Server: config.ServerConfig{
29+
Address: "localhost",
30+
Port: 8080,
31+
},
32+
Tools: config.ToolsConfig{
33+
Vulnerability: config.ToolsetVulnerabilityConfig{
34+
Enabled: true,
35+
},
36+
ConfigManager: config.ToolConfigManagerConfig{
37+
Enabled: false,
38+
},
39+
},
40+
}
41+
}
42+
43+
func TestGetToolsets(t *testing.T) {
44+
cfg := getDefaultConfig()
45+
cfg.Tools.ConfigManager.Enabled = true
46+
47+
allToolsets := getToolsets(cfg)
48+
49+
require.NotNil(t, allToolsets)
50+
assert.Len(t, allToolsets, 2, "Should have 2 allToolsets")
51+
assert.Equal(t, "config_manager", allToolsets[0].GetName())
52+
assert.Equal(t, "vulnerability", allToolsets[1].GetName())
53+
}
54+
55+
func TestGracefulShutdown(t *testing.T) {
56+
// Set up minimal valid config.
57+
t.Setenv("STACKROX_MCP__TOOLS__VULNERABILITY__ENABLED", "true")
58+
59+
cfg, err := config.LoadConfig("")
60+
require.NoError(t, err)
61+
require.NotNil(t, cfg)
62+
cfg.Server.Port = testutil.GetPortForTest(t)
63+
64+
registry := toolsets.NewRegistry(cfg, getToolsets(cfg))
65+
srv := server.NewServer(cfg, registry)
66+
ctx, cancel := context.WithCancel(context.Background())
67+
68+
errChan := make(chan error, 1)
69+
70+
go func() {
71+
errChan <- srv.Start(ctx)
72+
}()
73+
74+
serverURL := "http://" + net.JoinHostPort(cfg.Server.Address, strconv.Itoa(cfg.Server.Port))
75+
err = testutil.WaitForServerReady(serverURL, 3*time.Second)
76+
require.NoError(t, err, "Server should start within timeout")
77+
78+
// Establish actual HTTP connection to verify server is responding.
79+
//nolint:gosec,noctx
80+
resp, err := http.Get(serverURL)
81+
if err == nil {
82+
_ = resp.Body.Close()
83+
}
84+
85+
require.NoError(t, err, "Should be able to establish HTTP connection to server")
86+
87+
// Simulate shutdown signal by canceling context.
88+
cancel()
89+
90+
// Wait for server to shut down.
91+
select {
92+
case err := <-errChan:
93+
// Server should shut down cleanly (either nil or context.Canceled).
94+
if err != nil && errors.Is(err, context.Canceled) {
95+
t.Errorf("Server returned unexpected error: %v", err)
96+
}
97+
case <-time.After(5 * time.Second):
98+
t.Fatal("Server did not shut down within timeout period")
99+
}
100+
}

examples/config-read-only.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,17 @@ global:
3636
# When false, both read and write tools may be available (if implemented)
3737
read_only_tools: true
3838

39+
# HTTP server configuration
40+
server:
41+
# Server listen address (optional, default: localhost)
42+
# The address on which the MCP HTTP server will listen
43+
address: localhost
44+
45+
# Server listen port (optional, default: 8080)
46+
# The port on which the MCP HTTP server will listen
47+
# Must be between 1 and 65535
48+
port: 8080
49+
3950
# Configuration of MCP tools
4051
# Each tool has an enable/disable flag. At least one tool has to be enabled.
4152
tools:

go.mod

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ module github.com/stackrox/stackrox-mcp
33
go 1.24
44

55
require (
6+
github.com/modelcontextprotocol/go-sdk v1.1.0
7+
github.com/pkg/errors v0.9.1
8+
github.com/spf13/pflag v1.0.10
69
github.com/spf13/viper v1.21.0
710
github.com/stretchr/testify v1.11.1
811
)
@@ -11,15 +14,17 @@ require (
1114
github.com/davecgh/go-spew v1.1.1 // indirect
1215
github.com/fsnotify/fsnotify v1.9.0 // indirect
1316
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
17+
github.com/google/jsonschema-go v0.3.0 // indirect
1418
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
1519
github.com/pmezard/go-difflib v1.0.0 // indirect
1620
github.com/sagikazarmark/locafero v0.11.0 // indirect
1721
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
1822
github.com/spf13/afero v1.15.0 // indirect
1923
github.com/spf13/cast v1.10.0 // indirect
20-
github.com/spf13/pflag v1.0.10 // indirect
2124
github.com/subosito/gotenv v1.6.0 // indirect
25+
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
2226
go.yaml.in/yaml/v3 v3.0.4 // indirect
27+
golang.org/x/oauth2 v0.30.0 // indirect
2328
golang.org/x/sys v0.29.0 // indirect
2429
golang.org/x/text v0.28.0 // indirect
2530
gopkg.in/yaml.v3 v3.0.1 // indirect

go.sum

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,20 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
66
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
77
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
88
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
9-
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
10-
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
9+
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
10+
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
11+
github.com/google/jsonschema-go v0.3.0 h1:6AH2TxVNtk3IlvkkhjrtbUc4S8AvO0Xii0DxIygDg+Q=
12+
github.com/google/jsonschema-go v0.3.0/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
1113
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
1214
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
1315
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
1416
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
17+
github.com/modelcontextprotocol/go-sdk v1.1.0 h1:Qjayg53dnKC4UZ+792W21e4BpwEZBzwgRW6LrjLWSwA=
18+
github.com/modelcontextprotocol/go-sdk v1.1.0/go.mod h1:6fM3LCm3yV7pAs8isnKLn07oKtB0MP9LHd3DfAcKw10=
1519
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
1620
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
21+
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
22+
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
1723
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
1824
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
1925
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
@@ -34,12 +40,18 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
3440
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
3541
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
3642
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
43+
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
44+
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
3745
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
3846
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
47+
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
48+
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
3949
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
4050
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
4151
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
4252
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
53+
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
54+
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
4355
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
4456
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
4557
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

0 commit comments

Comments
 (0)