// Command gitea-admin-mcp is a LOCAL-ONLY stdio MCP server exposing exactly // one tool: mint_service_account_token. // // This exists because dispatch#67 fix #2 hit a hard wall: Gitea's own // token-creation and admin-user endpoints reject token auth outright — an // existing API token cannot create another token, or a new account, by // design (a leaked token being usable to escalate would defeat the point of // scoping it). The only way through is real basic auth as an actual admin, // which a sandboxed agent session cannot safely hold. This binary is meant // to be spawned by a LOCAL Claude Code session (or run by hand) on a machine // with real, interactive 1Password access — configured as a stdio MCP // server in .mcp.json, or invoked directly. // // Deliberately NOT part of cmd/gitea-mcp (the shared HTTP server): the // capability here (reset a target account's password, mint it a token) is // far too dangerous to ever expose over the network, even behind OIDC. // internal/localtools is a separate package specifically so it can never be // wired into that server's RegisterAll by accident. // // Required env (fetch admin credentials just-in-time, e.g. via // `op run --env-file ~/.op-env -- gitea-admin-mcp`): // // GITEA_BASE_URL e.g. https://git.d-ma.be // GITEA_ADMIN_USERNAME a real site-admin account's username // GITEA_ADMIN_PASSWORD that account's real password // OP_VAULT 1Password vault to store minted tokens in // (default: AgentSecrets, per the estate's agent-governed // ops-secrets policy) package main import ( "context" "fmt" "log/slog" "os" "git.d-ma.be/mathias/gitea-mcp/internal/adminbootstrap" "git.d-ma.be/mathias/gitea-mcp/internal/localtools" "git.d-ma.be/mathias/gitea-mcp/internal/mcp" "git.d-ma.be/mathias/gitea-mcp/internal/registry" ) func envOr(key, def string) string { if v := os.Getenv(key); v != "" { return v } return def } func main() { // Logs to stderr, never stdout — stdout is the MCP JSON-RPC channel, and // a stray log line there would corrupt the protocol stream. logger := slog.New(slog.NewJSONHandler(os.Stderr, nil)) baseURL := os.Getenv("GITEA_BASE_URL") adminUser := os.Getenv("GITEA_ADMIN_USERNAME") adminPass := os.Getenv("GITEA_ADMIN_PASSWORD") if baseURL == "" || adminUser == "" || adminPass == "" { fmt.Fprintln(os.Stderr, "GITEA_BASE_URL, GITEA_ADMIN_USERNAME, and GITEA_ADMIN_PASSWORD are all required") os.Exit(1) } b := adminbootstrap.Bootstrapper{ BaseURL: baseURL, AdminUsername: adminUser, AdminPassword: adminPass, Secrets: localtools.OpSecretStore{Vault: envOr("OP_VAULT", "AgentSecrets")}, } reg := registry.New() reg.Register(localtools.NewMintServiceAccountToken(b)) if err := mcp.ServeStdio(context.Background(), reg, os.Stdin, os.Stdout); err != nil { logger.Error("serve stdio", "err", err) os.Exit(1) } }