From d399a216c195657d2d41194e9c64c6e74cb9c2ad Mon Sep 17 00:00:00 2001 From: Mathias Bergqvist Date: Mon, 4 May 2026 20:19:31 +0200 Subject: [PATCH] feat(config): env-var loading Add internal/config package with Config struct and Load() function. Reads GITEA_BASE_URL, GITEA_API_TOKEN, GITEA_MCP_ALLOWED_OWNERS, GITEA_MCP_ORIGIN_ALLOWLIST, GITEA_MCP_PORT with sensible defaults. Wire cfg.Port into main.go. TDD: tests written first, then impl. Co-Authored-By: Claude Sonnet 4.6 --- cmd/gitea-mcp/main.go | 10 +++++++- go.mod | 7 ++++++ go.sum | 9 +++++++ internal/config/config.go | 46 ++++++++++++++++++++++++++++++++++ internal/config/config_test.go | 38 ++++++++++++++++++++++++++++ 5 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 go.sum create mode 100644 internal/config/config.go create mode 100644 internal/config/config_test.go diff --git a/cmd/gitea-mcp/main.go b/cmd/gitea-mcp/main.go index 4e96112..cd21d18 100644 --- a/cmd/gitea-mcp/main.go +++ b/cmd/gitea-mcp/main.go @@ -4,18 +4,26 @@ import ( "log/slog" "net/http" "os" + + "gitea.d-ma.be/mathias/gitea-mcp/internal/config" ) func main() { logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) + cfg, err := config.Load() + if err != nil { + logger.Error("load config", "err", err) + os.Exit(1) + } + mux := http.NewServeMux() mux.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte("ok")) }) - addr := ":8080" + addr := ":" + cfg.Port logger.Info("gitea-mcp starting", "addr", addr) if err := http.ListenAndServe(addr, mux); err != nil { logger.Error("server stopped", "err", err) diff --git a/go.mod b/go.mod index 16ffd11..bc7d9f1 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,10 @@ module gitea.d-ma.be/mathias/gitea-mcp go 1.26.2 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/testify v1.11.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..cc8b3f4 --- /dev/null +++ b/go.sum @@ -0,0 +1,9 @@ +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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +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/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= diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..9b0defe --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,46 @@ +package config + +import ( + "os" + "strings" +) + +type Config struct { + Port string // GITEA_MCP_PORT, default 8080 + GiteaBaseURL string // GITEA_BASE_URL, e.g. https://gitea.d-ma.be + GiteaAPIToken string // GITEA_API_TOKEN — bot user token + AllowedOwners []string // GITEA_MCP_ALLOWED_OWNERS, comma-separated, default "mathias" + OriginAllowlist []string // GITEA_MCP_ORIGIN_ALLOWLIST, comma-separated +} + +func Load() (Config, error) { + cfg := Config{ + Port: envOr("GITEA_MCP_PORT", "8080"), + GiteaBaseURL: os.Getenv("GITEA_BASE_URL"), + GiteaAPIToken: os.Getenv("GITEA_API_TOKEN"), + AllowedOwners: splitCSV(envOr("GITEA_MCP_ALLOWED_OWNERS", "mathias")), + OriginAllowlist: splitCSV(os.Getenv("GITEA_MCP_ORIGIN_ALLOWLIST")), + } + return cfg, nil +} + +func envOr(key, def string) string { + if v := os.Getenv(key); v != "" { + return v + } + return def +} + +func splitCSV(s string) []string { + if s == "" { + return nil + } + parts := strings.Split(s, ",") + out := make([]string, 0, len(parts)) + for _, p := range parts { + if p = strings.TrimSpace(p); p != "" { + out = append(out, p) + } + } + return out +} diff --git a/internal/config/config_test.go b/internal/config/config_test.go new file mode 100644 index 0000000..29f6baf --- /dev/null +++ b/internal/config/config_test.go @@ -0,0 +1,38 @@ +package config_test + +import ( + "testing" + + "gitea.d-ma.be/mathias/gitea-mcp/internal/config" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestLoadDefaults(t *testing.T) { + t.Setenv("GITEA_BASE_URL", "") + t.Setenv("GITEA_API_TOKEN", "") + t.Setenv("GITEA_MCP_ALLOWED_OWNERS", "") + t.Setenv("GITEA_MCP_ORIGIN_ALLOWLIST", "") + t.Setenv("GITEA_MCP_PORT", "") + + cfg, err := config.Load() + require.NoError(t, err) + assert.Equal(t, "8080", cfg.Port) + assert.Equal(t, []string{"mathias"}, cfg.AllowedOwners) +} + +func TestLoadFromEnv(t *testing.T) { + t.Setenv("GITEA_BASE_URL", "https://gitea.d-ma.be") + t.Setenv("GITEA_API_TOKEN", "test-token") + t.Setenv("GITEA_MCP_ALLOWED_OWNERS", "mathias,acme") + t.Setenv("GITEA_MCP_ORIGIN_ALLOWLIST", "https://claude.ai,https://api.anthropic.com") + t.Setenv("GITEA_MCP_PORT", "9000") + + cfg, err := config.Load() + require.NoError(t, err) + assert.Equal(t, "https://gitea.d-ma.be", cfg.GiteaBaseURL) + assert.Equal(t, "test-token", cfg.GiteaAPIToken) + assert.Equal(t, []string{"mathias", "acme"}, cfg.AllowedOwners) + assert.Equal(t, []string{"https://claude.ai", "https://api.anthropic.com"}, cfg.OriginAllowlist) + assert.Equal(t, "9000", cfg.Port) +}