summaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-16 03:25:52 +0200
committerPaul Buetow <paul@buetow.org>2026-03-16 03:25:52 +0200
commitad988c34181b7234a54d279874f29e126607fad3 (patch)
tree0cbae073e0a6b2641477c23129945bcbb33f482c /cmd
parent5cf8526fd81dadd181f30b1d4c862ba1f1b2a5b1 (diff)
Remove os.Setenv from MCP server production code
Replace environment variable communication between cmd and internal packages with explicit MCPOverrides struct. CLI flag values are now passed via typed struct fields through Run/RunWithFactory/RunBackfill. Env var support preserved through appconfig's applyMCPEnv pipeline. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'cmd')
-rw-r--r--cmd/hexai-mcp-server/main.go28
-rw-r--r--cmd/hexai-mcp-server/main_test.go71
2 files changed, 51 insertions, 48 deletions
diff --git a/cmd/hexai-mcp-server/main.go b/cmd/hexai-mcp-server/main.go
index 32817f3..d2495e6 100644
--- a/cmd/hexai-mcp-server/main.go
+++ b/cmd/hexai-mcp-server/main.go
@@ -12,7 +12,17 @@ import (
"codeberg.org/snonux/hexai/internal/hexaimcp"
)
+// buildOverrides constructs MCPOverrides from parsed CLI options.
+func buildOverrides(opts mcpOptions) hexaimcp.MCPOverrides {
+ return hexaimcp.MCPOverrides{
+ PromptsDir: opts.promptsDir,
+ SlashCommandSync: opts.slashCommandSync,
+ SlashCommandDir: opts.slashCommandDir,
+ }
+}
+
// Seams for testing: override in tests to avoid launching real MCP server.
+// Signatures match hexaimcp.Run and hexaimcp.RunBackfill respectively.
var (
runMCP = hexaimcp.Run
runBackfill = hexaimcp.RunBackfill
@@ -86,29 +96,21 @@ func main() {
}
// run executes the MCP server logic with the given options and I/O streams.
+// CLI flag values are passed via MCPOverrides instead of environment variables.
func run(opts mcpOptions, stdin io.Reader, stdout, stderr io.Writer) error {
- // Set environment variables for RunWithFactory based on flag values
- if opts.promptsDir != "" {
- os.Setenv("HEXAI_MCP_PROMPTS_DIR", opts.promptsDir)
- }
- if opts.slashCommandSync {
- os.Setenv("HEXAI_MCP_SLASHCOMMAND_SYNC", "true")
- }
- if opts.slashCommandDir != "" {
- os.Setenv("HEXAI_MCP_SLASHCOMMAND_DIR", opts.slashCommandDir)
- }
-
if opts.showVersion {
fmt.Fprintln(stdout, internal.Version)
return nil
}
+ overrides := buildOverrides(opts)
+
// Handle backfill operation
if opts.syncAll {
- return runBackfill(opts.logPath, opts.configPath)
+ return runBackfill(opts.logPath, opts.configPath, overrides)
}
- return runMCP(opts.logPath, opts.configPath, stdin, stdout, stderr)
+ return runMCP(opts.logPath, opts.configPath, overrides, stdin, stdout, stderr)
}
// defaultLogPath returns the default MCP log file path in the state directory.
diff --git a/cmd/hexai-mcp-server/main_test.go b/cmd/hexai-mcp-server/main_test.go
index 76d0fca..3d85fbb 100644
--- a/cmd/hexai-mcp-server/main_test.go
+++ b/cmd/hexai-mcp-server/main_test.go
@@ -9,6 +9,7 @@ import (
"testing"
"codeberg.org/snonux/hexai/internal"
+ "codeberg.org/snonux/hexai/internal/hexaimcp"
)
func TestPrintDeprecationWarning(t *testing.T) {
@@ -65,39 +66,21 @@ func TestRun_ShowVersion(t *testing.T) {
}
}
-func TestRun_SetsPromptsDir(t *testing.T) {
- t.Setenv("HEXAI_MCP_PROMPTS_DIR", "")
- opts := mcpOptions{showVersion: true, promptsDir: "/tmp/test-prompts"}
- var stdout bytes.Buffer
- if err := run(opts, nil, &stdout, nil); err != nil {
- t.Fatalf("run: %v", err)
- }
- if got := os.Getenv("HEXAI_MCP_PROMPTS_DIR"); got != "/tmp/test-prompts" {
- t.Fatalf("expected HEXAI_MCP_PROMPTS_DIR=/tmp/test-prompts, got %q", got)
- }
-}
-
-func TestRun_SetsSlashCommandSync(t *testing.T) {
- t.Setenv("HEXAI_MCP_SLASHCOMMAND_SYNC", "")
- opts := mcpOptions{showVersion: true, slashCommandSync: true}
- var stdout bytes.Buffer
- if err := run(opts, nil, &stdout, nil); err != nil {
- t.Fatalf("run: %v", err)
+func TestBuildOverrides(t *testing.T) {
+ opts := mcpOptions{
+ promptsDir: "/tmp/test-prompts",
+ slashCommandSync: true,
+ slashCommandDir: "/tmp/test-cmds",
}
- if got := os.Getenv("HEXAI_MCP_SLASHCOMMAND_SYNC"); got != "true" {
- t.Fatalf("expected HEXAI_MCP_SLASHCOMMAND_SYNC=true, got %q", got)
+ overrides := buildOverrides(opts)
+ if overrides.PromptsDir != "/tmp/test-prompts" {
+ t.Fatalf("expected PromptsDir=/tmp/test-prompts, got %q", overrides.PromptsDir)
}
-}
-
-func TestRun_SetsSlashCommandDir(t *testing.T) {
- t.Setenv("HEXAI_MCP_SLASHCOMMAND_DIR", "")
- opts := mcpOptions{showVersion: true, slashCommandDir: "/tmp/test-cmds"}
- var stdout bytes.Buffer
- if err := run(opts, nil, &stdout, nil); err != nil {
- t.Fatalf("run: %v", err)
+ if !overrides.SlashCommandSync {
+ t.Fatal("expected SlashCommandSync=true")
}
- if got := os.Getenv("HEXAI_MCP_SLASHCOMMAND_DIR"); got != "/tmp/test-cmds" {
- t.Fatalf("expected HEXAI_MCP_SLASHCOMMAND_DIR=/tmp/test-cmds, got %q", got)
+ if overrides.SlashCommandDir != "/tmp/test-cmds" {
+ t.Fatalf("expected SlashCommandDir=/tmp/test-cmds, got %q", overrides.SlashCommandDir)
}
}
@@ -106,13 +89,22 @@ func TestRun_SyncAll(t *testing.T) {
t.Cleanup(func() { runBackfill = old })
var gotLog, gotConfig string
- runBackfill = func(logPath, configPath string) error {
+ var gotOverrides hexaimcp.MCPOverrides
+ runBackfill = func(logPath, configPath string, overrides hexaimcp.MCPOverrides) error {
gotLog = logPath
gotConfig = configPath
+ gotOverrides = overrides
return nil
}
- opts := mcpOptions{syncAll: true, logPath: "/tmp/test.log", configPath: "/tmp/cfg.toml"}
+ opts := mcpOptions{
+ syncAll: true,
+ logPath: "/tmp/test.log",
+ configPath: "/tmp/cfg.toml",
+ promptsDir: "/tmp/prompts",
+ slashCommandSync: true,
+ slashCommandDir: "/tmp/cmds",
+ }
if err := run(opts, nil, nil, nil); err != nil {
t.Fatalf("run syncAll: %v", err)
}
@@ -122,6 +114,15 @@ func TestRun_SyncAll(t *testing.T) {
if gotConfig != "/tmp/cfg.toml" {
t.Fatalf("expected configPath=/tmp/cfg.toml, got %q", gotConfig)
}
+ if gotOverrides.PromptsDir != "/tmp/prompts" {
+ t.Fatalf("expected overrides.PromptsDir=/tmp/prompts, got %q", gotOverrides.PromptsDir)
+ }
+ if !gotOverrides.SlashCommandSync {
+ t.Fatal("expected overrides.SlashCommandSync=true")
+ }
+ if gotOverrides.SlashCommandDir != "/tmp/cmds" {
+ t.Fatalf("expected overrides.SlashCommandDir=/tmp/cmds, got %q", gotOverrides.SlashCommandDir)
+ }
}
func TestRun_SyncAllError(t *testing.T) {
@@ -129,7 +130,7 @@ func TestRun_SyncAllError(t *testing.T) {
t.Cleanup(func() { runBackfill = old })
wantErr := errors.New("backfill failed")
- runBackfill = func(_, _ string) error { return wantErr }
+ runBackfill = func(_, _ string, _ hexaimcp.MCPOverrides) error { return wantErr }
opts := mcpOptions{syncAll: true}
if err := run(opts, nil, nil, nil); !errors.Is(err, wantErr) {
@@ -142,7 +143,7 @@ func TestRun_MCPServer(t *testing.T) {
t.Cleanup(func() { runMCP = old })
called := false
- runMCP = func(logPath, configPath string, stdin io.Reader, stdout, stderr io.Writer) error {
+ runMCP = func(logPath, configPath string, overrides hexaimcp.MCPOverrides, stdin io.Reader, stdout, stderr io.Writer) error {
called = true
return nil
}
@@ -161,7 +162,7 @@ func TestRun_MCPServerError(t *testing.T) {
t.Cleanup(func() { runMCP = old })
wantErr := errors.New("server failed")
- runMCP = func(_, _ string, _ io.Reader, _, _ io.Writer) error { return wantErr }
+ runMCP = func(_, _ string, _ hexaimcp.MCPOverrides, _ io.Reader, _, _ io.Writer) error { return wantErr }
if err := run(mcpOptions{}, nil, nil, nil); !errors.Is(err, wantErr) {
t.Fatalf("expected server error, got: %v", err)