Skip to content

Commit 36a9393

Browse files
janiszclaudemtodor
authored
ROX-31475: add stdio support (#12)
Signed-off-by: Tomasz Janiszewski <tomek@redhat.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Mladen Todorovic <mtodor@gmail.com>
1 parent e5a4d8d commit 36a9393

File tree

5 files changed

+90
-6
lines changed

5 files changed

+90
-6
lines changed

README.md

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,9 @@ HTTP server settings for the MCP server.
9696

9797
| Option | Environment Variable | Type | Required | Default | Description |
9898
|--------|---------------------|------|----------|---------|-------------|
99-
| `server.address` | `STACKROX_MCP__SERVER__ADDRESS` | string | No | `0.0.0.0` | HTTP server listen address |
100-
| `server.port` | `STACKROX_MCP__SERVER__PORT` | int | No | `8080` | HTTP server listen port (must be 1-65535) |
99+
| `server.type` | `STACKROX_MCP__SERVER__TYPE` | string | No | `streamable-http` | Server transport type: `streamable-http` (HTTP server) or `stdio` (stdio transport). **Note**: stdio transport requires `central.auth_type` to be set to `static` |
100+
| `server.address` | `STACKROX_MCP__SERVER__ADDRESS` | string | No | `0.0.0.0` | HTTP server listen address (only applies when `server.type` is `http`) |
101+
| `server.port` | `STACKROX_MCP__SERVER__PORT` | int | No | `8080` | HTTP server listen port (must be 1-65535, only applies when `server.type` is `http`) |
101102

102103
#### Tools Configuration
103104

@@ -138,7 +139,9 @@ The server will start on `http://0.0.0.0:8080` by default (configurable via `ser
138139

139140
### Connecting with Claude Code CLI
140141

141-
Add the MCP server to Claude Code using command-line options:
142+
#### HTTP Transport
143+
144+
Add the MCP server to Claude Code using HTTP transport:
142145

143146
```bash
144147
claude mcp add stackrox \
@@ -147,6 +150,22 @@ claude mcp add stackrox \
147150
--url http://localhost:8080
148151
```
149152

153+
#### Stdio Transport
154+
155+
Add the MCP server to Claude Code using stdio transport with static authentication:
156+
157+
```bash
158+
claude mcp add --transport stdio stackrox \
159+
--env STACKROX_MCP__SERVER__TYPE=stdio \
160+
--env STACKROX_MCP__CENTRAL__AUTH_TYPE=static \
161+
--env STACKROX_MCP__CENTRAL__API_TOKEN="${ROX_TOKEN}" \
162+
--env STACKROX_MCP__CENTRAL__URL=central.stackrox:443 \
163+
--env STACKROX_MCP__TOOLS__CONFIG_MANAGER__ENABLED=true \
164+
-- /path/to/stackrox-mcp
165+
```
166+
167+
**Important**: Stdio transport requires static authentication (`central.auth_type=static`). Passthrough authentication is not supported with stdio transport.
168+
150169
### Verifying Connection
151170

152171
List configured MCP servers:

internal/config/config.go

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ type Config struct {
2929
}
3030

3131
type authType string
32+
type serverType string
3233

3334
const (
3435
// AuthTypePassthrough defines auth flow where API token, used to communicate with MCP server,
@@ -38,6 +39,11 @@ const (
3839
// AuthTypeStatic defines auth flow where API token is statically configured and
3940
// defined in configuration or environment variable.
4041
AuthTypeStatic authType = "static"
42+
43+
// ServerTypeStdio indicates server runs over stdio.
44+
ServerTypeStdio serverType = "stdio"
45+
// ServerTypeStreamableHTTP indicates server runs over streamable-http.
46+
ServerTypeStreamableHTTP serverType = "streamable-http"
4147
)
4248

4349
// CentralConfig contains StackRox Central connection configuration.
@@ -62,8 +68,9 @@ type GlobalConfig struct {
6268

6369
// ServerConfig contains HTTP server configuration.
6470
type ServerConfig struct {
65-
Address string `mapstructure:"address"`
66-
Port int `mapstructure:"port"`
71+
Type serverType `mapstructure:"type"`
72+
Address string `mapstructure:"address"`
73+
Port int `mapstructure:"port"`
6774
}
6875

6976
// ToolsConfig contains configuration for individual MCP tools.
@@ -138,6 +145,7 @@ func setDefaults(viper *viper.Viper) {
138145

139146
viper.SetDefault("server.address", "0.0.0.0")
140147
viper.SetDefault("server.port", defaultPort)
148+
viper.SetDefault("server.type", ServerTypeStreamableHTTP)
141149

142150
viper.SetDefault("tools.vulnerability.enabled", false)
143151
viper.SetDefault("tools.config_manager.enabled", false)
@@ -207,6 +215,14 @@ func (cc *CentralConfig) validate() error {
207215
}
208216

209217
func (sc *ServerConfig) validate() error {
218+
if sc.Type != ServerTypeStreamableHTTP && sc.Type != ServerTypeStdio {
219+
return errors.New("server.type must be either streamable-http or stdio")
220+
}
221+
222+
if sc.Type == ServerTypeStdio {
223+
return nil
224+
}
225+
210226
if sc.Address == "" {
211227
return errors.New("server.address is required")
212228
}
@@ -232,6 +248,10 @@ func (c *Config) Validate() error {
232248
return errors.New("at least one tool has to be enabled")
233249
}
234250

251+
if c.Server.Type == ServerTypeStdio && c.Central.AuthType != AuthTypeStatic {
252+
return errors.New("stdio server does require static auth type")
253+
}
254+
235255
return nil
236256
}
237257

internal/config/config_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ func getDefaultConfig() *Config {
2828
ReadOnlyTools: false,
2929
},
3030
Server: ServerConfig{
31+
Type: ServerTypeStreamableHTTP,
3132
Address: "localhost",
3233
Port: 8080,
3334
},
@@ -201,6 +202,26 @@ tools:
201202
assert.Contains(t, err.Error(), "central.url is required")
202203
}
203204

205+
func TestLoadConfig_ServerTypeValidationPass(t *testing.T) {
206+
validYAMLConfig := `
207+
central:
208+
url: "localhost:8080"
209+
auth_type: static
210+
api_token: "test-token"
211+
server:
212+
type: stdio
213+
address: ""
214+
port: 0
215+
tools:
216+
vulnerability:
217+
enabled: true
218+
`
219+
220+
configPath := testutil.WriteYAMLFile(t, validYAMLConfig)
221+
_, err := LoadConfig(configPath)
222+
require.NoError(t, err)
223+
}
224+
204225
func TestValidate_MissingURL(t *testing.T) {
205226
cfg := getDefaultConfig()
206227
cfg.Central.URL = ""
@@ -299,6 +320,26 @@ func TestValidate_AuthTypePassthrough_ForbidsAPIToken(t *testing.T) {
299320
assert.Contains(t, err.Error(), "passthrough")
300321
}
301322

323+
func TestValidate_AuthTypePassthrough_ForbidsStdio(t *testing.T) {
324+
cfg := getDefaultConfig()
325+
cfg.Central.AuthType = AuthTypePassthrough
326+
cfg.Central.APIToken = ""
327+
cfg.Server.Type = ServerTypeStdio
328+
329+
err := cfg.Validate()
330+
require.Error(t, err)
331+
assert.Contains(t, err.Error(), "stdio server does require static auth type")
332+
}
333+
334+
func TestValidate_ServerType_InvalidType(t *testing.T) {
335+
cfg := getDefaultConfig()
336+
cfg.Server.Type = "invalid-type"
337+
338+
err := cfg.Validate()
339+
require.Error(t, err)
340+
assert.Contains(t, err.Error(), "server.type must be either streamable-http or stdio")
341+
}
342+
302343
func TestValidate_AuthTypePassthrough_Success(t *testing.T) {
303344
cfg := getDefaultConfig()
304345
cfg.Central.AuthType = AuthTypePassthrough

internal/logging/logging.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func SetupLogging() {
3131
}
3232

3333
// Initialize slog with JSON handler.
34-
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
34+
logger := slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
3535
Level: logLevel,
3636
}))
3737

internal/server/server.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ func NewServer(cfg *config.Config, registry *toolsets.Registry) *Server {
5353
func (s *Server) Start(ctx context.Context) error {
5454
s.registerTools()
5555

56+
if s.cfg.Server.Type == config.ServerTypeStdio {
57+
return errors.Wrap(s.mcp.Run(ctx, &mcp.StdioTransport{}), "running mcp over stdio")
58+
}
59+
5660
// Create a new ServeMux for routing.
5761
mux := http.NewServeMux()
5862
s.registerRouteHealth(mux)

0 commit comments

Comments
 (0)