This file follows the agents.md convention for AI agent instructions.
If you are using AI assistance to contribute to this repository, you must disclose it in the pull request or issue body.
Include a brief statement like:
- "This PR was written with assistance from [agent name]."
- "I used [tool] to help generate this code."
This helps maintainers understand the context and apply appropriate review scrutiny.
This is the GitHub MCP Server, a Model Context Protocol (MCP) server that connects AI tools to GitHub's platform. It enables AI agents to manage repositories, issues, pull requests, workflows, and more through natural language.
Key Details:
- Language: Go 1.24+ (~38k lines of code)
- Type: MCP server application with CLI interface
- Primary Package: github-mcp-server (stdio MCP server - this is the main focus)
- Secondary Package: mcpcurl (testing utility - don't break it, but not the priority)
- Framework: Uses modelcontextprotocol/go-sdk for MCP protocol, google/go-github for GitHub API
- Size: ~60MB repository, 70 Go files
- Library Usage: This repository is also used as a library by the remote server. Functions that could be called by other repositories should be exported (capitalized), even if not required internally. Preserve existing export patterns.
Code Quality Standards:
- Popular Open Source Repository - High bar for code quality and clarity
- Comprehension First - Code must be clear to a wide audience
- Clean Commits - Atomic, focused changes with clear messages
- Structure - Always maintain or improve, never degrade
- Code over Comments - Prefer self-documenting code; comment only when necessary
ALWAYS run these commands in this exact order before using report_progress or finishing work:
- Format Code:
script/lint(runsgofmt -s -w .thengolangci-lint) - Run Tests:
script/test(runsgo test -race ./...) - Update Documentation:
script/generate-docs(if you modified MCP tools/toolsets)
These commands are FAST: Lint ~1s, Tests ~1s (cached), Build ~1s
If you change any MCP tool definitions or schemas:
- Run tests with
UPDATE_TOOLSNAPS=true go test ./...to update toolsnaps - Commit the updated
.snapfiles inpkg/github/__toolsnaps__/ - Run
script/generate-docsto update README.md - Toolsnaps document API surface and ensure changes are intentional
# Download dependencies (rarely needed - usually cached)
go mod download
# Build the server binary
go build -v ./cmd/github-mcp-server
# Run the server
./github-mcp-server stdio
# Run specific package tests
go test ./pkg/github -v
# Run specific test
go test ./pkg/github -run TestGetMeUse the MCP Inspector CLI to test the server:
# Build the server first
go build -v ./cmd/github-mcp-server
# List available tools
npx @modelcontextprotocol/inspector@0.7.0 --cli \
--config '{"mcpServers":{"github":{"command":"./github-mcp-server","args":["stdio"],"env":{"GITHUB_PERSONAL_ACCESS_TOKEN":"your-token"}}}}' \
--server github \
--method tools/list
# Call a specific tool (e.g., get authenticated user)
npx @modelcontextprotocol/inspector@0.7.0 --cli \
--config '{"mcpServers":{"github":{"command":"./github-mcp-server","args":["stdio"],"env":{"GITHUB_PERSONAL_ACCESS_TOKEN":"your-token"}}}}' \
--server github \
--method tools/call --tool-name get_meNote: Replace your-token with a valid GitHub Personal Access Token.
.
├── cmd/
│ ├── github-mcp-server/ # Main MCP server entry point (PRIMARY FOCUS)
│ └── mcpcurl/ # MCP testing utility (secondary - don't break it)
├── pkg/ # Public API packages
│ ├── github/ # GitHub API MCP tools implementation
│ │ └── __toolsnaps__/ # Tool schema snapshots (*.snap files)
│ ├── toolsets/ # Toolset configuration & management
│ ├── errors/ # Error handling utilities
│ ├── sanitize/ # HTML/content sanitization
│ ├── log/ # Logging utilities
│ ├── raw/ # Raw data handling
│ ├── buffer/ # Buffer utilities
│ └── translations/ # i18n translation support
├── internal/ # Internal implementation packages
│ ├── ghmcp/ # GitHub MCP server core logic
│ ├── githubv4mock/ # GraphQL API mocking for tests
│ ├── toolsnaps/ # Toolsnap validation system
│ └── profiler/ # Performance profiling
├── e2e/ # End-to-end tests (require GitHub PAT)
├── script/ # Build and maintenance scripts
├── docs/ # Documentation
├── .github/workflows/ # CI/CD workflows
└── [config files] # See below
- go.mod / go.sum: Go module dependencies (Go 1.24.0+)
- .golangci.yml: Linter configuration (v2 format, ~15 linters enabled)
- Dockerfile: Multi-stage build (golang:1.25.3-alpine → distroless)
- server.json: MCP server metadata for registry
- .goreleaser.yaml: Release automation config
- .gitignore: Excludes bin/, dist/, vendor/, *.DS_Store, github-mcp-server binary
- script/lint - Runs
gofmt+golangci-lint. MUST RUN before committing - script/test - Runs
go test -race ./...(full test suite) - script/generate-docs - Updates README.md tool documentation. Run after tool changes
- script/licenses - Updates third-party license files when dependencies change
- script/licenses-check - Validates license compliance (runs in CI)
- script/get-me - Quick test script for get_me tool
- script/get-discussions - Quick test for discussions
- script/tag-release - NEVER USE THIS - releases are managed separately
All workflows run on push/PR unless noted. Located in .github/workflows/:
- go.yml - Build and test on ubuntu/windows/macos. Runs
script/testand builds binary - lint.yml - Runs golangci-lint-action v2.5 (GitHub Action) with actions/setup-go stable
- docs-check.yml - Verifies README.md is up-to-date by running generate-docs and checking git diff
- code-scanning.yml - CodeQL security analysis for Go and GitHub Actions
- license-check.yml - Runs
script/licenses-checkto validate compliance - docker-publish.yml - Publishes container image to ghcr.io
- goreleaser.yml - Creates releases (main branch only)
- registry-releaser.yml - Updates MCP registry
All of these must pass for PR merge. If docs-check fails, run script/generate-docs and commit changes.
- Use
testifyfor assertions (requirefor critical checks,assertfor non-blocking) - Tests are in
*_test.gofiles alongside implementation (internal tests, not_testpackage) - Mock GitHub API with
go-github-mock(REST) orgithubv4mock(GraphQL) - Test structure for tools:
- Test tool snapshot
- Verify critical schema properties (e.g., ReadOnly annotation)
- Table-driven behavioral tests
- Every MCP tool has a JSON schema snapshot in
pkg/github/__toolsnaps__/*.snap - Tests fail if current schema differs from snapshot (shows diff)
- To update after intentional changes:
UPDATE_TOOLSNAPS=true go test ./... - MUST commit updated .snap files - they document API changes
- Missing snapshots cause CI failure
- Located in
e2e/directory withe2e_test.go - Require GitHub PAT token - you usually cannot run these yourself
- Run with:
GITHUB_MCP_SERVER_E2E_TOKEN=<token> go test -v --tags e2e ./e2e - Tests interact with live GitHub API via Docker container
- Keep e2e tests updated when changing MCP tools
- Use only the e2e test style when modifying tests in this directory
- For debugging:
GITHUB_MCP_SERVER_E2E_DEBUG=trueruns in-process (no Docker)
- gofmt with simplify flag (-s) - Automatically run by
script/lint - golangci-lint with these linters enabled:
- bodyclose, gocritic, gosec, makezero, misspell, nakedret, revive
- errcheck, staticcheck, govet, ineffassign, unused
- Exclusions for: third_party/, builtin/, examples/, generated code
- Acronyms in identifiers: Use
IDnotId,APInotApi,URLnotUrl,HTTPnotHttp - Examples:
userID,getAPI,parseURL,HTTPClient - This applies to variable names, function names, struct fields, etc.
- Keep changes minimal and focused on the specific issue being addressed
- Prefer clarity over cleverness - code must be understandable by a wide audience
- Atomic commits - each commit should be a complete, logical change
- Maintain or improve structure - never degrade code organization
- Use table-driven tests for behavioral testing
- Comment sparingly - code should be self-documenting
- Follow standard Go conventions (Effective Go, Go proverbs)
- Test changes thoroughly before committing
- Export functions (capitalize) if they could be used by other repos as a library
- Add tool implementation in
pkg/github/(e.g.,foo_tools.go) - Register tool in appropriate toolset in
pkg/github/orpkg/toolsets/ - Write unit tests following the tool test pattern
- Run
UPDATE_TOOLSNAPS=true go test ./...to create snapshot - Run
script/generate-docsto update README - Run
script/lintandscript/testbefore committing - If e2e tests are relevant, update
e2e/e2e_test.gousing existing test style - Commit code + snapshots + README changes together
- Write a failing test that reproduces the bug
- Fix the bug with minimal changes
- Verify test passes and existing tests still pass
- Run
script/lintandscript/test - If tool schema changed, update toolsnaps (see above)
- Update
go.mod(e.g.,go get -u ./...or manually) - Run
go mod tidy - Run
script/licensesto update license files - Run
script/testto verify nothing broke - Commit go.mod, go.sum, and third-party-licenses* files
Fix: Run script/generate-docs and commit README.md changes
Fix: Run UPDATE_TOOLSNAPS=true go test ./... and commit updated .snap files
Fix: Run script/lint locally - it will auto-format and show issues. Fix manually reported issues.
Fix: Run script/licenses to regenerate license files after dependency changes
Likely causes:
- Forgot to update toolsnaps - run with
UPDATE_TOOLSNAPS=true - Changed behavior broke existing tests - verify intent and fix tests
- Schema change not reflected in test - update test expectations
- GITHUB_PERSONAL_ACCESS_TOKEN - Required for server operation and e2e tests
- GITHUB_HOST - For GitHub Enterprise Server (prefix with
https://) - GITHUB_TOOLSETS - Comma-separated toolset list (overrides --toolsets flag)
- GITHUB_READ_ONLY - Set to "1" for read-only mode
- GITHUB_DYNAMIC_TOOLSETS - Set to "1" for dynamic toolset discovery
- UPDATE_TOOLSNAPS - Set to "true" when running tests to update snapshots
- GITHUB_MCP_SERVER_E2E_TOKEN - Token for e2e tests
- GITHUB_MCP_SERVER_E2E_DEBUG - Set to "true" for in-process e2e debugging
.dockerignore - Docker build exclusions
.gitignore - Git exclusions (includes bin/, dist/, vendor/, binaries)
.golangci.yml - Linter configuration
.goreleaser.yaml - Release automation
CODE_OF_CONDUCT.md - Community guidelines
CONTRIBUTING.md - Contribution guide (fork, clone, test, lint workflow)
Dockerfile - Multi-stage Go build
LICENSE - MIT license
README.md - Main documentation (auto-generated sections)
SECURITY.md - Security policy
SUPPORT.md - Support resources
gemini-extension.json - Gemini CLI configuration
go.mod / go.sum - Go dependencies
server.json - MCP server registry metadata
cmd/github-mcp-server/main.go - Uses cobra for CLI, viper for config, supports:
stdiocommand (default) - MCP stdio transportgenerate-docscommand - Documentation generation- Flags: --toolsets, --read-only, --dynamic-toolsets, --gh-host, --log-file
- PRIMARY FOCUS: The local stdio MCP server (github-mcp-server) - this is what you should work on and test with
- REMOTE SERVER: Ignore remote server instructions when making code changes (unless specifically asked). This repo is used as a library by the remote server, so keep functions exported (capitalized) if they could be called by other repos, even if not needed internally.
- ALWAYS trust these instructions first - only search if information is incomplete or incorrect
- NEVER use
script/tag-releaseor push tags - NEVER skip
script/lintbefore committing Go code changes - ALWAYS update toolsnaps when changing MCP tool schemas
- ALWAYS run
script/generate-docsafter modifying tools - For specific test files, use
go test ./path -run TestNamenot full suite - E2E tests require PAT token - you likely cannot run them
- Toolsnaps are API documentation - treat changes seriously
- Build/test/lint are very fast (~1s each) - run frequently
- CI failures for docs-check or license-check have simple fixes (run the script)
- mcpcurl is secondary - don't break it, but it's not the priority