# Contributing to gitlab-mcp-server Thank you for your interest in contributing to gitlab-mcp-server! This guide covers the process for submitting changes, reporting issues, and following project conventions. By participating, you agree to abide by the [Code of Conduct](CODE_OF_CONDUCT.md). For security issues, please follow the [Security Policy](SECURITY.md) instead of opening a public issue. ## Table of Contents - [Getting Started](#getting-started) - [Development Setup](#development-setup) - [Branch Naming](#branch-naming) - [Commit Messages](#commit-messages) - [Pull Requests](#pull-requests) - [Code Standards](#code-standards) - [Testing](#testing) - [Documentation](#documentation) - [Issue Reporting](#issue-reporting) - [Labels](#labels) ## Getting Started 1. Clone the repository 2. Create a `.env` file with your GitLab credentials (see [Configuration](docs/reference/configuration.md)) 3. Run `make build` to verify the setup 4. Run `make test` to ensure all tests pass ## Development Setup ### Prerequisites - **Go 1.26+** — [Download](https://go.dev/dl/) - **Node.js 22+ with Corepack** — required for the documentation site and MCP Inspector. The site declares `pnpm@11.8.0`; pnpm settings live in `site/pnpm-workspace.yaml`. - **Git** — configured with push access - **GitLab instance** — with a Personal Access Token (`api` scope) ### Build and Test ```bash # Build make build # Run all tests make test # Run tests with race detector make test-race # Run end-to-end tests (requires .env with real GitLab credentials) make test-e2e # Run end-to-end tests in Docker mode (ephemeral GitLab CE, ~4 GB RAM) make test-e2e-docker # Check test coverage make coverage # Lint make lint # Launch MCP Inspector (interactive tool testing UI) make inspector # Stop MCP Inspector make inspector-stop ``` ## Branch Naming Use the following naming convention for branches: | Prefix | Purpose | Example | | ----------- | ----------------------- | ----------------------------- | | `feature/` | New functionality | `feature/gitlab-wiki-tools` | | `fix/` | Bug fixes | `fix/pagination-off-by-one` | | `docs/` | Documentation only | `docs/add-wiki-reference` | | `test/` | Test additions | `test/increase-mr-coverage` | | `refactor/` | Code restructuring | `refactor/extract-pagination` | | `chore/` | Build, CI, dependencies | `chore/upgrade-go-sdk` | ## Commit Messages Follow [Conventional Commits](https://www.conventionalcommits.org/): ```text (): [optional body] [optional footer(s)] ``` ### Types | Type | Description | | ---------- | ------------------------------------------------------- | | `feat` | New feature or tool | | `fix` | Bug fix | | `docs` | Documentation changes | | `test` | Adding or updating tests | | `refactor` | Code change that neither fixes a bug nor adds a feature | | `chore` | Build process, CI, dependency updates | | `perf` | Performance improvement | | `style` | Code formatting (no logic change) | ### Scopes Use the package name as scope when applicable: ```text feat(tools): add gitlab_wiki_page_create tool fix(config): handle empty GITLAB_URL gracefully test(branches): increase coverage to 90% docs(readme): update tool count after wiki tools ``` ## Pull Requests ### Before Submitting - [ ] Code compiles: `go build ./...` - [ ] All tests pass: `go test ./... -count=1` - [ ] Static analysis is clean: `make analyze` (run `make analyze-fix` first to apply supported Go and Markdown fixes) - [ ] New tools have tests with >80% coverage - [ ] Documentation is updated if public API changed - [ ] Commit messages follow conventional commits ### PR Process 1. Create a feature branch from `main` 2. Make your changes in small, focused commits 3. Push the branch and open a pull request — reviewers are auto-requested via [CODEOWNERS](CODEOWNERS) 4. Fill in the PR template (auto-populated from `.github/pull_request_template.md`) 5. Address review feedback 6. Squash-merge once approved (the only allowed merge strategy) ### PR Size Guidelines - **Small** (<200 lines): Preferred — faster review, fewer conflicts - **Medium** (200–500 lines): Acceptable for feature additions - **Large** (>500 lines): Split into smaller PRs when possible ## Code Standards ### Go Conventions - Follow idiomatic Go and the repository's consolidated `golangci-lint` configuration (`goimports`, `gofumpt`, `gci`, `govet`, `staticcheck`, `gosec`, and related checks) - All exported types and functions must have doc comments - Error wrapping with `fmt.Errorf("context: %w", err)` - Use `context.Context` consistently for cancellation/timeouts - Table-driven tests with `t.Run()` subtests ### MCP Tool Patterns - Each GitLab operation = one canonical action with typed input/output structs - Use `jsonschema` struct tags for tool input documentation - Register runtime surfaces from the canonical action catalog - Set appropriate annotations (readOnlyHint, destructiveHint, etc.) - Return both structured JSON and human-readable Markdown ### Setup Wizard Internals The setup wizard lives in [`internal/wizard/`](internal/wizard/). When changing wizard behavior, follow the package layout and the `run` orchestration: | File | Purpose | | ------------------------- | ------------------------------------------------------------------------------------------------------------------ | | `wizard.go` | `Result` struct, `Apply()`, summary printing, `MaskToken()` | | `run.go` | `Run()` entry point and `runCascade` (web → tui → cli) auto-mode | | `webui.go` | Web UI HTTP server, `/api/defaults`, `/api/configure`, `/api/pick-directory` handlers, `configureRequest` shape | | `tui.go` | Bubble Tea model, steps (`tuiStepInstall`, `tuiStepGitLab`, `tuiStepOptions`, `tuiStepClients`), option rows | | `cli.go` | Plain prompt flow (`stepInstall`, `stepGitLabConfig`, `stepOptions`, `stepClients`) | | `prompt.go` | `Prompter` (AskString / AskYesNo / AskChoice / AskMultiChoice) shared by CLI and tests | | `envfile.go` | `LoadExistingConfig`, `WriteEnvFile` — `~/.gitlab-mcp-server.env` round-trip | | `clients.go` | `AllClients`, `ClientInfo`, `ServerConfig`, `DefaultServerConfig`, `GenerateEntry`, `RootKey`, `RestartHint` | | `paths.go` | OS-specific default install dir, env file path, and per-client JSON config paths (`vsCodeConfigPath`, etc.) | | `jsonmerge.go` | `MergeServerEntry` and JSONC-aware `readJSONFile` (handles VS Code's `mcp.json` comments) | | `install.go` | `InstallBinary` and `getInstalledVersion` | | `browser.go` | `openBrowser` (xdg-open / open / rundll32) and `hasDisplay` (DISPLAY / WAYLAND_DISPLAY) | | `dirpicker.go` | `pickDirectory` (PowerShell / osascript / zenity / kdialog) for the Web UI's "Browse" button | | `webui_assets/index.html` | Embedded browser wizard shell served at `/` — paired 1:1 with the `defaultsResponse` / `configureRequest` Go types | | `doc.go` | Package doc comment (rendered as godoc and linked from `internal/wizard/`) | When adding a new wizard option (env var, flag, advanced setting): 1. Add the field to `ServerConfig` in `clients.go`, including a `firstNonEmpty` line in `withDefaults()` if it has a default. 2. Add the field to `envFileEntries` in `envfile.go` (the writer) and to `loadExistingConfigFromPath` (the reader) so existing env files round-trip cleanly. 3. Wire it into the three UIs: `tui.go` `optionRows()`, `webui.go` `defaultsResponse` and `configureRequest` plus `index.html` form field, and `cli.go` `stepOptions()` prompt. 4. Cover it with a table-driven test in `tui_test.go`, `webui_test.go`, and `cli_test.go` if the behavior is non-trivial. 5. Run `go test ./internal/wizard/...` and the audit commands documented in [AGENTS.md](AGENTS.md) (`make audit-docs`). ### File Organization ```text internal/tools/ ├── register.go # RegisterAll() — projects individual tools from the canonical catalog ├── register_meta.go # RegisterAllMeta() — meta-tool registration ├── metatool.go # Meta-tool registration infrastructure ├── pagination.go # Pagination type aliases ├── errors.go # Error helpers (bridge to toolutil) ├── markdown.go # Markdown formatting (bridge to toolutil) ├── logging.go # Tool call logging (bridge to toolutil) └── / # 176 domain sub-packages ├── action_specs.go # Canonical ActionSpecs for catalog-backed tool surfaces ├── .go # Typed input/output structs + handlers ├── _test.go # Table-driven unit tests └── markdown.go # Markdown formatters (self-registered via init()) ``` ## Testing ### Requirements - **Unit tests** for every tool handler — use `httptest` to mock GitLab API responses - **Table-driven tests** with `t.Run()` subtests - **Test naming**: `TestToolName_Scenario_ExpectedResult` - **Coverage target**: >80% on tool handlers - **No external dependencies**: Unit tests must not call real GitLab APIs ### Running Tests ```bash # All unit tests go test ./... -count=1 # Specific package go test ./internal/tools/... -count=1 -v # With coverage go test ./internal/tools/... -coverprofile=cover.out go tool cover -func=cover.out # E2E tests (requires real GitLab) go test -tags e2e -timeout 300s ./test/e2e/suite/ ``` ## Documentation ### AI-Assisted Development This project ships with **7 AI agents** and **18 skills** for GitHub Copilot and compatible assistants. Key workflows for contributors: - **Adding new tools**: Use the `create-mcp-tool` skill — it scaffolds the full tool lifecycle (struct, handler, registration, tests, docs). - **Improving test coverage**: Use the `increase-test-coverage` skill to identify gaps and reach the 80% coverage target. - **Code quality reviews**: Use the `review-and-refactor` skill for code quality + OWASP security + MCP pattern checks. See [AGENTS.md](AGENTS.md) for the complete catalog of agents, skills, and instruction files. ### Snapshot Testing (Golden Files) Tool definitions are snapshot-tested to detect unintentional changes. Golden files live in `internal/tools/testdata/`: - `tools_individual.json` — all individual tool definitions - `tools_meta.json` — all meta-tool definitions When you intentionally change a tool definition (name, description, schema, annotations), update the golden files: ```bash UPDATE_TOOLSNAPS=true go test ./internal/tools/ -run TestToolSnapshots -count=1 ``` Then commit the updated golden files alongside your code changes. The CI will fail if snapshots are out of date. ### When to Update - Adding a new tool → update the relevant `docs/tools/.md` and `docs/reference/tools/README.md` - Adding a new meta-tool action → update `docs/concepts/meta-tools.md` - Adding a new resource → update `docs/reference/resources.md` - Adding a new prompt → update `docs/reference/prompts.md` - Adding a new capability → update `docs/reference/capabilities/README.md` - Changing configuration → update `docs/reference/configuration.md` - Changing the setup wizard (new option, new client, new env var) → update `docs/reference/configuration.md#setup-wizard-recommended` and `site/src/content/docs/setup-wizard.mdx` (EN+ES) - Adding or modifying tests → update `docs/development/testing/testing.md` with new test counts and coverage values ### Language Policy All project artifacts must be written in **English**: - Source code, comments, doc comments - Commit messages, branch names - Documentation, ADRs, specs - MCP tool names, descriptions, error messages - Test names and assertions ## Release Process When creating a new release and uploading binaries to GitHub Releases: 1. Build cross-platform binaries with `make release` (uses GoReleaser locally, flattens `dist/` to match GitHub Release asset names) 2. Create a GitHub release with the new tag and upload the binaries + checksum ## Issue Reporting Open an issue at and pick a template: - **Bug Report** — reproducible defects - **Feature Request** — new functionality / new MCP tool - **Enhancement** — improvement to existing behavior - **Documentation** — missing, outdated or incorrect docs For **security issues**, do not open a public issue — report privately via [GitHub Security Advisories](https://github.com/jmrplens/gitlab-mcp-server/security/advisories/new) (see [SECURITY.md](SECURITY.md)). Templates auto-apply the relevant labels listed in [Labels](#labels). ## Labels Issue templates auto-assign labels on submission. The repo uses a flat label set (no `type::`/`priority::` namespaces — those are GitLab conventions): | Label | Color | Used by | | ------------------ | --------- | ------------------------------------------------ | | `bug` | `#d73a4a` | Bug Report template | | `feature` | `#a2eeef` | Feature Request template | | `enhancement` | `#a2eeef` | Enhancement template (GitHub default) | | `documentation` | `#0075ca` | Documentation template (GitHub default) | | `security` | `#d73a4a` | Manual — applied to GitHub Security Advisories | | `high-priority` | `#b60205` | Manual — critical bugs and security advisories | | `needs-triage` | `#c2e0c6` | All issue templates (auto-applied on submission) | | `good first issue` | `#7057ff` | Manual — newcomer-friendly issues | | `help wanted` | `#008672` | Manual — community contributions welcome | | `question` | `#d876e3` | Manual — questions / discussions | | `duplicate` | `#cfd3d7` | Manual — duplicates of existing issues | | `invalid` | `#e4e669` | Manual — out of scope | | `wontfix` | `#ffffff` | Manual — accepted but won't implement | Manage labels with `gh label list` / `gh label create`.